编写webpack loader
muwoo opened this issue · comments
关于webpack loader
Loader 是支持链式执行的,如处理 sass 文件的 loader,可以由 sass-loader、css-loader、style-loader
组成,由 compiler
对其由右向左或者从下向上执行,第一个 Loader 将会拿到需处理的原内容,上一个 Loader 处理后的结果回传给下一个接着处理,最后的 Loader 将处理后的结果以 String 或 Buffer 的形式返回给 compiler。
{
test: /\.js/,
use: [
'bar-loader',
'foo-loader'
]
}
无状态(Stateless)
虽然loader
支持链式调用,但是请确保 loader 在不同模块转换之间不保存状态。每次运行都应该独立于其他编译模块以及相同模块之前的编译结果。
编写一个 loader
loader 是导出为一个函数的 node 模块。该函数在 loader 转换资源的时候调用。给定的函数将调用 loader API,并通过 this
上下文访问。那我们便可以这样来定义一下最基本的loader:
// 当一个 loader 在资源中使用,这个 loader 只能传入一个参数 - 这个参数是一个包含包含资源文件内容的字符串
module.exports = function(source) {
return source;
};
上面使用返回 return 返回,是因为是同步类的 Loader 且返回的内容唯一,如果你希望将处理后的结果(不止一个)返回给下一个 Loader,那么就需要调用 Webpack 所提供的 API。 一般来说,构建系统都会提供一些特有的 API 供开发者使用。Webpack 也如此,提供了一套 Loader API,可以通过在node module
中使用 this 来调用,如 this.callback(err, value…)
,这个 API 支持返回多个内容的结果给下一个 Loader 。
module.exports = function(source) {
let other = '';
return this.callback(null, source, other);
};
更多
1. 缓存
从提高执行效率上,如何处理利用缓存是极其重要的。 Mac OS 会让内存充分使用、尽量占满来提高交互效率。回到 Webpack,Hot-Replace 以及 React Hot Loader 也充分地利用缓存来提高编译效率。 Webpack Loader 同样可以利用缓存来提高效率,并且只需在一个可缓存的 Loader 上加一句 this.cacheable();
就是这么简单:
module.exports = function(source) {
this.cacheable();
return source;
};
2. 异步
异步并不陌生,当一个 Loader 无依赖,可异步的时候我想都应该让它不再阻塞地去异步。在一个异步的模块中,回传时需要调用 Loader API 提供的回调方法 this.async()
,使用起来也很简单:
module.exports = function(source) {
var callback = this.async();
// 做异步的事
doSomeAsyncOperation(content, function(err, result) {
if(err) return callback(err);
callback(null, result);
});
};
3. raw loader
默认的情况,原文件是以 UTF-8 String 的形式传入给 Loader,而在上面有提到的,module 可使用 buffer 的形式进行处理,针对这种情况,只需要设置 module.exports.raw = true;
这样内容将会以 raw Buffer 的形式传入到 loader 中了
module.exports = function(content) {
};
module.exports.raw = true;
4. loader 工具库
充分利用 loader-utils
包。它提供了许多有用的工具,但最常用的一种工具是获取传递给 loader 的选项。schema-utils
包配合 loader-utils
,用于保证 loader 选项,进行与 JSON Schema
结构一致的校验。这里有一个简单使用两者的例子:
const { getOptions } = require('loader-utils');
const validateOptions = require('schema-utils')
const schema = {
type: 'object',
properties: {
test: {
type: 'string'
}
}
}
export default function(source) {
const options = getOptions(this);
validateOptions(schema, options, 'Example Loader');
this.callback(null, source);
}
一些例子
比如编写一个处理Vue组件上的style,把px转成rem:
const { getOptions } = require('loader-utils');
const validateOptions = require('schema-utils')
const schema = {
type: 'object',
properties: {
remUnit: {
type: 'number'
},
forcePxProperty: {
type: 'array'
}
}
}
module.exports = function(source) {
const options = getOptions(this);
validateOptions(schema, options, 'style2rem Loader');
source = source.replace(/<template>(.|\n)*?<\/template>/ig, function (content) {
return content.replace(/style="(.|\n)*?"/ig, function (styleStr) {
let start = 0
return styleStr.replace(/:(.|\n)*?px/ig, function (px, num, end) {
let key = ''
let need = true
for (let i = start; i < end; i++) {
if (styleStr[i] !== ';' && styleStr[i]!==' ') {
key += styleStr[i]
}
}
options.forcePxProperty.forEach((property) => {
if(key.indexOf(property) !== -1) {
need = false
}
})
start = end
return px && need ? `: ${(parseInt(px.substring(1)) / options.remUnit).toFixed(6)}rem` : px
})
})
})
this.cacheable();
this.callback(null, source);
};
然后我们需要添加到webpack loader
中:
module: {
rules: [
{
test: /\.vue$/,
use: [{
loader: "vue-loader",
options: {
preserveWhitespace: false
}
}, {
loader: path.join(__dirname, './style2rem'),
options: {
remUnit: 37.5,
forcePxProperty: ['font-size']
}
}]
}
...
]
...
}