在vscode中配置webpack的时候,如果想要代码提示,可以在配置对象上添加一个注释,这样就可以有较好的提示了。
/** @type {import('webpack').Configuration} */
module.exports = {
...
}
分析插件
费时分析
查看每个阶段和插件的费时
const SpeedMeasureWebpackPlugin = require('speed-measure-webpack-plugin');
const smw = new SpeedMeasureWebpackPlugin();
module.exports =smw.wrap({
});
webpack-bundle-analyzer
启动一个服务端口,查看生成代码分析报告,帮助提升代码质量和网站性能
const {BundleAnalyzerPlugin} = require('webpack-bundle-analyzer');
module.exports={
plugins: [
new BundleAnalyzerPlugin()
]
}
细节配置
entry output
入出口配置,要注意output中的path要用绝对路径
let path = require('path')
module.export = {
// 此处配置的是trunk,属性名为trunk名称,属性值为入口模块
entry:{
// 默认trunk名为main
main:'./src/index.js'
// 启动模块有两个,两个文件的内容会被合并到一个js文件中
a:'["./src/a.js","./src/b.js"]'
},
output:{
// 输出路径,注意必须是绝对路径,表示资源放置的位置
path:'path.resolve(__dirname,src/dist)',
// 合并后的js代码文件,配置的是js合文件的规则,取5位的chunkhash
//filename可以深层选择如 filename:"scripts/bundle.js"就会加入scripts这个文件夹
filename:"[name],[chunkhash:5].js"
}
}
context
context: path.resolve(__dirname, "app")
该配置会影响入口和loaders的解析,入口和loaders的相对路径会以context的配置作为基准路径,这样,你的配置会独立于CWD(current working directory 当前执行路径)
exclude/include
在配置loader的时候,可以通过配置exlude,include,祛除某些不需要loader转换的代码,提升打包速度。
//webpack.config.js
const path = require('path');
module.exports = {
//...
module: {
rules: [
{
test: /\.js[x]?$/,
use: ['babel-loader'],
include: [path.resolve(__dirname, 'src')]
}
]
},
}
extensions
指定extension之后可以不用在require或是import的时候加文件扩展名,会依次尝试添加扩展名进行匹配
resolve: {
extensions: [".js",".jsx",".json",".css"]
},
alias
- 配置别名可以加快webpack查找模块的速度,每当引入bootstrap模块的时候,它会直接引入bootstrap,而不需要从node_modules文件夹中按模块的查找规则查找
- 在引入本地组件的时候可以简写路径
const bootstrap = path.resolve(__dirname,'node_modules/bootstrap/dist/css/bootstrap.css')
resolve: {
alias:{
bootstrap,
'@': path.resolve(__dirname, 'src')
}
},
modules
- 对于直接声明依赖名的模块(如 react ),webpack 会类似 Node.js 一样进行路径搜索,搜索node_modules目录
- 这个目录就是使用resolve.modules字段进行配置的 默认配置
resolve: {
modules: ['node_modules'],
}
resolveLoader
resolve.resolveLoader用于配置解析 loader 时的 resolve 配置,默认的配置:
module.exports = {
resolveLoader: {
modules: [ 'node_modules' ],
extensions: [ '.js', '.json' ],
mainFields: [ 'loader', 'main' ]
}
};
mainFields
默认情况下package.json 文件则按照文件中 main 字段的文件名来查找文件
resolve: {
// 配置 target === "web" 或者 target === "webworker" 时 mainFields 默认值是:
mainFields: ['browser', 'module', 'main'],
// target 的值为其他时,mainFields 默认值为:
mainFields: ["module", "main"],
}
mainFiles
当目录下没有 package.json 文件时,我们说会默认使用目录下的 index.js 这个文件,其实这个也是可以配置的
resolve: {
mainFiles: ['index'], // 你可以添加其他默认使用的文件名
},
libraryTarget 和 library
当用 Webpack 去构建一个可以被其他模块导入使用的库时需要用到它们
output.library配置导出库的名称output.libraryExport配置要导出的模块中哪些子模块需要被导出。 它只有在 output.libraryTarget 被设置成 commonjs 或者 commonjs2 时使用才有意义output.libraryTarget配置以何种方式导出库,是字符串的枚举类型:var(默认)commonjscommonjs2thiswindowumd
性能优化
noParse
- module.noParse 字段,可以用于配置哪些模块文件的内容不需要进行解析
- 不需要解析依赖(即无依赖) 的第三方大型类库(jquery、lodash),可以通过这个字段来配置,以提高整体的构建速度
module: {
// 正则表达式
noParse: /jquery|lodash/,
// 或者使用函数
noParse(content) {
return /jquery|lodash/.test(content)
},
}
IgnorePlugin
IgnorePlugin用于忽略某些特定的模块,让 webpack 不把这些指定的模块打包进去
- src/index.js
import moment from 'moment';
import 'moment/locale/zh-cn'
console.log(moment().format('MMMM Do YYYY, h:mm:ss a'));
- webpack.config.js
new webpack.IgnorePlugin({
//A RegExp to test the context (directory) against.
contextRegExp: /moment$/,
//A RegExp to test the request against.
resourceRegExp: /^\.\/locale/
});
thread-loader
thread-loader 放置在其它 loader 之前,那么放置在这个 loader 之后的 loader 就会在一个单独的 worker 池中运行。
在 worker 池(worker pool)中运行的 loader 是受到限制的。例如:
- 这些
loader不能产生新的文件。 - 这些
loader不能使用定制的loaderAPI(也就是说,通过插件)。 - 这些
loader无法获取webpack的选项设置。
externals
我们可以将一些JS文件存储在 CDN 上(减少 Webpack打包出来的 js 体积),在 index.html 中通过 <script> 标签引入,如:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div id="root">root</div>
<script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
</body>
</html>
我们希望在使用时,仍然可以通过 import 的方式去引用(如 import $ from 'jquery'),并且希望 webpack 不会对其进行打包,此时就可以配置 externals。
// webpack.config.js
module.exports = {
//...
externals: {
//jquery通过script引入之后,全局中即有了$变量
'jquery': '$'
}
}
这样配置以后,当在业务代码中引用jquery的时候,打包后的结果模块会将jquery模块这样导出,因为已经通过cdn引入的代码将`可以直接用了。
let __webpack_modules__ = {
'jquery': (module) => {
module.exports = $;
}
};
DllPlugin
DllPlugin和DllReferencePlugin主要功能可以将第三方包单独打包,有些时候,如果同一个包被多个chunk使用,会造成每个chunk文件都包含这个公用包的代码,导致chunk文件过大,这时就需要拆包了
-
DllPlugin和DLLReferencePlugin可以实现拆分bundles,并且可以大大提升构建速度,DllPlugin和DLLReferencePlugin都是webpack的内置模块。 -
我们使用
DllPlugin将不会频繁更新的库进行编译,当这些依赖的版本没有变化时,就不需要重新编译。我们新建一个webpack的配置文件,来专门用于编译动态链接库,例如名为:webpack.config.dll.js,这里我们将lodash和juqery单独打包成一个动态链接库,通过entry配置。 -
DllPlugin会生成两个文件,一个是动态链接库的json映射文件,一个要分出去的包,用于将需要分出去的包分出去,会暴露出一个全局变量通过DllPlugin options.name配置。
dll.config.js配置
const path = require('path');
const { DllPlugin } = require('webpack');
/** @type {import('webpack').Configuration} */
module.exports = {
mode: 'development',
devtool: false,
entry: {
utils: ['jquery', 'lodash']
},
output: {
path: path.resolve(__dirname, 'dist/dll'),
filename: 'utils.dll.js',
library: 'dll_util'
},
plugins: [
new DllPlugin({
path: path.join(__dirname, 'dist/dll/manifest.json')
})
]
};
manifest.json
{
"name": "dll_util",
"content": {
"./node_modules/jquery/dist/jquery.js": {
"id": "./node_modules/jquery/dist/jquery.js",
"buildMeta": {}
},
"./node_modules/lodash/lodash.js": {
"id": "./node_modules/lodash/lodash.js",
"buildMeta": {}
}
}
}
utils.dll.js
var dll_util;
(()=>{
...
var __webpack_exports__ = __webpack_require__("?2e89");
dll_util = __webpack_exports__;
})();
webpack.config.js
const webpack = require('webpack');
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
/** @type {import('webpack').Configuration} */
module.exports = {
entry: './src/index.js',
mode: 'development',
plugins: [
new webpack.DllReferencePlugin({
manifest: path.resolve(__dirname, 'dist', 'dll', 'manifest.json')
}),
new HtmlWebpackPlugin({
template: './public/index.html'
})
]
};
html使用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>index</title>
<script defer src="./dll/utils.dll.js"></script>
<script defer src="main.js"></script></head>
<body>
</body>
</html>
splitChunk
splitChunk是另一个分包的方法,用于分割公共代码
// webpack.config.js
module.exports = {
optimization: {
splitChunks: {
// all = async + initial:同步异步都要分割
chunks: 'all',
// 分割出去的代码最小体积 0为不限制
minSize: 0,
// 被几个代码块引用才会分割
minChunks: 2,
// 缓存组
cacheGroups: {
defaultVendors: {
test: /[\\/]node_modules[\\/]/,// [\]node_modules[\]
priority: 0
},
default: {
minChunks: 2,
priority: 1
}
}
}
},
}
异步分包预加载
对于import('xxx'),这种异步引入的文件,也会单独分出一个文件,对于这种异步文件我们可以利用html的preload(并行加载,针对重要的优先级高的文件),和prefetch(优先级较低的文件,利用网络进程空闲时请求),利用<link href="需要预加载的js文件路径" rel="preload" as="script">实现。
请求异步模块
index.js打包前的代码(点击按钮才请求asyncChunk文件)
btn.onclick = function () {
import('./asyncChunk').then(res => {
console.log(res);
});
};
打包后的html代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>index</title>
<script src="main.js"></script>
<link href="src_asyncChunk_js.js" rel="preload" as="script">
</link>
</head>
<body>
<button id="btn">按钮</button>
</body>
</html>
- 不进行预加载,不加载异步文件
- preload异步文件优先级最高
<link href="src_asyncChunk_js.js" rel="preload" as="script">
- prefetch异步文件优先级最低
<link href="src_asyncChunk_js.js" rel="prefetch" as="script">
PreloadWebpackPlugin
这个插件可以给异步代码块设置预加载或者懒加载 推荐@vue/preload-webpack-plugin这个插件
const webpack = require('webpack');
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const PreloadWebpackPlugin = require('@vue/preload-webpack-plugin');
/** @type {import('webpack').Configuration} */
module.exports = {
entry: './src/index.js',
mode: 'development',
plugins: [
new HtmlWebpackPlugin({
template: './public/index.html'
}),
new PreloadWebpackPlugin({
// preload or prefetch,这个位置的内容会填入link的rel属性
rel: 'prefetch',
// 只对异步代码块进行预加载
include: 'asyncChunks'
})
]
};
实现一个简易的PreloadWebpackPlugin插件
const HtmlWebpackPlugin = require('html-webpack-plugin');
class PreloadWebpackPlugin {
constructor(options) {
this.options = options;
}
apply(compiler) {
compiler.hooks.compilation.tap(
this.constructor.name,
compilation => {
//在产出的html标签生成前确定要生成哪些代码块的link
HtmlWebpackPlugin.getHooks(compilation).beforeAssetTagGeneration.tapAsync(this.constructor.name,
(_, callback) => {
this.generateLinks(compilation);
callback();
});
HtmlWebpackPlugin.getHooks(compilation).alterAssetTags.tap(this.constructor.name,
// html-webpack-plugin 的配置数据
(htmlPluginData) => {
if (this.resourceHints) {
// 将resourceHints的资源添加到配置中
htmlPluginData.assetTags.styles = [
...htmlPluginData.assetTags.styles,
...this.resourceHints
];
return htmlPluginData;
}
});
}
);
}
generateLinks(compilation) {
const { rel = 'preload', include } = this.options;
//获取本次编译产出了哪些代码
let chunks = compilation.chunks;
//或者没有指定包含的代码块,或者传了asyncChunks只包含异步代码块
if (!include || include === 'asyncChunks') {
// 过滤chunks,拿到异步加载的chunks
chunks = chunks.filter(chunk => {
return !chunk.canBeInitial();
});
}
// 拿到异步chunk文件名称:{ 'src_asyncChunk_js.js' }
const allFiles = chunks.reduce((accumulated, chunk) => {
// 给老的数组累加上当前代码块包含的文件
return accumulated.concat(chunk.files);
}, []);
// 去重
const uniqueFiles = new Set(allFiles);
const links = [];
for (const file of uniqueFiles) {
const href = file;
const attributes = { href, rel };
links.push({
tagName: 'link',
attributes
});
}
// 保存要处理的文件列表
this.resourceHints = links;
}
}
module.exports = PreloadWebpackPlugin;
vscode去除注释的插件
推荐分析打包文件时去除注释的的vscode插件:marketplace.visualstudio.com/items?itemN…