这里写目录标题
前端代码为何要进行构建和打包
module chunk bundle 分别什么意思,有何区别
loader 和 plugin 区别
webpack 如何实现懒加载
webpack 常见性能优化
bable-runtime 和 bable-polyfill 区别
前言
为什么要用webpack
当下前端开发(大前端)越来越复杂,带来一些问题:
- 采用模块化开发,但不同浏览器对于模块化支持是不一样的,而且模块化本身存在多种实现规范,给最终产出带来影响
- 编码的过程中使用es6+、less、typescript来提高开发效率、保证安全性
- 对文件进行实时监听,开发过程使用热更新
- 项目编码完成之后、部署之前,对相关资源进行压缩、合并的优化处理
—> 使用 webpack实现前端项目工程化
webpack定义
为现在Javascript应用提供静态模块打包的工具,
功能:
- 打包:将不同类型资源(js, css, img)按模块处理进行打包
- 静态:打包后最终产出静态资源
- 模块:支持不同规范的模块化开发
总体的处理流程:
1.指定入口(entry):告诉webpack从哪儿入手开始打包。2.打包封装(chunk):存在依赖关系的模块在打包时被封装为一个chunk,chunk就像文件袋,里面包裹着很多文件(模块)。根据配置,可能会产生一个或者多个chunk。3.打包产物(bundle):由上述chunk得到的打包产物则为bundle。
webpack 4 -> 5
# webpack4 demo
升级 webpack5 以及周边插件后,代码需要做出的调整:
- package.json 的 dev-server 命令改了 `"dev": "webpack serve --config build/webpack.dev.js",`
- 升级新版本 `const { merge } = require('webpack-merge')`
- 升级新版本 `const { CleanWebpackPlugin } = require('clean-webpack-plugin')`
- `module.rules` 中 `loader: ['xxx-loader']` 换成 `use: ['xxx-loader']`
- `filename: 'bundle.[contenthash:8].js'` 其中 `h` 小写,不能大写
也可以直接下载讲师的 webpack5-demo 源码,把配置文件删掉,自己重写练习。
Loader 和 Plugin
Loader
loader即文件加载器:用来预处理文件。接收原始资源数据作为参数(当多个加载器联合使用时,上一个loader的结果会传入下一个loader),最终输出 javascript 代码(和可选的 source map)给 webpack 做进一步编译。
处理一个文件可以使用多个loader,loader的执行顺序和配置中的顺序是相反的,最后一个loader最先执行,第一个loader最后执行。
Plugin
插件,用来增强功能。针对的是在loader结束之后,webpack打包的整个过程,他并不直接操作文件,而是基于事件机制工作,监听webpack打包过程中的某些节点,执行广泛的任务。
基本配置
拆分配置和merge
启动本地服务
处理ES6
处理样式
处理图片
(模块化)
proxy
可以解决跨域
devServer: {
port: 8080,
progress: true, // 显示打包的进度条
contentBase: distPath, // 根目录
open: true, // 自动打开浏览器
compress: true, // 启动 gzip 压缩
// 设置代理
proxy: {
// 将本地 /api/xxx 代理到 localhost:3000/api/xxx
'/api': 'http://localhost:3000',
// 将本地 /api2/xxx 代理到 localhost:3000/xxx
'/api2': {
target: 'http://localhost:3000',
pathRewrite: {
'/api2': ''
}
}
}
}
scripts:
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"devBuild": "webpack --config build-optimization/webpack.dev.js",
"dev": "webpack-dev-server --config build-optimization/webpack.dev.js",
"build": "webpack --config build-optimization/webpack.prod.js"
},
webpack.common.js:
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { srcPath, distPath } = require('./paths')
module.exports = {
entry: path.join(srcPath, 'index'),
module: {
rules: [
{
test: /\.js$/,
loader: ['babel-loader'],
include: srcPath,
exclude: /node_modules/
},
// {
// test: /\.vue$/,
// loader: ['vue-loader'],
// include: srcPath
// },
// {
// test: /\.css$/,
// // loader 的执行顺序是:从后往前(知识点)
// loader: ['style-loader', 'css-loader']
// },
{
test: /\.css$/,
// loader 的执行顺序是:从后往前
loader: ['style-loader', 'css-loader', 'postcss-loader'] // 加了 postcss
},
{
test: /\.less$/,
// 增加 'less-loader' ,注意顺序
loader: ['style-loader', 'css-loader', 'less-loader']
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: path.join(srcPath, 'index.html'),
filename: 'index.html'
})
]
}
.babelrc
{
"presets": ["@babel/preset-env"],
"plugins": []
}
postcss.config.js
module.exports = {
plugins: [require('autoprefixer')]
}
webpack.dev.js
const path = require('path')
const webpack = require('webpack')
const webpackCommonConf = require('./webpack.common.js')
const { smart } = require('webpack-merge')
const { srcPath, distPath } = require('./paths')
module.exports = smart(webpackCommonConf, {
mode: 'development',
module: {
rules: [
// 直接引入图片 url
{
test: /\.(png|jpg|jpeg|gif)$/,
use: 'file-loader'
}
]
},
plugins: [
new webpack.DefinePlugin({
// window.ENV = 'development'
ENV: JSON.stringify('development')
})
],
devServer: {
port: 8080,
progress: true, // 显示打包的进度条
contentBase: distPath, // 根目录
open: true, // 自动打开浏览器
compress: true, // 启动 gzip 压缩
// 设置代理
proxy: {
// 将本地 /api/xxx 代理到 localhost:3000/api/xxx
'/api': 'http://localhost:3000',
// 将本地 /api2/xxx 代理到 localhost:3000/xxx
'/api2': {
target: 'http://localhost:3000',
pathRewrite: {
'/api2': ''
}
}
}
}
})
webpack.prod.js
const path = require('path')
const webpack = require('webpack')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const webpackCommonConf = require('./webpack.common.js')
const { smart } = require('webpack-merge')
const { srcPath, distPath } = require('./paths')
module.exports = smart(webpackCommonConf, {
mode: 'production',
output: {
filename: 'bundle.[contentHash:8].js', // 打包代码时,加上 hash 戳
path: distPath,
// publicPath: 'http://cdn.abc.com' // 修改所有静态文件 url 的前缀(如 cdn 域名),这里暂时用不到
},
module: {
rules: [
// 图片 - 考虑 base64 编码的情况
{
test: /\.(png|jpg|jpeg|gif)$/,
use: {
loader: 'url-loader',
options: {
// 小于 5kb 的图片用 base64 格式产出
// 否则,依然延用 file-loader 的形式,产出 url 格式
limit: 5 * 1024,
// 打包到 img 目录下
outputPath: '/img1/',
// 设置图片的 cdn 地址(也可以统一在外面的 output 中设置,那将作用于所有静态资源)
// publicPath: 'http://cdn.abc.com'
}
}
},
]
},
plugins: [
new CleanWebpackPlugin(), // 会默认清空 output.path 文件夹
new webpack.DefinePlugin({
// window.ENV = 'production'
ENV: JSON.stringify('production')
})
]
})
高级配置
如何配置多入口
webpack.common.js:
entry
中配置多入口
plugins
中添加多个HtmlWebpackPlugin
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { srcPath, distPath } = require('./paths')
module.exports = {
entry: {
index: path.join(srcPath, 'index.js'),
other: path.join(srcPath, 'other.js')
},
module: {
...
},
plugins: [
// 多入口 - 生成 index.html
new HtmlWebpackPlugin({
template: path.join(srcPath, 'index.html'),
filename: 'index.html',
// chunks 表示该页面要引用哪些 chunk (即上面的 index 和 other),默认全部引用
chunks: ['index'] // 只引用 index.js
}),
// 多入口 - 生成 other.html
new HtmlWebpackPlugin({
template: path.join(srcPath, 'other.html'),
filename: 'other.html',
chunks: ['other'] // 只引用 other.js
})
]
}
webpack.prod.js:
配置出口 output
const path = require('path')
const webpack = require('webpack')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const webpackCommonConf = require('./webpack.common.js')
const { smart } = require('webpack-merge')
const { srcPath, distPath } = require('./paths')
module.exports = smart(webpackCommonConf, {
mode: 'production',
output: {
// filename: 'bundle.[contentHash:8].js', // 打包代码时,加上 hash 戳
filename: '[name].[contentHash:8].js', // name 即多入口时 entry 的 key
path: distPath,
// publicPath: 'http://cdn.abc.com' // 修改所有静态文件 url 的前缀(如 cdn 域名),这里暂时用不到
},
module: {
rules: [
// 图片 - 考虑 base64 编码的情况
{
test: /\.(png|jpg|jpeg|gif)$/,
use: {
loader: 'url-loader',
options: {
// 小于 5kb 的图片用 base64 格式产出
// 否则,依然延用 file-loader 的形式,产出 url 格式
limit: 5 * 1024,
// 打包到 img 目录下
outputPath: '/img1/',
// 设置图片的 cdn 地址(也可以统一在外面的 output 中设置,那将作用于所有静态资源)
// publicPath: 'http://cdn.abc.com'
}
}
},
]
},
plugins: [
new CleanWebpackPlugin(), // 会默认清空 output.path 文件夹
new webpack.DefinePlugin({
// window.ENV = 'production'
ENV: JSON.stringify('production')
})
]
})
如何抽离压缩css文件
js的:去除多余字符: 空格,换行及注释;压缩变量名:变量名,函数名及属性名;合并声明以及布尔值简化
主要用于生产环境
使用style-loader
会将css
代码直接放到js文件中,抽离压缩之后则以Link
形式引入css
文件
安装插件MiniCssExtractPlugin
,配置相应loader,配置plugins,配置optimization。
webpack.prod.js
const path = require('path')
const webpack = require('webpack')
const { smart } = require('webpack-merge')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const TerserJSPlugin = require('terser-webpack-plugin')
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin')
const webpackCommonConf = require('./webpack.common.js')
const { srcPath, distPath } = require('./paths')
module.exports = smart(webpackCommonConf, {
mode: 'production',
output: {
// filename: 'bundle.[contentHash:8].js', // 打包代码时,加上 hash 戳
filename: '[name].[contentHash:8].js', // name 即多入口时 entry 的 key
path: distPath,
// publicPath: 'http://cdn.abc.com' // 修改所有静态文件 url 的前缀(如 cdn 域名),这里暂时用不到
},
module: {
rules: [
// 图片 - 考虑 base64 编码的情况
{
test: /\.(png|jpg|jpeg|gif)$/,
use: {
loader: 'url-loader',
options: {
// 小于 5kb 的图片用 base64 格式产出
// 否则,依然延用 file-loader 的形式,产出 url 格式
limit: 5 * 1024,
// 打包到 img 目录下
outputPath: '/img1/',
// 设置图片的 cdn 地址(也可以统一在外面的 output 中设置,那将作用于所有静态资源)
// publicPath: 'http://cdn.abc.com'
}
}
},
// 抽离 css
{
test: /\.css$/,
loader: [
MiniCssExtractPlugin.loader, // 注意,这里不再用 style-loader
'css-loader',
'postcss-loader'
]
},
// 抽离 less --> css
{
test: /\.less$/,
loader: [
MiniCssExtractPlugin.loader, // 注意,这里不再用 style-loader
'css-loader',
'less-loader',
'postcss-loader'
]
}
]
},
plugins: [
new CleanWebpackPlugin(), // 会默认清空 output.path 文件夹
new webpack.DefinePlugin({
// window.ENV = 'production'
ENV: JSON.stringify('production')
}),
// 抽离 css 文件
new MiniCssExtractPlugin({
filename: 'css/main.[contentHash:8].css'
})
],
optimization: {
// 压缩 css
minimizer: [new TerserJSPlugin({}), new OptimizeCSSAssetsPlugin({})],
}
})
抽离公共代码和第三方代码
主要用于生产环境。经常需要import的代码可以单独打成一个包
optimization
中配置splitChunks
在HtmlWebpackPlugin
中配置chunks。
webpack.common.js:
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { srcPath, distPath } = require('./paths')
module.exports = {
entry: {
index: path.join(srcPath, 'index.js'),
other: path.join(srcPath, 'other.js')
},
module: {
rules: [
{
test: /\.js$/,
loader: ['babel-loader'],
include: srcPath,
exclude: /node_modules/
}
]
},
plugins: [
// new HtmlWebpackPlugin({
// template: path.join(srcPath, 'index.html'),
// filename: 'index.html'
// })
// 多入口 - 生成 index.html
new HtmlWebpackPlugin({
template: path.join(srcPath, 'index.html'),
filename: 'index.html',
// chunks 表示该页面要引用哪些 chunk (即上面的 index 和 other),默认全部引用
chunks: ['index', 'vendor', 'common'] // 要考虑代码分割
}),
// 多入口 - 生成 other.html
new HtmlWebpackPlugin({
template: path.join(srcPath, 'other.html'),
filename: 'other.html',
chunks: ['other', 'common'] // 考虑代码分割
})
]
}
webpack.prod.js
const path = require('path')
const webpack = require('webpack')
const { smart } = require('webpack-merge')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const TerserJSPlugin = require('terser-webpack-plugin')
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin')
const webpackCommonConf = require('./webpack.common.js')
const { srcPath, distPath } = require('./paths')
module.exports = smart(webpackCommonConf, {
mode: 'production',
output: {
// filename: 'bundle.[contentHash:8].js', // 打包代码时,加上 hash 戳
filename: '[name].[contentHash:8].js', // name 即多入口时 entry 的 key
path: distPath,
// publicPath: 'http://cdn.abc.com' // 修改所有静态文件 url 的前缀(如 cdn 域名),这里暂时用不到
},
module: {
rules: [
// 图片 - 考虑 base64 编码的情况
{
test: /\.(png|jpg|jpeg|gif)$/,
use: {
loader: 'url-loader',
options: {
// 小于 5kb 的图片用 base64 格式产出
// 否则,依然延用 file-loader 的形式,产出 url 格式
limit: 5 * 1024,
// 打包到 img 目录下
outputPath: '/img1/',
// 设置图片的 cdn 地址(也可以统一在外面的 output 中设置,那将作用于所有静态资源)
// publicPath: 'http://cdn.abc.com'
}
}
},
// 抽离 css
{
test: /\.css$/,
loader: [
MiniCssExtractPlugin.loader, // 注意,这里不再用 style-loader
'css-loader',
'postcss-loader'
]
},
// 抽离 less
{
test: /\.less$/,
loader: [
MiniCssExtractPlugin.loader, // 注意,这里不再用 style-loader
'css-loader',
'less-loader',
'postcss-loader'
]
}
]
},
plugins: [
new CleanWebpackPlugin(), // 会默认清空 output.path 文件夹
new webpack.DefinePlugin({
// window.ENV = 'production'
ENV: JSON.stringify('production')
}),
// 抽离 css 文件
new MiniCssExtractPlugin({
filename: 'css/main.[contentHash:8].css'
})
],
optimization: {
// 压缩 css
minimizer: [new TerserJSPlugin({}), new OptimizeCSSAssetsPlugin({})],
// 分割代码块
splitChunks: {
chunks: 'all',
/**
* initial 入口 chunk,对于异步导入的文件不处理
async 异步 chunk,只对异步导入的文件处理
all 全部 chunk
*/
// 缓存分组
cacheGroups: {
// 第三方模块
vendor: {
name: 'vendor', // chunk 名称
priority: 1, // 权限更高,优先抽离,重要!!!
test: /node_modules/,
minSize: 0, // 大小限制
minChunks: 1 // 最少复用过几次
},
// 公共的模块
common: {
name: 'common', // chunk 名称
priority: 0, // 优先级
minSize: 0, // 公共模块的大小限制
minChunks: 2 // 公共模块最少复用过几次
}
}
}
}
})
懒加载
例如某块数据非常大
// 引入动态数据
setTimeout(() => {
// 回顾 vue react 异步组件
// 类似于定义了一个chunk
import('./dynamic-data.js').then(res => {
log(res.default.message) // 注意default
})
}, 1500)
处理jsx和vue
jsx:
用到工具preset/react-loader,在.babelrc里给presets添加 @preset/react-loader
vue:
@vue-loader
module chunk bundle 区别
- module - 各个源码文件,webpack中一切皆模块
- chunk - 多模块合并成的,如 entry import() splitChunk (还处于内存中的)
- bundle - 最终的输出文件,一般一个chunk
webpack 如何配置热更新
自动刷新:(一般不开启,用devserver)
自动刷新:整个网页全部刷新,速度较慢,状态会丢失
热更新:新代码生效,网页不刷新,状态不丢失
用到插件:
const HotModuleReplacementPlugin = require('webpack/lib/HotModuleReplacementPlugin');
主要修改 entry -> index,plugins,devserver:hot,
以及需要自己注册 哪些模块需要触发 热更新,指定模块之外还是会刷新。(如index.js)
webpack.dev.js:
const path = require('path')
const webpack = require('webpack')
const webpackCommonConf = require('./webpack.common.js')
const { smart } = require('webpack-merge')
const { srcPath, distPath } = require('./paths')
const HotModuleReplacementPlugin = require('webpack/lib/HotModuleReplacementPlugin');
module.exports = smart(webpackCommonConf, {
mode: 'development',
entry: {
// index: path.join(srcPath, 'index.js'),
index: [
'webpack-dev-server/client?http://localhost:8080/',
'webpack/hot/dev-server',
path.join(srcPath, 'index.js')
],
other: path.join(srcPath, 'other.js')
},
module: {
rules: [
{
test: /\.js$/,
loader: ['babel-loader?cacheDirectory'],
include: srcPath,
// exclude: /node_modules/
},
// 直接引入图片 url
{
test: /\.(png|jpg|jpeg|gif)$/,
use: 'file-loader'
},
// {
// test: /\.css$/,
// // loader 的执行顺序是:从后往前
// loader: ['style-loader', 'css-loader']
// },
{
test: /\.css$/,
// loader 的执行顺序是:从后往前
loader: ['style-loader', 'css-loader', 'postcss-loader'] // 加了 postcss
},
{
test: /\.less$/,
// 增加 'less-loader' ,注意顺序
loader: ['style-loader', 'css-loader', 'less-loader']
}
]
},
plugins: [
new webpack.DefinePlugin({
// window.ENV = 'production'
ENV: JSON.stringify('development')
}),
new HotModuleReplacementPlugin()
],
devServer: {
port: 8080,
progress: true, // 显示打包的进度条
contentBase: distPath, // 根目录
open: true, // 自动打开浏览器
compress: true, // 启动 gzip 压缩
hot: true,
// 设置代理
proxy: {
// 将本地 /api/xxx 代理到 localhost:3000/api/xxx
'/api': 'http://localhost:3000',
// 将本地 /api2/xxx 代理到 localhost:3000/xxx
'/api2': {
target: 'http://localhost:3000',
pathRewrite: {
'/api2': ''
}
}
}
},
// watch: true, // 开启监听,默认为 false
// watchOptions: {
// ignored: /node_modules/, // 忽略哪些
// // 监听到变化发生后会等300ms再去执行动作,防止文件更新太快导致重新编译频率太高
// // 默认为 300ms
// aggregateTimeout: 300,
// // 判断文件是否发生变化是通过不停的去询问系统指定文件有没有变化实现的
// // 默认每隔1000毫秒询问一次
// poll: 1000
// }
})
index.js:
// 引入 css
import './style/style1.css'
import './style/style2.less'
import { sum } from './math'
const sumRes = sum(10, 20)
console.log('sumRes', sumRes)
// 增加,开启热更新之后的代码逻辑
if (module.hot) {
module.hot.accept(['./math'], () => {
const sumRes = sum(10, 30)
console.log('sumRes in hot', sumRes)
})
}
webpack 性能优化
优化打包构建效率 - 开发体验和效率
可用于生产环境的:
- 优化babel-loader
- IgnorePlugin 避免引入无用模块
- noParse 避免重复打包
- happyPack多进程打包
- ParallelUglifyPlugin 多进程压缩JS
不可用于生产环境的:
- 自动刷新
- 热更新
- DllPlugin
优化babel-loader
{
test: /\.js$/,
loader: ['babel-loader?cacheDirectory'], // cacheDirectory开启缓存,es6代码没有改的就缓存,不会重新编译
include: path.resolve(__dirname, 'src'), // 明确范围
// exclude: /node_modules/
},
IgnorePlugin 避免引入无用模块
可以让打包出来的代码更少些
prod.js
plugins: [
// 忽略 moment 下的表示语言的 /locale 目录(忽略所有语言包,但会引moment目录下的其他功能文件),改为按需引入
new webpack.IgnorePlugin(/\.\/locale/, /moment/),
],
index.js
import moment from 'moment'
import 'moment/locale/zh-cn' // 手动引入中文语言包
moment.locale('zh-cn') // 设置语言为中文
235kb 减少到 55kb
noParse 避免重复打包
module.exports = {
module: {
// 完整的'react.min.js'文件没有采用模块化
// 忽略对'react.min.js'文件的递归解析处理
// xx.min.js 基本上都采用模块化处理过了,没必要再做一次打包(不走babel,或者模块化分析等)处理
noParse: [/react\.min\.js$]
}
}
ingnorePlugin
和 noParse
区别:IgnorePlugin
直接不引入,代码中没有
noParse
引入,但不打包
happyPack多进程打包
JS是单线程的,开启多进程打包,可以提高构建速度(特别是多核CPU)。
const HappyPack = require('happypack')
module.exports = smart(webpackCommonConf, {
module: {
rules: [
// js
{
test: /\.js$/,
// 把对 .js 文件的处理转交给 id 为 babel 的 HappyPack 实例
use: ['happypack/loader?id=babel'],
include: srcPath,
// exclude: /node_modules/
},
]
},
plugins: [
// happyPack 开启多进程打包
new HappyPack({
// 用唯一的标识符 id 来代表当前的 HappyPack 是用来处理一类特定的文件
id: 'babel',
// 如何处理 .js 文件,用法和 Loader 配置中一样
loaders: ['babel-loader?cacheDirectory']
}),
],
})
ParallelUglifyPlugin 多进程压缩JS
webpack 内置Uglify 工具压缩JS。JS是单线程的,开启多进程压缩更快。和happyPack同理
注意:如果代码本身体积不大,没必要开启多进程,因为开启多进程也是耗费时间的。
webpack.prod.js。只在生产环境下压缩
const ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin')
module.exports = smart(webpackCommonConf, {
plugins: [
// 使用 ParallelUglifyPlugin 并行压缩输出的 JS 代码
new ParallelUglifyPlugin({
// 传递给 UglifyJS 的参数
// (还是使用 UglifyJS 压缩,只不过帮助开启了多进程)
uglifyJS: {
output: {
beautify: false, // 最紧凑的输出
comments: false, // 删除所有的注释
},
compress: {
// 删除所有的 `console` 语句,可以兼容ie浏览器
drop_console: true,
// 内嵌定义了但是只用到一次的变量
collapse_vars: true,
// 提取出出现多次但是没有定义成变量去引用的静态值
reduce_vars: true,
}
}
})
],
})
关于开启多进程:
- 项目较大,打包较慢,开启多进程能提高速度
- 项目较小,打包很快,开启多进程会降低速度(进程开启,通信,销毁等开销)
- 按需使用
自动刷新
热更新
DllPlugin
DLLPlugin 动态链接库插件
前端框架如 vue React ,体积大,构建慢,但是较稳定,不唱升级版本,所以同一个版本只构建一次即可,不用每次都重新构建。
webpack 已内置 DLLPlugin 支持。DLLPlugin 打包出dll文件,DllReferencePlugin 使用dll文件。
webpack.dll.js:
const path = require('path')
const DllPlugin = require('webpack/lib/DllPlugin')
const { srcPath, distPath } = require('./paths')
module.exports = {
mode: 'development',
// JS 执行入口文件
entry: {
// 把 React 相关模块的放到一个单独的动态链接库
react: ['react', 'react-dom']
},
output: {
// 输出的动态链接库的文件名称,[name] 代表当前动态链接库的名称,
// 也就是 entry 中配置的 react 和 polyfill
filename: '[name].dll.js',
// 输出的文件都放到 dist 目录下
path: distPath,
// 存放动态链接库的全局变量名称,例如对应 react 来说就是 _dll_react
// 之所以在前面加上 _dll_ 是为了防止全局变量冲突
library: '_dll_[name]',
},
plugins: [
// 接入 DllPlugin
new DllPlugin({
// 动态链接库的全局变量名称,需要和 output.library 中保持一致
// 该字段的值也就是输出的 manifest.json 文件 中 name 字段的值
// 例如 react.manifest.json 中就有 "name": "_dll_react"
name: '_dll_[name]',
// 描述动态链接库的 manifest.json 文件输出时的文件名称
path: path.join(distPath, '[name].manifest.json'),
}),
],
}
dev中引用:
// 第一,引入 DllReferencePlugin
const DllReferencePlugin = require('webpack/lib/DllReferencePlugin');
module.exports = merge(webpackCommonConf, {
mode: 'development',```
module: {
rules: [
{
test: /\.js$/,
use: ['babel-loader'],
include: srcPath,
exclude: /node_modules/ // 第二,不要再转换 node_modules 的代码
},
]
},
plugins: [
new webpack.DefinePlugin({
// window.ENV = 'production'
ENV: JSON.stringify('development')
}),
// 第三,告诉 Webpack 使用了哪些动态链接库
new DllReferencePlugin({
// 描述 react 动态链接库的文件内容
manifest: require(path.join(distPath, 'react.manifest.json')),
}),
],
}
优化产出代码 - 产品性能
作用:
- 让打包出来的代码体积更小,加载更快
- 合理分包,不重复加载(重复逻辑分出来,可以相互引用)
- 执行速度更快、内存占用更小
小图片 base64 编码
webpack.prod.js
const path = require('path')
const webpack = require('webpack')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const webpackCommonConf = require('./webpack.common.js')
const { smart } = require('webpack-merge')
const { srcPath, distPath } = require('./paths')
module.exports = smart(webpackCommonConf, {
mode: 'production',
output: {
filename: 'bundle.[contentHash:8].js', // 打包代码时,加上 hash 戳
path: distPath,
// publicPath: 'http://cdn.abc.com' // 修改所有静态文件 url 的前缀(如 cdn 域名),这里暂时用不到
},
module: {
rules: [
// 图片 - 考虑 base64 编码的情况
{
test: /\.(png|jpg|jpeg|gif)$/,
use: {
loader: 'url-loader',
options: {
// 小于 5kb 的图片用 base64 格式产出
// 否则,依然延用 file-loader 的形式,产出 url 格式
limit: 5 * 1024,
// 打包到 img 目录下
outputPath: '/img1/',
// 设置图片的 cdn 地址(也可以统一在外面的 output 中设置,那将作用于所有静态资源)
// publicPath: 'http://cdn.abc.com'
}
}
},
]
},
plugins: [
new CleanWebpackPlugin(), // 会默认清空 output.path 文件夹
new webpack.DefinePlugin({
// window.ENV = 'production'
ENV: JSON.stringify('production')
})
]
})
产出的 bundle 文件 加上 hash
配合缓存策略。上线之后,如果前端代码没有改变,那么浏览器就会命中缓存,就不会再重新加载一遍。
代码见上
懒加载
例如某块数据非常大,通过import.then 实现引入
// 引入动态数据
setTimeout(() => {
// 回顾 vue react 异步组件
// 类似于定义了一个chunk
import('./dynamic-data.js').then(res => {
log(res.default.message) // 注意default
})
}, 1500)
抽离公共代码和第三方代码
主要用于生产环境。经常需要import的代码可以单独打成一个包
optimization
中配置splitChunks
在HtmlWebpackPlugin
中配置chunks。
webpack.common.js:
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { srcPath, distPath } = require('./paths')
module.exports = {
entry: {
index: path.join(srcPath, 'index.js'),
other: path.join(srcPath, 'other.js')
},
module: {
rules: [
{
test: /\.js$/,
loader: ['babel-loader'],
include: srcPath,
exclude: /node_modules/
}
]
},
plugins: [
// new HtmlWebpackPlugin({
// template: path.join(srcPath, 'index.html'),
// filename: 'index.html'
// })
// 多入口 - 生成 index.html
new HtmlWebpackPlugin({
template: path.join(srcPath, 'index.html'),
filename: 'index.html',
// chunks 表示该页面要引用哪些 chunk (即上面的 index 和 other),默认全部引用
chunks: ['index', 'vendor', 'common'] // 要考虑代码分割
}),
// 多入口 - 生成 other.html
new HtmlWebpackPlugin({
template: path.join(srcPath, 'other.html'),
filename: 'other.html',
chunks: ['other', 'common'] // 考虑代码分割
})
]
}
webpack.prod.js
const path = require('path')
const webpack = require('webpack')
const { smart } = require('webpack-merge')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const TerserJSPlugin = require('terser-webpack-plugin')
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin')
const webpackCommonConf = require('./webpack.common.js')
const { srcPath, distPath } = require('./paths')
module.exports = smart(webpackCommonConf, {
mode: 'production',
output: {
// filename: 'bundle.[contentHash:8].js', // 打包代码时,加上 hash 戳
filename: '[name].[contentHash:8].js', // name 即多入口时 entry 的 key
path: distPath,
// publicPath: 'http://cdn.abc.com' // 修改所有静态文件 url 的前缀(如 cdn 域名),这里暂时用不到
},
module: {
rules: [
// 图片 - 考虑 base64 编码的情况
{
test: /\.(png|jpg|jpeg|gif)$/,
use: {
loader: 'url-loader',
options: {
// 小于 5kb 的图片用 base64 格式产出
// 否则,依然延用 file-loader 的形式,产出 url 格式
limit: 5 * 1024,
// 打包到 img 目录下
outputPath: '/img1/',
// 设置图片的 cdn 地址(也可以统一在外面的 output 中设置,那将作用于所有静态资源)
// publicPath: 'http://cdn.abc.com'
}
}
},
// 抽离 css
{
test: /\.css$/,
loader: [
MiniCssExtractPlugin.loader, // 注意,这里不再用 style-loader
'css-loader',
'postcss-loader'
]
},
// 抽离 less
{
test: /\.less$/,
loader: [
MiniCssExtractPlugin.loader, // 注意,这里不再用 style-loader
'css-loader',
'less-loader',
'postcss-loader'
]
}
]
},
plugins: [
new CleanWebpackPlugin(), // 会默认清空 output.path 文件夹
new webpack.DefinePlugin({
// window.ENV = 'production'
ENV: JSON.stringify('production')
}),
// 抽离 css 文件
new MiniCssExtractPlugin({
filename: 'css/main.[contentHash:8].css'
})
],
optimization: {
// 压缩 css
minimizer: [new TerserJSPlugin({}), new OptimizeCSSAssetsPlugin({})],
// 分割代码块
splitChunks: {
chunks: 'all',
/**
* initial 入口 chunk,对于异步导入的文件不处理
async 异步 chunk,只对异步导入的文件处理
all 全部 chunk
*/
// 缓存分组
cacheGroups: {
// 第三方模块
vendor: {
name: 'vendor', // chunk 名称
priority: 1, // 权限更高,优先抽离,重要!!!
test: /node_modules/,
minSize: 0, // 大小限制
minChunks: 1 // 最少复用过几次
},
// 公共的模块
common: {
name: 'common', // chunk 名称
priority: 0, // 优先级
minSize: 0, // 公共模块的大小限制
minChunks: 2 // 公共模块最少复用过几次
}
}
}
}
})
IgnorePlugin 避免引入无用模块
使用CDN加速
参考小图片 base64 编码
// publicPath: 'http://cdn.abc.com' // 修改所有静态文件 url 的前缀(如 cdn 域名),这里暂时用不到
需要额外操作:将dist目录下的文件上传到CDN资源上
使用 mode: production
优点:
- 会自动开启代码压缩(dev环境下可以用uglifyPlugin,但没有必要压缩)
- Vue React 等会自动删掉调试代码(如开发环境的warning)
- 启动Tree-Shaking(未被用到的函数不会加载,体积更小。但是必须用es6 Module 才能让其生效)
为什么(ES6 Module 和 Common JS 区别):
- ES6 Module 是静态引入,编译时引入(必须是无条件的直接引入,而不是通过变量来判断它是不是引用)
- Common JS 是静态引入,执行时引入(打包时候不知道,执行时候才知道)
- 所以只有ES6 Module 才能静态分析,实现Tree-Shaking
Scope Hosting
可以让打包后 的 多个函数 放到一个函数里,创建的函数作用域更少,减少内存消耗
babel
环境搭建 & 基本配置
babel-polyfill
什么是 polyfill:类似于补丁。远古时期,一些浏览器不支持数组.indexOf,通过polyfill可以打补丁,让数组具备这个方法。
core-js 和 regenerator:core-js 是所有稳定的 polyfill 的集合。regenerator 用于为 polyfill 提供支持。
babel-polyfill 就是两者的结合。babel 7.4 之后已被弃用。推荐直接用core-js 和 regenerator。
babel 只负责解析语法,不会检测 数组新 API 或 模块化 等,所以需要做一个补丁或者兼容。
配置按需引入:
.babelrc
{
"presets": [
[
"@babel/preset-env",
{
"useBuiltIns": "usage", // impotant
"corejs": 3
}
]
],
"plugins": [
[
"@babel/plugin-transform-runtime",
{
"absoluteRuntime": false,
"corejs": 3,
"helpers": true,
"regenerator": true,
"useESModules": false
}
]
]
}
babel-runtime
针对babel-polyfill配置的缺点:可能会污染全局变量。
需要安装插件:@babel/plugin-transform-runtime,@babel/runtime
重新取名,不会污染全局变量。