程序员

webpack插件原理 - 手写插件清除无用css

2020-05-24  本文已影响0人  进击的小短腿子

webpack在打包代码的过程中广播了许多事件,并且允许开发者通过订阅这些事件实现一些自己的操作进而影响输出结果。它的插件式设计,就是基于这种朴素的原理。

怎样订阅一个webpack事件 ? 这么来

compiler.hooks.entryOption.tap('MyPlugin', (context, entry) => {
    // 做点啥
});

entryOption 是诸多事件中的一个,大家扫一眼其他事件:

其中一个比较重要的事件是compilation ,只有它又细分出其他子事件

插件系统简易模型

webpack plugin system

啥是Tapable?

tapable是一个处理事件定义,订阅,广播的小型库, 是webpack插件系统的核心。

tapable五毛钱教程
const { SynHook } = require('tapable')

class Cat {
    constructor() {
        this.hooks = {
            poop: new SynHook() // 定义事件 - 朕要便便
        }
    }

    poop() {
        this.hooks.poop.call() // 广播便便事件
    }
}

const myCat = new Cat()
// 订阅事件 poop
myCat.hooks.poop.tap("myPlugin", () => {
    console.warn('有便便')
})

// 然后有一天午后
myCat.poop()

手写webpack插件

主要步骤
  1. 订阅compiler的compilation事件
  2. 订阅compilation的optimizeAssets子事件
  3. 获取css资源,js资源
  4. 利用purgecss库优化css
  5. 用优化后的css资源覆盖原始资源
代码

WebpackPluginPurgeCss.js

const { PurgeCSS, defaultOptions } = require('purgecss')
const { ConcatSource } = require('webpack-sources')
const pluginName = 'webpack-plugin-purgecss'

class WebpackPluginPurgeCss {
    constructor(options = {}) {
        this.options = options
    }

    apply(compiler) {
        // 1. 订阅compiler的compilation事件
        compiler.hooks.compilation.tap(pluginName, (compilation) => {
            // 2. 订阅compilation的optimizeAssets子事件
            compilation.hooks.optimizeAssets.tapPromise(pluginName, () => {
                return this.runPluginHook(compilation)
            })
        })
    }

    async runPluginHook(compilation) {
        // 3. 获取css资源,js资源
        const jsAssets = [], cssAssets = []
        for (const [name, asset] of Object.entries(compilation.assets)) {
            if (name.match(/js$/)) {
                jsAssets.push({ name, asset })
            }
            if (name.match(/css$/)) {
                cssAssets.push({ name, asset })
            }
        }

        // 4. 利用purgecss库优化css
        for (const { name, asset } of cssAssets) {
            const options = {
                ...defaultOptions,
                ...this.options,
                content: [
                    {
                        raw: jsAssets.map(i => i.asset.source()).join(';'),
                        extension: 'js'
                    }
                ],
                css: [
                    {
                        raw: asset.source(),
                    },
                ],
            }

            const purgecss = await new PurgeCSS().purge({
                content: options.content,
                css: options.css,
                defaultExtractor: options.defaultExtractor,
                extractors: options.extractors,
                fontFace: options.fontFace,
                keyframes: options.keyframes,
                output: options.output,
                rejected: options.rejected,
                variables: options.variables,
                whitelist: options.whitelist,
                whitelistPatterns: options.whitelistPatterns,
                whitelistPatternsChildren: options.whitelistPatternsChildren,
            })
            const purged = purgecss[0]

            // 5. 用优化后的css资源覆盖原始资源
            compilation.assets[name] = new ConcatSource(purged.css)
        }
    }
}
module.exports = WebpackPluginPurgeCss
使用

需要配合mini-css-extract-plugin使用

const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const PurgeCssPlugin = require('./WebpackPluginPurgeCss')

...

plugins: [
    new MiniCssExtractPlugin({
      filename: '[name].[hash].css',
      chunkFilename: '[id].[hash].css'
    }),
    new PurgeCssPlugin({
      whitelistPatterns: [/myclass/i]
    }),
  ],

硬广

迅速搭建React源码测试环境

上一篇下一篇

猜你喜欢

热点阅读