webpack常用配置汇总

爱吃猪头爱吃肉

webpack常见配置

npm脚本执行流程

运行npm run build

package.json文件

1
2
3
4
5
{
"script":{
"build":"webpack"
}
}
  1. 会去package.json文件中的script对象中查找该命令,没有找到报错
  2. 找到脚本对应的shell命令 执行对应命令
  3. 查找\node_modules\.bin\webpack.cmd 如果存在执行
  4. 如果不存在 则执行全局(C:\Users\skdzx\AppData\Roaming\npm\node_modules\webpack.cmd
  5. 如果找到对应脚本的执行文件,执行对应命令

webpack常用命令

1
2
3
4
5
6
7
8
9
webpack -p    //压缩混淆脚本,这个非常非常重要!
webpack -d //生成map映射文件,告知哪些模块被最终打包到哪里了
webpack -w 或 --watch //监听变动并自动打包
webpack --mode=development // 配置webpack模式 在模块内部可以使用 process.env.NODE_ENV 获取值
webpack --config XXX.js //使用另一份配置文件(比如webpack.config2.js)来打包
webpack --display-error-details 查看查找过程,方便出错时能查阅更详尽的信息
webpack --colors 输出结果带彩色,比如:会用红色显示耗时较长的步骤
webpack --profile 输出性能数据,可以看到每一步的耗时
webpack --display-modules 默认情况下node_modules下的模块会被隐藏,加上这个参数可以显示这些被隐藏的模块

webpack mode

webpack的开发模式 有 development开发模式、production生产模式、node不指定模式

优先级:命令行配置 > 配置文件配置

  1. 通过在命令行中配置mode

模块中可以通过process.env.NODE_ENV获取,webpack.config中不能获取

1
2
3
4
5
6
// package.json 
{
"script":{
"build":"webpack --mode=production"
}
}
  1. 在命令行中配置env

模块中不能获取,webpack.config中使用函数方式获取

1
2
3
4
5
6
7
8
9
10
// package.json 
{
"script":{
"build":"webpack --env=development"
}
}
// webpack.config.js
module.exports = (env) => {
console.log(env) // { WEBPACK_BUNDLE: true, WEBPACK_BUILD: true, production: true }
}
  1. 使用webpack.DefinePlugin 配置通用变量

模块中可以获取,webpack.config 中无法获取

1
2
3
4
5
6
7
8
9
10
// webpack.config.js
module.exports = {
...
plugins:[
new webpack.DefinePlugin({
"process.dev.NODE_ENV_DEV":JSON.stringify("development"),
"process.dev.NODE_ENV_PRO":JSON.stringify("production"),
})
]
}
  1. 使用cross-env配置环境变量
1
2
3
4
5
6
7
8
9
10
11
12
// package.json 
{
"script":{
"start":"cross-env NODE_ENV=development && webpack server"
}
}
// webpack.config.js
module.exports = {
mode:process.env.NODE_ENV
}
// index.js
console.log(process.env.NODE_ENV);

webpack entry

可以是字符串,数组,对象方式

entry对应的路径可以是相对路径、可以是绝对路径(output 只能是绝对路径)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//// 单入口
module.export = {
entry:"./src/index.js"
}
// 等价于
module.export = {
entry:{main:"./src/index.js"}
}
//---------------------------------------
module.export = {
entry:["./src/index.js","./src/index2.js"]
}
// 等价于
module.export = {
entry:{main:["./src/index.js","./src/index2.js"]}
}
//---------------------------------------
//// 多入口
module.export = {
entry:{
main:"./src/main.js",
index:"./src/index.js"
}
}

webpack output

1
2
3
4
5
6
7
8
9
// 出口
const path = require('path');
module.exports = {
...
output:{
filename:"[name].[hashchunk].js", // 输出文件名称
path:path.resolve(__dirname,"build"),
}
}

loader的三种配置方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
module.exports = {
entry:"./src/index.js"
module:{
rules:[
{test:/\.png|jpg|gif$/,use:"url-loader" // loader:"url-loader"},
// loader的执行是自下向上 自右向左执行的
{test:/\.css$/,use:["style-loader","css-loader"]},
{test:/\.js$/,use:{
loader:"babel-loader",
options:{
// 配置 babel-polyfill
preset:["@babel/preset-env",{
useBuiltIns:"usage",
corejs:{version:2}
}],
plugins:[["@babel/plugin-transform-runtime"]]
},
}}
]
}
}

webpack source-map

1
2
3
4
5
6
// 开启source-map
module.exports = {
...
devtool:"cheap-source-map"
...
}
  • source-map 配置
关键字 含义
eval 使用eval包裹模块代码
source-map 最完整的信息,产生.map文件
cheap 不包含列信息(关于列信息的解释下面会有详细介绍)也不包含loader的sourcemap
module 包含loader的sourcemap(比如jsx to js ,babel的sourcemap),否则无法定义源文件
inline 将.map作为DataURI嵌入,不单独生成.map文件
  • source-map 组合标准
1
^(inline-|hidden-|eval-)?(nosources-)?(cheap-(module-)?)?source-map$
  • source-map 常见组合
类型 含义
source-map 原始代码 最好的sourcemap质量有完整的结果,但是会很慢
eval-source-map 原始代码 同样道理,但是最高的质量和最低的性能
cheap-module-eval-source-map 原始代码(只有行内) 同样道理,但是更高的质量和更低的性能
eval-cheap-source-map 转换代码(行内) 每个模块被eval执行,并且sourcemap作为eval的一个dataurl
eval 生成代码 每个模块都被eval执行,并且存在@sourceURL,带eval的构建模式能cache SourceMap
cheap-source-map 转换代码(行内) 生成的sourcemap没有列映射,从loaders生成的sourcemap没有被使用
cheap-module-source-map 原始代码(只有行内) 与上面一样除了每行特点的从loader中进行映射
  • source-map

    • 生产单独的source-map文件
    • 包含完整的行和列信息
  • 在目标文件里建立关系,从而能提示源文件原始位置

    • 不能缓存模块的sourceMap,每次都要重新产生完整的sourcemap
  • inline-source-map

    • 以base64格式内联在打包后的文件中
    • 包含完整的行和列信息
  • 在目标文件里建立关系,从而能提示源文件原始位置

  • hidden-source-map

    • 会在外部生产source-map,但是在目标文件并没有建立关联,也不能提示原始错误位置
    • 线上的代码中存在对应source-map的,有可能会造成代码的泄露
  • 当线上代码出BUG时,可以使用对应的source-map进行调试

  • eval-source-map

    • 会为每一个模块生产一个单独的sourcemap进行关联,并使用eval进行执行
    • 它与sourceMap一样好使,但是可以缓存每个模块的sourcemap,在重新构建的时候速度更快。每个模块都有自己的sourcemap,并且模块之间的sourcemap相互独立
  • nosources-source-map

  • 会生产sourcemap文件,也能找到源代码位置,但是源代码位置是空的

  • cheap-source-map

    • 只包含行映射,不包含列映射
    • 映射代码是转换后代码 如使用了babel
  • cheap-module-source-map

    • 只包含行映射,不包含列映射
    • 映射到真实源代码文件
  • eval-cheap-module-source-map:提供了只映射行(没有列映射)的源映射,而且速度更快。

  • eval-cheap-source-map:与eval-cheap-source-map类似,但不为模块生成源映射(即jsx到js的映射)。

  • eval:具有最佳性能,但它只映射到每个模块的已编译源代码。在许多情况下,这已经足够了。(映射到编译后的源代码)

webpack 常用loader

webpack默认执行打包处理js和json,无法处理其他文件内容。loader的作用是将其他文件的内容转化为js可执行的代码

1
2
3
4
5
6
function loader(source){
/// ....处理
// loader可以拿到在 module.rules[0].options => loaderOptions
return `module.exports = ${xxx}`
}
module.exports = loader

loader的分类

执行顺序 pre > normal(默认)> inline > post (同级别: 先下后上,先右后左)

  • pre: 前置
  • normal: 正常
  • inline:内联
  • post: 后置

常见loader

  • raw-loader : 用于文本

  • url-loader url-loader : 判断是否小于指定大小 小于返回base64 否则返回图片dataUrl

  • file-loader :处理图片

  • css-loader

    1
    css-loader

    :处理css

    • importLoaders less 阶段会将会处理 @import["style-loader","css-loader","less-loader"] css-loader 不需要设置importLoaders
    • importLoaders 选项允许你配置在 css-loader 之前有多少 loader 应用于@import资源。
  • less-loader less-loader :处理less 具体配置查看 lessOptions

  • scss-loader:处理scss

  • vue-loader:处理vue文件

  • babel-loader babel-loader:处理js

  • ts-loader:处理ts

  • postcss-loader 处理css浏览器兼容 postcssOptions

  • image-webpack-loader :图片压缩

  • thread-loader thread-loader : js开启多进程压缩

  • expose-loader : 实现第三方模块的加载一次全局使用

  • cache-loader :用于将开销较大的loader存储到磁盘上,第一次构建时间会久

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.export = {
module:{
rules:[
{test:/\.txt$/,use:"raw-loader"},
{test:/\.(png|jpeg|bmp|gif|svg)$/,use:[
{
loader:"url-loader",
options:{
esModule:false, // 关闭esmodule 可以省略 xx.default
limit:4 * 1024, // 4Kb
outputPath:"imgs",
publicPath:"/imgs"
fallback:{ // 超出limit指定loader
loader:"file-loader",//默认file-loader
options:{
name:"imgs/[name].[chunkhash:8].[ext]"
}
}
}
},
'image-webpack-loader'
]},
{test:/\.css$/,use:[MiniCssExtractPlugin.loader,{
loader:"css-loader",
options:{
importLoaders: 1,
sourceMap: false,
}
},"postcss-loader"]}, // 单独抽离css文件
{test:/\.less$/,use:["style-loader","css-loader","postcss-loader",{
loader:"less-loader"
options:{
additionalData: "@env:" + process.env.NODE_ENV,
lessOptions:{
javascriptEnabled:true,

}
}
}]},
{test:/\.scss$/,use:["style-loader","css-loader","scss-loader"]},
{
test:/\.jsx?$/,
use:"eslint-loader",
enforce:"pre",// 执行顺序,pre 先前执行
exclude:/node_modules/,// 排除node_modules文件夹
options:{fix:true}
},
{test:/\.jsx?$/,use:["cache-loader",
{
loader:"babel-loader",
options:{
presets:[["@babel/preset-env",
{ // babel预设配置
targets:{chrome:"58",ie:"10"}, // 支持的浏览器版本
useBuiltins:"usage", // 配置polyfill
corejs:{version:3} // core-js的版本
}
],"@babel/preset-react"], // 支持jsx
plugins:[
// 配置babel插件
['@babel/plugin-proposal-decorators', { legacy: true }], ['@babel/plugin-proposal-class-properties', { loose: true }],
],
},
exclude:/node_modules/,
},
{
loader:"thread-loader",
options:{
workers: 5, // 开启的进出数量
}
}
],
{test:/\.tsx?$/,use:"ts-loader"}, // 配置ts
{ // 配置第三方模块jquery
test:require.resolve("jquery"),
use:{
loader:"expose-loader",
options:{
exposes:{
globalName: "$", // 全局名称
moduleLocalName: "$", // 局部模块名称
}
}
}
}
]
}
}

webpack常用plugins

webpack插件可以理解为,在webpack整体执行流程中有许多的钩子函数,插件通过钩子函数在webpack打包流程中做一些事情

  • htmlWebpackPlugin :打包生成html并自动引入script
  • MiniCssExtractPlugin :提取css到单独的文件
  • DefinePlugin :配置全局变量 webpack内置
  • TerserPlugin :压缩JS webpack内置了
  • OptimizeCssAssetsWebpackPlugin : 压缩优化CSS
  • FileManagerPlugin:用于将打包后的文件复制到指定文件
  • HtmlWebpackExternalsPlugin : 引入CDN模块
  • ProvidePlugin : 引入第三方库 webpack内置
  • CopyWebpackPlugin : copy静态文件
  • CleanWebpackPlugin :在打包前清空打包文件
  • CompressionWebpackPlugin : 对代码进压缩
  • BannerPlugin : 为项目文件插入注释
  • DllPlugin : 动态链接库 用于将组件库与不变的第三库 进行单独打包
  • DllReferencePlugin : 用于动态替换
  • SpeedMeasureWebpackPlugin :打包速度分析插件
  • BundleAnalyzerPlugin : 开启服务生成打包可视化的html文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const DefinePlugin = require("define-plugin");
const HtmlWebpackExternalsPlugin = require("html-webpack-externals-plugin");
const CopyWebpackPlugin = require("copy-webpack-plugin");
const CleanWebpackPlugin = require("clean-webpack-plugin");
const TerserPlugin = require("terser-webpack-plugin");
const FileManagerPlugin = require("file-manager-plugin");
const { ProvidePlugin,DefinePlugin,BannerPlugin } = require("webpack")
const OptimizeCssAssetsWebpackPlugin = requier("optimize-css-assets-webpack-plugin");
const SpeedMeasureWebpackPlugin = require('speed-measure-webpack-plugin');
const smw = new SpeedMeasureWebpackPlugin();
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = smw.wrap({
...
module:{
rules:[
{test:/\.css$/,user:[MiniCssExtractPlugin.loader,"css-loader"]},
{test:/\.less$/,user:[MiniCssExtractPlugin.loader,"css-loader","less-loader",]},
]
},
optimization:{
minimize:true,
minimizer:[
new TerserPlugin({
test:/\.js$/, //匹配需要压缩的文件
include:// 引入需要压缩的文件
exclude:/node_modules/ // 排除文件夹
parallel:4,// boolean 是否开启多进程并发 / number 最大并发数
}) // 压缩js
]
},
plugins:[
new HtmlWebpackPlugin({
template: './src/index.html',
chunks:["index"],
inject:"body", // body,head,true(在body底部生成script),false(不生成script)
minify:{ // 开启压缩
collapseWhitespace: true, // 去除空格
removeComments: true // 删除注释
}
}),
new DefinePlugin({
'NODE_ENV':JSON.stringify('development'),
}),
new MiniCssExtractPlugin({
filename: 'css/[name].css'
}),
// HtmlWebpackExternalsPlugin 不好使了
new HtmlWebpackExternalsPlugin({
externals: [{
module: 'lodash', // 模块名
entry: "https://cdn.bootcdn.net/ajax/libs/lodash.js/4.17.20/lodash.js",
global: '_', // 全局变量名
}],
}),
new CopyWebpackPlugin({
patterns: [{
from: path.resolve(__dirname,'src/static'),//静态资源目录源地址
to: path.resolve(__dirname,'dist/static'), //目标地址,相对于output的path目录
}],
}),
new FileManagerPlugin({ // 管理打包后的文件路径
events: {
onEnd: { // 结束时 将dist文件拷贝到指定文件夹下
copy: [{
source: './dist/*.map', // 指定目录下的.map为后缀文件
destination: 'C:/aprepare/zhufengwebpack2021/1.basic/sourcemap', // 目标文件
}],
delete: ['./dist/*.map'], // 删除当前生成的文件
},
},
}),
new CleanWebpackPlugin({cleanOnceBeforeBuildPatterns: ['**/*'],}),
new ProvidePlugin({ // 配置后即可全局使用
_:"lodash",
$:"jquery",
}),
new BannerPlugin("学习webpack"),
new OptimizeCssAssetsWebpackPlugin(),// css优化
new BundleAnalyzerPlugin(),// 可视化打包资源数据
// 默认配置的具体配置项
// new BundleAnalyzerPlugin({
// analyzerMode: 'server',
// analyzerHost: '127.0.0.1',
// analyzerPort: '8888',
// reportFilename: 'report.html',
// defaultSizes: 'parsed',
// openAnalyzer: true,
// generateStatsFile: false,
// statsFilename: 'stats.json',
// statsOptions: null,
// excludeAssets: null,
// logLevel: info
// })
]
})

webpack devServer

webpack 开发服务器配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// webpack.config.js
const path = require('path');
module.exports = {
// 配置 开发服务器
devServer:{
//配置额外的静态文件根目录(默认为打包输出路径) 开启一个新的静态文件目录
contentBase:path.resolve(__dirname,"public"), // 可以是数组 ["",""]
// contentBase中静态文件资源前缀,也可以是数组 与 contentBase对应
contentBasePublicPath:"/public",
watchContentBase:false,// 告诉webpack是否监听contentBase指定文件夹中的文件
compress:true,// 开启gzip压缩
port:7000,// 端口号
open:true,// 默认打开浏览器
allowedHosts:[],// 配置可以访问服务器的白名单
clientLogLevel:"slient", // 关闭日志信息, trace/debug/info/error/warn
quiet:true,// 除初始启动信息外,任何信息不会写入控制台
// 配置文件前缀 默认为 / 如 http://localhost:7000/public/index.html
publicPath:"/public",
headers:{}, // 配置响应头
// lazy:true,// 当服务器访问到文件时 才开始编译
// filename:"main.js", // 当为lazy时 指定特定的文件 访问时开始编译
watchOptions:{ // 开启对文件系统的监控
ignored: /node_modules/, // 忽略文件
aggregateTimeout:600,// 类似于防抖 延迟指定毫秒数再重新编译
poll:false, // 使用轮询方式 boolean/number
},
writeToDisk:true, // 写入硬盘
hot:true, // 开启更新
overlay:false,// 出现错误全屏覆盖
before(app){}, // 在静态资源中间件处理之前 可以理解为WebpackDevServer显示页面之前
after(app){} // 在静态资源中间件处理之后
}
}

webpack polyfill

polyfill 用于磨平js在各个浏览器的差异问题

  • useBuiltIns
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46

    的用法

    - `false` : 此时不对 polyfill 做操作。如果引入 `@babel/polyfill`,则无视配置的浏览器兼容,引入所有的 `polyfill`
    - `entry`:**会影响全局变量,因为是在全局对象添加polyfill方法**
    - 在项目入口引入一次(多次引入会报错)
    - `"useBuiltIns": "entry"` 根据配置的浏览器兼容,引入浏览器不兼容的 polyfill。需要在入口文件手动添加 `import '@babel/polyfill'`,会自动根据 browserslist 替换成浏览器不兼容的所有 polyfill
    - 这里需要指定 core-js 的版本, 如果 "corejs": 3, 则 import '@babel/polyfill' 需要改成 `import 'core-js/stable';import 'regenerator-runtime/runtime';`

    ```JavaScript
    // 入口文件
    import 'core-js/stable';
    import 'regenerator-runtime/runtime';

    // webpack.config.js
    {
    test: /\.jsx?$/,
    exclude: /node_modules/,
    use: {
    loader: 'babel-loader',
    options: {
    presets: [["@babel/preset-env", {
    useBuiltIns: 'entry',
    corejs: { version: 3 }
    }], "@babel/preset-react"],
    plugins: [
    ["@babel/plugin-proposal-decorators", { legacy: true }],
    ["@babel/plugin-proposal-class-properties", { loose: true }]
    ]
    }
    }
    },

    // package.json
    {
    "browserslist": {
    "development": [
    "last 1 chrome version",
    "last 1 firefox version",
    "last 1 safari version"
    ],
    "production": [
    ">1%"
    ]
    },
    }
  • usage : 按需引入,不需要配置polyfill,引入方式也是import xxx from 'xxx',当多个文件都需要polyfill时,会重复在局部添加引入代码,不会影响全局变量

  • babel-runtime
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12

    - Babel为了解决全局空间污染的问题,提供了单独的包[babel-runtime](https://babeljs.io/docs/en/babel-runtime)用以提供编译模块的工具函数
    - 简单说 `babel-runtime` 更像是一种按需加载的实现,比如你哪里需要使用 Promise,只要在这个文件头部`import Promise from 'babel-runtime/core-js/promise'`就行了

    ```Plain%20Text
    npm i babel-runtime -D
    import Promise from 'babel-runtime/core-js/promise';

    const p = new Promise(()=> {

    });
    console.log(p);
  • @babel/plugin-transform-runtime
    
    1
    2
    3
    4
    5
    6
    7

    - 启用插件`babel-plugin-transform-runtime`后,Babel就会使用`babel-runtime`下的工具函数。

    - `babel-plugin-transform-runtime`插件能够将这些工具函数的代码转换成`require`语句,指向为对`babel-runtime`的引用

    - ```
    babel-plugin-transform-runtime

就是可以在我们使用新 API 时自动import babel-runtime里面的polyfill

  • 当我们使用 async/await 时,自动引入 babel-runtime/regenerator

  • 当我们使用 ES6 的静态事件或内置对象时,自动引入

    1
    babel-runtime/core-js
    • 移除内联babel helpers并替换使用babel-runtime/helpers 来替换
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
cnpm i @babel/runtime-corejs2 -D
{
test: /\.jsx?$/,
use: {
loader: 'babel-loader',
options: {
presets: [["@babel/preset-env",{
targets: "> 0.25%, not dead",
}], '@babel/preset-react'],
plugins: [
+ [
+ "@babel/plugin-transform-runtime",
+ {
+ corejs: 3,//当我们使用 ES6 的静态事件或内置对象时自动引入 babel-runtime/core-js
+ helpers: true,//移除内联babel helpers并替换使用babel-runtime/helpers 来替换
+ regenerator: true,//是否开启generator函数转换成使用regenerator runtime来避免污染全局域
+ },
+ ],
['@babel/plugin-proposal-decorators', { legacy: true }],
['@babel/plugin-proposal-class-properties', { loose: true }],
],
},
},
},

main.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const p = new Promise(()=> {});
console.log(p);


class A {

}
class B extends A {

}
console.log(new B());

function* gen() {

}
console.log(gen());
  • 最佳实践
    • babel-runtime适合在组件和类库项目中使用,而babel-polyfill适合在业务项目中使用。
  • polyfill-service
    • polyfill.io 自动化的 JavaScript Polyfill 服务
    • polyfill.io 通过分析请求头信息中的 UserAgent 实现自动加载浏览器所需的 polyfills
1
<script src="https://polyfill.io/v3/polyfill.min.js"></script>

webpack resolve使用

1
2
3
4
5
6
7
8
9
10
11
12
module.export = {
resolve:{
alias:[
"@":"./src" // 配置别名
],
extensions:[".tsx",".ts",".jsx",".js",".json"], // 解析顺序
module:[/node_modules/], // 指定模块查找文件夹
mainFields: ['browser', 'module', 'main'], // 入口文件字段名称 默认优先级
mainFiles:["index"], // 优先查找index开头文件
resolveLoader:[path.resolve(__dirname,"loaders"),"node_modules"], // /loaders/demo-loader 可以使用了
}
}

webpack optimization优化

常见的优化配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
const TerserPlugin = require("terser-webpack-plugin")
module.exports = {
...
optimization:{
minimize:true,
minimizer:[
new TerserPlugin();// 开启插件压缩js
],
minChunks:1,
splitChunk:{ // 代码切割
chunks:"all",// 切割的代码块类型 all,async,initial
minChunk:3 * 1024, // 进行切割的最小代码块
maxChunk:0, // 进行切割的最大代码块,0表示任意大,如果不为0则需要比minChunk大
maxAsyncRequests:2, // 按需加载进行代码切割的最大个数
maxInitialRequests:3,
minSize:10,
maxSize:0,
cacheGroups:{
default:{
reuseExistingChunk: true,
priority: -10
}
}
}
}
...
}
  • minimize: 开启插件压缩

  • minimizer: 传入实现压缩功能的插件,minimize需要设置为true

  • splitChunk
    
    1
    2
    3
    4
    5

    代码块切割优化

    - chunks

    chunks
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13

    代码块切割方式

    - `all` : 对引入的(直接引入、按需引入)模块进行优化
    - `async` : 入口中按需加载的模块,模块中按需加载的模块,都会被优化
    - `initial` : 入口中直接引入的模块、模块中直接引入的模块,都会被拆解并单独打包。共用模块只打包一次

    - `minChunks`: 拆分前 非按需引入的共用代码块最小次数

    - `maxInitialRequests` : 当项目打包完成后,一个直接引入的包会被拆分为指定个数的包

    - maxAsyncRequests

    maxAsyncRequests
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    : 当项目打包完成后,一个按需加载的包会被拆分为指定个数的包

    - maxAsyncRequests 用来表示按需加载的模块其能拆分的最大数量;
    - 如果对于单独打包出来的模块有两种可能,被多次引入的那个模块或模块集会被优先打包出来;
    - 同样的情况,如果两种情况下模块被引用的次数相同,体积大的那个模块或模块集会被打包出来;
    - 如果两种情况下,情况一只包含一个模块,情况二则包含两个模块,并且两种情况下被引入的次数也是相同的,前两条规则仍然有效。

    - minSize、maxSize

    minSize、maxSize
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13

    : 分别表示 chunk 在被拆分之前的最小体积和最大体积(maxSize 可以小于 minSize)

    - minSize 表示被拆分出的 bundle 在拆分之前的体积的最小数值,只有 >= minSize 的 bundle 会被拆分出来;
    - maxSize 表示被拆分出的 bundle 在拆分之前的体积的最大数值,默认值为 0,表示 bundle 在拆分前的体积没有上限;
    - maxSize 如果为非 0 值时,不可以小于 minSize;
    - 如果 bundle 在被拆分前的体积大于 maxSize,webpack 将会尝试将它拆分成更小的模块(前提是 bundle 在拆分之前由多个模块组成,如果仅仅只包含一个模块,大于 maxSize 和大于 minSize 小于等于 maxSize 是一样的效果);
    - 应用 maxSize 打包的 bundle 其名称会由“不应用 maxSize 时产生的 bundle 名称”和“一个生成的 key 值”组成;
    - maxSize 对于直接引入(import 或 require 引入)的部分或者按需引入的部分都有效,不过有些许区别,按需引入的包如果被拆分则使用 chunk 的名称作为 bundle 名,不会像前面的案例那样包含一些 key,如果非按需引入的部分包含于入口点名称构建的 bundle 时,该 bundle 名称将包含前面案例中类似的 key;
    - minSize 对于按需引入的部分是没有效的,因为无论在什么情况下,按需引入的部分都会被拆分打包出来;

    - name

    name
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15

    : 这个配置用于控制 webpack 打包时被拆分出来的 bundle 的名称。

    - splitChunks.name 用于控制 webpack 打包时被拆分出来的 bundle 的名称;
    - 该配置的值有三种选择,可以是一个布尔值(true false),也可以是一个函数(形式如 function (module, chunks, cacheGroupKey) => string),又或许是一个单纯的 String 类型;
    - 该配置也可以在缓存组中单独配置,如:splitChunks.cacheGroups.{缓存组的名称}.name;
    - 如果配置为一个布尔值,比如默认下该配置为 true,对于生成的 chunk 的名称,将会基于打包过程中 chunks 和缓存组名称自动生成,如果值为 false,将会直接使用 chunk 名称;
    - 你可以通过给该配置配置一个字符串或者函数来自定义定制打包后 chunk 的名称。如果配置的字符串是静态的或者配置的函数返回的是一个静态的字符串,将会使得被另外单独拆分的所有 chunk 都被打包到一个单独的文件中,这会导致页面首次加载增加,减慢页面的加载;
    - 如果给该配置赋值为一个函数,我们可以很好地利用参数中的 chunk.name 和 chunk.hash 来定制打包后生成的 name(这里所说的 chunk 是参数 chunks 参数的某一项,chunks 是所有 chunk 的集合);
    - 如果 splitChunks.name 匹配到一个入口点名称,打包后生成的 bundle 中该入口点将会被删除;
    - 对于按需引入的模块,仅在 chunks 为 all 时有效,并且值得注意的是,如果 name 的赋值形式是 function,并根据 chunks 等信息来自定义 name 规则时,只对直接引入的部分有效,对按需引入无效,而对于返回一个静态字符串和直接赋值静态字符串是有效的,其他的情况也是有效的;
    - 官方建议在生产环境时将 name 设置为 false,为了“it doesn't change names unnecessarily”(这将保证不会不必要地更改名称),具体怎样去理解个人暂时还不清楚;

    - ```
    cacheGroups
    : 缓存组,可以使用并覆盖splitChunks的配置项。拥有三个特定的属性 - test: 表示要过滤 modules,默认为所有的 modules,可匹配模块路径或 chunk 名字,当匹配到某个 chunk 的名字时,这个 chunk 里面引入的所有 module 都会选中; - priority:权重,数字越大表示优先级越高。一个 module 可能会满足多个 cacheGroups 的正则匹配,到底将哪个缓存组应用于这个 module,取决于优先级;默认为辅助 - reuseExistingChunk:表示是否使用已有的 chunk,true 则表示如果当前的 chunk 包含的模块已经被抽取出去了,那么将不会重新生成新的,即几个 chunk 复用被拆分出去的一个 module;

tree-shaking 原理

利用es6模块的特点:

  • 只能作为模块顶层的语句出现
  • import的模块名只能是字符串常量,不能动态引入
  • import binding是immutable,引入的模块不能再做修改

webpack hash

webpack中hash分为 hash、chunkhash、contenthash

hash:也被叫做文件指纹,是指打包后输出的文件名和后缀

hash一般是结合CDN缓存来使用,通过webpack构建之后,生产对应文件名自动带上对应的MD5值。如果文件内容发生改变时contenthash也会发生变化,对应的HTML引用的URL地址也会发生变化,触发CDN服务器从源代码服务器上拉取最新的文件资源更新本地缓存

常见的webpack占位符

占位符名称 含义
ext 资源后缀名
name 文件名称
path 文件的相对路径
folder 文件所在的文件夹
hash 每次webpack构建时生成一个唯一的hash值
chunkhash 根据chunk生成hash值,来源于同一个chunk,则hash值就一样
contenthash 根据内容生成hash值,文件内容相同hash值就相同

img

hash

  • Hash 是整个项目的hash值,其根据每次编译内容计算得到,每次编译之后都会生成新的hash,即修改任何文件都会导致所有文件的hash发生改变

chunkhash

采用hash计算的话,每一次构建后生成的哈希值都不一样,即使文件内容压根没有改变。这样子是没办法实现缓存效果,我们需要换另一种哈希值计算方式,即chunkhash

  • chunkhash和hash不一样,它根据不同的入口文件(Entry)进行依赖文件解析、构建对应的chunk,生成对应的哈希值。我们在生产环境里把一些公共库和程序入口文件区分开,单独打包构建,接着我们采用chunkhash的方式生成哈希值,那么只要我们不改动公共库的代码,就可以保证其哈希值不会受影响

contenthash

  • 使用chunkhash存在一个问题,就是当在一个JS文件中引入CSS文件,编译后它们的hash是相同的,而且只要js文件发生改变 ,关联的css文件hash也会改变,这个时候可以使用mini-css-extract-plugin里的contenthash值,保证即使css文件所处的模块里就算其他文件内容改变,只要css文件内容不变,那么不会重复构建

webpack debugger测试

1
2
3
4
5
6
7
8
9
const webpack = require("webpack");
const config = require("./webpack.config");

const compiler = webpack(config);

compiler.run((err,stats)=>{
if(err) throw err;
console.log(stats);
})

dotenv 配置多个环境变量

  1. 生成.env文件
1
2
3
4
// .env
DB_HOST=localhost
DB_USER=root
DB_PASS=s1mpl3
  1. 将文件内容转为变量
1
2
3
4
5
6
7
8
9
const dotenvFile = ".env";
require('dotenv-expand')(
require('dotenv').config({
path:dotenvFile
})
);
console.log(process.env.DB_HOST)
console.log(process.env.DB_USER)
console.log(process.env.DB_PASS)

loader-runner 文件

loader的执行流程

img

loader的核心执行流程 loader-runner.js文件

https://github.com/webpack/loader-runner/tree/v2.4.0/lib

注意:

  • 如果loader需要返回多个值的时候,必须要this.callback(err,...args)这种方式返回,不要使用return
  • 上一个loader的返回值 会作为下一个loader的参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
var fs = require("fs");
var readFile = fs.readFile.bind(fs);
var loadLoader = require("./loadLoader"); // 获取解析好的loader的数组

// buffer -> 文字
function utf8BufferToString(buf) {
var str = buf.toString("utf-8");
if(str.charCodeAt(0) === 0xFEFF) {
return str.substr(1);
} else {
return str;
}
}

// 截取 ?xxx
function splitQuery(req) {
var i = req.indexOf("?");
if(i < 0) return [req, ""];
return [req.substr(0, i), req.substr(i)];
}
// 获取文件名
function dirname(path) {
if(path === "/") return "/";
var i = path.lastIndexOf("/");
var j = path.lastIndexOf("\\");
var i2 = path.indexOf("/");
var j2 = path.indexOf("\\");
var idx = i > j ? i : j;
var idx2 = i > j ? i2 : j2;
if(idx < 0) return path;
if(idx === idx2) return path.substr(0, idx + 1);
return path.substr(0, idx);
}
// 绝对路径的loader字符串 => loader对象
function createLoaderObject(loader) {
var obj = {
path: null,
query: null,
options: null,
ident: null,
normal: null,
pitch: null,
raw: null,
data: null,
pitchExecuted: false,
normalExecuted: false
};
// request 返回的相当于是路径
Object.defineProperty(obj, "request", {
enumerable: true,
get: function() {
return obj.path + obj.query;
},
set: function(value) {
if(typeof value === "string") {
var splittedRequest = splitQuery(value);
obj.path = splittedRequest[0];
obj.query = splittedRequest[1];
obj.options = undefined;
obj.ident = undefined;
} else {
if(!value.loader)
throw new Error("request should be a string or object with loader and object (" + JSON.stringify(value) + ")");
obj.path = value.loader;
obj.options = value.options;
obj.ident = value.ident;
if(obj.options === null)
obj.query = "";
else if(obj.options === undefined)
obj.query = "";
else if(typeof obj.options === "string")
obj.query = "?" + obj.options;
else if(obj.ident)
obj.query = "??" + obj.ident;
else if(typeof obj.options === "object" && obj.options.ident)
obj.query = "??" + obj.options.ident;
else
obj.query = "?" + JSON.stringify(obj.options);
}
}
});
obj.request = loader;
// 将对象标记为不可再扩展的
if(Object.preventExtensions) {
Object.preventExtensions(obj);
}
return obj;
}
// 同步或异步执行loader
/*
fn => 当前loader的pitch 或者是当前loader自身函数
context => loaderContext,
args => [remainingRequest,previousRequest,data] 或者是 包含buffer的数组
callback => 回调函数
*/
function runSyncOrAsync(fn, context, args, callback) {
var isSync = true; // 是否是异步
var isDone = false; // 是否执行完毕
var isError = false; // internal error 是否发生错误
var reportedError = false; // 是否有错误信息
// 为loaderContext定义async方法 => 也就是说 loader中 => const callback = this.async();
context.async = function async() {
if(isDone) { // 第一次执行 为false 不执行
if(reportedError) return; // ignore
throw new Error("async(): The callback was already called.");
}
// 将异步修改为false
isSync = false;
// 返回 对应的callback方法
return innerCallback;
};
// 定义callback方法同时也是 异步loader的返回值 this.callback(); 进行执行loader
var innerCallback = context.callback = function() {
if(isDone) {
if(reportedError) return; // ignore
throw new Error("callback(): The callback was already called.");
}
// 将loader标记为 执行完毕且不再是异步
isDone = true;
isSync = false;
try {
// 执行runSyncOrAsync的callback参数 并传入 callback中的参数
/*
const callback = this.async();
callback("执行成功了");
*/
callback.apply(null, arguments); // arguments => 0:"执行成功了"
} catch(e) {
isError = true;
throw e;
}
};
// 执行pitch方法 修改this指向为loaderContext 并传入 [remainingRequest,previousRequest,data]
try {
var result = (function LOADER_EXECUTION() {
return fn.apply(context, args);
}());
// 判断是否为true 默认为true
if(isSync) {
isDone = true; // 执行完毕
// 判断result的返回值
// undefined 表示没有返回值执行callback
// promise 执行then方法 并调用callback接受返回值
// 其他值 表示有返回值 调用callback 传入数据
if(result === undefined)
return callback();
if(result && typeof result === "object" && typeof result.then === "function") {
return result.then(function(r) {
callback(null, r);
}, callback);
}
return callback(null, result);
}
} catch(e) {
if(isError) throw e;
if(isDone) {
// loader is already "done", so we cannot use the callback function
// for better debugging we print the error on the console
if(typeof e === "object" && e.stack) console.error(e.stack);
else console.error(e);
return;
}
isDone = true;
reportedError = true;
callback(e);
}

}

function convertArgs(args, raw) {
if(!raw && Buffer.isBuffer(args[0]))
args[0] = utf8BufferToString(args[0]);
else if(raw && typeof args[0] === "string")
args[0] = new Buffer(args[0], "utf-8"); // eslint-disable-line
}
// ----------------------------------------------------------------------------
// 递归执行loader pitch方法
function iteratePitchingLoaders(options, loaderContext, callback) {
// abort after last loader
// 判断是否越界
if(loaderContext.loaderIndex >= loaderContext.loaders.length)
return processResource(options, loaderContext, callback); // 越界 开始获取执行loader的文件内容 以及遍历执行loader
// 获取当前loader
var currentLoaderObject = loaderContext.loaders[loaderContext.loaderIndex];

// iterate 判断当前loader的 pitch 是否执行过了 如果执行过了 递归向后执行
if(currentLoaderObject.pitchExecuted) {
loaderContext.loaderIndex++;
return iteratePitchingLoaders(options, loaderContext, callback);
}
//
// load loader module
// 开始执行loader 并将当前loader 传入
loadLoader(currentLoaderObject, function(err) {
if(err) {
// 如果发生错误 不缓存当前loader并返回错误
loaderContext.cacheable(false);
return callback(err);
}
// 获取当前pitch函数,并将pitchExecuted标记为true,以表示pitch函数执行过了
var fn = currentLoaderObject.pitch;
currentLoaderObject.pitchExecuted = true;
// 如果没有pitch函数继续执行返回 165 行进行判断并向下执行
if(!fn) return iteratePitchingLoaders(options, loaderContext, callback);
// 存在pitch函数
// 传入pitch函数,传入loaderContext,传入remainingRequest(当前loader之前的loader),previousRequest(当前loader之后的loader),以及当前loader的data属性
runSyncOrAsync(
fn,
loaderContext, [loaderContext.remainingRequest, loaderContext.previousRequest, currentLoaderObject.data = {}],
function(err) {
// 如果执行错误 则返回当前的无错误
if(err) return callback(err);
// 获取除错误信息,之后的arguments
var args = Array.prototype.slice.call(arguments, 1);
// 如果大于0表明pitch函数返回了数据, 让当前的pitch函数所在的loader的index减一 并执行对应loader的自身函数
// 如果不大于0 表示没有返回值 继续递归执行相应的loader
if(args.length > 0) {
loaderContext.loaderIndex--;
iterateNormalLoaders(options, loaderContext, args, callback);
} else {
iteratePitchingLoaders(options, loaderContext, callback);
}
}
);
});
}
// 获取loader的文件内容
function processResource(options, loaderContext, callback) {
// set loader index to last loader
// loaderIndex 指向loader的最后一位
loaderContext.loaderIndex = loaderContext.loaders.length - 1;
// 执行loader函数
var resourcePath = loaderContext.resourcePath;
if(resourcePath) {
loaderContext.addDependency(resourcePath);
// readFile 读取文件并保存到resourceBuffer中
options.readResource(resourcePath, function(err, buffer) {
if(err) return callback(err);
options.resourceBuffer = buffer;
iterateNormalLoaders(options, loaderContext, [buffer], callback);
});
} else {
iterateNormalLoaders(options, loaderContext, [null], callback);
}
}

// ----------------------------------------------------------------------------
// 递归执行loader函数 也就是normal函数
function iterateNormalLoaders(options, loaderContext, args, callback) {
// 判断loader是否越界
if(loaderContext.loaderIndex < 0)
return callback(null, args);
// 获取当前loader
var currentLoaderObject = loaderContext.loaders[loaderContext.loaderIndex];

// iterate // 判断当前loader 函数是否执行过 如果执行过递归向前执行
if(currentLoaderObject.normalExecuted) {
loaderContext.loaderIndex--;
return iterateNormalLoaders(options, loaderContext, args, callback);
}
// 获取loader自身函数
var fn = currentLoaderObject.normal;
// 标记自身函数为执行过
currentLoaderObject.normalExecuted = true;
// 如果没有函数 递归向前执行
if(!fn) {
return iterateNormalLoaders(options, loaderContext, args, callback);
}
// 判断是否存在raw属性 如果为true表示buffer,false表示字符串
convertArgs(args, currentLoaderObject.raw);
// 执行loader
runSyncOrAsync(fn, loaderContext, args, function(err) {
if(err) return callback(err);
// args 此时拿到的是当前loader的返回值 作为下一个loader的参数传入
var args = Array.prototype.slice.call(arguments, 1);
iterateNormalLoaders(options, loaderContext, args, callback);
});
}

exports.getContext = function getContext(resource) {
var splitted = splitQuery(resource);
return dirname(splitted[0]);
};
// 解析并执行loader
exports.runLoaders = function runLoaders(options, callback) {
// read options
var resource = options.resource || ""; // 需要执行loader 的文件内容
var loaders = options.loaders || []; // 需要执行的loader
var loaderContext = options.context || {}; // loader中的this指向
var readResource = options.readResource || readFile; // fs.readFile

var splittedResource = resource && splitQuery(resource);
var resourcePath = splittedResource ? splittedResource[0] : undefined;
var resourceQuery = splittedResource ? splittedResource[1] : undefined;
var contextDirectory = resourcePath ? dirname(resourcePath) : null;

// execution state
var requestCacheable = true;
var fileDependencies = [];
var contextDependencies = [];

// prepare loader objects
loaders = loaders.map(createLoaderObject); // 将loaders中的绝对路径的loader转为loader对象

loaderContext.context = contextDirectory; // loader内容
loaderContext.loaderIndex = 0; // loader当前位置
loaderContext.loaders = loaders; // 所有的loader对象
loaderContext.resourcePath = resourcePath; // 文件路径
loaderContext.resourceQuery = resourceQuery; // 文件查询字符串
loaderContext.async = null; // 是否是异步
loaderContext.callback = null; // 是否执行callback 在 runSyncOrAsync时被赋值
loaderContext.cacheable = function cacheable(flag) {
if(flag === false) {
requestCacheable = false;
}
};
loaderContext.dependency = loaderContext.addDependency = function addDependency(file) {
fileDependencies.push(file);
};
loaderContext.addContextDependency = function addContextDependency(context) {
contextDependencies.push(context);
};
loaderContext.getDependencies = function getDependencies() {
return fileDependencies.slice();
};
loaderContext.getContextDependencies = function getContextDependencies() {
return contextDependencies.slice();
};
loaderContext.clearDependencies = function clearDependencies() {
fileDependencies.length = 0;
contextDependencies.length = 0;
requestCacheable = true;
};
Object.defineProperty(loaderContext, "resource", {
enumerable: true,
get: function() {
if(loaderContext.resourcePath === undefined)
return undefined;
return loaderContext.resourcePath + loaderContext.resourceQuery;
},
set: function(value) {
var splittedResource = value && splitQuery(value);
loaderContext.resourcePath = splittedResource ? splittedResource[0] : undefined;
loaderContext.resourceQuery = splittedResource ? splittedResource[1] : undefined;
}
});
Object.defineProperty(loaderContext, "request", {
enumerable: true,
get: function() {
return loaderContext.loaders.map(function(o) {
return o.request;
}).concat(loaderContext.resource || "").join("!");
}
});
Object.defineProperty(loaderContext, "remainingRequest", {
enumerable: true,
get: function() {
if(loaderContext.loaderIndex >= loaderContext.loaders.length - 1 && !loaderContext.resource)
return "";
return loaderContext.loaders.slice(loaderContext.loaderIndex + 1).map(function(o) {
return o.request;
}).concat(loaderContext.resource || "").join("!");
}
});
Object.defineProperty(loaderContext, "currentRequest", {
enumerable: true,
get: function() {
return loaderContext.loaders.slice(loaderContext.loaderIndex).map(function(o) {
return o.request;
}).concat(loaderContext.resource || "").join("!");
}
});
Object.defineProperty(loaderContext, "previousRequest", {
enumerable: true,
get: function() {
return loaderContext.loaders.slice(0, loaderContext.loaderIndex).map(function(o) {
return o.request;
}).join("!");
}
});
Object.defineProperty(loaderContext, "query", {
enumerable: true,
get: function() {
var entry = loaderContext.loaders[loaderContext.loaderIndex];
return entry.options && typeof entry.options === "object" ? entry.options : entry.query;
}
});
Object.defineProperty(loaderContext, "data", {
enumerable: true,
get: function() {
return loaderContext.loaders[loaderContext.loaderIndex].data;
}
});

// finish loader context
if(Object.preventExtensions) {
Object.preventExtensions(loaderContext);
}

var processOptions = {
resourceBuffer: null,
readResource: readResource
};
// 定义完毕属性后 开始遍历执行loader的pitch
iteratePitchingLoaders(processOptions, loaderContext, function(err, result) {
if(err) {
// 返回错误信息
return callback(err, {
cacheable: requestCacheable,
fileDependencies: fileDependencies,
contextDependencies: contextDependencies
});
}
// 返回对应的数据信息
callback(null, {
result: result,
resourceBuffer: processOptions.resourceBuffer,
cacheable: requestCacheable,//缓存
fileDependencies: fileDependencies, // 文件依赖
contextDependencies: contextDependencies // context依赖
});
});
};



// loadLoader.js
// 核心就是加载loader 并为loader对象进行属性赋值
module.exports = function loadLoader(loader, callback) {
if(typeof System === "object" && typeof System.import === "function") {
System.import(loader.path).catch(callback).then(function(module) {
loader.normal = typeof module === "function" ? module : module.default;
loader.pitch = module.pitch;
loader.raw = module.raw;
if(typeof loader.normal !== "function" && typeof loader.pitch !== "function") {
return callback(new LoaderLoadingError(
"Module '" + loader.path + "' is not a loader (must have normal or pitch function)"
));
}
callback();
});
} else {
// 读取loader
try {
var module = require(loader.path);
} catch(e) {
// it is possible for node to choke on a require if the FD descriptor
// limit has been reached. give it a chance to recover.
if(e instanceof Error && e.code === "EMFILE") {
var retry = loadLoader.bind(null, loader, callback);
if(typeof setImmediate === "function") {
// node >= 0.9.0
return setImmediate(retry);
} else {
// node < 0.9.0
return process.nextTick(retry);
}
}
return callback(e);
}
// 判断;module 如果不是function 并且也不是个对象 就报错
if(typeof module !== "function" && typeof module !== "object") {
return callback(new LoaderLoadingError(
"Module '" + loader.path + "' is not a loader (export function or es6 module)"
));
}
// 为loader进行属性赋值
loader.normal = typeof module === "function" ? module : module.default;
loader.pitch = module.pitch;
loader.raw = module.raw;
if(typeof loader.normal !== "function" && typeof loader.pitch !== "function") {
return callback(new LoaderLoadingError(
"Module '" + loader.path + "' is not a loader (must have normal or pitch function)"
));
}
// 调用callback 此时的loader 已经赋值好了pitch、normal、raw了
callback();
}
};

简述: loader-runner 主要将loader转为对象loader自身函数转为normal属性,pitch函数转为pitch属性,通过runSyncOrAsync执行对应的loader的normal或pitch,iterateNormalLoaders与iteratePitchLoaders作用相似都是递归执行对应函数,其中iteratePitchLoaders会判断pitch函数是否有返回值,如果有返回值 则不会在执行当前loader之前的loader与当前pitch之后的pitch

  • 标题: webpack常用配置汇总
  • 作者: 爱吃猪头爱吃肉
  • 创建于: 2023-04-25 08:03:09
  • 更新于: 2023-04-25 08:03:16
  • 链接: https://zsasjy.github.io/2023/04/25/webpack常用配置汇总/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
推荐文章
搭建react开发环境 搭建react开发环境 探索Koa的中间件原理 探索Koa的中间件原理 前端工程化校验配置 前端工程化校验配置 学习算法 学习算法 浏览器架构 浏览器架构 less使用指南 less使用指南
 评论