定义:
将入口文件所引入的静态模块,进行处理解析打包,最后根据依赖关系进行输出到bundle
五个概念:
- entry: 入口起点文件,分析构建内部依赖图
- output:指示webpack打包后的资源输出到哪里去
- loader:处理解析那些非javascript的文件,让webpack可以识别,webpack只能识别执行javascript文件
- plugins: 执行范围更广的任务,更强大的功能
- mode:指示webpack使用相应的的模式:development/product
webpack.config.js
指示webpack做什么
// resolve 用来拼接绝对路径的方法
const { resolve } = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
// 输入
entry: './src/index.js',
// 输出
output: {
filename: 'js/bundle.js',
// __dirname nodejs的变量,代表当前文件的目录绝对路径
path: resolve(__dirname, 'build'),
},
// loader
module: {
rules: [
// 详细配置
{
// 匹配规则
test: /\.css$/,
// 使用哪些loader处理
use: [
// 创建style标签,将js中的样式资源插入到head中
'style-loader',
// 将css文件变成common.js模块加载到js中,里面内容都是样式字符串
'css-loader'
]
},
{
// 匹配规则
test: /\.less$/,
// 使用哪些loader处理
use: [
// 创建style标签,将js中的样式资源插入到head中
'style-loader',
// 将css文件变成common.js模块加载到js中,里面内容都是样式字符串
'css-loader',
// 将less编译成css
'less-loader'
]
},
{
// url-loader 处理不了html中的图片,只能处理样式中的资源
// 匹配规则
test: /\.(jpg|png|gif|jpeg)$/,
// 一个loader不需要使用use,直接loader就可以
loader: 'url-loader',
options: {
// 图片大小小于8kb,就会被base64编码
limit: 8 * 1024,
// 给图片重命名
// [hash:10]取图片的hash前十位
// [ext]取文件原来的扩展名
name: '[hash:10].[ext]',
// html-loader加载的图片是用commonjs模块解析,url-loader使用es6模块解析
// 所以对html-loader加载的图片解析会出问题[Object Module]
// 因此,关闭url-loader的es6模块解析,使用commonjs解析
esModule: false,
// 输出路径
outputPath: 'imgs'
}
},
{
// 专门处理html文件的img图片(负责引入img, 从而被url-loader处理)
test: /\.html$/,
loader: 'html-loader'
},
{
// 处理其他资源
exclude: /\.(css|less|js|html)|jpg|png|gif)$/,
// url-loader是在file-loader上做了一些优化升级(压缩)
loader: 'file-loader',
options: {
name: '[hash:10].[ext]',
outputPath: 'media'
}
}
]
},
plugins: [
// 详细plugins
// 默认创建一个空的html,引入打包输出的所有资源
new HtmlWebpackPlugin({
// 复制 './src/index.html'
template: './src/index.html',
minify: {
// 移除空格
collapseWhitespace: true
// 移除注释
removeComments: true
}
})
],
mode: 'development', // production
// 开发服务器 devServer: 自动化编译,刷新浏览器
// 特点:只在内存中编译打包,不会有任何输出
// 启动devServer指令为:webpack-dev-server
devServer: {
// 构建路径
contentBase: resolve(__dirname, 'build'),
// 启动gzip压缩
compress: true,
// 本地端口
port: 3000,
// 浏览器自动打开
open: true
}
}
CSS详细配置
// 提取css
// 压缩css
// css 兼容性处理:postcss -> postcss-loader postcss-preset-env
// postcss找到package.json中browserlist里面的配置,通过配置加载指定的css兼容性样式
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin')
// 设置nodejs环境变量
process.env.NODE_ENV = 'development'
module.exports = {
module: {
rules: [
{
test: /\.css/,
use: [
// 取代style-loader,提取js中的css成为单独的文件
MiniCssExtractPlugin.loader,
'css-loader',
// postcss-loader, 上面为简写,下面的为详细配置
// 'postcss-loader'
// postcss的browerlist默认会找生产环境的配置,所以如果要使用开发环境,那就需要手动配置process.env.NODE_ENV = 'development'
{
loader: 'postcss-loader',
options: {
ident: 'postcss',
plugins: () => {
require('postcss-preset-env')()
}
}
}
]
}
]
},
plugins: [
new MiniCssExtractPlugin({
filename: 'css/bundle.css'
}),
new OptimizeCssAssetsWebpackPlugin()
]
}
// package.json
// 可以从github搜索browerlist搜索详细配置
{
"borwerlist": {
"development": [
"last 1 chorme version",
"last 1 firefox version",
"last 1 safari version",
],
"production": [
">0.2%",
"not dead",
"not op_mini all"
]
}
}
JS详细配置
// 代码格式规则检测
// 代码兼容性处理 es6 babel
module.exports = {
module: {
rules: [
// 语法检查 eslint-loader eslint
// 只检查源代码,第三方库不检查
// 设置检查规则,在package.json中的eslintconfig
// aribnb -> eslint-config-aribnb-base eslint eslint-plugin-import
{
test: /\.js$/,
exclude: /node_modules/,
enforce: 'prev', // 优先执行
loader: 'eslint-loader',
options: {
// 自动修复eslint的错误
fix: true
}
},
// 兼容性处理 babel-loader @babel-core
// 1. 基本兼容性处理:@babel/preset-env, 这个必须配置,不管是按需还是全部
// 2. 全部的js兼容处理: @babel/polyfill 直接在接口require插件即可 但会导致代码体积变大
// 3. 按需加载:core-js
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
options: {
// 指示babel进行怎么样的兼容性处理
presets: ['@babel/preset-env'], // 基本的转换
// 按需加载
presets: [
[
'@babel/preset-env',
{
// 按需加载
useBuiltIns: 'usage',
// core-js版本
corejs: {
version: 3
},
// 支持的浏览器版本
target: {
chorme: '60',
firefox: '60',
ie: '9',
safari: '10',
edge: '17'
}
}
]
] // 基本的转换
}
}
]
}
}
// package.json
{
"eslintConfig": {
"extends": "aribnb-base"
}
}
性能优化
- 开发环境:
- 优化打包构建速度:HMR
- 优化代码调试: sourceMap
- oneOf
- 缓存
- 生产环境:
- 优化打包速度
- 优化代码性能
HMR 热加载
改变一个模块,只重新打包这一个模块
module.exports = {
devServer: {
hot: true
}
}
需要注意的是:
- 使用style-loader,而且style-loader支持hmr,不能使用其他压缩抽离的loader
- js使用hmr,监听js时候变化,只能处理非入口js,
- html使用hmr,没有必要,因为只要html变化了,肯定所有的多要重新加载,
sourceMap
提供源代码到构建后的代码之间映射的技术,可以通过映射追踪到出错的源代码
[inline-|-hidden-|eval-][nosources-][cheap-[module-]]source-map
source-map
外部,错误代码准确信息和错误位置inline-source-map
内联,速度更快,只生成一个sourcemap, 错误代码准确信息和错误位置hidden-source-map
外部,有错误信息,但没有错误位置,之隐藏源代码eval-source-map
内联,错误代码准确信息和错误位置nosources-source-map外部,有错误信息,但没有错误位置,全部隐藏cheap-srouce-map
外部,错误代码准确信息和错误位置,但是只能精确到行cheap-module-source-map外部,错误代码准确信息和错误位置
最快
eval-cheap-source-map,
eval-source-map
最友好
source-map
cheap-module-source-map
cheap-source-map
module.exports = {
devtool: 'source-map'
}
oneOf
提升构建速度,让rules里面的loader只会匹配一次
缓存
- babel缓存 第二次打包构建速度更快
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
options: {
// 开启babel缓存
// 第二次构建时,会读取之前的缓存
cacheDirectory: true
}
}
- 文件资源缓存
- hash
每次webpack构建时会生成一个唯一的hash值
问题:因为js和css同时使用一个hash值,如果重新打包,会导致所有缓存失效(可能我只是改动了一个文件) - chunkhash
根据chunk生成的hash值,如果打包来源同一个chunk,那么chunk值就一样
问题:js和css的hash值还是一样的,因为css是在js被引入的,所以同属于一个chunk - contenthash 根据文件的内容生成的hash值,不同文件hash值一定不一样
- hash
每次webpack构建时会生成一个唯一的hash值
tree shaking
去除在应用程序中没有使用的代码,让代码体积更小
- 必须使用es6模块化
- 开启production
下面可对tree shaking进行配置,不是必要的,但是最好配置
// package.json
{
"slideEffects": false // 所有代码都没有副作用(都能进行tree shaking),但可能会把css / @babel/polyfill文件干掉
"slideEffects": ["*.css"] // 保证这些文件不会被tree shaking
}
代码分割
- 根据入口文件进行分割
module.exports = { entry: [ a: './src/a.js', b: '.src/b.js' ] } - optimization
可以将node_modules中的代码单独打包一个chunk
module.exports = { optimization: { splitChunks: { chunks: 'all' } } } - 通过import动态导入,针对某个文件,单独打包
import('./test').then(() => { console.log('success') }).catch(() => { console.log('fail') }) // 下方可以固定chunk名字 import(/* webpackChunkName: 'test' */'./test').then(() => { console.log('success') }).catch(() => { console.log('fail') })
懒加载、预加载
// 懒加载
import(/* webpackChunkName: 'test' */'./test').then(() => {
console.log('success')
}).catch(() => {
console.log('fail')
})
// 预加载
import(/* webpackChunkName: 'test', webpackPrefetch: true */'./test').then(() => {
console.log('success')
}).catch(() => {
console.log('fail')
})
PWA
渐进式网络开发应用程序(离线可访问) workbox -> workbox-webpack-plugin
module.exports = {
plugins: [
new WorkboxWebpackPlugin.GenerateSW({
// 帮助serviceworker快速启动
// 删除旧的serviceworker
// 生成一个serviceworker配置文件
clientsClaim: true,
skipWaiting: true
})
]
}
// 入口文件 index.js
// eslint不认识window, navigator全局变量,所以需要修改eslintConfig配置
// serviceworker必须运行在服务器上
if('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWor ker.register('./service-worker.js').then(() => {
console.log('success')
}).catch(() => {
console.log('fail')
})
})
}
// package.json
{
"eslintConfig": {
"extends": "airbnb-base",
"env": {
"brower": true // 支付浏览器全局变量
}
}
}
多进程打包
npm i thread-loader
开启多进程打包大概需要600ms, 进程通信也有开销,只有工作消耗时间较长,才需要多进程打包。一般给babel-loader用,
{
use: [
// 'thread-laoder',
{
loader: 'thread-loader',
options: {
workers: 2 // 进程为2
}
},
{
loader: 'babel-loader',
options: []
}
]
}
externals
忽略指定的包被打包
module.exports = {
extrernals: {
jquery: 'jQuery' // jquery将不会被打包,手动引入cdn
}
}
dll
用dll可以对node_modules分别单独打包
// 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: [
// 打包生成一个mainfest.json 提供和jquery映射
new webpack.DllPlugin({
name: '[name]_[hash]', // 映射库的暴露的内容名称
path: resolve(__dirname, 'dll/manifest.json') // 输出文件路径
})
]
}
npm install add-asset-html-webpack-plugin -D
// webpack.config.js
const webpack = require('webpack')
const AddAssetHtmlWepbackPlugin = require('add-asset-html-webpack-plugin')
module.exports = {
plugins: [
new webpack.DllReferencePlugin({
mainfest: resolve(__dirname, 'dll/mainfest.json')
}),
new AddAssetHtmlWepbackPlugin({
filepath: require(__dirname, 'dll/jquery.js')
})
]
}
webpack性能优化
- 开发环境性能优化
- 生产环境性能优化
开发环境性能优化
- 优化打包速度
- HMR
- 优化代码调试
- source-map
生产环境性能优化
- 优化打包构建速度
- oneOf
- babel缓存
- 多进程
- externals
- dll
- 优化代码运行的性能
- 缓存(hash-chunkhash-contenthash)
- tree shaking
- code split
- 懒加载/预加载
- pwa
整理
// 打包成一个chunk, 输出一个bundle文件
entry: './src/index.js',
// 所有的入口文件,只会形成一个chunk, 输出只有一个bundle
entry: [
'.src/index.js',
'.src/add.js'
],
// 有几个入口文件,就有几个chunk,输出几个boundle
entry: {
index: ['.src/index.js', '.src/add.js'],
index2: '.src/index2.js'
}
outpath: {
filename: 'js/bundle.js',
// __dirname nodejs的变量,代表当前文件的目录绝对路径
path: resolve(__dirname, 'build'),
// 所有资源引入公共路径前缀 --> imgs/a.jpg --> '/imgs/a.jpg'
publicPath: '/',
// 非入口chunk的名称
chunkFilename: '[name]_chunk.js',
// 整个库向外暴露的变量名
library: '[name]',
// 变量名添加到哪个环境上 browser, node
libraryTarget: 'window',
libraryTarget: 'global',
libraryTarget: 'commonjs'
},
module: {
rules: [
// loader的配置
{
test: /\.css$/,
// 多个loader用use
user: [
'style-loader',
'css-loader'
]
},
{
test: /\.js$/,
// 排除 node_modules 下的js文件
exclude: /node_modules/,
// 只检查 src 下的js 文件
include: resolve(__dirname, 'src'),
// 优先执行
enforce: 'pre',
// 延后执行
// enforce: 'post',
// 单个loader直接用loader
loader: 'eslint-loader',
// 单个loader使用,需要添加options来配置选项
options: {}
},
{
// 以下配置只会生效一个
oneOf: []
}
]
},
// 解析模块的规则
resolve: {
// 配置解析模块的路径别名: 简写路径,缺点路径没有提示
alias: {
$css: resolve(__dirname, 'src/css')
},
// 配合省略文件路径的后缀名
extensions: ['.js', '.json', '.jsx', '.css'],
// 告诉 webpack 解析模块是去找哪个目录
modules: [resolve(__dirname, '../../node_modules'), 'node_modules']
},
devServer: {
// 运行代码的目录
contentBase: resolve(__dirname, 'build')
// 监视 contentBase 目录下的所有文件,一旦文件变化就会 reload
watchContentBase: true,
wiatchOptions: {
// 忽略文件
ignored: /node_modules/
},
// 启动gzip压缩
compress: true,
// 端口号
port: 5000,
// 域名
host: 'localhost',
// 自动打开浏览器
open: true,
// 开启HMR功能
hot: true,
// 不要显示启动服务器的日志信息
clientLogLevel: 'none',
// 除了一些基本的启动信息以外,其他内容都不要显示
quiet: true,
// 如果出错了,不要全屏提示
overlay: false,
// 服务器代理 --> 解决开发环境跨域问题
proxy: {
// 一旦 devServer(5000) 服务器接收到 /api/xxx 的请求,就会把请求转发到另一个服务器(3000)
'/api': {
target: 'http://localhost:3000',
// 发送请求是,请求路径重写:将 /api/xxx --> /xxx (去掉 /api)
pathRewrite: {
'^/api': ''
}
}
}
},
optimization: {
splitChunks: {
chunks: 'all',
// 分割的chunk最小为30kb
minSize: 30 * 1024,
// 最大没有限制
maxSize: 0,
// 要提取的chunk最少被引用1次
minChunks: 1,
// 按需加载时并行加载的文件的最大数量
maxAsyncRequest: 5,
// 入口js文件最大并行请求数量
maxInitialRequests: 3,
// 名称连接符
automaticNameDelimiter: '~',
// 可以使用命名规则
name: true,
// 分割chunk的组
chcheGroups: {
// node_modules 文件会被打包到 vendors 组的chunk中, --> vendors~xxx.js
// 满足上面的公共规则
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: {
// 需要引入 terser-webpack-plugin 包
// 配置生产环境的压缩方案:js和css
new TerserWebpackPlugin({
// 开启缓存
cache: true,
// 开启多进程打包
parallel: true,
// 启动source-map
sourceMap: true
})
}
}
总结:
- 只能处理js/json
- dev比pro少了一个压缩
- 能对es6模块化编译成能识别的模块化
- 开发环境不需要配置太多东西,不然开发环境会很慢,将一些必要的但是开发环境不必要的,放在生产环境中