前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【模块化】:Webpack 是如何将不同规范(ESM、CJS、UMD、AMD、CMD)的模块化代码打包到一起并协调它们运行的?

【模块化】:Webpack 是如何将不同规范(ESM、CJS、UMD、AMD、CMD)的模块化代码打包到一起并协调它们运行的?

作者头像
WEBJ2EE
发布2022-03-30 21:08:23
5.9K2
发布2022-03-30 21:08:23
举报
文章被收录于专栏:WebJ2EEWebJ2EE
代码语言:javascript
复制
目录
1. 背景
2. 回顾一波各种模块化规范
  2.1. ESM
  2.2. CJS
  2.3. AMD
  2.4. CMD
  2.5. UMD
3. Webpack 打包产物分析
  3.1. 打包测试环境
  3.2. 打包测试项目
  3.3. 分析过程中需要用到的一些 JS 知识
    3.3.1. this 与 bind
    3.3.2. Symbol.toStringTag
    3.3.3. Document.currentScript
    3.3.4. Promise.all
  3.4. 打包产物 bundle.js(入口文件) 分析
4. 扩展阅读
  4.1. ES 模块比 CJS 更好吗?
  4.2. 什么是 "tree-shaking"?
  4.3. pkg.module 是什么?

1. 背景

不知道大家有没有观察到

npm 上面发布的组件库

所使用的模块化规范并不是统一的

lodash-es:ESM 规范

lodash:CJS 规范

js-cookie:UMD 规范

但我们用这些库的时候

不需要针对这些库自身的模块化规范

调整我们的程序

原因是

Webpack、Rollup、Vite 这类工具

把模块化规范间的转换(兼容)工作

在暗地里偷偷干了

2. 回顾一波各种模块化规范

2.1. ESM

ES6 Module 简称 ESM。

代码语言:javascript
复制
// math.js
export function add(a, b) {
    return a + b;
}

// app.js:指定使用math模块的add命名导出
import { add } from './math.js';
console.log(add(1, 2)); // => 3

// 导入所有的命名导出作为math对象的成员
import * as math from './math.js';
console.log(math.add(1, 2)); // => 3

2.2. CJS

CommonJS 简称 CJS

代码语言:javascript
复制
// filename: foo.js
var $ = require('jquery');
var _ = require('underscore');

// methods
function a(){}; // private because it's omitted from module.exports (see below)
function b(){}; // public because it's defined in module.exports
function c(){}; // public because it's defined in module.exports

// exposed public methods
module.exports = {
    b: b,
    c: c
};

2.3. AMD

Asynchronous Module Definition (AMD) has gained traction on the frontend, with RequireJS being the most popular implementation.

代码语言:javascript
复制
//    filename: foo.js
define(['jquery', 'underscore'], function ($, _) {
    //    methods
    function a(){};    //    private because it's not returned (see below)
    function b(){};    //    public because it's returned
    function c(){};    //    public because it's returned

    //    exposed public methods
    return {
        b: b,
        c: c
    }
});

2.4. CMD

代码语言:javascript
复制
define(function(require, exports, module) {
    exports.max = function(a, b){
        return a > b ? a : b;
    };

    exports.min = function(a, b){
        return a > b ? b : a;
    }
});

2.5. UMD

Since CommonJS and AMD styles have both been equally popular, it seems there’s yet no consensus. This has brought about the push for a “universal” pattern that supports both styles, which brings us to none other than the Universal Module Definition.

代码语言:javascript
复制
(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        // AMD
        define(['jquery', 'underscore'], factory);
    } else if (typeof exports === 'object') {
        // Node, CommonJS-like
        module.exports = factory(require('jquery'), require('underscore'));
    } else {
        // Browser globals (root is window)
        root.returnExports = factory(root.jQuery, root._);
    }
}(this, function ($, _) {
    //    methods
    function a(){};    //    private because it's not returned (see below)
    function b(){};    //    public because it's returned
    function c(){};    //    public because it's returned

    //    exposed public methods
    return {
        b: b,
        c: c
    }
}));

3. Webpack 打包产物分析

3.1. 打包测试环境

webpack(5.69.1)配置(webpack.config.js):

代码语言:javascript
复制
const path = require('path');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
    mode: "development",
    entry: "./index.js", 
    output: {
        path: path.resolve(__dirname, './build'),
        filename: 'bundle.js',
    },
    // !! 
    // 关闭 devtool,
    // 在分析 webpack 打包原理时 
    // 可以减少一些不必要的干扰
    // !!
    devtool: false,
    devServer: {
        port: 9999,
        open: true,
        hot: true
      },
    plugins:[
        new CleanWebpackPlugin(),
        // 生成html
        new HtmlWebpackPlugin({
            title: 'webpack-demo',
            filename: 'index.html',
            inject: 'body'
        }),
    ]
}

3.2. 打包测试项目

在一个项目中同时使用 ES6、CJS、CMD、AMD、UMD 5种不同的模块化规范编写代码,并同时应用静态导入、动态导入(Dynamic Import)方法来引用这些模块。观察 Webpack 是如何将这些不同模块化规范的代码打包到一起和协调它们运行的。

执行 webpack 的打包命令:

代码语言:javascript
复制
webpack build

观察 webpack 的打包输出:

3.3. 分析过程中需要用到的一些 JS 知识

3.3.1. this 与 bind

代码语言:javascript
复制
const arr = [];

// 常规用法
arr.push("normal"); // ok
console.log(arr);

// 特殊一点: 把 push 函数提出来
const arr_push = arr.push;

try{
  arr_push("noop!");// throw Error
  console.log(arr);
}catch(oE){console.error(oE)} 

arr_push.call(arr, "call"); // 使用 call, ok
console.log(arr);

arr_push.bind(arr)("bind"); // 使用 bind, ok
console.log(arr);

3.3.2. Symbol.toStringTag

The Symbol.toStringTag wellknown symbol is a string valued property that is used in the creation of the default string description of an object. It is accessed internally by the Object.prototype.toString() method.

代码语言:javascript
复制
const p = console.log.bind(console.log);

// Default tags
p("---- Default tags ----");
p(Object.prototype.toString.call('foo'));     // "[object String]"
p(Object.prototype.toString.call([1, 2]));    // "[object Array]"
p(Object.prototype.toString.call({}));        // "[object Object]"
p(Object.prototype.toString.call(3));         // "[object Number]"
p(Object.prototype.toString.call(true));      // "[object Boolean]"
p(Object.prototype.toString.call(undefined)); // "[object Undefined]"
p(Object.prototype.toString.call(null));      // "[object Null]"
p(Object.prototype.toString.call(()=>{}));    // "[object Function]"
// ... and more

// Built-in toStringTag symbols
p("---- Built-in toStringTag symbols ----");
p(Object.prototype.toString.call(new Map()));         // "[object Map]"
p(Object.prototype.toString.call(function* () {}));   // "[object GeneratorFunction]"
p(Object.prototype.toString.call(Promise.resolve())); // "[object Promise]"
// ... and more


// Custom object,classes default to object tag
p("---- Built-in toStringTag symbols ----");
const o1 = {
  exports: {}
}
p(Object.prototype.toString.call(o1));

// Custom tag with toStringTag
p("---- Custom tag with toStringTag ----");
const o2 = {
  get[Symbol.toStringTag](){
        return 'Module-o2'
    },
  exports: {}
}
p(Object.prototype.toString.call(o2));


const o3 = {
  exports: {}
}
Object.defineProperty(o3, Symbol.toStringTag, { value: 'Module-o3' });
p(Object.prototype.toString.call(o3));

3.3.3. Document.currentScript

The Document.currentScript property returns the <script> element whose script is currently being processed and isn't a JavaScript module. (For modules use import.meta instead.) It's important to note that this will not reference the <script> element if the code in the script is being called as a callback or event handler; it will only reference the element while it's initially being processed

3.3.4. Promise.all

The Promise.all() method takes an iterable of promises as an input, and returns a single Promise that resolves to an array of the results of the input promises.

代码语言:javascript
复制
var p1 = Promise.resolve(3);
var p2 = 1337;
var p3 = new Promise((resolve, reject) => {
  setTimeout(resolve, 100, 'foo');
});

Promise.all([p1, p2, p3]).then(values => {
  console.log(values); // [3, 1337, "foo"]
});

接下来

我们从 Webpack 编译产物的入口文件

bundle.js

开始分析

3.4. 打包产物 bundle.js(入口文件) 分析

Webpack 的打包过程,除了需要将开发者写的业务代码打包外,还需要把一些用于支撑、调度这些业务代码运行的辅助代码(这类代码在 webpack 中叫做 runtime(运行时))一同打包进 bundle 中。以建筑作类比的话,业务代码相当于砖瓦水泥,是看得见摸得着能直接感知的逻辑;运行时(runtime)相当于掩埋在砖瓦之下的钢筋地基,通常不会关注但决定了整座建筑的功能、质量。

大多数 Webpack 特性都需要特定钢筋地基才能跑起来,比如:

  • 模块化
  • 异步按需加载
  • HMR
  • WASM
  • Module Federation

bundle.js:

代码语言:javascript
复制
/******/ (() => { // webpackBootstrap
/******/   "use strict";
/******/   var __webpack_modules__ = ({

/***/ "./static.js":
/*!*******************!*\
  !*** ./static.js ***!
  \*******************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {

__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */   "default": () => (/* binding */ parseInt)
/* harmony export */ });
function parseInt(x){
    return Number.parseInt(x);
}

/***/ })

/******/   });
/************************************************************************/
/******/   // The module cache
/******/   var __webpack_module_cache__ = {};
/******/   
/******/   // The require function
/******/   function __webpack_require__(moduleId) {
/******/     // Check if module is in cache
/******/     var cachedModule = __webpack_module_cache__[moduleId];
/******/     if (cachedModule !== undefined) {
/******/       return cachedModule.exports;
/******/     }
/******/     // Create a new module (and put it into the cache)
/******/     var module = __webpack_module_cache__[moduleId] = {
/******/       // no module.id needed
/******/       // no module.loaded needed
/******/       exports: {}
/******/     };
/******/   
/******/     // Execute the module function
/******/     __webpack_modules__[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/   
/******/     // Return the exports of the module
/******/     return module.exports;
/******/   }
/******/   
/******/   // expose the modules object (__webpack_modules__)
/******/   __webpack_require__.m = __webpack_modules__;
/******/   
/************************************************************************/
/******/   /* webpack/runtime/create fake namespace object */
/******/   (() => {
/******/     var getProto = Object.getPrototypeOf ? (obj) => (Object.getPrototypeOf(obj)) : (obj) => (obj.__proto__);
/******/     var leafPrototypes;
/******/     // 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 & 16: return value when it's Promise-like
/******/     // mode & 8|1: behave like require
/******/     __webpack_require__.t = function(value, mode) {
/******/       if(mode & 1) value = this(value);
/******/       if(mode & 8) return value;
/******/       if(typeof value === 'object' && value) {
/******/         if((mode & 4) && value.__esModule) return value;
/******/         if((mode & 16) && typeof value.then === 'function') return value;
/******/       }
/******/       var ns = Object.create(null);
/******/       __webpack_require__.r(ns);
/******/       var def = {};
/******/       leafPrototypes = leafPrototypes || [null, getProto({}), getProto([]), getProto(getProto)];
/******/       for(var current = mode & 2 && value; typeof current == 'object' && !~leafPrototypes.indexOf(current); current = getProto(current)) {
/******/         Object.getOwnPropertyNames(current).forEach((key) => (def[key] = () => (value[key])));
/******/       }
/******/       def['default'] = () => (value);
/******/       __webpack_require__.d(ns, def);
/******/       return ns;
/******/     };
/******/   })();
/******/   
/******/   /* webpack/runtime/define property getters */
/******/   (() => {
/******/     // define getter functions for harmony exports
/******/     __webpack_require__.d = (exports, definition) => {
/******/       for(var key in definition) {
/******/         if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
/******/           Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
/******/         }
/******/       }
/******/     };
/******/   })();
/******/   
/******/   /* webpack/runtime/ensure chunk */
/******/   (() => {
/******/     __webpack_require__.f = {};
/******/     // This file contains only the entry chunk.
/******/     // The chunk loading function for additional chunks
/******/     __webpack_require__.e = (chunkId) => {
/******/       return Promise.all(Object.keys(__webpack_require__.f).reduce((promises, key) => {
/******/         __webpack_require__.f[key](chunkId, promises);
/******/         return promises;
/******/       }, []));
/******/     };
/******/   })();
/******/   
/******/   /* webpack/runtime/get javascript chunk filename */
/******/   (() => {
/******/     // This function allow to reference async chunks
/******/     __webpack_require__.u = (chunkId) => {
/******/       // return url for filenames based on template
/******/       return "" + chunkId + ".bundle.js";
/******/     };
/******/   })();
/******/   
/******/   /* webpack/runtime/global */
/******/   (() => {
/******/     __webpack_require__.g = (function() {
/******/       if (typeof globalThis === 'object') return globalThis;
/******/       try {
/******/         return this || new Function('return this')();
/******/       } catch (e) {
/******/         if (typeof window === 'object') return window;
/******/       }
/******/     })();
/******/   })();
/******/   
/******/   /* webpack/runtime/hasOwnProperty shorthand */
/******/   (() => {
/******/     __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
/******/   })();
/******/   
/******/   /* webpack/runtime/load script */
/******/   (() => {
/******/     var inProgress = {};
/******/     var dataWebpackPrefix = "webpack-demo:";
/******/     // loadScript function to load a script via script tag
/******/     __webpack_require__.l = (url, done, key, chunkId) => {
/******/       if(inProgress[url]) { inProgress[url].push(done); return; }
/******/       var script, needAttach;
/******/       if(key !== undefined) {
/******/         var scripts = document.getElementsByTagName("script");
/******/         for(var i = 0; i < scripts.length; i++) {
/******/           var s = scripts[i];
/******/           if(s.getAttribute("src") == url || s.getAttribute("data-webpack") == dataWebpackPrefix + key) { script = s; break; }
/******/         }
/******/       }
/******/       if(!script) {
/******/         needAttach = true;
/******/         script = document.createElement('script');
/******/     
/******/         script.charset = 'utf-8';
/******/         script.timeout = 120;
/******/         if (__webpack_require__.nc) {
/******/           script.setAttribute("nonce", __webpack_require__.nc);
/******/         }
/******/         script.setAttribute("data-webpack", dataWebpackPrefix + key);
/******/         script.src = url;
/******/       }
/******/       inProgress[url] = [done];
/******/       var onScriptComplete = (prev, event) => {
/******/         // avoid mem leaks in IE.
/******/         script.onerror = script.onload = null;
/******/         clearTimeout(timeout);
/******/         var doneFns = inProgress[url];
/******/         delete inProgress[url];
/******/         script.parentNode && script.parentNode.removeChild(script);
/******/         doneFns && doneFns.forEach((fn) => (fn(event)));
/******/         if(prev) return prev(event);
/******/       }
/******/       ;
/******/       var timeout = setTimeout(onScriptComplete.bind(null, undefined, { type: 'timeout', target: script }), 120000);
/******/       script.onerror = onScriptComplete.bind(null, script.onerror);
/******/       script.onload = onScriptComplete.bind(null, script.onload);
/******/       needAttach && document.head.appendChild(script);
/******/     };
/******/   })();
/******/   
/******/   /* webpack/runtime/make namespace object */
/******/   (() => {
/******/     // define __esModule on exports
/******/     __webpack_require__.r = (exports) => {
/******/       if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/         Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/       }
/******/       Object.defineProperty(exports, '__esModule', { value: true });
/******/     };
/******/   })();
/******/   
/******/   /* webpack/runtime/publicPath */
/******/   (() => {
/******/     var scriptUrl;
/******/     if (__webpack_require__.g.importScripts) scriptUrl = __webpack_require__.g.location + "";
/******/     var document = __webpack_require__.g.document;
/******/     if (!scriptUrl && document) {
/******/       if (document.currentScript)
/******/         scriptUrl = document.currentScript.src
/******/       if (!scriptUrl) {
/******/         var scripts = document.getElementsByTagName("script");
/******/         if(scripts.length) scriptUrl = scripts[scripts.length - 1].src
/******/       }
/******/     }
/******/     // When supporting browsers where an automatic publicPath is not supported you must specify an output.publicPath manually via configuration
/******/     // or pass an empty string ("") and set the __webpack_public_path__ variable from your code to use your own logic.
/******/     if (!scriptUrl) throw new Error("Automatic publicPath is not supported in this browser");
/******/     scriptUrl = scriptUrl.replace(/#.*$/, "").replace(/\?.*$/, "").replace(/\/[^\/]+$/, "/");
/******/     __webpack_require__.p = scriptUrl;
/******/   })();
/******/   
/******/   /* webpack/runtime/jsonp chunk loading */
/******/   (() => {
/******/     // no baseURI
/******/     
/******/     // object to store loaded and loading chunks
/******/     // undefined = chunk not loaded, null = chunk preloaded/prefetched
/******/     // [resolve, reject, Promise] = chunk loading, 0 = chunk loaded
/******/     var installedChunks = {
/******/       "main": 0
/******/     };
/******/     
/******/     __webpack_require__.f.j = (chunkId, promises) => {
/******/         // JSONP chunk loading for javascript
/******/         var installedChunkData = __webpack_require__.o(installedChunks, chunkId) ? installedChunks[chunkId] : undefined;
/******/         if(installedChunkData !== 0) { // 0 means "already installed".
/******/     
/******/           // a Promise means "currently loading".
/******/           if(installedChunkData) {
/******/             promises.push(installedChunkData[2]);
/******/           } else {
/******/             if(true) { // all chunks have JS
/******/               // setup Promise in chunk cache
/******/               var promise = new Promise((resolve, reject) => (installedChunkData = installedChunks[chunkId] = [resolve, reject]));
/******/               promises.push(installedChunkData[2] = promise);
/******/     
/******/               // start chunk loading
/******/               var url = __webpack_require__.p + __webpack_require__.u(chunkId);
/******/               // create error before stack unwound to get useful stacktrace later
/******/               var error = new Error();
/******/               var loadingEnded = (event) => {
/******/                 if(__webpack_require__.o(installedChunks, chunkId)) {
/******/                   installedChunkData = installedChunks[chunkId];
/******/                   if(installedChunkData !== 0) installedChunks[chunkId] = undefined;
/******/                   if(installedChunkData) {
/******/                     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;
/******/                     installedChunkData[1](error);
/******/                   }
/******/                 }
/******/               };
/******/               __webpack_require__.l(url, loadingEnded, "chunk-" + chunkId, chunkId);
/******/             } else installedChunks[chunkId] = 0;
/******/           }
/******/         }
/******/     };
/******/     
/******/     // no prefetching
/******/     
/******/     // no preloaded
/******/     
/******/     // no HMR
/******/     
/******/     // no HMR manifest
/******/     
/******/     // no on chunks loaded
/******/     
/******/     // install a JSONP callback for chunk loading
/******/     var webpackJsonpCallback = (parentChunkLoadingFunction, data) => {
/******/       var [chunkIds, moreModules, runtime] = data;
/******/       // add "moreModules" to the modules object,
/******/       // then flag all "chunkIds" as loaded and fire callback
/******/       var moduleId, chunkId, i = 0;
/******/       if(chunkIds.some((id) => (installedChunks[id] !== 0))) {
/******/         for(moduleId in moreModules) {
/******/           if(__webpack_require__.o(moreModules, moduleId)) {
/******/             __webpack_require__.m[moduleId] = moreModules[moduleId];
/******/           }
/******/         }
/******/         if(runtime) var result = runtime(__webpack_require__);
/******/       }
/******/       if(parentChunkLoadingFunction) parentChunkLoadingFunction(data);
/******/       for(;i < chunkIds.length; i++) {
/******/         chunkId = chunkIds[i];
/******/         if(__webpack_require__.o(installedChunks, chunkId) && installedChunks[chunkId]) {
/******/           installedChunks[chunkId][0]();
/******/         }
/******/         installedChunks[chunkId] = 0;
/******/       }
/******/     
/******/     }
/******/     
/******/     var chunkLoadingGlobal = self["webpackChunkwebpack_demo"] = self["webpackChunkwebpack_demo"] || [];
/******/     chunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0));
/******/     chunkLoadingGlobal.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal));
/******/   })();
/******/   
/************************************************************************/
var __webpack_exports__ = {};
// This entry need to be wrapped in an IIFE because it need to be isolated against other modules in the chunk.
(() => {
/*!******************!*\
  !*** ./index.js ***!
  \******************/
__webpack_require__.r(__webpack_exports__);
/* harmony import */ var _static__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./static */ "./static.js");


console.log(`parseInt is: ${(0,_static__WEBPACK_IMPORTED_MODULE_0__["default"])(3.1415)}`);

__webpack_require__.e(/*! import() */ "cjs_js").then(__webpack_require__.t.bind(__webpack_require__, /*! ./cjs.js */ "./cjs.js", 23)).then((module)=>{
    const sum = module.add(1, 2);

    console.log(`sum is: ${sum}`);
})

__webpack_require__.e(/*! import() */ "es6_js").then(__webpack_require__.bind(__webpack_require__, /*! ./es6.js */ "./es6.js")).then((module)=>{
    const square = module.default(4);

    console.log(`square is: ${square}`);
})

__webpack_require__.e(/*! import() */ "cmd_js").then(__webpack_require__.t.bind(__webpack_require__, /*! ./cmd.js */ "./cmd.js", 23)).then((module)=>{
    const max = module.max(4, 8);

    console.log(`max is: ${max}`);
})


__webpack_require__.e(/*! import() */ "amd_js").then(__webpack_require__.t.bind(__webpack_require__, /*! ./amd.js */ "./amd.js", 23)).then((module)=>{
    const floor = module.floor(4.3);

    console.log(`floor is: ${floor}`);
})


__webpack_require__.e(/*! import() */ "umd_js").then(__webpack_require__.t.bind(__webpack_require__, /*! ./umd.js */ "./umd.js", 23)).then((module)=>{
    const round = module.round(4.5);

    console.log(`round is: ${round}`);
})


})();

/******/ })()
;

Webpack 的编译产物看上去有点奇葩

让我们来仔细捋一捋

梳理一下脉络

bundle 整体由一个 IIFE(webpackBootstrap) 包裹,主要包含:

  • __webpack_modules__ 对象,包含了除入口外的所有模块。
    • 注1:源码入口模块中,以静态方式引入的模块,会被直接编译到这里。
    • 注2:源码入口模块中,以动态方式引入的模块,会在运行时按需被添加到这个对象中。
  • __webpack_module_cache__ 对象,存储的是已经被引用(初始化)过的模块。
    • 注:同一个模块被引入多次,但只会被初始化一次。
  • __webpack_require__ 函数,实现模块引用(require) 逻辑
  • __webpack_require__.r ,ES模块工具函数,用于标记某模块是一个 ES 模块
  • __webpack_require__.d ,ES模块工具函数,用于转换ES模块导出的内容;
  • __webpack_require__.o,工具函数,本质就是hasOwnProperty,用于判定对象自身属性中是否具有指定的属性。

上面这几个函数和对象

构成了 Webpack 运行时的“基本特性”

—— 模块化 ——

下面这几个函数和对象则

构成了 Webpack 运行时的“高级特性”

—— 异步模块的加载、运行能力 ——

  • __webpack_require__.e :是异步模块(chunk)加载功能的入口。
    • 注:__webpack_require__.e 采用了中间件模式。
    • 注:所有需要注册给 __webpack_require__.e 的中间件,都需要注册到 __webpack_require__.f 对象中。
      • 注:__webpack_require__.f.j 则是实现了异步模块(chunk )路径拼接、缓存、异常处理功能的一个中间件。
  • __webpack_require__.l :基于 JSONP 的异步模块(chunk )加载与执行函数
  • __webpack_require__.u :用于拼接异步模块名称的函数
  • __webpack_require__.g:工具函数,获取全局对象。
  • __webpack_require__.p :存储的是自动获取的 publicPath,用于后续加载 chunk 时计算完整 URL 使用。

异步模块是被下载后如何与

__webpack_modules__、installedChunks

联动的呢?

  • chunkLoadingGlobal:每一个被下载异步模块(chunk)都会把自己存储到(push)一个全局数组中。
  • 但是chunkLoadingGlobal.push 这个动作被函数 webpackJsonpCallback 劫持(替换了)了,需要先完成与 installedChunks、__webpack_modules__ 的联动,然后才会被存储到 chunkLoadingGlobal 数组中。

4. 扩展阅读

4.1. ES 模块比 CJS 更好吗?

ES modules are an official standard and the clear path forward for JavaScript code structure, whereas CommonJS modules are an idiosyncratic legacy format that served as a stopgap solution before ES modules had been proposed. ES modules allow static analysis that helps with optimizations like tree-shaking and scope-hoisting, and provide advanced features like circular references and live bindings.

4.2. 什么是 "tree-shaking"?

Tree-shaking, also known as "live code inclusion", is a process of eliminating code that is not actually used in a given project. It is a form of dead code elimination but can be much more efficient than other approaches with regard to output size. The name is derived from the abstract syntax tree of the modules (not the module graph). The algorithm first marks all relevant statements and then "shakes the syntax tree" to remove all dead code. It is similar in idea to the mark-and-sweep garbage collection algorithm. Even though this algorithm is not restricted to ES modules, they make it much more efficient as they allow us to treat all modules together as a big abstract syntax tree with shared bindings.

4.3. pkg.module 是什么?

pkg.module will point to a module that has ES2015 module syntax but otherwise only syntax features that the target environments support.

Typically, a library will be accompanied with a package.json file (this is mandatory if you're publishing on npm, for example). That file will often specify a pkg.main property - something like this:

代码语言:javascript
复制
{
  "name": "my-package",
  "version": "0.1.0",
  "main": "dist/my-package.js"
}

That instructs Browserify or Webpack or [insert module bundler here] to include the contents of dist/my-package.js – plus any dependencies it has – in your bundle when you call require('my-package') in your app or library.

But for ES2015-aware tools like Rollup, using the CommonJS (or Universal Module Definition) build isn't ideal, because we can't then take advantage of ES2015 module features. So assuming that you've written your package as ES2015 modules, you can generate an ES2015 module build alongside your CommonJS/UMD build:

代码语言:javascript
复制
{
  "name": "my-package",
  "version": "0.1.0",
  "main": "dist/my-package.umd.js",
  "module": "dist/my-package.esm.js"
}

Now we're cooking with gas - my-package continues to work for everyone using legacy module formats, but people using ES2015 module bundlers such as Rollup don't have to compromise. Everyone wins!

参考:

UMD:https://github.com/umdjs/umd AMD:https://github.com/amdjs/amdjs-api/wiki RequireJS:http://requirejs.org/ CMD:https://github.com/seajs/seajs/issues/242 SeaJS:http://www.zhangxinxu.com/sp/seajs/ pkg.module: https://github.com/rollup/rollup/wiki/pkg.module es模块化语法回顾: https://www.rollupjs.org/guide/en/#es-module-syntax

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2022-02-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 WebJ2EE 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
消息队列 TDMQ
消息队列 TDMQ (Tencent Distributed Message Queue)是腾讯基于 Apache Pulsar 自研的一个云原生消息中间件系列,其中包含兼容Pulsar、RabbitMQ、RocketMQ 等协议的消息队列子产品,得益于其底层计算与存储分离的架构,TDMQ 具备良好的弹性伸缩以及故障恢复能力。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档