Site Overlay

前端工程化-Webpack-plugin

目的

了解webpack设计思路,手写一个压缩css plugin

什么是Webpack plugin?

webpack只能识别js,当打包非js文件时,就需要转化器loader对这些文件进行处理。loader所做的事是简单,单一的,loader做不了的事情就需要plugin进行“加工”。所以plugin所做的事就是负责扩展webpack的功能。

如何编写一个plugin?

  • 非匿名 JavaScript 函数或 JavaScript 类
  • 在其原型中定义一个apply方法
  • 指定要运用的钩子函数
  • 操作 webpack 内部实例特定数据
  • 在功能完成后调用 webpack 提供的回调
class HelloWorldPlugin {
  apply(compiler) {
    compiler.hooks.done.tap('Hello World Plugin', (
      stats /* stats is passed as an argument when done hook is tapped.  */
    ) => {
      console.log('Hello World!');
    });
  }
}

module.exports = HelloWorldPlugin;
// webpack.config.js
var HelloWorldPlugin = require('hello-world');

module.exports = {
  // ... configuration settings here ...
  plugins: [new HelloWorldPlugin({ options: true })],
};

? 以上是官方最简单的plugin例子,在 compilation 完成时打印出hello world字符串

compiler 和 compilation

上面例子中出现了 Compiler 和 Compilation,它们是 Plugin 和 Webpack 之间的桥梁,需要了解这两个对象的区别~

  • Compiler(负责编译):Compiler 对象包含了当前运行Webpack的配置,包括entry、output、loaders等配置,这个对象在启动Webpack时被实例化,而且是全局唯一的。Plugin可以通过该对象获取到Webpack的配置信息进行处理。
  • Compilation(负责创建bundles):Compilation对象代表了一次资源版本构建。当webpack 以开发模式运行时,每当检测到一个文件变化,就会创建一个新的 compilation,其中包含当前的模块资源、编译生成资源、变化的文件等。

简单来说,Compiler 代表了整个 Webpack 从启动到关闭的生命周期,而 Compilation 只是代表了一次新的编译,只要文件有改动,compilation就会被重新创建。

手写一个压缩css plugin

  • 需要借助mini-css-extract-plugin css提取插件辅助完成
  • 参考css-minimizer-webpack-plugin插件源码进行编写

未使用压缩css plugin时dist中的main.css

f81eca1d-5903-4ca4-a901-f3cb41fab097

使用plugin后的main.css
898d3a0d-c17a-4f43-a5c1-20e2a5d02dfe

最后的最后附上代码~

// ? webpack.config.js
const path = require('path');
const MinifyCssPlugin = require('./MinifyCssPlugin'); // 插件引用
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module.exports = {
  entry: './index.js',
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [MiniCssExtractPlugin.loader, 'css-loader'],
      }
    ],
  },
  plugins: [
    new MinifyCssPlugin(),
    new MiniCssExtractPlugin(),
  ],
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist'),
  },
};
// 手写的插件
const postcss = require('postcss');
const cssnano = require('cssnano');

async function cssnaoMinify({ input, name }) {
  const result = await postcss([cssnano({
    preset: 'default',
  })]).process(
    input,
    {
      to: name,
      from: name,
    }
  );

  return {
    code: result.css
  };
}

class MinifyCssPlugin {
  constructor() {

  }

  apply(compiler) {
    compiler.hooks.compilation.tap('MinifyCssPlugin', (compilation) => {
      compilation.hooks.processAssets.tap('MinifyCssPlugin', async (assets) => {
        const list = Object.keys(assets).map((name) => {
          const { info, source }  = compilation.getAsset(name);
          return {
            name,
            info,
            source,
          };
        }).filter((e) => e.name.includes('.css'));

        const { RawSource } = compiler.webpack.sources;
        for (const asset of list) {
          const { name, source } = asset;
          const { source: input } = source.sourceAndMap();
          const options = {
            name,
            input,
          }

          const output = await cssnaoMinify(options);
          output.source = new RawSource(output.code);

          const newInfo = { minimized: true };

          compilation.updateAsset(name, output.source, newInfo);
        }
      })
    })
  }
}

module.exports = MinifyCssPlugin;