-
- 1、devtool 配置 (找到报错位置)
- 2、优化打包速度
- 3、oneOf 每个文件只被一个loader处理
- 4、 include/exclude 处理某些文件或者排除某些文件
- 5、 cache 缓存 (提升后面几次的打包速度)
- 6、 多进程打包
- 7、减少代码体积 Tree Shaking
- 8、压缩图片
- 9、多入口
- 10、 多入口想提取公共模块,这个时候就用到了代码分割
- 11、chunk统一命名
- 12、 Preload和Prefetch
- 13、配置缓存,文件发生变化时,只有main 和runtime.js会重新打包,其他文件不变
- 14、 core-js
- 15、PWA
总结
我们从 4 个角度对 webpack 和代码进行了优化:
- 1.提升开发体验
。使用 sourceMap让开发或上线时代码报错能有更加准确的错误提示。 - 2.提升 webpack 提升打包构建速度
。使用 HotmduleReplacement让开发时只重新编译打包更新变化了的代码,不变的代码使用缓存,从而使更新速度更快。
。使用 one0f让资源文件一旦被某个 loader 处理了,就不会继续遍历了,打包速度更快。
。使用 Include/Exclude排除或只检测某些文件,处理的文件更少,速度更快。
。使用cache 对 eslint 和 babel 处理的结果进行缓存,让第二次打包速度更快。
。使用 Thead 多进程处理 esint和 babel 任务,速度更快。(需要注意的是,进程启动通信都有开销的,要在比较多代码处理时使用才有效
果) - 3.减少代码体积
。使用 Tree shaking剔除了没有使用的多余代码,让代码体积更小。
。使用 @babel/plugin-transform-runtime 插件对 babel 进行处理,让辅助代码从中引入,而不是每个文件都生成辅助代码,从而体积更小。。使用 image miniizer 对项目中图片进行压缩,体积更小,请求速度更快。(需要注意的是,如果项目中图片都是在线链接,那么就不需要了。本地项目静态图片才需要进行压缩。) - 4.优化代码运行性能
。使用 code split 对代码进行分割成多个i5文件,从而便单个文件体积更小,并行加载is速度更快。并通过 import 动态导入语法进行按需加载,从而达到需要使用时才加载该资源,不用时不加载资源。
使用 Preload/Prefetch,对代码进行提前加载,等未来需要使用时就能直接使用,从而用户体验更好。使用 Network cache能对输出资源文件进行更好的命名,将来好做缓存,从而用户体验更好。
。便用 core-js 对js 进行兼容性处理,让我们代码能运行在低版本浏览器。
。使用PA能让代码离线也能访问,从而提升用户体验。
1、devtool 配置 (找到报错位置)
SourceMap(源代码映射)是一个用来生成源代码与构建后代码一一映射的文件的方案。它会生成一个 xx.map 文件,里面包含源代码和构建后代码每一行、每一列的映射关系。当构建后代码出错了,会通过 xxx.map 文件,从构建后代码出错位置找到映射后源代码出错位置,从而让浏览器提示源代码文件出错位置,帮助我们更快的找到错误根源。
开发模式用
javascript
devtool: 'cheap-module-source-map',
- 优点:打包编译速度快,只包含行映射
- 缺点:没有列映射
生产模式用
javascript
devtool: 'source-map',
- 优点:包含行/列映射
- 缺点:打包编译速度更慢
build: slowest rebuild: slowest
2、优化打包速度(HMR)
HMR (HotModuleReplacement),即热模块替换,是 webpack 的一个功能,它可以在运行时更新模块,而无需完全刷新整个页面。
javascript
devServer: {
...
hot:true, // 提供HMR功能,只更新某个模块,没有替换整个项目
},
在模块中判断
javascript
if(module.hot){
module.hot.accept('./xxx')
}
实际开发并不需要上述这样,直接配置loader,以vule为例 vue-loader,生产模式不需要
3、oneOf 每个文件只被一个loader处理
未配置时,一个loader解析之后,其他loader还要解析,配置oneOf之后,每个文件一个loader解析之后就不往下解析了
javascript
module: {
rules: [
// oneOf 不支持 vue-loader ,要单独拿出来
{
test: /\.vue$/,
loader: "vue-loader",
},
// 每个文件只有一个loader配置处理
{
oneOf: [
{
test: /\.(gif|png|jpe?g)$/i,
type: "asset",
parser: {
dataUrlCondition: {
// 小于10kb的图片转成base64,减少请求数量
// 缺点:体积会大一点
maxSize: 10 * 1024, // 小于10kb
},
},
generator: {
// 输出图片的名称
filename: "imgs/[name].[contenthash:8][ext]",
},
},
]
}
],
},
- 报错 [VueLoaderPlugin Error] vue-loader 15 currently does not support vue rules with oneof
解决方案:将vue-loader 移到配置外面,单独拿出来
4、 include/exclude 处理某些文件或者排除某些文件
javascript
module: {
rules: [
{
test: /\.(js|ts|jsx|tsx)$/,
include: path.resolve(__dirname, 'src'),
// exclude: /node_modules/,
// loader:"babel-loader",
loader: 'esbuild-loader',
generator: {
target: 'es2015'
}
},
]
}
include和exclude的只写一个即可
javascript
plugins: [
new ESLintPlugin({
// 配置哪些目录需要检查
context: path.resolve(__dirname, './src'),
exclude: 'node_modules',// 不写 默认也有
}),
]
5、 cache 缓存 (提升后面几次的打包速度)
对eslint检查和babel编译进行缓存,提升打包速度,cacheDirectory 是否开启缓存。
javascript
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'],
cacheDirectory: true, // 开启babel缓存
cacheCompression: false,// 关闭缓存文件压缩
},
},
},
eslint 开启缓存
javascript
new ESLintPlugin({
// 配置哪些目录需要检查
context: path.resolve(__dirname, './src'),
exclude: 'node_modules',// 不写 默认也有
cache: true,// 开启缓存
cacheLocation: path.resolve(__dirname, '../node_modules/.cache/eslintcache'),
}),
6、 多进程打包
javascript
const os = require('os');
const threads = os.cpus().length;
- 下载 thread-loader
bash
npm install thread-loader --D
在loader配置多进程
javascript
const TerserWebpackPlugin = require("terser-webpack-plugin");
module.exports = {
...
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: [{
loader: 'thread-loader',
options: {
workers: threads, // 开启几个子进程去完成
}
},
{
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'],
cacheDirectory: true, // 开启babel缓存
cacheCompression: false,// 关闭缓存文件压缩
},
},]
},
]}
}
在eslint中开启多进程
javascript
new ESLintPlugin({
// 配置哪些目录需要检查
context: path.resolve(__dirname, './src'),
cache: true,// 开启缓存
cacheLocation: path.resolve(__dirname, '../node_modules/.cache/eslintcache'),
threads,
}),
引入压缩的插件
javascript
const TerserWebpackPlugin = require("terser-webpack-plugin");
在plugins中配置
javascript
new TerserWebpackPlugin({
parallel: threads, // 开启多进程
extractComments: false, // 移除注释
terserOptions: {
compress: {
pure_funcs: ['console.log'], // 移除console.log
},
},
}),
在optimization添加压缩配置
javascript
// 用于处理压缩
optimization: {
...
minimizer: [
// CssMinimizerPlugin、TerserWebpackPlugin 也可以放在plugins中
new CssMinimizerPlugin(),
new TerserWebpackPlugin({
parallel: threads, // 开启多进程
extractComments: false, // 移除注释
terserOptions: {
compress: {
pure_funcs: ['console.log'], // 移除console.log
},
},
}),
],
},
7、减少代码体积 Tree Shaking
减少babel体积
禁用babel自动对每个文件的runtime注入,@babel/plugin-transform-runtime 优化代码转换的运行时行为,通过复用辅助函数和减少全局污染来提升代码质量
- 下载
bash
npm i @babel/plugin-transform-runtime --D
在babel中配置
javascript
{
test: /\.js$/,
exclude: /node_modules/,
use: [{
loader: 'thread-loader',
options: {
workers: threads, // 开启几个子进程去完成
}
},
{
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'],
cacheDirectory: true, // 开启babel缓存
cacheCompression: false,// 关闭缓存文件压缩
plugins: ['@babel/plugin-transform-runtime'],
},
},]
},
8、压缩图片
下载包
bash
npm i image-minimizer-webpack-plugin --D
无损压缩(包下载不下来)
bash
npm install imagemin-gifsicle imagemin-jpegtran imagemin-optipng imagemin-svgo --D
有损压缩
bash
npm install imagemin-gifsicle imagemin-jpegtran imagemin-pngquant imagemin-svgo --D
包不好下
javascript
optimization: {
...
minimizer: [
...
new ImageMinimizerPlugin({
minimizer: {
implementation: ImageMinimizerPlugin.imageminGenerate,
options: {
plugins: [
['gifsicle', { interlaced: true }],
['jpegtran', { progressive: true }],
['optipng', { optimizationLevel: 5 }],
[
'svgo',
{
plugins: [
'preset-default',
"prefixIds",
{
name: "sortAttrs",
params: {
xmlnsOrder: "alphabetical"
}
}
]
},
],
]
}
}
})
],
},
9、多入口
javascript
const HtmlWebpackPlugin=require("html-webpack-plugin");
module.exports ={
// entry:'./src/main.js',// 只有一个入口文件,单入口
entry:{ // 有多个入口文件,多入口
app:"./src/app.js"
main:"./src/main.js"
}
output:{
path: path.resolve( dirname,"dist"),
filename: "[name].js",// webpack命名方式,[name]以文件名自己命名filename
}
plugins:[
new HtmlWebpackPlugin({template:path.resolve( dirname,"public/index.html")})
],
mode:"production"
}
10、 多入口想提取公共模块,这个时候就用到了代码分割
javascript
optimization: {
minimize: true, // 强制启用压缩
// 对代码进行分割
splitChunks: {
chunks: 'all',
// 以下是默认值
// minsize:20000,//分割代码最小的大小
// minRemainingsize:0,//类似于minsize,最后确保提取的文件大小不能为0// minChunks:1,//至少被引用的次数,满足条件才会代码分割
// maxAsyncRequests:3e,/按需加载时并行加载的文件的最大数量
// maxInitialRequests:30,//入口js文件最大并行请求数量
// enforcesizeThreshold:50000,
// 超过50kb一定会单独打包(此时会忽略minRemainingsize、maxAsyncRequests、maxInitialRegquests)
//cacheGroups:{// 组,哪些模块要打包到一个组)
// defaultVendors:{//组名
// test:/[\V/]node_modules[\\/]/,//需要打包到一起的模块
// priority:-10,//权重(越大越高)
// reuseExistingchunk:true,//如果当前 chunk 包含已从主 bundle 中拆分出的模块,则它将被重用,
// 而不是生成新的模块
// },
// default:{ // 其他没有写的配置会使用上面的默认值
// {
// minChunks:2,//这里的minChunks权重更大
// priority: -20,
// reuseExistingchunk: true,
// }
},
// runtimeChunk: 'single',
minimizer: [
new CssMinimizerPlugin(),
]
},
11、chunk统一命名
javascript
output: {
clean: true, // 清理 /dist 文件夹
// filename: "js/main.js", // 打包后的文件名称
filename: "js/[name].[contenthash:8].js", // 打包后的文件名称
// 打包输出的其他文件名称
chunkFilename: "js/[name].[contenthash:8].chunk.js",
path: path.resolve(__dirname, "../dist"), // 打包后的目录
// 图片 等字体通过type: asset处理资源命名方式
assetModuleFilename: 'media/[name].[contenthash:8][ext][query]',
},
javascript
new MiniCssExtractPlugin({
filename: "css/[name].[contenthash:8].css"
}),
添加 移除loader中字体、媒体文件的命名规则
javascript
// generator: {
// filename: "fonts/[name].[contenthash:8][ext][query]"
// }
12、 Preload和Prefetch(兼容性差)
- Preload :告诉浏览器立即加载资源。
- Prefetch :告诉浏览器在空闲时才开始加载资源。
它们共同点:
。都只会加载资源,并不执行。
。都有缓存。
它们区别:
Preload 加载优先级高,Prefetch 加载优先级低
只能加载当前页面需要使用的资源, Prefetch 可以加载当前页面资源,也可以加载下一个页面需要使用的资源。reloa
总结:
。当前页面优先级高的资源用 Preload 加载。
。下一个页面需要使用的资源用Prefetch 加载
它们的问题:兼容性较差,
。我们可以去 CanlUseB 网站查询 API 的兼容性问题Preload 相对于 Prefetch 兼容性好一点。
13、配置缓存,文件发生变化时,只有main 和runtime.js会重新打包,其他文件不变
javascript
runtimeChunk:{
name:(entrypoint)=>runtime~${entrypoint.name}.js`
}
14、 core-js
过去我们使用 babel 对js 代码进行了兼容性处理,其中使用@babe/preset-env 智能预设来处理兼容性问题它能将 ES6 的一些语法进行编译转换,比如箭头函数、点点点运算符等。但是如果是 async 函数、promise 对象、数组的一些方法(includes)等,它没办法处理
所以此时我们js 代码仍然存在兼容性问题,一旦遇到低版本浏览器会直接报错。所以我们想要将js 兼容性问题彻底解决
core-js 是专门用来做ES6 以及以上API的 polyfill。
polyfi11 翻译过来叫做垫片/补丁。就是用社区上提供的一段代码,让我们在不兼容某些新特性的浏览器上,使用该新特性。
例如:Promise 就没有被打包
javascript
return new Promise(resolve => {
resolve("成功");
});
解决方案:
- 通过在代码中按需引入core-js的Promise
- 在babel.config.js中配置core-js
javascript
module.exports = {
presets: [
'@babel/preset-env',
{
useBuiltIns: "usage", //按需加载自动引入
corejs: 3,
}
]
};
15、PWA
开发 Web App 项目,项目一旦处于网络离线情况,就没法访问了。我们希望给项目提供离线体验。
渐进式网络应用程序(progressiveweb application·PWA):是一种可以提供类似于 native app(原生应用程序) 体验的 Web App 的技术。
其中最重要的是,在 离线(offline)时应用程序能够继续运行功能。内部通过 Service Workers 技术实现的。
- 下载
bash
npm install workbox-webpack-plugin --save-dev
npm run build
现在你可以看到,生成了两个额外的文件:service-worker.js 和名称冗长的 precache-manifest.b5ca1c555e832d6fbf9462efd29d27eb.js。service-worker.js 是 Service Worker 文件,precache-manifest.b5ca1c555e832d6fbf9462efd29d27eb.js 是 service-worker.js 引用的文件,所以它也可以运行。你本地生成的文件可能会有所不同,但是应该会有一个 service-worker.js 文件。
- 注册 Service Worker
javascript
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/service-worker.js').then(registration => {
console.log('SW registered: ', registration);
}).catch(registrationError => {
console.log('SW registration failed: ', registrationError);
});
});
}
启动会发现未成功,这个时候要安装server
npm i server -g
启动
sever dist
具体可参考\](https://www.webpackjs.com/guides/progressive-web-application/#adding-workbox) ### 完整配置代码 ```javascript const chalk = require('chalk'); const path = require("path"); const os = require('os'); const threads = os.cpus().length; const { VueLoaderPlugin } = require("vue-loader"); const MiniCssExtractPlugin = require("mini-css-extract-plugin"); const HtmlWebpackPlugin = require("html-webpack-plugin"); const ProgressBarPlugin = require('progress-bar-webpack-plugin'); const ESLintPlugin = require('eslint-webpack-plugin'); const { DefinePlugin } = require('webpack'); const CssMinimizerPlugin = require('css-minimizer-webpack-plugin'); const TerserWebpackPlugin = require("terser-webpack-plugin"); const ImageMinimizerPlugin = require('image-minimizer-webpack-plugin'); // 用来处理获取的样式 function getStyleLoaders(pre) { return [MiniCssExtractPlugin.loader, "css-loader", { loader: "postcss-loader", options: { postcssOptions: { // plugins: [["autoprefixer"]], plugins: ['postcss-preset-env'],//能解决大多数兼容性问题 }, }, }, pre].filter(Boolean); } module.exports = { mode: "production", // 开发模式 entry: path.resolve(__dirname, "../src/main.js"), // 入口文件 相对路径 // web server devtool: 'source-map', output: { clean: true, // 清理 /dist 文件夹 // filename: "js/main.js", // 打包后的文件名称 filename: "js/[name].[contenthash:8].js", // 打包后的文件名称 path: path.resolve(__dirname, "../dist"), // 打包后的目录 // 图片 等字体通过type: asset处理资源命名方式 assetModuleFilename: 'media/[name].[contenthash:8][ext][query]', }, cache: { type: 'filesystem', allowCollectingMemory: true, idleTimeout: 60000, compression: 'gzip', }, // 用于处理压缩 optimization: { splitChunks: { chunks: 'all', }, runtimeChunk: 'single', minimizer: [ // CssMinimizerPlugin、TerserWebpackPlugin 也可以放在plugins中 new CssMinimizerPlugin(), new TerserWebpackPlugin({ parallel: threads, // 开启多进程 extractComments: false, // 移除注释 terserOptions: { compress: { pure_funcs: ['console.log'], // 移除console.log }, }, }), new ImageMinimizerPlugin({ minimizer: { implementation: ImageMinimizerPlugin.imageminGenerate, options: { plugins: [ ['gifsicle', { interlaced: true }], ['jpegtran', { progressive: true }], ['optipng', { optimizationLevel: 5 }], [ 'svgo', { plugins: [ 'preset-default', "prefixIds", { name: "sortAttrs", params: { xmlnsOrder: "alphabetical" } } ] }, ], ] } } }) ], }, plugins: [ new ESLintPlugin({ // 配置哪些目录需要检查 context: path.resolve(__dirname, './src'), cache: true,// 开启缓存 cacheLocation: path.resolve(__dirname, '../node_modules/.cache/eslintcache'), threads,// 开启多进程打包 }), new ProgressBarPlugin({ format: ` :msg [:bar] ${chalk.green.bold(':percent')} (:elapsed s)` }), new VueLoaderPlugin(), new HtmlWebpackPlugin({ // 模版:以public/index.html为模板生成打包后的index.html template: path.resolve(__dirname, "../public/index.html"), BASE_URL: process.env.BASE_URL || '/' }), new MiniCssExtractPlugin({ filename: "css/[name].[contenthash:8].css" }), new DefinePlugin({ // window.ENV = 'production' ENV: JSON.stringify('production'), BASE_URL: '"../"' // 定义全局变量BASE_URL }), // new CssMinimizerPlugin(), // new TerserWebpackPlugin({ // parallel: threads, // 开启多进程 // extractComments: false, // 移除注释 // terserOptions: { // compress: { // pure_funcs: ['console.log'], // 移除console.log // }, // }, // }), ], // loader 加载器 module: { rules: [ // oneOf 不支持 vue-loader ,要单独拿出来 { test: /\.vue$/, loader: "vue-loader", }, // 每个文件只有一个loader配置处理 { oneOf: [ { test: /\.(gif|png|jpe?g)$/i, type: "asset", parser: { dataUrlCondition: { // 小于10kb的图片转成base64,减少请求数量 // 缺点:体积会大一点 maxSize: 10 * 1024, // 小于10kb }, }, generator: { // 输出图片的名称 filename: "imgs/[name].[contenthash:8][ext]", }, }, { test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, //媒体文件 // 对文件原封不动的输出 type: "asset/resource", // generator: { // filename: "media/[name].[contenthash:8][ext]", // }, }, { test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/i, // 字体 type: "asset/resource", // generator: { // filename: "fonts/[name].[contenthash:8][ext]" // } }, // 简单场景,不需要复杂转义时 // { // test: /\.(js|ts|jsx|tsx)$/, // include: path.resolve(__dirname, 'src'), // // loader:"babel-loader", // loader: 'esbuild-loader', // generator: { // target: 'es2015' // } // }, // 复杂场景用 { test: /\.js$/, exclude: /node_modules/, use: [{ loader: 'thread-loader', options: { workers: threads, // 开启几个子进程去完成 } }, { loader: 'babel-loader', options: { presets: ['@babel/preset-env'], cacheDirectory: true, // 开启babel缓存 cacheCompression: false,// 关闭缓存文件压缩 plugins: ['@babel/plugin-transform-runtime'], }, },] }, { test: /\.css$/, // MiniCssExtractPlugin.loader 最终会将css提取到单独的文件 use: getStyleLoaders(), // 从右向左解析原则 // use: ["style-loader", "css-loader"] }, { test: /\.less$/, use: getStyleLoaders("less-loader"), // 从右向左解析原则 // use: ["style-loader", "css-loader", "less-loader"] }, { test: /\.s[ac]ss$/, use: getStyleLoaders("sass-loader") }, { test: /\.styl$/, use: getStyleLoaders("stylus-loader") },] } ], }, // 配置模块如何解析 resolve: { alias: { vue$: "vue/dist/vue.runtime.esm.js", // 末尾添加 $,以表示精准匹配 "@": path.resolve(__dirname, "../src"), }, extensions: ["*", ".js", ".json", ".vue"], // 尝试按顺序解析这些后缀名。如果有多个文件有相同的名字,但后缀名不同,webpack 会解析列在数组首位的后缀的文件 并跳过其余的后缀 } }; ```