webpack

webpack学习

webpack 默认只能处理 JavaScript,如果你打算用它处理其它的东西,比如 html,css 等等,你需要安装对应的loader。webpack 里说的loader有点像是一种转换器,它可以把资源从一种形式转换成另一种形式,不过最终得到的还是 JavaScript。

项目结构

配置webpack.config.js

设置入口

配置哪些js需要处理,entry有三种写法,每个入口称为一个chunk。

  • 字符串:
    entry: "./index/index.js" :配置模块会被解析为模块,并在启动时加载。chunk名为默认为main, 具体打包文件名视output配置而定。

  • 数组
    entry: ['./src/mod1.js', [...,] './src/index.js'] :所有的模块会在启动时 按照配置顺序 加载,合并到最后一个模块会被导出。chunk名默认为main

  • 对象
    entry: {index: '...', login : [...] }:如果传入Object,则会生成多个入口打包文件, key是chunk名,value可以是字符串,也可是数组。

配置输出目录

  • output.path: 指定输出文件路径,通常设置为__dirname + ‘/build’,

  • output.filename: 输出文件名称,有下面列出的四种可选的变量。 filename项的配置可以是这几种的任意一种或多种的组合。 如 output.filename = ‘[name]-[id].js’, 则输出就是 index-1.js、 login-2.js。

    • [id], chunk的id

    • [name] ,chunk名

    • [hash], 编译哈希值

    • [chunkhash] , chunk的hash值

  • output.publicPath:设置为想要的资源访问路径。访问时,则需要通过类似 http://localhost:8080/asstes/...来访问资源,如果没有设置,则默认从站点根目录加载。

loader

loader是webpack中比较重要的部分,她是处理各类资源的执行者。它们是一系列的函数(运行在node.js中),将资源中的代码作为参数,然后返回新的代码。你可以用loader告诉webpack可以加载哪些文件,或者不加载哪些文件。

Loader的特点

  • 可以链式执行。它们在一个管道中被提交,只需要保证最后的loader返回JavaScript即可,其他loader可以返回任意方便下一个loader处理的内容。

  • 可以异步or同步执行

  • 运行在Node.js中,可以做几乎任何事儿

  • 可以接收query参数,用于向loader传递参数

  • 配置中可与正则/扩展结合使用

  • 可以在npm中发布并使用

  • 除了main,其他模块可以导出成loader

  • 可以通过配置调入

  • 和插件(plugins)配合可获得更多功能

  • 可生成其他格式文件

simple loader module

注意module加载先后顺序(style!css)

webpack.config.js配置loader
module.exports = {
    entry: "./entry.js",
    output: {
        path: __dirname,
        filename: "bundle.js"
    },
    module: {
        loaders: [
            { test: /\.jade$/, loader: "jade" },
            // => "jade" loader is used for ".jade" files
            { test: /\.css$/, loader: "style!css" },
            // => "style" and "css" loader is used for ".css" files
            // Alternative syntax:
            { test: /\.css$/, loaders: ["style", "css"] }
        ]
    }
};
cli(打包过程中指定loader)
$ webpack --module-bind jade --module-bind 'css=style!css'

babel

比如你要打包的项目里面用了一些 es2015或者 react 的 jsx,这些东西你可能需要使用 babel 去转换一下,然后再交给 webpack 去打包。想在 webpack 里使用 babel 需要一个babel-loader。

  1. 新建.babelrc文件

  2. 配置webpack.config.js

.babelrc
{
  "presets":["es2015"]
}
webpack.config.js
/* add loader */
{test:/\.js$/,loader:"babel"}

解析文件

/* 解析jade */
require('jade!./index.jade')

/* 解析css并内联,使用到分隔符 ! */
require(!style!css!./style.css)

/* 使用yaml加载数据 */
require('yaml!json!./cats.yaml')

/* 解析less,转换成css之后,再内联 */
require('!style!css!less!./style.less!') ; 
// 先调用less-loader解析style.less文件,输出结果会被css-loader处理, 然后再被style-loader处理

/* 解析JSX */
$ npm install react-hot-loader jsx-loader --save
 loaders: [
       {
           test: /\.js$/,
           exclude: /node_modules/,
           loader: 'react-hot!jsx-loader?harmony'
       }
   ]

解析并抽取css

在webpack中css默认方案是,将css编译并通过内联的方式在html页面中插入style样式标签。当然这远远不能满足我们的要求,webpack提供css-loader模块用于编译css文件,并且提供了插件extract-text-webpack-plugin将css从js代码中抽出并合并。这样你可以在模块中,尽情使用 require(style.css),webpack会帮你做解析,合并entry中定义js及其依赖中所用到的所有css,然后生成一个指定的css文件。

处理图片、字体等文件

在css中或者js逻辑中,都会涉及到require图片的情况,webpack可以内联图片地址到打包js中并且通过require()返回图片路径。当然,不只是图片,还有css中用到的iconfont,特殊情况用到的flash等,都可以相似处理。这里,我们需要用到url-loader 或 file-loader。

  • file-loader: 将匹配到的文件复制到输出文件夹,并根据output.publicPath的设置返回文件路径

  • url-loader: 类似file-loader,但是它可以返回一个DataUrl (base 64)如果文件小于设置的限制值limit。

借助web_modules引用外部库

有些时候,我们用到的第三方库并没有采用CommonJS或AMD规范,也没有提交到npm。这样的话,我们无法通过npm来下载,并通过require()来引用这些库。webpack给我们提供了一个很好的实现方式。我们可以在项目根目录下,创建一个叫做web_modules的文件夹,然后将需要用到的第三方库存放在此处。那么之后,不需要做任何设置,可以在我们的逻辑代码中使用require(
‘xx-lib.js’)并且使用了。

去除多个文件中的频繁依赖

当我们经常使用React、jQuery等外部第三方库的时候,通常在每个业务逻辑JS中都会遇到这些库。如我们需要在各个文件中都是有jQuery的$对象,因此我们需要在每个用到jQuery的JS文件的头部通过require('jquery')来依赖jQuery。 这样做非常繁琐且重复,因此webpack提供了我们一种比较高效的方法,我们可以通过在配置文件中配置使用到的变量名,那么webpack会自动分析,并且在编译时帮我们完成这些依赖的引入。

var webpack = require('webpack');
plugins: [
   new webpack.ProvidePlugin({
       'Moment': 'moment',
       "$": "jquery",
       "jQuery": "jquery",
       "window.jQuery": "jquery",
       "React": "react"
   })
]
...
/* 这样,我们在JS中,就不需要引入jQuery等常用模块了,直接使用配置的这些变量,webpack就会自动引入配置的库。 */

plugin

troubleshooting

# 查看更多信息
$ webpack --display-error-details
# 监听改变
$ webpack --watch --progress

合并公共代码

项目中,对于一些常用的组件,站点公用模块经常需要与其他逻辑分开,然后合并到同一个文件,以便于长时间的缓存。要实现这一功能,配置参照:

var webpack = require('webpack');
var CommonsChunkPlugin = webpack.optimize.CommonsChunkPlugin;
entry: {
   a: './index/a.js',
   b: './idnex/b.js',
   c: './index/c.js',
   d: './index/d.js'
},
plugins: [
   new CommonsChunkPlugin('part1.js', ['a', 'b']),
   new CommonsChunkPlugin('common.js', ['part1', 'c'])
]

调试

开发环境与发布环境配置

某些情况,我们需要在页面中输出开发调试内容,但是又不想让这些调试内容在发布的时候泄露出去,那么我们可以采用魔力变量(magic globals)来处理。

配置文件
var webpack = require('webpack');
var definePlugin = new webpack.DefinePlugin({
    __DEV__: JSON.stringify(JSON.parse(process.env.BUILD_DEV || 'false')),
    __PRERELEASE__: JSON.stringify(JSON.parse(process.env.BUILD_PRERELEASE || 'true'))
});
plugins: [
    definePlugin
]
业务逻辑代码中写入

按照下面的代码写入,我们就可以在我们自己设定的环境下进行更具针对性的调试。比如我们希望在开发环境下可以AJAX可以调试本地mock数据,然后在发布的时候,可以正常访问服务端数据。那么通过此种方式可以完全实现。

if (__DEV__) {
    console.warn('Extra logging');
    //开发环境需要进行的处理
    //...
}
if (__PRERELEASE__) {
    console.log('prerelease');
    //预发环境需要进行的处理
   //...
}
设置环境命令

要告诉webpack我们希望当前是什么环境,只需要在命令中写入 BUILD_DEV=1 webpck 那么webpack通过配置,就会将所有我们引用到的__DEV__变量设置为true。
我们可以在package.json中事先定义好命令:

"scripts": {
    "dev": "BUILD_DEV=1 webpack-dev-server --progress --colors",
    "build": "BUILD_PRERELEASE=1 webpack -p"
}
# 开发时输入
$ npm run dev
# 发布时输入
$ npm run build

webpack-dev-server

webpack-dev-server 这个东西可以给为我们生成一个开发用的服务器,在文件有变化的时候自动给我们打包,然后刷新页面。它还有个模块热替换的功能,就是它可以只替换有变化的地方,不需要刷新整个页面,这个功能有很多好处 。

# 安装
$ npm i -g webpack-dev-server
# 使用http://localhost:8080/地址访问
$ webpack-dev-server --progress --colors

devtool

文档

可以通过在配置中加入devtool项,选择预设调试工具来提高代码调试质量和效率:

常用配置文件

var webpack            = require('webpack');
var CommonsChunkPlugin = webpack.optimize.CommonsChunkPlugin;
var ExtractTextPlugin  = require('extract-text-webpack-plugin');
//自定义"魔力"变量
var definePlugin = new webpack.DefinePlugin({
    __DEV__: JSON.stringify(JSON.parse(process.env.BUILD_DEV || 'false')),
    __PRERELEASE__: JSON.stringify(JSON.parse(process.env.BUILD_PRERELEASE || 'false'))
});
module.exports = {
    //上下文
    context: __dirname + '/src',
    //配置入口
    entry: {
        a: './view/index/index.js',
        b: './view/index/b.js',
        vender: ['./view/index/c.js', './view/index/d.js']
    },
    //配置输出
    output: {
        path: __dirname + '/build/',
        filename: '[name].js?[hash]',
        publicPath: '/assets/',
        sourceMapFilename: '[file].map'
    },
    devtool: 'source-map',
    //模块
    module: {
        loaders: [
            {
                //处理javascript
                test: /\.js$/,
                exclude: /node_modules/,
                loader: 'babel'
            }, {
                test: /\.css$/,
                loader: ExtractTextPlugin.extract(
                    "style-loader",
                    "css-loader?sourceMap"
                )
            }, {
                test: /\.less$/,
                loader: ExtractTextPlugin.extract(
                    "style-loader",
                    "css-loader!less-loader"
                )
            }, {
                test: /\.(png|jpg)$/,
                loader: 'url-loader?limit=1024'
            }, {
                //处理vue
                test: /\.vue$/,
                loader: 'vue-loader'
            },
            {
                test: /\.woff(\?v=\d+\.\d+\.\d+)?$/,
                loader: 'url?limit=10000&minetype=application/font-woff'
            },
            {
                test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/,
                loader: 'url?limit=10&minetype=application/font-woff'
            },
            {
                test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/,
                loader: 'url?limit=10&minetype=application/octet-stream'
            },
            {
                test: /\.eot(\?v=\d+\.\d+\.\d+)?$/,
                loader: 'file'
            },
            {
                test: /\.svg(\?v=\d+\.\d+\.\d+)?$/,
                loader: 'url?limit=10&minetype=image/svg+xml'
            }
        ]

    },
    plugins: [
        //公用模块
        new CommonsChunkPlugin('common.js', ['a', 'b']),
        //设置抽出css文件名
        new ExtractTextPlugin("css/[name].css?[hash]-[chunkhash]-[contenthash]-[name]", {
            disable: false,
            allChunks: true
        }),
        //定义全局变量
        definePlugin,
        //设置此处,则在JS中不用类似require('./base')引入基础模块, 只要直接使用Base变量即可
        //此处通常可用做,对常用组件,库的提前设置
        new webpack.ProvidePlugin({
            Moment: 'moment', //直接从node_modules中获取
            Base: '../../base/index.js' //从文件中获取
        })
    ],
    //添加了此项,则表明从外部引入,内部不会打包合并进去
    externals: {
        jquery: 'window.jQuery',
        react: 'window.React',
        //...
    }
};

react-hot-loader

使用 react 编写代码时,能让修改的部分自动刷新。但这和自动刷新网页是不同的,因为 hot-loader 并不会刷新网页,而仅仅是替换你修改的部分,也就是上面所说的 without losing state 。

配置

注意publicPath属性

package.json

"scripts": {
    "start": "node server.js"
},

webpack.config.js

var path = require('path');
var webpack = require('webpack');

module.exports = {
    entry: [    
                'webpack-dev-server/client?http://localhost:3000',
                'webpack/hot/only-dev-server',
                './index'
            ],
    output: {
        path: path.join(__dirname, 'dist'),
        filename: 'bundle.js',
        publicPath: "/dist/"
    },
    module: {
        loaders: [
            {
                test: /\.js$/,
                loaders: ['react-hot', 'babel'],
                include: __dirname
            }
        ]
    },
    plugins: [
        new webpack.DefinePlugin({
            'process.env.NODE_ENV': '"development"'
        }),
        new webpack.HotModuleReplacementPlugin()
    ],
    devtool: 'eval'
}

server.js

var webpack = require('webpack');
var WebpackDevServer = require('webpack-dev-server');
var config = require('./webpack.config');

new WebpackDevServer(webpack(config), {
  publicPath: config.output.publicPath,
  hot: true,
  historyApiFallback: true
}).listen(3000, 'localhost', function (err, result) {
  if (err) {
    return console.log(err);
  }
  console.log('Listening at http://localhost:3000/');
});

luckyyulin
217 声望9 粉丝

有一种鸟没有脚,他的一生只能在天上飞来飞去,飞累了就在风里睡觉,一辈子只能落地一次,那就是他死的时候


« 上一篇
git 使用
下一篇 »
mobile调试方法