Webpack学习-Loader
什么是Loader?
继上两篇文章webpack工作原理介绍(上篇、下篇),我们了解到Loader:模块转换器,也就是将模块的内容按照需求装换成新内容
,而且每个Loader的职责都是单一,只会完成一种转换,所以我们一般对源文件的处理,也是由多个Loader以链式顺序执行的方式来进行多次装换,然后得到我们要的结果。
那么这样Loader只需要关心输入和输出,Loader其实是一个Node.js模块,该模块导出的是一个函数(意味着,所有node.js的api我们都可以使用),如下:
module.exports = function (source) {
// 对source做一系列的转换
return source;
}
下面我们介绍一下webpack提供了哪些供Loader调用的api,对Loader有个比较深刻的理解,然后来分析babel-loader
的源码,看看我们常用的loader是怎么编写出来的。
获得Loader的options
const loaderUtils = require('loader-utils');
module.exports = function(source) {
// 获取用户为当前Loader传入的options
console.log(loaderUtils.getOptions(this));
return source;
}
返回其他结果
如上,我们返回的是转换后的内容,但是有些情况下,我们不仅仅需要返回转换后的内容,还需要返回一些其他的内容,如sourceMap或是AST语法树,那么这时候我们可以使用webpack提供的APIthis.callback
,当使用this.callback
了,那么我们就必须需要在Loader函数返回undefined
,以此来让webpack知道返回的结果在this.callback
中,API详细参数如下:
this.callback(
// 无法装换原内容时的Error
err: Error || null,
// 装换后的的内容,如上述的source
content: string | Buffer,
// 用于通过装换后的内容得出原内容的Source Map,方便调试
// 我们了解到,SourceMap我们只是会在开发环境去使用,于是就会变成可控制的,
// webpack也提供了this.sourceMap去告诉是否需要使用sourceMap,
// 当然也可以使用loader的option来做判断,如css-loader
sourceMap?: SourceMap,
// 如果本次转换同时生成ast语法树,也可以将这个ast返回,方便后续loader需要复用该ast,这样可以提高性能
abstractSyntaxTree? AST
);
同步与异步
看看异步Loader在this.async
API下如何实现,
module.exports = async function (source) {
const callback = this.async();
const { err, content, sourceMap, AST } = await Func();
callback(err, content, sourceMap, AST); // 如上诉`this.callback`参数一样
}
处理二进制数据
像file-loader
这样的Loader,处理的是二进制数据,那么就需要告诉webpack给loader传入二进制格式的数据,代码可以如下:
module.exports = function(source) {
if (source instanceof Buffer) {
// 一系列操作
return source; //当然我本身也可以返回二进制数据提供给下一个loader
}
}
moudle.exports.raw = true; //不设置,就会拿到字符串
通过moudle.exports.raw = true;
告知webpack,自己本身需要二进制数据。
缓存加速
优化的最佳点,可以使用this.cacheable(Boolen)
,缓存loader转换后的内容,当处理文件或依赖文件没有发生变化时,使用缓存的转换内容,以此提速!
其他API
说到学习,当然越系统越好了,api多介绍 ,除了上面常用的api之外,还存在以下常用的api。
this.context
: 当前处理转换的文件所在的目录this.resource
: 当前处理转换的文件完整请求路径,包括querystringthis.resourcePath
: 当前处理转换的文件的路径this.resourceQuery
: 当前处理文件的querystringthis.target
: webpack配置的targetthis.loadMoudle
: 处理文件时,需要依赖其他文件的处理结果时,可以使用this.loadMoudle(request: string, callback: function(err, source, sourceMap, module))去获取到依赖文件的处理结果。this.resolve
: 获取指定文件的完整路径,this.resolve(context: string, request: string, callback: function(err, result: string))this.addDependency
: 为当前处理文件添加依赖文件,以便依赖文件发生变化时重新调用Loader转换该文件,this.addDependency(file: string)this.addContextDependency
: 为当前处理文件添加依赖文件目录,以便依赖文件目录里文件发生变化时重新调用Loader转换该文件,this.addContextDependency(dir: string)this.clearDependencies
: 清除当前正在处理的文件的所有依赖this.emitFile
: 输出一个文件,使用的方法为this.emitFile(name: string, content: Buffer | string, sourceMap: {...})
babel-loader源码简析
源码第一行如下:
let babel;
try {
babel = require("@babel/core");
} catch (err) {
if (err.code === "MODULE_NOT_FOUND") {
err.message +=
"\n babel-loader@8 requires Babel 7.x (the package '@babel/core'). " +
"If you'd like to use Babel 6.x ('babel-core'), you should install 'babel-loader@7'.";
}
throw err;
}
babel-loader依赖了@babel/core
,这就是安装babel-loader需要同时安装@babel/core
(通常会再安装babel-preset-env
、babel-plugin-transform-runtime
、babel-runtime
)的原因。我们接下去看,src/index.js
整个文件是不是按照我们前面所讲编写Loader的方法来组织代码的。
//引入package.json
const pkg = require("../package.json");
/*
根据babel-loader是否配置cacheDirectory属性来告诉
babel-loader是否缓存loader的执行结果,如果true,
便会使用cache方法去实现,`cache.js`文件有着read、write、filename(文件命名方法)
以及如何处理缓存的handleCache方法(有则读,无则写再读),有兴趣可以去看看。
*/
const cache = require("./cache");
/*
transfrom.js用来转换内容,内部调用了babel.transform方法进行转换,这里简单介绍一下babel的原理:
babylon将es6/es7代码解析成ast,babel-traverse对ast进行转译,得到新的ast,新的ast通过
babel-generator转换成es5,核心方法在@babel/core/lib/transformation/index.js中的`runSync`
方法,有兴趣可以去了解一下。
*/
const transform = require("./transform");
const injectCaller = require("./injectCaller");
const path = require("path");
// 获取Loader参数options
const loaderUtils = require("loader-utils");
module.exports = makeLoader();
module.exports.custom = makeLoader;
function makeLoader(callback) {
const overrides = callback ? callback(babel) : undefined;
return function(source, inputSourceMap) {
// 上面介绍过的api可以得知,这是个异步Loader,做的是异步装换的工作
const callback = this.async();
loader
.call(this, source, inputSourceMap, overrides)
.then(args => callback(null, ...args), err => callback(err));
};
}
async function loader(source, inputSourceMap, overrides) {
....
}
可以看到确实和我们Loader编写方式是一样的,通过module.exports = makeLoader();
导出一个函数,makeLoader()
是一个高阶函数,又返回了一个函数,通过const callback = this.async();
可以知道,这是一个异步的loader,不难看出最重要的实现都在这一步函数loader里面了,那么到底在loader函数里面究竟做了些什么呢?我们来看看,在阅读源码前,最好先看看babel-loader
的README,先做个基本了解.
上面代码可以看出loader(source, inputSourceMap, overrides)
函数入参有三个,分别是source=>待转换的code
,inputSourceMap=>上一个loader处理后的sourceMap,有的话
,overrides=>自定义加载器
,整块源码可以分成几部分,
let loaderOptions = loaderUtils.getOptions(this) || {};
,获取options,并且获取当前处理转换的文件的路径this.resourcePath
。- 判断是否自定义加载器转换,这里会进行一系列对options.customize进行判断,options.customize一个相对路径,loader函数参数overrides为空时起效,执行
let override = require(loaderOptions.customize);
,有了override之后,后续逻辑(如转换、获取option)override都会进行介入处理。 - 将函数传入参数和LoaderOptions归并,得到programmaticOptions。
- 调用
babel.loadPartialConfig
可以拿到babel配置并赋值给config变量,其实就是为了允许系统轻松操作和验证用户的配置,此功能解决了插件和预设 - 生成cacheIdentifier
- 判断options.cacheDirectory是否需要缓存Loader转换内容,如为true,调用cache.js的module.export Cache方法(上面已做介绍)
config.babelrc
不为空,则有.babelrc文件,依赖.babelrc文件变化,使用this.addDependency(config.babelrc);
- metadataSubscribers 订阅元数据,主要作用是订阅一些编译过程中的一些元数据,订阅以后这些元数据将会被添加 到webpack的上下文中。通常我们是用不上的,估计在某些babel-plugin中可能会使用到。
- 最后将处理后的结果返回
小结
每一个Loader其实返回值就是一个Function,而且就是把带转换内容传入,得到转换后的内容,做的事情就是这样,这篇文章先对Loader的基本概念进行介绍,并且了解webpack为Loader的编写提供一些常用的API,最后通过简析babel-loader的源码,我觉得应该差不多知道如何去写一个简单的Loader了,文章原文地址我的博客。
Webpack学习-Loader的更多相关文章
- webpack学习笔记一:安装webpack、webpack-dev-server、内存加载js和html文件、loader处理非js文件
一 .webpack学习环境准备: 1:window系统 2:安装node.js 官方网址 下载好后下一步下一步安装即可 安装步骤略过....... 3:nrm的安装 打开cmd命令控制台 输入:n ...
- 【原】webpack学习笔记
之前在react的项目中有用过webpack,不过没有认真的去研究,这段时间又重新好好的学习一下webpack,发觉 webpack是一个很强大的东西.而且很好用,方便,接下来主要是做一下学习的笔记 ...
- webpack学习(入门基础)
webpack的学习webpack是什么?1:加载模块(将JS.sass/less.图片等作为模块来处理使用) 2:进行打包 webpack的优势?1:webpack以commonJS(JS的规范)的 ...
- 前端模块化工具--webpack学习心得
话说前头 webpack前段时间有听说一下,现在已经到了3.x的版本,自己没去接触.因为之前使用gulp来作为自己的项目构建工具.现在感觉gulp使用的趋势在减少.现在这段时间去接触了webpack, ...
- webpack学习(五)—webpack+react+es6(第1篇)
如果你看过webpack学习系列的前一个文章,接下来做的东西会比较简单 :webpack学习(四)— webpack-dev-server react发展的很快,现在大部分开发react相关的项目,都 ...
- 前端小白webpack学习(一)
俗话说得好,好记性不如烂笔头. 之前就在学习中看过webpack的教程,然而一段时间没用,火速的忘光了.写这篇博文,做个总结,也让自己以后有个地方回顾. 看webpack之前,我先去看了一下官方文档, ...
- Webpack学习系列 - Webpack5 怎么集成Babel ?
程序员优雅哥简介:十年程序员,呆过央企外企私企,做过前端后端架构.分享vue.Java等前后端技术和架构. 本文摘要:主要通过实操讲解运用Webpack 5 如何集成 Babel Babel 对于前端 ...
- webpack学习笔记一
主要参考: https://blog.madewithlove.be/post/webpack-your-bags/ 起因: 作为运维狗, 对前端一窍不通但心向往之, 最近做一个Dashboard, ...
- webpack学习笔记(二)-- 初学者常见问题及解决方法
这篇文章是webpack学习第二篇,主要罗列了本人在实际操作中遇到的一些问题及其解决方法,仅供参考,欢迎提出不同意见. 注:本文假设读者已有webpack方面相关知识,故文中涉及到的专有名词不做另外解 ...
随机推荐
- Truck Adblue Emulator For SCANIA
For sale online Truck Adblue Emulator For SCANIA See the price Where to buy? Truck Adblue Emulator F ...
- Linux下/proc目录的作用
文章是摘抄过来,方便自己查阅! 1. /proc目录 Linux 内核提供了一种通过 /proc 文件系统,在运行时访问内核内部数据结构.改变内核设置的机制.proc文件系统是一个伪文件系统,它只存在 ...
- python关于类和正则表达( 编写一个程序(类))
1.什么是类对象,实例对象 类对象:类名实例对象:类创建的对象 2.类属性: 就是类对象所拥有的属性,它被所有类对象的实例对象所共有,在内存中只存在一个副本.对于公有的类属性,在类外可以通过类对象和实 ...
- js 获取屏幕或元素宽高...
窗口相对于屏幕顶部距离 window.screenTop 窗口相对于屏幕左边距离 window.screenLeft, 屏幕分辨率的高 window.screen.height, 屏幕分辨率的宽 wi ...
- P4177 [CEOI2008]order(网络流)最大权闭合子图
P4177 [CEOI2008]order 如果不能租机器,这就是最大权闭合子图的题: 给定每个点的$val$,并给出限制条件:如果取点$x$,那么必须取$y_1,y_2,y_3......$,满足$ ...
- iOS __block 关键字的底层实现原理 -- 堆栈地址的变更
默认情况下,在block中访问的外部变量是复制过去的.但是可以加上 __block 来让其写操作生效. 原理: Block 不允许修改外部变量的值,这里所说的外部变量的值,指的是栈中指针的内存地址. ...
- vue怎么引入外网json文件
今日吃午饭时,伊万卡前端小妹问了我一个问题."App中有一个模块是用H5做的,其中有一个接口读取的是本地json资源文件,但是这个文件修改时间不定,我又不想每次修改了这个文件再重新发版打个包 ...
- 应对 Visual Stdio 编译时出现错误:常量中有换行符
笔者最近用 Visual Stdio 时,发现一个问题,在某一次写完语言进行编绎运行时,出现了以下错误: C2001错误:变量中有换行符 C2413错误:语法错误 缺少")"(在& ...
- Activiti 工作流之所学所感(基本配置) DAY1
由于公司需求,最近在研究工作流,在此记录一下所学所感以备往后使用时候可以方便查询,有不足之处请各位大牛提点,下面直接进入主题. 下载activiti 所需资料 可以直接在官网上下载,也可以在我的网盘 ...
- 第 10 章 容器监控 - 080 - Weave Scope 容器地图
Weave Scope 容器地图 Weave Scope 的最大特点是会自动生成一张 Docker 容器地图,让我们能够直观地理解.监控和控制容器. 安装 执行如下脚本安装运行 Weave Scope ...