优化Webpack打包构建速度

201 阅读4分钟

优化babel-loader

// webpack.dev.js
module.exports = {
    module:{
        rules:[
             {
                test: /\.js$/,
                loader: ['babel-loader?cacheDirectory'], // 开启缓存
                include: path.resolve(__dirname, 'src'), // 明确范围
                // 排除范围,include和exclude 两者选一个即可
                // exclude: path.resolve(__dirname, 'node_modules')
            }
        ]
    }
}

IgnorePlugin

  • IgnorePlugin避免引入无用模块
// 栗子
// moment会默认引入所有语言包js代码,代码量很大 
// index.js
import moment from 'moment';
// webpack配置好以后,手动引入中文包
import 'moment/locale/zh-cn';

// webpack.prod.js
module.exports = {
    plugins: [
        // 忽略moment 下的locale目录
        new webpack.IgnorePlugin(/\.\/locale/, /moment/)
    ]
}

noPase

  • 避免重复打包
module.exports = {
    module: {
        // 检验到这个文件名就不会打包
        noParse: [/vue\.min\.js$/]
    }
}

IgnorePugin VS noParse

  • IgnorePlugin直接不引入代码,代码中没有,需要什么手动引入
  • IgnorePlugin减少产出的体积
  • noParse引入代码,但不打包,也不进行编译,不进行模块化的分析
happyPack
  • happyPack多进程打包
  • JS单线程,开启多进程打包
  • 提高构建速度(特别是多核CPU)
npm install --save-dev happypack

// webpack.prod.js
const HappyPack = require('happypack')

module.exports = {
    module: {
        rule: [
            // js
            {
                test: /\.js$/,
                // 把对 .js 文件的处理转交给 id 为 babel 的 HappyPack 实例
                use: ['happypack/loader?id=babel'],
                include: path.resolve(__dirname, 'src'),
                // exclude: /node_modules/
            },
        ]
    },
    plugins: [
        // happyPack 开启多进程打包
        new HappyPack({
            // 用唯一的标识符 id 来代表当前的 HappyPack 是用来处理一类特定的文件
            id: 'babel',
            // 如何处理 .js 文件,用法和 Loader 配置中一样
            loaders: ['babel-loader?cacheDirectory']
        }),
    ]
}

ParallelUglifyPlugin

  • 多进程压缩js
  • webpack内置Uglify工具压缩JS
  • JS单线程,开启多进程压缩更快
  • 和HappyPack同理
// webpack.prod.js
module.exports = {
    plugins: [
        // 使用 ParallelUglifyPlugin 并行压缩输出的 JS 代码
        new ParallelUglifyPlugin({
            // 传递给 UglifyJS 的参数
            // (还是使用 UglifyJS 压缩,只不过帮助开启了多进程)
            uglifyJS: {
                output: {
                    beautify: false, // 最紧凑的输出
                    comments: false, // 删除所有的注释
                },
                compress: {
                    // 删除所有的 `console` 语句,可以兼容ie浏览器
                    drop_console: true,
                    // 内嵌定义了但是只用到一次的变量
                    collapse_vars: true,
                    // 提取出出现多次但是没有定义成变量去引用的静态值
                    reduce_vars: true,
                }
            }
        })
    ]
}

关于开启多进程

  • 如果项目较大,打包较慢,开启多进程能提高速度
  • 如果项目较小,打包很快,开启多进程会降低速度(进程开销)

自动刷新

  • 指保存代码以后,浏览器会自动刷新,作用有别于热更新
  • 一般用webpack-dev-server,知道有这么个东西就行了
// webpack.dev.js
module.exports = {
    watch: true, // 开启监听,默认为 false
    // 注意,开启监听之后,webpack-dev-server 会自动开启刷新浏览器!!!
    
    // 监听配置
    watchOptions: {
        ignored: /node_modules/, // 忽略哪些
        // 监听到变化发生后会等300ms再去执行动作,防止文件更新太快导致重新编译频率太高
        // 默认为 300ms
        aggregateTimeout: 300,
        // 判断文件是否发生变化是通过不停的去询问系统指定文件有没有变化实现的
        // 默认每隔1000毫秒询问一次
        poll: 1000
    }
}

热更新

  • 自动刷新:整个网页全部刷新,速度较慢
  • 自动刷新: 整个网页全部刷新,状态会丢失,如:表单输入全部被清除,路由回到首页等缺点
  • 热更新:新代码生效,网页不刷新,状态不丢失
  • 热更新:有代价,需要在开发环境下去监听或注册哪些模块我们需要热更新,模块化之外的还是以自动刷新的方式去更新。
const path = require('path')

const srcPath = path.join(__dirname, '..', 'src')
const distPath = path.join(__dirname, '..', 'dist')
// webpack.dev.js
// 引入热更新模块,entry配置修改,插件配置新增,dev-server配置设置hot:true
const HotModuleReplacementPlugin = require('webpack/lib/HotModuleReplacementPlugin');

module.exports = {
     entry: {
        // index: path.join(srcPath, 'index.js'),
        index: [
            'webpack-dev-server/client?http://localhost:8080/',
            'webpack/hot/dev-server',
            path.join(srcPath, 'index.js')
        ],
        other: path.join(srcPath, 'other.js')
    },
    plugins: [
        new HotModuleReplacementPlugin()
    ],
    devServer: {
        port: 8080,
        progress: true,  // 显示打包的进度条
        contentBase: distPath,  // 根目录
        open: true,  // 自动打开浏览器
        compress: true,  // 启动 gzip 压缩

        hot: true,

        // 设置代理
        proxy: {
            // 将本地 /api/xxx 代理到 localhost:3000/api/xxx
            '/api': 'http://localhost:3000',

            // 将本地 /api2/xxx 代理到 localhost:3000/xxx
            '/api2': {
                target: 'http://localhost:3000',
                pathRewrite: {
                    '/api2': ''
                }
            }
        }
    },
}

DllPlugin

  • DllPlugin动态链接库插件
  • 前端框架如Vue React,体积大,构建慢
  • 但是框架较稳定,不常升级版本
  • 同一个版本只构建一次即可,不用每次都重新构建
  • webpack已内置DllPlugin支持
  • DllPlugin - 打包出Dll文件
  • DllReferencePlugin - 使用dll文件 // 栗子
  1. 第一步使用DllPlugin打包出react.dll.js
// webpack.dll.js
const path = require('path')
const DllPlugin = require('webpack/lib/DllPlugin')
const { srcPath, distPath } = require('./paths')

module.exports = {
    mode: 'development',
    // JS 执行入口文件
    entry: {
        // 把 React 相关模块的放到一个单独的动态链接库
        react: ['react', 'react-dom']
    },
    output: {
        // 输出的动态链接库的文件名称,[name] 代表当前动态链接库的名称,
        // 也就是 entry 中配置的 react 和 polyfill
        filename: '[name].dll.js',
        // 输出的文件都放到 dist 目录下
        path: distPath,
        // 存放动态链接库的全局变量名称,例如对应 react 来说就是 _dll_react
        // 之所以在前面加上 _dll_ 是为了防止全局变量冲突
        library: '_dll_[name]',
    },
    plugins: [
        // 接入 DllPlugin
        new DllPlugin({
          // 动态链接库的全局变量名称,需要和 output.library 中保持一致
          // 该字段的值也就是输出的 manifest.json 文件 中 name 字段的值
          // 例如 react.manifest.json 中就有 "name": "_dll_react"
          name: '_dll_[name]',
          // 描述动态链接库的 manifest.json 文件输出时的文件名称
          path: path.join(distPath, '[name].manifest.json'),
        }),
    ],
}
  1. 第二步在html模板文件中引入react.dll.js
// index.html
...
<html>
    <body>
        <script src="./react.dll.js"></script>
    </body>
</html>
  1. 第三步在webpack.dev.js中使用DllReferencePlugin插件忽略对Dll文件的打包
module.exports = {
    plugins: [
        // 第三,告诉 Webpack 使用了哪些动态链接库
        new DllReferencePlugin({
            // 描述 react 动态链接库的文件内容
            manifest: require(path.join(distPath, 'react.manifest.json')),
        }),
    ],
}

可用于生产环境

  • babel-loader
  • IgnorePlugin
  • noParse
  • happPack
  • ParallelUglifyPlugin

不可用于生产环境

  • 自动刷新
  • 热更新
  • DllPlugin