本文github仓库地址: https://github.com/Rynxiao/webpack-tutorial ,里面包括了本教程的所有代码。

【如果你觉得这篇文章写得不错,麻烦给本仓库一颗星:-D】

1. 导语

1.1 什么叫做webpack

webpack is a module bundler.
webpack takes modules with dependencies and generates static assets representing those modules.

简单的概括就是:webpack是一个模块打包工具,处理模块之间的依赖同时生成对应模块的静态资源。

1.2 webpack可以做一些什么事情

Android培训,安卓培训,手机开发培训,移动开发培训,云培训培训

图中已经很清楚的反应了几个信息:

  • webpack把项目中所有的静态文件都看作一个模块

  • 模快之间存在着一些列的依赖

  • 多页面的静态资源生成(打包之后生成多个静态文件,涉及到代码拆分)

2. webpack安装

  • 全局安装(供全局调用:如webpack --config webpack.config.js)

    npm install -g webpack
  • 项目安装
    ```javascript
    npm install webpack

// 处理类似如下调用
import webpack from "webpack";
var webpack = require("webpack");
```

建议安装淘宝的npm镜像,这样下载npm包会快上很多,具体做法:

// 方式一npm install xx --registry=https://registry.npm.taobao.org/// 方式二:安装淘宝提供的npm工具npm install -g cnpm
cnpm install xx// 方式三// 在用户主目录下,找到.npmrc文件,加上下面这段配置registry=https://registry.npm.taobao.org/

3. webpack的基本配置

创建配置文件(webpack.config.js,执行webpack命令的时候,默认会执行这个文件)

module.export = {
    entry : 'app.js',
    output : {
        path : 'assets/',
        filename : '[name].bundle.js'
    },
    module : {
        loaders : [            // 使用babel-loader解析js或者jsx模块
            { test : /\.js|\.jsx$/, loader : 'babel' },
            // 使用css-loader解析css模块
            { test : /\.css$/, loader : 'style!css' },
            // or another way
            { test : /\.css$/, loader : ['style', 'css'] }
        ]    }};

说明一: webpack.config.js默认输出一个webpack的配置文件,与CLI方式调用相同,只是更加简便
说明二: 执行webpack命令即可以运行配置,先决条件,全局安装webpack,项目安装各模块loader
说明三: entry对应需要打包的入口js文件,output对应输出的目录以及文件名,module中的loaders对应解析各个模块时需要的加载器

一个简单的例子

basic/app.js

require('./app.css');document.getElementById('container').textContent = 'APP';

basic/app.css

* {
    margin: 0;
    padding: 0;}#container {
    margin: 50px auto;
    width: 50%;
    height: 200px;
    line-height: 200px;
    border-radius: 5px;
    box-shadow: 0 0 .5em #000;
    text-align: center;
    font-size: 40px;
    font-weight: bold;}

basic/webpack.config.js

/** * webpack打包配置文件 */module.exports = {
    // 如果你有多个入口js,需要打包在一个文件中,那么你可以这么写 
    // entry : ['./app1.js', './app2.js']
    entry : './app.js',
    output : {
        path : './assets/',
        filename : '[name].bundle.js'
    },
    module : {
        loaders : [            { test : /\.js$/, loader : 'babel' },
            { test : /\.css$/, loader : 'style!css' }
        ]    }};

basic/index.html

<!DOCTYPE html><html lang="en"><head>
    <meta charset="UTF-8">
    <title>basic webpack</title></head><body>
    <div id="container"></div>
    <script src="./assets/main.bundle.js"></script></body></html>

basic文件夹执行webpack,打包信息如下

Android培训,安卓培训,手机开发培训,移动开发培训,云培训培训

生成main.bundle.js文件,chunk名称为main,也是webpack默认生成的chunk

## 4. webapck常用到的各点拆分

### 4.1 entry相关

4.1.1webpack的多入口配置

上例的简单配置中,只有一个入口文件,那么如果对应于一个页面需要加载多个打包文件或者多个页面想同时引入对应的打包文件的时候,应该怎么做?

entry : {
    app1 : './app1.js',
    app2 : './app2.js'}

multi-entry文件夹执行webpack,打包信息如下

Android培训,安卓培训,手机开发培训,移动开发培训,云培训培训

可见生成了两个入口文件,以及各自对应的chunk


### 4.2 output相关

4.2.1 output.publicPath

output: {
    path: "/home/proj/cdn/assets/[hash]",
    publicPath: "http://cdn.example.com/assets/[hash]/"}

引用一段官网的话:

The publicPath specifies the public URL address of the output files when referenced in a browser. For loaders that embed <script> or <link>tags or reference assets like images, publicPath is used as the href or url() to the file when it’s different then their location on disk (as specified by path).

大致意思就是:publicPath指定了你在浏览器中用什么地址来引用你的静态文件,它会包括你的图片、脚本以及样式加载的地址,一般用于线上发布以及CDN部署的时候使用。

比如有下面一段配置:

var path = require('path');var HtmlWebpackPlugin =  require('html-webpack-plugin');module.exports = {
    entry : './app.js',
    output : {
        path : './assets/',
        filename : '[name].bundle.js',
        publicPath : 'http://rynxiao.com/assets/'
    },
    module : {
        loaders : [            { test : /\.js$/, loader : 'babel' },
            { test : /\.css$/, loader : 'style!css' }
        ]    },
    plugins : [        new HtmlWebpackPlugin({
            filename: './index-release.html',
            template: path.resolve('index.template'),
            inject: 'body'
        })
    ]};

其中我将publicPath设置成了http://rynxiao.com/assets/,其中设置到了插件的一些东西,这点下面会讲到,总之这个插件的作用是生成了上线发布时候的首页文件,其中script中引用的路径将会被替换。如下图:

Android培训,安卓培训,手机开发培训,移动开发培训,云培训培训


4.2.2 output.chunkFilename

各个文件除了主模块以外,还可能生成许多额外附加的块,比如在模块中采用代码分割就会出现这样的情况。其中chunkFilename中包含以下的文件生成规则:

[id] 会被对应块的id替换.

[name] 会被对应块的name替换(或者被id替换,如果这个块没有name).

[hash] 会被文件hash替换.

[chunkhash] 会被块文件hash替换.

例如,我在output中如下设置:

output : {
    path : './assets/',
    filename : '[name].[hash].bundle.js',
    chunkFilename: "chunk/[chunkhash].chunk.js"}

同时我修改了一下basic/app.js中的文件

require('./app.css');require.ensure('./main.js', function(require) {
    require('./chunk.js');});document.getElementById("container").textContent = "APP";

其中对应的chunk.js就会生成带有chunkhashchunk文件,如下图:

Android培训,安卓培训,手机开发培训,移动开发培训,云培训培训

这在做给文件打版本号的时候特别有用,当时如何进行hash替换,下面会讲到


4.2.3 output.library

这个配置作为库发布的时候会用到,配置的名字即为库的名字,通常可以搭配libraryTarget进行使用。例如我给basic/webpack.config.js加上这样的配置:

output : {
    // ...
    library : 'testLibrary'
    // ...}

那么实际上生成出来的main.bundle.js中会默认带上以下代码:

var testLibrary = (//....以前的打包生成的代码);// 这样在直接引入这个库的时候,就可以直接使用`testLibrary`这个变量

Android培训,安卓培训,手机开发培训,移动开发培训,云培训培训


4.2.4 output.libraryTarget

规定了以哪一种方式输出你的库,比如:amd/cmd/或者直接变量,具体包括如下

"var" - 以直接变量输出(默认library方式) var Library = xxx (default)

"this" - 通过设置this的属性输出 this["Library"] = xxx

"commonjs" - 通过设置exports的属性输出 exports["Library"] = xxx

"commonjs2" - 通过设置module.exports的属性输出 module.exports = xxx

"amd" - 以amd方式输出

"umd" - 结合commonjs2/amd/root

例如我以umd方式输出,如图:

Android培训,安卓培训,手机开发培训,移动开发培训,云培训培训


### 4.3 module相关

4.3.1 loader!代表的含义

require("!style!css!less!bootstrap/less/bootstrap.less");
// => the file "bootstrap.less" in the folder "less" in the "bootstrap"
// module (that is installed from github to "node_modules") is
// transformed by the "less-loader". The result is transformed by the
// "css-loader" and then by the "style-loader".
// If configuration has some transforms bound to the file, they will not be applied.

代表加载器的流式调用,例如:

{ test : /\.css|\.less$/, loader : 'style!css!less' }

就代表了先使用less加载器来解释less文件,然后使用css加载器来解析less解析后的文件,依次类推


4.3.2 loaders中的includeexclude

include表示必须要包含的文件或者目录,而exclude的表示需要排除的目录

比如我们在配置中一般要排除node_modules目录,就可以这样写

{ 
    test : /\.js$/, 
    loader : 'babel',
    exclude : nodeModuleDir 
}

官方建议:优先采用include,并且include最好是文件目录


4.3.3 module.noParse

使用了noParse的模块将不会被loaders解析,所以当我们使用的库如果太大,并且其中不包含requiredefine或者类似的关键字的时候(因为这些模块加载并不会被解析,所以就会报错),我们就可以使用这项配置来提升性能。

例如下面的例子:在basic/目录中新增no-parse.js

var cheerio = require('cheerio');module.exports = function() {
    console.log(cheerio);}

webpack.config.js中新增如下配置:

module : {
    loaders : [        { test : /\.js$/, loader : 'babel' },
        { test : /\.css$/, loader : 'style!css' }
    ],
    noParse : /no-parse.js/}

当执行打包后,在浏览器中打开index.html时,就会报错require is not defined

Android培训,安卓培训,手机开发培训,移动开发培训,云培训培训

4.4 resolve相关

4.4.1 resolve.alias

为模块设置别名,能够让开发者指定一些模块的引用路径。对一些经常要被import或者require的库,如react,我们最好可以直接指定它们的位置,这样webpack可以省下不少搜索硬盘的时间。
例如我们修改basic/app.js中的相关内容:

var moment = require("moment");document.getElementById("container").textContent = moment().locale('zh-cn').format('LLLL');

加载一个操作时间的类库,让它显示当前的时间。使用webpack --profile --colors --display-modules执行配置文件,得到如下结果:

Android培训,安卓培训,手机开发培训,移动开发培训,云培训培训

其中会发现,打包总共生成了104个隐藏文件,其中一半的时间都在处理关于moment类库相关的事情,比如寻找moment依赖的一些类库等等。

basic/webpack.config.js加入如下配置,然后执行配置文件

resolve : {
    alias : {
        moment : 'moment/min/moment-with-locales.min.js'
    }}

Android培训,安卓培训,手机开发培训,移动开发培训,云培训培训

有没有发现打包的时间已经被大大缩短,并且也只产生了两个隐藏文件。

配合module.noParse使用

module.noParse参看上面的解释

noParse: [/moment-with-locales/]

执行打包后,效果如下:

Android培训,安卓培训,手机开发培训,移动开发培训,云培训培训

是不是发现打包的时间进一步缩短了。

配合externals使用

externals参看下面的解释

Webpack 是如此的强大,用其打包的脚本可以运行在多种环境下,Web 环境只是其默认的一种,也是最常用的一种。考虑到 Web 上有很多的公用 CDN 服务,那么 怎么将 Webpack 和公用的 CDN 结合使用呢?方法是使用 externals 声明一个外部依赖。

externals: {
    moment: true}

当然了 HTML 代码里需要加上一行

<script src="//apps.bdimg.com/libs/moment/2.8.3/moment-with-locales.min.js"></script>

执行打包后,效果如下:

Android培训,安卓培训,手机开发培训,移动开发培训,云培训培训


4.4.2 resolve.extensions

resolve : {
    extensions: ["", ".webpack.js", ".web.js", ".js", ".less"]}

这项配置的作用是自动加上文件的扩展名,比如你有如下代码:

require('style.less');var app = require('./app.js');

那么加上这项配置之后,你可以写成:

require('style');var app = require('./app');

4.5 externals

当我们想在项目中require一些其他的类库或者API,而又不想让这些类库的源码被构建到运行时文件中,这在实际开发中很有必要。此时我们就可以通过配置externals参数来解决这个问题:

//webpack.config.jsmodule.exports = {
    externals: {
      'react': 'React'
    },
    //...}

externals对象的key是给require时用的,比如require('react'),对象的value表示的是如何在global(即window)中访问到该对象,这里是window.React。

同理jquery的话就可以这样写:'jquery': 'jQuery',那么require('jquery')即可。

HTML中注意引入顺序即可:

<script src="react.min.js" /><script src="bundle.js" />

4.6 devtool

提供了一些方式来使得代码调试更加方便,因为打包之后的代码是合并以后的代码,不利于排错和定位。其中有如下几种方式,参见官网devtool

例如,我在basic/app.js中增加如下配置:

require('./app.css');// 新增hello.js,显然在文件夹中是不会存在hello.js文件的,这里会报错require('./hello.js');document.getElementById("container").textContent = "APP";

执行文件,之后运行index.html,报错结果如下:

Android培训,安卓培训,手机开发培训,移动开发培训,云培训培训

给出的提示实在main.bundle.js第48行,点进去看其中的报错如下:

Android培训,安卓培训,手机开发培训,移动开发培训,云培训培训

从这里你完全看不出到底你程序的哪个地方出错了,并且这里的行数还算少,当一个文件出现了上千行的时候,你定位bug的时间将会更长。

增加devtool文件配置,如下:

module.exports = {
    devtool: 'eval-source-map',
    // ....};

执行文件,之后运行index.html,报错结果如下:

Android培训,安卓培训,手机开发培训,移动开发培训,云培训培训

这里发现直接定位到了app.js,并且报出了在第二行出错,点击去看其中的报错如下:

Android培训,安卓培训,手机开发培训,移动开发培训,云培训培训

发现问题定位一目了然。

5. webpack常用技巧

### 5.1 代码块划分

5.1.1 Commonjs采用require.ensure来产生chunk

require.ensure(dependencies, callback);//static importsimport _ from 'lodash'// dynamic importsrequire.ensure([], function(require) {
  let contacts = require('./contacts')})

这一点在output.chunkFileName中已经做过演示,可以去查看


5.1.2 AMD采用require来产生chunk

require(["module-a", "module-b"], function(a, b) {
    // ...});

5.1.3 将项目APP代码与公共库文件单独打包

我们在basic/app.js中添加如下代码

var $ = require('juqery'),
    _ = require('underscore');//.....

然后我们在配置文件中添加vendor,以及运用代码分离的插件对生成的vendor块重新命名

var webpack = require("webpack");module.exports = {
    entry: {
        app: "./app.js",
        vendor: ["jquery", "underscore", ...],
    },
    output: {
        filename: "bundle.js"
    },
    plugins: [        new webpack.optimize.CommonsChunkPlugin(/* chunkName= */"vendor", /* filename= */"vendor.bundle.js")
    ]};

运行配置文件,效果如下:

Android培训,安卓培训,手机开发培训,移动开发培训,云培训培训


5.1.4 抽取多入口文件的公共部分

我们重新建立一个文件夹叫做common,有如下文件:

// common/app1.jsconsole.log("APP1");
// common/app2.jsconsole.log("APP2");

打包之后生成的app1.bundle.jsapp2.bundle.js中会存在许多公共代码,我们可以将它提取出来。

// common/webpack.config.js/** * webpack打包配置文件 * 抽取公共部分js */var webpack = require('webpack');module.exports = {
   http://www.cnblogs.com/rynxiao/p/7149274.html