1.webpack介绍

  • Webpack 是一个前端资源加载/打包工具。它将根据模块的依赖关系进行静态分析,然后将这些模块按照指定的规则生成对应的静态资源。

webpack_intro

2. 预备知识

2.1 toStringTag

  • Symbol.toStringTag 是一个内置 symbol,它通常作为对象的属性键使用,对应的属性值应该为字符串类型,这个字符串用来表示该对象的自定义类型标签,通常只有内置的 Object.prototype.toString() 方法会去读取这个标签并把它包含在自己的返回值里。
console.log(Object.prototype.toString.call('foo')); // "[object String]"
console.log(Object.prototype.toString.call([1, 2])); // "[object Array]"
console.log(Object.prototype.toString.call(3)); // "[object Number]"
console.log(Object.prototype.toString.call(true)); // "[object Boolean]"
console.log(Object.prototype.toString.call(undefined)); // "[object Undefined]"
console.log(Object.prototype.toString.call(null)); // "[object Null]"
let myExports = {};
Object.defineProperty(myExports, Symbol.toStringTag, {
    value: 'Module'
});
console.log(Object.prototype.toString.call(myExports));

2.2 Object.create(null)

  • 使用 create 创建的对象,没有任何属性, 把它当作一个非常纯净的map来使用,我们可以自己定义 hasOwnPropertytoString 方法, 完全不必担心会将原型链上的同名方法覆盖掉
  • 在我们使用 for..in 循环的时候会遍历对象原型链上的属性,使用 create(null) 就不必再对属性进行检查了
var ns = Object.create(null);
if (typeof Object.create !== "function") {
    Object.create = function(proto) {
        function F() {}
        F.prototype = proto;
        return new F();
    };
}
console.log(ns)
console.log(Object.getPrototypeOf(ns));

2.3 getter

  • defineProperty 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象。
    • obj 要在其上定义属性的对象。
    • prop 要定义或修改的属性的名称。
    • descriptor 将被定义或修改的属性描述符。

2.3.1 描述符可同时具有的键值

configurable enumerable value writable get set
数据描述符 Yes Yes Yes Yes No No
存取描述符 Yes Yes No No Yes Yes

2.3.2 示例

var ageValue;
Object.defineProperty(obj, "age", {
    value: 10, //数据描述符和存取描述符不能混合使用
    get() {
        return ageValue;
    },
    set(newValue) {
        ageValue = newValue;
    }
    writable: true, //是否可修改
    enumerable: true, //是否可枚举
    configurable: true //是否可配置可删除
});

2. 同步加载

2.1 webpack.config.js

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
    mode: 'development',
    devtool: "none",
    context: process.cwd(),
    entry: './src/index.js',
    output: {
        filename: 'bundle.js'
    },
    devServer: {
        contentBase: path.resolve(__dirname, './dist')
    },
    module: {
        rules: [

            {
                test: /\.js$/,
                use: {
                    loader: "babel-loader",
                    options: {
                        presets: ["@babel/preset-env"]
                    }
                },
                include: path.join(__dirname, "src"),
                exclude: /node_modules/
            }
        ]
    },
    plugins: []
};

2.2 index.js

src\index.js

let title = require('./title.js');
console.log(title);

2.3 title.js

src\title.js

module.exports = "title";

2.4 打包文件分析

(function(modules) {
    // webpack的启动函数
    //模块的缓存
    var installedModules = {};

    //定义在浏览器中使用的require方法
    function __webpack_require__(moduleId) {
        //检查模块是否在缓存中
        if (installedModules[moduleId]) {
            return installedModules[moduleId].exports;
        }
        //创建一个新的模块并且放到模块的缓存中
        var module = (installedModules[moduleId] = {
            i: moduleId,
            l: false,
            exports: {}
        });

        //执行模块函数
        modules[moduleId].call(
            module.exports,
            module,
            module.exports,
            __webpack_require__
        );

        //把模块设置为已经加载
        module.l = true;

        //返回模块的导出对象
        return module.exports;
    }

    //暴露出模块对象
    __webpack_require__.m = modules;

    //暴露出模块缓存
    __webpack_require__.c = installedModules;

    //为harmony导出定义getter函数
    __webpack_require__.d = function(exports, name, getter) {
        if (!__webpack_require__.o(exports, name)) {
            Object.defineProperty(exports, name, {
                enumerable: true,
                get: getter
            });
        }
    };

    //在导出对象上定义__esModule属性
    __webpack_require__.r = function(exports) {
        if (typeof Symbol !== "undefined" && Symbol.toStringTag) {
            Object.defineProperty(exports, Symbol.toStringTag, {
                value: "Module"
            });
        }
        Object.defineProperty(exports, "__esModule", {
            value: true
        });
    };

    /**
     * 创建一个模拟的命名空间对象
     * mode & 1 value是模块ID直接用__webpack_require__加载
     * mode & 2 把所有的属性合并到命名空间ns上
     * mode & 4 当已经是命名空间的时候(__esModule=true)可以直接返回值
     * mode & 8|1 行为类似于require
     */
    __webpack_require__.t = function(value, mode) {
        if (mode & 1) value = __webpack_require__(value);
        if (mode & 8) return value;
        if (mode & 4 && typeof value === "object" && value && value.__esModule)
            return value;
        var ns = Object.create(null); //定义一个空对象
        __webpack_require__.r(ns);
        Object.defineProperty(ns, "default", {
            enumerable: true,
            value: value
        });
        if (mode & 2 && typeof value != "string")
            for (var key in value)
                __webpack_require__.d(
                    ns,
                    key,
                    function(key) {
                        return value[key];
                    }.bind(null, key)
                );
        return ns;
    };

    // getDefaultExport函数为了兼容那些非non-harmony模块
    __webpack_require__.n = function(module) {
        var getter =
            module && module.__esModule ?
            function getDefault() {
                return module["default"];
            } :
            function getModuleExports() {
                return module;
            };
        __webpack_require__.d(getter, "a", getter);
        return getter;
    };

    //判断对象身上是否拥有此属性
    __webpack_require__.o = function(object, property) {
        return Object.prototype.hasOwnProperty.call(object, property);
    };

    //公共路径
    __webpack_require__.p = "";

    //加载入口模块并且返回导出对象
    return __webpack_require__((__webpack_require__.s = "./src/index.js"));
})({
    "./src/index.js": function(module, exports, __webpack_require__) {
        var title = __webpack_require__("./src/title.js");
        console.log(title);
    },
    "./src/title.js": function(module, exports) {
        module.exports = "title";
    }
});

2.5 实现

(function(modules) {
    var installedModules = {};

    function __webpack_require__(moduleId) {
        if (installedModules[moduleId]) {
            return installedModules[moduleId];
        }
        var module = installedModules[moduleId] = {
            i: moduleId,
            l: false,
            exports: {}
        }
        modules[moduleId].call(modules.exports, module, module.exports, __webpack_require__);
        module.l = true;
        return module.exports;
    }
    return __webpack_require__((__webpack_require__.s = "./src/index.js"));
})({
    "./src/index.js": function(module, exports, __webpack_require__) {
        var title = __webpack_require__('./src/title.js');
        console.log(title);
    },
    "./src/title.js": function(module, exports) {
        module.exports = "title";
    }
})

3.harmony

3.1 common.js加载 common.js

3.1.1 index.js

let title = require('./title');
console.log(title.name);
console.log(title.age);

3.1.2 title.js

exports.name = 'title_name';
exports.age = 'title_age';

3.1.3 bundle.js

{
    "./src/index.js": (function(module, exports, __webpack_require__) {
        var title = __webpack_require__("./src/title.js");
        console.log(title.name);
        console.log(title.age);
    }),
    "./src/title.js": (function(module, exports) {
        exports.name = 'title_name';
        exports.age = 'title_age';
    })
}

3.2 common.js加载 ES6 modules

3.2.1 index.js

let title = require('./title');
console.log(title.name);
console.log(title.age);

3.2.2 title.js

exports.name = 'title_name';
exports.age = 'title_age';

3.2.3 bundle.js

{
    "./src/index.js": (function(module, exports, __webpack_require__) {
        var title = __webpack_require__("./src/title.js");
        console.log(title["default"]);
        console.log(title.age);
    }),
    "./src/title.js": (function(module, __webpack_exports__, __webpack_require__) {
        __webpack_require__.r(__webpack_exports__); //__esModule=true
        __webpack_require__.d(__webpack_exports__, "age", function() {
            return age;
        });
        __webpack_exports__["default"] = 'title_name';
        var age = 'title_age';
    })
}

3.3 ES6 modules 加载 ES6 modules

3.3.1 index.js

import name, {
    age
} from './title';
console.log(name);
console.log(age);

3.3.2 title.js

export default name = 'title_name';
export const age = 'title_age';

3.3.3 bundle.js

{
    "./src/index.js": (function(module, __webpack_exports__, __webpack_require__) {
        __webpack_require__.r(__webpack_exports__); //__esModule=true
        var _title__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./src/title.js");
        console.log(_title__WEBPACK_IMPORTED_MODULE_0__["default"]);
        console.log(_title__WEBPACK_IMPORTED_MODULE_0__["age"]);
    }),
    "./src/title.js": (function(module, __webpack_exports__, __webpack_require__) {
        __webpack_require__.r(__webpack_exports__); //__esModule=true
        __webpack_require__.d(__webpack_exports__, "age", function() {
            return age;
        });
        __webpack_exports__["default"] = 'title_name';
        var age = 'title_age';
    })
}

3.4 ES6 modules 加载 common.js

3.4.1 index.js

import name, {
    age
} from './title';
console.log(name);
console.log(age);

3.4.2 title.js

export default name = 'title_name';
export const age = 'title_age';

3.4.3 bundle.js

{
    "./src/index.js": (function(module, __webpack_exports__, __webpack_require__) {
        __webpack_require__.r(__webpack_exports__); //__esModule=true
        /* 兼容common.js导出 */
        var _title__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./src/title.js");
        /* 兼容common.js导出 */
        var _title__WEBPACK_IMPORTED_MODULE_0___default = __webpack_require__.n(_title__WEBPACK_IMPORTED_MODULE_0__);
        console.log(_title__WEBPACK_IMPORTED_MODULE_0___default.a.name);
        console.log(_title__WEBPACK_IMPORTED_MODULE_0___default.a.age);
    }),
    "./src/title.js": (function(module, __webpack_exports__, __webpack_require__) {
        __webpack_exports__.name = 'title_name';
        __webpack_exports__.age = 'title_age';
    }),
    "./src/title_esm.js": (function(module, __webpack_exports__, __webpack_require__) {
        __webpack_require__.r(__webpack_exports__); //__esModule=true
        __webpack_exports__.name = 'title_name';
        __webpack_exports__.age = 'title_age';
        __webpack_exports__.default = {
            name: 'default_name',
            age: 'default_age'
        };
    })
}

4. 异步加载

4.1 index.html

< body >
    <
    script src = "entry1.js" > < /script> <
script >
    setTimeout(function() {
        let script = document.createElement('script');
        script.src = 'entry2.js';
        document.body.appendChild(script);
    }, 3000); <
/script>    < /
body >

4.2 entry1.js

src\index.js

let button = document.createElement("button");
button.innerHTML = "点我1";
button.onclick = function() {
    import( /*webpackChunkName: 'title'*/ './title.js').then(function(result) {
        console.log(result.default);
    });
};
document.body.appendChild(button);

4.3 entry2.js

src\index.js

let button = document.createElement("button");
button.innerHTML = "点我2";
button.onclick = function() {
    import( /*webpackChunkName: 'title'*/ './title.js').then(function(result) {
        console.log(result.default);
    });
};
document.body.appendChild(button);

4.4 title.js

src\title.js

module.exports = 'title';

4.5 bundle.js

(function(modules) {
    //通过JSONP加载额外的模块
    function webpackJsonpCallback(data) {
        var chunkIds = data[0]; //代码块的IDS
        var moreModules = data[1]; //额外的模块
        //把moreModules添加到modules对象中,然后把所有的chunkIds设置为已加载并触发callback函数
        var moduleId, chunkId, i = 0,
            resolves = [];
        for (; i < chunkIds.length; i++) {
            chunkId = chunkIds[i];
            if (Object.prototype.hasOwnProperty.call(installedChunks, chunkId) && installedChunks[chunkId]) {
                resolves.push(installedChunks[chunkId][0]);
            }
            installedChunks[chunkId] = 0;
        }
        for (moduleId in moreModules) {
            if (Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
                modules[moduleId] = moreModules[moduleId];
            }
        }
        if (parentJsonpFunction) parentJsonpFunction(data);
        while (resolves.length) {
            resolves.shift()();
        }
    };

    //模块缓存
    var installedModules = {};
    // 存储加载中和加载过的chunks对象
    // 存储加载中和加载过的chunks对象
    // chunk undefined(未加载)  null (预加载/预获取) Promise (加载中)  0 加载完成
    var installedChunks = {
        "main": 0 //刚开始只加载main
    };

    //返回要加载的代码块的路径
    function jsonpScriptSrc(chunkId) {
        return __webpack_require__.p + "" + chunkId + ".bundle.js"
    }

    // webpack自已实现的 require方法
    function __webpack_require__(moduleId) {
        if (installedModules[moduleId]) {
            return installedModules[moduleId].exports;
        }
        var module = installedModules[moduleId] = {
            i: moduleId,
            l: false,
            exports: {}
        };

        modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);

        module.l = true;

        return module.exports;
    }

    //异步模块加载函数,如果没有再缓存模块中 则用jsonscriptsrc 加载
    //此主文件中只包含入口代码块,此函数用来加载额外的代码块
    __webpack_require__.e = function requireEnsure(chunkId) {
        var promises = [];
        //JSONP代码块加载
        var installedChunkData = installedChunks[chunkId];
        if (installedChunkData !== 0) { //0表示已经安装
            //如果是一个Promise表示正在安装或加载,添加到promises数组中
            if (installedChunkData) {
                promises.push(installedChunkData[2]);
            } else { //否则就是未加载
                //在chunk缓存中设置Promise
                var promise = new Promise(function(resolve, reject) {
                    installedChunkData = installedChunks[chunkId] = [resolve, reject];
                });
                promises.push(installedChunkData[2] = promise);

                //开始加载代码块
                var script = document.createElement('script');
                var onScriptComplete;

                script.charset = 'utf-8';
                script.timeout = 120;
                //表明脚本需要安全加载 CSP 策略
                if (__webpack_require__.nc) {
                    script.setAttribute("nonce", __webpack_require__.nc);
                }
                script.src = jsonpScriptSrc(chunkId);

                // create error before stack unwound to get useful stacktrace later
                var error = new Error();
                onScriptComplete = function(event) {
                    // avoid mem leaks in IE.
                    script.onerror = script.onload = null;
                    clearTimeout(timeout);
                    var chunk = installedChunks[chunkId];
                    if (chunk !== 0) {
                        if (chunk) {
                            var errorType = event && (event.type === 'load' ? 'missing' : event.type);
                            var realSrc = event && event.target && event.target.src;
                            error.message = 'Loading chunk ' + chunkId + ' failed.\n(' + errorType + ': ' + realSrc + ')';
                            error.name = 'ChunkLoadError';
                            error.type = errorType;
                            error.request = realSrc;
                            chunk[1](error);
                        }
                        installedChunks[chunkId] = undefined;
                    }
                };
                var timeout = setTimeout(function() {
                    onScriptComplete({
                        type: 'timeout',
                        target: script
                    });
                }, 120000);
                script.onerror = script.onload = onScriptComplete;
                document.head.appendChild(script);
            }
        }
        return Promise.all(promises);
    };

    // 所有构建生成的模块
    __webpack_require__.m = modules;

    // expose the module cache
    __webpack_require__.c = installedModules;

    // 设定getter 辅助函数
    __webpack_require__.d = function(exports, name, getter) {
        if (!__webpack_require__.o(exports, name)) {
            Object.defineProperty(exports, name, {
                enumerable: true,
                get: getter
            });
        }
    };

    // 给exports设定__esModule属性
    __webpack_require__.r = function(exports) {
        if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {
            Object.defineProperty(exports, Symbol.toStringTag, {
                value: 'Module'
            });
        }
        Object.defineProperty(exports, '__esModule', {
            value: true
        });
    };

    // 用于取值,伪造namespace
    // mode & 1: 值是一个模块ID,加载它
    // mode & 2: 把value所有的属性合并到ns上
    // mode & 4: 如果ns对象已经是一个对象了,则返回值
    // mode & 8|1: 类似于require
    __webpack_require__.t = function(value, mode) {
        if (mode & 1) value = __webpack_require__(value);
        if (mode & 8) return value;
        if ((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
        var ns = Object.create(null);
        __webpack_require__.r(ns);
        Object.defineProperty(ns, 'default', {
            enumerable: true,
            value: value
        });
        if (mode & 2 && typeof value != 'string')
            for (var key in value) __webpack_require__.d(ns, key, function(key) {
                return value[key];
            }.bind(null, key));
        return ns;
    };

    //用于兼容性取值(es module 取default, 非es module 直接返回module)
    __webpack_require__.n = function(module) {
        var getter = module && module.__esModule ?
            function getDefault() {
                return module['default'];
            } :
            function getModuleExports() {
                return module;
            };
        __webpack_require__.d(getter, 'a', getter);
        return getter;
    };

    // 辅助函数 Object.prototype.hasOwnProperty.call
    __webpack_require__.o = function(object, property) {
        return Object.prototype.hasOwnProperty.call(object, property);
    };

    //公共路径,为所有资源指定一个基础路径
    __webpack_require__.p = "";

    // 异步加载失败处理函数 辅助函数
    __webpack_require__.oe = function(err) {
        console.error(err);
        throw err;
    };
    //获取全局的webpackJsonp函数,第一次执行此函数就是一个空的数组
    var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || [];
    //在替换其push函数之前会将原有的push方法保存为oldJsonpFunction,同时
    var oldJsonpFunction = jsonpArray.push.bind(jsonpArray);
    //把jsonpArray的push等于当前的webpackJsonpCallback
    jsonpArray.push = webpackJsonpCallback;
    //把数组克隆一份
    jsonpArray = jsonpArray.slice();
    //循环这个数组,把数组中的所有的元素传给webpackJsonpCallback
    //如果把以前懒加载过的模块在自己身上安装一下,就不用再异步加载了
    for (var i = 0; i < jsonpArray.length; i++)
        webpackJsonpCallback(jsonpArray[i]);
    //把上一个oldJsonpFunction赋给parentJsonpFunction,第一次的时候就是push方法
    var parentJsonpFunction = oldJsonpFunction;
    //加载入口模块并返回exports导出对象
    return __webpack_require__(__webpack_require__.s = "./src/index.js");
})
({
    "./src/index.js": (function(module, exports, __webpack_require__) {

        var button = document.createElement("button");
        button.innerHTML = "点我";

        button.onclick = function() {
            __webpack_require__.e( /*! import() | title */ "title").then(__webpack_require__.t.bind(null, /*! ./title.js */ "./src/title.js", 7)).then(function(result) {
                console.log(result["default"]);
            });
        };
        document.body.appendChild(button);
    })
});

4.6 title.bundle.js

(window["webpackJsonp"] = window["webpackJsonp"] || []).push([
    ["title"], {
        "./src/title.js": (function(module, exports) {
            module.exports = 'title';
        })
    }
]);

4.7 实现bundle.js

(function(modules) {
    function webpackJsonpCallback(data) {
        var chunkIds = data[0];
        var moreModules = data[1];
        var moduleId, chunkId, i = 0,
            resolves = [];
        for (; i < chunkIds.length; i++) {
            chunkId = chunkIds[i];
            if (installedChunks[chunkId]) {
                resolves.push(installedChunks[chunkId][0]);
            }
            installedChunks[chunkId] = 0;
        }
        for (moduleId in moreModules) {
            modules[moduleId] = moreModules[moduleId];
        }
        if (parentJsonpFunction) parentJsonpFunction(data);
        while (resolves.length) {
            resolves.shift()();
        }
    }
    var installedModules = {};
    var installedChunks = {
        main: 0
    };
    __webpack_require__.p = "";

    function jsonpScriptSrc(chunkId) {
        return __webpack_require__.p + "" + chunkId + ".bundle.js";
    }

    function __webpack_require__(moduleId) {
        if (installedModules[moduleId]) {
            return installedModules[moduleId].exports;
        }
        var module = (installedModules[moduleId] = {
            i: moduleId,
            l: false,
            exports: {}
        });
        modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
        module.l = true;
        return module.exports;
    }
    __webpack_require__.t = function(value, mode) {
        value = __webpack_require__(value);
        var ns = Object.create(null);
        Object.defineProperty(ns, "__esModule", {
            value: true
        });
        Object.defineProperty(ns, "default", {
            enumerable: true,
            value: value
        });
        return ns;
    };

    __webpack_require__.e = function requireEnsure(chunkId) {
        var promises = [];
        var installedChunkData = installedChunks[chunkId];
        var promise = new Promise(function(resolve, reject) {
            installedChunkData = installedChunks[chunkId] = [resolve, reject];
        });
        promises.push((installedChunkData[2] = promise));
        var script = document.createElement("script");
        script.src = jsonpScriptSrc(chunkId);
        document.head.appendChild(script);
        return Promise.all(promises);
    }
    var jsonpArray = (window["webpackJsonp"] = window["webpackJsonp"] || []);
    var oldJsonpFunction = jsonpArray.push.bind(jsonpArray);
    jsonpArray.push = webpackJsonpCallback;
    var parentJsonpFunction = oldJsonpFunction;
    return __webpack_require__((__webpack_require__.s = "./src/index.js"));
})({
    "./src/index.js": function(module, exports, __webpack_require__) {
        let button = document.createElement("button");
        button.innerHTML = "点我1";
        button.onclick = function() {
            __webpack_require__.e("title").then(
                __webpack_require__.t.bind(null, "./src/title.js", 7)
            ).then(function(result) {
                console.log(result.default);
            });
        };
        document.body.appendChild(button);
    }
});

5.loader运行的总体流程

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

loader

6.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']
}

7.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

7.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;

7.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;

7.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;

7.4 webpack.config.js

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

8.loader-runner

8.1 loader类型

8.2 特殊配置

符号 变量 含义
-! noPreAutoLoaders 不要前置和普通loader
! noAutoLoaders 不要普通loader
!! noPrePostAutoLoaders 不要前后置和普通loader, 只要内联loader

8.3 查找规则执行

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);

8.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);
});

9.file

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

9.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)} , 这样就会把原来的文件路径替换为编译后的路径

9.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;

10 css

10.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;

10.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;
Last Updated: 2019-10-22 16:19:16