Webpack
一、Webpack 基本使用
Webpack 简介
Webpack是一种前端资源构建工具,一个静态模块打包器(module bundler)。在 Webpack 看来,前端的所有资源文件(js / json / css / img / less / ...)都会作为模块处理,它将根据模块的依赖关系进行静态分析,打包生成对应的静态资源(bundle)。
Webpack 五个核心概念
1.Entry
入口(Entry)指示 Webpack 以哪个文件为入口起点开始打包,分析构建内部依赖图
2.Output
输出(Output)指示 Webpack 打包后的资源 bundles 输出到哪里去,以及如何命名
3.Loader
加载器(Loader)让 Webpack 能够去处理那些非 js 文件(Webpack 自身只理解 js)
4.Plugins
插件(Plugins)可以用于执行范围更广的任务。插件的范围包括,从打包优化到压缩,一直到重新定义环境中的变量等
5.Mode
模式(Mode)指示 Webpack 使用相应模式的配置
选型 | 描述 | 特点 |
---|---|---|
development | process.env.NODE_ENV = development。 启用 NamedChunksPlugin 和 NamedModulesPlugin |
代码本地调试运行的环境 |
production | process.env.NODE_ENV = production。 启用 FlagDependencyUsagePlugin,FlagIncludeChunksPlugin,ModuleConcatenationPlugin,NoEmitOnErrorsPlugin,OccurrenceOrderPlugin,SideEffectsFlagPlugin 和 UglifyJsPlugin |
代码优化上线运行的环境 |
Webpack 安装
1.全局安装
npm i webpack webpack-cli -g
2.开发环境安装
npm i webpack webpack-cli -D
Webpack 打包指令
1.开发环境打包
webpack ./src/main.js -o ./build/built.js --mode=development
2.生产环境打包
webpack ./src/main.js -o ./build/built.js --mode=production
Webpack 配置文件
// resolve用来拼接绝对路径
const {resolve} = require('path');
// webpack配置
module.exports = {
// 入口文件
entry: './src/index.js',
// 输出
output: {
// 输出文件名
filename: 'built.js',
// 输出路径
// __dirname是nodejs 的变量,代表当前文件的目录绝对路径
path: resolve(__dirname, 'build')
},
// loader的配置
module: {
rules: [
// 详细loader配置
]
},
// plugins的配置
plugins: [
// 详细plugins配置
],
mode: 'development'
// mode: 'production'
}
二、Webpack 开发环境配置
打包样式资源
安装 module
npm i css-loader style-loader less less-loader -D
webpack.config.js
module: {
rules: [
// 详细loader配置
{
// 匹配css文件
test: /\.css$/,// 注意这里不要带引号
// 使用loader进行处理
use: [
// use数组中loader执行顺序:从右到左,从下到上,依次执行
// 创建style标签,将js的样式资源插入进去,添加到head中生效
'style-loader',
// 将css文件变成commonjs模块加载到js中,里面的内容时样式字符串
'css-loader'
]
},
{
// 匹配less文件
test: /\.less$/,
// 使用loader进行处理
use: [
'style-loader',
'css-loader',
// 将less文件编译成css文件
'less-loader'
]
}
]
}
打包 HTML 资源
安装 module
npm i html-webpack-plugin -D
webpack.config.js
plugins: [
// 默认会创建一个空的HTML,自动引入打包输出的所有资源(JS/CSS)
new HtmlWebpackPlugin({
// 复制'./src/index.html'文件,并自动引入打包输出的所有资源(JS/CSS)
template: './src/index.html'
})
],
注意不要在 HTML 页面手动引入 JS/CSS 资源,打包后会自动引入
打包图片资源
安装 module
npm i url-loader file-loader html-loader -D
webpack.config.js
module: {
rules: [
{
test: /\.less$/,
use: ['style-loader', 'css-loader', 'less-loader']
},
{
test:/\.(png|jpg)$/,
loader: 'url-loader',
options: {
// 图片大小如果小于5kb,就会被base64处理
// base64优点:减少请求数量(减轻服务器压力)
// base64缺点:图片体积会更大(文件请求速度更慢)
limit: 5 * 1024,
// 给图片重命名
// [hash:10]指取图片的hash值的前10位
// [ext]指图片原来的扩展名
name: '[hash:10].[ext]'
}
},
{
test: /\.html$/,
// 处理html文件的img图片(负责引入img,从而能被url-loader进行处理)
loader: 'html-loader'
}
]
},
打包字体图标资源
使用 module:file-loader
webpack.config.js
module: {
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
},
{
// 排除js|css|html|less|png|jpg资源
exclude: /\.(js|css|html|less|png|jpg)/,
loader: 'file-loader',
options: {
name: '[hash:10].[ext]'
}
}
]
},
devServer
开发服务器(devServer):用于自动化
- 自动编译
- 自动打开浏览器
- 自动刷新浏览器
特点:只会在内存中编译打包,不会有任何输出
安装 devServer:
npm i webpack-dev-server -D
启动 devServer 指令:(本地安装启动时使用 npx)
npx webpack-dev-server
devServer 配置:
devServer: {
// 项目构建后的路径
contentBase: resolve(__dirname, 'build'),
// 启动gzip压缩
compress: true,
// 端口号
port: 9527,
// 自动打开浏览器
open: true
}
开发环境基本配置
const {resolve} = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: './src/js/index.js',
output: {
filename: 'js/built.js',//
path: resolve(__dirname, 'build')
},
module: {
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
},
{
test: /\.less$/,
use: ['style-loader', 'css-loader', 'less-loader']
},
{
test: /\.(png|jpg)$/,
loader: 'url-loader',
options: {
limit: 5 * 1024,
name: '[hash:10].[ext]',
outputPath: 'img'// 指定打包后输出的路径
}
},
{
test: /\.html$/,
loader: 'html-loader'
},
{
exclude: /\.(html|css|js|less|png|jpg)/,
loader: 'file-loader',
options: {
name: '[hash:10].[ext]',
outputPath: 'font'
}
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html'
})
],
mode: 'development',
devServer: {
contentBase: resolve(__dirname, 'build'),
compress: true,
open: true,
port: 9527
}
}
三、Webpack 生产环境配置
CSS 处理
将 CSS 提取成单独文件
安装 module
npm i mini-css-extract-plugin -D
webpack.config.js
const {resolve} = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
entry: './src/js/index.js',
output: {
filename: 'built.js',
path: resolve(__dirname, 'build')
},
module: {
rules: [
{
test: /\.css$/,
use: [
// 用这个loader取代style-loader,将js中的css提取成单独文件
MiniCssExtractPlugin.loader,
'css-loader'
]
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html'
}),
new MiniCssExtractPlugin({
filename: 'css/main.css'
})
],
mode: 'development'
};
CSS 兼容性处理
安装 module
npm i postcss-loader postcss-preset-env -D
webpack.config.js
const {resolve} = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
entry: './src/js/index.js',
output: {
filename: 'built.js',
path: resolve(__dirname, 'build')
},
module: {
rules: [
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
{
loader: 'postcss-loader',
options: {
ident: 'postcss',
plugins: () => [
// postcss的插件
require('postcss-preset-env')
]
}
}
]
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html'
}),
new MiniCssExtractPlugin({
filename: 'css/style.css'
})
],
mode: 'development'
};
package.json 配置
"browserslist": {
"development": [
"last 1 chrome version", // 兼容 chrome 最新的版本
"last 1 firefox version",
"last 1 safari version"
],
"production": [
">0.1%",// 兼容99.9%的浏览器
"not dead",// 除去不再使用的浏览器
"not op_mini all"// 除去op_mini所有的浏览器
]
}
注意 postcss-loader 默认使用生产环境的配置,要使用开发环境的配置,需要在 webpack.config.js 中设置 nodejs 的环境变量
process.env.NODE_ENV = 'development'
压缩 CSS
安装 module
npm i optimize-css-assets-webpack-plugin -D
webpack.config.js 中引入并使用该插件
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');
plugins: [
new OptimizeCssAssetsWebpackPlugin()
]
JS 处理
JS 语法检查
安装 module
npm i eslint eslint-loader eslint-config-airbnb-base eslint-plugin-import -D
创建测试的 index.js
function add(x,y) {
return x+y;
}
console.log(add(1,2));
webpack.config.js
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'eslint-loader',
options: {
// 自动修复
fix: true
}
}
]
},
注意eslint 只需要检查自己写的源代码,不需要检查第三方库,所以要排除第三方库,即 node_modules
设置检查规则,package.json 配置
"eslintConfig": {
"extends": "airbnb-base"
}
"extends": "airbnb-base"
指使用 airbnb-base
的语法风格
eslint 识别不了 window、document 等全局变量,打包时会报错,可以在 eslintConfig 中添加配置:
"eslintConfig": {
"extends": "airbnb-base",
"env": {
"browser": true // 支持浏览器端全局变量
}
}
warning Unexpected console statement no-console
:指不期望的 console 语句,开发环境中调试可以,生产环境中不建议使用 console 语句,可以设置不检查
// eslint-disable-next-line
指下一行不进行 eslint 检查
JS 兼容性处理
1.JS 基本兼容性处理
安装 module
npm i babel @babel/preset-env @babel/core -D
webpack.config.js
module: {
rules: [
{
test: /\.js$/,
loader: 'babel-loader',
options: {
// 预设:指定 babel 做什么样的兼容性处理
presets: ['@babel/preset-env']
}
}
]
}
这种方式只能转换基本的语法,对于像 promise 这样的新特性不能转换
2.JS 全部兼容性处理
安装 module
npm i @babel/polyfill -D
在入口文件中引入
import '@babel/polyfill'
这种方式会将所有的新特性全部转换,造成打包后的 js 文件提供过大
3.JS 按需加载兼容性处理
安装 module
npm i core-js -D
webpack.config.js
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
options: {
// 预设:指定 babel 做什么样的兼容性处理
presets: [ // 注意这里需要在数组中嵌套数组
[
'@babel/preset-env',
{
// 按需加载
useBuiltIns: 'usage',
// 指定 core-js 版本
corejs: {
version: 3
},
// 指定兼容性做到哪个版本的浏览器
targets: {
chrome: '60',
firefox: '60',
ie: '9',
safari: '10'
}
}
]
]
}
}
]
}
这种按需加载的方式打包后的 js 文件会比第二种方式打包后的 js 文件体积小很多(core-js🐮🍺)
压缩 JS
压缩 JS 只需要在 webpack.config.js 中的 mode 改为 production 即可,生产环境下自动压缩 JS 代码
mode: 'production'
HTML 处理
压缩 HTML
webpack.config.js
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html',
// 压缩html代码
minify: {
// 移除空格
collapseWhitespace: true,
// 移除注释
removeComments: true
}
})
]
生产环境基本配置
const {resolve} = require('path');
const HtmlWebpackPlugin =require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');
// 抽取共用的 loader
const commonCssLoader = [
// 将 CSS 抽取成单独文件
{
loader: MiniCssExtractPlugin.loader,
options: {
publicPath: '../' // 解决打包后的图片路径问题
}
},
'css-loader',
// CSS 兼容性处理
{
loader: 'postcss-loader',
options: {
ident: 'postcss',
plugins: () => [
require('postcss-preset-env')
]
// 需要在 package.json 中指定 browserslist:即兼容哪些版本的浏览器
}
}
];
process.env.NODE_ENV = 'production';
module.exports = {
entry: './src/js/index.js',
output: {
filename: 'js/built.js',
path: resolve(__dirname, 'build'),
},
module: {
rules: [
// 处理 CSS 资源
{
test: /\.css$/,
use: [...commonCssLoader]
},
// 处理 less 资源
{
test: /\.less$/,
use: [...commonCssLoader, 'less-loader']
},
// JS 语法检查
{
test: /\.js$/,
exclude: /node_modules/,
enforce: 'pre', // 为了防止冲突,指定 eslint-loader 先执行
loader: 'eslint-loader',
// 需要在 package.json 中指定 eslintConfig
options: {
fix: true
}
},
// JS 兼容性处理
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
options: {
presets: [
[
'@babel/preset-env',
{
useBuiltIns: 'usage',
corejs: {
version: 3
},
targets: {
chrome: '60',
ie: '9'
}
}
]
]
}
},
// 处理图片资源
{
test: /\.(jpg|png)/,
loader: 'url-loader',
options: {
limit: 5 * 1024,
name: '[hash:10].[ext]',
outputPath: 'img'
}
},
// 处理html中的img资源
{
test: /\.html$/,
loader: 'html-loader',
},
// 处理其他资源
{
exclude: /\.(html|js|css|less|jpg|png)/,
loader: 'file-loader',
options: {
name: '[hash:10].[ext]',
outputPath: 'font',
}
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html',
minify: {
collapseWhitespace: true,
removeComments: true
}
}),
// 抽取 CSS
new MiniCssExtractPlugin({
filename: 'css/built.css'
}),
// 压缩 CSS
new OptimizeCssAssetsWebpackPlugin()
],
mode: 'production'
}
四、Webpack 性能优化
开发环境性能优化
- 优化打包构建速度
- HMR
- 优化代码调试
- source-map
生产环境性能优化
- 优化打包构建速度
- oneOf
- babel 缓存
- 多进程打包
- externals
- dll
- 优化代码运行性能
- 文件资源缓存
- tree-shaking
- code split
- 懒加载和预加载
- PWA
HMR
HMR:hot modules replacement,热模块更新/模块热更新,用来当某个文件更新以后,打包时只打包那一个文件,而不用打包所有文件,极大提升打包速度
HMR 只能在开发环境中使用,devServer 内部支持 HMR,只需要开启即可
devServer: {
// 开启 HMR
hot: true
}
开启后,更新样式文件
可以看到打包时只打包了样式文件,页面也没有刷新,这是因为 style-loader 内部实现了 HMR
更新 JS 文件,页面刷新了,因为 JS 默认不支持 HMR 功能,要想实现 HMR,可以为需要的 JS 文件添加监听事件
if(module.hot){
module.hot.accept('./print.js', function(){
print();
})
}
如果 module.hot 为 true,说明开启了 HMR 功能,此时添加监听事件监听 print.js 文件的变化,如果发生变化,其他的文件不会重新打包构建,然后会执行后面的回调函数,将需要更新的代码放入其中,注意只能监听非入口 JS 文件,入口文件更新后,会重新打包构建
开启 HMR 后,修改 HTML 文件后页面不生效,可以修改 entry 入口,将 HTML 文件引入
entry: ['./src/js/index.js', './src/index.html']
因为项目一般只有一个HTML 文件,当 HTML 文件发生变化时,所有的文件都会发生变化,所以 HTMl 文件不用实现 HMR
source-map
source-map:一种提供源代码到构建后代码映射的技术,如果构建后代码出错了,可以通过映射追踪到源代码错误
开启 source-map:
webpack.config.js 中配置
devtool: 'source-map'
source-map 分类:
-
外部
-
source-map
提供错误代码准确信息和源代码的错误位置
-
hidden-source-map
提供错误代码准确信息,但是没有错误位置,不能追踪源代码错误,只能提示构建后代码的错误位置,只隐藏源代码
-
nosources-source-map
提供错误代码准确信息,但是没有任何错误位置,隐藏源代码和构建后代码
-
cheap-source-map
提供错误代码准确信息和源代码的错误位置,但是只能精确到行
-
cheap-module-source-map
提供错误代码准确信息和源代码的错误位置,并且加入了 loader 的 source-map,也只能精确到行
-
-
内联
-
inline-source-map
只生成一个内联 source-map,提供错误代码准确信息和源代码的错误位置
-
eval-source-map
每一个文件都生成对应的 source-map,提供错误代码准确信息和源代码的错误位置
-
开发环境使用 source-map:
要求:速度快,调试更友好,使用 eval-source-map / eval-cheap-module-source-map
生产环境使用 source-map:
内联会让代码体积变大,所以在生产环境不用内联
考虑:源代码是否隐藏,调试是否友好,使用 source-map / cheap-module-surce-map
oneOf
- 使用 oneOf 根据文件类型加载对应的 loader,只要匹配一个即可退出
- 对于同一类型文件,比如处理 js,需要多个 loader,可以抽离出其中一个,确保 oneOf 里面一种文件类型只对应一个 loader
webpack.config.js
module: {
rules: [
// 将 eslint-loader 放在oneOf外面,指定先执行
{
test: /\.js$/,
exclude: /node_modules/,
enforce: 'pre', // 为了防止冲突,指定 eslint-loader 先执行
loader: 'eslint-loader',
// 需要在 package.json 中指定 eslintConfig
options: {
fix: true
}
},
{
oneOf: [
{
test: /\.css$/,
use: [...commonCssLoader]
},
{
test: /\.less$/,
use: [...commonCssLoader, 'less-loader']
},
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
options: {
presets: [
[
'@babel/preset-env',
{
useBuiltIns: 'usage',
corejs: {
version: 3
},
targets: {
chrome: '60',
ie: '9'
}
}
]
]
}
},
{
test: /\.(jpg|png)/,
loader: 'url-loader',
options: {
limit: 5 * 1024,
name: '[hash:10].[ext]',
outputPath: 'img'
}
},
{
test: /\.html$/,
loader: 'html-loader',
},
{
exclude: /\.(html|js|css|less|jpg|png)/,
loader: 'file-loader',
options: {
name: '[hash:10].[ext]',
outputPath: 'font',
}
}
]
}
]
}
缓存
缓存分为 babel 缓存和文件资源缓存
babel 缓存
babel-loader 添加配置
options: {
cacheDirectory: true
}
文件资源缓存
- hash:每次 webpack 构建时会生成唯一的 hash 值,但是 JS 和 CSS 使用同一个 hash 值,如果只改动一个文件然后重新打包,会导致所有缓存失效
- chunkhash:根据 chunk 生成的 hash 值,如果打包来源于同一个 chunk,那么 hash 值就一样,因为 CSS 是在 JS 中引入,所以同属于一个 chunk
- contenthash:根据文件的内容生成的 hash 值,不同文件的 hash 值肯定不一样
tree shaking
tree-shaking 用于移除 JS 上下文中的未引用代码,它依赖于 ES6 模块化,减少代码体积
使用 tree-shaking:
- 必须使用 ES6 模块化
- 开启 production 环境
为了避免造成误杀,可以在 package.json 中配置 sideEffects
"sideEffects": ["*.CSS", "*.less"]
code split
code split 用于把代码分离到不同的 bundle 中,可以按需加载或并行加载这些文件
通过多入口拆分
对象形式多入口,有一个入口,最终输出就有一个 bundle
entry: {
index: './src/js/index.js',
test: './src/js/test.js'
},
output: {
filename: 'js/[name].[contenthash:10].js',
path: resolve(__dirname, 'build'),
}
单入口配置 splitChunks
可以将 node_modules 中的代码单独打包成一个 chunk 输出
optimization: {
splitChunks: {
chunks: 'all'
}
}
单入口打包时会将入口文件与在入口文件中引入的其他 js 文件打包为一个 chunk,可以使用 import 动态导入语法,将引入的文件单独打包为一个 chunk
index.js
import('./test').then(({reduce}) => {
console.log(reduce(1,2,3,4,5));
}).catch(() => {
console.log('文件加载失败');
});
打包后生成的 JS 文件是通过 id 来命名的,可以通过 webpack 的魔法注释来指定名称
import(/* webpackChunkName:'test'*/'./test')
多入口配置 splitChunks
自动分析多入口 chunk 中,有没有公共的文件(至少大于30kb),如果有会单独打包成一个 chunk
懒加载和预加载
懒加载和预加载基于代码分割
正常加载:可以认为是并行加载(同一时间加载很多文件),没有先后顺序
懒加载:当文件需要使用时才加载
document.getElementById('btn').onclick = function () {
import(/* webpackChunkName:'test'*/'./test').then(({reduce}) => {
console.log(reduce(1,2,3,4,5));
}).catch(() => {
console.log('文件加载失败');
});
};
预加载:等其他资源加载完毕,浏览器空闲时,再进行加载(兼容性较差)
import(/* webpackChunkName:'test', webpackPrefetch: true*/'./test')
配置 webpackPrefetch 为 true 开启预加载
PWA
PWA:渐进式 web 应用,离线可访问技术
安装 module
npm i workbox-webpack-plugin -D
webpack.config.js
const WorkboxWebpackPlugin = require('workbox-webpack-plugin');
plugins: [
new WorkboxWebpackPlugin.GenerateSW({
// 删除旧的 serviceWorker
clientsClaim: true,
// 使 serviceWorker 快速启动
skipWaiting: true
})
]
打包后会生成一个 serviceWorker 配置文件
index.js
// 注册 serviceWorker
// 处理兼容性问题
// navigator 对象包含有关浏览器的信息
if('serviceWorker' in navigator){
window.navigator.serviceWorker.register('./service-worker.js')
.then(() => {
console.log('sw注册成功')
}).catch(() => {
console.log('sw注册失败')
})
}
serviceWorker 代码必须运行在服务器上
可以通过 npm 安装 serve 包,通过 serve 启动
npm i serve -D
serve ./build
或者使用 express
npm i express -D
const express = require('express');
const app = express();
app.use(express.static('build'));
app.listen(3000);
启动后刷新浏览器即可看到
多进程打包
安装 module
npm i thread-loader -D
一般用于 babel-loader
webpack.config.js
module: {
rules: [
{
test: /\.js$/,
use: [
{
loader: 'thread-loader',
options: {
workers: 2 // 进程2个
}
},
{
loader: 'babel-loader',
options: {
presets: [
[
'@babel/preset-env',
{
useBuiltIns: 'usage',
corejs: {
version: 3
},
targets: {
chrome: '60',
ie: '9'
}
}
]
]
}
}
]
}
]
}
开启多进程打包,进程启动事件大概为600ms,进程通信也有开销,只有工作消耗时间比较长时,才需要多进程打包
externals
防止将某些 import 的包打包到 bundle 中,而是在运行时再去外部获取这些扩展依赖(external dependencies)
例如,从 CDN 引入 JQuery,而不把它打包
index.html
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
webpack.config.js
externals: {
// 库名: npm 包名
jquery: 'jQuery'
}
这样仍然可以使用 jQuery
import $ from 'jquery'
dll
在通常的打包过程中,会将第三方库也打包进 bundle 中,由于这些库的内容基本不会改变,每次都打包它们会造成性能的浪费。dll 可以在第一次打包时将所有的第三方库单独打包,而之后的打包则只打包自己编写的内容,可以和 code split 配合拆分文件
webpack.dll.js
const {resolve} = require('path');
const webpack = require('webpack');
module.exports = {
entry: {
jquery: ['jquery']
},
output: {
filename: '[name].js',
path: resolve(__dirname, 'dll'),
library: '[name]_[hash]'// 打包的库里面向外暴露的变量的名称
},
plugins: [
new webpack.DllPlugin({
name: '[name]_[hash]',// 映射库的暴露的内容名称
path: resolve(__dirname, 'dll/manifest.json')// 提供打包的库的映射
})
],
mode: 'production'
}
webpack 进行打包时,默认运行 webpack.config.js 配置文件,想要运行 webpack.dll.js,需要通过 --config 指定配置文件
webpack --config webpack.dll.js
打包后生成的 dll 文件夹中包含 jquery.js 和 manifest.json
引入打包生成的 jquery.js,需要安装一个 module
npm i add-asset-html-webpack-plugin -D
webpack.config.js
plugins: [
// 告诉webpack哪些库不参与打包,同时使用时的名称也要变
new webpack.DllReferencePlugin({
manifest: resolve(__dirname, 'dll/manifest.json')
}),
// 将某个文件打包输出,并在html中自动引入该资源
new AddAssetHtmlWebpackPlugin({
filepath: resolve(__dirname, 'dll/jquery.js')
})
]
五、Webpack 配置详解
entry 详细配置
entry:入口起点,用于指定入口文件,可以配置单入口和多入口,有以下几种类型
1.String 类型
entry: './src/index.js'
单入口,打包形成一个 chunk,输出一个 bundle 文件,此时 chunk 的默认名称是 main
2.array 类型
entry: ['./src/index.js', './src/test.js']
多入口,所有入口文件最终只会形成一个 chunk,输出一个 bundle 文件,只能用于 HMR 功能中使 HTML 热更新生效
entry: ['./src/index.js', './src/index.html']
3.Object 类型
entry: {
index: './src/index.js',
test: './src/test.js'
}
多入口,有几个入口文件就形成几个 chunk,输出几个 bundle 文件,此时 chunk 的名称是对应入口文件的名称
特殊用法:
entry: {
index: ['./src/index.js', './src/add.js'],
test: './src/test.js'
}
output 详细配置
webpack.config.js
output: {
// 文件名称(指定名称+目录)
filename: 'js/[name].js',
// 输出文件目录(所有资源输出的公共目录)
path: resolve(__dirname, 'dist'),
// 所有资源引入公共路径的前缀
publicPath: '/',
// 非入口chunk的名称
chunkFilename: 'js/[name]_chunk.js',
// 整个库向外暴露的变量名
library: '[name]',
// 变量名添加到window属性上 browser
libraryTarget: 'window',
// 变量名添加到global属性上 node
libraryTarget: 'global',
// 变量通过commonjs的方式向外暴露
libraryTarget: 'commonjs',
}
module 详细配置
webpack.config.js
module: {
rules: [
{
test: /\.js$/,
// 单个loader用loader
loader: 'eslint-loader',
// 排除 node_modules 下的js文件
exclude: /node_moduules/,
// 只检查 src 下的js文件
include: resolve(__dirname, src),
// 优先执行
enforce: 'pre',
// 延后执行
enforce: 'post',
// 不写则是中间执行
// loader 的配置
options: {
}
},
{
test: /\.css$/,
// 多个loader用use
use: ['style-loader', 'css-loader']
},
{
// 以下配置只会生效一个
oneOf: []
}
]
}
resolve 详细配置
resolve 指解析模块的规则
webpack.config.js
resolve: {
// 配置解析模块的路径别名
alias: {
// 配置绝对路径,用@代替src
@: resolve(__dirname, 'src')
},
// 配置省略文件路径的后缀名
extensions: ['js', 'json', 'jsx', 'css']
// 告诉 webpack 解析模块的目录路径
modules: [resolve(__dirname), '../../node_modules'), 'node_modules']
}
devServer 详细配置
devServer 用于开发环境
webpack.config.js
devServer: {
// 运行代码的目录
contentBase: resolve(__dirname, 'build'),
// 监视 contentBase 下的目录,一旦发生变化就会 reload
watchContentBase: true,
watchOptions: {
// 忽略 node_modules 下的文件
ignored: 'node_modules'
},
// 启动 gzip 压缩
compress: true,
// 端口号
port: 9527,
// 域名
host: 'localhost',
// 自动打开浏览器
open: true,
// 开启 HMR 功能
hot: true,
// 不显示启动服务器日志信息
clientLogLevel: 'none',
// 除了基本启动信息外,其他内容都不显示
quiet: true,
// 如果出错了,不全屏提示
overLay: false,
// 服务器代理,解决开发环境跨域问题
proxy: {
// 如果devServer(9527)服务器接收到 /api/xxx 的请求,就会把请求转发到另一个服务器(8080)
'/api': {
target: 'http://localhost:8080',
// 发送请求时,请求路径重写:将 /api/xxx 重写为 /xxx (去掉/api)
pathRewrite: {
'^/api': ''
}
}
}
}
optimization 详细配置
optimization 用于生产环境
contenthash 存在的问题:修改 a 文件导致 b 文件的 contenthash 改变。在入口文件 index.js 中引入 a.js,打包后 index.js 中记录的 a.js 的 hash 值,修改 a.js 后重新打包,因为 a.js 的 hash 值发生变化,所以 index.js 中记录的 a.js 的 hash 值也会发生变化,导致 index.js 也会重新打包,造成缓存失效
解决方法:runtimeChunk,将当前模块记录的其他模块的 hash 值单独打包为一个 runtime.js 文件,修改 a.js 后也只会影响 runtime.js,不会影响到 index.js
webpack.config.js
output: {
filename: 'js/[name].[contenthash:10].js',
path: resolve(__dirname, 'build'),
chunkFilename: 'js/[name].[contenthash:10]_chunk.js' // 指定非入口文件的名称
},
optimization: {
splitChunks: {
chunks: 'all',
/* 以下都是splitChunks默认配置,可以不写
miniSize: 30 * 1024, // 分割的chunk最小为30kb(大于30kb的才分割)
maxSize: 0, // 最大没有限制
minChunks: 1, // 要提取的chunk最少被引用1次
maxAsyncRequests: 5, // 按需加载时并行加载的文件的最大数量为5
maxInitialRequests: 3, // 入口js文件最大并行请求数量
automaticNameDelimiter: '~', // 名称连接符
name: true, // 可以使用命名规则
cacheGroups: { // 分割chunk的组
vendors: {
// node_modules中的文件会被打包到vendors组的chunk中,--> vendors~xxx.js
// 满足上面的公共规则,大小超过30kb、至少被引用一次
test: /[\\/]node_modules[\\/]/,
// 优先级
priority: -10
},
default: {
// 要提取的chunk最少被引用2次
minChunks: 2,
prority: -20,
// 如果当前要打包的模块和之前已经被提取的模块是同一个,就会复用,而不是重新打包
reuseExistingChunk: true
}
} */
},
// 将index.js记录的a.js的hash值单独打包到runtime文件中
runtimeChunk: {
name: entrypoint => `runtime-${entrypoint.name}`
},
minimizer: [
// 配置生产环境的压缩方案:js/css
new TerserWebpackPlugin({
// 开启缓存
cache: true,
// 开启多进程打包
parallel: true,
// 启用sourceMap(否则会被压缩掉)
sourceMap: true
})
]
}
六、Webpack5 介绍和使用
此版本重点关注以下内容:
- 通过持久缓存提高构建性能
- 使用更好的算法和默认值来改善长期缓存
- 通过更好的树摇和代码生成来改善捆绑包大小
- 清除处于怪异状态的内部结构,同时在 v4 中实现功能而不引入任何重大更改
- 通过引入重大更改来为将来的功能做准备,以使我们能够尽可能长时间地使用 v5
下载
npm i webpack@next webpack-cli -D
自动删除 Node.js Polyfills
早期,webpack 的目标是允许在浏览器中运行大多数 node.js 模块,但是模块格局发生了变化,许多模块用途现在主要是为前端目的而编写的。webpack <= 4 附带了许多 node.js 核心模块的 polyfill,一旦模块使用任何核心模块(即 crypto 模块),这些模块就会自动应用。
尽管这使使用为 node.js 编写的模块变得容易,但它会将这些巨大的 polyfill 添加到包中。在许多情况下,这些 polyfill 是不必要的。
webpack 5 会自动停止填充这些核心模块,并专注于与前端兼容的模块。
迁移:
- 尽可能尝试使用与前端兼容的模块。
- 可以为 node.js 核心模块手动添加一个 polyfill。错误消息将提示如何实现该目标。
chunk 和模块 ID
添加了用于长期缓存的新算法。在生产模式下默认情况下启用这些功能。
chunkIds: "deterministic", moduleIds: "deterministic"
chunk ID
你可以不用使用 import(/* webpackChunkName: "name" */ "module")
在开发环境来为 chunk 命名,生产环境还是有必要的
webpack 内部有 chunk 命名规则,不再是以 id(0, 1, 2)命名了
tree shaking
1.webpack5 能处理嵌套模块的 tree shaking
// inner.js
export const a = 1;
export const b = 2;
// module.js
import * as inner from './inner';
export { inner };
// user.js
import * as module from './module';
console.log(module.inner.a);
在生产环境中, inner 模块暴露的 b
会被删除
2.webpack5 能处理多个模块之间的关系
import { something } from './something';
function usingSomething() {
return something;
}
export function test() {
return usingSomething();
}
当设置了"sideEffects": false
时,一旦发现test
方法没有使用,不但删除test
,还会删除"./something"
3.webpack5 能处理对 commonjs 的 tree shaking
output
webpack4 默认只能输出 ES5 代码
webpack5 开始新增一个属性 output.ecmaVersion, 可以生成 ES5 和 ES6 / ES2015 代码
如:output.ecmaVersion: 2015
SplitChunk
webpack5 代码分割可以指定不同文件最小体积
// webpack4
minSize: 30000;
// webpack5
minSize: {
javascript: 30000,
style: 50000,
}
cache
// 配置缓存
cache: {
// 磁盘存储
type: "filesystem",
buildDependencies: {
// 当配置修改时,缓存失效
config: [__filename]
}
}
缓存将存储到 node_modules/.cache/webpack
监视输出文件
webpack4 总是在第一次构建时输出全部文件,但是监视重新构建时会只更新修改的文件。
webpack5 在第一次构建时会找到输出文件看是否有变化,从而决定要不要输出全部文件。
webpack.config.js 配置文件默认值
entry: './src/index.js',
output: {
filename: '[name].js',
path: path.resolve(__dirname, 'dist')
}