webpack
/**
* webpack的配置文件
* * 所有构建工具都是基于nodejs平台运行的,模块化默认采用commonjs
*/
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')
const WorkboxWebpackPlugin = require('workbox-webpack-plugin');
// 定义nodejs环境变量:决定使用browserslist的哪个环境
process.env.NODE_ENV = 'development';
const commonCssLoader = [
// 'style-loader',
{
loader: MiniCssExtractPlugin.loader,
options: {
publicPath: '../' // 解决css中图片路径问题
}
}, // 将js中的css提取,取代style-loader
'css-loader',
{
loader: 'postcss-loader',
options: {
ident: 'postcss',
plugins: () => [
// postcss的插件
require('postcss-preset-env')(),
],
},
},
];
module.exports = {
// 开发环境和生产环境的差别
// 开发环境:能让代码本地调试运行的环境。源码(es6,less, etc.) -> webpack -> bundle (自动化)
// 生产环境:能让代码优化上线运行的环境。css -> js(闪屏,大 -> 提取);代码压缩;样式和js兼容
mode: 'development',
// mode: 'production',
entry: './src/index.js',
output: {
// 输出文件资源目录
path: resolve(__dirname, 'dist'),
// 指定名称+目录
filename: 'bundle.[contenthash].js',
},
module: {
rules: [
// 在package.json中eslintConfig --> airbnb
{
test: /\.js/,
exclude: /node_modules/,
loader: 'eslint-loader',
// 优先执行
enforce: 'pre', // post 延后执行
options: {
// 自动修复
fix: true,
},
},
{
// 以下loader只会匹配一个
// 优化生产环境构建打包速度
// 注意:不能有两个配置处理同一个类型文件
oneOf: [
// less-loader 将less文件编译成css文件
// css-loader 将css文件变成commonjs模块加载js中,内容是样式字符串
// style-loader 创建style标签,将js中的样式资源插入,添加到head中生效
{
test: /\.css$/,
use: [...commonCssLoader],
},
{
test: /\.less$/,
use: [...commonCssLoader, 'less-loader'],
},
{
test: /\.(png|jpg|jpeg|gif)/,
loader: 'url-loader',
options: {
// 图片大小小于8kb,就会被base64处理
// 优点:减少请求数量(减轻服务器压力)
// 缺点:图片体积会更大(文件请求速度更慢)
limit: 30 * 1024,
// 问题:因为url-loader默认使用es6模块化解析,而html-loader引入图片是commonjs
// 解析时会出问题:[object Module]
// 解决:关闭url-loader的es6模块化,使用commonjs解析
esModule: false,
// 给图片进行重命名
// [hash:10] 取图片hash的前10位
// [ext] 取文件原来的扩展名
name: '[hash:10].[ext]',
outputPath: 'images',
},
},
{
test: /\.html/,
// 处理html文件中的img图片(负责引入img,从而能被url-loader进行处理)
loader: 'html-loader',
},
// {
// exclude: /\.(css|html|js|less)/,
// loader: 'file-loader'
// }
{
test: /\.(ttf|eot|woff|woff2|svg)$/i,
use: [{
loader: 'file-loader',
options: {
name: '[name].[ext]',
outputPath: 'fonts',
},
}, ],
},
{
test: /\.js/,
exclude: /node_modules/,
use: [
/**
* 开启多进程打包
* * 进程启动大概为600ms,进程通信也有开销
* * 只有工作消耗时间比较长,才需要多进程打包
*/
{
loader: 'thread-loader',
options: {
workers: 2 // 进程2个
}
},
{
loader: 'babel-loader',
options: {
// 预设:指示babel做怎样的兼容性处理
presets: [
[
'@babel/preset-env',
{
// 按需加载
useBuiltIns: 'usage',
// 指定core-js版本
corejs: {
version: 3,
},
// 指定兼容性做到哪个版本浏览器
targets: {
chrome: '60',
},
},
],
],
// 开启babel缓存
// 第二次构建时,会读取之前的缓存
cacheDirectory: true,
},
}
]
},
],
},
],
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html',
// 压缩
minify: {
// 去空格
collapseWhitespace: true,
// 删注释
removeComments: true,
},
}),
// 提取css
new MiniCssExtractPlugin({
filename: 'css/main.[contenthash].css',
}),
// 压缩css
new OptimizeCssAssetsWebpackPlugin(),
new WorkboxWebpackPlugin.GenerateSW({
// 1. 帮助serviceworker快速启动 2. 删除旧的serviceworker
// 生成一个serviceworker配置文件
clientClaim: true,
skipWaiting: true,
}),
],
// 开发服务器devServer:用来自动化(自动编译,自动打开浏览器,自动刷新浏览器)
// 特点:只会在内存中编译打包,不会有任何输出
// 启动devServer指令为:npx webpack-dev-server
devServer: {
// 项目构建后路径
contentBase: resolve(__dirname, 'dist'),
// 启动gzip压缩
compress: true,
// 端口号
port: 3000,
// 自动打开浏览器
open: true,
// HMR
hot: true,
},
optimization: {
splitChunks: {
chunks: 'all',
},
},
// 配置某些包不参与打包
externals: {
jquery: 'jQuery'
},
devtool: 'source-map',
};
entry
1.1 单入口(string)
'./src/index.js'- 打包形成一个chunk,输出一个bundle文件,此时chunk的名称默认是main
1.2 多入口(array)
['./src/index.js', './src/add.js']- 所有入口文件最终只会形成一个chunk,输出一个bundle文件
- 只有在HMR功能中让html热更新生效
1.3 多入口(object)
{index: './src/index.js', add: './src/add.js'}或{index: ['./src/index.js', './src/count.js'], add: './src/add.js'}- 有几个入口文件就形成几个chunk,输出几个bundle文件。此时chunk的名称是key值
output
output: {
// 输出文件资源目录
path: resolve(__dirname, 'dist'),
// 指定名称+目录
filename: 'js/[name].js',
//所有资源引入公共路径前缀
publicPath: '/',
// 非入口chunk的名称
chunkFilename: 'js/[name]_chunk.js',
libraray: '[name]', // 整个库向外暴露的变量名
// libraray: 'window', // 变量名添加到browser
// libraray: 'global', // 变量名添加到node
// libraray: 'commonjs/amd'
}
devServer
devServer: {
// 运行代码的目录
contentBase: resolve(__dirname, 'dist'),
// 监视contentBase目录下的所有文件,一旦文件变化就回reload
watchContentBase: true,
watchOptions: {
// 忽略文件
ignored: /node_modules/
},
// 启动gzip压缩
compress: true,
// 端口
port: 5000,
// 域名
host: 'localhost',
// 开启HMR
hot: true,
// 自动打开浏览器
open: true,
// 不要显示启动服务器日志信息
clientLogLevel: 'none',
// 除了一些基本启动信息以外,其他内容都不要显示
quiet: true,
// 如果出错,不要全屏提示
overlay: false,
// 服务器代理:解决开发环境跨越问题
proxy: {
'/api': {
// 一旦(5000)服务器接收到/api/xxx请求,就会把请求转发到另一个服务器(3000)
target: 'http://localhost:3000',
// 发送请求时,请求路径重写: 将/api/xxx --> /xxx (去掉/api)
pathRewrite: {
'^/api': ''
}
}
}
}
optimization
optimization: {
splitChunks: {
chunks: 'all',
// 以下配置为默认值,可不写
// minSize: 30 * 1024, // 分割的chunk最小为30kb
// maxSize: 0, // 最大没有限制
// minChunks: 1, // 要提取的chunk最小被引用1次
// maxAsyncRequests: 5, // 按需加载试并行加载的文件最大数量
// maxInitialRequests: 3, // 入口js文件最大并行请求数量
// automaticNameDelimiter: '~', // 名称连接符
// name: true, // 可以使用命名规则
// cacheGroups: {
// // 分割chunk的组
// // node_modules文件会被打包到vendors组的chunk中 --> vendors~xx.js
// // 满足上面的公共规则,如:大小超过30kb,至少被引用一次
// vendors: {
// test: /[\\/]node_modules[\\/]/,
// // 优先级
// priority: -10
// },
// default: {
// // 要提取的chunk最少被引用2次
// minChunks: 2,
// // 优先级
// priority: -20,
// // 如果当前要打包的模块和之前已经被提取的模块是同一个,就会复用,而不是重新打包模块
// reuseExistingChunk: true
// }
// }
},
// 将当前模块记录其他模块的hash单独打包为一个runtime文件
// 解决:修改a文件导致b文件的contenthash变化
runtimeChunk: {
name: entrypoint => `runtime-${entrypoint.name}`
},
minimizer: [
// 配置生产环境的压缩方案:js和css
new TerserWebpackPlugin({
// 开启缓存
cache: true,
// 开启多进程打包
parallel: true,
// 启动source-map
sourceMap: true
})
]
}
缓存
- babel缓存
- 让第二次打包构建速度更快
{ loader: 'babel-loader', options: { cacheDirectory: true } } - 文件资源缓存(让代码上线远行缓存更好使用)
- hash:每次webpack构建时会生成一个唯一的hash值
- 问题:因为js和css同时会使用一个hash值,如果重新打包,会导致所有缓存失效(可能只改动一个文件)
- chunkhash:根据chunk生成的hash值,如果打包来源于同一个chunk,那么hash值就一样
- 问题:js和css的hash值还是一样的,因为css是在js中被引入的,所以同属于一个chunk
- contenthash(推荐):根据文件的内容生成hash值,不同文件hash值一定不一样
- hash:每次webpack构建时会生成一个唯一的hash值
HMR
hot module replacement 热模块替换/模块热替换
作用:一个模块发生变化:只会重新打包这一个模块,而不是打包所有模块 -> 极大提升构建速度
- 样式文件:支持HMR,因为style-loader内部实现了
- js文件:不支持HMR,需要修改js代码,添加支持HMR功能的代码
if(module.hot){ module.hot.accept('./print.js', function(){ print() }) }- 注意:HMR功能对js的处理,只能处理非入口js文件的其他文件
- html文件:不支持HMR (不用做HMR功能)
- 解决:修改entry入口,将html文件引入,会导致html文件不能热更新
PWA
渐进式网络开发应用程序(离线可访问)
- workbox --> workbox-webpack-plugin
plugins: [
new WorkboxWebpackPlugin.GenerateSW({
// 1. 帮助serviceworker快速启动 2. 删除旧的serviceworker
// 生成一个serviceworker配置文件
clientClaim: true,
skipWaiting: true,
}),
]
- 在入口文件中配置,必须在服务器运行
// 注册serviceworker(处理兼容性问题)
if('serviceWorker' in navigator){
window.addEventListener('load', ()=>{
navigator.serviceworker.register('/service-worker.js')
.then(()=>{})
.catch(()=>{})
})
}
code split
optimization: {
splitChunks: {
chunks: 'all',
},
}
- 可以将node_modules中代码单独打包一个chunk最终输出
- 自动分析多入口chunk中有没有公共的文件,如果有会打包成单独一个chunk
- 通过js代码,让某个文件被单独打包成一个chunk
// import动态导入语法:能将某个文件单独打包 import(/* webpackChunkName: 'test'*/"./test") .then(({fun1, fun2})=>{}) .catch(res=>{})
tree shaking
去除无用代码
- 前提:1. 必须使用es6模块化 2. 开启production环境
- 作用:减少代码体积
- 在package.json中配置
"sideEffects": false所有代码都没有副作用(都可以进行tree shaking)- 问题:可能会把css / @babel/polyfill 等文件干掉
- 解决:
"sideEffects": ["*.css", "*.less"]
source-map
一种提供源代码构建后代码映射技术
如果构建后代码出错了,通过映射可以追踪代码错误
devtool: 'source-map'
-[inline-|hidden-|eval-][nosource-][cheap-[module-]]source-map
区别:外部生成了map文件,内联没有;内联构建速度更快
source-map: 外部
- 错误代码准备信息 & 源代码的错误位置
inline-source-map:内联
- 只生成一个内联source-map
- 错误代码准备信息 & 源代码的错误位置
hidden-source-map:外部
- 错误代码错误原因 & 没有错误位置
- 不能追踪源代码错误,只能提示到构建后代码的错误位置
eval-source-map:内联
- 每一个文件都生成对应的source-map,都在eval中
- 错误代码准确信息 & 源代码的错误位置
nosource-source-map:外部
- 错误代码准确信息 & 但是没有任何源代码信息
cheap-source-map:外部
- 错误代码错误原因 & 源代码的错误位置
- 只能精确到行
cheap-module-source-map:外部
- 错误代码准确信息 & 源代码的错误位置
- module会将loader的source-map加入
开发环境:速度快,调试更友好
- 速度快 eval > inline > cheap > ...
eval-cheap-source-map/eval-source-map
- 调试更友好
- source-map | cheap-module-source-map | cheap-source-map
eval-source-map/eval-cheap-module-source-map
- 速度快 eval > inline > cheap > ...
生产环境:源代码要不要隐藏?调试要不要更友好
- 内联会让代码体积变大,所以在生产环境不用内联
nosource-source-map全部隐藏hidden-source-map只隐藏源代码,会提示错误位置
source-map/cheap-module-source-map
- 内联会让代码体积变大,所以在生产环境不用内联
懒加载/预加载
- 懒加载:当文件需要使用时才加载(使用import动态导入)
- 预加载prefetch:会在使用之前,提前加载js文件
- 等其他资源加载完毕后再加载
- 存在兼容性问题,慎用
// import动态导入语法:能将某个文件单独打包 import(/* webpackChunkName: 'test' , webpackPrefetch: true*/"./test") .then(({fun1, fun2})=>{}) .catch(res=>{}) - 正常加载:可以认为是并行加载(同一时间加载多个文件)
js兼容性处理
babel-loader @babel/core
eslint处理
- 正常来讲,一个文件只能被一个loader处理
- 当一个文件要被多个loader处理,那么一定要指定loader执行的先后顺序:
- 先执行eslint 再执行babel
// webpack.config.js { test: /\.js/, exclude: /node_modules/, loader: 'eslint-loader', // 优先执行 enforce: 'pre', options: { // 自动修复 fix: true, }, }, // package.json "eslintConfig": { "extends": "airbnb-base", "env": { "browser": true // 支持浏览器的全局变量 } }
基本js兼容性处理:
@babel/preset-env{ loader: 'babel-loader', options: { // 预设:指示babel做怎样的兼容性处理 presets: [ [ '@babel/preset-env' ], ] } }- 问题:只能转换基本语法,如promise高级语法不能转换
全部js兼容性处理:
@babel/polyfill通过import引入- 问题:只能解决部分兼容性问题,但所有兼容性代码会全部引入,体积太大了
按需加载:
core-js
{
loader: 'babel-loader',
options: {
// 预设:指示babel做怎样的兼容性处理
presets: [
[
'@babel/preset-env',
{
// 按需加载
useBuiltIns: 'usage',
// 指定core-js版本
corejs: {
version: 3,
},
// 指定兼容性做到哪个版本浏览器
targets: {
chrome: '60',
},
},
],
]
}
}
css兼容性处理
postcss -> postcss-loader postcss-preset-env
// 帮postcss找到package.json中browserslist里面的配置,通过配置加载指定的css兼容性样式
{
loader: 'postcss-loader',
options: {
ident: 'postcss',
plugins: () => [
// postcss的插件
require('postcss-preset-env')(),
],
},
},
- browserslist默认选中生产环境,可通过node环境变量调整:
process.env.NODE_ENV = "development"
// package.json
"browserslist": {
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
],
"production": [
">0.2%",
"not dead",
"not op_mini all"
]
},
开发环境优化
优化打包构建速度
优化代码调试
生产环境优化
优化打包构建速度
- oneOf
- babel缓存
- 多进程打包
- externals
- dll
优化代码运行性能
webpack5
此版本重点关注以下内容:
- 通过持久缓存提高构建性能
- 使用更好的算法和默认值来改善长期缓存
- 通过更好的树摇和代码生成来改善捆绑包大小
- 清除处于怪异状态的内部结构,同时在V4中实现功能而不引入任何重大更改
- 通过引入重大更改来为将来的功能做准备,以便使我们能够尽可能长时间地使用V5
下载
npm i webpack@next webpack-cli -D
自动删除Node.js Polyfills
早期,webpack的目标是允许在浏览器中运行大多数node.js模块,但是模块格局发生了变化,许多模块用途现在主要是为了前端目的而编写的。webpack <= 4 附带了许多node.js核心模块的polyfill,一旦模块使用任何核心模块(即crypro模块),这些模块就会自动应用。
尽管这使使用为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
- 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会被删除
- webpack5能够处理多个模块之前的关系
import { something } from './something';
function usingSomething() {
return something;
}
export function test() {
return usingSomething();
}
当设置'sideEffects': false时,一旦发现test方法没有使用,不但删除test,还会删除'./something'
- webpack5能处理对Commonjs的tree shaking
Output
webpack 4 默认只能输出ES5代码
webpack 5 开始新增一个属性output.ecmaVersion,可以生成ES5和ES6/ES2015代码
如:output.ecmaVersion: 2015
SplitChunk
// webpack 4
miniSize: 30000;
// webpack 5
miniSize: {
javascript: 30000,
style: 50000
}
Caching
// 配置缓存
cache: {
// 磁盘存储
type: "filesystem",
buildDependencies: {
// 当配置修改时,缓存失效
config: [__filename]
}
}
缓存将存储到node_modules/.cache/webpack
监视输出文件
之前webpack总是在第一次构建时输出全部文件,但是监视重新构建时会只更新修改的文件。此次更新在第一次构建时会找到输出文件看是否有变化,从而决定要不要输出全部文件。
默认值
entry: "./src/index.js"output.path: path.resolve(__dirname, "dist")output.filename: "[name].js