前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >webpack4.41+性能优化(高级篇)

webpack4.41+性能优化(高级篇)

作者头像
砖业洋__
发布2023-05-06 20:16:29
6560
发布2023-05-06 20:16:29
举报
文章被收录于专栏:博客迁移同步博客迁移同步

以下配置是在webpack 4.41.6+测试

可用于生产环境:

  • babel-loader缓存优化
  • ignoreplugin
  • noparse
  • happyPack
  • ParallelUglifyPlugin

不可用于生产环境的:

  • 自动刷新
  • 热更新
  • DllPlugin

babel-loader的缓存优化

代码语言:javascript
复制
module: {
        rules: [
            {
                test: /\.js$/,
                loader: 'babel-loader?cacheDirectory', // 开启缓存
                include: path.resolve(__dirname, 'src'), // 明确范围
                // 排除范围,include和exclude两者选一个就行
                // exclude: path.resolve(__dirname, 'node_modules') 
            }
        ]
    }

这里的?cacheDirectory放在babel-loader后面,把语法转换的代码缓存下来。只要ES6代码没有改变的,第二次编译的时候,这些ES6没有改动的部分就不会重新编译,直接使用缓存,编译速度更快。

或者这样写

代码语言:javascript
复制
          {
            test: /\.js$/,
            exclude: /node_modules/,
            loader: 'babel-loader',
            options: {
              // 开启babel缓存
              // 第二次构建时,会读取之前的缓存
              cacheDirectory: true
            }
          },

一般来说,一个loader写成loader:"babel-loader"这种字符串的形式,多个loader写成use:["babel-loader", "eslint-loader"]字符串数组的形式

happyPack多线程打包

HappyPack是一个通过多线程来提升Webpack打包速度的工具,不是多进程,很多博客写的多进程,为此我查阅githubhappypack插件说明明确说到是多线程。 在打包过程中有一项非常耗时的工作,就是使用loader将各种资源进行转译处理 我们可以简单地将代码转译的工作流程概括如下:

1)从配置中获取打包入口; 2)匹配loader规则,并对入口模块进行转译; 3)对转译后的模块进行依赖查找(如a.js中加载了b.jsc.js); 4)对新找到的模块重复进行步骤2)和步骤3),直到没有新的依赖模块。 不难看出从步骤2)到步骤4)是一个递归的过程,Webpack需要一步步地获取更深层级的资源,然后逐个进行转译。

这里的问题在于Webpack是单线程的,假设一个模块依赖于几个其他模块,Webpack必须对这些模块逐个进行转译。虽然这些转译任务彼此之间没有任何依赖关系,却必须串行地执行。HappyPack恰恰以此为切入点,它的核心特性是可以开启多个线程,并行地对不同模块进行转译,这样就可以充分利用本地的计算资源来提升打包速度。

HappyPack适用于那些转译任务比较重的工程,当我们把类似babel-loaderts-loader迁移到HappyPack之上后,一般都可以收到不错的效果,而对于其他的如sass-loader、less-loader本身消耗时间并不太多的工程则效果一般。

每次webpack解析模块时,HappyPack都会获取它及其所有依赖项,并将这些文件分发到多个工作程序“线程”。如下图

在这里插入图片描述
在这里插入图片描述

在实际使用时,要用HappyPack提供的loader来替换原有loader,并将原有的那个通过HappyPack插件传进去。请看下面的例子

单个loader的优化(一般不用这个方式,都是使用多个loader的优化,多个loader只写一个就是单个loader

代码语言:javascript
复制
// 初始Webpack配置(使用HappyPack前)
module.exports = {
  //...
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: 'babel-loader',
        options: {
          presets: ['react'],
        },
      }
    ],
  },
};

// 使用HappyPack的配置
const HappyPack = require('happypack');
module.exports = {
  //...
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: 'happypack/loader',
      }
    ],
  },
  plugins: [
    new HappyPack({
      loaders: [
        {
          loader: 'babel-loader',
          options: {
            presets: ['react'],
          },
        }
      ],
    })
  ],
};

module.rules中,我们使用happypack/loader替换了原有的babel-loader,并在plugins中添加了HappyPack的插件,将原有的babel-loader连同它的配置插入进去即可。

多个loader的优化 提高构建速度,利用好多核CPU 1.安装happyPack 2.引入const HappyPack = require('happypack') 3.使用

在使用HappyPack优化多个loader时,需要为每一个loader配置一个id,否则HappyPack无法知道rulesplugins如何一一对应。

代码语言:javascript
复制
module: {
        rules: [
            {
                test: /\.js$/,
                // 把对 .js 文件的处理转交给 id 为 babel123 的 HappyPack 实例
                loader: 'happypack/loader?id=babel123',
                include: path.resolve(__dirname, 'src'), // 明确范围
                // 排除范围,include和exclude两者选一个就行
                // exclude: path.resolve(__dirname, 'node_modules') 
            },
            {
		        test: /\.ts$/,
		        include: path.resolve(__dirname, 'src'), // 明确范围
		        loader: 'happypack/loader?id=ts',
		    }
        ]
    },
    plugins: [
		// ...省略其他代码
        // happyPack 开启多线程打包
        new HappyPack({
            // 用唯一的标识符 id 来代表当前的 HappyPack 是用来处理一类特定的文件
            id: 'babel123',
            // 如何处理 .js 文件,用法和 Loader 配置中一样
            use: ['babel-loader?cacheDirectory']
            // 这里写成loaders: ['babel-loader?cacheDirectory']也可以
            // 这里必须用数组形式
        }),
        new HappyPack({
			id: 'ts',
			use: [{
				loader: 'ts-loader',
				options: {}, // ts options
	     	}],
	    })
    ],

如果你的happyPackid对应不上就会报如下错误 AssertionError [ERR_ASSERTION]: HappyPack: plugin for the loader 'babel123' could not be found! Did you forget to add it to the plugin list?...

在使用多个HappyPack loader的同时也就意味着要插入多个HappyPack的插件,每个插件加上id来作为标识。同时我们也可以为每个插件设置具体不同的配置项,如使用的线程数、是否开启debug模式等。

ParallelUglifyPlugin多进程压缩JS

现在的webpack内置Uglify工具压缩js,只要你是生产环境就会自动压缩js(当然你webpack版本太旧是不能自动在生产环境压缩的),因为JS是单线程的,开启多线程会压缩的更快。 1.安装webpack-parallel-uglify-plugin 2.引入const ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin')

代码语言:javascript
复制
plugins: [
		// ...省略部分无关代码
        new ParallelUglifyPlugin({
            // 传递给 UglifyJS 的参数
            // (还是使用 UglifyJS 压缩,只不过帮助开启了多进程)
            uglifyJS: {
                output: {
                    beautify: false, // 最紧凑的输出
                    comments: false, // 删除所有的注释
                },
                compress: {
                    // 删除所有的 `console` 语句,可以兼容ie浏览器
                    drop_console: true,
                    // 内嵌定义了但是只用到一次的变量
                    collapse_vars: true,
                    // 提取出出现多次但是没有定义成变量去引用的静态值
                    reduce_vars: true,
                }
            }
        })
    ],

热更新

热更新:新代码生效,网页不刷新,状态不丢失 自动网页刷新状态会丢失 自动刷新会用到devServer 1.引入const HotModuleReplacementPlugin = require('webpack/lib/HotModuleReplacementPlugin'); 2.在plugins加入配置

代码语言:javascript
复制
plugins: [
        // ...省略其他无关代码
        new HotModuleReplacementPlugin()
    ],

3.在devServer加入hot: true

代码语言:javascript
复制
    devServer: {
        port: 8080,
        progress: true,  // 显示打包的进度条
        contentBase: distPath,  // 根目录
        open: true,  // 自动打开浏览器
        compress: true,  // 启动 gzip 压缩

        hot: true, // ======在这里加入热更新配置=============

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

            // 将本地 /api2/xxx 代理转发到 http://localhost:3000/xxx
            '/api2': {
                target: 'http://localhost:3000',
                pathRewrite: {
                    '/api2': '' // 将路径中的'/api2'变为''空串
                }
            }
        }
    },

举例子: 这里开启devServer,如果不是热更新,我们修改代码会自动刷新整个网页。如果每次刷新都会有网络请求,增加了后台负担;如果填写都表单有数据,网页刷新表单数据会丢失;如果你进了路由都子路由的子路由,层级比较深,而刷新后又回到了根路由… 开启热更新之后,需要热更新部分加上监听

代码语言:javascript
复制
// 增加,开启热更新之后的代码逻辑
if (module.hot) {
    module.hot.accept(['./math.js'], () => {
        const sumRes = sum(10, 30)
        console.log('sumRes in hot', sumRes)
    })
}

那么你只要修改了math.js里面的代码,就只会热更新,执行这里module.hot.accept的第二个参数----回调函数中的内容。 并且这里不会清空你在Console中定义的变量值,不会清空你在input框里面的值,因为它并不会刷新整个网页,仅仅只是针对math.js里面的东西作出响应。

webpack在生产环境的常用优化思路

1.小图片base64编码

代码语言:javascript
复制
module: {
        rules: [
        	// ...省略无关代码
            // 图片 - 考虑 base64 编码的情况
            {
                test: /\.(png|jpg|jpeg|gif)$/,
                use: {
                    loader: 'url-loader',
                    options: {
                        // 小于 5kb 的图片用 base64 格式产出
                        // 否则,依然延用 file-loader 的形式,产出 url 格式
                        limit: 5 * 1024,

                        // 打包到 img 目录下
                        outputPath: '/img1/',

                        // 设置图片的 cdn 地址(也可以统一在外面的 output 中设置,那将作用于所有静态资源)
                        // publicPath: 'http://cdn.abc.com'
                    }
                }
            },
        ]
    },

这个例子,小于5kbbase64产出,url-loader处理,打包到了对应js,这样就不会单独打包成图片,减少网络请求的耗时。 太大对图片就单独打包成图片,避免js文件过大,下载太耗时导致页面渲染卡住。

2.bundle加上hash值

代码语言:javascript
复制
output: {
        // filename: 'bundle.[contentHash:8].js',  // 打包代码时,加上 hash 戳
        filename: '[name].[contentHash:8].js', // name 即多入口时 entry 的 key
        path: path.join(__dirname, '..', 'dist'),
        // publicPath: 'http://cdn.abc.com'  // 修改所有静态文件 url 的前缀(如 cdn 域名),这里暂时用不到
    },

加上contentHash是因为只要文件js内容不变,这个contentHash值就不会变,这样上线之后用户发起请求可以命中缓存,直接取本地缓存,当内容变化之后contentHash变化,缓存失效,再发起请求拉去新的文件。 为什么不用[hash]而是[contentHash],因为webpack每次打包都会有一个hash,而且每次不一样,这样每次还是回去请求新的文件,没有利用到缓存,失去了意义。 后面对:8是取contentHash值的前8位。 CSS操作也是一样,css-loader是将css文件变成commonjs模块加载js中,里面内容是样式字符串,这样CSS文件就放在了打包后的JS文件中,当多个JS引入相同的CSS的时候,如果这样操作,每个打包出来的CSS文件都放在不同的JS文件中,而这些CSS又是重复的样式,所以需要把CSS提取出来减小JS体积,我们一般会对CSS文件命名,这里也是加上了[contentHash:8]

代码语言:javascript
复制
	plugins: [
        // ...省略无关代码
        // 抽离 css 文件
        new MiniCssExtractPlugin({
            filename: 'css/main.[contentHash:8].css'
        })
    ],

3.懒加载和预加载

比较大的文件用懒加载(异步加载)

代码语言:javascript
复制
document.getElementById('btn').onclick = function() {
  // 懒加载~:当文件需要使用时才加载~
  // 预加载 prefetch:会在使用之前,提前加载js文件 
  // 正常加载可以认为是并行加载(同一时间加载多个文件)  
  // 预加载 prefetch:等其他资源加载完毕,浏览器空闲了,再偷偷加载资源
  import(/* webpackChunkName: 'test', webpackPrefetch: true */'./test').then(({ mul }) => {
    console.log(mul(4, 5));
  });
};

为什么选择懒加载呢? 这样可以提高代码覆盖率。也就是一个js里面的代码使用率提高。我们可以在MAC电脑使用快捷键command+shift+P,输入coverage–>选择Show Coverage,然后如下图所示点击

在这里插入图片描述
在这里插入图片描述

如果不使用懒加载,你的代码属于Unused Bytes,使用了之后,你的代码是是属于Used Bytes,我们的目的就是提高Used Bytes,这样就提高了代码覆盖率。优化首先考虑代码覆盖率再才会考虑缓存。

这里写了/* webpackChunkName: 'test', webpackPrefetch: true */ 表示这里的回调函数的内容会打包到chunkName为test到js中,默认entry我们是单入口文件,比如

代码语言:javascript
复制
entry: './src/js/index.js',

实际上等同于

代码语言:javascript
复制
entry: {
	main: './src/js/index.js' // 这个默认的main就是默认的webpackChunkName
}

webpackChunkNamemain,当我们把/* webpackChunkName: 'test' */之后就指定webpackChunkNametest,所以console.log(mul(4, 5));会打包到test.[contentHash:8].js中 当然,你的输出文件名仍然是可以在output修改的

代码语言:javascript
复制
  output: {
    filename: 'js/[name].[contenthash:10].js',
    path: resolve(__dirname, 'build'),
    chunkFilename: 'js/[name].[contenthash:10]_chunk.js' // 这个[name]是你/* webpackChunkName: 'xxx'*/指定的,打包出来就是js/xxx.[contentHash:10]_chunk.js
    // 如果你不指定webpackChunkName,这里就会输出js/[id].[contentHash:10]_chunk.js,以从0开始的数字往后命名,看你webpack打包日志的chunks这一项是什么数字,这个[id]就会显示多少
  },

这个就不多说了,不然篇幅太长。 这里还提到了/* webpackPrefetch: true */,懒加载是等用到的时候再去发起请求获取数据,而预加载是等网络带宽空闲时去加载,比如这里的test.[contentHash:8].js是现在不需要的,后面可能会用到,但是这里是在onclick监听事件里面才import(),属于宏任务,宏任务一定会在尝试一次DOM渲染之后才执行, 所以在这个例子中是渲染一次完成了再去加载,然后当你点击触发获取test.[contentHash:8].js的时候就不用再发起请求了,直接在本地加载,速度看起来更快。预加载目前在一些浏览器和移动端可能不支持。

举个例子:比如网页登录按钮点击之后弹出提示登录的操作,很显然我们需要懒加载这个登录界面,那么如果我点击按钮之后才去请求这个js(创建DOM结点操作显示界面),会不会有点慢,让人感觉会卡顿一下?那么这里的预加载就是很好的一种方案了。在网络带宽空闲的时候会去把这个预加载的js下载下来,再次加载的时候之后从缓存请求这个js,速度就非常快了。

有人可能会问了,这里在onlick事件里面,我没去点击按钮,没触发这个回调你怎么知道我回调函数里面有个预加载或者懒加载?因为DOM事件是宏任务,在你的同步代码执行完=>微任务=>尝试DOM渲染=>宏任务,按照这样的执行顺序来的。如果你不了解JS异步,可以看看这里JS 异步进阶【想要进大厂,更多异步的问题等着你】

4.提取公共代码(我想多说一点东西)

代码语言:javascript
复制
optimization: {
    splitChunks: {
		// initial 入口chunk,对于异步导入的文件不处理
		// async 异步chunk,只对异步导入的文件处理
		// all 全部chunk
      chunks: 'all', // 默认是async
      // 为什么默认是async呢?上面说过了,懒加载会提高代码覆盖率,而拆分同步代码只是利用缓存,优化十分有限,所以默认拆分懒加载的代码,为async!!!!
      // 下面是默认值,可以不写~
      minSize: 30 * 1024, // 分割的chunk最小为30kb
      maxSize: 0, // 最大没有限制
      minChunks: 1, // 要提取的chunk最少被引用1次
      maxAsyncRequests: 5, // 按需加载时并行加载的文件的最大数量
      maxInitialRequests: 3, // 入口js文件最大并行请求数量
      automaticNameDelimiter: '~', // 名称连接符
      name: true, // 可以使用命名规则
      // === 以上为公共规则 ==========
      cacheGroups: {
        // 分割chunk的组的规则
        // node_modules文件会被打包到 vendors 组的chunk中。--> vendors~xxx.js,这个~是名称链接符
        // 满足上面的公共规则,如:大小超过30kb,至少被引用一次。比如vue、vue-router等等
        vendors: {
          test: /[\\/]node_modules[\\/]/,
          // 优先级
          priority: -10
        },
        default: {
          // 要提取的chunk最少被引用2次
          minChunks: 2,
          // 优先级
          priority: -20,
          // 如果当前要打包的模块,和之前已经被提取的模块是同一个,就会复用,而不是重新打包模块
          reuseExistingChunk: true
        } 
      }
    },
    // 将当前模块的记录其他模块的hash单独打包为一个文件 runtime
    // 解决:修改a文件导致b文件的contenthash变化,做代码分割一定要加上runtimeChunk,否则导致缓存失效
    runtimeChunk: {
      name: entrypoint => `runtime-${entrypoint.name}`
    },
    minimizer: [
      // 配置生产环境的压缩方案:js和css,4.26以上的webpack压缩js使用terser-webpack-plugin
      // 压缩js
      new TerserWebpackPlugin({
        // 开启缓存
        cache: true,
        // 开启多进程打包
        parallel: true,
        // 启动source-map
        sourceMap: true
      }),
      // 压缩css
      new OptimizeCSSAssetsPlugin({})
    ]
  }
个人测试例子
代码语言:javascript
复制
// output配置
entry: path.join(__dirname, 'src/index.js'),
output: {
     filename: 'test.js',
     path: path.resolve(__dirname, '../dist'),
     chunkFilename: '[name]_chunk.js'
 },

optimization.splitChunks就是默认配置,和上面一样。

代码语言:javascript
复制
    optimization: {
        splitChunks: {
            chunks: 'all' // 这里要写,不然就是只分割异步代码
            // 后面不写,都是默认配置
        }
    }

代码中自定义webpackChunkNamevConsole

代码语言:javascript
复制
import _ from 'lodash'// 这个在node_modules里面,分割时属于vendors组的规则
import myjs from './lib/myjs' // 这个在我新建的lib文件夹下的目录
// promise方式
if (process.env.NODE_ENV === 'development') {
	let VConsole;
    import(/* webpackChunkName: 'vConsole' */'vconsole/dist/vconsole.min.js').then((module) => {
        VConsole = module.default;
        new VConsole();
    });
}
// 或者async/await方式
async created() {
  if (process.env.NODE_ENV === 'development') {
        try {
            const { default: VConsole } = 
            	await import(/* webpackChunkName: 'vConsole' */ 'vconsole/dist/vconsole.min.js');
            new VConsole();
        } catch (err) {
            console.log(err);
        }
    }
},

打包出来的vendors~vConsole_chunk.jsmain_chunk.jsvendors~main_chunk.js

vendors~vConsole_chunk.js的文件说明

vendors是缓存组cacheGroups的组名字,~是默认的automaticNameDelimiter名称链接符,vConsole _chunk.js就是output中的chunkFilename规则[name]_chunk.js,这里[name]就是由于魔法注释(magic comment)/* webpackChunkName: 'vConsole' */变成vConosle。这里如果打生产包是不会把vconsole打进去的,因为process.env.NODE_ENV === 'development'false

main_chunk.js的文件说明

入口文件默认chunk名为main,这里面有lodash的映射关系,但是lodash库的js不在这。 不过myjs在这里,所以引入的js或者库文件只要不是node_modules目录下的js都会打包在这里

vendors~main_chunk.js的文件说明

默认cacheGroups里面的test: /[\\/]node_modules[\\/]/, 所以node_modules里面的文件都会满足这个拆分规则,[name]vendors,所以node_modules里的lodash包拆分到这里来了,~是默认的automaticNameDelimiter名称链接符,因为入口文件默认chunkNamemainchunkFilename规则[name]_chunk.js,连起来就是main_chunk.js

默认配置的一些属性说明:

chunks: 'all', // 默认是async 为什么默认是async呢?上面说过了,懒加载会提高代码覆盖率,而拆分同步代码只是利用缓存,优化十分有限,所以默认拆分懒加载的代码,为async!!!!

注意:terser-webpack-plugin插件压缩js,而不是uglifyjs-webpack-plugin,在webpack4.26+就用terser-webpack-plugin去压缩js,因为uglifyjs-webpack-plugin不再维护了。

缓存组cacheGroups里面default组里有一个reuseExistingChunk: true,解释一下,比如文件c.js里引入a.jsb.js,而a.js里面又引入里b.js,打包的时候设置reuseExistingChunk: true,则会忽略第二次引入b.js,这样就避免了重复引入b.js

webpack 5开始就不支持{cacheGroup}.name,即

代码语言:javascript
复制
optimization: {
    splitChunks: {
      cacheGroups: {
        commons: {
          test: /[\\/]node_modules[\\/]/,
-         name: 'vendors', // 这里不支持
          chunks: 'all'
        }
      }
    }
  }

这里块名称是commons,那么分割出的包名就是commons.jsname命名无效,默认就是那个组名称。

这里为什么写/[\\/]node_modules[\\/]/而不是/node_modules/ webpack在处理文件路径时,默认在Unix/,在Windows\[\\/]避免在跨平台使用时出现问题

分割chunk组规则里的优先级priority有什么用? 当满足公共规则的时候,比如提取出引入的第三方jquery,既满足vendors组的规则(因为在node_modules路径下),也满足default组的规则的时候,谁的优先级高就匹配对应组的规则,这里-10 > -20,所以打包出来的[name]vendors而不是default

我们建议webpack都升级到4.0以上,如果你还是webpack 4.0以下,那就不得不说一下runtimeChunk,这是为了防止修改a文件导致b文件的contenthash变化,做代码分割一定要加上runtimeChunk,否则可能导致缓存失效。 我们先看现象,再讲解原因

代码语言:javascript
复制
// a.js
export function add(x, y) {
  return x + y;
}
代码语言:javascript
复制
// main.js
import(/* webpackChunkName: 'a' */'./a.js').then(({ add }) => {
  console.log(add(1, 2));
});

举个例子,还是上面的默认分割规则,没有配置runtimeChunk的时候,打包出来如下

main.[contentHash:10].js中存在映射关系,包含了a.[contentHash:10].js文件映射,在html文件的<script>标签只会引入main.e9bc442f61.js文件,而main.e9bc442f61.js会动态创建<script>标签并引入a.ad13327f26_chunk.js

在这里插入图片描述
在这里插入图片描述

如果我修改a.js文件的内容,打包后a.jscontentHash会变化,因为映射关系要对应,从而会导致main.jscontentHash会变化,那么客户端根据缓存发现哈希值不一致,会重新下载。所以我们需要把映射关系从main.[contentHash:10].js提取出来,加上runtimeChunk配置之后,打包如下

在这里插入图片描述
在这里插入图片描述

打包之后 html文件中,<script>只引入main.[contentHash:10]_chunk.jsruntime-main.[contentHash:10].js,映射关系跑到了runtime-main.[contentHash:10].js里面去了,而打开runtime-main.[contentHash:10].js会发现是管理着映射关系,会动态创建<script>标签然后引入a.[contentHash:10]_chunk.js

所以再次修改a.js,就只是runtime-main.[contentHash:10].jsa.[contentHash:10]_chunk.js去变化,main.[contentHash:10]_chunk.js就不会改变。这里main也能变成chunk块了,匹配output.chunkFilename:[name]_chunk.js规则。这样客户端只会拉取下载runtime-main.[contentHash:10].jsmain.[contentHash:10]_chunk.js利用缓存还可以继续使用。

实际上,平时写代码的时候,main.[contenthash:10].js是业务逻辑,vendors.[contenthash:10].js放的是库文件,业务逻辑和库文件的映射关系代码叫做manifest,默认manifest存在于main.[contenthash:10].js中,也存在于vendors.[contenthash:10].js里面,manifest在旧版webpack中打包可能会有差异,正是这种差异导致在旧版中哪怕内容没改变,contenthash值也会发生改变,原因在于包之间的关系或者js之间的关系嵌套在mainvendors文件里面,打包的时候会发生变化。我们把manifest里关联关系的代码抽离出来放在runtime文件里去。这样的话,main里面就是业务相关的代码,vendors就是库文件代码,关联关系代码放在runtime文件,这样打包后main文件的contenthashvendors文件的contenthash都不会变,这样新旧版本都实现了每次打包只要内容不变,contenthash就不改变的情况。

5.IgnorePlugin

在项目中可能有几处体积占用较大的库,其中一个便是moment.js这个日期处理库。对于一个日期处理的功能,为何这个库会占用如此大的体积,仔细查看发现当引用这个库的时候,所有的locale文件都被引入,而这些文件甚至在整个库的体积中占了大部分,因此当webpack打包时移除这部分内容会让打包文件的体积有所减小。 webpack自带的两个库可以实现这个功能:

代码语言:javascript
复制
IgnorePlugin
ContextReplacementPlugin

IgnorePlugin的使用方法如下:

代码语言:javascript
复制
// 插件配置
plugins: [
  // 忽略moment.js中所有的locale文件
  new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
],
// 使用方式
const moment = require('moment');
// 引入zh-cn locale文件
require('moment/locale/zh-cn');
moment.locale('zh-cn');

复制代码ContextReplacementPlugin的使用方法如下:

代码语言:javascript
复制
// 插件配置
plugins: [
  // 只加载locale zh-cn文件
  new webpack.ContextReplacementPlugin(/moment[\/\\]locale$/, /zh-cn/),
],
// 使用方式
const moment = require('moment');
moment.locale('zh-cn');

复制代码通过以上两种方式,moment.js的体积大致能缩减为原来的四分之一。

6.CDN加速

你要引入一个库,但是这个库的在线js比较慢,你可以放到CDN。 如果你最终是在线页面,你会把这些资源包上传到公司的CDN或者自己的CDN,你可以这么写

代码语言:javascript
复制
output: {
        // filename: 'bundle.[contentHash:8].js',  // 打包代码时,加上 hash 戳
        filename: '[name].[contentHash:8].js', // name 即多入口时 entry 的 key
        path: path.join(__dirname, '..', 'dist'),
        publicPath: 'http://cdn.abc.com'  // 修改所有静态文件 url 的前缀(如 cdn 域名),这里暂时用不到
    },

这里的publicPath写为你公司的CDN或者自己的CDN,打包之后是这样的

在这里插入图片描述
在这里插入图片描述

如果不写,那么publicPath默认是相对路径,相对于根目录

在这里插入图片描述
在这里插入图片描述

如果你最终是会变成下载下来的本地包加载,那么就不用写在线CDNURL了,直接写上publicPath: '/'或者publicPath: './',根据你的的资源最后打包出来的路径选择 这个publicPath也可以写在loaderoptions里面,比如写在url-loader里面,去解析图片,这样打包出来的东西大于指定范围limit的东西会变成file-loader处理输出,outputPath决定输出路径,而publicPath的可以改变在线CDN的前缀路径。

7.使用production

  • 会自动开启代码压缩
  • vuereact等会自动删掉调试代码(如开发环境的warning
  • 启动Tree Shaking(1. 必须使用ES6模块化import引入 2. 开启production环境)

说一下Tree Shaking摇树,如果是开发环境,如果JS中有很多函数,而我只import了一个函数,打包的时候会把所有的函数代码打包进去,而生产环境,就只会引入你用到的那个函数。 形象比喻:树上很多果子代表函数,你只要一个果子,生产环境就是就会把整个树上无用的果子摇掉,简称“摇树Tree Shaking

为什么必须使用ES6模块化import引入才能Tree Shaking呢?

  • ES6 Module是静态引入,编译时引入
  • Commonjs是动态引入,执行时引入
  • 只有ES6 Module才能静态分析,实现Tree Shaking Commonjs执行的时候才知道哪个函数需要哪个不需要,Commonjs就不能实现编译的时候摇树

commonjs可以加上条件判断去引入,因为动态执行的时候根据条件变化可以执行,而ES6 Module静态编译的时候无法确定条件,会直接报错告诉你Module parse failed: 'import' and 'export' may only appear at the top level只能出现在最外层,外层不能再加条件判断了。

代码语言:javascript
复制
const flag = true
if (flag) {
	import test from './test
} // 会直接报错
代码语言:javascript
复制
const flag = true
if (flag) {
	require('./test')
} // 完全没问题

8.Scope Hosting

创建函数作用域更少,体积更小,可读性更好,现在的webpack自动集成了这一功能 以前引入一个js,默认打包的时候就会产生一个新的作用域,当引入文件比较多的时候就产生了很多作用域,现在的webpack将这些代码优化在了一个作用域,减小了体积。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2020-07-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • babel-loader的缓存优化
  • happyPack多线程打包
  • ParallelUglifyPlugin多进程压缩JS
  • 热更新
  • webpack在生产环境的常用优化思路
    • 1.小图片base64编码
      • 2.bundle加上hash值
        • 3.懒加载和预加载
          • 4.提取公共代码(我想多说一点东西)
            • 个人测试例子
          • 5.IgnorePlugin
            • 6.CDN加速
              • 7.使用production
                • 8.Scope Hosting
                相关产品与服务
                内容分发网络 CDN
                内容分发网络(Content Delivery Network,CDN)通过将站点内容发布至遍布全球的海量加速节点,使其用户可就近获取所需内容,避免因网络拥堵、跨运营商、跨地域、跨境等因素带来的网络不稳定、访问延迟高等问题,有效提升下载速度、降低响应时间,提供流畅的用户体验。
                领券
                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档