从黑盒和白盒两个角度讲讲如何写一个loader以及loader这个东西是如何在webpack中运作起来的。loader相当于一个翻译器,具体的loader会有很大不同,比如less-loader和ejs-loader内部代码肯定差异极大,这里只以最简单的loader举例,入坑以后大家根据自己需求自行探索。
本系列所有博客的测试代码可以点击。
所谓黑盒角度,就是我把webpack的运作过程当作一个黑盒,只从官网学习API。
官网告诉我们,loader就是一个export出来的function,这个函数接受的参数是源文件的字符串,返回经过“翻译”后的文件。这个过程可以是异步的。在函数内部,可以通过
获得loader API上的内容。
- this
例如:有多个结果的同步loader,多个结果指该loader不仅返回翻译后的文件,还需要执行一系列操作返回其他结果。
- module.exports = function(content) {
- this.callback(null, someSyncOperation(content), sourceMaps, ast);
- return; // always return undefined when calling callback()
- };
例如:异步loader
- module.exports = function(content) {
- var callback = this.async();
- someAsyncOperation(content, function(err, result) {
- if(err) return callback(err);
- callback(null, result);
- });
- };
例如:有多个结果的异步loader
- module.exports = function(content) {
- var callback = this.async();
- someAsyncOperation(content, function(err, result, sourceMaps, ast) {
- if(err) return callback(err);
- callback(null, result, sourceMaps, ast);
- });
- };
不再举例,API很简单,大家可以自己看看。
下面写一个最简单的loader,仅返回源文件原文,捋顺一下写loader的流程。
- module: {
- //加载器配置
- loaders:[
- {
- test: /\.tpl\.html$/,
- loader: 'html-template-loader'
- }
- ]
- }
我要去找
文件,打包前先通过我写的
- .tpl.html
进行翻译。于是我现在文件中写一个
- html-template-loader
文件,并在
- test.tpl.html
中
- index.js
它。
- require
- // index.js
- let tpl = require('./src/loaders/test.tpl.html');
- // test.tpl.html
- this is a template.
- what's wrong?
新建了一个
文件夹,我只在里面装了一个
- html-template-loader
。这个
- index.js
就是需要利用上面提到的loader API进行编写的地方了。
- index.js
- // node_modules/html-template-loader/index.js
- var _ = require('lodash');
- module.exports = function(source) {
- // console.log(source);
- var template = _.template(source + 'loader has worked!');
- return 'module.exports = ' + template;
- };
原谅我写的这么简单,毕竟我没有什么要写loader的需求,就不想陷进去。
只需这两步,你就可以进行打包了,打包成功证明我的loader工作了。
所谓白盒角度,就是我想弄明白webpack到底怎么把这个loader运行起来的,不再关心loader内部代码。
以常用的less编译打包为例看。配置文件:
- loaders:[
- {
- test: /\.less$/,loader: 'style-loader!CSS-loader!less-loader'
- }
- ]
这个配置文件翻译过来就是,遇到
结尾的文件,首先用less-loader翻译成css,再把css文本传入css-loader,来处理
- .less
和
- @import
等情况,再把结果传给style-loader,在最终的html文件中加入style标签。
- url()
用我上篇博客中写的plugin看看打包出来的chunk和modules:
那么模块2,3,4呢,为什么会多出这些模块?
答案大家都想得到,应该是less文件对应的三个loader增加的内容,下面去打包后的bundle文件中看看这些模块的内部代码。
- /******/
- (function(modules) { // webpackBootstrap
- /******/
- // The module cache
- /******/
- var installedModules = {};
- /******/
- // The require function
- /******/
- function __webpack_require__(moduleId) {
- /******/
- // 省略内部代码,这部分代码在webpack系列第一篇博客中我已经分析过了
- /******/
- return module.exports;
- /******/
- }
- /******/
- // 省略部分代码
- /******/
- return __webpack_require__(0);
- /******/
- })
- /************************************************************************/
- /******/
- ([
- /* 0 */
- /***/
- (function(module, exports, __webpack_require__) {
- /*
- * @Author: Cynthia
- * @Date: 2017-03-06 14:51:16
- * @Last Modified by: Cynthia
- * @Last Modified time: 2017-05-19 12:46:30
- */
- 'use strict';
- __webpack_require__(1);
- /***/
- }),
- /* 1 */
- /***/
- (function(module, exports, __webpack_require__) {
- // style-loader: Adds some css to the DOM by adding a <style> tag
- // load the styles
- var content = __webpack_require__(2);
- if (typeof content === 'string') content = [[module.id, content, '']];
- // add the styles to the DOM
- var update = __webpack_require__(4)(content, {});
- if (content.locals) module.exports = content.locals;
- // 省略部分代码
- }
- /***/
- }),
- /* 2 */
- /***/
- (function(module, exports, __webpack_require__) {
- exports = module.exports = __webpack_require__(3)();
- // imports
- // module
- exports.push([module.id, "h1 {\n color: blue;\n}\nh2 {\n background-color: silver;\n}\n", ""]);
- // exports
- /***/
- }),
- /* 3 */
- /***/
- (function(module, exports) {
- "use strict";
- /*
- MIT License http://www.opensource.org/licenses/mit-license.php
- Author Tobias Koppers @sokra
- */
- // css base code, injected by the css-loader
- module.exports = function() {
- var list = [];
- // return the list of modules as css string
- list.toString = function toString() {
- // 省略部分代码
- };
- // import a list of modules into the list
- list.i = function(modules, mediaQuery) {
- // 省略部分代码
- };
- /***/
- }),
- /* 4 */
- /***/
- (function(module, exports, __webpack_require__) {
- /*
- MIT License http://www.opensource.org/licenses/mit-license.php
- Author Tobias Koppers @sokra
- */
- var stylesInDom = {},
- memoize = function(fn) {
- // 省略部分代码
- },
- // 省略部分代码
- module.exports = function(list, options) {
- // 省略部分代码
- options = options || {};
- // 省略部分代码
- var styles = listToStyles(list);
- addStylesToDom(styles, options);
- return function update(newList) {
- // 省略部分代码
- };
- }
- function addStylesToDom(styles, options) {
- // 省略部分代码
- }
- function listToStyles(list) {
- // 省略部分代码
- }
- function insertStyleElement(options, styleElement) {
- // 省略部分代码
- }
- function removeStyleElement(styleElement) {
- // 省略部分代码
- }
- function createStyleElement(options) {
- // 省略部分代码
- }
- function createLinkElement(options) {
- // 省略部分代码
- }
- function addStyle(obj, options) {
- // 省略部分代码
- }
- update(obj);
- return function updateStyle(newObj) {
- // 省略部分代码
- }
- var replaceText = (function() {
- // 省略部分代码
- })();
- function applyToSingletonTag(styleElement, index, remove, obj) {
- // 省略部分代码
- }
- function applyToTag(styleElement, obj) {
- // 省略部分代码
- }
- function updateLink(linkElement, obj) {
- // 省略部分代码
- }
- /***/
- })
- /******/
- ]);
下图为几个模块的调用关系:
这里我们关心一下数据流。
和
- toString
,数组也是对象哟,可以在数组上用键值对的形式添加属性的
- i
中,这个变量的结构如下:
- content
- content = [
- ['2', 'css string', ''],
- toString //function ,
- i //function
- ]
控制台打印下content:
- stylesInDom = [
- {
- id: ,
- refs: ,
- parts: fucntion //这个函数用于更新样式,它的作用域链中保存着当前的样式,当更新样式时首先会对比新样式与当前样式的区别
- }
- ]
看看parts这个函数什么样:
模块4最终将stylesInDom转换成如下格式:
至此,大概理解了loader的大致运作。下面一个问题,这些loader是在webpack的哪个事件中触发的?
这个问题我查看源码并没有找到哪句话写到loader的触发,在这篇博客中提到:
我查看源码中监听这个事件的函数,大致有progressPlugin和uglifyPlugin两类,也没有发现触发loader的代码。
因为不想挖坑太深,这里保留这个疑问,以后开发中如果解决了这个问题再回来更新。
首先通过官网学习了如何写一个简单的loader,loader中this上挂载的一些API;然后分析打包后的文件,了解loader如何运行;最后提出一个疑问,loader是再webpack打包过程中哪个事件被触发、触发loader的代码写在哪?最后这个问题只解决了前一半,后一半有待探索。
来源: https://juejin.im/entry/5a02a1c651882531d827d804