loader

loader 是导出为一个函数的 node 模块。该函数在 loader 转换资源的时候调用。给定的函数将调用 loader API,并通过 this 上下文访问。

loader配置

{
test: /\.js$/
use: [
{
loader: path.resolve('path/to/loader.js'),
options: {/* ... */}
}
]
}

本地loader配置

resolveLoader: {
modules: [
'node_modules',
path.resolve(__dirname, 'loaders')
]
}

loader用法

//返回简单结果
module.exports = function(content){
return content
} //返回多个值
module.exports = function(content){
this.callback(...)
} //同步loader
module.exports = function(content){
this.callback(...)
} //异步loader
module.exports = function(content){
let callback = this.async(...)
setTimeout(callback,1000)
}

loader 工具库

1.loader-utils 但最常用的一种工具是获取传递给 loader 的选项

2.schema-utils 用于保证 loader 选项,进行与 JSON Schema 结构一致的校验

import { getOptions } from 'loader-utils';
import validateOptions from 'schema-utils'; const schema = {
type: 'object',
properties: {
test: {
type: 'string'
}
}
} export default function(source) {
const options = getOptions(this); validateOptions(schema, options, 'Example Loader'); // 对资源应用一些转换…… return `export default ${ JSON.stringify(source) }`;
};

loader依赖

如果一个 loader 使用外部资源(例如,从文件系统读取),必须声明它。这些信息用于使缓存 loaders 无效,以及在观察模式(watch mode)下重编译。
import path from 'path';

export default function(source) {
var callback = this.async();
var headerPath = path.resolve('header.js'); this.addDependency(headerPath); fs.readFile(headerPath, 'utf-8', function(err, header) {
if(err) return callback(err);
callback(null, header + "\n" + source);
});
};

模块依赖

根据模块类型,可能会有不同的模式指定依赖关系。例如在 CSS 中,使用 @import 和 url(...) 语句来声明依赖。这些依赖关系应该由模块系统解析。

可以通过以下两种方式中的一种来实现:

通过把它们转化成 require 语句。
使用 this.resolve 函数解析路径。
css-loader 是第一种方式的一个例子。它将 @import 语句替换为 require 其他样式文件,将 url(...) 替换为 require 引用文件,从而实现将依赖关系转化为 require 声明。 对于 less-loader,无法将每个 @import 转化为 require,因为所有 .less 的文件中的变量和混合跟踪必须一次编译。因此,less-loader 将 less 编译器进行了扩展,自定义路径解析逻辑。然后,利用第二种方式,通过 webpack 的 this.resolve 解析依赖。
loaderUtils.stringifyRequest(this,require.resolve('./xxx.js'))

loader API

方法名 含义
this.request 被解析出来的 request 字符串。例子:"/abc/loader1.js?xyz!/abc/node_modules/loader2/index.js!/abc/resource.js?rrr"
this.loaders 所有 loader 组成的数组。它在 pitch 阶段的时候是可以写入的。
this.loaderIndex 当前 loader 在 loader 数组中的索引。
this.async 异步回调
this.callback 回调
this.data 在 pitch 阶段和正常阶段之间共享的 data 对象。
this.cacheable 默认情况下,loader 的处理结果会被标记为可缓存。调用这个方法然后传入 false,可以关闭 loader 的缓存。cacheable(flag = true: boolean)
this.context 当前处理文件所在目录
this.resource 当前处理文件完成请求路径,例如 /src/main.js?name=1
this.resourcePath 当前处理文件的路径
this.resourceQuery 查询参数部分
this.target webpack配置中的target
this.loadModule 但 Loader 在处理一个文件时,如果依赖其它文件的处理结果才能得出当前文件的结果时,就可以通过 this.loadModule(request: string, callback: function(err, source, sourceMap, module)) 去获得 request 对应文件的处理结果
this.resolve 解析指定文件路径
this.addDependency 给当前处理文件添加依赖文件,依赖发送变化时,会重新调用loader处理该文件
this.addContextDependency 把整个目录加入到当前正在处理文件的依赖当中
this.clearDependencies 清除当前正在处理文件的所有依赖中
this.emitFile 输出一个文件
loader-utils.stringifyRequest 把绝对路径转换成相对路径
loader-utils.interpolateName 用多个占位符或一个正则表达式转换一个文件名的模块。这个模板和正则表达式被设置为查询参数,在当前loader的上下文中被称为name或者regExp

loader原理

loader-runner

runLoaders({
resource: "/abs/path/to/file.txt?query",
// String: Absolute path to the resource (optionally including query string) loaders: ["/abs/path/to/loader.js?query"],
// String[]: Absolute paths to the loaders (optionally including query string)
// {loader, options}[]: Absolute paths to the loaders with options object context: { minimize: true },
// Additional loader context which is used as base context readResource: fs.readFile.bind(fs)
// A function to read the resource
// Must have signature function(path, function(err, buffer)) }, function(err, result) {
// err: Error? // result.result: Buffer | String
// The result // result.resourceBuffer: Buffer
// The raw resource as Buffer (useful for SourceMaps) // result.cacheable: Bool
// Is the result cacheable or do it require reexecution? // result.fileDependencies: String[]
// An array of paths (files) on which the result depends on // result.contextDependencies: String[]
// An array of paths (directories) on which the result depends on
}) function splitQuery(req) {
var i = req.indexOf("?");
if(i < 0) return [req, ""];
return [req.substr(0, i), req.substr(i)];
} function dirname(path) {
if(path === "/") return "/";
var i = path.lastIndexOf("/");
var j = path.lastIndexOf("\\");
var i2 = path.indexOf("/");
var j2 = path.indexOf("\\");
var idx = i > j ? i : j;
var idx2 = i > j ? i2 : j2;
if(idx < 0) return path;
if(idx === idx2) return path.substr(0, idx + 1);
return path.substr(0, idx);
} //loader开始执行阶段
function processResource(options, loaderContext, callback) {
// 将loader索引设置为最后一个loader
loaderContext.loaderIndex = loaderContext.loaders.length - 1; var resourcePath = loaderContext.resourcePath
if(resourcePath) {
//添加文件依赖
loaderContext.addDependency(resourcePath);
//读取文件
options.readResource(resourcePath, function(err, buffer) {
if(err) return callback(err);
//读取完成后放入options
options.resourceBuffer = buffer;
iterateNormalLoaders(options, loaderContext, [buffer], callback);
}); } else {
iterateNormalLoaders(options, loaderContext, [null], callback);
}
} //从右往左递归执行loader
function iterateNormalLoaders(options, loaderContext, args, callback) {
//结束条件,loader读取完毕
if(loaderContext.loaderIndex < 0)
return callback(null, args); var currentLoaderObject = loaderContext.loaders[loaderContext.loaderIndex]; //迭代
if(currentLoaderObject.normalExecuted) {
loaderContext.loaderIndex--;
return iterateNormalLoaders(options, loaderContext, args, callback);
} var fn = currentLoaderObject.normal;
currentLoaderObject.normalExecuted = true; if(!fn) {
return iterateNormalLoaders(options, loaderContext, args, callback);
} //转换buffer数据。如果当前loader设置了raw属性
convertArgs(args, currentLoaderObject.raw); runSyncOrAsync(fn, loaderContext, args, function(err) {
if(err) return callback(err); var args = Array.prototype.slice.call(arguments, 1);
iterateNormalLoaders(options, loaderContext, args, callback);
}); } function convertArgs(args, raw) {
if(!raw && Buffer.isBuffer(args[0]))
args[0] = utf8BufferToString(args[0]);
else if(raw && typeof args[0] === "string")
args[0] = Buffer.from(args[0], "utf-8");
} exports.getContext = function getContext(resource) {
var splitted = splitQuery(resource);
return dirname(splitted[0]);
}; function createLoaderObject(loader){
//初始化loader配置
var obj = {
path: null,
query: null,
options: null,
ident: null,
normal: null,
pitch: null,
raw: null,
data: null,
pitchExecuted: false,
normalExecuted: false
}; //设置响应式属性
Object.defineProperty(obj, "request", {
enumerable: true,
get: function() {
return obj.path + obj.query;
},
set: function(value) {
if(typeof value === "string") {
var splittedRequest = splitQuery(value);
obj.path = splittedRequest[0];
obj.query = splittedRequest[1];
obj.options = undefined;
obj.ident = undefined;
} else {
if(!value.loader)
throw new Error("request should be a string or object with loader and object (" + JSON.stringify(value) + ")");
obj.path = value.loader;
obj.options = value.options;
obj.ident = value.ident;
if(obj.options === null)
obj.query = "";
else if(obj.options === undefined)
obj.query = "";
else if(typeof obj.options === "string")
obj.query = "?" + obj.options;
else if(obj.ident)
obj.query = "??" + obj.ident;
else if(typeof obj.options === "object" && obj.options.ident)
obj.query = "??" + obj.options.ident;
else
obj.query = "?" + JSON.stringify(obj.options);
}
}
}); obj.request = loader; //冻结对象
if(Object.preventExtensions) {
Object.preventExtensions(obj);
}
return obj; } exports.runLoaders = function runLoaders(options, callback) {
//options = {resource...,fn...} // 读取options
var resource = options.resource || "";
var loaders = options.loaders || [];
var loaderContext = options.context || {};
var readResource = options.readResource || readFile; //
var splittedResource = resource && splitQuery(resource);
var resourcePath = splittedResource ? splittedResource[0] : undefined;
var resourceQuery = splittedResource ? splittedResource[1] : undefined;
var contextDirectory = resourcePath ? dirname(resourcePath) : null; //执行状态
var requestCacheable = true;
var fileDependencies = [];
var contextDependencies = []; //准备loader对象
loaders = loaders.map(createLoaderObject); loaderContext.context = contextDirectory; //当前文件所在目录
loaderContext.loaderIndex = 0; //从0个开始
loaderContext.loaders = loaders; //loaders数组
loaderContext.resourcePath = resourcePath; //当前文件所在位置
loaderContext.resourceQuery = resourceQuery; //当前文件的?部分
loaderContext.async = null; //异步状态
loaderContext.callback = null; //同步状态
loaderContext.cacheable = function cacheable(flag) { //是否设置缓存
if(flag === false) {
requestCacheable = false;
}
};
loaderContext.dependency = loaderContext.addDependency = function addDependency(file) {
fileDependencies.push(file);
};//记录文件依赖
loaderContext.addContextDependency = function addContextDependency(context) {
contextDependencies.push(context);
};//记录目录依赖
loaderContext.getDependencies = function getDependencies() {
return fileDependencies.slice();
};//获取文件依赖
loaderContext.getContextDependencies = function getContextDependencies() {
return contextDependencies.slice();
};//获取文件目录依赖
loaderContext.clearDependencies = function clearDependencies() {
fileDependencies.length = 0;
contextDependencies.length = 0;
requestCacheable = true;
};//删除依赖 //设置响应属性,获取resource自动添加query,设置时自动解析
Object.defineProperty(loaderContext, "resource", {
enumerable: true,
get: function() {
if(loaderContext.resourcePath === undefined)
return undefined;
return loaderContext.resourcePath + loaderContext.resourceQuery;
},
set: function(value) {
var splittedResource = value && splitQuery(value);
loaderContext.resourcePath = splittedResource ? splittedResource[0] : undefined;
loaderContext.resourceQuery = splittedResource ? splittedResource[1] : undefined;
}
});
Object.defineProperty(loaderContext, "request", {
enumerable: true,
get: function() {
return loaderContext.loaders.map(function(o) {
return o.request;
}).concat(loaderContext.resource || "").join("!");
}
});
Object.defineProperty(loaderContext, "remainingRequest", {
enumerable: true,
get: function() {
if(loaderContext.loaderIndex >= loaderContext.loaders.length - 1 && !loaderContext.resource)
return "";
return loaderContext.loaders.slice(loaderContext.loaderIndex + 1).map(function(o) {
return o.request;
}).concat(loaderContext.resource || "").join("!");
}
});
Object.defineProperty(loaderContext, "currentRequest", {
enumerable: true,
get: function() {
return loaderContext.loaders.slice(loaderContext.loaderIndex).map(function(o) {
return o.request;
}).concat(loaderContext.resource || "").join("!");
}
});
Object.defineProperty(loaderContext, "previousRequest", {
enumerable: true,
get: function() {
return loaderContext.loaders.slice(0, loaderContext.loaderIndex).map(function(o) {
return o.request;
}).join("!");
}
});
Object.defineProperty(loaderContext, "query", {
enumerable: true,
get: function() {
var entry = loaderContext.loaders[loaderContext.loaderIndex];
return entry.options && typeof entry.options === "object" ? entry.options : entry.query;
}
});
Object.defineProperty(loaderContext, "data", {
enumerable: true,
get: function() {
return loaderContext.loaders[loaderContext.loaderIndex].data;
}
}); // 完成loader上下文
//冻结对象
if(Object.preventExtensions) {
Object.preventExtensions(loaderContext);
} var processOptions = {
resourceBuffer: null,
readResource: readResource
}; //进入loaderPitching阶段
iteratePitchingLoaders(processOptions, loaderContext, function(err, result) {
if(err) {
return callback(err, {
cacheable: requestCacheable,
fileDependencies: fileDependencies,
contextDependencies: contextDependencies
});
}
callback(null, {
result: result,
resourceBuffer: processOptions.resourceBuffer,
cacheable: requestCacheable,
fileDependencies: fileDependencies,
contextDependencies: contextDependencies
});
});
} //进入loaderPitch阶段
function iteratePitchingLoaders(options, loaderContext, callback) {
// 在最后一个loader之后终止
if(loaderContext.loaderIndex >= loaderContext.loaders.length)
//开始递归解析依赖
return processResource(options, loaderContext, callback); var currentLoaderObject = loaderContext.loaders[loaderContext.loaderIndex]; // 迭代
if(currentLoaderObject.pitchExecuted) {
loaderContext.loaderIndex++;
return iteratePitchingLoaders(options, loaderContext, callback);
} // 加载loader module
loadLoader(currentLoaderObject, function(err) {
if(err) return callback(err);
var fn = currentLoaderObject.pitch;
//记录pitch执行状态
currentLoaderObject.pitchExecuted = true;
//没有pitch方法就执行下一个
if(!fn) return iteratePitchingLoaders(options, loaderContext, callback);
//执行pitch方法
runSyncOrAsync(
fn,
loaderContext, [loaderContext.remainingRequest, loaderContext.previousRequest, currentLoaderObject.data = {}],
function(err) {
if(err) return callback(err);
var args = Array.prototype.slice.call(arguments, 1);
// Determine whether to continue the pitching process based on
// argument values (as opposed to argument presence) in order
// to support synchronous and asynchronous usages.
var hasArg = args.some(function(value) {
return value !== undefined;
});
//根据有无返回值执行对象loader,如果有返回值就执行normalloader,不执行后面的pitch了
if(hasArg) {
loaderContext.loaderIndex--;
iterateNormalLoaders(options, loaderContext, args, callback);
} else {
iteratePitchingLoaders(options, loaderContext, callback);
}
}
);
});
} //运行异步或同步loader
function runSyncOrAsync(fn, context, args, callback) {
//设置初始状态
var isSync = true;
var isDone = false;
var isError = false; // 内部错误
var reportedError = false; //挂载loader异步方法
context.async = function async() {
if(isDone) {
if(reportedError) return; // ignore
throw new Error("async(): The callback was already called.");
}
isSync = false;
return innerCallback;
};
//挂载loader同步方法
var innerCallback = context.callback = function() {
if(isDone) {
if(reportedError) return; // ignore
throw new Error("callback(): The callback was already called.");
}
isDone = true;
isSync = false;
try {
callback.apply(null, arguments);
} catch(e) {
isError = true;
throw e;
}
}; try {
var result = (function LOADER_EXECUTION() {
return fn.apply(context, args);
}());
if(isSync) {
isDone = true;
if(result === undefined)
return callback();
if(result && typeof result === "object" && typeof result.then === "function") {
return result.catch(callback).then(function(r) {
callback(null, r);
});
}
return callback(null, result);
}
} catch(e) {
if(isError) throw e;
if(isDone) {
// loader is already "done", so we cannot use the callback function
// for better debugging we print the error on the console
if(typeof e === "object" && e.stack) console.error(e.stack);
else console.error(e);
return;
}
isDone = true;
reportedError = true;
callback(e);
} }
//loaderLoader.js
module.exports = function loadLoader(loader, callback) {
//加载loader,并且拿到loader设置的pitch与raw属性
if(typeof System === "object" && typeof System.import === "function") {
System.import(loader.path).catch(callback).then(function(module) {
loader.normal = typeof module === "function" ? module : module.default;
loader.pitch = module.pitch;
loader.raw = module.raw;
if(typeof loader.normal !== "function" && typeof loader.pitch !== "function")
throw new Error("Module '" + loader.path + "' is not a loader (must have normal or pitch function)");
callback();
});
} else {
try {
var module = require(loader.path);
} catch(e) {
// it is possible for node to choke on a require if the FD descriptor
// limit has been reached. give it a chance to recover.
if(e instanceof Error && e.code === "EMFILE") {
var retry = loadLoader.bind(null, loader, callback);
if(typeof setImmediate === "function") {
// node >= 0.9.0
return setImmediate(retry);
} else {
// node < 0.9.0
return process.nextTick(retry);
}
}
return callback(e);
}
if(typeof module !== "function" && typeof module !== "object")
throw new Error("Module '" + loader.path + "' is not a loader (export function or es6 module))");
loader.normal = typeof module === "function" ? module : module.default;
loader.pitch = module.pitch;
loader.raw = module.raw;
if(typeof loader.normal !== "function" && typeof loader.pitch !== "function")
throw new Error("Module '" + loader.path + "' is not a loader (must have normal or pitch function)");
callback();
}
};

webpack-loader原理的更多相关文章

  1. 手把手教你撸一个 Webpack Loader

    文:小 boy(沪江网校Web前端工程师) 本文原创,转载请注明作者及出处 经常逛 webpack 官网的同学应该会很眼熟上面的图.正如它宣传的一样,webpack 能把左侧各种类型的文件(webpa ...

  2. webpack系列--浅析webpack的原理

    一.前言 现在随着前端开发的复杂度和规模越来越大,鹰不能抛开工程化来独立开发,比如:react的jsx代码必须编译后才能在浏览器中使用,比如sass和less代码浏览器是不支持的.如果摒弃这些开发框架 ...

  3. webpack loader & pulgin

    webpack loader & plugin https://webpack.js.org/concepts/loaders/ https://webpack.js.org/concepts ...

  4. 怎样写一个webpack loader

    div{display:table-cell;vertical-align:middle}#crayon-theme-info .content *{float:left}#crayon-theme- ...

  5. 如何开发webpack loader

    关于webpack 作为近段时间风头正盛的打包工具,webpack基本占领了前端圈.相信你都不好意思说不知道webpack. 有兴趣的同学可以参考下我很早之前的webpack简介 . 确实webpac ...

  6. webpack模块化原理

    https://segmentfault.com/a/1190000010349749     webpack模块化原理-commonjs https://segmentfault.com/a/119 ...

  7. webpack构建原理和实现简单webpack

    webpack打包原理分析 基础配置,webpack会读取配置 (找到入口模块) 如:读取webpack.config.js配置文件: const path = require("path& ...

  8. Webpack相关原理浅析

    基本打包机制 本质上,webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler).当 webpack 处理应用程序时,它会递归地构建一个依赖关系图(de ...

  9. 案例实战之如何写一个webpack loader

    通过以下几个实例掌握webpack loader的写法 1.写一个多语言替换的loader 在index.js在页面上插入了一个{{title}}文本,我们需要在打包的时候将其替换成对应的多语言 fu ...

  10. 发布一个npm包(webpack loader)

    发布一个npm包,webpack loader: reverse-color-loader,实现颜色反转. 初始化项目 mkdir reverse-color-loader cd ./reverse- ...

随机推荐

  1. [译文]casperjs的API-clientutils模块

    casper提供了少量的客户端接口用来进行远程DOM环境注入,通过clientutils模块的ClientUtils类实例中的__utils__对象来执行: casper.evaluate(funct ...

  2. 低版本php对json的处理

    由于低版本php(php5以下)没有json_encode和json_decode 所以有下面函数实现 function json_encode($data) { switch ($type = ge ...

  3. 2016级算法第三次上机-A.Bamboo的小吃街

    A Bamboo的小吃街 分析 经典的两条流水线问题,题目描述基本类似于课件中的流水线调度,符合动态规划最优子结构性质 关键的动态规划式子为: dp[0][j] = min(dp[0][j - 1], ...

  4. JavaScript DOM编程艺术 笔记(一)

    探测浏览器品牌版本代码-----浏览器嗅探 代码 JavaScript==ECMAScript java几乎可以部署在任何环境,js只应用于web浏览器 API是一组得到各方面共同认同的基本约定(元素 ...

  5. Opencv3.0: undefined reference to cv::imread(cv::String const&, int)

    使用opencv,编译出错: undefined reference to cv::imread(cv::String const&, int) 自opencv3.0之后,图像读取相关代码在i ...

  6. Python全栈-magedu-2018-笔记12

    第三章 - Python 内置数据结构 字典dict key-value键值对的数据的集合 可变的.无序的.key不重复 字典dict定义 初始化 d = dict() 或者 d = {} dict( ...

  7. 解决js array的key不为数字时获取长度的问题

    最近写js时碰到了当数组key不为数字时,获取数组的长度为0 的情况. 1.问题场景 var arr = new Array(); arr[‘s1‘] = 1001; console.log(arr. ...

  8. Dubbo的Api+Provider+Customer示例(IDEA+Maven+Springboot+dubbo)

    项目结构 dubbo-demo dubbo-api:提供api接口,一般存储实体类和接口服务 dubbo-provider:dubbo生产者提供服务,一般存储接口具体实现 dubbo-customer ...

  9. 微信小程序踩坑

    微信小程序自定义属性data-xx使用注意事项 data-xx采用驼峰式命名时,数据传递打印显示(以jxsName与jxsname打印显示对比) data-xx全小写命名时,数据传递打印显示

  10. JAVA学习3:Eclipse中集成Tomcat

    问题: 很多时候在Eclipse中启动Tmocat后,不能访问本机的localhost:8080主页,并且其他项目也不能访问. 原因: 打开Tomcat下的webapp后也找补到项目目录,这是因为Ec ...