webpack是什么?
webpack是一个现代的前端打包工具,用于构建和优化Web应用程序的前端资源,包括js,css,图片,字体等。它的主要目标试讲所有依赖项打包到一个或多个静态文件中,以便在浏览器中加载,提高了代码的可维护性和性能。下面是我对webpack配置的一些理解。
入口配置entry
首先关键就是需要配置入口文件,例如我有两个入口文件,entry.page1和entry.page2,则需要进行如下配置:
js
// 入口配置
entry: {
'entry.page1': '/app/pages/page1/entry.page1.js' // 文件路径
'entry.page2': '/app/pages/page1/entry.page1.js'
},
同时需要配置htmlWebpackPlugin如下:
js
// html-webpack-plugin 辅助注入打包后的 bundle 文件到 tpl文件中
new HtmlWebpackPlugin({
// 产物(最终输出路径)
filename: path.resolve(
process.cwd(),
"./app/public/dist",
`entry.page1.tpl`
),
// 指定要使用的模板文件
template: path.resolve(process.cwd(), "./app/view/entry.tpl"),
// 要注入的代码块
chunks: ['entry.page1'],
})
new HtmlWebpackPlugin({
// 产物(最终输出路径)
filename: path.resolve(
process.cwd(),
"./app/public/dist",
`entry.page2.tpl`
),
// 指定要使用的模板文件
template: path.resolve(process.cwd(), "./app/view/entry.tpl"),
// 要注入的代码块
chunks: ['entry.page2'],
})
通过上面可以看出这样配置显然不太合理,如果有多个入口文件,难道要一个一个去写吗?所以这时针对多文件就需要来动态构造,具体实现如下:
js
// 动态构造
const pageEntries = {};
const htmlWebpackPluginList = [];
// 获取app/pages 目录下所有入口文件(entry.xxx.js)
const entryList = path.resolve(process.cwd(), `./app/pages/**/entry.*.js`);
glob.sync(entryList).forEach((file) => {
const entryName = path.basename(file, ".js");
// 构造entry
pageEntries[entryName] = file;
// 构造最终渲染的页面文件
htmlWebpackPluginList.push(
// html-webpack-plugin 辅助注入打包后的 bundle 文件到 tpl文件中
new HtmlWebpackPlugin({
// 产物(最终输出路径)
filename: path.resolve(
process.cwd(),
"./app/public/dist",
`${entryName}.tpl`
),
// 指定要使用的模板文件
template: path.resolve(process.cwd(), "./app/view/entry.tpl"),
// 要注入的代码块
chunks: [entryName],
})
);
});
通过这种方法不管有多少入口文件,就不需要每个都去配置,直接使用 pageEntries 和htmlWebpackPluginList 即可,大大的提高了我们的开发效率。
分包策略
好的分包策略目的是把改动和引用频率不一样的js区分出来,以达到更好利用浏览器缓存的效果。大体上可以分为三类,第一种是第三方lib库,在开发过程中基本不会进行改动,除非依赖版本升级;第二种是业务组件代码的公共部分,改动较少;第三种就是我们的业务代码需要经常改动。具体分包方式如下:
js
// 配置打包输出优化(代码分割,模块合并,缓存,TreeShaking,压缩等优化策略)
optimization: {
/**
* 把js文件打包成3种类型
* 1.vendor: 第三方 lib 库,基本不会改动,除非依赖版本升级
* 2.common: 业务组件代码的公共部分抽离出来,改动较少
* 3.entry.{page}: 不同页面entry里的业务组件代码的差异部分,会经常改动
* 目的:把改动和引用频率不一样的js区分出来,以达到更好利用浏览器缓存的效果
*/
splitChunks: {
chunks: "all", // 对同步和异步模块都进行分割
maxAsyncRequests: 10, // 每次异步加载的最大并行请求数
maxInitialRequests: 10, // 入口点的最大并行请求数
cacheGroups: {
// 第三方依赖库
vendor: {
test: /[\\/]node_modules[\\/]/,
name: "vendor", // 模块名称
priority: 20, // 优先级,数字越大优先级越高
enforce: true, // 强制执行
reuseExistingChunk: true, // 如果当前 chunk 包含的模块已经被抽取出去了,那么它将被忽略
},
// 公共模块
common: {
name: "common",
minChunks: 2, // 最小引用次数
minSize: 1, // 最小引用字节
priority: 10, // 优先级,数字越大优先级越高
reuseExistingChunk: true, // 复用已有的公共chunk
},
},
},
runtimeChunk: true,
},
生产环境和开发环境的不同配置
生产环境
生产环境是最终需要打包发布上线的,所以通常需要一些额外的配置以达到更好的效果。
output(输出配置)
js
// 生产环境的output配置
output: {
filename: "js/[name]_[chunkhash:8].bundle.js",
path: path.join(process.cwd(), "./app/public/dist/prod"),
publicPath: "/dist/prod",
crossOriginLoading: "anonymous",
},
多线程打包配置
这里使用的happypack,还有另一种方式thread-loader也可完成。
js
const HappyPack = require("happypack");
const os = require("os");
// 多线程build设置
const happypackCommonConfig = {
debug: false,
threadPool: HappyPack.ThreadPool({ size: os.cpus().length }),
};
plugins: [
// 多线程打包js,加快打包速度
new HappyPack({
...happypackCommonConfig,
id: "js",
loaders: [
`babel-loader?${JSON.stringify({
presets: ["@babel/preset-env"],
plugins: ["@babel/plugin-transform-runtime"],
})}`,
],
}),
// 多线程打包css,加快打包速度
new HappyPack({
...happypackCommonConfig,
id: "css",
loaders: [
{
path: "css-loader",
options: {
importLoaders: 1,
},
},
],
}),
]
配置TerserWebpackPlugin
使用TerserWebpackPlugin的并发和缓存,提升压缩阶段的性能,同时清除console.log的打印信息。
js
const TerserWebpackPlugin = require("terser-webpack-plugin");
optimization: {
// 使用TerserWebpackPlugin的并发和缓存,提升压缩阶段的性能
// 清除console.log
minimize: true,
minimizer: [
new TerserWebpackPlugin({
cache: true, // 启用缓存来加速构建过程
parallel: true, // 利用多核CPU的优势来加快压缩速度
extractComments: false,
terserOptions: {
compress: {
drop_console: true, // 删除console.log
},
},
}),
],
},
开发环境
开发环境为了提高开发效率需要配置热更新插件,以便在业务文件改动时可以实时的更新页面。主要流程就是通过一个devServer中间件,可以监控到业务文件的改动,并通知浏览器进行更新,总结来说就是需要拥有监控文件改动和通知页面更新的能力。具体实现方式如下:
js
// webpack.dev.js
const baseConfig = require("./webpack.base.js");
const merge = require("webpack-merge");
const webpack = require("webpack");
const path = require("path");
// devServer的配置
const DEV_SERVER_CONFIG = {
HOST: "127.0.0.1",
PORT: 9002,
HMR_PATH: "__webpack_hmr", // 官方规定
TIMEOUT: 20000,
};
const { HOST, PORT, HMR_PATH, TIMEOUT } = DEV_SERVER_CONFIG;
// 开发阶段的 entry 配置需要加入 hmr
Object.keys(baseConfig.entry).forEach((key) => {
// 第三方包不作为hmr入口
if (key !== "vendor") {
baseConfig.entry[key] = [
// 主入口文件
baseConfig.entry[key],
// hmr 更新入口,官方指定的 hmr 路径
`webpack-hot-middleware/client?path=http://${HOST}:${PORT}/${HMR_PATH}&timeout=${TIMEOUT}&reload=true`,
];
}
});
// 开发环境webpack配置
const webpackConfig = merge.smart(baseConfig, {
mode: "development", // 指定开发环境
// sourceMap 开发工具,呈现代码的映射关系,便于在开发过程中调试代码
devtool: "eval-cheap-module-source-map",
// 开发环境的output配置
output: {
filename: "js/[name]_[chunkhash:8].js",
path: path.resolve(process.cwd(), "./app/public/dist/dev/"), // 输出文件存储路径
publicPath: `http://${HOST}:${PORT}/public/dist/dev/`, // 外部资源公共路径
globalObject: "this", // 全局变量
},
// 开发阶段插件
plugins: [
// 模块热替换允许在应用程序运行时替换,提高开发效率
new webpack.HotModuleReplacementPlugin({
multiStep: false,
}), // 热更新插件
],
});
module.exports = {
webpackConfig,
// devServer 配置,给dev.js 使用
DEV_SERVER_CONFIG,
};
dev.js
js
// 本地开发启动 devServer
const express = require("express");
const path = require("path");
const webpack = require("webpack");
const devMiddleware = require("webpack-dev-middleware");
const hotMiddleware = require("webpack-hot-middleware");
const consoler = require("consoler");
// 从webpack.dev.js获取配置
const { webpackConfig, DEV_SERVER_CONFIG } = require("./config/webpack.dev.js");
const app = express();
const compiler = webpack(webpackConfig);
// 指定静态文件目录
app.use(express.static(path.join(__dirname, "../public/dist")));
// 引用devMiddleware中间件(监控文件改动)
app.use(
devMiddleware(compiler, {
// 落地文件
writeToDisk: (filePath) => filePath.endsWith(".tpl"),
// 资源路径
publicPath: webpackConfig.output.publicPath,
// headers 配置
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, PATCH, OPTIONS",
"Access-Control-Allow-Headers":
"X-Requested-With, content-type, Authorization",
},
stats: {
colors: true,
},
})
);
// 引入hotMiddleware中间件(实现热更新通讯)
app.use(
hotMiddleware(compiler, {
path: `/${DEV_SERVER_CONFIG.HMR_PATH}`,
log: () => {},
})
);
consoler("请等待webpack初次构建完成提示....");
// 启动 devServer
const port = DEV_SERVER_CONFIG.PORT;
app.listen(port, () => {
console.log(`app listening on port ${port}`);
});
可以看到我们使用了 HotModuleReplacementPlugin 热替换插件允许在应用程序运行时替换,再借助 devMiddleware 监控文件改动,以及hotMiddleware 实现热更新通讯,同时也配置了sourceMap开发工具,便于在开发过程中调试代码。
总结
以上就是生产环境和开发环境的一些不同配置,当然webpack还有许多其他的配置这里没有一一列出,例如一些loader,处理vue文件的vue-loader,处理css文件的css-loader等,大家可以根据自己需要进行各种不一样的配置。通过这一章节对webpack的学习,让我更加清晰的了解了工程化相关知识,对项目中需要配置哪些东西也有了更深的理解,重要的是了解了一些主要思想,以后再使用其他工具如vite,rollup等也能够很快上手啦。fighting~
引用:抖音"哲玄前端"《大前端全栈实践》