所谓 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,现在的文件目录结构:
//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
。
修改webpack.config.js:
...
resolveLoader: {
modules: ['node_modules', './loader']
}
...
这样设置resolveLoader,webpack在打包文件时,如果需要使用loader,它将首先在node_modules
中查找该loader,如果找不到,就会在./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 API对options的使用,我们发现:
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
}
异步处理需要使用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官网查看。