分享web开发知识

注册/登录|最近发布|今日推荐

主页 IT知识网页技术软件开发前端开发代码编程运营维护技术分享教程案例
当前位置:首页 > 软件开发

webpack-loader原理

发布时间:2023-09-06 02:30责任编辑:蔡小小关键词:webpack

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(...)}//同步loadermodule.exports = function(content){ ???this.callback(...)}//异步loadermodule.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.targetwebpack配置中的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); ???}}//从右往左递归执行loaderfunction 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); ???????????????} ???????????} ???????); ???});}//运行异步或同步loaderfunction 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.jsmodule.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原理

原文地址:https://www.cnblogs.com/pluslius/p/10264865.html

知识推荐

我的编程学习网——分享web前端后端开发技术知识。 垃圾信息处理邮箱 tousu563@163.com 网站地图
icp备案号 闽ICP备2023006418号-8 不良信息举报平台 互联网安全管理备案 Copyright 2023 www.wodecom.cn All Rights Reserved