webpack打包原理和基本配置
webpack简介
webpack实际上是一个静态模块打包工具
webpack 处理项目时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle。
打包原理

- 识别入口文件
- 通过逐层识别模块依赖。(Commonjs、amd或者es6的import,webpack都会对其进行分析。来获取代码的依赖)
- webpack做的就是分析代码。转换代码,编译代码,输出代码
- 最终形成打包后的代码
打包调试命令
npm run dev
npm run build
packages.json
...
"scripts": {
"dev_def": "webpack-dev-server --inline --public --config build/dev.js",
"dev": "nodemon --watch config/index.js --exec \"webpack-dev-server --inline --public --config build/dev.js\"",
"start": "npm run dev",
"build": "cross-env NODE_ENV=production node build/build.js"
}
...
webpack-dev-server
是一个轻量级的服务器,修改文件源码后,自动刷新页面将修改同步到页面上
webpack-dev-server --inline --public --config build/dev.js
- 【--inline 或者 --inline=false】 内联模式:将在包中插入脚本以处理实时重新加载,并且构建消息将显示在浏览器控制台中.
module.exports = {
//...
devServer: {
inline: true
}
};
- 【--public xxx】使用内联模式并且正在代理dev-server时,内联客户端脚本并不总是知道连接到哪里。它将尝试根据服务器的URL来猜测window.location,但如果失败则需要使用它
- 【--config xxx】指定配置文件
- 【--progress】输出运行进度到控制台。
nodemon
会监测项目中的文件,一旦发现文件有改动,Nodemon 会自动重启应用
- 【--watch xxx】 监控指定的文件或者目录
- 【--exec xxx】 执行指定的命令
nodemon --watch config/index.js --exec \"webpack-dev-server --inline --public --config build/dev.js\"
这句话的意思就是: 用nodemon监控config/index.js文件,如果有变化,则重新执行【webpack-dev-server --inline --public --config build/dev.js】的命令
而【webpack-dev-server --inline --public --config build/dev.js】命令对项目本身具有热更新功能,但webpack配置文件修改时,dev-sever本身不会生效。而用nodemon就是在webpack配置文件修改时也重启服务,算是一个自动补充
cross-env
解决跨平台设置和使用环境变量的脚本,如变量名称、路径方面的抹平
- cross-env NODE_ENV=production 抹平了跨平台环境变量的设置问题
基本配置
module.exports = {
// 入口文件
entry: {
app: './src/js/index.js'
},
// 在哪里输出它所创建的 bundles
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist'),
publicPath: '/' //确保文件资源能够在 http://localhost:3000 下正确访问
},
// 开发者工具 source-map
devtool: 'inline-source-map',
// 创建开发者服务器
devServer: {
contentBase: './dist',
hot: true // 热更新
},
plugins: [
// 删除dist目录
new CleanWebpackPlugin(['dist']),
// 重新穿件html文件
new HtmlWebpackPlugin({
title: 'Output Management'
}),
// 以便更容易查看要修补(patch)的依赖
new webpack.NamedModulesPlugin(),
// 热更新模块
new webpack.HotModuleReplacementPlugin()
],
// 环境
mode: "development",
// loader配置
module: {
rules: [
{
test: /\.css$/,
use: [
'style-loader',
'css-loader'
]
},
{
test: /\.(png|svg|jpg|gif)$/,
use: [
'file-loader'
]
}
]
}
}
__dirname: 当前文件所在文件夹的绝对路径
entry(入口文件配置)
单个入口语法(简写)
entry: './path/to/my/entry/file.js'
// 或者(对象写法)
entry: {
main: './path/to/my/entry/file.js'
}
多页面应用程序
entry: {
pageOne: './src/pageOne/index.js',
pageTwo: './src/pageTwo/index.js',
pageThree: './src/pageThree/index.js'
}
注意:webpack之前的写法(不推荐)
entry:{
vendor:[resolve('src/lib/polyfill.js'), 'vue', 'vue-router'], // 不推荐
app: resolve('src/main.ts')
}
在webpack4之前的版本中,通常将供应商添加为单独的入口点,以将其编译为单独的文件vendor(与之结合使用CommonsChunkPlugin)
在webpack 4中不鼓励这样做。相反,该optimization.splitChunks选项负责分离供应商和应用程序模块并创建单独的文件。不要为供应商或其他不是执行起点的东西创建条目。
output(输出文件配)
output: {
filename: '[name].bundle.js',
chunkFilename: [name].min.js,
path: path.resolve(__dirname, 'dist'),
publicPath: '/' //确保文件资源能够在 http://localhost:3000 下正确访问
}
- filename: 输出文件名
- chunkFilename:此选项决定了非入口文件的名称
- path:所有输出文件的目标路径
- publicPath: 指定资源文件引用的目录,build后的文件,资源引用路径前缀
devtool(调试工具:文件映射)
// dev
devtool: 'eval-source-map'
// prod
devtool: 'source-map'
关键字揭秘:
关键字 | 含义 |
---|---|
eval | 在打包的时候,生成的bundle.js文件,模块都被eval包裹,并且后面跟着sourceUrl,指向的是原文件 |
source-map | 这种配置会生成一个带有.map文件,这个map文件会和原始文件做一个映射,调试的时候,就是通过这个.map文件去定位原来的代码位置的 |
cheap | 低消耗打包,就是打包的时候map文件,不会保存原始代码的列位置信息,只包含行位置信息,所以这就解释官网图后面的说明(仅限行) |
... | ... |
devServer
devServer: {
compress: true,
port: 9000,
hot: true,
https: true,
overlay: {
warnings: false,
errors: true
},
publicPath: '/platform/redapply/'
}
- compress:启用gzip压缩
- port: 端口号
- hot:是否启用Hot Module Replacement特性
- https:可以使用自签名的证书,同样可以自定义签名证书 | boolean、object
- overlay:在浏览器上全屏显示编译的errors或warnings | boolean、object
- publicPath:打包的文件将被部署到该配置对应的path。http://localhost:8080/platform/redapply/index.html
mode(告诉webpack相应地使用其内置优化)
// dev
mode: 'development'
// build
mode: 'production'
plugins(插件)
plugins: [
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: '"production"'
}
}),
]
module.rules(loader配置)
module: {
rules: [
{
test: /\.css$/,
use: [
'style-loader',
'css-loader'
]
},
{
test: /\.(png|svg|jpg|gif)$/,
use: [
'file-loader'
]
}
]
}
对于loader的执行顺序,是从后往前的
项目常用配置
resolve(解析)
resolve: {
extensions: ['.js','.ts', '.vue', '.json'],
alias: {
'@lib': resolve('src/lib'),
'@models': resolve('build/models'),
'@components': resolve('src/components'),
'@data': resolve('src/data'),
'@': resolve('src')
}
}
- extensions 解析路径缺省文件名时候的后缀(按顺序依次尝试)
- alias 自定义路径符号定义
预加载文件
比如定义一些公共的scss文件。为了不再每个页面都引入该文件。我们可以设置文件预加载
module: {
rules: [
...
{
test: /\.sass|scss|css$/,
use: [
...
{
loader: 'sass-resources-loader',
options: {
resources: [
path.resolve(__dirname, '../src/assets/css/vars.scss'),
path.resolve(__dirname, '../src/assets/css/common.scss')
]
}
}
]
}
]
}
optimization(优化配置项,build时配置)
optimization: {
minimize: true, // 默认为true,效果就是压缩js代码。
minimizer: [ // 压缩时调用的插件
new TerserPlugin(),
new OptimizeCSSAssetsPlugin({})
],
runtimeChunk: { // 默认为false,抽离出运行时公共代码块。
name: 'manifest'
},
splitChunks:{
chunks: 'all', // 必须三选一: "initial" | "all"(推荐) | "async" (默认就是async)
minSize: 30000, // 生成块的最小字节数,30000
minChunks: 1, // 最少被引用的次数
maxAsyncRequests: 3, // 按需加载时候最大的并行请求数
maxInitialRequests: 3, // 一个入口最大的并行请求数
name: true, // 打包的chunks的名字
cacheGroups: { // 缓存配置
common: {
name: 'common', // 要缓存的 分隔出来的 chunk 名称
chunks: 'initial', // 必须三选一: "initial" | "all" | "async"(默认就是async)
priority: 11,
enforce: true,
reuseExistingChunk: true, // 可设置是否重用该chunk
test: /[\/]node_modules[\/](vue|babel\-polyfill|mint\-ui)/
},
vendor: { // key 为entry中定义的 入口名称
name: 'vendor', // 要缓存的 分隔出来的 chunk 名称
chunks: 'initial', // 必须三选一: "initial" | "all" | "async"(默认就是async)
priority: 10,
enforce: true,
reuseExistingChunk: true, // 可设置是否重用该chunk
test: /node_modules\/(.*)\.js/
},
styles: {
name: 'styles',
test: /\.(scss|css)$/,
chunks: 'all',
minChunks: 1,
reuseExistingChunk: true,
enforce: true
}
}
}
}
runtimeChunk
默认为false, 抽离出运行时公共代码块
什么是运行时(runtime)?
JS在浏览器中可以调用浏览器提供的API,如window对象,DOM相关API等。这些接口并不是由V8引擎提供的,是存在与浏览器当中的。因此简单来说,对于这些相关的外部接口,可以在运行时供JS调用,以及JS的事件循环(Event Loop)和事件队列(Callback Queue),把这些称为RunTime。有些地方也把JS所用到的core lib核心库也看作RunTime的一部分。
chunk运行时
在chunk执行的时候所依赖的环境(方法)
splitChunks
chunks
function (chunk) | string
这表示将选择哪些块进行优化
string:
- initial - 入口chunk,对于异步导入的文件不处理
- async - 异步chunk,只对异步导入的文件处理(个人理解)
- all - 全部chunk
function:
splitChunks: {
chunks (chunk) {
// exclude `my-excluded-chunk`
return chunk.name !== 'my-excluded-chunk';
}
}
cacheGroups
缓存组可以继承和/或覆盖任何选项splitChunks.*;要禁用任何默认缓存组,请将其设置为false。
- priority 优先级,模块可以属于多个缓存组,单最终会被打入优先级高的chunk
- reuseExistingChunk 表示可以使用已经存在的块,即如果满足条件的块已经存在就使用已有的,不再创建一个新的块
- enforce minSize,minChunks,maxInitialRequests选项,为快速创建chunk用
- test 缓存组的规则,表示符合条件的的放入当前缓存组
optimization: {
splitChunks: {
chunks: 'async',
minSize: 30000,
minChunks: 1,
maxAsyncRequests: 3,
maxInitialRequests: 3,
name: true,
cacheGroups: {
common: {
name: 'common',
chunks: 'initial',
priority: 11,
enforce: true,
reuseExistingChunk: true, // 可设置是否重用该chunk
test: /[\/|\\]node_modules[\/|\\](vue|babel\-polyfill|mint\-ui)/
},
vendor: {
name: "vendor",
chunks: "initial",
priority: 10,
test: /[\/|\\]node_modules[\/|\\](.*)\.js/
},
styles: {
name: 'styles',
test: /\.(scss|css|less)$/,
chunks: 'initial',
minChunks: 1,
reuseExistingChunk: true,
enforce: true
}
}
}
}
注意:
这里有个坑,就是关于test匹配路径的问题 一般网上看到的如:
...
common: {
name: 'common',
chunks: 'initial',
priority: 11,
enforce: true,
reuseExistingChunk: true,
test: /[\/]node_modules[\/](vue|babel\-polyfill|mint\-ui)/
}
...
这里面test匹配正则是根据linux环境路径匹配的。(如:node_modules/vue)
但window路径和linux路径不一样,它是反斜杠。(如:node_modules\vue)
这样我们要把正则改成
/[\/|\\]node_modules[\/|\\](vue|babel\-polyfill|mint\-ui)/
这样就可以兼容两种环境了
项目中的配置
通常来讲webpack就需要3个配置文件
- webpack.base.conf.js // 公共配置
- webpack.dev.conf.js // 开发环境配置
- webpack.prod.conf.js // 生产环境配置
但在我们的项目里大家看到的配置和上面介绍的并不完全相同。
是因为:我们在脚手架生成项目的时候,已经集成了webpack的基本配置。
它们都在@zz/webpack-vue下

而我们项目中仅暴露了一些配置对象入口。

暴露出来的配置项结构和webpack本身的略有区别。
开发人员可以通过自定义这些对象,然后程序会和默认的配置进行合并,形成最终的配置参数。
(本文为内部专题学习webpack的一次分享,内容比较基础)
转载自:https://juejin.cn/post/6844903908284825608