美文网首页
loader简介

loader简介

作者: 薯条你哪里跑 | 来源:发表于2023-03-13 16:46 被阅读0次

    前言

    webpack 功能非常强大的前端工程化帮手,在纷杂的技术圈屹立不倒。能有如此地位少不了她的左膀右臂的帮忙----loader和plugin。loader可将非js代码转化成js模块化代码或者将内敛突降转换为dataurl,plugin可对js代码进行各种花式组合,双剑合璧天下无敌~
    本文主要针对loader进行展开,最近集中梳理了一下;

    四种类型

    loader有四种类型:pre、normal、inline、post,类型与执行顺序息息相关;
    其中的pre、normal、post是通过在webpack配置文件中的enforce字段进行配置。enforce默认是normal。

    module.exports = {
      module: {
        rules: [
          // normal loader
          {
            test: /\.(css|less)$/,
            use: ['normal1-loader', 'normal2-loader'],
          },
          // pre loader
          {
            test: /\.(css|less)$/,
            use: ['pre1-loader', 'pre2-loader'],
            enforce: 'pre',
          },
          // post loader
          {
            test: /\.(css|less)$/,
            use: ['post1-loader', 'post2-loader'],
            enforce: 'post',
          },
        ],
      },
    };
    

    另外一种 inline,顾名思义是在文件内部进行配置的

    import XXXX  from 'inline1-loader!inline2-loader!./index.js';
    

    与plugin执行从左到右不同(从上到下)不同,由于webpack内部是使用reduceRight方式进行处理,大家常说loader是从右到左(从下到上)执行的。但是这回答比较片面;


    两个阶段

    loader在处理文件资源时分为两个阶段: pitch阶段和nomral阶段。有种js冒泡捕获事件的感觉。

    每个loader的index文件,基本都是下面的结构。
    其中导出的loader本身就是normal阶段执行的代码,导出的pitch属性中的内容是在pitch阶段执行的;

    style-loader,normal阶段执行代码为空,均在pitch阶段执行
    less-loader,均在normal阶段执行代码,没有pitch阶段

    Pitching阶段的调用顺序: post > inline > normal > pre。
    Normal阶段的调用顺序:pre > normal > inline > post。

    在上面的代码示例中,我们执行的顺序其实是这样的:

    loader流程图.png

    在normal阶段的最后一个loader处理完成后,一定会生成js的模块化代码并返回供webpack使用。如果在pitch阶段有loader返回了非undefined的值就会发生熔断,即直接调头将这个值传递到normal阶段的loader。

    style-loader

    不论是less-loader还是postcss-loader都是在normal阶段对代码进行处理,只有style-loader的normal空空如也,所有的逻辑都在pitch中。为什么这么设计呢?

    首先style-loader的作用是将css代码插入到dom中。如果在normal而不是pitch阶段执行loader的话,当css-loader执行完毕,到style-loader执行时此时接收到的是个js字符串,无法完成将原始的css插入dom的工作。所以需要在style-loader里面去执行css-loader等,拿到处理过的css再插到dom中。

    举个例子,在项目中用到 style-loader、css-loader、postcss-loader、less-loader,
    src/pages/collectlist/index.js为例,其代码中引用用如下:

    无标题流程图 (4).png

    我们在style-loader的pitch中打印下:

    // pitch中接收到的requst
    module.exports.pitch = function (request) {
      console.log(request)
    }
    

    request内容为

     /项目/node_modules/`css-loader`/index.js??ref--6-1!/项目/node_modules/`postcss-loader`/src/index.js??postcss!/项目/node_modules/`less-loader`/dist/cjs.js??ref--6-3!/项目路径/src/pages/collectlist/index.less
    // 后续使用均会转换成inline loader, 例如:
    var content = require(" + loaderUtils.stringifyRequest(this, "!!" + request) + ")
    

    双叹号的意思是禁用配置文件中所有的loader
    request参数的含义是 剩余需要处理的loader

    插播,这里pitch支持接收三个参数,分别是
    • remainingRequest:剩余需要处理的loader(不包括自身)
    • previousRequest:已迭代过的loader(不包括自身)
    • data: normal和pitch传递数据的纽带,默认是个空对象

    再回到上述的例子,按照顺序将index.less进行处理转换,在编译后生成的入口js(pages/collectlist/index.js)中可以看到:

    // EXTERNAL MODULE: ./src/pages/collectlist/index.less
    var collectlist = __webpack_require__("Wehs");
    ...
    // EXTERNAL MODULE: ./src/components/Head/index.js + 4 modules
    var Head = __webpack_require__("rLZa");
    ...
    // EXTERNAL MODULE: ./src/components/Center_slide/index.js
    var Center_slide = __webpack_require__("0wbC");
    ...
    // EXTERNAL MODULE: ./src/pages/collectlist/components/house_event/index.less
    var house_event = __webpack_require__("Wn+z");
    ...
    // 还有一些遍历的依赖项
    // 比如:house_event/index.js中依赖的@/components/Collect/del
    // EXTERNAL MODULE: ./src/components/Collect/del.js  
    var del = __webpack_require__("pePU");
    ...
    //开始追collectlist/index.less的依赖,首先是style-loader
    /***/"Wehs": 
    /***/ (function(module, exports, __webpack_require__) {
    var content = __webpack_require__("W7or");
    ...
    // 经postcss-loader &less-loader 处理过的
    /***/"W7or": 
    /***/ (function(module, exports, __webpack_require__) {
    exports = module.exports = __webpack_require__("I1BE")(false);
    exports.push([module.i, "@charset \"utf-8\";\n\nhtml {\n  overflow-y: scroll;\n  -webkit-text-size-adjust: 100%;\n}\n.....close {\n  display: none;\n}\n", ""]);
    ...
    // css-loader 将转换过后的原生css导出模块,等待被引用
    /***/"I1BE":
    /***/ (function(module, exports, __webpack_require__) {
    module.exports = function(useSourceMap){...}
    function cssWithMappingToString(..){...}
    ...
    // 开始追Head js的依赖
    /***/"rLZa":
    /***/ (function(module, exports, __webpack_require__) {
    // EXTERNAL MODULE: ./src/components/Head/index.less
    var components_Head = __webpack_require__("HBZh");
    ...
    //style-loader
    /***/"HBZh": 
    /***/ (function(module, exports, __webpack_require__) {
    var content = __webpack_require__("ZbnD");
    ...
    //  经postcss-loader &less-loader 处理过的
    /***/"ZbnD"":  
    /***/ (function(module, exports, __webpack_require__) {
    exports = module.exports = __webpack_require__("I1BE")(false);
    exports.push([module.i, ".searchBar {\n  background: #fff;\n  height: 70px;\n  -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.13);\n...z-index: 100;\n}\n", ""]);
    ...
    /***/"0wbC":
    /***/ (function(module, exports, __webpack_require__) {
    var _index_less__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("Kyd0");
    ...
    //style-loader
    /***/"Kyd0":
    /***/ (function(module, exports, __webpack_require__) {
    var content = __webpack_require__("BZBj");
    ...
    /***/ "BZBj":
    /***/ (function(module, exports, __webpack_require__) {
    exports = module.exports = __webpack_require__("I1BE")(false);
    exports.push([module.i, "body .main-wrap,\nhtml .main-wrap {\n  margin-top: 20px;\n ...height: 100%;\n}\n.nav dd {\n  width: 100%;\n}\n", ""]);
    ...
    

    loader也分有同步和异步之分,在上面调试的过程中,我们能在 less-loader 和 postcss-loader中看到 :

    //postcss-loader
    const cb = this.async()
    // less-loader
    var loaderContext = this;
    var done = loaderContext.async();
    ...
    

    this.async()就是异步的关键,比如:

    const LoaderUtils = require("loader-utils");
    module.exports = function(source){
      const options = LoaderUtils.getOptions(this);
      setTimeout(() => {
            return  source.replace( "webpack ', options.name);
      },1000)
    };
    

    此时会报错,因为有settimeout异步操作导致无法正确返回,我们应该使用 this.async(),告诉webpack这个loader中有异步事件需要处理。

    module.exports = function(source){
      const options = LoaderUtils.getOptions(this);
     const cb = this.async();
      setTimeout(() => {
            const res =  source.replace( "webpack ', options.name);
            cb(null, res)
      },1000)
    

    运行正常~ 完美~~~

    相关文章

      网友评论

          本文标题:loader简介

          本文链接:https://www.haomeiwen.com/subject/tnualdtx.html