webpack系统学习(十四)如何编写一个loader

1. 创建一个简单的loader

所谓 loader 只是一个导出为函数的 JavaScript 模块。loader runner 会调用这个函数,然后把上一个 loader 产生的结果或者资源文件(resource file)传入进去。函数的 this 上下文将由 webpack 填充。
假设我们现在有一个index.js:

//index.js
console.log("hello world")

在不对js文件进行额外处理时对其进行打包,得到/dist/main.js

//main.js
...
/***/ (function(module, exports) {
eval("console.log(\"hello world\")\n\n//# sourceURL=webpack:///./src/index.js?");
/***/ })
...

我们希望借助loader使js文件中要输出的hello world转换为hello js,像上文所说,一个loader就是一个导出为函数的JavaScript模块。
创建replaceLoader.js,现在的文件目录结构:
webpack系统学习(十四)如何编写一个loader_第1张图片

//replaceLoader.js
//使用声明式函数而不是箭头函数,因为在该函数中我们需要使用this
module.exports = function (source){
  return source.replace('world','js')
}

然后我们使用replaceLoader对js文件进行处理。
修改webpack.config.js:

...
	module: {
		rules: [{
			test: /\.js/,
			use: [
        path.resolve(__dirname,'./loader/replaceLoader')
			]
		}]
	}
...

重新打包,查看此时生成的 main.js

...
/***/ (function(module, exports) {
eval("console.log(\"hello js\")\n\n//# sourceURL=webpack:///./src/index.js?");
/***/ })
...

不难发现,此时运行main.js,控制台的输出就从hello world转换成了hello js
这个replaceLoader就是一个简单的loader,它的作用就是在打包js文件时,将js文件中的world替换成js

2. 设置resolveLoader简化自定义loader的加载路径

修改webpack.config.js:

...
	resolveLoader: {
		modules: ['node_modules', './loader']
	}
...

这样设置resolveLoader,webpack在打包文件时,如果需要使用loader,它将首先在node_modules中查找该loader,如果找不到,就会在./loader文件夹下面查找。

3. 向Loader传入参数

我们在使用Loader时,可以向其传递一些参数,比如我们在使用url-loader 时:

	{
      test:/\.(png|jpg|gif)$/,
      use:{
        loader: 'url-loader',
        options: {
          name: '[name].[ext]',//placeholder占位符
          outputPath:'image/',
          limit: 2048 //2kb
        }
      }
	}

我们为url-loader设置了options,这里面的配置是如何被url-loader获取并使用呢?
查看webpack官网上Loader APIoptions的使用,我们发现:

this.query
如果这个 loader 配置了 options 对象的话,this.query 就指向这个 option 对象。
如果 loader 中没有 options,而是以 query 字符串作为参数调用时,this.query 就是一个以 ? 开头的字符串。
使用 loader-utils 中提供的 getOptions 方法 来提取给定 loader 的 option。

修改webpack.config.js:

...
{
	test: /\.js/,
		use: [
		        {
		          loader: 'replaceLoader',
		          options: {
		            from: 'world',
		            to: 'js'
		          }
		        }
		]
}
...

我们现在修改replaceLoader,查看一下this.query的内容:

//使用声明式函数而不是箭头函数,因为在该函数中我们需要使用this
module.exports = function (source){
  console.log(this.query)
  return source.replace('world','js')
}

打包时的输出:

{ from: 'world', to: 'js' }
...

所以this.query就是一个对象,里面包含了我们出入的options信息。
修改replaceLoader,使用options传入的配置信息。

//使用声明式函数而不是箭头函数,因为在该函数中我们需要使用this
module.exports = function (source){
  return source.replace(this.query.from,this.query.to)
}

但是我们注意到,webpack官网建议我们使用 loader-utils 中提供的 getOptions 方法 来提取给定 loader 的 option。

npm i loader-util -s

修改replaceLoader.js:

const loaderUtils = require('loader-utils')
module.exports = function (source){
  const options = loaderUtils.getOptions(this)
  return source.replace(options.from, options.to)
}

在返回转换后的content时,可以使用this.callback()this.callback
方法更加灵活,因为它允许传递多个参数,而不仅仅是content

this.callback(
  err: Error | null,
  content: string | Buffer,
  sourceMap?: SourceMap,
  meta?: any
);

第一个参数必须是 Error 或者 null
第二个参数是一个 string 或者 Buffer。
可选的:第三个参数必须是一个可以被这个模块解析的 source map。
可选的:第四个选项,会被 webpack 忽略,可以是任何东西(例如一些元数据)。

使用this.callback 返回处理结果,修改replaceLoader.js

const loaderUtils = require('loader-utils')

module.exports = function (source){
  const options = loaderUtils.getOptions(this)
  this.callback(null,source.replace(options.from, options.to))
  return
}

4. 在Loader中使用异步处理

异步处理需要使用this.async 告诉 loader-runner 这个 loader 将会异步地回调。返回 this.callback。

新创建一个replaceLoaderAsync.js

//replaceLoaderAsync和replaceLoader的异步版本
const loaderUtils = require('loader-utils')

module.exports = function (source){
  const options = loaderUtils.getOptions(this)
  const callBack = this.async()

  setTimeout(()=>{
   const result = source.replace(options.from, options.to)
   callBack(null, result)
  },5000)
}

我们先不使用这个异步loader,查看打包信息:

...
Time: 124ms
...

打包时间为124ms。
现在我们更改webpack.config.js,使用replaceLoaderAsync

{
			test: /\.js/,
			use: [
        {
          loader: 'replaceLoader',
          options: {
            from: 'world',
            to: 'js'
          }
        },{
          loader: 'replaceLoaderAsync',
          options: {
            from: 'hello',
            to: 'hi'
          }
        }
			]
		}

现在再次打包:

...
Time: 5254ms
...

打包时间增加到了5254ms。

同时查看打包后生成的main.js:

...
/***/ (function(module, exports) {

eval("console.log(\"hi js\")\n\n//# sourceURL=webpack:///./src/index.js?");

/***/ })
...

两个loader都生效了。

结语:

loader中还有很多配置,更多配置可以前往webpack官网查看。

你可能感兴趣的:(webpack,webpack,javascript,学习)