webpack5 邦联加载流程学习

654 阅读3分钟

待完善

1.引言

webpack5新特性 模块邦联(module-federation)让 Webpack 项目可以实现线上 Runtime 的效果,让代码组件直接在项目间共享,不再需要本地安装 Npm 包、构建再发布了

在以往技术中,大多数项目中通过DLL或者引入npm包等来复用组件,提高开发效率。而多个项目之间使用的相同组件大多通过copy实现。模块邦联内置于webpack5,让项目间组件共享成为了可能。

2.线上使用情况

umi中的mfsu就是依赖于module-federation实现

3.邦联整体加载流程学习

首先了解一下webpack模块加载流程__webpack_require__

加载第一步

var __webpack_exports__ = __webpack_require__(60119);
// 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](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__;

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

通过__webpack_require__之后,开始渲染页面

import React, { FC, Suspense } from "react";
import ReactDOM from "react-dom";
const MFC = React.lazy(() => import('MFComponents/MFC'));
import "./index.less";

const Test: FC<any> = () => {
  return <div>
    <Suspense fallback={""}>
      <MFC />
    </Suspense>
  </div>;
};

export default ReactDOM.render(<Test />, document.querySelector("#root"));

当执行const MFC = React.lazy(() => import('MFComponents/MFC')); 时,会触发webpack插件中配置的邦联的remote

 new ModuleFederationPlugin({
    name: "weimaiUI",
    remotes: {
      MFComponents: 'url or promise'
    },
    shared: { react: { singleton: true }, "react-dom": { singleton: true } },
  })

此时会触发__webpack_require__.e

__webpack_require__.f = {};
// This file contains only the entry chunk.
// The chunk loading function for additional chunks
__webpack_require__.e = function(chunkId) {
    return Promise.all(Object.keys(__webpack_require__.f).reduce(function(promises, key) {
        __webpack_require__.f[key](chunkId, promises);
        return promises;
    }, []));
};

__webpack_require__.e中包含jremote,来加载邦联组件js

加载完之后,页面html上会新增对于邦联组件js地址引用的

到此主机引用步骤完成,进入远端组件渲染流程

进入远端组件渲染流程

远端js加载完成后,会挂载get与init

var get = function(module, getScope) {
    __webpack_require__.R = getScope;
    getScope = (
        __webpack_require__.o(moduleMap, module)
            ? moduleMap[module]()
            : Promise.resolve().then(function() {
                throw new Error('Module "' + module + '" does not exist in container.');
            })
    );
    __webpack_require__.R = undefined;
    return getScope;
};
var init = function(shareScope, initScope) {
    if (!__webpack_require__.S) return;
    var name = "default"
    var oldScope = __webpack_require__.S[name];
    if(oldScope && oldScope !== shareScope) throw new Error("Container initialization failed as it has already been initialized with a different share scope");
    __webpack_require__.S[name] = shareScope;
    return __webpack_require__.I(name, initScope);
};

// This exports getters to disallow modifications
__webpack_require__.d(exports, {
    get: function() { return get; },
    init: function() { return init; }
});

挂载完成后,执行init完成邦联初始化工作

此时window上已经可以获取到邦联模块,window[remote]

获取组件

完成挂载后,此时可以通过window[remote].get('./componentName')获取对应组件

通过__webpack_require__.e执行__webpack_require__.f中的jcompat方法

__webpack_require__.f.j = function(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(function(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 = function(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;
        }
    }
};
__webpack_require__.f.compat = function(chunkId, promises) {

    // mini-css-extract-plugin CSS loading
    var cssChunks = {"213":1,"533":1,"608":1};
    if(installedCssChunks[chunkId]) promises.push(installedCssChunks[chunkId]);
    else if(installedCssChunks[chunkId] !== 0 && cssChunks[chunkId]) {
        promises.push(installedCssChunks[chunkId] = new Promise(function(resolve, reject) {
            var href = "" + ({}[chunkId]||chunkId) + ".css";
            var fullhref = __webpack_require__.p + href;
            var existingLinkTags = document.getElementsByTagName("link");
            for(var i = 0; i < existingLinkTags.length; i++) {
                    var tag = existingLinkTags[i];
                    var dataHref = tag.getAttribute("data-href") || tag.getAttribute("href");
                    if(tag.rel === "stylesheet" && (dataHref === href || dataHref === fullhref)) return resolve();
            }
            var existingStyleTags = document.getElementsByTagName("style");
            for(var i = 0; i < existingStyleTags.length; i++) {
                    var tag = existingStyleTags[i];
                    var dataHref = tag.getAttribute("data-href");
                    if(dataHref === href || dataHref === fullhref) return resolve();
            }
            var linkTag = document.createElement("link");
            linkTag.rel = "stylesheet";
            linkTag.type = "text/css";
            linkTag.onload = resolve;
            linkTag.onerror = function(event) {
                var request = event && event.target && event.target.src || fullhref;
                var err = new Error("Loading CSS chunk " + chunkId + " failed.\n(" + request + ")");
                err.code = "CSS_CHUNK_LOAD_FAILED";
                err.request = request;
                delete installedCssChunks[chunkId]
                linkTag.parentNode.removeChild(linkTag)
                reject(err);
            };
            linkTag.href = fullhref;

            var head = document.getElementsByTagName("head")[0];
            head.appendChild(linkTag);
        }).then(function() {
            installedCssChunks[chunkId] = 0;
        }));
    }
};

通过__webpack_require__.l加载插入组件对应的js以及css

    "./MFC": function() {
        return Promise.all([__webpack_require__.e(38), __webpack_require__.e(690), __webpack_require__.e(533)]).then(function() { return function() { return (__webpack_require__(62035)); }; });
    },
    "./Test": function() {
        return __webpack_require__.e(608).then(function() { return function() { return (__webpack_require__(55608)); }; });
    }
};

此时页面html上会新增 对组件css的引用链接
加载组件仅在被使用时,未使用的组件不会加载

image.png

image.png

image.png