loader是导出为一个函数的node模块。该函数在loader转换资源的时候调用。给定的函数将调用loader API,并通过this上下文访问。
{
test: /\.js$/
use: [
{
loader: path.resolve('path/to/loader.js'),
options: {/* ... */}
}
]
}
const loaderUtils = require('loader-utils');
const validateOptions = require('schema-utils');
module.exports = function (source) {
const options = loaderUtils.getOptions(this);
let schema = {
"type": "object",
"properties": {
"content": {
"type": "string"
}
}
}
validateOptions(schema, options, '参数校验!');
console.log(this.context);
console.log(this.resource);
console.log(this.resourcePath);
console.log(this.resoureQuery);
console.log(this.loadModule);
console.log(this.resolve);
console.log(this.addDependency);
console.log(this.addContextDependency);
console.log(this.clearDependencies);
this.cacheable && this.cacheable();
this.emitFile('myfile.txt', '文件内容');
let callback = this.async();
console.log(source);
// setTimeout(function () {
// callback(null, '');
// }, 100);
console.log('========================' + options.content + '==================================');
this.callback(null, 'let a;');
}
//module.exports.raw = true;
resolveLoader: {
modules: [path.resolve('node_modules'), path.resolve(__dirname, 'src', 'loaders')]
},
var less = require('less');
module.exports = function (source) {
less.render(source, (err, output) => {
this.callback(err, output.css);
});
}
var less = require('less');
module.exports = function (source) {
console.log(source);
return `let style = document.createElement('style');style.innerHTML = ${JSON.stringify(source)};document.head.appendChild(style);`;
}
{
test: /\.js/,
use: [
'bar-loader',
'foo-loader'
]
}
如果是处理顺序排在最后一个的 Loader,那么它的返回值将最终交给webpack的
require,换句话说,它一定是一段用字符串来存储可执行的JavaScript脚本
webpack充分地利用缓存来提高编译效率
this.cacheable();
当一个 Loader 无依赖,可异步的时候我想都应该让它不再阻塞地去异步
// 让 Loader 缓存
module.exports = function(source) {
var callback = this.async();
// 做异步的事
doSomeAsyncOperation(content, function(err, result) {
if(err) return callback(err);
callback(null, result);
});
};
默认的情况源文件是以 UTF-8 字符串的形式传入给 Loader,设置module.exports.raw = true可使用 buffer 的形式进行处理
module.exports.raw = true;
`
const loaderUtils = require('loader-utils');
module.exports = function(source) {
// 获取到用户给当前 Loader 传入的 options
const options = loaderUtils.getOptions(this);
return source;
};
Loader有些场景下还需要返回除了内容之外的东西。
module.exports = function(source) {
// 通过 this.callback 告诉 Webpack 返回的结果
this.callback(null, source, sourceMaps);
// 当你使用 this.callback 返回内容时,该 Loader 必须返回 undefined,
// 以让 Webpack 知道该 Loader 返回的结果在 this.callback 中,而不是 return 中
return;
};
this.callback(
// 当无法转换原内容时,给 Webpack 返回一个 Error
err: Error | null,
// 原内容转换后的内容
content: string | Buffer,
// 用于把转换后的内容得出原内容的 Source Map,方便调试
sourceMap?: SourceMap,
// 如果本次转换为原内容生成了 AST 语法树,可以把这个 AST 返回,
// 以方便之后需要 AST 的 Loader 复用该 AST,以避免重复生成 AST,提升性能
abstractSyntaxTree?: AST
);
Loader 有同步和异步之分,上面介绍的 Loader 都是同步的 Loader,因为它们的转换流程都是同步的,转换完成后再返回结果。 但在有些场景下转换的步骤只能是异步完成的,例如你需要通过网络请求才能得出结果,如果采用同步的方式网络请求就会阻塞整个构建,导致构建非常缓慢。
module.exports = function(source) {
// 告诉 Webpack 本次转换是异步的,Loader 会在 callback 中回调结果
var callback = this.async();
someAsyncOperation(source, function(err, result, sourceMaps, ast) {
// 通过 callback 返回异步执行后的结果
callback(err, result, sourceMaps, ast);
});
};
在默认的情况下,Webpack 传给 Loader 的原内容都是 UTF-8 格式编码的字符串。 但有些场景下 Loader 不是处理文本文件,而是处理二进制文件,例如 file-loader,就需要 Webpack 给 Loader 传入二进制格式的数据。 为此,你需要这样编写 Loader:
module.exports = function(source) {
// 在 exports.raw === true 时,Webpack 传给 Loader 的 source 是 Buffer 类型的
source instanceof Buffer === true;
// Loader 返回的类型也可以是 Buffer 类型的
// 在 exports.raw !== true 时,Loader 也可以返回 Buffer 类型的结果
return source;
};
// 通过 exports.raw 属性告诉 Webpack 该 Loader 是否需要二进制数据
module.exports.raw = true;
在有些情况下,有些转换操作需要大量计算非常耗时,如果每次构建都重新执行重复的转换操作,构建将会变得非常缓慢。 为此,Webpack 会默认缓存所有 Loader 的处理结果,也就是说在需要被处理的文件或者其依赖的文件没有发生变化时, 是不会重新调用对应的 Loader 去执行转换操作的。
module.exports = function(source) {
// 关闭该 Loader 的缓存功能
this.cacheable(false);
return source;
};
this.context:当前处理文件的所在目录,假如当前 Loader 处理的文件是 /src/main.js,则 this.context 就等于 /src。this.resource:当前处理文件的完整请求路径,包括 querystring,例如 /src/main.js?name=1。this.resourcePath:当前处理文件的路径,例如 /src/main.js。this.resourceQuery:当前处理文件的 querystring。
-this.target:等于 Webpack 配置中的 Targetthis.loadModule:但 Loader 在处理一个文件时,如果依赖其它文件的处理结果才能得出当前文件的结果时,
就可以通过 this.loadModule(request: string, callback: function(err, source, sourceMap, module)) 去获得 request 对应文件的处理结果。this.resolve:像 require 语句一样获得指定文件的完整路径,使用方法为 resolve(context: string, request: string, callback: function(err, result: string))。this.addDependency:给当前处理文件添加其依赖的文件,以便再其依赖的文件发生变化时,会重新调用 Loader 处理该文件。使用方法为 addDependency(file: string)。this.addContextDependency:和 addDependency 类似,但 addContextDependency 是把整个目录加入到当前正在处理文件的依赖中。使用方法为 addContextDependency(directory: string)。this.clearDependencies:清除当前正在处理文件的所有依赖,使用方法为 clearDependencies()。this.emitFile:输出一个文件,使用方法为 emitFile(name: string, content: Buffer|string, sourceMap: {...})。const path = require('path');
const fs = require('fs');
module.exports = function (source) {
let callback = this.async();
let banner = path.resolve(__dirname,'banner.js');
this.addDependency(banner);
fs.readFile(banner, 'utf8', (err, banner) => {
if (err) return callback(err);
callback(null, banner + '\n\n' + source);
});
}
{
test: /\.html$/,
use: {
loader: 'html-layout-loader',
options: {
layout: path.join(__dirname, 'src', 'layout.html'),
placeholder: '{{__content__}}'
}
}
}
plugins: [
new HtmlWebpackPlugin({
template: './src/login.html',
filename: 'login.html'
}),
new HtmlWebpackPlugin({
template: './src/home.html',
filename: 'home.html'
})
]
const path = require('path');
const fs = require('fs');
const loaderUtils = require('loader-utils');
const defaultOptions = {
placeholder: '{{__content__}}',
decorator: 'layout'
}
module.exports = function (source) {
let callback = this.async();
this.cacheable && this.cacheable();
const options = Object.assign(loaderUtils.getOptions(this), defaultOptions);
const { placeholder, decorator, layout } = options;
fs.readFile(layout, 'utf8', (err, html) => {
html = html.replace(placeholder, source);
callback(null, `module.exports = ${JSON.stringify(html)}`);
})
}
const path = require('path');
const fs = require('fs');
const loaderUtils = require('loader-utils');
const defaultOptions = {
placeholder:'{{__content__}}',
decorator:'layout'
}
module.exports = function(source){
let callback = this.async();
this.cacheable&& this.cacheable();
const options = {...loaderUtils.getOptions(this),...defaultOptions};
const {placeholder,layout,decorator} = options;
const layoutReg = new RegExp(`@${decorator}\\((.+?)\\)`);
let result = source.match(layoutReg);
if(result){
source = source.replace(result[0],'');
render(path.resolve(this.context,result[1]), placeholder, source, callback)
}else{
render(layout, placeholder, source, callback);
}
}
function render(layout, placeholder, source, callback) {
fs.readFile(layout, 'utf8', (err, html) => {
html = html.replace(placeholder, source);
callback(null, `module.exports = ${JSON.stringify(html)}`);
})
}