Contents

从头开始配置webpack4

最近好像流行零配置,parcel的开箱即用的概念,也影响了webpack4,在 webapck4中也开始朝着这个方向发展,比如默认的入口为src,默认的打包输出为dist,新增了mode属性,有利于拆分生产环境和开发环境,默认为production,并且默认压缩js,还有一些其他的插件也不再适用,在此也没必要了解,最近就从头梳理了一下webpack4.

本片文章主要参考自董沅鑫的个人博客https://godbmw.com

环境

首先我们要全局安装webpack-cli和webpack,然后也要在项目中安装webpack和webpack-cli,这样我们才能使用。

编译ES6

编译es6,我们需要以下loaders

  • babel-loader babel转换器
  • babel-core babel转换时所调用的api(听名字就知道它是babel的核心)
  • babel-preset-env 语法转换规则
  • babel-plugin-transform-runtime和Babel-runtime polyfill按需引入
  • babel-polyfill polyfill全局引入 注意:在babel7中所有前缀都变为@babel/,注意版本的统一

新建webpack.config.js:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
const path = require("path")
// 使用babel编译ES6
module.exports = {
    entry: {
        app: "./index.js" // 以对象形式写推荐
    },
    output: {
        filename: "bundle.js",
        // resolve解析为一个绝对路径
        path: path.resolve(__dirname, "dist")
    },
    module: {
        rules: [
            {
                test: /\.js$/, // 正则匹配以js结尾的文件
                use: {
                    loader: "babel-loader"
                    // option: { // babel-loader的具体配置信息写在根目录下的.babelrc中
                    // }
                },
                exclude: /node_modules/ // 排除依赖包
            }
        ]
    }
}

babel的配置一般放在.babelrc中

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
{
    "presets": [
        "@babel/env",{
            "targets": {
               "browsers": ["last 2 versions"] 
            }
        }
    ],
    "plugins": ["@babel/plugin-transform-runtime"]
}
  • presets配置中你可以具体指定,比如这里指定为浏览器上两个版本

  • 我们知道用babel来转换es6语法,语法规则根据presets(预设的规则来的),但却转换不了es6的一些方法比如promise,generator函数,所以我们需要polyfill(垫片)

  • 上面用的是按需引入polyfill,@babel/plugin-transform-runtime这个插件会去@babel/runtime中自动引入,所以即使打包后我们也用到@babel/runtime,它应该安装在生产环境中的依赖包中

  • 如果要使用babel-polyfill的话,安装@babel/polyfill,并且在webpack.config.js中最开头引入即可,或者在写在入口函数字符串数组第一个

1
2
3
4
5
import '@babel/polyfill'
// 或者
module.exports = {
entry: ["@babel/polyfill","index.js"]
}

提取公共JS

  • 在配置中新增optimization属性
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
const path = require("path")
// 多页面打包,提取公共代码
module.exports = {
    entry: {
        a: './a.js',
        b: './b.js'
    },
    output: {
        filename: '[name].bundle.js',
        chunkFilename: "[name].chunk.js", // 非入口打包文件
        path: path.resolve(__dirname, 'dist')
    },
    optimization: {
        splitChunks: {
          cacheGroups: {
            commons: {
                name: 'common', // 提取公共chunk的名字
                priority: 0, // 缓存优先级
                chunks: 'initial', // 作用范围
                minChunks: 2,//最小引用2次
                minSize: 0 // 只要超出0字节就生成一个新包
              },
              vendor: {
                name: "vendor",
                test: /node_modules/,
                chunks: "initial",
                priority: 10,
                chunks: 'all'
              }
          }
        }
      }
}
  • 这里最重要的是priority属性,表示优先级的意思,上面我们就是先打包node_modules,打包后的名字为vendor,其次再打包Js,取名为common,
  • 输出的时候也要注意加上ChunkFilename,表示非入口打包文件,抽离出的公共部分就是非入口打包文件
  • 具体的其他属性,可以去官网去看看,这里只给出基本示例

代码分割与懒加载

在入口文件中使用import()或者require.ensure()方法,webpack.config.js并不需要配置 示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import(/* webpackChunkName: 'a'*/ "./a").then(function(a) {
    console.log(a);
  });
import(/* webpackChunkName: 'b'*/ "b").then(function(b) {
    console.log(b));
  });
// 或者
require.ensure(
  ["./a.js", "./b.js"],
  function () {
    var a = require("./a");
    var b = require("./b");
    console.log("加载完成")
  },
  "page"
);
  • 我们在vue中使用路由懒加载就是使用import()
  • 我们在node中因为CommonJs规范为同步加载模块,我们使用的是require.ensure()来按需加载,其实这些方法我们以前就用过

处理CSS

解析CSS

需要用到的loaders

  • style-loader
  • css-loader
  • sass-loader/node-sass(以sass为例)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
const path = require("path")
module.exports = {
    entry: {
        app: './src/app.js'
    },
    output: {
        filename: "[name].bundle.js",
        path: path.resolve(__dirname, "dist")
    },
    module: {
        rules: [
            {
                test:/\.scss$/,
                use: ["style-loader","css-loader","sass-loader"]// 如果每个loader有配置,可以以对象形式书写
            }
        ]
    }
}

提取CSS

需要用到的loaders

  • extract-text-webpack-plugin@next(注意后缀@next意为最新版)
  • 或者mini-css-extract-plugin
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
const path = require("path")
const ExtractTextPlugin = require("extract-text-webpack-plugin");

module.exports = {
    entry: {
        app: './src/app.js'
    },
    output: {
        filename: "[name].bundle.js", // []表示占位符
        path: path.resolve(__dirname, "dist")
    },
    module: {
        rules: [
            {
                test: /\.scss$/,
                use: ExtractTextPlugin.extract({
                    fallback: {
                        loader: "style-loader" // css编译后用什么来提取的loader
                    },
                    use: [
                        {
                            loader: "css-loader",
                            options: {
                                minimize: true // 是否压缩
                            }
                        },
                        {
                            loader: "sass-loader"
                        }
                    ]
                })
            }
        ]
    },
    plugins: [
        new ExtractTextPlugin("[name].min.css")
        // new ExtractTextPlugin({
        //     filename: "[name].min.css", // 压缩后的名字
        // })
    ]
}

处理图片

  • 通过CSS引入图片 style-loader和css-loder解析CSS,用url-loader解析图片
  • html标签插入图片(webpack不推荐这么引用) html-withimg-loader
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
const path = require("path")
const webpack = require("webpack")
const ExtractTextPlugin = require("extract-text-webpack-plugin");

module.exports = {
    entry: {
        app: "./src/app.js"
    },
    output: {
        filename: "app.[hash:8].bundle.js",
        path: path.resolve(__dirname, "dist"),
        chunkFilename: "[name].chunk.js",
        publicPath: "./", // 资源引用路径
    },
    module: {
        rules: [
            {
                test: /\.css$/,
                use: ExtractTextPlugin.extract({
                fallback: {
                    loader: "style-loader", // 回调style注入
                    options: {
                    singleton: true
                    }
                },
                use: {
                    loader: "css-loader",// 解析url()和@import
                    options: {
                    minimize: true
                    }
                }
                }) 
            },
            {
                test: /\.(png|jpg|jpeg|gif)$/,
                use: [
                    {
                    loader: "url-loader", // 解析图片
                    options: {
                        name: "[name]-[hash:5].min.[ext]",
                        limit: 10000, // size <= 20KB
                        publicPath: "static/",
                        outputPath: "static/"
                    }
                    }
                ]
            },
            // 也可以对字体处理
            {
                test: /\.(eot|woff2?|ttf|svg)$/,
                use: [
                  {
                    loader: "url-loader",
                    options: {
                      name: "[name]-[hash:5].min.[ext]",
                      limit: 5000, // fonts file size <= 5KB, use 'base64'; else, output svg file
                      publicPath: "fonts/",
                      outputPath: "fonts/"
                    }
                  }
                ]
              },
              // html标签方式插入图片
              {    test: /\.(htm|html)$/i,
                use:[ 'html-withimg-loader'] 
           }
        ]
    },
    plugins: [
        // 压缩后CSS的名字
        new ExtractTextPlugin("app.[hash:8].min.css"),
    ],
}

tree shaking

  • js tree shaking
1
2
3
4
module.exports = {
    // 设置为生产环境,自动调用插件
    mode: "production"
}
  • css tree shaking 需要用到的loaders
  • purifycss-webpack shaking主要插件
  • glob-all 帮助指示路径
  • extract-text-webpack-plugin 提取分离CSS
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
const path = require("path")
const PurifyCSS = require("purifycss-webpack");
const glob = require("glob-all");
const ExtractTextPlugin = require("extract-text-webpack-plugin");

module.exports = {
    entry: {
        app: "./src/app.js"
    },
    output: {
        filename: "app.[hash:8].bundle.js",
        path: path.resolve(__dirname, "dist"),
        chunkFilename: "[name].chunk.js",
        publicPath: __dirname + "/dist/",
    },
    module: {
        rules: [
          {
            test: /\.css$/,
            use: ExtractTextPlugin.extract({
              fallback: {
                loader: "style-loader", // 编译后使用style插入
                options: {
                  singleton: true
                }
              },
              use: {
                loader: "css-loader",
                options: {
                  minimize: true
                }
              }
            })
          }
        ]
      },
    plugins: [
        new ExtractTextPlugin("app.[hash:8].min.css"),
        new PurifyCSS({
          paths: glob.sync([
            path.join(__dirname, ".html"),
            path.join(__dirname, "src/*.js")
          ])
        }),
    ],
}
}

自动生成html&&清除打包缓存

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
const CleanWebpackPlugin = require('clean-webpack-plugin');
const htmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
    plugins: [
        // 字符串数组
        new CleanWebpackPlugin(['dist']),
        // 生成html,并且自动引入
        new htmlWebpackPlugin({
            filename: "index.html",  //打包后的文件名
            template: path.join(__dirname , "./index.html")  // 可以指定模板,如果是自定义的模板需要下载对于loader
        }),
    ],
}

第三方js库

jquery为例 npm install jquery –save npm i expose-loader –save

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15

module.exports = {
   module: {
       {
        test: require.resolve('jquery'),
        use: [{
            loader: 'expose-loader',
            options: 'jQuery'
        },{
            loader: 'expose-loader',
            options: '$'
        }]
    }
   }
}

然后在组件中可以这样引用

1
2
require('jquery')
require('jQuery第三方插件')

webpack-dev-server

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
module.exports = {
    devServer: {
        contentBase: path.join(__dirname, 'dist'),  //启动路径
        host:'localhost',  //域名
        port: 8888,  //端口号
         // 声明为热替换
        hot: true,
        // 第一次打包时打开浏览器
        open: true,
        compress:true, //压缩,
        overlay: true // 浏览器显示错误
        //请求到 /api/users 现在会被代理到请求 http://localhost:9000/api/users。
        // proxy: {
        //     "/api": "http://localhost:9000",
        // }
    }
}

生产环境和开发环境

我们可以把我们的webpack.config.js复制两份,使用mode属性来拆分成如下 webapck.dev.config.js mode:“development” webapck.pro.config.js mode:“production” 然后在package.json的书写脚本

1
2
3
4
{
"dev": "webpack --config webpack.dev.config.js"
"build": "webpack --config webpack.pro.config.js"
}