loader

1. loader运行的总体流程

  • Compiler.js中会为将用户配置与默认配置合并,其中就包括了loader部分
  • webpack就会根据配置创建NormalModuleFactory,它可以用来创建NormalModule
  • 在工厂创建NormalModule实例之前还要通过loader的resolver来解析loader路径
  • 在NormalModule实例创建之后,则会通过其build方法来进行模块的构建。构建模块的第一步就是使用loader来加载并处理模块内容。而loader-runner这个库就是webpackloader的运行器
  • 最后,将loader处理完的模块内容输出,进入后续的编译流程
loader

2.babel-loader

属性
this.request /loaders/babel-loader.js!/src/index.js'
this.userRequest /src/index.js
this.rawRequest ./src/index.js
this.resourcePath /src/index.js
$ cnpm i @babel/preset-env @babel/core -D
const babel = require("@babel/core");
function loader(source,inputSourceMap) {
    //C:\webpack-analysis2\loaders\babel-loader.js!C:\webpack-analysis2\src\index.js
    const options = {
        presets: ['@babel/preset-env'],
        inputSourceMap:inputSourceMap,
        sourceMaps: true,//ourceMaps: true 是告诉 babel 要生成 sourcemap
        filename:this.request.split('!')[1].split('/').pop()
    }
    //在webpack.config.js中 增加devtool: 'eval-source-map'
    let {code,map,ast}=babel.transform(source,options);
    return this.callback(null,code,map,ast);
}
module.exports = loader;
resolveLoader: {
    alias: {//可以配置别名
      "babel-loader": resolve('./build/babel-loader.js')
    },//也可以配置loaders加载目录
    modules: [path.resolve('./loaders'), 'node_modules']
},
{
    test: /\.js$/,
    use:['babel-loader']
}

3.pitch

  • 比如a!b!c!module, 正常调用顺序应该是c、b、a,但是真正调用顺序是 a(pitch)、b(pitch)、c(pitch)、c、b、a,如果其中任何一个pitching loader返回了值就相当于在它以及它右边的loader已经执行完毕
  • 比如如果b返回了字符串"result b", 接下来只有a会被系统执行,且a的loader收到的参数是result b
  • loader根据返回值可以分为两种,一种是返回js代码(一个module的代码,含有类似module.export语句)的loader,还有不能作为最左边loader的其他loader
  • 有时候我们想把两个第一种loader chain起来,比如style-loader!css-loader!
    问题是css-loader的返回值是一串js代码,如果按正常方式写style-loader的参数就是一串代码字符串
  • 为了解决这种问题,我们需要在style-loader里执行require(css-loader!resources)

pitch与loader本身方法的执行顺序图

|- a-loader `pitch`
  |- b-loader `pitch`
    |- c-loader `pitch`
      |- requested module is picked up as a dependency
    |- c-loader normal execution
  |- b-loader normal execution
|- a-loader normal execution

3.1 loaders\loader1.js

loaders\loader1.js

function loader(source) {
    console.log('loader1',this.data);
    return source+"//loader1";
}
loader.pitch = function (remainingRequest,previousRequest,data) {
    data.name = 'pitch1';
    console.log('pitch1');
}
module.exports = loader;

3.2 loaders\loader2.js

loaders\loader2.js

function loader(source) {
    console.log('loader2');
    return source+"//loader2";
}
loader.pitch = function (remainingRequest,previousRequest,data) {
    console.log('remainingRequest=',remainingRequest);
    console.log('previousRequest=',previousRequest);
    console.log('pitch2');
    //return 'console.log("pitch2")';
}
module.exports = loader;

3.3 loaders\loader3.js

loaders\loader3.js

function loader(source) {
    console.log('loader3');
    return source+"//loader3";
}
loader.pitch = function () {
    console.log('pitch3');
}
module.exports = loader;

3.4 webpack.config.js

 {
    test: /\.js$/,
    use: ['loader1', 'loader2', 'loader3']
 }

4.loader-runner

4.1 loader类型

4.2 特殊配置

符号 变量 含义
-! noPreAutoLoaders 不要前置和普通loader Prefixing with -! will disable all configured preLoaders and loaders but not postLoaders
! noAutoLoaders 不要普通loader Prefixing with ! will disable all configured normal loaders
!! noPrePostAutoLoaders 不要前后置和普通loader,只要内联loader Prefixing with !! will disable all configured loaders (preLoaders, loaders, postLoaders)

4.2 查找规则执行

let path = require("path");
let nodeModules = path.resolve(__dirname, "node_modules");
let request = "-!inline-loader1!inline-loader2!./styles.css";
//首先解析出所需要的 loader,这种 loader 为内联的 loader
let inlineLoaders = request
  .replace(/^-?!+/, "")
  .replace(/!!+/g, "!")
  .split("!");
let resource = inlineLoaders.pop();//// 获取资源的路径
let resolveLoader = loader => path.resolve(nodeModules, loader);
//从相对路径变成绝对路径
inlineLoaders = inlineLoaders.map(resolveLoader);
let rules = [
  {
    enforce: "pre",
    test: /\.css?$/,
    use: ["pre-loader1", "pre-loader2"]
  },
  {
    test: /\.css?$/,
    use: ["normal-loader1", "normal-loader2"]
  },
  {
    enforce: "post",
    test: /\.css?$/,
    use: ["post-loader1", "post-loader2"]
  }
];
let preLoaders = [];
let postLoaders = [];
let normalLoaders = [];
for(let i=0;i<rules.length;i++){
    let rule = rules[i];
    if(rule.test.test(resource)){
        if(rule.enforce=='pre'){
          preLoaders.push(...rule.use);
        }else if(rule.enforce=='post'){
          postLoaders.push(...rule.use);
        }else{
          normalLoaders.push(...rule.use);   
        }
    }
}
preLoaders = preLoaders.map(resolveLoader);
postLoaders= postLoaders.map(resolveLoader);
normalLoaders = normalLoaders.map(resolveLoader);

let loaders = [];
//noPrePostAutoLoaders  忽略所有的 preLoader / normalLoader / postLoader
if(request.startsWith('!!')){
  loaders = inlineLoaders;//只保留inline
//noPreAutoLoaders 是否忽略 preLoader 以及 normalLoader
}else if(request.startsWith('-!')){
  loaders = [...postLoaders,...inlineLoaders];//只保留post和inline
//是否忽略 normalLoader  
}else if(request.startsWith('!')){
  loaders = [...postLoaders,...inlineLoaders,...preLoaders];//保留post inline pre
}else{
  loaders = [...postLoaders,...inlineLoaders,...normalLoaders,...preLoaders];
}
console.log(loaders);

4.4 run-loader

let readFile = require("fs");
let path = require("path");
function createLoaderObject(loader) {
  let obj = { data: {} };
  obj.request = loader;
  obj.normal = require(loader);
  obj.pitch = obj.normal.pitch;
  return obj;
}
function runLoaders(options, callback) {
  let loaderContext = {};
  let resource = options.resource;
  let loaders = options.loaders;
  loaders = loaders.map(createLoaderObject);
  loaderContext.loaderIndex = 0;
  loaderContext.readResource = readFile;
  loaderContext.resource = resource;
  loaderContext.loaders = loaders;
  let isSync = true;
  var innerCallback = (loaderContext.callback = function(err, args) {
    loaderContext.loaderIndex--;
    iterateNormalLoaders(loaderContext, args, callback);
  });
  loaderContext.async = function async() {
    isSync = false;
    return innerCallback;
  };
  Object.defineProperty(loaderContext, "request", {
    get: function() {
      return loaderContext.loaders
        .map(function(o) {
          return o.request;
        })
        .concat(loaderContext.resource)
        .join("!");
    }
  });
  Object.defineProperty(loaderContext, "remainingRequest", {
    get: function() {
      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", {
    get: function() {
      return loaderContext.loaders
        .slice(0, loaderContext.loaderIndex)
        .map(function(o) {
          return o.request;
        })
        .join("!");
    }
  });
  Object.defineProperty(loaderContext, "data", {
    get: function() {
      return loaderContext.loaders[loaderContext.loaderIndex].data;
    }
  });
  iteratePitchingLoaders(loaderContext, callback);
  function iteratePitchingLoaders(loaderContext, callback) {
    if (loaderContext.loaderIndex >= loaderContext.loaders.length) {
      loaderContext.loaderIndex--;
      return processResource(loaderContext, callback);
    }

    let currentLoaderObject = loaderContext.loaders[loaderContext.loaderIndex];
    let fn = currentLoaderObject.pitch;
    if (!fn) return iteratePitchingLoaders(options, loaderContext, callback);

    let args = fn.apply(loaderContext, [
      loaderContext.remainingRequest,
      loaderContext.previousRequest,
      currentLoaderObject.data
    ]);
    if (args) {
      loaderContext.loaderIndex--;
      return iterateNormalLoaders(loaderContext, args, callback);
    } else {
      loaderContext.loaderIndex++;
      iteratePitchingLoaders(loaderContext, callback);
    }
    function processResource(loaderContext, callback) {
      let buffer = loaderContext.readResource.readFileSync(
        loaderContext.resource,
        "utf8"
      );
      iterateNormalLoaders(loaderContext, buffer, callback);
    }
  }
  function iterateNormalLoaders(loaderContext, args, callback) {
    if (loaderContext.loaderIndex < 0) return callback(null, args);

    var currentLoaderObject = loaderContext.loaders[loaderContext.loaderIndex];
    var fn = currentLoaderObject.normal;
    if (!fn) {
      loaderContext.loaderIndex--;
      return iterateNormalLoaders(loaderContext, args, callback);
    }
    args = fn.apply(loaderContext, [args]);
    if (isSync) {
      loaderContext.loaderIndex--;
      iterateNormalLoaders(loaderContext, args, callback);
    }
  }
}

let entry = "./src/world.js";

let options = {
  resource: path.join(__dirname, entry),
  loaders: [
    path.join(__dirname, "loaders/loader1.js"),
    path.join(__dirname, "loaders/loader2.js"),
    path.join(__dirname, "loaders/loader3.js")
  ]
};

runLoaders(options, (err, result) => {
  console.log(result);
});

5. file

  • file-loader 并不会对文件内容进行任何转换,只是复制一份文件内容,并根据配置为他生成一个唯一的文件名。

5.1 file-loader

const { getOptions, interpolateName } = require('loader-utils');
function loader(content) {
  let options=getOptions(this)||{};
  let url = interpolateName(this, options.filename || "[hash].[ext]", {content});
  this.emitFile(url, content);
  return `module.exports = ${JSON.stringify(url)}`;
}
loader.raw = true;
module.exports = loader;
  • 通过 loaderUtils.interpolateName 方法可以根据 options.name 以及文件内容生成一个唯一的文件名 url(一般配置都会带上hash,否则很可能由于文件重名而冲突)
  • 通过 this.emitFile(url, content) 告诉 webpack 我需要创建一个文件,webpack会根据参数创建对应的文件,放在 public path 目录下
  • 返回 module.exports = ${JSON.stringify(url)},这样就会把原来的文件路径替换为编译后的路径

5.2 url-loader

let { getOptions } = require('loader-utils');
var mime = require('mime');
function loader(source) {
    let options=getOptions(this)||{};
    let { limit, fallback='file-loader' } = options;
    if (limit) {
      limit = parseInt(limit, 10);
    }
    const mimetype=mime.getType(this.resourcePath);
    if (!limit || source.length < limit) {
        let base64 = `data:${mimetype};base64,${source.toString('base64')}`;
        return `module.exports = ${JSON.stringify(base64)}`;
    } else {
        let fileLoader = require(fallback || 'file-loader');
        return fileLoader.call(this, source);
    }
}
loader.raw = true;
module.exports = loader;

6 css

6.1 less-loader.js

let less = require('less');
function loader(source) {
    let callback = this.async();
    less.render(source, { filename: this.resource }, (err, output) => {
        callback(err, output.css);
    });
}
module.exports = loader;

6.2 style-loader

let loaderUtils=require("loader-utils");
 function loader(source) {
    let script=(`
      let style = document.createElement("style");
      style.innerHTML = ${JSON.stringify(source)};
      document.head.appendChild(style);
    `);
    return script;
} 
module.exports = loader;
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,716评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,558评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,431评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,127评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,511评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,692评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,915评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,664评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,412评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,616评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,105评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,424评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,098评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,096评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,869评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,748评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,641评论 2 271

推荐阅读更多精彩内容