海阔凭鱼跃,天高任鸟飞。Hey 你好!我是秦爱德。😄
上篇文章,介绍了webpack的入门基础配置(传送门🚀🚀🚀)。那现在我们就来讲一讲如何通过优化配置项来提高我们的项目构建性能吧。
从项目实战角度出发,我们更关心的还是从项目开发到发布上线。所以优化项可以分为以下几个维度考虑:
开发环境
生产环境优化
在开发环境中,主要是为了代码能够友好的运行起来并且方便我们开发调试,所以我们需要借助devServer
来启动一个本地服务,让代码run
起来。
devServer
用于项目自动化(自动编译,自动打开浏览器,自动刷新浏览器等等)。只会在内存中编译打包,不会对外输出build
包,而是存在于内存中,关闭后会自动删除。
启动开发服务器devServer需要下载包
npm i -D webpack-dev-server
写入基本服务器配置项
devServer: { // 项目构建后路径 contentBase: resolve(__dirname, 'dist'), // 端口号 port: 3000, // 自动打开浏览器 open: true, },
配置完毕后即可启动项目了,我的webpack
是项目维度安装的,所以使用npx
运行
npx webpack 会输出打包结果在dist文件夹 npx webpack-dev-server 只会在内存中编译打包,没有输出
HMR: hot module replacement 热模块替换 / 模块热替换,感应文件变化及时响应到页面,解放F5手动刷新。
只需要在 devServer 中设置 hot 为 true,就会自动开启HMR功能
devServer: { // 开启HMR功能 hot: true }
HMR支持文件:
style-loader
内部默认实现了热模块替换功能修改一个 js 模块所有 js 模块都会刷新,显然这样做会导致热替换效率低下。我们需要修改入口js文件代码,让一个模块发生变化,只会重新构建这一个模块,而不是所有。
// print.js 为入口js之外的js if (module.hot) { // 一旦 module.hot 为true,说明开启了HMR功能。 --> 让HMR功能代码生效 module.hot.accept('./print.js', function() { // 方法会监听 print.js 文件的变化,一旦发生变化,只有这个模块会重新打包构建,其他模块不会。 // 会执行后面的回调函数 print(); }); }
注意:使用 HMR
会导致html文件不能热替换,可修改 entry
入口,将 html 文件引入
entry: ['./src/index.js', './src/index.html']
由于css-loader
将css文件整合到js文件中,会造成如下影响:
1:js文件体积会很大
2:需要先加载js再动态创建style标签,样式渲染速度就慢
解决办法:用MiniCssExtractPlugin.loader
替代style-loader
,提取js中的css成单独文件
需要引入mini-css-extract-plugin
npm i -D mini-css-extract-plugin
// webpack.config.js const MiniCssExtractPlugin = require('mini-css-extract-plugin') { module: { rules: [ { test: /\.css$/, use: [ { loader: MiniCssExtractPlugin.loader, options: { publicPath: '../' } }, ], }, ] }, plugins: [ new MiniCssExtractPlugin({ filename: 'css/main.css' }), ] }
在开发中,我们在写一些高级样式时在老一代浏览器中并不支持,这时候我们需要引入一些loader
来帮我们解决这个问题。
需要引入postcss-loader
postcss-preset-env
npm i -D postcss-loader postcss-preset-env
// webpack.config.js { loader: 'postcss-loader', options: { ident: 'postcss', plugins: () => [ require('postcss-preset-env')(), ], }, },
引入loader后还需在package.json
中定义browserslist
// package.json "browserslist": { // 开发环境 "development": [ // 这里可根据具体业务场景,匹配不同浏览器 "last 1 chrome version", "last 1 firefox version", "last 1 safari version" ], // 生产环境 "production": [ // 满足绝大多数浏览器的兼容 ">0.2%", "not dead", "not op_mini all" ] },
为了兼容老一代浏览器,同样需要将js做一下兼容。
需要引入babel-loader
@babel/preset-env
core-js
@babel/core
{ test: /\.js$/, exclude: /node_modules/, use:[ { loader: 'babel-loader', options: { presets: [ [ '@babel/preset-env', { //按需加载 useBuiltIns: 'usage', // 指定core-js版本 corejs:{ version: 3 }, // 指定兼容到什么版本的浏览器 targets:{ chrome: '60', firefox: '60', ie: '9', safari: '10', edge: '17' } } ] ], cacheDirectory:true } } ], },
source-map
:一种提供源代码到构建后代码的映射的技术 (如果构建后代码出错了,通过映射可以追踪源代码错误)
参数:[inline-|hidden-|eval-][nosources-][cheap-[module-]]source-map
devtool: 'eval-source-map'
以上所列的参数是可以任意组合的,每个组合各有不同。常见的组合可以有7种
这么多种模式,那什么组合才是比较合理的呢?
最终得出最好的两种方案
1:eval-source-map(完整度高,速度快)
2:eval-cheap-module-souce-map(错误提示忽略列但是包含其他信息,速度快)
团队开发,规范自然是必不可少的。个人觉得eslint
太过于严谨,更推荐团队根据自身情况书写一套合适的eslint
规则。
这里引入的是目前流行的一套js风格airbnb
传送门🚀🚀🚀
需要引入eslint-loader
eslint
// webpack.config.js { test: /\.js$/, exclude: /node_modules/, // 忽略node_modules loader: 'eslint-loader', options: { fix: true, // 自动修复 }, }
引入loader之后,还需要在package.json
的eslintConfig
中写入配置
// package.json "eslintConfig": { "extends": "airbnb-base", // 继承airbnb的风格规范 "env": { "browser": true // 可以使用浏览器中的全局变量(使用window不会报错) } }
oneOf
:匹配到 loader
后就不再向后进行匹配,优化生产环境的打包构建速度
babel 缓存
:将 babel
处理后的资源缓存起来,让第二次打包构建时只更新改变内容,其他不变的内容走缓存。故此提高构建速度
// 开启babel缓存 cacheDirectory:true
这里存在一个问题:当文件名没有发生变化的时候,同名文件都是走缓存。会导致修改内容与实际展示内容不一致。
解决办法:使用hash
命名,通过更换文件名来判断哪些文件需要更新。
hash值可分为(hash
、chunkhash
、contenthash
)这里使用contenthash
较为合理
hash:每次 wepack
打包时会生成一个唯一的 hash 值。
chunkhash:根据 模块 生成的 hash
值。隶属于同一个 模块 的 hash
值一样
contenthash:根据文件的内容生成 hash
值,可以保证不同文件 hash
值唯一性
thread-loader
会对其后面的loader
开启多进程打包。
需要引入thread-loader
启动thread-loader的开销比较昂贵,一般项目不建议使用
npm i -D thread-loader
{ loader: 'thread-loader', options: { workers: 2 // 开启2个进程 } },
externals
让某些第三方库不打包
externals: { jquery: 'jQuery' }
压缩样式文件
需要引入optimize-css-assets-webpack-plugin
npm i -D optimize-css-assets-webpack-plugin
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin') plugins: [ new OptimizeCssAssetsWebpackPlugin(), ]
压缩html
需要引入html-webpack-plugin
html-webpack-plugin
会自动将单独打包的样式文件通过link标签引入
npm i -D html-webpack-plugin
const HtmlWebpackPlugin = require('html-webpack-plugin') plugins: [ new HtmlWebpackPlugin({ template: './src/index.html', minify: { // 移除空格 collapseWhitespace: true, // 移除注释 removeComments: true } }), ]
如果把程序想象成一棵树的话,咱们正在使用的有用代码或第三方库代表绿色的树叶。那些废代码,无用代码代表枯萎的枯叶。 tree shaking
就像一只无形的大手可劲儿的摇这棵树,把枯叶(无用代码)给摇下来。
tree shaking(树摇)前提
1:必须使用es6模块化
2:开启production环境
在上述二者前提条件下,webpack会自动将我们的无用代码去除掉
可在 package.json
中写入配置项,控制树摇范围
// 不会对css/less文件tree shaking处理 "sideEffects": ["*.css", "*.less"]
webpack
将js代码打包输出到一个build.js
文件里面,这对于大型项目来说,无疑是致命的。build.js
文件过大,会导致页面加载时间过长、只改动一点代码而要下载整个大文件等等一系列问题。
代码分割 将打包输出的一个大的 bundle.js
文件拆分成多个小文件,这样可以并行加载多个文件,比加载一个文件更快。
实现代码分割有三种方式:
1:多入口拆分(entry 入口使用多个入口文件)
entry: { index: './src/js/index.js', test: './src/js/test.js' }, output: { filename: 'js/[name].[contenthash:10].js', path: resolve(__dirname, 'build') },
2:配置optimization
optimization: { splitChunks: { chunks: 'all' } },
3:import
动态导入语法
import('./test').then(({ a, b }) => { // 文件加载成功~ }).catch(() => { // 文件加载失败~ });
懒加载:当文件需要使用的时候才加载
预加载:在使用之前,提前加载
可通过import
动态导入的方式实现懒加载和预加载
// 将import的内容放在异步回调函数中使用,需要用到的时候再进行加载 // webpackPrefetch: true表示开启预加载 import(/* webpackChunkName: 'test', webpackPrefetch: true */'./test').then(() => { ... });
至此关于webpack优化内容差不多就是这些了。这里总结的可能并不全面,不过应对大部分项目还是足够了。一起共勉,好好学习,天天向上吧。
点赞👍再看,已成习惯!该系列持续更新,你们的一键三连就是我持续写作的最大动力,如果对本篇博客有任何意见和建议,欢迎小伙伴们留言!欢迎来扰!😜😝
本文首发于公众号「前端秦爱德」,欢迎关注。
感兴趣的小伙伴还可以加入我的「秦爱德前端交流群」,我在成都上班儿,群里有大批成都本土优秀大佬以及美女HR,如果你碰巧也是成都的,那就赶快进来吧。
我是秦爱德,一个在互联网夹缝求生的程序员!欢迎关注我的个人公众号“前端秦爱德”