libin1991 / libin_Blog

爬虫-博客大全
https://libin.netlify.com/
123 stars 18 forks source link

Webpack4优化 #499

Open libin1991 opened 6 years ago

libin1991 commented 6 years ago

简介

Webpack4 那点儿东西 基于webpack4总结了一些webpack的常见配置,但是webpack 各种强大的配置有时候让你不堪重负,会打包很多的文件,遍历解析很多文件。。。。。总之,这些操作会让webpack打包过程变得很慢,所以开发过程中我们不得不去优化一些配置,让webpack更好的服务于我们的开发。

动态链接库DLL

即把基础模块的代码打包进入动态链接库里,比如常用的react,vue等,当需要导入的模块在动态连接库里的时候,模块不能再次被打包,而是去动态连接库里获取

  1. 创建一个webpack.dll.config.js文件打包常用类库到dll中
module.exports = {
    entry: {
        react: ['vue'] //vue模块打包到一个动态连接库
    },
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: '[name].dll.js', //输出动态连接库的文件名称
        library: '_dll_[name]' //全局变量名称
    },
    plugins: [
        new webpack.DllPlugin({
            name: '_dll_[name]', //和output.library中一致,也就是输出的manifest.json中的 name值
            path: path.join(__dirname, 'dist', '[name].manifest.json')
        })
    ]
}
webpack --config webpack.dll.config.js --mode production
  1. 在住配置文件 webpack.config.js中加入以下代码
plugins: [
 new webpack.DllReferencePlugin({
       manifest: require(path.join(__dirname, 'dist', 'vue.manifest.json')),
   })
]
webpack --config webpack.config.js --mode development

这样会从dll中获取vue,而且不用再次打包vue了。

Webpack热替换之HMR

Hot Module Replacement(以下简称 HMR)是 webpack 中超级有用的特性之一 ,当你对代码进行修改并保存后,webpack 将对代码重新打包,并将新的模块发送到浏览器端,浏览器通过新的模块替换老的模块,这样在不刷新浏览器的前提下就能够对应用进行更新。从而减少很多时间。の。。。。比如,页面中有一个modal框,需要点击button触发modal显示,在开发过程中,如果修改了modal 的样式,触发浏览器刷新,你还需要再次点击button才能看到修改后的modal样式,但是热替换是不需要刷新浏览器的,可以直接观察到修改后的变化。 上文中已经介绍了watch的用法,但是watch是针对打包时文件发生变化进行重新打包,而HMR是针对webpack-dev-server的。

  1. devserver配置如下
devServer: {//配置此静态文件服务器,可以用来预览打包后项目
    inline:true,//打包后加入一个websocket客户端
    hot:true,//热加载
    contentBase: path.resolve(__dirname, 'dist'),//开发服务运行时的文件根目录
    host: 'localhost',//主机地址
    port: 9090,//端口号
    compress: true//开发服务器是否启动gzip等压缩
}
  1. plugins配置项加入以下两行
 new webpack.HotModuleReplacementPlugin(),
 new webpack.NamedModulesPlugin()//用户名替代id

3.业务代码中的修改

if(module.hot) {
    module.hot.accept('./hello.js', function() {
        div.innerHTML = hello()
    })
}
  1. 原理及流程解析大致流程: webpack-dev-server可以和浏览器之间建立一个web socket进行通信,一旦新文件被打包出来,webpack-dev-server就告诉浏览器这个消息,这时浏览器就可以自动刷新页面或者进行热替换操作。当一个模块b发生改变,而模块内又没有HMR代码(类似于上述3中的代码)来处理这一消息时,那这一消息就会被传递到依赖模块b的其他模块上;如果消息在新模块上没有被捕获的话就会再次进行传递;如果所有的消息都被捕获了的话,那我们的应用就应该已经按照代码进行了更新;反之如果有消息冒泡到了入口(entry)文件还没有被捕获的话,那就说明我们的代码中没有处理这类变更方法,那webpack就会刷新浏览器页面,即从HMR回退到LiveReload。

Tree Shaking

tree shaking 是一个术语,通常用于描述移除 JavaScript 上下文中的未引用代码。这个术语和概念实际上是兴起于 ES2015 模块打包工具 rollup。你可以将应用程序想象成一棵树。绿色表示实际用到的源码和 library,是树上活的树叶。灰色表示无用的代码,是秋天树上枯萎的树叶。为了除去死去的树叶,你必须摇动这棵树,使它们落下。但是webpakc的Tree Shaking依赖静态的ES6模块化语法即通过import和export导入导出的代码,而且需要引入一个能够删除未引用代码(dead code)的压缩工具(minifier)(例如 UglifyJSPlugin)或者在运行命令的时候用webpack --display-used-exports --optimize-minimize --mode production

比如以下代码

export function getName() {
    return 'hello world';
}
export function getAge() {
    return 9999;
}

如果你只引用了其中一个,那么通过Tree Shaking会剔除另一个未用到的,打包的时候直接忽略。

{
    test: /\.js/,
    use: {
        loader: 'babel-loader',
        query: {
            presets: ["env", {
+                                   modules: false //关闭 Babel 的模块转换功能,保留原本的 ES6 模块化语法
+                               }]
        }
    }
}
const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
plugins: [
    new UglifyJSPlugin()
 ]
webpack --display-used-exports --optimize-minimize

提取公共代码

这个变化还是很大的,之前的webpack版本用的都是commonchunkplugin,但是webpack4开始使用common-chunk-and-vendor-chunk 配置如下:

optimization: {
    splitChunks: {
        cacheGroups: {
            commons: {
                chunks: "initial",
                minChunks: 2,
                maxInitialRequests: 5, // The default limit is too small to showcase the effect
                minSize: 0 // This is example is too small to create commons chunks
            },
            vendor: {
                test: /node_modules/,
                chunks: "initial",
                name: "vendor",
                priority: 10,
                enforce: true
            }
        }
    }
}

Scope Hoisting

作用域提升,这是在webpack3中所提出来的。它会使代码体积更小,因为函数申明语句会产生大量代码.

const ModuleConcatenationPlugin = require('webpack/lib/optimize/ModuleConcatenationPlugin');
plugins: [
// 开启 Scope Hoisting
new ModuleConcatenationPlugin(),
],

CDN

对于静态资源的处理,放入CDN是一个很好的选择,webpack中配置CDN的方式如下:

output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name]_[hash:8].js',
    publicPath: 'http://static.xxxx.com/'
},

多进程之HappyPack

HappyPack就能让Webpack把任务分解给多个子进程去并发的执行,子进程处理完后再把结果发送给主进程,其中子进程的个数为cpu的个数减去1,需要在loader处修改如下

 use: 'happypack/loader?id=babel',

并且在plugin中添加以下代码:

new HappyPack({
    id: 'babel',
    //如何处理.js文件,和rules里的配置相同
    loaders: [{
        loader: 'babel-loader',
        query: {
            presets: [
                "env", "stage-0"
            ]
        }
    }]
}),

小结

以上为webpack配置中的一些常见优化方案,但是根据项目的不同,需要选择的方案也不太一样,多尝试,根据特定的环境选择特定的优化方案,我觉得是一件比较不错的事情。

libin1991 commented 6 years ago

最近朋友圈被《头号玩家》刷爆了,斯皮尔伯格一个资深电影导演,把对过去经典的致敬,对未来的憧憬浓缩在这一部电影中,可以说让观众燃了起来。

观望整个前端开发,不断的演化,发展迅速。前端开发从最开始切页面, 前端自动化构建工具日新月异,从最初的Grunt,Gulp到现在前端项目可以说标配的webpack。

我们先来致敬经典:

1. 什么是webpack?

可以看做一个模块化打包机,分析项目结构,处理模块化依赖,转换成为浏览器可运行的代码。

  • 代码转换: TypeScript 编译成 JavaScript、SCSS,LESS 编译成 CSS.
  • 文件优化:压缩 JavaScript、CSS、HTML 代码,压缩合并图片。
  • 代码分割:提取多个页面的公共代码、提取首屏不需要执行部分的代码让其异步加载。
  • 模块合并:在采用模块化的项目里会有很多个模块和文件,需要构建功能把模块分类合并成一个文件。
  • 自动刷新:监听本地源代码的变化,自动重新构建、刷新浏览器。

构建把一系列前端代码自动化去处理复杂的流程,解放生产力。

2. 进入webpack世界

初始化项目

   npm install webpack webpack-cli -D

webpack4抽离出了webpack-cli,所以我们需要下载2个依赖。

Webpack 启动后会从Entry里配置的Module开始递归解析 Entry 依赖的所有 Module。 每找到一个 Module, 就会根据配置的Loader去找出对应的转换规则,对 Module 进行转换后,再解析出当前 Module 依赖的 Module。 这些模块会以 Entry 为单位进行分组,一个 Entry 和其所有依赖的 Module 被分到一个组也就是一个 Chunk。最后 Webpack 会把所有 Chunk 转换成文件输出。 在整个流程中 Webpack 会在恰当的时机执行 Plugin 里定义的逻辑。

webpack需要在项目根目录下创建一个webpack.config.js来导出webpack的配置,配置多样化,可以自行定制,下面讲讲最基础的配置。

module.exports = {
    entry: './src/index.js',
    output: {
        path: path.join(__dirname, './dist'),
        filename: 'main.js',
    }
}
  • entry代表入口,webpack会找到该文件进行解析
  • output代表输入文件配置
  • path把最终输出的文件放在哪里
  • filename输出文件的名字

有时候我们的项目并不是spa,需要生成多个js html,那么我们就需要配置多入口。

module.exports = {
    entry: {
        pageA: './src/pageA.js',
        pageB: './src/pageB.js'
    },
    output: {
        path: path.join(__dirname, './dist'),
        filename: '[name].[hash:8].js',
    },
}

entry配置一个对象,key值就是chunk: 代码块,一个 Chunk 由多个模块组合而成,用于代码合并与分割。看看filename[name]: 这个name指的就是chunk的名字,我们配置的key值pageA pageB,这样打包出来的文件名是不同的,再来看看[hash],这个是给输出文件一个hash值,避免缓存,那么:8是取前8位。

这里有人会有疑问了,项目是多页面的,应该有pageA.html``pageA.js``pageA.css, 那么我应该生成多个html,这个只是做了JS的入口区分,我不想每一个页面都去复制粘贴一个html,并且html是大部分重复的,可能不同页面只需要修改title,下面来看看这个问题怎么解决:

需要引入一个webpack的plugin:

npm install html-webpack-plugin -D

该插件可以给每一个chunk生成html,指定一个template,可以接收参数,在模板里面使用,下面来看看如何使用:

const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
    entry: {
        pageA: './src/pageA.js',
        pageB: './src/pageB.js'
    },
    output: {
        path: path.join(__dirname, './dist'),
        filename: '[name].[hash:8].js',
    },
    plugins: [
         new HtmlWebpackPlugin({
            template: './src/templet.html',
            filename: 'pageA.html',
            title: 'pageA',
            chunks: ['pageA'],
            hash: true,
            minify: {
                removeAttributeQuotes: true
            }
        }),
        new HtmlWebpackPlugin({
            template: './src/templet.html',
            filename: 'pageB.html',
            title: 'pageB',
            chunks: ['pageB'],
            hash: true,
            minify: {
                removeAttributeQuotes: true
            }
        }),
    ]
}

在webpack中,插件的引入顺序没有规定,这个在后面在继续详说。

  • template: html模板的路径地址
  • filename: 生成的文件名
  • title: 传入的参数
  • chunks: 需要引入的chunk
  • hash: 在引入JS里面加入hash值 比如: <script src='index.js?2f373be992fc073e2ef5'></script>
  • removeAttributeQuotes: 去掉引号,减少文件大小<script src=index.js></script>
  • 具体文档

这样在dist目录下就生成了pageA.html和pageB.html并且通过配置chunks,让pageA.html里加上了script标签去引入pageA.js。那么现在还剩下css没有导入,css需要借助loader去做,所以现在要下载几个依赖,以scss为例,less同理

npm install css-loader style-loader sass-loader node-sass -D
  • css-loader: 支持css中的import
  • style-loader: 把css写入style内嵌标签
  • sass-loader: scss转换为css
  • node-sass: scss转换依赖

来看看如何配置loader

module.exports = {
    module: {
        rules: [
                {
                    test: /\.scss$/,
                    use: ['style-loader', 'css-loader', 'sass-loader'],
                    exclude: /node_modules/
                }
        ]
    }
}
  • test: 一个正则表达式,匹配文件名
  • use: 一个数组,里面放需要执行的loader,倒序执行,从右至左。
  • exclude: 取消匹配node_modules里面的文件

如果想把css作为一个单独的文件,需要用到一个插件来做(webpack4.0.0以上版本需要next版本):

 npm i extract-text-webpack-plugin@next -D
const ExtractTextPlugin = require('extract-text-webpack-plugin');
module.exports = {
    entry: './src/index.js',
    output: {
        path: path.join(__dirname, './dist'),
        filename: 'main.js',
    },
    module: {
        rules: [
            {
                test: /\.scss$/,
                use: ExtractTextPlugin.extract({
                    // style-loader 把css直接写入html中style标签
                    fallback: 'style-loader',
                    // css-loader css中import支持
                    // loader执行顺序 从右往左执行
                    use: ['css-loader', 'sass-loader']
                }),
                exclude: /node_modules/
            }
        ]
    },
    plugins: [
        new ExtractTextPlugin('[name].[contenthash:8].css'),
    ]
}
  • 需要在plugins里加入插件name: chunk名字 contenthash:8: 根据内容生成hash值取前8位
  • 修改loader配置下的use: fallback: 兼容方案

这样就实现了js,html,css的打包,那么再来看看一些常用的loader:

  • babel-loader: 用babel转换代码
  • url-loader: 依赖于file-loader,把图片转换成base64嵌入html,如果超出一定阈值则交给file-loader
   rules: [
         // 处理js
         {
            test: /\.js?$/,
            exclude: /node_modules/,
            use: ['babel-loader']
        },
        // 处理图片
        {
            test: /\.(png|jpg|gif|ttf|eot|woff(2)?)(\?[=a-z0-9]+)?$/,
            use: [{
                loader: 'url-loader',
                options: {
                    query: {
                        // 阈值 单位byte
                        limit: '8192',
                        name: 'images/[name]_[hash:7].[ext]',
                    }
                }
            }]
        },
    ]

babel的配置建议在根目录下新建一个.babelrc文件

{
    "presets": [
        "env",
        "stage-0", 
        "react"
    ],
    "plugins": [
        "transform-runtime",
        "transform-decorators-legacy",
        "add-module-exports"
    ]
}
  • presets: 预设, 一个预设包含多个插件 起到方便作用 不用引用多个插件
  • env: 只转换新的句法,例如const let => ..等 不转换 Iterator、Generator、Set、Maps、Proxy、Reflect、Symbol、Promise、Object.assign。
  • stage-0: es7提案转码规则 有 0 1 2 3 阶段 0包含 1 2 3里面的所有
  • react: 转换react jsx语法
  • plugins: 插件 可以自己开发插件 转换代码(依赖于ast抽象语法数)
  • transform-runtime: 转换新语法,自动引入polyfill插件,另外可以避免污染全局变量
  • transform-decorators-legacy: 支持装饰器
  • add-module-exports: 转译export default {}; 添加上module.exports = exports.default 支持commonjs

因为我们在文件名中加入hash值,打包多次后dist目录变得非常多文件,没有删除或覆盖,这里可以引入一个插件,在打包前自动删除dist目录,保证dist目录下是当前打包后的文件:

plugins: [
    new CleanWebpackPlugin(
        // 需要删除的文件夹或文件
        [path.join(__dirname, './dist/*.*')],
        {
            // root目录
            root: path.join(__dirname, './')
        }
    ),
]

指定extension之后可以不用在require或是import的时候加文件扩展名,会依次尝试添加扩展名进行匹配:

resolve: {
    extensions: ['.js', '.jsx', '.scss', '.json'],
},

3. 优化实战 高级装备

天下武功唯快不破,优化方案千千万万,各取所需吧。

提出公共的JS文件

webpack4中废弃了webpack.optimize.CommonsChunkPlugin插件,用新的配置项替代

module.exports = {
    entry: './src/index.js',
    output: {
        path: path.join(__dirname, './dist'),
        filename: 'main.js',
    },
    optimization: {
        splitChunks: {
            cacheGroups: {
                commons: {
                    chunks: 'initial',
                    minChunks: 2,
                    maxInitialRequests: 5,
                    minSize: 2,
                    name: 'common'
                }
            }
        }
    },
}

把多次import的文件打包成一个单独的common.js

HappyPack

在webpack运行在node中打包的时候是单线程去一件一件事情的做,HappyPack可以开启多个子进程去并发执行,子进程处理完后把结果交给主进程

 npm i happypack -D

需要改造一下loader配置,此loader用子进程去处理

const HappyPack = require('happypack');
module.exports = {
    entry: './src/index.js',
    output: {
        path: path.join(__dirname, './dist'),
        filename: 'main.js',
    },
    module: {
        rules: [
            {
                test: /\.jsx?$/,
                exclude: /node_modules/,
                use: 'happypack/loader?id=babel',
            },
        ]
    },
    plugins: [
        new HappyPack({
            id: 'babel',
            threads: 4,
            loaders: ['babel-loader']
        }),
    ]
}
  • id: id值,与loader配置项对应
  • threads: 配置多少个子进程
  • loaders: 用什么loader处理
  • 具体文档

作用域提升

如果你的项目是用ES2015的模块语法,并且webpack3+,那么建议启用这一插件,把所有的模块放到一个函数里,减少了函数声明,文件体积变小,函数作用域变少。

module.exports = {
    entry: './src/index.js',
    output: {
        path: path.join(__dirname, './dist'),
        filename: 'main.js',
    },
    plugins: [
        new webpack.optimize.ModuleConcatenationPlugin(),
    ]
}

提取第三方库

方便长期缓存第三方的库,新建一个入口,把第三方库作为一个chunk,生成vendor.js

module.exports = {
    entry: {
        main: './src/index.js',
        vendor: ['react', 'react-dom'],
    },
}

DLL动态链接

第三库不是经常更新,打包的时候希望分开打包,来提升打包速度。打包dll需要新建一个webpack配置文件,在打包dll的时候,webpack做一个索引,写在manifest文件中。然后打包项目文件时只需要读取manifest文件。

webpack.vendor.js

const webpack = require('webpack');
const path = require('path');

module.exports = {
    entry: {
        vendor: ['react', 'react-dom'],
    },
    output: {
        path: path.join(__dirname, './dist'),
        filename: 'dll/[name]_dll.js',
        library: '_dll_[name]',
    },
    plugins: [
        new webpack.DllPlugin({
            path: path.join(__dirname, './dist/dll', 'manifest.json'),
            name: '_dll_[name]',
        }),
    ]
};

path: manifest文件的输出路径 name: dll暴露的对象名,要跟output.library保持一致 context: 解析包路径的上下文,这个要跟接下来配置的dll user一致

webpack.config.js

module.exports = {
    entry: {
        main: './src/index.js',
        vendor: ['react', 'react-dom'],
    },
    plugins: [
        new webpack.DllReferencePlugin({
            manifest: path.join(__dirname, './dist/dll', 'manifest.json')
        })
    ]
}

html

<script src="vendor_dll.js"></script>

4. 线上和线下

在生产环境和开发环境其实我们的配置是存在相同点,和不同点的,为了处理这个问题,会创建3个文件:

  • webpack.base.js: 共同的配置
  • webpack.dev.js: 在开发环境下的配置
  • webpack.prod.js: 在生产环境的配置

通过webpack-merge去做配置的合并,比如:

开发环境

const path = require('path');
const webpack = require('webpack');
const merge = require('webpack-merge');
const base = require('./webpack.base');

const dev = {
    devServer: {
        contentBase: path.join(__dirname, '../dist'),
        port: 8080,
        host: 'localhost',
        overlay: true,
        compress: true,
        open:true,
        hot: true,
        inline: true,
        progress: true,
    },
    devtool: 'inline-source-map',
    plugins: [
        new webpack.HotModuleReplacementPlugin(),
        new webpack.NamedModulesPlugin(),
    ]
}
module.exports = merge(base, dev);

开发环境中我们可以启动一个devServer静态文件服务器,预览我们的项目,引入base配置文件,用merge去合并配置。

  • contentBase: 静态文件地址
  • port: 端口号
  • host: 主机
  • overlay: 如果出错,则在浏览器中显示出错误
  • compress: 服务器返回浏览器的时候是否启动gzip压缩
  • open: 打包完成自动打开浏览器
  • hot: 模块热替换 需要webpack.HotModuleReplacementPlugin插件
  • inline: 实时构建
  • progress: 显示打包进度
  • devtool: 生成代码映射,查看编译前代码,利于找bug
  • webpack.NamedModulesPlugin: 显示模块的相对路径

生产环境

再来看看生产环境最重要的代码压缩,混淆:

const path = require('path');
const merge = require('webpack-merge');
const WebpackParallelUglifyPlugin = require('webpack-parallel-uglify-plugin');
const base = require('./webpack.base');

const prod = {
    plugins: [
        // 文档: https://github.com/gdborton/webpack-parallel-uglify-plugin
        new WebpackParallelUglifyPlugin(
            {
                uglifyJS: {
                    mangle: false,
                    output: {
                        beautify: false,
                        comments: false
                    },
                    compress: {
                        warnings: false,
                        drop_console: true,
                        collapse_vars: true,
                        reduce_vars: true
                    }
                }
            }
        ),
    ]
}
module.exports = merge(base, prod);

webpack-parallel-uglify-plugin可以并行压缩代码,提升打包效率

uglifyJS配置:

  • mangle: 是否混淆代码
  • output.beautify: 代码压缩成一行 true为不压缩 false压缩
  • output.comments: 去掉注释
  • compress.warnings: 在删除没用到代码时 不输出警告
  • compress.drop_console: 删除console
  • compress.collapse_vars: 把定义一次的变量,直接使用,取消定义变量
  • compress.reduce_vars: 合并多次用到的值,定义成变量
  • 具体文档

5. 成为头号玩家

想要成为头号玩家,玩转配置可不行,当然还要做一些loader和plugin的开发,去为项目做一些优化,解决痛点。

loader

loader是一个模块导出函数,在正则匹配成功的时候调用,webpack把文件数组传入进来,在this上下文可以访问loader API

  • this.context: 当前处理文件的所在目录,假如当前 Loader 处理的文件是 /src/main.js,则 this.context 就等于 /src。
  • this.resource: 当前处理文件的完整请求路径,包括 querystring,例如 /src/main.js?name=1。
  • this.resourcePath: 当前处理文件的路径,例如 /src/main.js。
  • this.resourceQuery: 当前处理文件的 querystring。
  • this.target: 等于 Webpack 配置中的 Target
  • this.loadModule: 但 Loader 在处理一个文件时,如果依赖其它文件的处理结果才能得出当前文件的结果时, 就可以通过 - - - this.loadModule(request: string, callback: function(err, source, sourceMap, module)) 去获得 request 对应文件的处理结果。
  • this.resolve: 像 require 语句一样获得指定文件的完整路径,使用方法为 resolve(context: string, request: string, callback: function(err, result: string))。
  • this.addDependency: 给当前处理文件添加其依赖的文件,以便再其依赖的文件发生变化时,会重新调用 Loader 处理该文件。使用方法为 addDependency(file: string)。
  • this.addContextDependency: 和 addDependency 类似,但 addContextDependency 是把整个目录加入到当前正在处理文件的依赖中。使用方法为 addContextDependency(directory: string)。
  • this.clearDependencies: 清除当前正在处理文件的所有依赖,使用方法为 clearDependencies()。
  • this.emitFile: 输出一个文件,使用方法为 emitFile(name: string, content: Buffer|string, sourceMap: {...})。
  • this.async: 返回一个回调函数,用于异步执行。

下面来看看less-loaderstyle-loader如何实现:

let less = require('less');
module.exports = function (source) {
    const callback = this.async();
    less.render(source, (err, result) => {
        callback(err, result.css);
    });
}
module.exports = function (source) {
    let script = (`
      let style = document.createElement("style");
      style.innerText = ${JSON.stringify(source)};
      document.head.appendChild(style);
   `);
    return script;
}

plugin

webpack整个构建流程有许多钩子,开发者可以在指定的阶段加入自己的行为到webpack构建流程中。插件由以下构成:

  • 一个 JavaScript 命名函数。
  • 在插件函数的 prototype 上定义一个 apply 方法。
  • 指定一个绑定到 webpack 自身的事件钩子。
  • 处理 webpack 内部实例的特定数据。
  • 功能完成后调用 webpack 提供的回调。

整个webpack流程由compiler和compilation构成,compiler只会创建一次,compilation如果开起了watch文件变化,那么会多次生成compilation. 那么这2个类下面生成了需要事件钩子

compiler hooks 文档 compilation hooks 文档

写一个小插件,生成所有打包的文件列表(webpack4不推荐使用compiler.plugin来注册插件,webpack5将不支持):

class FileListPlugin{
    constructor(options) {
        this.options = options;
    }
    apply(compiler) {
        compiler.hooks.emit.tap('FileListPlugin',function (compilation) {
            let fileList = 'filelist:\n\n';
            for (let filename in compilation.assets) {
                fileList += ('- '+filename+'\n');
            }
            compilation.assets['filelist.md']={
                source() {
                    return fileList;
                },
                size() {
                    return fileList.length
                }
            }
        });
    }
}
module.exports = FileListPlugin;

6. 最后

都读在这里了,还不点个赞吗。

谢谢你阅读我的文章