webpack打包体积优化
webpack打包的体积越小,对于部署应用的网站来说,性能越好,加载速度越快。
1. 分析打包文件
1. 生成统计信息文件
首先需要通过webpack命令生成统计信息的文件。在package.json的脚本中添加命令
"scripts": { "stats": "webpack --config webpack.prod.js --profile --json > stats.json", //... }
上面的命令会在根目录下生成一个stats.json的打包统计信息文件。
2. 可视化分析
使用插件可视化分析插件:webpack-bundle-analyzer
npm install --save-dev webpack-bundle-analyzer
配置插件的使用信息;
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; module.export = { //... plugins: [ new BundleAnalyzerPlugin({ analyzerMode: 'disabled', // 关闭默认启动的展示信息的http服务器 generateStatsFile: true // 打包的时候生成stats.json文件; }), }
从上面配置信息可知,使用该插件,不需要再手动生成stats.json文件,第一步可以省略。
然后,在脚本中添加手动启动分析器的http
// 分析命令的使用,应该在打包命令之后。因为它的作用就是分析打包后的文件 "scripts": { "build": "webpack --config webpack.prod.js", "analyzer": "webpack-bundle-analyzer ./dist/stats.json --port 8081" }
3. 优化建议
将生成的stats.json文件拖入该网站https://webpack.jakoblind.no/optimize/。
会给出打包体积太大的优化措施。
该插件基于webpack-optimize-helper插件。
2. 抽离css并压缩
1.抽离css
使用mini-css-extract-plugin抽离css
npm install --save-dev mini-css-extract-plugin
在plugins配置文件中使用插件
module.exports = { plugins: [ new MiniCssExtractPlugin({ filename: 'css/[name].[contenthash:8].css' // 将css文件统一放入css文件夹 }) ] }
2. 压缩css
使用optimize-css-assets-webpack-plugin压缩css文件
npm install -D optimize-css-assets-webpack-plugin
在optimization中使用该插件
module.exports = { optimization: { minimize: true, minimizer: [ new OptimizeCssAssetsWebpackPlugin() ] }
3. 移除未使用的css
在大型项目中,经常会有很多样式内容,在代码中根本未使用,但是会被打包,这些样式需要打包时应该移除。
使用purgecss-webpack-plugin移除未使用的css样式。
npm i purgecss-webpack-plugin -D
在pluigns中配置插件
const glob = require('glob'); // 根据路径查找文件 module.exports = {
plugins:[ new PurgecssWebpackPlugin({ //paths要求是绝对路径 paths: glob.sync(`${path.join(__dirname, 'src/**/*')}`, { nodir: true }) })
]
3. 复用babal转化时runtime代码
@babel/plugin-transform-runtime
✅该插件的引入只能通过.babelrc文件,不能在babel-loader的options中,否则报错。
4. 使用CDN
1. 第三方库使用CDN
new HtmlWebpackExternalsPlugin({ externals: [ { module: 'lodash', global: '_', entry: 'https://cdn.jsdelivr.net/npm/lodash@4.17.15/lodash.min.js' } ] })
然后为了避免用户在模块中手动又导入,需要添加
externals: { lodash: '_' },
2. 项目生成的静态文件可以部署到CDN服务器中
需要配置publicPath为CDN服务器的域名
// output, module->MiniCssWebpackLoader.loader, image等可以部署publicPath publicPath: 'http://lyralee.com'
5. webpack.IgnorePlugin
忽略第三方库中多余的文件夹。
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/)
上面忽略了moment库中的语言包
6. 图片压缩image-webpack-loader
{ test: /\.(gif|png|jpe?g|svg)$/i, use: [ { loader: 'url-loader', options: { limit: 2 * 1024, outputPath: 'images', name: '[name].[contenthash:8].[ext]' } }, { loader: 'image-webpack-loader', options: { mozjpeg: { progressive: true, quality: 65 }, // optipng.enabled: false will disable optipng optipng: { enabled: false }, pngquant: { quality: [0.65, 0.90], speed: 4 }, gifsicle: { interlaced: false }, // the webp option will enable WEBP webp: { quality: 75 } } } ] }
7. 使用babel-polyfill的替代方案
如果项目中允许使用外部链接,可以使用
<script src="https://polyfill.io/v3/polyfill.min.js/"></script>
它按照浏览器类型返回需要的内容
8. TreeShaking
1. 原理:
1)利用es6模块的静态结构,依赖模块命令import/export,进行代码分析,分析出无用/永远不会访问的代码。
1)对于自己写的代码,禁止babel将esModule转为require rules: [ { test: /\.jsx?$/, use: { loader: 'babel-loader', options: { // 设置{moduels: false} 则保留ES6的import/export presets: [['@babel/preset-env', {modules: false}], '@babel/preset-react'], plugins: [ ['@babel/plugin-proposal-decorators', {legacy: true}], ['@babel/plugin-proposal-class-properties', {loose: true}] ] }, }, exclude: /node_modules/ } ] 2) 对于第三方库,要使用es模块的包, 如: lodash-es(有问题,先不用) import {join } from 'lodash-es'
2)通过terser-webpack-plugin移除分析出的多余代码
// ⚠️只要写了optimization属性(即使是空),即使是production,也不会启动该Terser插件 ; 如果是完整的配置,但是mode=development,该属性也不起作用 optimization: { minimizer: [ new TerserWebpackPlugin({ parallel: true, cache: true }) ], ... },
2. 注意事项
1. 要使TreeShaking起作用,必须使用ES6语法import/export 2. 减少sideEffects(副作用)代码的书写 3. 引入的第三方库,需要引入他们的ES版本
3. 特殊情形
当多入口文件存在时,只要有一个入口中使用了某方法,就不会被TreeShaking掉
9. 代码分割
1. 入口文件代码分割
entry: { index: './src/index.js', vendors: ['lodash'] }
2. 动态import导入实现懒加载
当代码中触发使用import()动态引入文件时,会发起请求,并返回一个webpackJsonp方法,
该方法中实现代码的按需加载。
另外,React16.6.0中引入的React.lazy()就是基于该方法实现模块的代码分割和懒加载。
为了提高网站的性能,可以配合webpackPrefetch实现预加载。
window.addEventListener('click', function(e) { import(/*webpackPrefetch: true */ /*webpackChunkName: 'hello' */'./test.js').then(() => { console.log('代码分割') }) });
它的实际作用是在入口文件中生成一段js脚本,脚本用于向html页面插入以下标签
<link rel="prefetch" as="script" href="hello.c671.js"></link>
实际应用
react16.6.0中添加了React.lazy()和React.Suspense组件。结合React-router用于实现代码分割。
React.lazy()加载的组件,必须用于React.Suspense组件中。React.Suspense组件含有一个fallback属性,用于添加加载过程的过渡效果。
最好的是使用浏览器的prefetch性能。如: webpackPrefetch属性
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'; import React, { Suspense, lazy } from 'react'; const Home = lazy(() => import(/*webpackPrefetch: true*/'./routes/Home')); const About = lazy(() => import(/*webpackPrefetch: true*/'./routes/About')); const App = () => ( <Router> <Suspense fallback={<div>Loading...</div>}> <Switch> <Route exact path="/" component={Home}/> <Route path="/about" component={About}/> </Switch> </Suspense> </Router> );
3. 使用splitChunks插件
webpack4.0中默认加入了splitchunks插件,并设置了默认属性。默认只分割异步加载(按需)的代码。但是通过optimization.splitChunk,可以修改配置。
对于import()加载的代码,任何情况下都可以分割,而且没有大小限制。
非import()加载的代码,如果想要实现webpack默认代码分割,其规则如下:
1. 代码块在模块之间共享或者来自node_modules 2. 代码块的大小至少30Kb,即30000byte 3. 并行加载的按需代码块<=5 4. 页面初始化的并行加载代码块<=3
要满足4,5 需要修改代码块的体积。
其默认配置如下:
optimization: { splitChunks: { /** * chunks的有效类型: * 1. async: 默认值,异步;如import('./test.js),以及test.js中同步方式引入的模块 * 2. initial: 同步;设为该值,代码中import,require引入的模块会自动根据规则分割代码 * 3. all: 所有类型 * 也可以是函数function(chunk) {} */ chunks: 'async', minChunks: 1, // 模块至少被引用的次数 /** * 配置的优先级顺序 * maxInitialRequest/maxAsyncRequests < maxSize < minSize */ minSize: 30000, // 约30Kb maxSize: 0, // 用于http2和长期缓存 maxAsyncRequests: 5, // 异步并行加载的最大代码块数量 maxInitialRequests: 3, // 页面初始化加载的最大代码块数量 automaticNameDelimiter: '~', // 自动生成的名字之间的间隔符号 /** * 值为true表示基于cacheGroups的key值自动生成名称; * 也可以是函数name (module, chunks, cacheGroupKey) {return } * ⚠️名称不能和入口代码块名称相同,否则会被移除 * production模式下建议使用false */ name: true, /** * cacheGroups可以继承和覆盖????提到的属性; * 它还有三个额外的属性: * 1. test * 2. priority * 3. reuseExistingChunk * 禁止默认缓存行为可以设置default: false */ cacheGroups: { // 默认node_modules中的代码打入venders中 vendors: { /** * test指定被打入该代码块的模块 * 1. regExp * 2. 函数function(module, chunks) {} */ test: /[\\/]node_modules[\\/]/, priority: -10, name: 'xxx', // 覆盖自动生成的名字 enforce: true //忽略继承的minSize等属性,总是生成代码块 }, // 默认本地代码的打包行为 default: { minChunks: 2, priority: -20, reuseExistingChunk: true } } } },
可以根据上面的配置信息,自定义需要打包的模块
// 将node_modules中的react和react-dom打包 optimization: { splitChunks: { vendors: { test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/, name: 'vendors', chunks: 'all' } } },
- 上一篇 »webpack 之,10 css 提取,兼容,压缩
- 下一篇 »Webpack 学习2