详细的Webpack配置教程

1 链接

个人博客: https://alex-my.xyz
CSDN: https://blog.csdn.net/alex_my
Keylala在线工具: https://www.keylala.cn
程序员导航: https://list.keylala.cn

2 说明

  • webpack是一个现在很流行的Javascript应用程序的模块打包器
  • 之前学习node.js的时候各种源码基本上都有这个,对于其的配置文件感觉就像天书,都是边抄边搜索学习,感觉好累
  • 经过一些学习之后,对各个配置就慢慢清晰了,于是就有了本文
  • 本文并不是一个配置大全,并不会把每个选项的每个配置都详细的讲解,本文只有涉及常用的加载器和一些插件。阅读过本文后基本上对webpack有一定的了解,再深入学习也容易的多
  • 如果需要更深入的学习,可以在本文最后的参考文献中挑选学习
  • 最最最后要说的是**亲自动手**

3 环境安装

  • 安装node.js

    • 强烈推荐使用nvm来安装和管理node.js
    • 关闭终端后再执行npm命令时发现npm找不到命令之类的问题,可以执行nvm use [version],比如nvm use 8.4.0
    • 也可以执行一次nvm alias default v8.4.0,设置默认的就是8.4.0
  • 安装国内的版本cnpm, 淘宝的镜像

    npm install -g cnpm
    
  • 创建一个文件夹, 比如learn-webpack做为工程目录, 并初始化工程

    cd learn-webpack
    cnpm init
    
    • 执行初始化命令的时候可以一路确定,使用默认值
    • 这样在根目录下会出现一个package.json,以后安装的各种包都会记录在上面
  • 本文使用的环境

    • macOS Sierra
    • node v8.4.0
    • webpack 3.5.6
    • IDE vscode

4 第一个简单的例子

  • 先安装webpack

    cnpm install webpack --save-dev
    
    • 在根目录下会生成一个node_modules的目录,同时webpack安装包信息也会被记录到package.json
  • 在根目录创建一个src文件夹, 用于存放源码, 并在内部创建2个文件index.js, hello.js

    mkdir src
    cd src
    touch index.js
    touch hello.js
    
  • 在根目录创建一个public目录, 并在内部创建1个文件index.html

    cd ..
    mkdir public
    touch index.html
    
  • 在根目录创建一个webpack的配置文件webpack.config.js

    cd ..
    touch webpack.config.js
    
  • 当前的目录结构如下

    • learn-webpack
      • node_modules
      • public
        • index.html
      • src
        • index.js
        • hello.js
      • package.json
      • webpack.config.js
  • index.html源码如下

    <!DOCTYPE html>
    <html lang="en">
    
    <head>
        <meta charset="utf-8">
        <title>learn webpack</title>
    </head>
    
    <body>
        <div id="root"></div>
        <script src="bundle.js"></script>
    </body>
    
    </html>
    
    • 这个文件的目的在于加载webpack打包出来的文件, 我们命名打包出来的这个文件为bundle.js
  • hello.js源码如下

    var helloFunc = function() {
        let helloElement = document.createElement('div');
        helloElement.textContent = 'Hello world';
        return helloElement;
    }
    
    module.exports = helloFunc;
    
  • index.js源码如下

    const hello = require('./hello');
    
    document.getElementById('root').appendChild(hello());
    
  • webpack.config.js配置如下

    const config = {
        entry: __dirname + '/src/index.js',
        output: {
            path: __dirname + '/public',
            filename: 'bundle.js'
        }
    }
    
    module.exports = config;
    
    • entry: 告诉webpack从这个指定的文件开始,将所牵涉到的文件统统打包起来
    • output: 告诉webpack如何去输出,去哪里输出打包好的文件
  • 通过命令行执行打包

    • 在根目录下执行node_modules/.bin/webpack命令, 就会进行打包,你会发现,在public目录下多了一个文件bundle.js

      // 输出结果
      Hash: 121ceb0c1b3711b0d009
      Version: webpack 3.5.6
      Time: 82ms
          Asset     Size  Chunks             Chunk Names
      bundle.js  2.83 kB       0  [emitted]  main
         [0] ./src/index.js 88 bytes {0} [built]
         [1] ./src/hello.js 184 bytes {0} [built]
      
    • webpack命令默认使用webpack.config.js文件做为配置文件,也可以指定配置文件node_modules/.bin/webpack --config ./webpack.config.js

    • 在浏览器上打开public下的index.html文件,就能看到hello world的输出了

  • 通过npm命令执行打包(推荐)

    • 我们的webpack是安装在本地的,并非全局(不推荐全局),所以要通过node_modules/.bin/webpack来执行,这么长的路径比较烦人,再加上后面可能会在命令行上再加额外的参数,实在是不方便

    • 因此可以通过package.json中对scripts进行设置,辅助打包(就像makefile文件一样)

    • package.json完整文件如下(删除了没什么用的信息,避免篇幅过长)

      {
          "name": "learn-webpack",
          "version": "1.0.0",
          "description": "",
          "scripts": {
            "start": "webpack"
          },
          "license": "MIT",
          "devDependencies": {
          "webpack": "^3.5.6"
          }
      }
      
    • package.json中的script会按照一定的顺序查找命令,node_modules也在寻找的范围之内,所以就不需要再写成node_modules/.bin/webpack

    • 一般正常情况下执行npmscript命令为npm run scriptName,但是start是比较特殊的,可以直接执行npm start

    • 在根目录下执行npm run start,同样的在public目录下生成了一个bundle.js文件

5 加载器loaders

  • 加载器尝试对工程中的资源文件进行转换,它们是函数,把资源文件做为参数,把转换的内容做为结果。使用不同的加载器,可以对不同格式的文件进行处理

  • 加载器配置在module.rules中, 每一个loader都有以下选项

    • test: 用于匹配所处理文件扩展名的正则表达式(必须)
    • use: 是每一个rule的属性,用于指定用什么loader(必须)
    • include/exclude: 添加要处理的文件(夹)或者是屏蔽不需要处理的文件(夹)(可选)

6 babel-loader

  • 这是第一个介绍到的加载器

  • babel用于转换js文件,具有强大的功能

    • 你可以在代码中使用ES6/ES7功能,即使这些特性还未被当前的浏览器完全的支持
    • 可以使用基于js进行扩展的语言,比如后文中要用到的React的jsx
  • babel的核心功能在babel-core中,可以使用扩展功能,但要额外安装,比如用的最多的就是解析ES6的babel-preset-es2015包和解析jsx的babel-preset-react

  • 安装这些包

    cnpm install babel-core babel-loader babel-preset-es2015 babel-preset-react --save-dev
    
  • webpack.config.js中配置babel-loader,当前完整的webpack.config.js如下

    const config = {
        entry: __dirname + '/src/index.js',
        output: {
            path: __dirname + '/public',
            filename: 'bundle.js'
        },
        module: {
            rules: [{
                test: /(\.js|\.jsx)$/,
                use: {
                    loader: "babel-loader",
                },
                exclude: /node_modules/
            }]
        }
    }
    
    module.exports = config;
    
    • 我们添加了module,并在其中添加了babel-loader配置,这个配置的意思是告诉webpack,如果遇到js或者jsx文件,请调用babel-loader进行转码
    • 这个转码过程可能会很慢,因为匹配了js文件,有可能会转码node_modules中的内容,为了避免这个情况,我们将node_modules排除了。也可以使用cacheDirectory选项把转换的结果缓存起来,就可以加快以后的转换速度了,这个选项默认不开启
  • babel-core由于配置项比较多,通常我们将babel的一些选项放到.babelrc的配置文件中,这个文件我们放到根目录下, 完整如下:

    {
      "presets": ["react", "es2015"]
    }
    
  • 这个加载器有一个特别的选项babelrc,默认为true,当设置为false时,会忽略.babelrc文件

  • 这样我们已经可以使用ES6以及jsx了,接下来我们会使用到React,先安装它们

    cnpm install react react-dom --save
    
  • 使用react来改造hello.jsindex.js

    // hello.js
    
    const React = require('react');
    
    class Hello extends React.Component{
      render() {
        return (
          <div>
            <h1>Hello world 2</h1>
          </div>
        );
      }
    }
    
    module.exports = Hello;
    
    // index.js
    
    const React = require('react');
    const ReactDom = require('react-dom');
    const Hello = require('./Hello');
    
    ReactDom.render(
        <Hello />, 
        document.getElementById('root')
    );
    
  • 运行npm run start打包,并打开index.html,看看是否有Hello world 2输出

7 webpack-dev-server

  • 每次修改代码后,要打包,刷新界面,比较繁琐,所以这里将使用webpack-dev-server来帮助我们完成这些任务

  • 安装

    cnpm install webpack-dev-server --save-dev
    
  • webpack.config.js中配置webpack-dev-server,当前完整的webpack.config.js如下

    const config = {
        entry: __dirname + '/src/index.js',
        output: {
            path: __dirname + '/public',
            filename: 'bundle.js'
        },
        devServer: {
            contentBase: "./public/",
            historyApiFallback: true,
            inline: true,
            port: 8080
        },
        module: {
            rules: [{
                test: /(\.js|\.jsx)$/,
                use: {
                    loader: "babel-loader",
                    options: {
                        presets: [
                            "es2015", "react"
                        ]
                    }
                },
                exclude: /node_modules/
            }]
        }
    }
    
    module.exports = config;
    
    • inline: 设置为true,当源文件发生改变的时候会自动刷新页面
    • historyApiFallback: 当访问不存在的页面时会重定向到index.html
    • port: webpack-dev-server其实是一个小型的服务器,这个是监听端口,默认8080
  • package.json中的scripts添加命令,当前完整的package.json如下

    {
      "name": "learn-webpack",
      "version": "1.0.0",
      "description": "",
      "scripts": {
        "start": "webpack",
        "dev": "webpack-dev-server --open"
      },
      "license": "ISC",
      "devDependencies": {
        "babel-core": "^6.26.0",
        "babel-loader": "^7.1.2",
        "babel-preset-es2015": "^6.24.1",
        "babel-preset-react": "^6.24.1",
        "webpack": "^3.5.6",
        "webpack-dev-server": "^2.7.1"
      },
      "dependencies": {
        "react": "^15.6.1",
        "react-dom": "^15.6.1"
      }
    }
    
  • 在根目录运行npm run dev,可以在http://localhost:8080/查看结果

  • 当我们手动修改代码时,比如hello.js中的内容修改hello world 3,就会自动运行打包,然后刷新网页。

8 css-loader

  • 另一个经常用到的就是各种css处理器了,比如通过css-loader,我们可以在js文件中通过require来引入css。同时还要安装一个style-loader,用于在html中以style方式将css插入。

  • 还有less-loader将less文件编译成css,sass-loader将sass文件编译成css,不过本文没有使用到这两个

  • 首先安装css-loaderstyle-loader

    cnpm install css-loader style-loader --save-dev
    
  • webpack.config.js中配置css-loaderstyle-loader,当前完整的webpack.config.js如下

    const config = {
        entry: __dirname + '/src/index.js',
        output: {
            path: __dirname + '/public',
            filename: 'bundle.js'
        },
        devServer: {
            contentBase: "./public/",
            historyApiFallback: true,
            inline: true,
            port: 8080
        },
        module: {
            rules: [{
                test: /(\.js|\.jsx)$/,
                use: {
                    loader: "babel-loader",
                    options: {
                        presets: [
                            "es2015", "react"
                        ]
                    }
                },
                exclude: /node_modules/
            }, {
                test: /(\.css)$/,
                use: [{
                    loader: "style-loader"
                }, {
                    loader: "css-loader",
                    options: {
                        modules: true
                    }
                }]
            }]
        }
    }
    
    module.exports = config;
    
    • css-loader中的options.modules意思是是否启用css-modules功能,关于css-modules可以查看阮一峰 CSS Modules 用法教程。大意就是模块化思想,把CSS的类名传递到组件的代码中,且只对当前组件有效,不用担心在不同的模块中使用相同的类名造成的冲突。
  • src/中创建static文件夹,用于存放css文件,并在其中创建hello.cssindex.css

    # hello.css
    .hello {
        color: blue
    }
    
    # index.css
    h1,
    h2,
    h3,
    h4,
    h5,
    h6,
    p,
    ul {
        margin: 0;
        padding: 0;
        text-align: center;
    }
    
  • hello.jsindex.js引用hello.cssindex.css

    // hello.js
    const React = require('react');
    const helloCSS = require('./static/hello.css');
    
    class Hello extends React.Component{
      render() {
        return (
          <div>
            <h1 className={helloCSS.hello}>
              Hello world 7
            </h1>
          </div>
        );
      }
    }
    
    module.exports = Hello;
    
    // index.js
    const React = require('react');
    const ReactDom = require('react-dom');
    const Hello = require('./Hello');
    
    require('./static/index.css');
    
    ReactDom.render(
        <Hello />, 
        document.getElementById('root')
    );
    
  • 运行npm run dev, 我们可以看到, 文字居中且变成蓝色了

9 插件plugins

  • 加载器loaders是告诉webpack,将符合条件的文件用指定的loader进行编译转换,而插件plugins则是扩展webpack功能的

  • 加载器配置在plugins

10 BannerPlugin

  • 该插件使用简单,无需额外安装,做为第一个介绍的插件

  • 该插件的作用是对打包出的每一个chunk头部添加信息

  • 我们修改webpack.config.js,在其中添加plugins模块,并添加BannerPlugin配置, 当前完整的webpack.config.js如下

    const webpack = require('webpack');
    
    const config = {
        entry: __dirname + '/src/index.js',
        output: {
            path: __dirname + '/public',
            filename: 'bundle.js'
        },
        devServer: {
            contentBase: "./public/",
            historyApiFallback: true,
            inline: true,
            port: 8080
        },
        module: {
            rules: [{
                test: /(\.js|\.jsx)$/,
                use: {
                    loader: "babel-loader",
                    options: {
                        presets: [
                            "es2015", "react"
                        ]
                    }
                },
                exclude: /node_modules/
            }, {
                test: /(\.css)$/,
                use: [{
                    loader: "style-loader"
                }, {
                    loader: "css-loader",
                    options: {
                        modules: true
                    }
                }]
            }]
        },
        plugins: [
            new webpack.BannerPlugin('这里是banner,请添加信息')
        ]
    }
    
    module.exports = config;
    
  • 执行npm run start,打开public/bundle.js看看顶部是否加了一行信息

11 热加载模块

  • 前面我们使用webpack-dev-server来辅助我们打包,刷新页面,比如我们修改了hello world内容,可以很明显的看到页面进行了刷新。

  • 实际上webpack-dev-serverinline模式下,具有热加载功能,即尝试重新加载组件的改变部分,而不是重新加载整个页面。

  • 我们的组件使用的是react,所以我们需要安装两个插件帮助React只加载改变的部分

    cnpm install babel-plugin-react-transform react-transform-hmr --save-dev
    
  • .babelrc中需要配置这两个插件

    {
        "presets": ["react", "es2015"],
        "env": {
            "development": {
                "plugins": [
                    ["react-transform", {
                        "transforms": [{
                            "transform": "react-transform-hmr",
                            "imports": ["react"],
                            "locals": ["module"]
                        }]
                    }]
                ]
            }
        }
    }
    
  • webpack.config.js中需要修改两处地方

    const webpack = require('webpack');
    
    const config = {
        entry: __dirname + '/src/index.js',
        output: {
            path: __dirname + '/public',
            filename: 'bundle.js'
        },
        devServer: {
            contentBase: "./public/",
            historyApiFallback: true,
            inline: true,
            port: 8080,
            // 添加hot
            hot: true
        },
        module: {
            rules: [{
                test: /(\.js|\.jsx)$/,
                use: {
                    loader: "babel-loader",
                },
                exclude: /node_modules/
            }, {
                test: /(\.css)$/,
                use: [{
                    loader: "style-loader"
                }, {
                    loader: "css-loader",
                    options: {
                        modules: true
                    }
                }]
            }]
        },
        plugins: [
            new webpack.BannerPlugin('这里是banner,请添加信息'),
            // 开启热加载
            new webpack.HotModuleReplacementPlugin()
        ]
    }
    
    module.exports = config;
    
    • devServer中添加hot: true
    • 在插件中开启热加载功能 webpack.HotModuleReplacementPlugin()
  • 执行npm run dev,然后修改hello world内容,我们可以发现,不需要刷新整个界面,内容却随之改变了

12 HtmlWebpackPlugin

  • 有一个问题就是缓存问题,比如我们生成的文件名都叫做bundle.js,我们对内容进行了修改,但是由于文件名没有改变,浏览器很可能直接从缓存中读取旧的数据了

  • 如果我们给打包出来的文件加一些后缀,比如加一个hash值,内容发生变化后,hash值不同,文件名称也不同,这样就不会出现上面的问题了,比如bundle-a61f37756a508cb151b5.js

  • 新问题来了,由于我们之前public/index.html中,是直接引用的bundle.js,由于hash值是不确定的,怎样才能让index.html能够正确的使用新打出的bundle-[hash].js文件?

  • 这就使用到HtmlWebpackPlugin了,该插件能够根据一个模板文件,自动生成一个index.html,该文件会自动引用打包后的js文件。

  • 之前的public文件夹可以删除不用了,我们按照惯例在根目录创建一个名为build文件夹

  • src目录下建一个文件template.html做为模板文件

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="utf-8">
        <title>learn webpack</title>
    </head>
    <body>
        <div id="root"></div>
    </body>
    </html>
    
  • 安装插件

    cnpm install html-webpack-plugin --save-dev
    
  • webpack.config.js中对HtmlWebpackPlugin进行配置,当前完整的webpack.config.js如下

    const webpack = require('webpack');
    
    // 引入插件
    const HtmlWebpackPlugin = require('html-webpack-plugin');
    
    const config = {
        entry: __dirname + '/src/index.js',
        output: {
            path: __dirname + '/build',
            //  修改输出的文件名称
            filename: 'bundle-[hash].js'
        },
        devServer: {
            contentBase: "./public/",
            historyApiFallback: true,
            inline: true,
            port: 8080,
            hot: true
        },
        module: {
            rules: [{
                test: /(\.js|\.jsx)$/,
                use: {
                    loader: "babel-loader",
                },
                exclude: /node_modules/
            }, {
                test: /(\.css)$/,
                use: [{
                    loader: "style-loader"
                }, {
                    loader: "css-loader",
                    options: {
                        modules: true
                    }
                }]
            }]
        },
        plugins: [
            new webpack.BannerPlugin('这里是banner,请添加信息'),
            new webpack.HotModuleReplacementPlugin(),
            // 使用插件,指定模板位置
            new HtmlWebpackPlugin({
                template: __dirname + '/src/template.html'
            })
        ]
    }
    
    module.exports = config;
    
  • 执行npm run start后,我们可以在build文件夹看到生成了bundle-[hash].js文件和一个index.html文件,且index.html文件正确的引用了bundle-[hash].js

13 UglifyJsPlugin

  • 在产品发布阶段,可以使用一些优化插件,比如将要介绍的UglifyJsPlugin,它可以压缩JS代码,大家可以进入到build文件夹,执行du -sh看看各个文件的大小

    920K    bundle-94e84680995a92af5838.js
    4.0K    index.html
    
  • 该插件使用很简单,如果都使用默认值,则在webpack.config.js中的plugins添加上配置信息就行,无需额外安装

    ...
    
    plugins: [
        new webpack.BannerPlugin('这里是banner,请添加信息'),
        new webpack.HotModuleReplacementPlugin(),
        new HtmlWebpackPlugin({
            template: __dirname + '/src/template.html'
        }),
        // 全部使用默认值
        new webpack.optimize.UglifyJsPlugin()
    ]
    
  • 重新运行npm run start后可以发现bundle-[hash].js变得很紧凑了,文件也变小了

    268K    bundle-94e84680995a92af5838.js
    4.0K    index.html
    
  • 这个插件也有一些选项,具体的可以查看webpack uglifyjsplugin

  • 当前完整版的webpack.config.js如下

    const webpack = require('webpack');
    
    const HtmlWebpackPlugin = require('html-webpack-plugin');
    
    const config = {
        entry: __dirname + '/src/index.js',
        output: {
            path: __dirname + '/build',
            filename: 'bundle-[hash].js'
        },
        devServer: {
            contentBase: "./public/",
            historyApiFallback: true,
            inline: true,
            port: 8080,
            hot: true
        },
        module: {
            rules: [{
                test: /(\.js|\.jsx)$/,
                use: {
                    loader: "babel-loader",
                },
                exclude: /node_modules/
            }, {
                test: /(\.css)$/,
                use: [{
                    loader: "style-loader"
                }, {
                    loader: "css-loader",
                    options: {
                        modules: true
                    }
                }]
            }]
        },
        plugins: [
            new webpack.BannerPlugin('这里是banner,请添加信息'),
            new webpack.HotModuleReplacementPlugin(),
            new HtmlWebpackPlugin({
                template: __dirname + '/src/template.html'
            }),
            new webpack.optimize.UglifyJsPlugin({
                // 最紧凑的输出
                beautify: false,
                // 不要注释
                comments: false,
                compress: {
                    // 删除console语句
                    drop_console: true,
                    // 内嵌定义了但是只用到一次的变量
                    collapse_vars: true,
                    // 提取出出现多次但是没有定义成变量去引用的静态值
                    reduce_vars: true
                }
            })
        ]
    }
    
    module.exports = config;
    

14 CommonsChunkPlugin

  • 到目前为止,我们的应用都是把所有牵涉到的文件打包到一个文件里面(bundle-[hash].js),加载的时候把所有的文件都加载进来,不管是否有用到。实际上,当工程复杂了以后,包括各种依赖,打包后的文件实际上挺大的,这样就浪费流量,也增加了加载的时间。如果我们只需要加载所用到的代码或者将公共代码缓存起来,那就省事多了

  • 比如我们将reactreact-dom做为公共文件提取出来,最终打包成一个文件,浏览器缓存起来后,去访问例如hello2.js,hello3.js等,都可以从缓存中取出来,而不是每次访问一个新页面的时候,都重复加载这个文件

  • 无需安装这个插件,当前完整的webpack.config.js如下

    const webpack = require('webpack');
    
    const HtmlWebpackPlugin = require('html-webpack-plugin');
    
    const config = {
        entry: {
            index: __dirname + '/src/index.js',
            // 把react,react-dom放到一起打包
            vendor: ['react', 'react-dom']
        },
        output: {
            path: __dirname + '/build',
            // 按照实际名称输出
            filename: '[name]-[hash].js',
            publicPath: '',
            chunkFilename: '[name]-[hash].js'
        },
        devServer: {
            contentBase: "./public/",
            historyApiFallback: true,
            inline: true,
            port: 8080,
            hot: true
        },
        module: {
            rules: [{
                test: /(\.js|\.jsx)$/,
                use: {
                    loader: "babel-loader",
                },
                exclude: /node_modules/
            }, {
                test: /(\.css)$/,
                use: [{
                    loader: "style-loader"
                }, {
                    loader: "css-loader",
                    options: {
                        modules: true
                    }
                }]
            }]
        },
        plugins: [
            new webpack.BannerPlugin('这里是banner,请添加信息'),
            new webpack.HotModuleReplacementPlugin(),
            new HtmlWebpackPlugin({
                template: __dirname + '/src/template.html'
            }),
            new webpack.optimize.UglifyJsPlugin({
                beautify: false,
                comments: false,
                compress: {
                    drop_console: true,
                    collapse_vars: true,
                    reduce_vars: true
                }
            }),
            // 把公共代码提取出来,命名为vendor.js,当然受到output配置影响,会追加一个hash
            new webpack.optimize.CommonsChunkPlugin({
                names: ['vendor']
            })
        ]
    }
    
    module.exports = config;
    
  • 执行npm run start后,在build文件夹下出现vender-[hash].js文件

     44K    build/index-c57eda02b7e22fad43c3.js
    4.0K    build/index.html
    228K    build/vendor-c57eda02b7e22fad43c3.js
    
  • 但还存在一个问题,那就是当我们修改了文件内容,比如hello.js中输出的内容修改为hello world 123,然后执行npm run start,会发现index.jsvendor.js都重新打包了,实际上我们并不希望vendor.js重新打包,所以还要再做一些修改

  • 首先,文件名称就不能用hash了,而是改用chunkhash

    • chunkhash: chunk就是指模块,如果chunk内容不变,chunkhash的值就不会变化,非常符合我们的要求
    • hash: 当项目文件发生改变的时候,就会生成一个compilation对象,就是这个对象的hash值
  • 修改webpack.config.js,当前完整版如下

    const webpack = require('webpack');
    
    const HtmlWebpackPlugin = require('html-webpack-plugin');
    
    const config = {
        entry: {
            index: __dirname + '/src/index.js',
            vendor: ['react', 'react-dom']
        },
        output: {
            // 使用chunkhash替换hash,个人感觉hash太长,只保留十位
            path: __dirname + '/build',
            filename: '[name]-[chunkhash:10].js',
            publicPath: '',
            chunkFilename: '[name]-[chunkhash:10].js'
        },
        devServer: {
            contentBase: "./public/",
            historyApiFallback: true,
            inline: true,
            port: 8080,
            hot: true
        },
        module: {
            rules: [{
                test: /(\.js|\.jsx)$/,
                use: {
                    loader: "babel-loader",
                },
                exclude: /node_modules/
            }, {
                test: /(\.css)$/,
                use: [{
                    loader: "style-loader"
                }, {
                    loader: "css-loader",
                    options: {
                        modules: true
                    }
                }]
            }]
        },
        plugins: [
            new webpack.BannerPlugin('这里是banner,请添加信息'),
            // 注释掉热更新,否则会报错: Cannot use [chunkhash] for chunk in '[name]-[chunkhash:10].js' (use [hash] instead)
            // new webpack.HotModuleReplacementPlugin(),
            new HtmlWebpackPlugin({
                template: __dirname + '/src/template.html'
            }),
            new webpack.optimize.UglifyJsPlugin({
                beautify: false,
                comments: false,
                compress: {
                    drop_console: true,
                    collapse_vars: true,
                    reduce_vars: true
                }
            }),
            // 这里也要修改
            new webpack.optimize.CommonsChunkPlugin({
                name: 'manifest',
                chunks: ['vendor']
            }),
            new ExtractTextPlugin('[name]-[contenthash:10].css')
        ]
    }
    
    module.exports = config;
    
  • 现在修改逻辑内容后再运行npm run start进行打包,不会再生成新的vender.js文件了。

15 ExtractTextPlugin

  • 我们之前的打包的文件中也包含了css信息,我们现在也将它们提取出来,让它可以单独缓存

  • 安装插件

    cnpm install extract-text-webpack-plugin --save-dev
    
  • webpack.config.js中进行配置,当前完整的webpack.config.js如下

    const webpack = require('webpack');
    
    const HtmlWebpackPlugin = require('html-webpack-plugin');
    
    const ExtractTextPlugin = require('extract-text-webpack-plugin');
    
    const config = {
        entry: {
            index: __dirname + '/src/index.js',
            vendor: ['react', 'react-dom']
        },
        output: {
            path: __dirname + '/build',
            filename: '[name]-[chunkhash:10].js',
            publicPath: '',
            chunkFilename: '[name]-[chunkhash:10].js'
        },
        devServer: {
            contentBase: "./public/",
            historyApiFallback: true,
            inline: true,
            port: 8080,
            hot: true
        },
        module: {
            rules: [{
                test: /(\.js|\.jsx)$/,
                use: {
                    loader: "babel-loader",
                },
                exclude: /node_modules/
            }, {
                test: /(\.css)$/,
                use: ExtractTextPlugin.extract({
                    fallback: "style-loader",
                    use: [{
                        loader: "css-loader",
                        options: {
                            modules: true
                        }
                    }]
                })
            }]
        },
        plugins: [
            new webpack.BannerPlugin('这里是banner,请添加信息'),
            // 注释掉热更新,否则会报错: Cannot use [chunkhash] for chunk in '[name]-[chunkhash:10].js' (use [hash] instead)
            // new webpack.HotModuleReplacementPlugin(),
            new HtmlWebpackPlugin({
                template: __dirname + '/src/template.html'
            }),
            new webpack.optimize.UglifyJsPlugin({
                beautify: false,
                comments: false,
                compress: {
                    drop_console: true,
                    collapse_vars: true,
                    reduce_vars: true
                }
            }),
            new webpack.optimize.CommonsChunkPlugin({
                name: 'manifest',
                chunks: ['vendor']
            }),
            // 命名生成的文件
            new ExtractTextPlugin('[name]-[contenthash:10].css')
        ]
    }
    
    module.exports = config;
    
  • 执行npm run start,我们可以在build目录下发现css文件

    4.0K    build/index-0f50bc847f.css
     36K    build/index-bac776987c.js
    
    ... ...
    
  • 该插件中文版配置选项webpack extract-text-webpack-plugin

16 OccurenceOrderPlugin

  • 在当前版本(3.5.6),该插件已经是默认开启的,所以无需在webpack.config.js中设置

17 NamedModulesPlugin

  • 在插件CommonsChunkPlugin中解决了当修改文件内容的时候vender.js随之改变的问题,但实际上,并不能完全保证vendor.js的chunkhash值不发生改变,webpack的作者提到了这一点:

    默认情况下,模块的id是这个模块在模块数组中的索引。OccurenceOrderPlugin 会将引用次数多的模块放在前面,在每次编译时模块的顺序都是一致的……如果你修改代码时新增或删除了一些模块,这将会影响到所有模块的id
    
  • 为了测试,我们在src目录下新建了一个文件content.js

    // content.js
    module.exports = function() {
        return 'hello world';
    };
    
  • 然后在hello.js中引用,只添加一行require('./content');,然后打包,我们发现又出现了一个新的vendor.js文件

  • 实际上,从webpack作者的话来看,问题的根源在于模块的id是这个模块在模块数组中的索引,当我们新增文件的时候,在OccurenceOrderPlugin的影响下,可能会影响到索引的变化。

  • NamedModulesPlugin这个插件,使用模块的相对路径做为模块的id,只要我们不重命名一个模块文件,那么它的id就不会发生改变

  • 此插件不需要另外安装,只要在plugins加上一句

    new webpack.NamedModulesPlugin()
    
  • 这样,新增或减少模块,也不会让公共模块发生变化了。但是带来的问题就是包会变大一些,使用前后文件大小变化

    // 未使用NamedModulesPlugin插件
    4.0K    build/index-0f50bc847f.css
    256K    build/index-5ea7cf14d7.js
    4.0K    build/index.html
    4.0K    build/manifest-e40eafac92.js
    220K    build/vendor-2615736a31.js
    
    // 使用NamedModulesPlugin插件
    4.0K    build/index-0f50bc847f.css
    324K    build/index-6333c0bc61.js
    4.0K    build/index.html
    4.0K    build/manifest-0d2ca7dca3.js
    268K    build/vendor-70142bb2ee.js
    
  • 我们发现index.js还是增大挺多的,所以NamedModulesPlugin对于复杂的应用来说,并不是很满意,这就需要用到下一个要介绍的插件HashedModuleIdsPlugin

18 HashedModuleIdsPlugin

  • 这个插件会根据模块的相对路径生成一个长度只有四位的字符串做为模块id

  • 使用也非常简单,不需要额外安装,直接配置在webpack.config.js中的plugins即可

    // 不再使用NamedModulesPlugin
    // new webpack.NamedModulesPlugin()
    new webpack.HashedModuleIdsPlugin()
    
  • 这样打包出的文件并不会大多少,且增加和减少模块也不会引起vendor.js改变

    4.0K    build/index-0f50bc847f.css
    260K    build/index-b6e4ad2c69.js
    4.0K    build/index.html
    4.0K    build/manifest-3671567576.js
    224K    build/vendor-e6102e2bfe.js
    

19 指定环境

  • 有的库通过与process.env.NODE_ENV环境变量关联,当非生产模式下时,为了调试方便,可能会添加额外的日志记录和测试。当处于生产模式的时候,某些库可能会对代码优化。

  • 有没优化看不出来,不过大小倒是有变化。

  • 不需要安装,在plugins添加以下内容:

    new webpack.DefinePlugin({
        'process.env': {
            'NODE_ENV': JSON.stringify('production')
         }
    })
    
  • 下面的代码中css文件提取到static目录下了,没有和js文件放一起

    new ExtractTextPlugin('./static/[name]-[contenthash:10].css')
    
  • 重新执行打包后的大小:

    # 修改前
    260K    build/index-432c275a7c.js
    4.0K    build/index.html
    4.0K    build/manifest-3671567576.js
    8.0K    build/static
    224K    build/vendor-e6102e2bfe.js
    
    # 修改后
    184K    build/index-74daaba778.js
    4.0K    build/index.html
    4.0K    build/manifest-ccb57023d0.js
    8.0K    build/static
    148K    build/vendor-2ca39a59c2.js
    

20 CleanWebpackPlugin

  • 当每次执行打包的时候,旧的文件并没有被删除,加上带着hash值,看上去糟糕透了。

  • 现在可以使用CleanWebpackPlugin来清理这些文件

  • 安装

    cnpm install clean-webpack-plugin --save-dev
    
  • 配置

    const CleanWebpackPlugin = require('clean-webpack-plugin');
    
    ...
    
    plugins: [
        ...
        
        // 也可以指定 build/index.*.js 来指定删除
        new CleanWebpackPlugin(['build'], {
            root: __dirname,
            verbose: true,
            dry: false
        })
    ]
    
  • 这样每次就会清理掉build文件夹

21 两份webpack配置

  • 在插件CommonsChunkPlugin中有使用到chunkhash,但是在热加载模式下并不适合使用,所以我们可以创建两份webpack配置

    • webpack.config.dev.js
    • webpack.config.prod.js
  • 开发板支持热更新,最终版支持chunkhash

  • 可以修改package.json中的scripts

    "scripts": {
        "start": "webpack --config ./webpack.config.dev.js",
        "build": "webpack --config ./webpack.config.prod.js",
        "dev": "webpack-dev-server --open"
      }
    

22 package.json

  • 该文件记录了本文中用到的所有的插件和加载器

    {
      "name": "learn-webpack",
      "version": "1.0.0",
      "description": "",
      "scripts": {
        "start": "webpack",
        "dev": "webpack-dev-server --open"
      },
      "license": "MIT",
      "devDependencies": {
        "babel-core": "^6.26.0",
        "babel-loader": "^7.1.2",
        "babel-plugin-react-transform": "^2.0.2",
        "babel-preset-es2015": "^6.24.1",
        "babel-preset-react": "^6.24.1",
        "css-loader": "^0.28.7",
        "extract-text-webpack-plugin": "^3.0.0",
        "html-webpack-plugin": "^2.30.1",
        "react-transform-hmr": "^1.0.4",
        "style-loader": "^0.18.2",
        "webpack": "^3.5.6",
        "webpack-dev-server": "^2.7.1"
      },
      "dependencies": {
        "react": "^15.6.1",
        "react-dom": "^15.6.1"
      }
    }
    
    

23 参考文献

Webpack 配置文件 英文文档
Webpack 配置文件 中文文档
Pro React
github工程 fairy
使用 Webpack 打包单页应用的正确姿势

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值