webpack学习-plugin、loader

164 阅读3分钟

1、plugin

1.1 compiler

Compiler 模块是 webpack 的支柱引擎,它通过 CLI 或 Node API 传递的所有选项,创建出一个 compilation 实例

相关钩子:

以下生命周期钩子函数,是由 compiler 暴露,可以通过如下方式访问:

compiler.hooks.someHook.tap(...)

取决于不同的钩子类型,也可以在某些钩子上访问 tapAsync 和 tapPromise。

1.2 compilation

Compilation 模块会被 Compiler 用来创建新的编译(或新的构建)。compilation 实例能够访问所有的模块和它们的依赖(大部分是循环依赖)。它会对应用程序的依赖图中所有模块进行字面上的编译(literal compilation)。在编译阶段,模块会被加载(loaded)、封存(sealed)、优化(optimized)、分块(chunked)、哈希(hashed)和重新创建(restored)。

Compilation 类扩展(extend)自 Tapable,并提供了以下生命周期钩子。可以按照 compiler 钩子的相同方式,调用 tap:

compilation.hooks.someHook.tap(...)

和 compiler 用法相同,取决于不同的钩子类型,也可以在某些钩子上访问 tapAsync 和 tapPromise。

compiler.hooks.emit.tapAsync(pluginName, (compilation, callback) => {
  ...一系列疯狂操作之后
callback();
}

compiler.hooks.emit.tapPromise(pluginName, (compilation) => {
  ...一系列疯狂操作之后
return new Promise(resolve => {
  // 执行完你的任务后
  resolve();
});
}

1.3 Compiler 和 Compilation

在插件开发中最重要的两个资源就是 compiler 和 compilation 对象。理解它们的角色是扩展 webpack 引擎重要的第一步。

compiler 对象代表了完整的 webpack 环境配置。这个对象在启动 webpack 时被一次性建立,并配置好所有可操作的设置,包括 options,loader 和 plugin。当在 webpack 环境中应用一个插件时,插件将收到此 compiler 对象的引用。可以使用它来访问 webpack 的主环境。

compilation 对象代表了一次资源版本构建。当运行 webpack 开发环境中间件时,每当检测到一个文件变化,就会创建一个新的 compilation,从而生成一组新的编译资源。一个 compilation 对象表现了当前的模块资源、编译生成资源、变化的文件、以及被跟踪依赖的状态信息。compilation 对象也提供了很多关键时机的回调,以供插件做自定义处理时选择使用。

1.4 手写plugin例子

const replacePathVariables = (path, data, assetInfo) => {
  ...一系列疯狂操作之后
  return path;
};

const plugin = "TemplatedPathPlugin";

class TemplatedPathPlugin {
  apply(compiler) {
    compiler.hooks.compilation.tap(plugin, compilation => {
        compilation.hooks.assetPath.tap(plugin, replacePathVariables);
    });
  }
}

module.exports = TemplatedPathPlugin;

2、loader

2.1使用loader-utils,schema-utils

loader-utils, schema-utils是webpack的loader工具库,有很多便捷的方法可以调用。


const { getOptions,stringifyRequest, parseQuery  } = require("loader-utils");
const validateOptions = require("schema-utils");
const schema = {
    type: "object",
    properties: {
        test: {
            type: "string"
        }
    }
};
module.exports = function(source) {
    //getOptions 用于在loader里获取传入的options,返回的是对象值。
    const options = getOptions(this);
    
    // stringifyRequest转换路径,避免require()或impot时使用的绝对路径
    stringifyRequest(this, "./test.js"); //   Result =>  "\"./test.js\""
    
    //parseQuery获取query参数的,这个很简单就不说啦
    parseQuery('?name=kev&age=14') // Result => {name: 'kev', age: '14'}
    
    //验证参数的类型是否正确。
    validateOptions(schema, options, "loader");
};

2.2编写自己的loader

设计稿在pc端的尺寸是750px,部分文件要转化成移动端也可以适配。

定义一个移动端适配的样式文件,如:**.mobile

//webapck.config.js
const path = require("path");
 
module.exports = {
    entry: "./src",
    output: {
        path: path.resolve(__dirname, "dist"),
        filename: "package.js"
    },
    mode: "production",
    module: {
    	rules: [{
          test: /\.mobile$/,
          use: [
            'style-loader',
            'css-loader',
            {
               loader: './mobile-css-loader',
              	options: {
                  width: 750,
              	}
            }
          ]
        }]
    }

};

第一步先验证options是否符合类型,

第二步获取参数,然后替换传入的资源文件字符串。

//loader.js
const { getOptions } = require("loader-utils");
const validateOptions = require("schema-utils");
 
const schema = {
    type: "object",
    properties: {
        width: {
          type: 'number',
        }
    }
};
 
module.exports = function(source) {
 
    const options = getOptions(this);
    validateOptions(schema, options, 'loader');
 
    validate(schema, options, {
        name: 'px2vw Loader',
        baseDataPath: 'options',
    });

    const px2vw = px => px / options.width * 100 + 'vw';
    return source.replace(/(\d+)px/g, (_, px) => px2vw(px));
};