webpack是什么
webpack是一个现代JavaScript应用程序的静态模块打包工具,他能解决各个模块之间的依赖,构建依赖关系图,把各个模块按照特定的规则和顺序组织起来,最终合并成一个或者多个js文件,这一过程就是模块打包.
webpack基本结构
- entry: 入口配置
- output: 出口配置
- module: 模块loader配置
- plugins: 插件
- mode: 模式
- resolve: 解析配置
- devtool: source-map配置
- watch: 监听开关
- watchOptions: 监听配置
- devServer: webpack-dev-server配置
webpack打包流程
webpack打包可分为三个阶段:初始化参数,编译阶段,输出阶段
初始化阶段
- 初始化参数:读取配置文件,shell,npm script等参数,合并参数
- 开始编译: 使用得到的参数初始化Compiler对象,加载所有配置的插件,执行run方法进行编译
- 确定入口: 根据配置文件找出所有入口 编译阶段
- 编译模块:从入口文件出发,串行调用所有配置的loader对模块进行翻译,再找出该模块依赖的模块,递归进行编译,知道所有模块编译完成
- 编译完成: 编译完成,得到每个模块最终的内容和依赖关系 输出阶段
- 输出资源列表: 根据入口和模块之间的依赖关系,组装成一个个包含多个模块的Chunk,再把Chunk转换成一个单独的文件加入输出列表
- 输出完成: 根据配置的路径和文件名,把文件内容写入文件系统
webpack-loader
- loader的职责是单一的,只需要完成一种转换, 一个源文件需要多次转换,则需要多个loader串行处理,loader的调用是链式的, 从右往左的,第一个loader拿到源文件转换后,会将转换的结果传递给下一个loader,最后一个loader转换完成会将结果输出给webpack,loader只需要关注输入输出结果即可,保持单一原则
webpack-plugin
- webpack 就像一条生产线,要经过一些列处理流程后才能将源文件转换成输出结果,这条生产线上的每个流程职责都是单一的,多流程之间存在依赖关系,只有完成当前流程后,才能交给下一个流程去处理,而插件就像是插入到生产线中的一个功能,在特定ed时机对生产线的资源做出处理.
- webpack这种事件流机制应用了观察者模式,依赖于tapable,webpack运行生命周期中会广播出很多事件,plugin可以监听这些事件也只需要关心这些事件,在合适的时机调用来改变输出结果.
- webpack-plugin必须提供apply方法,webpack内部会遍历所有插件,调用apply将compiler实例传给plugin
- webpack通过 compiler和compilation 与plugin连接 compiler包含了webpack运行时的所有配置信息,compilation包含了当前模块资源,编译后的资源文件等.
webpack优化
构建速度优化
指标: speed-measure-webpack-plugin
- exclude,include, noParse, IgnorePulgin缩小构建范围
- 多进程打包 happypack/thread-loader
- 缓存,cache-loader/cacheDirectory
- Dll动态连接库
注意:
DLL缓存是大大缩短了首次构建时间,像之前的cache-loader优化都是缩短rebuild时间
构建体积优化
指标:webpack-bundle-analyzer
- optimization.splitChunks分包,抽离公共模块
- css压缩
- 去除死代码 terser-webpack-plugin
- babel配置, 配置@babel/plugin-transform-runtime防止所需的helper函数重复注入
- tree-shaking terser-webpack-plugin示例
const TerserPlugin = require('terser-webpack-plugin');
const config = {
// 生产模式下tree-shaking才生效
mode: 'production',
optimization: {
// Webpack 将识别出它认为没有被使用的代码,并在最初的打包步骤中给它做标记。
usedExports: true,
minimizer: [
// 删除死代码的压缩器
new TerserPlugin({...})
]
}
};
什么时候才会触发tree-shaking?
tree-shaking条件:import/export导入导出的静态模块(ES6模块)+无副作用 thread-loader示例:
把 thread-loader 放置在其它 loader 之前,那么它之后的 loader 就会在一个单独的 worker 池中运行。
rules: [
{
test: /\.jsx?$/,
use: ['thread-loader', 'cache-loader', 'babel-loader']
}
]
cacheDirectory示例
rules: [
{
test: /\.(j|t)sx?$/,
use: [
{
loader: 'babel-loader',
options: {
cacheDirectory: true,
},
}
}
]
cache-loader示例:
rules: [
{
test: /\.(css)$/,
use: [
{ loader: 'style-loader' },
{ loader: 'cache-loader' },
{ loader: 'css-loader' },
{ loader: 'postcss-loader' }
]
}
]
HMR热更新配置及其原理热更新原理:
- 启动webpack-dev-server后,浏览器和客户端使用websocket实现长连接;
- webpack监听源文件的变化,当监听到文件内容变化后,webpack重新编译,每次编译都会生成hash值,已改动模块的json文件,已改动模块代码的js文件;
- 编译完成后通过socket向客户端推送当前编译的hash戳
- 客户端的websocket监听到有文件改动推送过来的hash戳,会和上一次对比,一致则走缓存,不一致则通过ajax和jsonp向服务端获取最新资源
- 使用内存文件系统,去替换有修改的内容实现局部刷新
wbepack 常用loader及其配置
babel-loader babel/core babel/present-env
@babel/runtime + @babel/plugin-transform-runtime 配套使用。 沙箱,提取所有页面所需的helper函数到一个包里,避免重复注入
babel配置:
babel-loader, babel/core, babel/preset-env
{
test: /\.m?js$/,
exclude: /(node_modules|bower_components)/,
include: path.resolve(__dirname,'src'),
use: {
loader: "babel-loader",
options: {
// 也可以配置.babelrc文件
// babel提供的插件预设, 允许使用babel插件
presets: ["@babel/preset-env"],
plugins: [
["@babel/plugin-proposal-decorators", { legacy: true }], // 支持装饰器写法
["@babel/plugin-proposal-class-properties"], // 支持class写法
],
},
},
},
.babelr常用配置
{
// "plugins": ["@babel/plugin-transform-arrow-functions", "@babel/plugin-transform-destructuring"] // 一个个插件不如使用preset-env就可以一次性全部引入
// "presets": ["@babel/preset-env"]
"presets": [
"@babel/preset-env",
{
"modules": false,
"useBuiltIns": "entry", // 按需引入:在入口处把所有ie8以上浏览器不支持api的polyfill引入进来
// 'usage',其功能更为强大,它会扫描你的代码,只有你的代码用到了哪个新的api, 它才会引入相应的polyfill。【试验状态,谨慎使用】
"targets": "ie >= 8" // 只有ie8以上版本浏览器不支持的语法才会被转换
}
'@babel/preset-react',
'@babel/preset-typescript',
],
"plugins": [
"@babel/plugin-syntax-dynamic-import", //动态导入
["@babel/plugin-transform-runtime", {
"corejs": 2
}] // 所有的helper函数抽离到一个包中,由所有的文件共同引用则可以减少可观的代码量。也可以为你的代码创建一个sandboxed environment(沙箱环境),这在你编写一些类库等公共代码的时候尤其重要。
]
}
样式处理(style-loader,css-loader, less-loader,postcss-loader)
- postcss-loader + autoprefixer插件,可以为我们的样式添加前缀-webkit-,-ms- 提高兼容性
// 样式处理
style-loader, css-loader, less-loader,postcss-loader
// 自动添加css前缀 配置
// 使用 autoprefixer 插件
{
test: /\.css$/i,
use: [
MiniCssExtractPlugin.loader,
{
loader: "css-loader",
},
{
loader: "postcss-loader",
options: {
postcssOptions: {
plugins: [
[
"autoprefixer", // postcss-loader + autoprefixer + package.json (browserslist), 来设置css3前缀
],
],
},
},
},
],
},
// package.json
"browserslist": [
"last 2 versions",
"> 1%",
"iOS 7",
"last 3 iOS versions"
]
文件处理(url-loader, file-loader)
TS转换js (awesome-typescript-loader)
react解析
- 结合babel,config中配置预设presen支持;
- 结合ts,config里配置"compilerOptions": { "jsx": "react" // 开启 jsx ,支持 React }", 入口文件需要更改为tsx,需要安装types/react @types/react-dom tsConfig示例
{
"presets": ["es2015", "stage-2", "react"],
"plugins": [
"react-hot-loader/babel",
"transform-function-bind",
"transform-class-properties",
"transform-export-extensions",
],
"env": {
"backend": {
"plugins": [
[ "webpack-loaders",
{ "config": "./webpack.config.babel.js"
, "verbose": true
}
]
]
}
}
}
}
module.exports = {
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
user: {
loader: 'babel-loader?cacheDirectory=true', // cacheDirectory用于缓存babel的编译结果,加快重新编译的速度
options: {
presets: ['@babel/preset-env'],
plugins: [
'@babel/plughin-transform-runtime',
'@babel/plugin-transform-modules-commonjs'
]
}
}
}
]
}
}
webpack常用plugin
打包html文件 html-webpack-plugin
引入html-webpack-plugin插件
const HtmlWebpackPlugin = require("html-webpack-plugin"); // 打包html文件
plugin中配置
plugins: [
new HtmlWebpackPlugin({
template: "./src/index.html",
filename: "index.html",
minify: true, // 最小化
hash: true,
}),
]
抽离压缩css: MiniCssExtractPlugin, CssMinimizerPlugin
const MiniCssExtractPlugin = require("mini-css-extract-plugin"); // 抽离css
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin"); // 压缩css
plugins: [
new MiniCssExtractPlugin({
filename: 'css/main.css' // css分类打包
}), // css单独打包
]
optimization: {
// 优化项
minimizer: [
// 压缩工具优化
new CssMinimizerPlugin(),
new UglifyJsPlugin(), // 使用了minimizer, 貌似必须要配置uglifyJsPlugin
],
}
每次打包前清除dist文件,防止缓存
const { CleanWebpackPlugin } = require("clean-webpack-plugin"); // 每次build 清楚dist文件
new CleanWebpackPlugin(), // 每次打包前清除dist文件
ESlint配置
// 配置eslint插件
const ESLintPlugin = require('eslint-webpack-plugin'); // eslint
new ESLintPlugin(), // eslint
// 配置.eslintrl.json文件
{
"parser": "babel-eslint", // 使用babel-eslint来解析 可以防止使用babel解析高级语法(es2015等) 后的eslint的报错
...
}
webpack-merge: 提供公共配置,分离生产和开发环境配置文件
使用 webpack-merge来合并webpack.common.js 配置到对应环境配置文件
// wabpack.dev.js
const { merge } = require('webpack-merge')
const common = require('./webpack.common')
const devWebpackCfg = {
mode: 'development',
devServer: {
port: 3000,
progress: true,
contentBase: "./dist",
// open: true // 自动打开浏览器
proxy: {
'/api': { // dev-server解决开发跨域, 以'/api开头的将代理到dev-server,然后在代理到服务器'
target: 'http://localhost:3000',
pathRewrite: {'^/api' : ''},// 并不是每个接口都是/api开头的,需要将api替换''
}
}
},
}
module.exports = merge([common, devWebpackCfg])
definePlugin: 写入环境变量
new webpack.DefinePlugin({
PRODUCTION: JSON.stringify(true),
VERSION: JSON.stringify('5fa3b9'),
BROWSER_SUPPORTS_HTML5: true,
TWO: '1+1',
'typeof window': JSON.stringify('object'),
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV)
});
optimize-css-assets-webpack-plugin:合并相同的css样式文件
ignorePlugin忽略文件,防止被打包进去
plugins: [
// moment自带edlocale包很大, 很多语言包是用不到的,但是依然会打包进去,使用webpack ignorePlugin 忽略语言包
new webpack.IgnorePlugin({
resourceRegExp: /^\.\/locale$/, // 匹配.locale
contextRegExp: /moment$/, // 匹配目录,在moment中匹配 .locale
}),
]
动态连接库 DllPlugin & DllReferencePlugin
// 1.webpack.dll.conf.js
module.exports = {
entry: {
vendor: ['react','react-dom']
},
output: {
path: path.resolve(__dirname,'dist', 'dll'),
filename: '[name]_dll.js',
library: '[name]_library' //定义动态链接库库名
},
plugins: [
new webpack.DllPlugin({
name: '[name]_library', // 和library要一致
path: path.resolve(__dirname, './dist/dll', 'manifest.json'), //资源映射文件路径
})
]
}
// 2.在webpack.conf.js中引用dll
plugins: [
// 引用动态链接库
new webpack.DllReferencePlugin({
manifest: path.resolve(__dirname, './dist/dll', 'manifest.json')
})
]
// 3.在index.html中引入
<script src="./dll/vendor.dll.js"></script>
多进程打包HappyPack
const Happypack = require('happypack');
{
test: /\.m?js$/,
exclude: /(node_modules|bower_components)/,
include: path.resolve(__dirname, 'src'),
use: ['Happypack/loader?id=js'], // 这里使用Happypack来代替babel-loader, 标注id值
},
plugins: [
new Happypack({
id: 'js', // 这里和loader中标椎的id值相同
use: [{
loader: "babel-loader", // babel-loader配置在这里
options: {
// babel提供的插件预设, 允许使用babel插件
presets: ["@babel/preset-env", "@babel/preset-react"],
plugins: [
["@babel/plugin-proposal-decorators", { legacy: true }], // 支持装饰器写法
["@babel/plugin-proposal-class-properties"], // 支持class写法
['@babel/plugin-syntax-jsx']
],
},
}]
}),
]
CopyWebpackPlugin,复制静态资源到dist
resolve优化
- extensions: 配置扩展后缀,使用require和import时可以省略后缀
- alias: 别名,简化引用
- modules:限制第三方包查询范围
- mainFiles: 配置模块优先查找文件名
- mainFields: 配置第三方插件的package.json的main字段
其他优化
- optimization: 抽离公共代码