如何⾃⼰编写一个Loader

如何⾃⼰编写一个Loader

⾃己编写一个Loader的过程是比较简单的,

Loader就是⼀个函数,声明式函数,不能⽤用箭头函数

拿到源代码,作进一步的修饰处理,再返回处理后的源码就可以了

简单案例

创建一个替换源码中字符串的loader

//index.js

console.log("hello world");

//replaceLoader.js

module.exports = function(source) {  

console.log(source, this, this.query);  

return source.replace(world,'大家好')

 };

//需要⽤声明式函数,因为要上到上下文的this,用到this的数据,该函数接受一个参数

在配置文件中使用loader

 //需要使用node核心模块path来处理路径

const path = require('path');

......

 module: {    

rules: [      

{        

test: /.js$/,        

use: path.resolve(__dirname, "./loader/replaceLoader.js")      

}    

]  

},

如何给loader配置参数,loader如何接受参数?

² this.query

² loader-utils

² 

//需要使用node核⼼心模块path来处理路径

const path = require('path'); 

module: {    

rules: [      

{        

test: /.js$/,        

use: path.resolve(__dirname, "./loader/replaceLoader.js")      

}   

 ]  

},

//webpack.config.js

.........

module: {    

rules: [      

{        

test: /.js$/,        

use: [          

{            

loader: path.resolve(__dirname, "./loader/replaceLoader.js"),            

options: {              

name: "你好吗"            

}          

}        

]      

}    

]  

},    

//replaceLoader.js

//方式一: this.query获取参数

module.exports = function(source) {  

//this.query 通过this.query来接受配置文件传递进来的参数

   //return source.replace("world", this.query.name);   

return source.replace("world", options.name);

}

//方式二: loader-utils获取参数,官方推荐

const loaderUtils = require("loader-utils");//官方推荐处理loader,query的⼯具

module.exports = function(source) {  

//this.query 通过this.query来接受配置文件传递进来的参数

const options = loaderUtils.getOptions(this);  

return source.replace("world", options.name);

}

this.callback :如何返回多个信息,不止是处理好的源码呢,可以使用this.callback来处理

 //replaceLoader.js

const loaderUtils = require("loader-utils");//官⽅方推荐处理理loader,query的⼯工具

module.exports = function(source) {  

const options = loaderUtils.getOptions(this);  

const result = source.replace("world", options.name);  

this.callback(null, result);

};

//this.callback(  

err: Error | null,  

content: string | Buffer,  

sourceMap?: SourceMap,  

meta?: any );

this.async:如果loader⾥⾯有异步的事情要怎么处理理呢

我们使⽤this.async来处理,他会返回this.callback

            

//replaceLoader.js

const loaderUtils = require("loader-utils");//官⽅方推荐处理理loader,query的⼯工具

module.exports = function(source) {  

const options = loaderUtils.getOptions(this);  

const result = source.replace("world", options.name);  

this.callback(null, result);

};

//this.callback(  

err: Error | null,  

content: string | Buffer,  

sourceMap?: SourceMap,  

meta?: any

);

const loaderUtils = require("loader-utils");

module.exports = function(source) {  

const options = loaderUtils.getOptions(this);  

setTimeout(() => {    

const result = source.replace("world", options.name);

return result;  

}, 1000);

};

//先⽤setTimeout处理下试试,发现会报错

我们使⽤用this.async来处理理,他会返回this.callback

const loaderUtils = require("loader-utils");

module.exports = function(source) {  

const options = loaderUtils.getOptions(this);

//定义一个异步处理,告诉webpack,这个loader⾥有异步事件,在里⾯调⽤下这个异步

//callback 就是 this.callback 注意参数的使⽤  

const callback = this.async();  

setTimeout(() => {    

const result = source.replace("world", options.name);    

callback(null, result);  

}, 3000);

};

多个loader的使⽤

//replaceLoader.js

module.exports = function(source) {  

return source.replace("软谋课堂", "word");

};

//replaceLoaderAsync.js

const loaderUtils = require("loader-utils");

module.exports = function(source) {  

const options = loaderUtils.getOptions(this);

  //定义⼀一个异步处理理,告诉webpack,这个loader⾥里里有异步事件,在⾥里里⾯面调⽤用下这个异步  

const callback = this.async();  

setTimeout(() => {    

const result = source.replace("world", options.name);    

callback(null, result);  

}, 3000); };

//webpack.config.js

module: {    

rules: [      

{        

test: /.js$/,        

use: [          

path.resolve(__dirname, "./loader/replaceLoader.js"),          

{            

loader: path.resolve(__dirname, "./loader/replaceLoaderAsync.js"),            

options: {              

name: "你好吗"            

}          

}        

]        

// use: [path.resolve(__dirname, "./loader/replaceLoader.js")]      

}   

 ]

顺序,自下⽽上,自右到左

处理loader的路径问题

 resolveLoader: {    

modules: ["node_modules", "./loader"]  

},  

module: {    

rules: [      

{        

test: /.js$/,        

use: [          

"replaceLoader",          

{            

loader: "replaceLoaderAsync",            

options: {              

name: "软谋课堂"            

}          

}        

]        

}    

]  

},

..............

参考:loader API

https://webpack.js.org/api/loaders

如何⾃己编写一个Plugins

Plugin: 开始打包,在某个时刻,帮助我们处理一些什么事情的机制 plugin要比loader稍微复杂一些,在webpack的源码中,用plugin的机制还是占有非常大的场景,可以说pluginwebpack的灵魂

l 设计模式

l 事件驱动

l 发布订阅

 

plugin是一个类,⾥面包含一个apply函数,接受一个参数,compiler

 

案例:

创建copyright-webpack-plugin.js

  1. plugin实际是一个类(构造函数),通过在plugins配置中实例化进行调用
  2. 它在原型对象上指定了一个apply方法,入参是compiler对象
  3. 指定一个事件钩子,并调用内部提供的API
  4. 完成操作后,调用webpack 提供的callback方法

class CopyrightWebpackPlugin {  

constructor() {  }

    // 将 `apply` 定义为其原型方法,此方法以 compiler 作为参数

apply(compiler) {  }

 }

module.exports = CopyrightWebpackPlugin;

配置文件里使⽤

const CopyrightWebpackPlugin = require("./plugin/copyright-webpack-plugin");

plugins: [new CopyrightWebpackPlugin()]

如何传递参数

//webpack配置⽂文件

plugins: [    

new CopyrightWebpackPlugin({      

name: "你好吗"    

})  

]  

//copyright-webpack-plugin.js

class CopyrightWebpackPlugin {  

constructor(options) {    

//接受参数    

console.log(options);  

}

  apply(compiler) { }

}

module.exports = CopyrightWebpackPlugin;

配置plugin在什么时刻进⾏

class CopyrightWebpackPlugin {  

constructor(options) {    

// console.log(options);  

}

  apply(compiler) {    

//hooks.emit 定义在某个时刻

// 指定要附加到的事件钩子函数

   

compiler.hooks.emit.tapAsync(      

"CopyrightWebpackPlugin",      

(compilation, cb) => {     

// 使用 webpack 提供的 plugin API 操作构建结果   

compilation.assets["copyright.txt"] = {          

source: function() {            

return "hello copy";          

},          

size: function() {            

return 20;          

}        

};        

cb();      

} );        

//同步的写法    

//compiler.hooks.compile.tap("CopyrightWebpackPlugin", compilation => {   

 //  console.log("开始了了");    

//});

 }

}

module.exports = CopyrightWebpackPlugin;

参考:compiler-hooks

https://webpack.js.org/api/compiler-hooks

实现插件的背景知识

由上面的步骤可知,插件功能的实现主要依赖于compilercomplation对象,而两者都是继承自Tapable对象。它暴露三种注册监听的方法Tapable对象主要是9种钩子:

const {

    SyncHook,      

    SyncBailHook,  

    SyncWaterfallHook,

    SyncLoopHook,

    AsyncParallelHook,

    AsyncParallelBailHook,

    AsyncSeriesHook,

    AsyncSeriesBailHook,

    AsyncSeriesWaterfallHook

 } = require("tapable");

其中同步四种,异步并行两种,异步串行3种。

同步钩子进行同步操作;异步钩子中进行异步操作。

compiler和compilation中的钩子都是来自9种钩子。钩子的工作机制类似于浏览器的事件监听。

1)生成的钩子可以注册监听事件,其中同步钩子通过tap方法监听,异步钩子通过tapAsync(+回调函数)和tapPromise(+返回promise)进行监听。

2)还可以进行拦截,通过intercept方法。

3)对于监听事件的触发,同步钩子通过call方法; 异步钩子通过callAsync方法和promise

  

原文地址:https://www.cnblogs.com/laneyfu/p/12269965.html