先说下webpack打包原理
- 识别入口文件
- 通过逐层识别模块依赖。(Commonjs、amd或者es6的import,webpack都会对其进行分析。来获取代码的依赖)
- webpack做的就是分析代码。转换代码,编译代码,输出代码
最终形成打包后的代码
这些都是webpack的一些基础知识,对于理解webpack的工作机制很有帮助。
步入正题,loader
什么是lodaer呢,其实就是一个个函数,loader是文件加载器,能够加载资源文件,并对这些文件进行一些处理,诸如编译、压缩等,最终一起打包到指定的文件中
- loader是文件加载器,能够加载资源文件,并对这些文件进行一些处理,诸如编译、压缩等,最终一起打包到指定的文件中
- 第一个执行的loader接收源文件内容作为参数,其他loader接收前一个执行的loader的返回值作为参数。最后执行的loader会返回此模块的JavaScript源码
手写一个loader
需求:
1.处理.txt文件
2.对字符串做反转操作
3.首字母大写
4.对打印出来的111进行重写
例如:abcdefg转换后为Gfedcba, console.log(111)=>console.log(222)
let's go!~
-
首先创建两个loader(这里以本地loader为例)
为什么要创建两个laoder?理由后面会介绍
看,loader结构是不是很简单,接收一个参数,并且return一个内容就ok了。
然后创建一个txt文件,里面随便写点什么,abcdef都行
现在开始配置webpack,
我们在入口文件中导入这个脚本
为什么这里需要导入呢,我们不是配置了webapck处理所有的.txt文件么?
因为webpack会做过滤,如果不引用该文件的话,webpack是不会对该文件进行打包处理的,那么你的loader也不会执行(好像有点废话- - !)
这个就是入口文件啦,我稍微做了点操作,目的是为了展示在页面中。
那你可能有疑问了,html怎么才能够展示呢,很简单,把打包之后dist中的js引入html就OK
然后允许webpack就好了。webpack会自动去找webpack.config.js。
现在回答为什么要写两个loader?
看到执行的顺序没,我们的配置的是这样的。
use: [
'./loader/uppercase-loader.js',
'./loader/reverse-loader.js'
]
正如前文所说, 处理一个文件可以使用多个loader,loader的执行顺序是和本身的顺序是相反的。
我们也可以自己写loader解析自定义模板,像vue-loader是非常复杂的,它内部会写大量的对.vue文件的解析,然后会生成对应的html、js和css。
我们这里只是讲述了一个最基础的用法,如果有更多的需要,可以查看《loader官方文档》
了解了基本模式后,我们先不急着开发。所谓磨刀不误砍柴工,我们先看看开发一个 loader 需要注意些什么,这样可以少走弯路,提高开发质量。下面是 webpack 提供的几点指南,它们按重要程度排序,注意其中有些点只适用特定情况。
获得 Loader 的 options
const loaderUtils = require('loader-utils');
module.exports = function(source) {
// 获取到用户给当前 Loader 传入的 options
const options = loaderUtils.getOptions(this);
return source;
};
返回其它结果
上面的 Loader 都只是返回了原内容转换后的内容,但有些场景下还需要返回除了内容之外的东西。
例如以用 babel-loader 转换 ES6 代码为例,它还需要输出转换后的 ES5 代码对应的 Source Map,以方便调试源码。
为了把 Source Map 也一起随着 ES5 代码返回给 Webpack,可以这样写:
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 给 Loader 注入的 API,以方便 Loader 和 Webpack 之间通信。
this.callback 的详细使用方法如下:
this.callback(
// 当无法转换原内容时,给 Webpack 返回一个 Error
err: Error | null,
// 原内容转换后的内容
content: string | Buffer,
// 用于把转换后的内容得出原内容的 Source Map,方便调试
sourceMap?: SourceMap,
// 如果本次转换为原内容生成了 AST 语法树,可以把这个 AST 返回,
// 以方便之后需要 AST 的 Loader 复用该 AST,以避免重复生成 AST,提升性能
abstractSyntaxTree?: AST
);
Source Map 的生成很耗时,通常在开发环境下才会生成 Source Map,其它环境下不用生成,以加速构建。
为此 Webpack 为 Loader 提供了 this.sourceMap API 去告诉 Loader 当前构建环境下用户是否需要 Source Map。
如果你编写的 Loader 会生成 Source Map,请考虑到这点。
同步与异步
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;
以上代码中最关键的代码是最后一行 module.exports.raw = true;,没有该行 Loader 只能拿到字符串。
缓存加速
在有些情况下,有些转换操作需要大量计算非常耗时,如果每次构建都重新执行重复的转换操作,构建将会变得非常缓慢。
为此,Webpack 会默认缓存所有 Loader 的处理结果,也就是说在需要被处理的文件或者其依赖的文件没有发生变化时,
是不会重新调用对应的 Loader 去执行转换操作的。
如果你想让 Webpack 不缓存该 Loader 的处理结果,可以这样:
module.exports = function(source) {
// 关闭该 Loader 的缓存功能
this.cacheable(false);
return source;
};
还有一些loader API就不一一说了,想了解的同学,上面都有链接的,直接去官网看就好了。
下面附上git地址:
git源码地址
希望对你有所帮助哦~
最后手写不易,点个赞吧