# webpack
前端的工程化离不开 webpack, 它是一个现代 JavaScript 应用程序的静态模块打包器,用于
将适合用于开发的代码打包构建为适合用于生产的代码
# webpack.config.js
const path = require("path"); | |
const { DefinePlugin } = require("webpack"); | |
module.exports = { | |
mode: "development", //development 优化打包速度,production 优化打包结果,none 不做处理 | |
entry: "./src/main.js", | |
devtool: "source-map", // 开启 source-map, 构建后会产生 source-map 文件,但想要浏览器使用还需要主 js 文件添加对应的魔法注释 | |
output: { | |
filename: "bundle.js", | |
path: path.join(__dirname,"output") | |
}, | |
module:{ | |
rules:[ | |
{ | |
test:/\.css/, | |
use:[ | |
{ | |
loader: "style-loader", | |
optioins:{} | |
}, | |
{ | |
loader: "css-loader", | |
optioins:{} | |
}, | |
"less-loader", | |
{ | |
loader:"postcss-loader", | |
optioins: { | |
plugins: [ | |
require("postcss-preset-env") | |
] | |
} | |
} | |
] | |
}, | |
{ | |
test:/\.(ttf|eot|woff2?)$/i, | |
type: "asset/resource", | |
generate: { | |
filename: "img/[name].[hash:6][ext]" | |
} | |
} | |
] | |
}, | |
plugin:[ | |
new CleanWebpackPlugin(), | |
new HtmlWebpackPlugin({ | |
title: "webpack测试标题", | |
Template: "./public/index.html" | |
}), | |
new DefinePlugin({ | |
BASE_URL: '"./"' | |
}), | |
new CopyWebpackPlugin({ | |
patterns: [ | |
{ | |
from: "public", | |
globalOptions:[] | |
} | |
] | |
}) | |
] | |
} |
# mode 模式
用于配置构建模式
# entry 入口
用于配置入口文件
# output 输出
用于配置出口文件
# devtool 工具
用于配置开发工具,如:source-map、eval 等,它是由:inline、nosources、cheap、等的组合
vue-cli 在开发模式下直接配置的是 source-map, 生产环境直接不生成
react 的 cra 在生产环境下根据传入的配置判断是否配置 source-map, 开发环境下配置 cheap-module-source-map
# 最佳实践
- 开发阶段:推荐使用 source-map 或者 cheap-module-source-map, 方便本地调试
- 测试阶段:推荐使用 source-map 或者 cheap-module-source-map, 方便定位错误
- 生成阶段:推荐不生成 source-map (配置为 false 或缺省不写), 因为多余的 source-map 文件加载消耗资源,并且通过 source-map 文件,别人可以还原我们的代码
# optimization 优化
optimization 字段用于配置优化项,主要有 minimizer 压缩、splitChunks 分包、mergeDuplicateChunks 合并相同模块的 chunk 等选项
# resolve 解析
设置模块如何配解析,主要包括: alias 别名、extensions 后缀、fallback 重定向模块等选项
# devServer 开发服务器
内部使用 webpack-dev-server, 开发服务器,用于快速开发应用程序,主要包括: static 静态文件、compress 压缩、port 端口、devMiddleware 资源配置项、http2 开启 http2 等选项
# cache 缓存
配置使用缓存
# targets 构建目标
告知 webpack 为目标 (target) 指定一个环境。默认值为 "browserslist",如果没有找到 browserslist 的配置,则默认为 "web"
# watch 监听
监听任何已解析文件的更改
# watchOptions
用来定制 watch 模式的选项:
# externals 外部链接
防止将某些 import 的包 (package) 打包到 bundle 中,而是在运行时 (runtime) 再去从外部获取这些扩展依赖 (external dependencies), 例如一些使用 cdn 引用的文件
# externalsType 外链类型
指定 externals 的默认类型。当 external 被设置为 amd,umd,system 以及 jsonp 时,output.libraryTarget 的值也应相同。例如,你只能在 amd 库中使用 amd 的 externals。
# performance
配置如何展示性能提示。例如,如果一个资源超过 250kb,webpack 会对此输出一个警告来通知你。
# node
这些选项可以配置是否 polyfill 或 mock 某些 Node.js 全局变量。
# module 模块
module 字段用于配置模块 (对应的 loader)
# loader
处理对应的模块 (webpack 默认只支持 JS 文件)
loader 是一个函数,接收传来的代码字符串作为参数,返回处理后的代码字符串,webpack 中的模块处理是以管道架构方式 (也成为构造者的设计模式) 来进行设计的,并且注意它的处理是逆序进行处理
# plugin 插件
plugin 字段用于配置插件
# stats 显示
stats 选项让你更精确地控制 bundle 信息该怎么显示。 如果你不希望使用 quiet 或 noInfo 这样的不显示信息,而是又不想得到全部的信息,只是想要获取某部分 bundle 的信息,使用 stats 选项是比较好的折衷方式。
# 深入源码
# eval 函数
webpack 当 mode 为
development
模式下的devtool
默认是eval
, 这将导致打包后的文件中,JS 代码是通过 eval 函数进行执行的,而不是直接执行的,原因是:通过 eval 函数执行可以添加上source-map对应的魔法注释
,这样当出现报错时就能准确定位到对应的文件位置
# 知识点
开发模式下,只要引入模块就会将模块打包进构建结果
生产模式下,会进行 tree-shaking, 如果引入了但不使用就不会被打包进构建结果
# webpack.config.js 配置示例
const HtmlWebpackPlugin = require("html-webpack-plugin"); | |
const path = require("path"); | |
// MiniCssExtractPlugin 插件用于单独生成 css 文件,而不是嵌入到 html 中 | |
const MiniCssExtractPlugin = require("mini-css-extract-plugin"); | |
// HTMLInlineCSSWebpackPlugin 插件用于在 html 中内联 css 文件 | |
const HTMLInlineCSSWebpackPlugin = require("html-inline-css-webpack-plugin"); | |
// CleanWebpackPlugin 插件用于清除 dist 目录 | |
const { CleanWebpackPlugin } = require('clean-webpack-plugin'); | |
//stats-webpack-plugin 插件用于生成 webpack 构建的详细信息 | |
const StatsPlugin = require('stats-webpack-plugin'); | |
//progress-bar-webpack-plugin 插件用于生成一个进度条,用于显示构建的进度 | |
const ProgressBarPlugin = require('progress-bar-webpack-plugin'); | |
//webpack-manifest-plugin 为构建后的文件生成一个 manifest.json 文件,用于记录文件名和文件路径的映射关系,对 SPA 应用友好 | |
const ManifestPlugin = require('webpack-manifest-plugin'); | |
//webpack-md5-hash 为文件名添加一个 MD5 哈希值,从而保证文件名的唯一性 | |
const WebpackMd5Hash = require('webpack-md5-hash'); | |
const webpack = require("webpack"); | |
const { | |
WebpackBundleSizeAnalyzerPlugin, | |
} = require('webpack-bundle-size-analyzer'); | |
module.exports = { | |
entry:"./src/index.js", | |
output:{ | |
path:path.resolve(__dirname,"dist2"), | |
filename:"[name]_[hash].js" | |
}, | |
mode:"development", | |
// 注意 pitch loader 是从前往后执行,normal loader 从后往前执行(洋葱模型,loaderIndex 指针控制) | |
module:{ | |
strictExportPresence: true, // 导出一个内容没有导出时报错而不是警告 | |
rules:[ | |
{ | |
test:/\.html/, | |
//inline-html-loader 的作用是将图片和 css 等资源内嵌到 html 中,而不是引用的方式使用 | |
use:"inline-html-loader" | |
}, | |
{ | |
test:/\.css$/, | |
use:[ | |
// { | |
// loader:"style-loader" | |
// }, | |
// 用于单独生成 css 文件 | |
MiniCssExtractPlugin.loader, | |
{ | |
loader:"css-loader" | |
}, | |
// 用于将 css 中的 px 单位转为 rem 单位 | |
{ | |
loader:"px2rem-loader", | |
options:{ | |
remUnit:75, | |
remPrecision:8 | |
} | |
}, | |
// 使用 postcss-loader 将 css 文件进行转换 | |
{ | |
//postcss-loader 用于将 css 文件进行转换,例如添加前缀等操作 | |
loader:"postcss-loader", | |
options:{ | |
plugins:() => [ | |
require('autoprefixer')({ | |
// 指定兼容的浏览器版本,可以用 browserlist 文件代替 | |
overrideBrowserslist: ['last 100 version', 'ios 7'], | |
}), | |
], | |
} | |
} | |
] | |
}, | |
{ | |
test:/\.less$/, | |
use:["style-loader","css-loader","less-loader"] | |
}, | |
{ | |
test:/\.scss$/, | |
use:["style-loader","css-loader","sass-loader"] | |
}, | |
//svg-inline-loader 的作用是将 svg 文件内联到 html 中,而不是引用的方式使用 | |
{ | |
test:/\.svg/, | |
use:[{loader:"svg-inline-loader"}] | |
}, | |
{ | |
test:/\.(png|jpg|gif|jpeg)$/, | |
use:[ | |
{ | |
loader:"file-loader", | |
options:{ | |
name:"[name].[ext]", | |
//file-loader 中的 limit 配置用于配置指定的文件大小限制,如果文件大小超出了这个限制 | |
//file-loader 会将文件转换为 base64 格式存在 Js 文件中 | |
limit:200 * 1024 | |
} | |
} | |
] | |
}, | |
{ | |
test:/\.j|tsx?$/, | |
use:[{ | |
// 使用 babel-loader 配置 jsx 语法,也可以在.babelrc 文件中配置 | |
loader:"babel-loader", | |
options:{ | |
presets:["@babel/preset-env","@babel/preset-react"] | |
} | |
}, | |
//eslint-loader 用于将 eslint 集成到 webpack 构建流程中,用于在代码中发现错误并强制执行代码规范 | |
{ | |
loader:"eslint-loader", | |
// 通常使用 eslint 配置文件进行配置 | |
options:{ | |
fix:true | |
} | |
} | |
], | |
include:path.resolve(__dirname,'src') | |
}, | |
{ | |
test:/.(woff|woff2|eot|tff|otf)$/, | |
use:[ | |
{ | |
loader:"file-loader", | |
options:{ | |
name:"[name].[ext]", | |
//file-loader 用于限制大小,超过这个大小就会将文件存在 js 之中 | |
limit: 30 * 1024 * 1024 | |
} | |
} | |
] | |
} | |
] | |
}, | |
plugins:[ | |
new CleanWebpackPlugin(), | |
new HtmlWebpackPlugin({ | |
// 配置内联资源,和 HTMLInlineCSSWebpackPlugin 一起使用 | |
inlineSource:".css$", | |
template: "src/index.html", | |
inject:true, | |
minify:{ | |
html5:true, | |
collapseWhitespace: true, | |
preserveLineBreaks: false, | |
minifyCSS: true, | |
minifyJS: true, | |
removeComments: false, | |
} | |
}), | |
new HTMLInlineCSSWebpackPlugin(), | |
// 插件也可以是一个函数,webpack 会在构建时进行执行,注册对应的事件 | |
function () { | |
this.hooks.done.tap("done",(stats) => { | |
// 如果构建过程中报错,直接退出 | |
if (stats.compilation.errors && stats.compilation.errors.length && process.argv.indexOf('--watch') == -1) { | |
console.log('webpack.config.js: build error'); | |
process.exit(1); | |
} else { | |
console.log('webpack.config.js: build done'); | |
} | |
}) | |
}, | |
// HtmlWebpackExternalsPlugin 插件用于排除项目中希望从外部引入的资源,而不是将它们打包到 bundle 中 | |
// 配置 HtmlWebpackExternalsPlugin 之后,需要删除掉项目中 import 或者 require 的资源,否则会构建两次 | |
new HtmlWebpackExternalsPlugin({ | |
externals: [ | |
{ | |
module: 'react', | |
entry: { | |
path: 'https://cdn.bootcdn.net/ajax/libs/react/16.13.1/umd/react.production.min.js', | |
attributes: { | |
crossorigin: 'crossorigin', | |
}, | |
}, | |
global: 'React', | |
}, | |
{ | |
module: 'react-dom', | |
entry: { | |
path: 'https://cdn.bootcdn.net/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js', | |
attributes: { | |
crossorigin: 'crossorigin', | |
}, | |
}, | |
global: 'ReactDOM', | |
}, | |
], | |
}), | |
new StatsPlugin("stats.json",{ | |
chunkModules:true, | |
exclude:[/node_modules/], | |
}), | |
new ManifestPlugin(),// 创建 manifest.json 文件,包含项目中所有资源和最终输出路径,对 SPA 应用非常有用 | |
new WebpackMd5Hash(),// 为生成的文件添加 md5 加密后的 hash 值 | |
new WebpackBundleSizeAnalyzerPlugin('./bundle-size-analyzer.log'),// 分析打包后文件的大小 | |
new ProgressBarPlugin(),// | |
//webpack.BannerPlugin 插件用于为输出的文件顶部添加一个横幅注释,通常包含版权声明、作者信息等 | |
new webpack.BannerPlugin({ | |
banner:`这是 BannerPlugin 注入的内容 | |
https://github.com/ShenBao | |
Copyright 2016 - Present`, | |
raw: true, // 表示 banner 字符串将被原样处理,不进行任何处理 | |
entryOnly:true, // 表示只在入口文件处添加 banner | |
}), | |
], | |
// 用于配置构建过程中的优化项 | |
optimization: { | |
minimize:true, //webpack 会自动选择合适的插件来压缩代码 | |
minimizer: [ | |
// OptimizeCSSAssetsPlugin 插件用于压缩 css 文件,需要配合 MiniCssExtractPlugin 使用 | |
// new OptimizeCSSAssetsPlugin({ | |
// assetNameRegExp: /\.css$/g, | |
// cssProcessor: require('cssnano'), | |
// }), | |
new TerserPlugin({ | |
include:/\.min\.js$/, | |
}) | |
], | |
splitChunks: { | |
chunks: 'async', | |
minSize: 30000, | |
maxSize: 0, | |
minChunks: 1, | |
maxAsyncRequests: 2, | |
maxInitialRequests: 2, | |
automaticNameDelimiter: '-', // 文件名连接符 | |
name: true, | |
cacheGroups: { | |
vendors: { | |
test: /[\\/]node_modules[\\/]/, | |
priority: -10, // 优先级 | |
filename: 'vendors.js', | |
}, | |
default: { | |
minChunks: 2, | |
priority: -20, | |
reuseExistingChunk: true, // 已经被打包过的使用之前的 | |
filename: 'common.js', | |
}, | |
}, | |
}, | |
}, | |
// 配置信息量,errors-only 表示只有当出错时才会输出 | |
stats: "errors-only", | |
devServer:{ | |
contextBase:"./dist", | |
host:true, | |
}, | |
devtool: "source-map", | |
resolveLoader:{ | |
modules:["node_modules","./MyLoaders"] // 自定义查找 loader 的文件夹 | |
}, | |
bail:true, // 如果构建过程中出现错误,立即停止构建过程 | |
} |