基础前端React

梳理 webpack打包的逻辑过程

2019-12-29  本文已影响0人  CondorHero
一、前置知识了解
  1. eval
    简单理解就是执行字符串里面的JavaScript程序。例如:
let str = "console.log(1 + 1)"
eval(str);
控制台打印出 2
  1. Object.defineProperty

Object.defineProperty()的作用就是直接在一个对象上定义一个新属性,或者修改一个已经存在的属性,使用语法如下:

Object.defineProperty(obj, prop, desc);
obj 需要定义属性的当前对象
prop 当前需要定义的属性名
desc 属性描述符
属性描述符大约如上图
  1. 给一个对象设置一个值或者改写这个值
var obj = {};

Object.defineProperty(obj,"sex",{
    value:"女"
});
console.log(obj.sex);//女
  1. 属性是否可写

上面的例子我们改写如下:

var obj = {};

Object.defineProperty(obj,"sex",{
    value:"女"
});
obj.sex = "男";
console.log(obj.sex);//女

输出的还是女,这是因为 writeable 默认为 false ,表示这个属性不能更改。我们把这个属性设置为 true 就行了。

var obj = {};

Object.defineProperty(obj,"sex",{
    value:"女",
    writable:true
});
obj.sex = "男";
console.log(obj.sex);//男
  1. get 和 set
var obj = {sex:"233"};

Object.defineProperty(obj,"sex",{
    get:()=>{
        return "男"
    },
    set:(v)=>{
        console.log(v);//男
        return "不男不女";
    }
});
console.log(obj.sex);//男
obj.sex = "男";

读取的时候触发 get 函数,返回值作为读取的值,更改 obj 属性的时候,返回值作为读取的值,其中接收的第一个参数是 oldValue。

  1. 属性是否可以遍历
var obj = {
    name:"Condor Hero",
    age : 18,
    sex : 233
};
Object.defineProperty(obj,"sex",{
    get:()=>{
        return "男"
    },
    set:(v)=>{
        console.log(v);//男
        return "不男不女";
    },
    enumerable:false
});
for( let k in obj){
    console.log(`我的key是${k},我的value是${obj[k]}`);
}
//运算结果
//我的key是name,我的value是Condor Hero
//我的key是age,我的value是18

sex 并没有给遍历出来。要想便利出来 enumerable:false 改为 true 或者不写都行。

二、认识项目目录和项目文件

新建一个 app 文件作为项目文件,项目目录下的文件如下:

┣✈mian.js //主函数
┣✈index.html //在浏览器运行 bundle.js
┣✈circle.js //求圆的面积
┣✈rectangle.js //求长方形的面积函数
┣✈bundle.js //打包之后的文件
┣✈webpack.config.js //webpack编译依赖文件

来看看项目文件的内容:
index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>webpack执行机理</title>
    <style>
        html,body{
            width: 100%;
            height: 100%;
            overflow: hidden;
            background-color: #cfc;
        }
    </style>
</head>
<body>
    <script src="./bundle.js"></script>
</body>
</html>

rectangle.js

// 求长方形的面积
function rect(a , b){
    return a * b;
};

export default rect;

circle.js

// 求圆的面积
function circle(r){
    return Math.PI * r ** 2;
};

export default circle;

main.js

import rect from "./rectangle.js";
import circle from "./circle.js";

function main(){
    // 长方形的面积 5 X 10 = 50;
    const r = rect(5 , 10);

    // 圆的面积 πX10X10≈314
    const c = circle(10);

    // 浏览器弹出结果
    alert(r);
    alert(c);
};

main();

webpack.config.js

const path = require("path");

module.exports = {
    mode: "development",

    entry: "./main.js",

    output: {
        path: __dirname,
        filename: "bundle.js"
    }
}

这时候在项目目录运行 CMD 窗口,使用 webpack 进行编译:

webpack main.js -o bundle.js
C:\Users\Administrator\Desktop\app>webpack -v
4.41.5

C:\Users\Administrator\Desktop\app>webpack
Hash: c1528ceb89195bae839e
Version: webpack 4.41.5
Time: 225ms
Built at: 2019-12-29 12:19:08 AM
    Asset      Size  Chunks             Chunk Name
bundle.js  5.37 KiB    main  [emitted]  main
Entrypoint main = bundle.js
[./circle.js] 96 bytes {main} [built]
[./main.js] 279 bytes {main} [built]
[./rectangle.js] 91 bytes {main} [built]

项目文件夹,这时候会自动生成一个 bundle.js 文件,这时候直接在浏览器打开 index.html。就会看到下面的情况。


三、bundle.js 文件的执行逻辑分析

因为在 webpack.config.js 中设置 mode 为 开发环境,所以 bundle.js 没有被压缩。

/******/ (function(modules) { // webpackBootstrap
/******/    // The module cache
/******/    var installedModules = {};
/******/
/******/    // The require function
/******/    function __webpack_require__(moduleId) {
/******/
/******/        // Check if module is in cache
/******/        if(installedModules[moduleId]) {
/******/            return installedModules[moduleId].exports;
/******/        }
/******/        // Create a new module (and put it into the cache)
/******/        var module = installedModules[moduleId] = {
/******/            i: moduleId,
/******/            l: false,
/******/            exports: {}
/******/        };
/******/
/******/        // Execute the module function
/******/        modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/        // Flag the module as loaded
/******/        module.l = true;
/******/
/******/        // Return the exports of the module
/******/        return module.exports;
/******/    }
/******/
/******/
/******/    // expose the modules object (__webpack_modules__)
/******/    __webpack_require__.m = modules;
/******/
/******/    // expose the module cache
/******/    __webpack_require__.c = installedModules;
/******/
/******/    // define getter function for harmony exports
/******/    __webpack_require__.d = function(exports, name, getter) {
/******/        if(!__webpack_require__.o(exports, name)) {
/******/            Object.defineProperty(exports, name, { enumerable: true, get: getter });
/******/        }
/******/    };
/******/
/******/    // define __esModule on exports
/******/    __webpack_require__.r = function(exports) {
/******/        if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/            Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/        }
/******/        Object.defineProperty(exports, '__esModule', { value: true });
/******/    };
/******/
/******/    // create a fake namespace object
/******/    // mode & 1: value is a module id, require it
/******/    // mode & 2: merge all properties of value into the ns
/******/    // mode & 4: return value when already ns object
/******/    // mode & 8|1: behave like 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 function for compatibility with non-harmony modules
/******/    __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_public_path__
/******/    __webpack_require__.p = "";
/******/
/******/
/******/    // Load entry module and return exports
/******/    return __webpack_require__(__webpack_require__.s = "./main.js");
/******/ })
/************************************************************************/
/******/ ({

/***/ "./circle.js":
/*!*******************!*\
  !*** ./circle.js ***!
  \*******************/
/*! exports provided: default */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
eval("__webpack_require__.r(__webpack_exports__);\n// 求圆的面积\r\nfunction circle(r){\r\n\treturn Math.PI * r ** 2;\r\n};\r\n\r\n/* harmony default export */ __webpack_exports__[\"default\"] = (circle);\n\n//# sourceURL=webpack:///./circle.js?");

/***/ }),

/***/ "./main.js":
/*!*****************!*\
  !*** ./main.js ***!
  \*****************/
/*! no exports provided */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _rectangle_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./rectangle.js */ \"./rectangle.js\");\n/* harmony import */ var _circle_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./circle.js */ \"./circle.js\");\n\r\n\r\n\r\nfunction main(){\r\n\t// 长方形的面积 5 X 10 = 50;\r\n\tconst r = Object(_rectangle_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"])(5 , 10);\r\n\r\n\t// 圆的面积 πX10X10≈314\r\n\tconst c = Object(_circle_js__WEBPACK_IMPORTED_MODULE_1__[\"default\"])(10);\r\n\r\n\t// 浏览器弹出结果\r\n\talert(r);\r\n\talert(c);\r\n};\r\n\r\nmain();\n\n//# sourceURL=webpack:///./main.js?");

/***/ }),

/***/ "./rectangle.js":
/*!**********************!*\
  !*** ./rectangle.js ***!
  \**********************/
/*! exports provided: default */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
eval("__webpack_require__.r(__webpack_exports__);\n// 求长方形的面积\r\nfunction rect(a , b){\r\n\treturn a * b;\r\n};\r\n\r\n/* harmony default export */ __webpack_exports__[\"default\"] = (rect);\n\n//# sourceURL=webpack:///./rectangle.js?");

/***/ })

/******/ });

连代码加注释是一百二十五行,文件里面是个大的 IIFE。这逻辑是通的webpack是为了模块化开发,解决浏览器不认识 export import ES6 的语法,但是又不能丧失模块开发功能所以 IIFE 是个最好的选择。

去掉一些注释大致文件如下:

(function(modules) { // webpackBootstrap
    // The module cache
    var installedModules = {};

    // The require function
    function __webpack_require__(moduleId) {

        // Check if module is in cache
        if (installedModules[moduleId]) {
            return installedModules[moduleId].exports;
        }
        // Create a new module (and put it into the cache)
        var module = installedModules[moduleId] = {
            i: moduleId,
            l: false,
            exports: {}
        };

        // Execute the module function
        modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);

        // Flag the module as loaded
        module.l = true;

        // Return the exports of the module
        return module.exports;
    }


    // expose the modules object (__webpack_modules__)
    __webpack_require__.m = modules;

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

    // define getter function for harmony exports
    __webpack_require__.d = function(exports, name, getter) {
        if (!__webpack_require__.o(exports, name)) {
            Object.defineProperty(exports, name, { enumerable: true, get: getter });
        }
    };

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

    // create a fake namespace object
    // mode & 1: value is a module id, require it
    // mode & 2: merge all properties of value into the ns
    // mode & 4: return value when already ns object
    // mode & 8|1: behave like 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 function for compatibility with non-harmony modules
    __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_public_path__
    __webpack_require__.p = "";


    // Load entry module and return exports
    return __webpack_require__(__webpack_require__.s = "./main.js");
})({
    "./circle.js": (function(module, __webpack_exports__, __webpack_require__) {
        "use strict";
        eval("__webpack_require__.r(__webpack_exports__);\n// 求圆的面积\r\nfunction circle(r){\r\n\treturn Math.PI * r ** 2;\r\n};\r\n\r\n/* harmony default export */ __webpack_exports__[\"default\"] = (circle);\n\n//# sourceURL=webpack:///./circle.js?");
    }),
    "./main.js": (function(module, __webpack_exports__, __webpack_require__) {
        "use strict";
        eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _rectangle_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./rectangle.js */ \"./rectangle.js\");\n/* harmony import */ var _circle_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./circle.js */ \"./circle.js\");\n\r\n\r\n\r\nfunction main(){\r\n\t// 长方形的面积 5 X 10 = 50;\r\n\tconst r = Object(_rectangle_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"])(5 , 10);\r\n\r\n\t// 圆的面积 πX10X10≈314\r\n\tconst c = Object(_circle_js__WEBPACK_IMPORTED_MODULE_1__[\"default\"])(10);\r\n\r\n\t// 浏览器弹出结果\r\n\talert(r);\r\n\talert(c);\r\n};\r\n\r\nmain();\n\n//# sourceURL=webpack:///./main.js?");
    }),
    "./rectangle.js": (function(module, __webpack_exports__, __webpack_require__) {
        "use strict";
        eval("__webpack_require__.r(__webpack_exports__);\n// 求长方形的面积\r\nfunction rect(a , b){\r\n\treturn a * b;\r\n};\r\n\r\n/* harmony default export */ __webpack_exports__[\"default\"] = (rect);\n\n//# sourceURL=webpack:///./rectangle.js?");
    })

});

来看程序大致的执行思路:

i: moduleId,//函数的ID
l: false,//是否已经执行loading
exports: {}//创建一个空对象

我们在改造下代码,不动主要逻辑,只是让我们更容易看懂。

(function(modules) {
    // 缓存
    var installedModules = {};

    // wr函数
    function wr(moduleId) {

        // 是否函数已经执行
        if (installedModules[moduleId]) {
            return installedModules[moduleId].exports;
        }
        //创建一个对象,并放入缓存
        var module = installedModules[moduleId] = {
            i: moduleId,
            l: false,//未缓存loading
            exports: {}
        };

        // 函数执行并绑定上下文
        modules[moduleId].call(module.exports, module, module.exports, wr);

        // 已经缓存
        module.l = true;

        // 暴露出去一个对象
        return module.exports;
    }


    // 没用到
    wr.m = modules;

    // 没用到
    wr.c = installedModules;

    // 没用到
    wr.d = function(exports, name, getter) {
        if (!wr.o(exports, name)) {
            Object.defineProperty(exports, name, { enumerable: true, get: getter });
        }
    };

    // 用到了
    wr.r = function(exports) {
        if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {
            Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
        }
        Object.defineProperty(exports, '__esModule', { value: true });
    };

    // 没用到
    wr.t = function(value, mode) {
        if (mode & 1) value = wr(value);
        if (mode & 8) return value;
        if ((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
        var ns = Object.create(null);
        wr.r(ns);
        Object.defineProperty(ns, 'default', { enumerable: true, value: value });
        if (mode & 2 && typeof value != 'string')
            for (var key in value) wr.d(ns, key, function(key) { return value[key]; }.bind(null, key));
        return ns;
    };

    // 没用到
    wr.n = function(module) {
        var getter = module && module.__esModule ?
            function getDefault() { return module['default']; } :
            function getModuleExports() { return module; };
        wr.d(getter, 'a', getter);
        return getter;
    };

    // 没用到
    wr.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };

    //没用
    wr.p = "";


    // 先执行入口函数wr.s可以不绑定
    return wr(wr.s = "./main.js");
})({
    "./circle.js": function(m, we, wr) {
        wr.r(we);
        function circle(r){
            return Math.PI * r ** 2;
        };
        we["default"] = circle;
    },
    "./main.js": function(m, we, wr) {
        wr.r(we);
        var rwim = wr("./rectangle.js");
        var cwim = wr("./circle.js");
        console.log(rwim)
        function main() {
            const r = Object(rwim["default"])(5, 10);
            const c = Object(cwim["default"])(10);
            alert(r);
            alert(c);
        };
        main();
    },
    "./rectangle.js": function(m, we, wr) {
        wr.r(we);
        function rect(a , b){
            return a * b;
        };
        we["default"] = rect;
    }

});

这样看就顺畅多了,标注没用到只是现在没用到,不是代表webpack之后没用到,我们去掉没用的和处理缓存的避免视线干扰再来看看代码。

(function(modules) {
    // wr函数
    function wr(moduleId) {
        //创建一个对象,并放入缓存
        var module = {
            i: moduleId,
            l: false, //未缓存loading
            exports: {}
        };
        // 函数执行并绑定上下文
        modules[moduleId](module.exports, wr);
        // 暴露出去一个对象
        return module.exports;
    }
    // 用到了
    wr.r = function(exports) {
        if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {
            Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
        }
        Object.defineProperty(exports, '__esModule', { value: true });
    };
    // 先执行入口函数wr.s可以不绑定
    return wr("./main.js");
})({
    "./circle.js": function(we, wr) {
        wr.r(we);
        function circle(r) {
            return Math.PI * r ** 2;
        };
        we["default"] = circle;
    },
    "./main.js": function(we, wr) {
        wr.r(we);
        var rwim = wr("./rectangle.js");
        var cwim = wr("./circle.js");
        function main() {
            const r = Object(rwim["default"])(5, 10);
            const c = Object(cwim["default"])(10);
            alert(r);
            alert(c);
        };
        main();
    },
    "./rectangle.js": function(we, wr) {
        wr.r(we);
        function rect(a, b) {
            return a * b;
        };
        we["default"] = rect;
    }
});

现在就剩下这些代码了。这看着多舒服~~~~。 wr.r 是用来标记此模块是否是 es 模块。这里也用不到~删。

(function(modules) {
    // wr函数
    function wr(moduleId) {
        //创建一个对象
        var module = {};
        // 函数执行并绑定上下文
        modules[moduleId](module, wr);
        // 暴露出去一个对象
        return module;
    };
    return wr("./main.js");
})({
    "./circle.js": function(we, wr) {
        function circle(r) {
            return Math.PI * r ** 2;
        };
        we["default"] = circle;
    },
    "./main.js": function(we, wr) {
        var rwim = wr("./rectangle.js");
        var cwim = wr("./circle.js");
        function main() {
            const r = Object(rwim["default"])(5, 10);
            const c = Object(cwim["default"])(10);
            alert(r);
            alert(c);
        };
        main();
    },
    "./rectangle.js": function(we, wr) {
        function rect(a, b) {
            return a * b;
        };
        we["default"] = rect;
    }
});

代码就剩这点了,一个IIFE,结合递归回调来执行你的代码。然后没了,讲完了~~~

四、第三方 npm 如何打包的

以数字转人民币大写这个为例,npm 包名为 rmb-x 。你一定好奇为什么选择这个,没错就是因为这个包纯碎,没有依赖第三方包。

更改 main.js:

import rect from "./rectangle.js";
import circle from "./circle.js";
import rmb_x from "rmb-x";

function main(){
    // 长方形的面积 5 X 10 = 50;
    const r = rect(5 , 10);
    // 圆的面积 πX10X10≈314
    const c = circle(10);
    const rmb = rmb_x(1873947);
    // 浏览器弹出结果
    alert(rmb);
    alert(r);
    alert(c);
};
main();

打包之后的效果:


来看看打包之后的 bundle.js 文件,文件没啥变换,只是作为参数传进 IIFE 的大对象增加了一个字段。

说白了,这个第三方包你可以想象就是你自己写的程序,事实上这个 npm 包也不是很难,难道你看不懂,我们顺着引用路径去看看这个我们现在的 npm 包。


看看这个包不是很难,就是封装一个函数默认暴露出去,和我们写求圆的面积差不多。所以经过 webpack 编译也没啥难度,只不过是路径可能有点不同。

上一篇下一篇

猜你喜欢

热点阅读