webpack-loader原理

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.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); } } //从右往左递归执行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 = []; 

转载于:https://www.cnblogs.com/cangqinglang/p/11149494.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
vue-loader 是一个用于解析和转换 Vue 单文件组件(.vue 文件)的 webpack loader。下面是 vue-loader 的工作原理: 1. 预处理:vue-loader 接收到一个 .vue 文件后,首先会对其进行预处理,将其拆分为三个部分:template、script 和 style。 2. 解析:vue-loader 会对 template、script 和 style 部分进行解析。 - 对于 template 部分,vue-loader 使用 compiler 将其转换为 render 函数,然后将其与所在的 script 部分合并。这允许开发者以编写 HTML 模板的方式来编写 Vue 组件。 - 对于 script 部分,vue-loader 解析其中的 ES6+ 语法,并且将其转换为浏览器可识别的 JavaScript 代码。 - 对于 style 部分,vue-loader 会将样式代码转换为 JavaScript 对象,并使用 CSS-loader 进一步处理。 3. 组件资源处理:vue-loader 还会处理组件中引用的资源,如图片、字体等。它会将这些资源的路径进行转换,并使用 file-loader 或 url-loader 对其进行加载。 4. 热重载:vue-loader 可以与 webpack-dev-server 或 webpack-hot-middleware 配合,实现在开发过程中的热重载功能。它会监听 .vue 文件的变化,并在文件发生改变时重新编译和更新组件。 总的来说,vue-loader 通过对 .vue 文件中的模板、脚本和样式进行解析和转换,实现了将 Vue 单文件组件转换为可以在浏览器中运行的 JavaScript 代码,并提供了热重载功能。这样可以提高开发效率,同时也方便了组件化开发的实现。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值