# webpack4(三) webpack实战配置

# library的打包

我们如果想要打包生成一个库文件,那么还需要再进行一些配置。

在webpack.config.js中

module.exports = {
    //...
    mode: 'production',
    entry: './src/index.js',
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'library.js',
        externals: 'lodash',// 在打包时不引入库文件,在业务代码使用的时候再在业务代码里引入
        library: 'library', // 将library库挂载在library上,使用全局变量library就可以拿到库内容,这样在使用<script></script>标签时就可以通过全局变量library拿到库
        libraryTarget: 'umd', // umd 支持各种引入模式 import require
    }
    //...
}
1
2
3
4
5
6
7
8
9
10
11
12
13

# PWA的打包

Progressive Web Application

在生产环境下运行时,如果服务器挂掉,那么页面就访问不到了,PWA目的在于当第一次浏览页面后,在本地有缓存,当服务器挂掉后,依旧可以使用本地缓存看到页面。

npm install workbox-webpack-plugin --save-dev
1

在生产环境下才需要PWA,修改webpack.prod.js

const WorkboxWebpackPlugin = require('workbox-webpack-plugin');

const prodConfig = {
    //....
    plugins: [
        new WorkboxWebpackPlugin.GenerateSW({
            clientsClaim: true,
            skipWaiting: true
        })
    ],
    //...
}
1
2
3
4
5
6
7
8
9
10
11
12

然后在我们的业务代码里添加一些东西,这样就可以使用PWA了。只是简单的使用,还有很多东西,需要再做了解。

if ('serviceWorker' in navigator) {
    window.addEventListener('load', () => {
        navigator.serviceWorker.register('/service-worker.js').then(registration => {
            console.log('service-worker registed');
        }).catch(error => {
            console.log('service-worker register error');
        })
    })
}
1
2
3
4
5
6
7
8
9

# typescript的打包配置

首先安装 typescript 和 ts-loader

npm install typescript ts-loader --save-dev
1

在webpack.config.js中进行配置. 入口文件为 index.tsx 对tsx文件进行loader处理

module.exports = {
    // ...
    entry: './src/index.tsx',
    module: {
        rules: [
            {
                test: /\.tsx?$/,
                use: 'ts-loader',
                exclude: /node_modules/
            }
        ]
    },
    // ....
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

光配置loader还不能使用,接下来还需要在根目录创建一个 tsconfig.json

{
    "compilerOptions": {
        "outDir": "./dist", // 打包到dist文件夹下
        "module": "es6", // 允许使用es6的引入 import
        "target": "es5", // 打包成es5代码
        "allowJs": true // 允许引入js文件
    }
}
1
2
3
4
5
6
7
8

在引入其他第三方库时,如lodash等,ts无法继续为我们对其进行代码检测,这时我们需要对其进行配置,来使用ts对其进行检测

npm install @types/lodash --save-dev
1

这样在使用lodash库时如果没有按规则使用的话,ts会提示报错。

# 使用webpackDevServer实现请求转发

在开发环境下使用 webpackDevServer 进行代理转发 配置proxy

module.exports = {
    //...
    devServer:{
        //...
        proxy: {
            // 将/react/api下的接口进行代理
            '/react/api': {
                target: 'http://www.dell-lee.com', // 代理到这个地址上
                pathRewrite: {
                    'header.json': 'demo.json' // 可以不用改变业务代码,直接代理到demo.js上
                },
                headers: {
                    host: 'www.dell-lee.com' // 可以在请求头上添加一些东西
                }
            }
        }
    },
    //...
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# EsLint 在webpack中的配置

npm install eslint eslint-loader --save-dev
1
npm install babel-eslint --save-dev
1

生成eslint配置文件

npx eslint --init
1

然后在webpack中配置一下,添加eslint-loader

module.exports = {
    entry: {
        "main": './src/index.js'
    },
    module: {
        rules: [
            {
                test: /\.js$/,
                exclude: /node_modules/,
                use: ["babel-loader", "eslint-loader"]
            },
        ]
    },
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# webpack 性能提升————提升打包速度

  1. 跟上技术的迭代(node, npm , yarm)
  2. 在尽可能少的模块上的应用loader
  3. plugin尽可能精简并确保可靠
  4. resolve参数合理配置
  5. 使用DllPlugin提高打包速度

创建webpack.dll.js,通过命令在dll文件夹下将用到的库文件单独打包出来,并使用library将打包的vendors挂在全局。 也可以分开打成多个包挂在全局

webpack.dll.js

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

module.exports = {
    mode: 'production',
    entry: {
        vendors: ['react', 'react-dom', 'lodash'] // 也可以打成多个包
    },
    output: {
        filename: '[name].dll.js',
        path: path.resolve(__dirname, '../dll'),
        library: '[name]'// 挂在全局
    },
    plugins: [
        new webpack.DllPlugin({
            name: '[name]',
            path: path.resolve(__dirname, '../dll/[name].manifest.json'),
        })
    ]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

在package.json中添加生成dll文件热键命令

{
    //...
    "scripts": {
        //...
        "build:dll": "webpack --config ./scripts/webpack.dll.js"
    }
  //...
}

1
2
3
4
5
6
7
8
9

安装add-asset-html-webpack-plugin插件

npm install add-asset-html-webpack-plugin --save-dev
1

在webpack.common.js中添加两个插件

module.exports = {
    //...
    plugins: [
        //...
        new AddAssetHtmlWebpackPlugin({
            filepath: path.resolve(__dirname, '../dll/vendors.dll.js')// 将打包的静态资源挂在html上
        }),
        new webpack.DllReferencePlugin({
            manifest: path.resolve(__dirname, '../dll/vendors.manifest.json')// 将打包成的全局变量跟文件内引用的第三方库形成映射
        })
    ],
    //...
}
1
2
3
4
5
6
7
8
9
10
11
12
13

此时,先运行 npm run build:dev 生成所有dll文件 然后再运行启动服务或者打包,降低了多次查找打包第三方库的时间

但是如果我们要拆分多个库,那岂不是要每加一个库我们都要在webpack.common.js中手动添加一个plugin? 所有我们使用node进行一个自动添加的工作。

在webpack.common.js中

var webpack = require('webpack');
var AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin');
var fs = require('fs');

// 定义一个plugins数组,先将之前用的插件添加进去
var plugins = [
    new HtmlWebpackPlugin({
        template: 'src/index.html'
        }), // 打包后在html中自动插入js
    new CleanWebpackPlugin(),// 每次打包之前清除文件
    new webpack.ProvidePlugin({
         _: 'lodash'// 垫片,在遇到"_"时,就会自动加载lodash库
    })
];

// 用node获取所有dll生成的第三方库
const files = fs.readdirSync(path.resolve(__dirname, '../dll'));
// 循环判断file的后缀,根据.dll.js 和 .manifest.json 将所有dll库分别动态加载到plugins中
files.forEach(file => {
    // 找到所有 .dll.js 结尾文件添加,使用插件挂载在html上
    if (/.*\.dll.js/.test(file)) {
        plugins.push(new AddAssetHtmlWebpackPlugin({
            filepath: path.resolve(__dirname, '../dll', file)
        }))
    }
    // 找到所有 .manifest.json 结尾文件添加,使用插件将其映射与全局挂载的变量对应
    if (/.*\.manifest.json/.test(file)) {
        plugins.push(new webpack.DllReferencePlugin({
            manifest: path.resolve(__dirname, '../dll', file)
        }))
    }
})
// 再将plugins数组传给plugins
module.exports = {
    //...
    plugins,
    //.....
}
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

这样,我们只需要在 webpack.dll.js 中添加想要加入到dll中的库,分别进行打包,在文件中引用时就会先到dll中进行查找,如果dll中已经打包过此第三方库,那么再打包的时候就不会再去对其进行打包。

webpack.dll.js

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

module.exports = {
    mode: 'production',
    entry: {
        vendors: ['lodash'],// 将不同的第三方库分开来打包
        react: ['react', 'react-dom'], // 将不同的第三方库分开来打包
        redux: ['redux'], // 将不同的第三方库分开来打包
    },
    output: {
        filename: '[name].dll.js',
        path: path.resolve(__dirname, '../dll'),
        library: '[name]',
    },
    plugins: [
        new webpack.DllPlugin({
            name: '[name]',
            path: path.resolve(__dirname, '../dll/[name].manifest.json'),
        })
    ]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
  1. 控制包文件大小

  2. thread-loader, parallel-webpack, happypack 多进程打包

  3. 合理使用sourceMap

  4. 结合stats分析打包结果

  5. 开发环境内存编译

  6. 开发环境无用插件剔除

# 多页面打包配置

本质上其实是在entry中添加多个入口文件,再在plugins插件中多添加几个 HtmlWebpackPlugin 插件

module.exports = {
    entry: {
        "index" : {

        }
    },
    //...
    plugins: [
        new HtmlWebpackPlugin({
            template: 'src/index.html', // html模板位置
            filename: 'index.html', // 生成的html文件
            chunks: ['runtime', 'vendors', 'main'] // 需要挂在html上的js文件
        }), 
        new HtmlWebpackPlugin({
            template: 'src/index.html', // html模板位置
            filename: 'list.html', // 生成的html文件
            chunks: ['runtime', 'vendors', 'list'] // 需要挂在html上的js文件
        }), 
        new HtmlWebpackPlugin({
            template: 'src/index.html', // html模板位置
            filename: 'test.html', // 生成的html文件
            chunks: ['runtime', 'vendors', 'test'] // 需要挂在html上的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
26

最后我们再对这段配置进行做自动化设计

webpack.common.js

const path = require('path');
var HtmlWebpackPlugin = require('html-webpack-plugin');
var { CleanWebpackPlugin } = require('clean-webpack-plugin');
var webpack = require('webpack');
var AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin');
var fs = require('fs');

//创建一个函数来动态创建加载插件
const makePugins = configs => {

    let plugins = [
        new CleanWebpackPlugin(),// 每次打包之前清除文件
        new webpack.ProvidePlugin({
             _: 'lodash'// 垫片,在遇到"_"时,就会自动加载lodash库
        })
    ];
// 通过判断入口文件的个数名称,来动态添加 HtmlWebpackPlugin 插件
    Object.keys(configs.entry).forEach(item => {
        plugins.push(new HtmlWebpackPlugin({
            template: 'src/index.html', // html模板位置
            filename: `${item}.html`, // 生成的html文件
            chunks: ['runtime', 'vendors', item] // 需要挂在html上的js文件
        }))
    })

    // 获取所有dll生成的第三方库
    const files = fs.readdirSync(path.resolve(__dirname, '../dll'));
    // 循环将所有dll库动态加载到plugins中
    files.forEach(file => {
        if (/.*\.dll.js/.test(file)) {
            plugins.push(new AddAssetHtmlWebpackPlugin({
                filepath: path.resolve(__dirname, '../dll', file)
            }))
        }
        if (/.*\.manifest.json/.test(file)) {
            plugins.push(new webpack.DllReferencePlugin({
                manifest: path.resolve(__dirname, '../dll', file)
            }))
        }
    })
    return plugins;
}

// 将配置项放在对象中保存
const config = {
    entry: {
        "main": './src/index.js',
        "list": './src/list.js'
    },
    module: {
        rules: [
            {
                test: /\.js$/,
                exclude: /node_modules/,
                loader: "babel-loader",
            },
            {
                test: /\.(jpg|png|gif)$/,
                use: {
                    loader: 'url-loader',
                    options: {
                        name: '[name].[ext]',
                        outputPath:'images/',
                        limit: 1024// 图片大小小于限制的时候直接打包到js里,大于的话生成到images/下
                        // 其他用法和file-loader基本一样
                    }
                },
            }
        ]
    },
    performance: false,
    optimization: {
        splitChunks: {
            chunks: 'async',
            minSize: 30000,
            maxSize: 0,
            minChunks: 1,
            maxAsyncRequests: 5,
            maxInitialRequests: 3,
            automaticNameDelimiter: '~',
            automaticNameMaxLength: 30,
            name: true,
            cacheGroups: {
              vendors: {
                test: /[\\/]node_modules[\\/]/,
                priority: -10
              },
              default: {
                minChunks: 2,
                priority: -20,
                reuseExistingChunk: true
              }
            }
        }
    },
    output: {
        path: path.resolve(__dirname, '../dist')
    },
}

// 调用函数生成plugins
config.plugins = makePugins(config);
// 导出
module.exports = config;
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
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
更新时间: 11/8/2019, 4:51:43 PM