diff --git a/.gitignore b/.gitignore index 587af51..2ee0033 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,4 @@ node_modules -/lib dist .DS_Store .idea diff --git a/README.md b/README.md index 6755b5a..4dd60d0 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # Vite plugin for Module Federation +[![npm](https://img.shields.io/npm/v/@module-federation/vite.svg)](https://www.npmjs.com/package/@module-federation/vite) + ## Reason why 🤔 [Microservices](https://martinfowler.com/articles/microservices.html) nowadays is a well-known concept and maybe you are using it in your current company. @@ -15,6 +17,14 @@ This plugin makes Module Federation work together with [Vite](https://vitejs.dev ### [More examples here](https://github.com/module-federation/vite/tree/main/examples)
+``` +pnpm install && pnpm run dev-vv # vite+vite dev demo +``` + +``` +pnpm install && pnpm run preview-vv # vite+vite build demo +``` + ## Getting started 🚀 https://module-federation.io/guide/basic/webpack.html @@ -69,10 +79,11 @@ export default defineConfig({ ## roadmap -- feat: generate mf-manifest.json -- feat: support chrome plugin +- ✅ ~~feat: generate mf-manifest.json~~ +- ✅ ~~feat: support chrome plugin~~ * ✅ ~~feat: support runtime plugins~~ +* feat: nuxt ssr - feat: download remote d.ts - feat: generate d.ts diff --git a/examples/nuxt-vite/nuxt-host/app.vue b/examples/nuxt-vite/nuxt-host/app.vue index 0303fe1..04857f5 100644 --- a/examples/nuxt-vite/nuxt-host/app.vue +++ b/examples/nuxt-vite/nuxt-host/app.vue @@ -3,7 +3,7 @@

Nuxt host

- Mode: {{ runtimeConfig.app.buildId ? ' dev ' : ' prod ' }} mode + Mode1: {{ runtimeConfig.app.buildId ? ' dev ' : ' prod ' }} mode


diff --git a/examples/nuxt-vite/nuxt-host/nuxt.config.ts b/examples/nuxt-vite/nuxt-host/nuxt.config.ts index 3215116..0a98f9d 100644 --- a/examples/nuxt-vite/nuxt-host/nuxt.config.ts +++ b/examples/nuxt-vite/nuxt-host/nuxt.config.ts @@ -1,24 +1,30 @@ import { federation } from '@module-federation/vite'; +import TopAwait from 'vite-plugin-top-level-await'; export default defineNuxtConfig({ compatibilityDate: '2024-04-03', + debug: true, devtools: { enabled: true }, vite: { plugins: [ federation({ - name: 'viteViteHost', + name: 'nuxhost', remotes: { - '@namespace/viteViteRemote': { - entry: 'http://localhost:5176/remoteEntry.js', - type: 'module', - }, + '@namespace/viteViteRemote': 'viteRemote@http://localhost:3000/_nuxt/mf-manifest.json', }, filename: 'remoteEntry.js', shared: { - vue: {}, + // vue: {}, }, runtimePlugins: ['./utils/mfPlugins'], + // exposes: { + // "./App": "./App.vue" + // } + // manifest: { + // fileName: "_nuxt/mf-manifest.json", + // } }), + new TopAwait(), ], build: { target: 'chrome89', diff --git a/examples/nuxt-vite/nuxt-host/package.json b/examples/nuxt-vite/nuxt-host/package.json index a3dc010..558a8c9 100644 --- a/examples/nuxt-vite/nuxt-host/package.json +++ b/examples/nuxt-vite/nuxt-host/package.json @@ -4,14 +4,17 @@ "type": "module", "scripts": { "build": "nuxt build", - "dev": "nuxt dev", + "dev": "NODE_OPTIONS=--experimental-vm-modules nuxt dev --experimental-vm-modules --port=3001", "generate": "nuxt generate", - "preview": "nuxt preview", + "preview": "NITRO_PORT=3001 nuxt preview", "postinstall": "nuxt prepare" }, "dependencies": { "nuxt": "^3.13.0", "vue": "latest", "@module-federation/vite": "workspace:*" + }, + "devDependencies": { + "vite-plugin-top-level-await": "^1.4.4" } } diff --git a/examples/nuxt-vite/nuxt-remote/.gitignore b/examples/nuxt-vite/nuxt-remote/.gitignore new file mode 100644 index 0000000..4a7f73a --- /dev/null +++ b/examples/nuxt-vite/nuxt-remote/.gitignore @@ -0,0 +1,24 @@ +# Nuxt dev/build outputs +.output +.data +.nuxt +.nitro +.cache +dist + +# Node dependencies +node_modules + +# Logs +logs +*.log + +# Misc +.DS_Store +.fleet +.idea + +# Local env files +.env +.env.* +!.env.example diff --git a/examples/nuxt-vite/nuxt-remote/app.vue b/examples/nuxt-vite/nuxt-remote/app.vue new file mode 100644 index 0000000..34930cb --- /dev/null +++ b/examples/nuxt-vite/nuxt-remote/app.vue @@ -0,0 +1,14 @@ + + + diff --git a/examples/nuxt-vite/nuxt-remote/nuxt.config.ts b/examples/nuxt-vite/nuxt-remote/nuxt.config.ts new file mode 100644 index 0000000..99cdd3a --- /dev/null +++ b/examples/nuxt-vite/nuxt-remote/nuxt.config.ts @@ -0,0 +1,30 @@ +import { federation } from '@module-federation/vite'; +import TopAwait from 'vite-plugin-top-level-await'; + +export default defineNuxtConfig({ + compatibilityDate: '2024-04-03', + debug: true, + devtools: { enabled: true }, + vite: { + plugins: [ + federation({ + name: 'nuxremote', + filename: 'remoteEntry.js', + shared: { + // vue: {}, + }, + runtimePlugins: ['./utils/mfPlugins'], + exposes: { + './app': './app.vue', + }, + manifest: { + fileName: '_nuxt/mf-manifest.json', + }, + }), + new TopAwait(), + ], + build: { + target: 'chrome89', + }, + }, +}); diff --git a/examples/nuxt-vite/nuxt-remote/package.json b/examples/nuxt-vite/nuxt-remote/package.json new file mode 100644 index 0000000..25e6f10 --- /dev/null +++ b/examples/nuxt-vite/nuxt-remote/package.json @@ -0,0 +1,20 @@ +{ + "name": "examples-nuxt-vite-remote", + "private": true, + "type": "module", + "scripts": { + "build": "nuxt build", + "dev": "NODE_OPTIONS=--experimental-vm-modules nuxt dev --experimental-vm-modules", + "generate": "nuxt generate", + "preview": "nuxt preview --port=3001", + "postinstall": "nuxt prepare" + }, + "dependencies": { + "@module-federation/vite": "workspace:*", + "nuxt": "^3.13.0", + "vue": "latest" + }, + "devDependencies": { + "vite-plugin-top-level-await": "^1.4.4" + } +} diff --git a/examples/nuxt-vite/nuxt-remote/public/favicon.ico b/examples/nuxt-vite/nuxt-remote/public/favicon.ico new file mode 100644 index 0000000..18993ad Binary files /dev/null and b/examples/nuxt-vite/nuxt-remote/public/favicon.ico differ diff --git a/examples/nuxt-vite/nuxt-remote/public/robots.txt b/examples/nuxt-vite/nuxt-remote/public/robots.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/examples/nuxt-vite/nuxt-remote/public/robots.txt @@ -0,0 +1 @@ + diff --git a/examples/nuxt-vite/nuxt-remote/server/tsconfig.json b/examples/nuxt-vite/nuxt-remote/server/tsconfig.json new file mode 100644 index 0000000..b9ed69c --- /dev/null +++ b/examples/nuxt-vite/nuxt-remote/server/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "../.nuxt/tsconfig.server.json" +} diff --git a/examples/nuxt-vite/nuxt-remote/tsconfig.json b/examples/nuxt-vite/nuxt-remote/tsconfig.json new file mode 100644 index 0000000..a746f2a --- /dev/null +++ b/examples/nuxt-vite/nuxt-remote/tsconfig.json @@ -0,0 +1,4 @@ +{ + // https://nuxt.com/docs/guide/concepts/typescript + "extends": "./.nuxt/tsconfig.json" +} diff --git a/examples/vite-vite/vite-host/vite.config.js b/examples/vite-vite/vite-host/vite.config.js index a4a6003..062e38d 100644 --- a/examples/vite-vite/vite-host/vite.config.js +++ b/examples/vite-vite/vite-host/vite.config.js @@ -22,15 +22,13 @@ export default defineConfig({ remote2: 'mfapp02@https://unpkg.com/mf-app-02/dist/remoteEntry.js', remote3: 'remote1@https://unpkg.com/react-manifest-example_remote1@1.0.6/dist/mf-manifest.json', - '@namespace/viteViteRemote': { - entry: 'http://localhost:5176/remoteEntry.js', - type: 'module', - }, + '@namespace/viteViteRemote': 'http://localhost:5176/mf-manifest.json', }, - filename: 'remoteEntry.js', + filename: 'remoteEntry-[hash].js', + manifest: true, shared: { vue: {}, - react: { + 'react/': { requiredVersion: '18', }, 'react-dom': {}, diff --git a/examples/vite-vite/vite-remote/vite.config.js b/examples/vite-vite/vite-remote/vite.config.js index 7aefe5e..46624fa 100644 --- a/examples/vite-vite/vite-remote/vite.config.js +++ b/examples/vite-vite/vite-remote/vite.config.js @@ -15,14 +15,16 @@ export default defineConfig({ }, // base: 'http://localhost:5176', experimental: { - renderBuiltUrl() { return { relative: true } } + renderBuiltUrl() { + return { relative: true }; + }, }, plugins: [ react({ jsxImportSource: '@emotion/react' }), federation({ name: '@namespace/viteViteRemote', exposes: { - './App1': './src/App1.jsx', + './App1': './src/App1', './App2': './src/App2.jsx', './AgGridDemo': './src/AgGridDemo.jsx', './MuiDemo': './src/MuiDemo.jsx', @@ -30,7 +32,8 @@ export default defineConfig({ './EmotionDemo': './src/EmotionDemo.jsx', '.': './src/App.jsx', }, - filename: 'remoteEntry.js', + filename: 'remoteEntry-[hash].js', + manifest: true, shared: { vue: {}, 'react/': {}, @@ -52,5 +55,12 @@ export default defineConfig({ ], build: { target: 'chrome89', + rollupOptions: { + output: { + chunkFileNames: 'static/js/[name]-[hash].js', + entryFileNames: 'static/js/[name]-[hash].js', + assetFileNames: 'static/[ext]/[name]-[hash].[ext]', + }, + }, }, }); diff --git a/lib/index.cjs b/lib/index.cjs new file mode 100644 index 0000000..e3fd316 --- /dev/null +++ b/lib/index.cjs @@ -0,0 +1,1262 @@ +var fs = require('fs'); +var path = require('pathe'); +var pluginutils = require('@rollup/pluginutils'); +var estreeWalker = require('estree-walker'); +var MagicString = require('magic-string'); +var defu = require('defu'); + +function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } + +function _interopNamespace(e) { + if (e && e.__esModule) return e; + var n = Object.create(null); + if (e) { + Object.keys(e).forEach(function (k) { + if (k !== 'default') { + var d = Object.getOwnPropertyDescriptor(e, k); + Object.defineProperty(n, k, d.get ? d : { + enumerable: true, + get: function () { return e[k]; } + }); + } + }); + } + n["default"] = e; + return n; +} + +var fs__namespace = /*#__PURE__*/_interopNamespace(fs); +var path__namespace = /*#__PURE__*/_interopNamespace(path); +var path__default = /*#__PURE__*/_interopDefaultLegacy(path); +var MagicString__default = /*#__PURE__*/_interopDefaultLegacy(MagicString); + +var addEntry = function addEntry(_ref) { + var entryName = _ref.entryName, + entryPath = _ref.entryPath, + fileName = _ref.fileName; + var devEntryPath = entryPath.startsWith("virtual:mf") ? "/@id/" + entryPath : entryPath; + var entryFiles = []; + var htmlFilePath; + var _command; + return [{ + name: 'add-entry', + apply: "serve", + config: function config(_config, _ref2) { + var command = _ref2.command; + _command = command; + }, + configureServer: function configureServer(server) { + var _server$httpServer; + (_server$httpServer = server.httpServer) == null || _server$httpServer.once == null || _server$httpServer.once('listening', function () { + var port = server.config.server.port; + fetch(path__namespace.join("http://localhost:" + port, "" + devEntryPath))["catch"](function (e) {}); + }); + server.middlewares.use(function (req, res, next) { + if (!fileName) { + next(); + return; + } + if (req.url && req.url.startsWith(fileName.replace(/^\/?/, '/'))) { + req.url = devEntryPath; + } + next(); + }); + }, + transformIndexHtml: function transformIndexHtml(c) { + return c.replace('', ""); + } + }, { + name: "add-entry", + enforce: "post", + configResolved: function configResolved(config) { + var inputOptions = config.build.rollupOptions.input; + if (!inputOptions) { + htmlFilePath = path__namespace.resolve(config.root, 'index.html'); + } else if (typeof inputOptions === 'string') { + entryFiles = [inputOptions]; + htmlFilePath = path__namespace.resolve(config.root, inputOptions); + } else if (Array.isArray(inputOptions)) { + entryFiles = inputOptions; + htmlFilePath = path__namespace.resolve(config.root, inputOptions[0]); + } else if (typeof inputOptions === 'object') { + entryFiles = Object.values(inputOptions); + htmlFilePath = path__namespace.resolve(config.root, Object.values(inputOptions)[0]); + } + }, + buildStart: function buildStart() { + var _this$emitFile; + if (_command === "serve") return; + var hasHash = fileName == null || fileName.includes == null ? void 0 : fileName.includes("[hash"); + this.emitFile((_this$emitFile = { + name: entryName + }, _this$emitFile[hasHash ? "name" : "fileName"] = fileName, _this$emitFile.type = 'chunk', _this$emitFile.id = entryPath, _this$emitFile.preserveSignature = 'strict', _this$emitFile)); + if (htmlFilePath) { + var htmlContent = fs__namespace.readFileSync(htmlFilePath, 'utf-8'); + var scriptRegex = /]*src=["']([^"']+)["'][^>]*>/gi; + var match; + while ((match = scriptRegex.exec(htmlContent)) !== null) { + entryFiles.push(match[1]); + } + } + }, + transform: function transform(code, id) { + if (entryFiles.some(function (file) { + return id.endsWith(file); + })) { + var injection = "\n import " + JSON.stringify(entryPath) + ";\n "; + return injection + code; + } + } + }]; +}; + +/** + * Solve the problem that dev mode dependency prebunding does not support top-level await syntax + */ +function PluginDevProxyModuleTopLevelAwait() { + var filterFunction = pluginutils.createFilter(); + return { + name: "dev-proxy-module-top-level-await", + apply: "serve", + transform: function transform(code, id) { + if (!code.includes("/*mf top-level-await placeholder replacement mf*/")) { + return null; + } + if (!filterFunction(id)) return null; + var ast; + try { + ast = this.parse(code, { + allowReturnOutsideFunction: true + }); + } catch (e) { + throw new Error(id + ": " + e); + } + var magicString = new MagicString__default["default"](code); + estreeWalker.walk(ast, { + enter: function enter(node) { + if (node.type === 'ExportNamedDeclaration' && node.specifiers) { + var exportSpecifiers = node.specifiers.map(function (specifier) { + return specifier.exported.name; + }); + var proxyStatements = exportSpecifiers.map(function (name) { + return "\n const __mfproxy__await" + name + " = await " + name + "();\n const __mfproxy__" + name + " = () => __mfproxy__await" + name + ";\n "; + }).join('\n'); + var exportStatements = exportSpecifiers.map(function (name) { + return "__mfproxy__" + name + " as " + name; + }).join(', '); + var start = node.start; + var end = node.end; + var replacement = proxyStatements + "\nexport { " + exportStatements + " };"; + magicString.overwrite(start, end, replacement); + } + if (node.type === 'ExportDefaultDeclaration') { + var declaration = node.declaration; + var _start = node.start; + var _end = node.end; + var proxyStatement; + var exportStatement = 'default'; + if (declaration.type === 'Identifier') { + // example: export default foo; + proxyStatement = "\n const __mfproxy__awaitdefault = await " + declaration.name + "();\n const __mfproxy__default = __mfproxy__awaitdefault;\n "; + } else if (declaration.type === 'CallExpression' || declaration.type === 'FunctionDeclaration') { + // example: export default someFunction(); + var declarationCode = code.slice(declaration.start, declaration.end); + proxyStatement = "\n const __mfproxy__awaitdefault = await (" + declarationCode + ");\n const __mfproxy__default = __mfproxy__awaitdefault;\n "; + } else { + // other + proxyStatement = "\n const __mfproxy__awaitdefault = await (" + code.slice(declaration.start, declaration.end) + ");\n const __mfproxy__default = __mfproxy__awaitdefault;\n "; + } + var _replacement = proxyStatement + "\nexport { __mfproxy__default as " + exportStatement + " };"; + magicString.overwrite(_start, _end, _replacement); + } + } + }); + return { + code: magicString.toString(), + map: magicString.generateMap({ + hires: true + }) + }; + } + }; +} + +function _arrayLikeToArray(r, a) { + (null == a || a > r.length) && (a = r.length); + for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; + return n; +} +function _createForOfIteratorHelperLoose(r, e) { + var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; + if (t) return (t = t.call(r)).next.bind(t); + if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { + t && (r = t); + var o = 0; + return function () { + return o >= r.length ? { + done: !0 + } : { + done: !1, + value: r[o++] + }; + }; + } + throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); +} +function _unsupportedIterableToArray(r, a) { + if (r) { + if ("string" == typeof r) return _arrayLikeToArray(r, a); + var t = {}.toString.call(r).slice(8, -1); + return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; + } +} + +function normalizeExposesItem(key, item) { + var importPath = ''; + if (typeof item === 'string') { + importPath = item; + } + if (typeof item === 'object') { + importPath = item["import"]; + } + return { + "import": importPath + }; +} +function normalizeExposes(exposes) { + if (!exposes) return {}; + var res = {}; + Object.keys(exposes).forEach(function (key) { + res[key] = normalizeExposesItem(key, exposes[key]); + }); + return res; +} +function normalizeRemotes(remotes) { + if (!remotes) return {}; + var result = {}; + if (typeof remotes === 'object') { + Object.keys(remotes).forEach(function (key) { + result[key] = normalizeRemoteItem(key, remotes[key]); + }); + } + return result; +} +function normalizeRemoteItem(key, remote) { + if (typeof remote === 'string') { + var _remote$split = remote.split('@'), + entryGlobalName = _remote$split[0]; + var entry = remote.replace(entryGlobalName + '@', ''); + return { + type: 'var', + name: key, + entry: entry, + entryGlobalName: entryGlobalName, + shareScope: 'default' + }; + } + return Object.assign({ + type: 'var', + name: key, + shareScope: 'default', + entryGlobalName: key + }, remote); +} +function removePathFromNpmPackage(packageString) { + // 匹配npm包名的正则表达式,忽略路径部分 + var regex = /^(?:@[^/]+\/)?[^/]+/; + // 使用正则表达式匹配并提取包名 + var match = packageString.match(regex); + // 返回匹配到的包名,如果没有匹配到则返回原字符串 + return match ? match[0] : packageString; +} +function normalizeShareItem(key, shareItem) { + var version; + try { + version = require(path__namespace.join(removePathFromNpmPackage(key), 'package.json')).version; + } catch (e) { + console.log(e); + } + if (typeof shareItem === 'string') { + return { + name: shareItem, + version: version, + scope: 'default', + from: '', + shareConfig: { + singleton: false, + requiredVersion: "^" + version || '*' + } + }; + } + return { + name: key, + from: '', + version: shareItem.version || version, + scope: shareItem.shareScope || 'default', + shareConfig: { + singleton: shareItem.singleton || false, + requiredVersion: shareItem.requiredVersion || "^" + version || '*', + strictVersion: !!shareItem.strictVersion + } + }; +} +function normalizeShared(shared) { + if (!shared) return {}; + var result = {}; + if (Array.isArray(shared)) { + shared.forEach(function (key) { + result[key] = normalizeShareItem(key, key); + }); + return result; + } + if (typeof shared === 'object') { + Object.keys(shared).forEach(function (key) { + result[key] = normalizeShareItem(key, shared[key]); + }); + } + return result; +} +function normalizeLibrary(library) { + if (!library) return undefined; + return library; +} +function normalizeManifest(manifest) { + if (manifest === void 0) { + manifest = false; + } + if (typeof manifest === "boolean") { + return manifest; + } + return Object.assign({ + filePath: "", + disableAssetsAnalyze: false, + fileName: "mf-manifest.json" + }, manifest); +} +var config; +function getNormalizeModuleFederationOptions() { + return config; +} +function getNormalizeShareItem(key) { + var options = getNormalizeModuleFederationOptions(); + var shareItem = options.shared[removePathFromNpmPackage(key)] || options.shared[removePathFromNpmPackage(key) + "/"]; + return shareItem; +} +function normalizeModuleFederationOptions(options) { + return config = { + exposes: normalizeExposes(options.exposes), + filename: options.filename || 'remoteEntry-[hash]', + library: normalizeLibrary(options.library), + name: options.name, + // remoteType: options.remoteType, + remotes: normalizeRemotes(options.remotes), + runtime: options.runtime, + shareScope: options.shareScope || 'default', + shared: normalizeShared(options.shared), + runtimePlugins: options.runtimePlugins || [], + getPublicPath: options.getPublicPath, + implementation: options.implementation, + manifest: normalizeManifest(options.manifest), + dev: options.dev, + dts: options.dts + }; +} + +/** + * Escaping rules: + * Convert using the format __${mapping}__, where _ and $ are not allowed in npm package names but can be used in variable names. + * @ => 1 + * / => 2 + * - => 3 + * . => 4 + */ +/** + * Encodes a package name into a valid file name. + * @param {string} name - The package name, e.g., "@scope/xx-xx.xx". + * @returns {string} - The encoded file name. + */ +function packageNameEncode(name) { + if (typeof name !== "string") throw new Error("A string package name is required"); + return name.replace(/@/g, "_mf_0_").replace(/\//g, "_mf_1_").replace(/-/g, "_mf_2_").replace(/\./g, "_mf_3_"); +} +/** + * Decodes an encoded file name back to the original package name. + * @param {string} encoded - The encoded file name, e.g., "_mf_0_scope_mf_1_xx_mf_2_xx_mf_3_xx". + * @returns {string} - The decoded package name. + */ +function packageNameDecode(encoded) { + if (typeof encoded !== "string") throw new Error("A string encoded file name is required"); + return encoded.replace(/_mf_0_/g, "@").replace(/_mf_1_/g, "/").replace(/_mf_2_/g, "-").replace(/_mf_3_/g, "."); +} + +/** + * https://github.com/module-federation/vite/issues/68 + */ +function getLocalSharedImportMapPath_windows() { + var _getNormalizeModuleFe = getNormalizeModuleFederationOptions(), + name = _getNormalizeModuleFe.name; + return path__default["default"].resolve(".__mf__win", packageNameEncode(name), "localSharedImportMap"); +} +function writeLocalSharedImportMap_windows(content) { + var localSharedImportMapId = getLocalSharedImportMapPath_windows(); + createFile(localSharedImportMapId + ".js", "\n// Windows temporarily needs this file, https://github.com/module-federation/vite/issues/68\n" + content); +} +function createFile(filePath, content) { + var dir = path__default["default"].dirname(filePath); + fs.mkdirSync(dir, { + recursive: true + }); + fs.writeFileSync(filePath, content); +} + +var nodeModulesDir = function findNodeModulesDir(startDir) { + if (startDir === void 0) { + startDir = process.cwd(); + } + var currentDir = startDir; + while (currentDir !== path.parse(currentDir).root) { + var nodeModulesPath = path.join(currentDir, 'node_modules'); + if (fs.existsSync(nodeModulesPath)) { + return nodeModulesPath; + } + currentDir = path.dirname(currentDir); + } + return ""; +}(); +var virtualPackageName = "__mf__virtual"; +if (!fs.existsSync(path.resolve(nodeModulesDir, virtualPackageName))) { + fs.mkdirSync(path.resolve(nodeModulesDir, virtualPackageName)); +} +fs.writeFileSync(path.resolve(nodeModulesDir, virtualPackageName, "empty.js"), ""); +fs.writeFileSync(path.resolve(nodeModulesDir, virtualPackageName, "package.json"), JSON.stringify({ + name: virtualPackageName, + main: "empty.js" +})); +var patternMap = {}; +var cacheMap = {}; +/** + * Physically generate files as virtual modules under node_modules/__mf__virtual/* + */ +var VirtualModule = /*#__PURE__*/function () { + function VirtualModule(name, tag, suffix) { + var _name$split$slice$pop; + if (tag === void 0) { + tag = '__mf_v__'; + } + if (suffix === void 0) { + suffix = ""; + } + this.name = void 0; + this.tag = void 0; + this.suffix = void 0; + this.inited = false; + this.name = name; + this.tag = tag; + this.suffix = suffix || ((_name$split$slice$pop = name.split(".").slice(1).pop()) == null ? void 0 : _name$split$slice$pop.replace(/(.)/, ".$1")) || ".js"; + if (!cacheMap[this.tag]) cacheMap[this.tag] = {}; + cacheMap[this.tag][this.name] = this; + } + VirtualModule.findModule = function findModule(tag, str) { + if (str === void 0) { + str = ""; + } + if (!patternMap[tag]) patternMap[tag] = new RegExp("(.*" + packageNameEncode(tag) + "(.+?)" + packageNameEncode(tag) + ".*)"); + var moduleName = (str.match(patternMap[tag]) || [])[2]; + if (moduleName) return cacheMap[tag][packageNameDecode(moduleName)]; + return undefined; + }; + var _proto = VirtualModule.prototype; + _proto.getPath = function getPath() { + return path.resolve(nodeModulesDir, this.getImportId()); + }; + _proto.getImportId = function getImportId() { + var _getNormalizeModuleFe = getNormalizeModuleFederationOptions(), + mfName = _getNormalizeModuleFe.name; + return virtualPackageName + "/" + packageNameEncode("" + mfName + this.tag + this.name + this.tag) + this.suffix; + }; + _proto.writeSync = function writeSync(code, force) { + if (!force && this.inited) return; + if (!this.inited) { + this.inited = true; + } + fs.writeFileSync(this.getPath(), code); + }; + _proto.write = function write(code) { + fs.writeFile(this.getPath(), code, function () {}); + }; + return VirtualModule; +}(); + +var virtualRuntimeInitStatus = new VirtualModule("runtimeInit"); +function writeRuntimeInitStatus() { + virtualRuntimeInitStatus.writeSync("\n let initResolve, initReject\n const initPromise = new Promise((re, rj) => {\n initResolve = re\n initReject = rj\n })\n export {\n initPromise,\n initResolve,\n initReject\n }\n "); +} + +var cacheRemoteMap = {}; +var LOAD_REMOTE_TAG = '__loadRemote__'; +function getRemoteVirtualModule(remote, command) { + if (!cacheRemoteMap[remote]) { + cacheRemoteMap[remote] = new VirtualModule(remote, LOAD_REMOTE_TAG, ".js"); + cacheRemoteMap[remote].writeSync(generateRemotes(remote, command)); + } + var virtual = cacheRemoteMap[remote]; + return virtual; +} +var usedRemotesMap = { + // remote1: {remote1/App, remote1, remote1/Button} +}; +function addUsedRemote(remoteKey, remoteModule) { + if (!usedRemotesMap[remoteKey]) usedRemotesMap[remoteKey] = new Set(); + usedRemotesMap[remoteKey].add(remoteModule); +} +function getUsedRemotesMap() { + return usedRemotesMap; +} +function generateRemotes(id, command) { + return "\n const {loadRemote} = require(\"@module-federation/runtime\")\n const {initPromise} = require(\"" + virtualRuntimeInitStatus.getImportId() + "\")\n const res = initPromise.then(_ => loadRemote(" + JSON.stringify(id) + "))\n const exportModule = " + (command !== "build" ? "/*mf top-level-await placeholder replacement mf*/" : "await ") + "initPromise.then(_ => res)\n module.exports = exportModule\n "; +} + +/** + * Even the resolveId hook cannot interfere with vite pre-build, + * and adding query parameter virtual modules will also fail. + * You can only proxy to the real file through alias + */ +// *** __prebuild__ +var preBuildCacheMap = {}; +var PREBUILD_TAG = "__prebuild__"; +function writePreBuildLibPath(pkg) { + if (!preBuildCacheMap[pkg]) preBuildCacheMap[pkg] = new VirtualModule(pkg, PREBUILD_TAG); + preBuildCacheMap[pkg].writeSync(""); +} +function getPreBuildLibImportId(pkg) { + if (!preBuildCacheMap[pkg]) preBuildCacheMap[pkg] = new VirtualModule(pkg, PREBUILD_TAG); + var importId = preBuildCacheMap[pkg].getImportId(); + return importId; +} +// *** __loadShare__ +var LOAD_SHARE_TAG = "__loadShare__"; +var loadShareCacheMap = {}; +function getLoadShareModulePath(pkg) { + if (!loadShareCacheMap[pkg]) loadShareCacheMap[pkg] = new VirtualModule(pkg, LOAD_SHARE_TAG, ".js"); + var filepath = loadShareCacheMap[pkg].getPath(); + return filepath; +} +function writeLoadShareModule(pkg, shareItem, command) { + loadShareCacheMap[pkg].writeSync("\n \n ;() => import(" + JSON.stringify(getPreBuildLibImportId(pkg)) + ").catch(() => {});\n // dev uses dynamic import to separate chunks\n " + (command !== "build" ? ";() => import(" + JSON.stringify(pkg) + ").catch(() => {});" : '') + "\n const {loadShare} = require(\"@module-federation/runtime\")\n const {initPromise} = require(\"" + virtualRuntimeInitStatus.getImportId() + "\")\n const res = initPromise.then(_ => loadShare(" + JSON.stringify(pkg) + ", {\n customShareInfo: {shareConfig:{\n singleton: " + shareItem.shareConfig.singleton + ",\n strictVersion: " + shareItem.shareConfig.strictVersion + ",\n requiredVersion: " + JSON.stringify(shareItem.shareConfig.requiredVersion) + "\n }}}))\n const exportModule = " + (command !== "build" ? "/*mf top-level-await placeholder replacement mf*/" : "await ") + "res.then(factory => factory())\n module.exports = exportModule\n "); +} + +var usedShares = new Set(); +function getUsedShares() { + return usedShares; +} +function addUsedShares(pkg) { + usedShares.add(pkg); +} +// *** Expose locally provided shared modules here +var localSharedImportMapModule = new VirtualModule("localSharedImportMap"); +function getLocalSharedImportMapPath() { + if (process.platform === "win32") { + return getLocalSharedImportMapPath_windows(); + } + return localSharedImportMapModule.getPath(); +} +var prevSharedCount; +function writeLocalSharedImportMap() { + var sharedCount = getUsedShares().size; + if (prevSharedCount !== sharedCount) { + prevSharedCount = sharedCount; + if (process.platform === "win32") { + writeLocalSharedImportMap_windows(generateLocalSharedImportMap()); + } else { + localSharedImportMapModule.writeSync(generateLocalSharedImportMap(), true); + } + } +} +function generateLocalSharedImportMap() { + var options = getNormalizeModuleFederationOptions(); + return "\n const importMap = {\n " + Array.from(getUsedShares()).map(function (pkg) { + return "\n " + JSON.stringify(pkg) + ": async () => {\n let pkg = await import(\"" + getPreBuildLibImportId(pkg) + "\")\n return pkg\n }\n "; + }).join(",") + "\n }\n const usedShared = {\n " + Array.from(getUsedShares()).map(function (key) { + var shareItem = getNormalizeShareItem(key); + return "\n " + JSON.stringify(key) + ": {\n name: " + JSON.stringify(key) + ",\n version: " + JSON.stringify(shareItem.version) + ",\n scope: [" + JSON.stringify(shareItem.scope) + "],\n loaded: false,\n from: " + JSON.stringify(options.name) + ",\n async get () {\n usedShared[" + JSON.stringify(key) + "].loaded = true\n const {" + JSON.stringify(key) + ": pkgDynamicImport} = importMap \n const res = await pkgDynamicImport()\n const exportModule = {...res}\n // All npm packages pre-built by vite will be converted to esm\n Object.defineProperty(exportModule, \"__esModule\", {\n value: true,\n enumerable: false\n })\n return function () {\n return exportModule\n }\n },\n shareConfig: {\n singleton: " + shareItem.shareConfig.singleton + ",\n requiredVersion: " + JSON.stringify(shareItem.shareConfig.requiredVersion) + "\n }\n }\n "; + }).join(',') + "\n }\n const usedRemotes = [" + Object.keys(getUsedRemotesMap()).map(function (key) { + var remote = options.remotes[key]; + return "\n {\n entryGlobalName: " + JSON.stringify(remote.entryGlobalName) + ",\n name: " + JSON.stringify(remote.name) + ",\n type: " + JSON.stringify(remote.type) + ",\n entry: " + JSON.stringify(remote.entry) + ",\n }\n "; + }).join(',') + "\n ]\n export {\n usedShared,\n usedRemotes\n }\n "; +} +var REMOTE_ENTRY_ID = 'virtual:mf-REMOTE_ENTRY_ID'; +function generateRemoteEntry(options) { + var pluginImportNames = options.runtimePlugins.map(function (p, i) { + return ["$runtimePlugin_" + i, "import $runtimePlugin_" + i + " from \"" + p + "\";"]; + }); + return "\n import {init as runtimeInit, loadRemote} from \"@module-federation/runtime\";\n " + pluginImportNames.map(function (item) { + return item[1]; + }).join('\n') + "\n\n const exposesMap = {\n " + Object.keys(options.exposes).map(function (key) { + return "\n " + JSON.stringify(key) + ": async () => {\n const importModule = await import(" + JSON.stringify(options.exposes[key]["import"]) + ")\n const exportModule = {}\n Object.assign(exportModule, importModule)\n Object.defineProperty(exportModule, \"__esModule\", {\n value: true,\n enumerable: false\n })\n return exportModule\n }\n "; + }).join(',') + "\n }\n import {usedShared, usedRemotes} from \"" + getLocalSharedImportMapPath() + "\"\n import {\n initResolve\n } from \"" + virtualRuntimeInitStatus.getImportId() + "\"\n async function init(shared = {}) {\n const initRes = runtimeInit({\n name: " + JSON.stringify(options.name) + ",\n remotes: usedRemotes,\n shared: usedShared,\n plugins: [" + pluginImportNames.map(function (item) { + return item[0] + "()"; + }).join(', ') + "]\n });\n initRes.initShareScopeMap('" + options.shareScope + "', shared);\n initResolve(initRes)\n return initRes\n }\n\n function getExposes(moduleName) {\n if (!(moduleName in exposesMap)) throw new Error(`Module ${moduleName} does not exist in container.`)\n return (exposesMap[moduleName])().then(res => () => res)\n }\n export {\n init,\n getExposes as get\n }\n "; +} +/** + * Inject entry file, automatically init when used as host, + * and will not inject remoteEntry + */ +var hostAutoInitModule = new VirtualModule("hostAutoInit"); +function writeHostAutoInit() { + hostAutoInitModule.writeSync("\n import {init} from \"" + REMOTE_ENTRY_ID + "\"\n init()\n "); +} +function getHostAutoInitImportId() { + return hostAutoInitModule.getImportId(); +} +function getHostAutoInitPath() { + return hostAutoInitModule.getPath(); +} + +function initVirtualModules() { + writeLocalSharedImportMap(); + writeHostAutoInit(); + writeRuntimeInitStatus(); +} + +var Manifest = function Manifest() { + var mfOptions = getNormalizeModuleFederationOptions(); + var name = mfOptions.name, + filename = mfOptions.filename, + manifestOptions = mfOptions.manifest; + var mfManifestName = ""; + if (manifestOptions === true) { + mfManifestName = "mf-manifest.json"; + } + if (typeof manifestOptions !== "boolean") { + mfManifestName = path.join((manifestOptions == null ? void 0 : manifestOptions.filePath) || "", (manifestOptions == null ? void 0 : manifestOptions.fileName) || ""); + } + var extensions; + var root; + var remoteEntryFile; + return [{ + name: 'moddule-federation-manifest', + apply: 'serve', + configureServer: function configureServer(server) { + server.middlewares.use(function (req, res, next) { + if (!mfManifestName) { + next(); + return; + } + if (req.url === mfManifestName.replace(/^\/?/, "/")) { + res.setHeader('Content-Type', 'application/json'); + res.setHeader('Access-Control-Allow-Origin', '*'); + res.end(JSON.stringify({ + id: name, + name: name, + metaData: { + name: name, + type: 'app', + buildInfo: { + buildVersion: '1.0.0', + buildName: name + }, + remoteEntry: { + name: filename, + path: '', + type: 'module' + }, + ssrRemoteEntry: { + name: filename, + path: '', + type: 'module' + }, + types: { + path: '', + name: '' + }, + globalName: name, + pluginVersion: '0.2.5', + publicPath: 'auto' + }, + shared: Array.from(getUsedShares()).map(function (shareKey) { + var shareItem = getNormalizeShareItem(shareKey); + return { + id: name + ":" + shareKey, + name: shareKey, + version: shareItem.version, + requiredVersion: shareItem.shareConfig.requiredVersion, + assets: { + js: { + async: [], + sync: [] + }, + css: { + async: [], + sync: [] + } + } + }; + }), + remotes: function () { + var remotes = []; + var usedRemotesMap = getUsedRemotesMap(); + Object.keys(usedRemotesMap).forEach(function (remoteKey) { + var usedModules = Array.from(usedRemotesMap[remoteKey]); + usedModules.forEach(function (moduleKey) { + remotes.push({ + federationContainerName: mfOptions.remotes[remoteKey].entry, + moduleName: moduleKey.replace(remoteKey, '').replace('/', ''), + alias: remoteKey, + entry: '*' + }); + }); + }); + return remotes; + }(), + exposes: Object.keys(mfOptions.exposes).map(function (key) { + var formatKey = key.replace('./', ''); + return { + id: name + ':' + formatKey, + name: formatKey, + assets: { + js: { + async: [], + sync: [] + }, + css: { + sync: [], + async: [] + } + }, + path: key + }; + }) + })); + } else { + next(); + } + }); + } + }, { + name: 'moddule-federation-manifest', + enforce: 'post', + config: function config(_config) { + if (!_config.build) _config.build = {}; + if (!_config.build.manifest) _config.build.manifest = _config.build.manifest || !!manifestOptions; + }, + configResolved: function configResolved(config) { + root = config.root; + extensions = config.resolve.extensions || ['.mjs', '.js', '.mts', '.ts', '.jsx', '.tsx', '.json']; + }, + generateBundle: function generateBundle(options, bundle) { + try { + var _this = this; + // 递归查找模块的同步导入文件 + var _findSynchronousImports = function findSynchronousImports(fileName, array) { + var fileData = bundle[fileName]; + if (fileData && fileData.type === 'chunk') { + array.push(fileName); // 将当前文件加入预加载列表 + // 遍历该文件的同步导入文件 + fileData.imports.forEach(function (importedFile) { + if (array.indexOf(importedFile) === -1) { + _findSynchronousImports(importedFile, array); // 递归查找同步导入的文件 + } + }); + } + }; + if (!mfManifestName) return Promise.resolve(); + var exposesModules = Object.keys(mfOptions.exposes).map(function (item) { + return mfOptions.exposes[item]["import"]; + }); // 获取你提供的 moduleIds + var filesContainingModules = {}; + // 帮助函数:检查模块路径是否匹配 + var isModuleMatched = function isModuleMatched(relativeModulePath, preloadModule) { + // 先尝试直接匹配 + if (relativeModulePath === preloadModule) return true; + // 如果 preloadModule 没有后缀,尝试添加可能的后缀进行匹配 + for (var _iterator = _createForOfIteratorHelperLoose(extensions), _step; !(_step = _iterator()).done;) { + var ext = _step.value; + if (relativeModulePath === "" + preloadModule + ext) { + return true; + } + } + return false; + }; + // 遍历打包生成的每个文件 + for (var _i = 0, _Object$entries = Object.entries(bundle); _i < _Object$entries.length; _i++) { + var _Object$entries$_i = _Object$entries[_i], + fileName = _Object$entries$_i[0], + fileData = _Object$entries$_i[1]; + if (mfOptions.filename.replace(/[\[\]]/g, "_") === fileData.name) { + remoteEntryFile = fileData.fileName; + } + if (fileData.type === 'chunk') { + // 遍历该文件的所有模块 + for (var _i2 = 0, _Object$keys = Object.keys(fileData.modules); _i2 < _Object$keys.length; _i2++) { + var modulePath = _Object$keys[_i2]; + // 将绝对路径转换为相对于 Vite root 的相对路径 + var relativeModulePath = path.relative(root, modulePath); + // 检查模块是否在 preloadModules 列表中 + for (var _iterator2 = _createForOfIteratorHelperLoose(exposesModules), _step2; !(_step2 = _iterator2()).done;) { + var preloadModule = _step2.value; + var formatPreloadModule = preloadModule.replace("./", ""); + if (isModuleMatched(relativeModulePath, formatPreloadModule)) { + var _filesContainingModul; + if (!filesContainingModules[preloadModule]) { + filesContainingModules[preloadModule] = { + sync: [], + async: [] + }; + } + console.log(Object.keys(fileData.modules)); + filesContainingModules[preloadModule].sync.push(fileName); + (_filesContainingModul = filesContainingModules[preloadModule].async).push.apply(_filesContainingModul, fileData.dynamicImports || []); + _findSynchronousImports(fileName, filesContainingModules[preloadModule].sync); + break; // 如果找到匹配,跳出循环 + } + } + } + } + } + ; + var fileToShareKey = {}; + return Promise.resolve(Promise.all(Array.from(getUsedShares()).map(function (shareKey) { + try { + return Promise.resolve(_this.resolve(getPreBuildLibImportId(shareKey))).then(function (_this$resolve) { + var file = _this$resolve.id.split("?")[0]; + fileToShareKey[file] = shareKey; + }); + } catch (e) { + return Promise.reject(e); + } + }))).then(function () { + // 遍历打包生成的每个文件 + for (var _i3 = 0, _Object$entries2 = Object.entries(bundle); _i3 < _Object$entries2.length; _i3++) { + var _Object$entries2$_i = _Object$entries2[_i3], + _fileName = _Object$entries2$_i[0], + _fileData = _Object$entries2$_i[1]; + if (_fileData.type === 'chunk') { + // 遍历该文件的所有模块 + for (var _i4 = 0, _Object$keys2 = Object.keys(_fileData.modules); _i4 < _Object$keys2.length; _i4++) { + var _modulePath = _Object$keys2[_i4]; + var sharedKey = fileToShareKey[_modulePath]; + if (sharedKey) { + var _filesContainingModul2; + if (!filesContainingModules[sharedKey]) { + filesContainingModules[sharedKey] = { + sync: [], + async: [] + }; + } + filesContainingModules[sharedKey].sync.push(_fileName); + (_filesContainingModul2 = filesContainingModules[sharedKey].async).push.apply(_filesContainingModul2, _fileData.dynamicImports || []); + _findSynchronousImports(_fileName, filesContainingModules[sharedKey].sync); + break; // 如果找到匹配,跳出循环 + } + } + } + } + Object.keys(filesContainingModules).forEach(function (key) { + filesContainingModules[key].sync = Array.from(new Set(filesContainingModules[key].sync)); + filesContainingModules[key].async = Array.from(new Set(filesContainingModules[key].async)); + }); + _this.emitFile({ + type: 'asset', + fileName: mfManifestName, + source: generateMFManifest(filesContainingModules) + }); + }); + } catch (e) { + return Promise.reject(e); + } + } + }]; + function generateMFManifest(preloadMap) { + var options = getNormalizeModuleFederationOptions(); + var name = options.name; + var remoteEntry = { + name: remoteEntryFile, + path: '', + type: 'module' + }; + var remotes = []; + var usedRemotesMap = getUsedRemotesMap(); + Object.keys(usedRemotesMap).forEach(function (remoteKey) { + var usedModules = Array.from(usedRemotesMap[remoteKey]); + usedModules.forEach(function (moduleKey) { + remotes.push({ + federationContainerName: options.remotes[remoteKey].entry, + moduleName: moduleKey.replace(remoteKey, '').replace('/', ''), + alias: remoteKey, + entry: '*' + }); + }); + }); + // @ts-ignore + var shared = Array.from(getUsedShares()).map(function (shareKey) { + // assets(.css, .jpg, .svg等)其他资源, 不重要, 暂未处理 + if (!preloadMap[shareKey]) return; + var shareItem = getNormalizeShareItem(shareKey); + return { + id: name + ":" + shareKey, + name: shareKey, + version: shareItem.version, + requiredVersion: shareItem.shareConfig.requiredVersion, + assets: { + js: { + async: preloadMap[shareKey].async, + sync: preloadMap[shareKey].sync + }, + css: { + async: [], + sync: [] + } + } + }; + }).filter(function (item) { + return item; + }); + var exposes = Object.keys(options.exposes).map(function (key) { + // assets(.css, .jpg, .svg等)其他资源, 不重要, 暂未处理 + var formatKey = key.replace('./', ''); + var sourceFile = options.exposes[key]["import"]; + if (!preloadMap[sourceFile]) return; + return { + id: name + ':' + formatKey, + name: formatKey, + assets: { + js: { + async: preloadMap[sourceFile].async, + sync: preloadMap[sourceFile].sync + }, + css: { + sync: [], + async: [] + } + }, + path: key + }; + }).filter(function (item) { + return item; + }); // Filter out any null values + var result = { + id: name, + name: name, + metaData: { + name: name, + type: 'app', + buildInfo: { + buildVersion: '1.0.0', + buildName: name + }, + remoteEntry: remoteEntry, + ssrRemoteEntry: remoteEntry, + types: { + path: '', + name: '' + // "zip": "@mf-types.zip", + // "api": "@mf-types.d.ts" + }, + globalName: name, + pluginVersion: '0.2.5', + publicPath: 'auto' + }, + shared: shared, + remotes: remotes, + exposes: exposes + }; + return JSON.stringify(result); + } +}; + +var _resolve, + promise = new Promise(function (resolve, reject) { + _resolve = resolve; + }); +var parsePromise = promise; +var parseStartSet = new Set(); +var parseEndSet = new Set(); +function pluginModuleParseEnd (excludeFn) { + return [{ + name: "_", + apply: "serve", + config: function config() { + // No waiting in development mode + _resolve(1); + } + }, { + enforce: "pre", + name: "parseStart", + apply: "build", + load: function load(id) { + if (excludeFn(id)) { + return; + } + parseStartSet.add(id); + } + }, { + enforce: "post", + name: "parseEnd", + apply: "build", + moduleParsed: function moduleParsed(module) { + var id = module.id; + if (excludeFn(id)) { + return; + } + parseEndSet.add(id); + if (parseStartSet.size === parseEndSet.size) { + _resolve(1); + } + } + }]; +} + +var filter = pluginutils.createFilter(); +function pluginProxyRemoteEntry () { + return { + name: 'proxyRemoteEntry', + enforce: 'post', + resolveId: function resolveId(id) { + if (id === REMOTE_ENTRY_ID) { + return REMOTE_ENTRY_ID; + } + }, + load: function load(id) { + if (id === REMOTE_ENTRY_ID) { + return parsePromise.then(function (_) { + return generateRemoteEntry(getNormalizeModuleFederationOptions()); + }); + } + }, + transform: function transform(code, id) { + try { + if (!filter(id)) return Promise.resolve(); + if (id.includes(REMOTE_ENTRY_ID)) { + return Promise.resolve(parsePromise.then(function (_) { + return generateRemoteEntry(getNormalizeModuleFederationOptions()); + })); + } + return Promise.resolve(); + } catch (e) { + return Promise.reject(e); + } + } + }; +} + +pluginutils.createFilter(); +function pluginProxyRemotes (options) { + var remotes = options.remotes; + return { + name: "proxyRemotes", + config: function config(_config, _ref) { + var _command = _ref.command; + Object.keys(remotes).forEach(function (key) { + var remote = remotes[key]; + _config.resolve.alias.push({ + find: new RegExp("^(" + remote.name + "(/.*|$))"), + replacement: "$1", + customResolver: function customResolver(source) { + var remoteModule = getRemoteVirtualModule(source, _command); + addUsedRemote(remote.name, source); + return remoteModule.getPath(); + } + }); + }); + } + }; +} + +/** + * example: + * const store = new PromiseStore(); + * store.get("example").then((result) => { + * console.log("Result from example:", result); // 42 + * }); + * setTimeout(() => { + * store.set("example", Promise.resolve(42)); + * }, 2000); + */ +var PromiseStore = /*#__PURE__*/function () { + function PromiseStore() { + this.promiseMap = new Map(); + this.resolveMap = new Map(); + } + var _proto = PromiseStore.prototype; + _proto.set = function set(id, promise) { + if (this.resolveMap.has(id)) { + promise.then(this.resolveMap.get(id)); + this.resolveMap["delete"](id); + } + this.promiseMap.set(id, promise); + }; + _proto.get = function get(id) { + var _this = this; + if (this.promiseMap.has(id)) { + return this.promiseMap.get(id); + } + var pendingPromise = new Promise(function (resolve) { + _this.resolveMap.set(id, resolve); + }); + this.promiseMap.set(id, pendingPromise); + return pendingPromise; + }; + return PromiseStore; +}(); + +function proxySharedModule(options) { + var _options$shared = options.shared, + shared = _options$shared === void 0 ? {} : _options$shared; + return [{ + name: "generateLocalSharedImportMap", + enforce: "post", + load: function load(id) { + if (id.includes(getLocalSharedImportMapPath())) { + return parsePromise.then(function (_) { + return generateLocalSharedImportMap(); + }); + } + }, + transform: function transform(code, id) { + if (id.includes(getLocalSharedImportMapPath())) { + return parsePromise.then(function (_) { + return generateLocalSharedImportMap(); + }); + } + } + }, { + name: 'proxyPreBuildShared', + enforce: 'post', + config: function config(_config, _ref) { + var _config$resolve$alias, _config$resolve$alias2; + var command = _ref.command; + (_config$resolve$alias = _config.resolve.alias).push.apply(_config$resolve$alias, Object.keys(shared).map(function (key) { + var pattern = key.endsWith("/") ? "(^" + key.replace(/\/$/, "") + "(/.+)?$)" : "(^" + key + "$)"; + return { + // Intercept all shared requests and proxy them to loadShare + find: new RegExp(pattern), + replacement: "$1", + customResolver: function customResolver(source, importer) { + var loadSharePath = getLoadShareModulePath(source); + writeLoadShareModule(source, shared[key], command); + writePreBuildLibPath(source); + addUsedShares(source); + writeLocalSharedImportMap(); + return this.resolve(loadSharePath); + } + }; + })); + var savePrebuild = new PromiseStore(); + (_config$resolve$alias2 = _config.resolve.alias).push.apply(_config$resolve$alias2, Object.keys(shared).map(function (key) { + return command === "build" ? { + find: new RegExp("(.*" + PREBUILD_TAG + ".*)"), + replacement: function replacement($1) { + var pkgName = VirtualModule.findModule(PREBUILD_TAG, $1).name; + return pkgName; + } + } : { + find: new RegExp("(.*" + PREBUILD_TAG + ".*)"), + replacement: "$1", + customResolver: function customResolver(source, importer) { + try { + var _this = this; + var pkgName = VirtualModule.findModule(PREBUILD_TAG, source).name; + if (importer.includes(LOAD_SHARE_TAG)) { + // save pre-bunding module id + savePrebuild.set(pkgName, _this.resolve(pkgName).then(function (item) { + return item.id; + })); + } + // Fix localSharedImportMap import id + var _resolve = _this.resolve; + return Promise.resolve(savePrebuild.get(pkgName)).then(function (_savePrebuild$get) { + return Promise.resolve(_resolve.call(_this, _savePrebuild$get)); + }); + } catch (e) { + return Promise.reject(e); + } + } + }; + })); + } + }, { + name: "watchLocalSharedImportMap", + apply: "serve", + config: function config(_config2) { + _config2.optimizeDeps = defu.defu(_config2.optimizeDeps, { + exclude: [getLocalSharedImportMapPath()] + }); + _config2.server = defu.defu(_config2.server, { + watch: { + ignored: [] + } + }); + var watch = _config2.server.watch; + watch.ignored = [].concat(watch.ignored); + watch.ignored.push("!**" + getLocalSharedImportMapPath() + "**"); + } + }]; +} + +var aliasToArrayPlugin = { + name: 'alias-transform-plugin', + config: function config(_config, _ref) { + if (!_config.resolve) _config.resolve = {}; + if (!_config.resolve.alias) _config.resolve.alias = []; + var alias = _config.resolve.alias; + if (typeof alias === 'object' && !Array.isArray(alias)) { + _config.resolve.alias = Object.entries(alias).map(function (_ref2) { + var find = _ref2[0], + replacement = _ref2[1]; + return { + find: find, + replacement: replacement + }; + }); + } + } +}; + +var normalizeOptimizeDepsPlugin = { + name: 'normalizeOptimizeDeps', + config: function config(_config, _ref) { + var optimizeDeps = _config.optimizeDeps; + if (!optimizeDeps) { + _config.optimizeDeps = {}; + optimizeDeps = _config.optimizeDeps; + } + // todo: fix this workaround + optimizeDeps.force = true; + if (!optimizeDeps.include) optimizeDeps.include = []; + if (!optimizeDeps.needsInterop) optimizeDeps.needsInterop = []; + } +}; + +function federation(mfUserOptions) { + var options = normalizeModuleFederationOptions(mfUserOptions); + initVirtualModules(); + var name = options.name, + shared = options.shared, + filename = options.filename; + if (!name) throw new Error("name is required"); + return [aliasToArrayPlugin, normalizeOptimizeDepsPlugin].concat(addEntry({ + entryName: 'remoteEntry', + entryPath: REMOTE_ENTRY_ID, + fileName: filename + }), addEntry({ + entryName: 'hostInit', + entryPath: getHostAutoInitPath() + }), [pluginProxyRemoteEntry(), pluginProxyRemotes(options)], pluginModuleParseEnd(function (id) { + return id.includes(getHostAutoInitImportId()) || id.includes(REMOTE_ENTRY_ID) || id.includes(getLocalSharedImportMapPath()); + }), proxySharedModule({ + shared: shared + }), [PluginDevProxyModuleTopLevelAwait(), { + name: 'module-federation-vite', + enforce: 'post', + config: function config(_config, _ref) { + var _config$optimizeDeps; + _config.resolve.alias.push({ + find: '@module-federation/runtime', + replacement: require.resolve('@module-federation/runtime') + }); + (_config$optimizeDeps = _config.optimizeDeps) == null || (_config$optimizeDeps = _config$optimizeDeps.include) == null || _config$optimizeDeps.push('@module-federation/runtime'); + } + }], Manifest()); +} + +exports.federation = federation; diff --git a/lib/index.d.ts b/lib/index.d.ts new file mode 100644 index 0000000..c543f08 --- /dev/null +++ b/lib/index.d.ts @@ -0,0 +1,4 @@ +import { Plugin } from 'vite'; +import { ModuleFederationOptions } from './utils/normalizeModuleFederationOptions'; +declare function federation(mfUserOptions: ModuleFederationOptions): Plugin[]; +export { federation }; diff --git a/lib/index.esm.js b/lib/index.esm.js new file mode 100644 index 0000000..dd6b064 --- /dev/null +++ b/lib/index.esm.js @@ -0,0 +1,1239 @@ +import * as fs from 'fs'; +import { mkdirSync, writeFileSync, existsSync, writeFile } from 'fs'; +import * as path from 'pathe'; +import path__default, { parse, join, dirname, resolve, relative } from 'pathe'; +import { createFilter } from '@rollup/pluginutils'; +import { walk } from 'estree-walker'; +import MagicString from 'magic-string'; +import { defu } from 'defu'; + +var addEntry = function addEntry(_ref) { + var entryName = _ref.entryName, + entryPath = _ref.entryPath, + fileName = _ref.fileName; + var devEntryPath = entryPath.startsWith("virtual:mf") ? "/@id/" + entryPath : entryPath; + var entryFiles = []; + var htmlFilePath; + var _command; + return [{ + name: 'add-entry', + apply: "serve", + config: function config(_config, _ref2) { + var command = _ref2.command; + _command = command; + }, + configureServer: function configureServer(server) { + var _server$httpServer; + (_server$httpServer = server.httpServer) == null || _server$httpServer.once == null || _server$httpServer.once('listening', function () { + var port = server.config.server.port; + fetch(path.join("http://localhost:" + port, "" + devEntryPath))["catch"](function (e) {}); + }); + server.middlewares.use(function (req, res, next) { + if (!fileName) { + next(); + return; + } + if (req.url && req.url.startsWith(fileName.replace(/^\/?/, '/'))) { + req.url = devEntryPath; + } + next(); + }); + }, + transformIndexHtml: function transformIndexHtml(c) { + return c.replace('', ""); + } + }, { + name: "add-entry", + enforce: "post", + configResolved: function configResolved(config) { + var inputOptions = config.build.rollupOptions.input; + if (!inputOptions) { + htmlFilePath = path.resolve(config.root, 'index.html'); + } else if (typeof inputOptions === 'string') { + entryFiles = [inputOptions]; + htmlFilePath = path.resolve(config.root, inputOptions); + } else if (Array.isArray(inputOptions)) { + entryFiles = inputOptions; + htmlFilePath = path.resolve(config.root, inputOptions[0]); + } else if (typeof inputOptions === 'object') { + entryFiles = Object.values(inputOptions); + htmlFilePath = path.resolve(config.root, Object.values(inputOptions)[0]); + } + }, + buildStart: function buildStart() { + var _this$emitFile; + if (_command === "serve") return; + var hasHash = fileName == null || fileName.includes == null ? void 0 : fileName.includes("[hash"); + this.emitFile((_this$emitFile = { + name: entryName + }, _this$emitFile[hasHash ? "name" : "fileName"] = fileName, _this$emitFile.type = 'chunk', _this$emitFile.id = entryPath, _this$emitFile.preserveSignature = 'strict', _this$emitFile)); + if (htmlFilePath) { + var htmlContent = fs.readFileSync(htmlFilePath, 'utf-8'); + var scriptRegex = /]*src=["']([^"']+)["'][^>]*>/gi; + var match; + while ((match = scriptRegex.exec(htmlContent)) !== null) { + entryFiles.push(match[1]); + } + } + }, + transform: function transform(code, id) { + if (entryFiles.some(function (file) { + return id.endsWith(file); + })) { + var injection = "\n import " + JSON.stringify(entryPath) + ";\n "; + return injection + code; + } + } + }]; +}; + +/** + * Solve the problem that dev mode dependency prebunding does not support top-level await syntax + */ +function PluginDevProxyModuleTopLevelAwait() { + var filterFunction = createFilter(); + return { + name: "dev-proxy-module-top-level-await", + apply: "serve", + transform: function transform(code, id) { + if (!code.includes("/*mf top-level-await placeholder replacement mf*/")) { + return null; + } + if (!filterFunction(id)) return null; + var ast; + try { + ast = this.parse(code, { + allowReturnOutsideFunction: true + }); + } catch (e) { + throw new Error(id + ": " + e); + } + var magicString = new MagicString(code); + walk(ast, { + enter: function enter(node) { + if (node.type === 'ExportNamedDeclaration' && node.specifiers) { + var exportSpecifiers = node.specifiers.map(function (specifier) { + return specifier.exported.name; + }); + var proxyStatements = exportSpecifiers.map(function (name) { + return "\n const __mfproxy__await" + name + " = await " + name + "();\n const __mfproxy__" + name + " = () => __mfproxy__await" + name + ";\n "; + }).join('\n'); + var exportStatements = exportSpecifiers.map(function (name) { + return "__mfproxy__" + name + " as " + name; + }).join(', '); + var start = node.start; + var end = node.end; + var replacement = proxyStatements + "\nexport { " + exportStatements + " };"; + magicString.overwrite(start, end, replacement); + } + if (node.type === 'ExportDefaultDeclaration') { + var declaration = node.declaration; + var _start = node.start; + var _end = node.end; + var proxyStatement; + var exportStatement = 'default'; + if (declaration.type === 'Identifier') { + // example: export default foo; + proxyStatement = "\n const __mfproxy__awaitdefault = await " + declaration.name + "();\n const __mfproxy__default = __mfproxy__awaitdefault;\n "; + } else if (declaration.type === 'CallExpression' || declaration.type === 'FunctionDeclaration') { + // example: export default someFunction(); + var declarationCode = code.slice(declaration.start, declaration.end); + proxyStatement = "\n const __mfproxy__awaitdefault = await (" + declarationCode + ");\n const __mfproxy__default = __mfproxy__awaitdefault;\n "; + } else { + // other + proxyStatement = "\n const __mfproxy__awaitdefault = await (" + code.slice(declaration.start, declaration.end) + ");\n const __mfproxy__default = __mfproxy__awaitdefault;\n "; + } + var _replacement = proxyStatement + "\nexport { __mfproxy__default as " + exportStatement + " };"; + magicString.overwrite(_start, _end, _replacement); + } + } + }); + return { + code: magicString.toString(), + map: magicString.generateMap({ + hires: true + }) + }; + } + }; +} + +function _arrayLikeToArray(r, a) { + (null == a || a > r.length) && (a = r.length); + for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; + return n; +} +function _createForOfIteratorHelperLoose(r, e) { + var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; + if (t) return (t = t.call(r)).next.bind(t); + if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { + t && (r = t); + var o = 0; + return function () { + return o >= r.length ? { + done: !0 + } : { + done: !1, + value: r[o++] + }; + }; + } + throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); +} +function _unsupportedIterableToArray(r, a) { + if (r) { + if ("string" == typeof r) return _arrayLikeToArray(r, a); + var t = {}.toString.call(r).slice(8, -1); + return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; + } +} + +function normalizeExposesItem(key, item) { + var importPath = ''; + if (typeof item === 'string') { + importPath = item; + } + if (typeof item === 'object') { + importPath = item["import"]; + } + return { + "import": importPath + }; +} +function normalizeExposes(exposes) { + if (!exposes) return {}; + var res = {}; + Object.keys(exposes).forEach(function (key) { + res[key] = normalizeExposesItem(key, exposes[key]); + }); + return res; +} +function normalizeRemotes(remotes) { + if (!remotes) return {}; + var result = {}; + if (typeof remotes === 'object') { + Object.keys(remotes).forEach(function (key) { + result[key] = normalizeRemoteItem(key, remotes[key]); + }); + } + return result; +} +function normalizeRemoteItem(key, remote) { + if (typeof remote === 'string') { + var _remote$split = remote.split('@'), + entryGlobalName = _remote$split[0]; + var entry = remote.replace(entryGlobalName + '@', ''); + return { + type: 'var', + name: key, + entry: entry, + entryGlobalName: entryGlobalName, + shareScope: 'default' + }; + } + return Object.assign({ + type: 'var', + name: key, + shareScope: 'default', + entryGlobalName: key + }, remote); +} +function removePathFromNpmPackage(packageString) { + // 匹配npm包名的正则表达式,忽略路径部分 + var regex = /^(?:@[^/]+\/)?[^/]+/; + // 使用正则表达式匹配并提取包名 + var match = packageString.match(regex); + // 返回匹配到的包名,如果没有匹配到则返回原字符串 + return match ? match[0] : packageString; +} +function normalizeShareItem(key, shareItem) { + var version; + try { + version = require(path.join(removePathFromNpmPackage(key), 'package.json')).version; + } catch (e) { + console.log(e); + } + if (typeof shareItem === 'string') { + return { + name: shareItem, + version: version, + scope: 'default', + from: '', + shareConfig: { + singleton: false, + requiredVersion: "^" + version || '*' + } + }; + } + return { + name: key, + from: '', + version: shareItem.version || version, + scope: shareItem.shareScope || 'default', + shareConfig: { + singleton: shareItem.singleton || false, + requiredVersion: shareItem.requiredVersion || "^" + version || '*', + strictVersion: !!shareItem.strictVersion + } + }; +} +function normalizeShared(shared) { + if (!shared) return {}; + var result = {}; + if (Array.isArray(shared)) { + shared.forEach(function (key) { + result[key] = normalizeShareItem(key, key); + }); + return result; + } + if (typeof shared === 'object') { + Object.keys(shared).forEach(function (key) { + result[key] = normalizeShareItem(key, shared[key]); + }); + } + return result; +} +function normalizeLibrary(library) { + if (!library) return undefined; + return library; +} +function normalizeManifest(manifest) { + if (manifest === void 0) { + manifest = false; + } + if (typeof manifest === "boolean") { + return manifest; + } + return Object.assign({ + filePath: "", + disableAssetsAnalyze: false, + fileName: "mf-manifest.json" + }, manifest); +} +var config; +function getNormalizeModuleFederationOptions() { + return config; +} +function getNormalizeShareItem(key) { + var options = getNormalizeModuleFederationOptions(); + var shareItem = options.shared[removePathFromNpmPackage(key)] || options.shared[removePathFromNpmPackage(key) + "/"]; + return shareItem; +} +function normalizeModuleFederationOptions(options) { + return config = { + exposes: normalizeExposes(options.exposes), + filename: options.filename || 'remoteEntry-[hash]', + library: normalizeLibrary(options.library), + name: options.name, + // remoteType: options.remoteType, + remotes: normalizeRemotes(options.remotes), + runtime: options.runtime, + shareScope: options.shareScope || 'default', + shared: normalizeShared(options.shared), + runtimePlugins: options.runtimePlugins || [], + getPublicPath: options.getPublicPath, + implementation: options.implementation, + manifest: normalizeManifest(options.manifest), + dev: options.dev, + dts: options.dts + }; +} + +/** + * Escaping rules: + * Convert using the format __${mapping}__, where _ and $ are not allowed in npm package names but can be used in variable names. + * @ => 1 + * / => 2 + * - => 3 + * . => 4 + */ +/** + * Encodes a package name into a valid file name. + * @param {string} name - The package name, e.g., "@scope/xx-xx.xx". + * @returns {string} - The encoded file name. + */ +function packageNameEncode(name) { + if (typeof name !== "string") throw new Error("A string package name is required"); + return name.replace(/@/g, "_mf_0_").replace(/\//g, "_mf_1_").replace(/-/g, "_mf_2_").replace(/\./g, "_mf_3_"); +} +/** + * Decodes an encoded file name back to the original package name. + * @param {string} encoded - The encoded file name, e.g., "_mf_0_scope_mf_1_xx_mf_2_xx_mf_3_xx". + * @returns {string} - The decoded package name. + */ +function packageNameDecode(encoded) { + if (typeof encoded !== "string") throw new Error("A string encoded file name is required"); + return encoded.replace(/_mf_0_/g, "@").replace(/_mf_1_/g, "/").replace(/_mf_2_/g, "-").replace(/_mf_3_/g, "."); +} + +/** + * https://github.com/module-federation/vite/issues/68 + */ +function getLocalSharedImportMapPath_windows() { + var _getNormalizeModuleFe = getNormalizeModuleFederationOptions(), + name = _getNormalizeModuleFe.name; + return path__default.resolve(".__mf__win", packageNameEncode(name), "localSharedImportMap"); +} +function writeLocalSharedImportMap_windows(content) { + var localSharedImportMapId = getLocalSharedImportMapPath_windows(); + createFile(localSharedImportMapId + ".js", "\n// Windows temporarily needs this file, https://github.com/module-federation/vite/issues/68\n" + content); +} +function createFile(filePath, content) { + var dir = path__default.dirname(filePath); + mkdirSync(dir, { + recursive: true + }); + writeFileSync(filePath, content); +} + +var nodeModulesDir = function findNodeModulesDir(startDir) { + if (startDir === void 0) { + startDir = process.cwd(); + } + var currentDir = startDir; + while (currentDir !== parse(currentDir).root) { + var nodeModulesPath = join(currentDir, 'node_modules'); + if (existsSync(nodeModulesPath)) { + return nodeModulesPath; + } + currentDir = dirname(currentDir); + } + return ""; +}(); +var virtualPackageName = "__mf__virtual"; +if (!existsSync(resolve(nodeModulesDir, virtualPackageName))) { + mkdirSync(resolve(nodeModulesDir, virtualPackageName)); +} +writeFileSync(resolve(nodeModulesDir, virtualPackageName, "empty.js"), ""); +writeFileSync(resolve(nodeModulesDir, virtualPackageName, "package.json"), JSON.stringify({ + name: virtualPackageName, + main: "empty.js" +})); +var patternMap = {}; +var cacheMap = {}; +/** + * Physically generate files as virtual modules under node_modules/__mf__virtual/* + */ +var VirtualModule = /*#__PURE__*/function () { + function VirtualModule(name, tag, suffix) { + var _name$split$slice$pop; + if (tag === void 0) { + tag = '__mf_v__'; + } + if (suffix === void 0) { + suffix = ""; + } + this.name = void 0; + this.tag = void 0; + this.suffix = void 0; + this.inited = false; + this.name = name; + this.tag = tag; + this.suffix = suffix || ((_name$split$slice$pop = name.split(".").slice(1).pop()) == null ? void 0 : _name$split$slice$pop.replace(/(.)/, ".$1")) || ".js"; + if (!cacheMap[this.tag]) cacheMap[this.tag] = {}; + cacheMap[this.tag][this.name] = this; + } + VirtualModule.findModule = function findModule(tag, str) { + if (str === void 0) { + str = ""; + } + if (!patternMap[tag]) patternMap[tag] = new RegExp("(.*" + packageNameEncode(tag) + "(.+?)" + packageNameEncode(tag) + ".*)"); + var moduleName = (str.match(patternMap[tag]) || [])[2]; + if (moduleName) return cacheMap[tag][packageNameDecode(moduleName)]; + return undefined; + }; + var _proto = VirtualModule.prototype; + _proto.getPath = function getPath() { + return resolve(nodeModulesDir, this.getImportId()); + }; + _proto.getImportId = function getImportId() { + var _getNormalizeModuleFe = getNormalizeModuleFederationOptions(), + mfName = _getNormalizeModuleFe.name; + return virtualPackageName + "/" + packageNameEncode("" + mfName + this.tag + this.name + this.tag) + this.suffix; + }; + _proto.writeSync = function writeSync(code, force) { + if (!force && this.inited) return; + if (!this.inited) { + this.inited = true; + } + writeFileSync(this.getPath(), code); + }; + _proto.write = function write(code) { + writeFile(this.getPath(), code, function () {}); + }; + return VirtualModule; +}(); + +var virtualRuntimeInitStatus = new VirtualModule("runtimeInit"); +function writeRuntimeInitStatus() { + virtualRuntimeInitStatus.writeSync("\n let initResolve, initReject\n const initPromise = new Promise((re, rj) => {\n initResolve = re\n initReject = rj\n })\n export {\n initPromise,\n initResolve,\n initReject\n }\n "); +} + +var cacheRemoteMap = {}; +var LOAD_REMOTE_TAG = '__loadRemote__'; +function getRemoteVirtualModule(remote, command) { + if (!cacheRemoteMap[remote]) { + cacheRemoteMap[remote] = new VirtualModule(remote, LOAD_REMOTE_TAG, ".js"); + cacheRemoteMap[remote].writeSync(generateRemotes(remote, command)); + } + var virtual = cacheRemoteMap[remote]; + return virtual; +} +var usedRemotesMap = { + // remote1: {remote1/App, remote1, remote1/Button} +}; +function addUsedRemote(remoteKey, remoteModule) { + if (!usedRemotesMap[remoteKey]) usedRemotesMap[remoteKey] = new Set(); + usedRemotesMap[remoteKey].add(remoteModule); +} +function getUsedRemotesMap() { + return usedRemotesMap; +} +function generateRemotes(id, command) { + return "\n const {loadRemote} = require(\"@module-federation/runtime\")\n const {initPromise} = require(\"" + virtualRuntimeInitStatus.getImportId() + "\")\n const res = initPromise.then(_ => loadRemote(" + JSON.stringify(id) + "))\n const exportModule = " + (command !== "build" ? "/*mf top-level-await placeholder replacement mf*/" : "await ") + "initPromise.then(_ => res)\n module.exports = exportModule\n "; +} + +/** + * Even the resolveId hook cannot interfere with vite pre-build, + * and adding query parameter virtual modules will also fail. + * You can only proxy to the real file through alias + */ +// *** __prebuild__ +var preBuildCacheMap = {}; +var PREBUILD_TAG = "__prebuild__"; +function writePreBuildLibPath(pkg) { + if (!preBuildCacheMap[pkg]) preBuildCacheMap[pkg] = new VirtualModule(pkg, PREBUILD_TAG); + preBuildCacheMap[pkg].writeSync(""); +} +function getPreBuildLibImportId(pkg) { + if (!preBuildCacheMap[pkg]) preBuildCacheMap[pkg] = new VirtualModule(pkg, PREBUILD_TAG); + var importId = preBuildCacheMap[pkg].getImportId(); + return importId; +} +// *** __loadShare__ +var LOAD_SHARE_TAG = "__loadShare__"; +var loadShareCacheMap = {}; +function getLoadShareModulePath(pkg) { + if (!loadShareCacheMap[pkg]) loadShareCacheMap[pkg] = new VirtualModule(pkg, LOAD_SHARE_TAG, ".js"); + var filepath = loadShareCacheMap[pkg].getPath(); + return filepath; +} +function writeLoadShareModule(pkg, shareItem, command) { + loadShareCacheMap[pkg].writeSync("\n \n ;() => import(" + JSON.stringify(getPreBuildLibImportId(pkg)) + ").catch(() => {});\n // dev uses dynamic import to separate chunks\n " + (command !== "build" ? ";() => import(" + JSON.stringify(pkg) + ").catch(() => {});" : '') + "\n const {loadShare} = require(\"@module-federation/runtime\")\n const {initPromise} = require(\"" + virtualRuntimeInitStatus.getImportId() + "\")\n const res = initPromise.then(_ => loadShare(" + JSON.stringify(pkg) + ", {\n customShareInfo: {shareConfig:{\n singleton: " + shareItem.shareConfig.singleton + ",\n strictVersion: " + shareItem.shareConfig.strictVersion + ",\n requiredVersion: " + JSON.stringify(shareItem.shareConfig.requiredVersion) + "\n }}}))\n const exportModule = " + (command !== "build" ? "/*mf top-level-await placeholder replacement mf*/" : "await ") + "res.then(factory => factory())\n module.exports = exportModule\n "); +} + +var usedShares = new Set(); +function getUsedShares() { + return usedShares; +} +function addUsedShares(pkg) { + usedShares.add(pkg); +} +// *** Expose locally provided shared modules here +var localSharedImportMapModule = new VirtualModule("localSharedImportMap"); +function getLocalSharedImportMapPath() { + if (process.platform === "win32") { + return getLocalSharedImportMapPath_windows(); + } + return localSharedImportMapModule.getPath(); +} +var prevSharedCount; +function writeLocalSharedImportMap() { + var sharedCount = getUsedShares().size; + if (prevSharedCount !== sharedCount) { + prevSharedCount = sharedCount; + if (process.platform === "win32") { + writeLocalSharedImportMap_windows(generateLocalSharedImportMap()); + } else { + localSharedImportMapModule.writeSync(generateLocalSharedImportMap(), true); + } + } +} +function generateLocalSharedImportMap() { + var options = getNormalizeModuleFederationOptions(); + return "\n const importMap = {\n " + Array.from(getUsedShares()).map(function (pkg) { + return "\n " + JSON.stringify(pkg) + ": async () => {\n let pkg = await import(\"" + getPreBuildLibImportId(pkg) + "\")\n return pkg\n }\n "; + }).join(",") + "\n }\n const usedShared = {\n " + Array.from(getUsedShares()).map(function (key) { + var shareItem = getNormalizeShareItem(key); + return "\n " + JSON.stringify(key) + ": {\n name: " + JSON.stringify(key) + ",\n version: " + JSON.stringify(shareItem.version) + ",\n scope: [" + JSON.stringify(shareItem.scope) + "],\n loaded: false,\n from: " + JSON.stringify(options.name) + ",\n async get () {\n usedShared[" + JSON.stringify(key) + "].loaded = true\n const {" + JSON.stringify(key) + ": pkgDynamicImport} = importMap \n const res = await pkgDynamicImport()\n const exportModule = {...res}\n // All npm packages pre-built by vite will be converted to esm\n Object.defineProperty(exportModule, \"__esModule\", {\n value: true,\n enumerable: false\n })\n return function () {\n return exportModule\n }\n },\n shareConfig: {\n singleton: " + shareItem.shareConfig.singleton + ",\n requiredVersion: " + JSON.stringify(shareItem.shareConfig.requiredVersion) + "\n }\n }\n "; + }).join(',') + "\n }\n const usedRemotes = [" + Object.keys(getUsedRemotesMap()).map(function (key) { + var remote = options.remotes[key]; + return "\n {\n entryGlobalName: " + JSON.stringify(remote.entryGlobalName) + ",\n name: " + JSON.stringify(remote.name) + ",\n type: " + JSON.stringify(remote.type) + ",\n entry: " + JSON.stringify(remote.entry) + ",\n }\n "; + }).join(',') + "\n ]\n export {\n usedShared,\n usedRemotes\n }\n "; +} +var REMOTE_ENTRY_ID = 'virtual:mf-REMOTE_ENTRY_ID'; +function generateRemoteEntry(options) { + var pluginImportNames = options.runtimePlugins.map(function (p, i) { + return ["$runtimePlugin_" + i, "import $runtimePlugin_" + i + " from \"" + p + "\";"]; + }); + return "\n import {init as runtimeInit, loadRemote} from \"@module-federation/runtime\";\n " + pluginImportNames.map(function (item) { + return item[1]; + }).join('\n') + "\n\n const exposesMap = {\n " + Object.keys(options.exposes).map(function (key) { + return "\n " + JSON.stringify(key) + ": async () => {\n const importModule = await import(" + JSON.stringify(options.exposes[key]["import"]) + ")\n const exportModule = {}\n Object.assign(exportModule, importModule)\n Object.defineProperty(exportModule, \"__esModule\", {\n value: true,\n enumerable: false\n })\n return exportModule\n }\n "; + }).join(',') + "\n }\n import {usedShared, usedRemotes} from \"" + getLocalSharedImportMapPath() + "\"\n import {\n initResolve\n } from \"" + virtualRuntimeInitStatus.getImportId() + "\"\n async function init(shared = {}) {\n const initRes = runtimeInit({\n name: " + JSON.stringify(options.name) + ",\n remotes: usedRemotes,\n shared: usedShared,\n plugins: [" + pluginImportNames.map(function (item) { + return item[0] + "()"; + }).join(', ') + "]\n });\n initRes.initShareScopeMap('" + options.shareScope + "', shared);\n initResolve(initRes)\n return initRes\n }\n\n function getExposes(moduleName) {\n if (!(moduleName in exposesMap)) throw new Error(`Module ${moduleName} does not exist in container.`)\n return (exposesMap[moduleName])().then(res => () => res)\n }\n export {\n init,\n getExposes as get\n }\n "; +} +/** + * Inject entry file, automatically init when used as host, + * and will not inject remoteEntry + */ +var hostAutoInitModule = new VirtualModule("hostAutoInit"); +function writeHostAutoInit() { + hostAutoInitModule.writeSync("\n import {init} from \"" + REMOTE_ENTRY_ID + "\"\n init()\n "); +} +function getHostAutoInitImportId() { + return hostAutoInitModule.getImportId(); +} +function getHostAutoInitPath() { + return hostAutoInitModule.getPath(); +} + +function initVirtualModules() { + writeLocalSharedImportMap(); + writeHostAutoInit(); + writeRuntimeInitStatus(); +} + +var Manifest = function Manifest() { + var mfOptions = getNormalizeModuleFederationOptions(); + var name = mfOptions.name, + filename = mfOptions.filename, + manifestOptions = mfOptions.manifest; + var mfManifestName = ""; + if (manifestOptions === true) { + mfManifestName = "mf-manifest.json"; + } + if (typeof manifestOptions !== "boolean") { + mfManifestName = join((manifestOptions == null ? void 0 : manifestOptions.filePath) || "", (manifestOptions == null ? void 0 : manifestOptions.fileName) || ""); + } + var extensions; + var root; + var remoteEntryFile; + return [{ + name: 'moddule-federation-manifest', + apply: 'serve', + configureServer: function configureServer(server) { + server.middlewares.use(function (req, res, next) { + if (!mfManifestName) { + next(); + return; + } + if (req.url === mfManifestName.replace(/^\/?/, "/")) { + res.setHeader('Content-Type', 'application/json'); + res.setHeader('Access-Control-Allow-Origin', '*'); + res.end(JSON.stringify({ + id: name, + name: name, + metaData: { + name: name, + type: 'app', + buildInfo: { + buildVersion: '1.0.0', + buildName: name + }, + remoteEntry: { + name: filename, + path: '', + type: 'module' + }, + ssrRemoteEntry: { + name: filename, + path: '', + type: 'module' + }, + types: { + path: '', + name: '' + }, + globalName: name, + pluginVersion: '0.2.5', + publicPath: 'auto' + }, + shared: Array.from(getUsedShares()).map(function (shareKey) { + var shareItem = getNormalizeShareItem(shareKey); + return { + id: name + ":" + shareKey, + name: shareKey, + version: shareItem.version, + requiredVersion: shareItem.shareConfig.requiredVersion, + assets: { + js: { + async: [], + sync: [] + }, + css: { + async: [], + sync: [] + } + } + }; + }), + remotes: function () { + var remotes = []; + var usedRemotesMap = getUsedRemotesMap(); + Object.keys(usedRemotesMap).forEach(function (remoteKey) { + var usedModules = Array.from(usedRemotesMap[remoteKey]); + usedModules.forEach(function (moduleKey) { + remotes.push({ + federationContainerName: mfOptions.remotes[remoteKey].entry, + moduleName: moduleKey.replace(remoteKey, '').replace('/', ''), + alias: remoteKey, + entry: '*' + }); + }); + }); + return remotes; + }(), + exposes: Object.keys(mfOptions.exposes).map(function (key) { + var formatKey = key.replace('./', ''); + return { + id: name + ':' + formatKey, + name: formatKey, + assets: { + js: { + async: [], + sync: [] + }, + css: { + sync: [], + async: [] + } + }, + path: key + }; + }) + })); + } else { + next(); + } + }); + } + }, { + name: 'moddule-federation-manifest', + enforce: 'post', + config: function config(_config) { + if (!_config.build) _config.build = {}; + if (!_config.build.manifest) _config.build.manifest = _config.build.manifest || !!manifestOptions; + }, + configResolved: function configResolved(config) { + root = config.root; + extensions = config.resolve.extensions || ['.mjs', '.js', '.mts', '.ts', '.jsx', '.tsx', '.json']; + }, + generateBundle: function generateBundle(options, bundle) { + try { + var _this = this; + // 递归查找模块的同步导入文件 + var _findSynchronousImports = function findSynchronousImports(fileName, array) { + var fileData = bundle[fileName]; + if (fileData && fileData.type === 'chunk') { + array.push(fileName); // 将当前文件加入预加载列表 + // 遍历该文件的同步导入文件 + fileData.imports.forEach(function (importedFile) { + if (array.indexOf(importedFile) === -1) { + _findSynchronousImports(importedFile, array); // 递归查找同步导入的文件 + } + }); + } + }; + if (!mfManifestName) return Promise.resolve(); + var exposesModules = Object.keys(mfOptions.exposes).map(function (item) { + return mfOptions.exposes[item]["import"]; + }); // 获取你提供的 moduleIds + var filesContainingModules = {}; + // 帮助函数:检查模块路径是否匹配 + var isModuleMatched = function isModuleMatched(relativeModulePath, preloadModule) { + // 先尝试直接匹配 + if (relativeModulePath === preloadModule) return true; + // 如果 preloadModule 没有后缀,尝试添加可能的后缀进行匹配 + for (var _iterator = _createForOfIteratorHelperLoose(extensions), _step; !(_step = _iterator()).done;) { + var ext = _step.value; + if (relativeModulePath === "" + preloadModule + ext) { + return true; + } + } + return false; + }; + // 遍历打包生成的每个文件 + for (var _i = 0, _Object$entries = Object.entries(bundle); _i < _Object$entries.length; _i++) { + var _Object$entries$_i = _Object$entries[_i], + fileName = _Object$entries$_i[0], + fileData = _Object$entries$_i[1]; + if (mfOptions.filename.replace(/[\[\]]/g, "_") === fileData.name) { + remoteEntryFile = fileData.fileName; + } + if (fileData.type === 'chunk') { + // 遍历该文件的所有模块 + for (var _i2 = 0, _Object$keys = Object.keys(fileData.modules); _i2 < _Object$keys.length; _i2++) { + var modulePath = _Object$keys[_i2]; + // 将绝对路径转换为相对于 Vite root 的相对路径 + var relativeModulePath = relative(root, modulePath); + // 检查模块是否在 preloadModules 列表中 + for (var _iterator2 = _createForOfIteratorHelperLoose(exposesModules), _step2; !(_step2 = _iterator2()).done;) { + var preloadModule = _step2.value; + var formatPreloadModule = preloadModule.replace("./", ""); + if (isModuleMatched(relativeModulePath, formatPreloadModule)) { + var _filesContainingModul; + if (!filesContainingModules[preloadModule]) { + filesContainingModules[preloadModule] = { + sync: [], + async: [] + }; + } + console.log(Object.keys(fileData.modules)); + filesContainingModules[preloadModule].sync.push(fileName); + (_filesContainingModul = filesContainingModules[preloadModule].async).push.apply(_filesContainingModul, fileData.dynamicImports || []); + _findSynchronousImports(fileName, filesContainingModules[preloadModule].sync); + break; // 如果找到匹配,跳出循环 + } + } + } + } + } + ; + var fileToShareKey = {}; + return Promise.resolve(Promise.all(Array.from(getUsedShares()).map(function (shareKey) { + try { + return Promise.resolve(_this.resolve(getPreBuildLibImportId(shareKey))).then(function (_this$resolve) { + var file = _this$resolve.id.split("?")[0]; + fileToShareKey[file] = shareKey; + }); + } catch (e) { + return Promise.reject(e); + } + }))).then(function () { + // 遍历打包生成的每个文件 + for (var _i3 = 0, _Object$entries2 = Object.entries(bundle); _i3 < _Object$entries2.length; _i3++) { + var _Object$entries2$_i = _Object$entries2[_i3], + _fileName = _Object$entries2$_i[0], + _fileData = _Object$entries2$_i[1]; + if (_fileData.type === 'chunk') { + // 遍历该文件的所有模块 + for (var _i4 = 0, _Object$keys2 = Object.keys(_fileData.modules); _i4 < _Object$keys2.length; _i4++) { + var _modulePath = _Object$keys2[_i4]; + var sharedKey = fileToShareKey[_modulePath]; + if (sharedKey) { + var _filesContainingModul2; + if (!filesContainingModules[sharedKey]) { + filesContainingModules[sharedKey] = { + sync: [], + async: [] + }; + } + filesContainingModules[sharedKey].sync.push(_fileName); + (_filesContainingModul2 = filesContainingModules[sharedKey].async).push.apply(_filesContainingModul2, _fileData.dynamicImports || []); + _findSynchronousImports(_fileName, filesContainingModules[sharedKey].sync); + break; // 如果找到匹配,跳出循环 + } + } + } + } + Object.keys(filesContainingModules).forEach(function (key) { + filesContainingModules[key].sync = Array.from(new Set(filesContainingModules[key].sync)); + filesContainingModules[key].async = Array.from(new Set(filesContainingModules[key].async)); + }); + _this.emitFile({ + type: 'asset', + fileName: mfManifestName, + source: generateMFManifest(filesContainingModules) + }); + }); + } catch (e) { + return Promise.reject(e); + } + } + }]; + function generateMFManifest(preloadMap) { + var options = getNormalizeModuleFederationOptions(); + var name = options.name; + var remoteEntry = { + name: remoteEntryFile, + path: '', + type: 'module' + }; + var remotes = []; + var usedRemotesMap = getUsedRemotesMap(); + Object.keys(usedRemotesMap).forEach(function (remoteKey) { + var usedModules = Array.from(usedRemotesMap[remoteKey]); + usedModules.forEach(function (moduleKey) { + remotes.push({ + federationContainerName: options.remotes[remoteKey].entry, + moduleName: moduleKey.replace(remoteKey, '').replace('/', ''), + alias: remoteKey, + entry: '*' + }); + }); + }); + // @ts-ignore + var shared = Array.from(getUsedShares()).map(function (shareKey) { + // assets(.css, .jpg, .svg等)其他资源, 不重要, 暂未处理 + if (!preloadMap[shareKey]) return; + var shareItem = getNormalizeShareItem(shareKey); + return { + id: name + ":" + shareKey, + name: shareKey, + version: shareItem.version, + requiredVersion: shareItem.shareConfig.requiredVersion, + assets: { + js: { + async: preloadMap[shareKey].async, + sync: preloadMap[shareKey].sync + }, + css: { + async: [], + sync: [] + } + } + }; + }).filter(function (item) { + return item; + }); + var exposes = Object.keys(options.exposes).map(function (key) { + // assets(.css, .jpg, .svg等)其他资源, 不重要, 暂未处理 + var formatKey = key.replace('./', ''); + var sourceFile = options.exposes[key]["import"]; + if (!preloadMap[sourceFile]) return; + return { + id: name + ':' + formatKey, + name: formatKey, + assets: { + js: { + async: preloadMap[sourceFile].async, + sync: preloadMap[sourceFile].sync + }, + css: { + sync: [], + async: [] + } + }, + path: key + }; + }).filter(function (item) { + return item; + }); // Filter out any null values + var result = { + id: name, + name: name, + metaData: { + name: name, + type: 'app', + buildInfo: { + buildVersion: '1.0.0', + buildName: name + }, + remoteEntry: remoteEntry, + ssrRemoteEntry: remoteEntry, + types: { + path: '', + name: '' + // "zip": "@mf-types.zip", + // "api": "@mf-types.d.ts" + }, + globalName: name, + pluginVersion: '0.2.5', + publicPath: 'auto' + }, + shared: shared, + remotes: remotes, + exposes: exposes + }; + return JSON.stringify(result); + } +}; + +var _resolve, + promise = new Promise(function (resolve, reject) { + _resolve = resolve; + }); +var parsePromise = promise; +var parseStartSet = new Set(); +var parseEndSet = new Set(); +function pluginModuleParseEnd (excludeFn) { + return [{ + name: "_", + apply: "serve", + config: function config() { + // No waiting in development mode + _resolve(1); + } + }, { + enforce: "pre", + name: "parseStart", + apply: "build", + load: function load(id) { + if (excludeFn(id)) { + return; + } + parseStartSet.add(id); + } + }, { + enforce: "post", + name: "parseEnd", + apply: "build", + moduleParsed: function moduleParsed(module) { + var id = module.id; + if (excludeFn(id)) { + return; + } + parseEndSet.add(id); + if (parseStartSet.size === parseEndSet.size) { + _resolve(1); + } + } + }]; +} + +var filter = createFilter(); +function pluginProxyRemoteEntry () { + return { + name: 'proxyRemoteEntry', + enforce: 'post', + resolveId: function resolveId(id) { + if (id === REMOTE_ENTRY_ID) { + return REMOTE_ENTRY_ID; + } + }, + load: function load(id) { + if (id === REMOTE_ENTRY_ID) { + return parsePromise.then(function (_) { + return generateRemoteEntry(getNormalizeModuleFederationOptions()); + }); + } + }, + transform: function transform(code, id) { + try { + if (!filter(id)) return Promise.resolve(); + if (id.includes(REMOTE_ENTRY_ID)) { + return Promise.resolve(parsePromise.then(function (_) { + return generateRemoteEntry(getNormalizeModuleFederationOptions()); + })); + } + return Promise.resolve(); + } catch (e) { + return Promise.reject(e); + } + } + }; +} + +createFilter(); +function pluginProxyRemotes (options) { + var remotes = options.remotes; + return { + name: "proxyRemotes", + config: function config(_config, _ref) { + var _command = _ref.command; + Object.keys(remotes).forEach(function (key) { + var remote = remotes[key]; + _config.resolve.alias.push({ + find: new RegExp("^(" + remote.name + "(/.*|$))"), + replacement: "$1", + customResolver: function customResolver(source) { + var remoteModule = getRemoteVirtualModule(source, _command); + addUsedRemote(remote.name, source); + return remoteModule.getPath(); + } + }); + }); + } + }; +} + +/** + * example: + * const store = new PromiseStore(); + * store.get("example").then((result) => { + * console.log("Result from example:", result); // 42 + * }); + * setTimeout(() => { + * store.set("example", Promise.resolve(42)); + * }, 2000); + */ +var PromiseStore = /*#__PURE__*/function () { + function PromiseStore() { + this.promiseMap = new Map(); + this.resolveMap = new Map(); + } + var _proto = PromiseStore.prototype; + _proto.set = function set(id, promise) { + if (this.resolveMap.has(id)) { + promise.then(this.resolveMap.get(id)); + this.resolveMap["delete"](id); + } + this.promiseMap.set(id, promise); + }; + _proto.get = function get(id) { + var _this = this; + if (this.promiseMap.has(id)) { + return this.promiseMap.get(id); + } + var pendingPromise = new Promise(function (resolve) { + _this.resolveMap.set(id, resolve); + }); + this.promiseMap.set(id, pendingPromise); + return pendingPromise; + }; + return PromiseStore; +}(); + +function proxySharedModule(options) { + var _options$shared = options.shared, + shared = _options$shared === void 0 ? {} : _options$shared; + return [{ + name: "generateLocalSharedImportMap", + enforce: "post", + load: function load(id) { + if (id.includes(getLocalSharedImportMapPath())) { + return parsePromise.then(function (_) { + return generateLocalSharedImportMap(); + }); + } + }, + transform: function transform(code, id) { + if (id.includes(getLocalSharedImportMapPath())) { + return parsePromise.then(function (_) { + return generateLocalSharedImportMap(); + }); + } + } + }, { + name: 'proxyPreBuildShared', + enforce: 'post', + config: function config(_config, _ref) { + var _config$resolve$alias, _config$resolve$alias2; + var command = _ref.command; + (_config$resolve$alias = _config.resolve.alias).push.apply(_config$resolve$alias, Object.keys(shared).map(function (key) { + var pattern = key.endsWith("/") ? "(^" + key.replace(/\/$/, "") + "(/.+)?$)" : "(^" + key + "$)"; + return { + // Intercept all shared requests and proxy them to loadShare + find: new RegExp(pattern), + replacement: "$1", + customResolver: function customResolver(source, importer) { + var loadSharePath = getLoadShareModulePath(source); + writeLoadShareModule(source, shared[key], command); + writePreBuildLibPath(source); + addUsedShares(source); + writeLocalSharedImportMap(); + return this.resolve(loadSharePath); + } + }; + })); + var savePrebuild = new PromiseStore(); + (_config$resolve$alias2 = _config.resolve.alias).push.apply(_config$resolve$alias2, Object.keys(shared).map(function (key) { + return command === "build" ? { + find: new RegExp("(.*" + PREBUILD_TAG + ".*)"), + replacement: function replacement($1) { + var pkgName = VirtualModule.findModule(PREBUILD_TAG, $1).name; + return pkgName; + } + } : { + find: new RegExp("(.*" + PREBUILD_TAG + ".*)"), + replacement: "$1", + customResolver: function customResolver(source, importer) { + try { + var _this = this; + var pkgName = VirtualModule.findModule(PREBUILD_TAG, source).name; + if (importer.includes(LOAD_SHARE_TAG)) { + // save pre-bunding module id + savePrebuild.set(pkgName, _this.resolve(pkgName).then(function (item) { + return item.id; + })); + } + // Fix localSharedImportMap import id + var _resolve = _this.resolve; + return Promise.resolve(savePrebuild.get(pkgName)).then(function (_savePrebuild$get) { + return Promise.resolve(_resolve.call(_this, _savePrebuild$get)); + }); + } catch (e) { + return Promise.reject(e); + } + } + }; + })); + } + }, { + name: "watchLocalSharedImportMap", + apply: "serve", + config: function config(_config2) { + _config2.optimizeDeps = defu(_config2.optimizeDeps, { + exclude: [getLocalSharedImportMapPath()] + }); + _config2.server = defu(_config2.server, { + watch: { + ignored: [] + } + }); + var watch = _config2.server.watch; + watch.ignored = [].concat(watch.ignored); + watch.ignored.push("!**" + getLocalSharedImportMapPath() + "**"); + } + }]; +} + +var aliasToArrayPlugin = { + name: 'alias-transform-plugin', + config: function config(_config, _ref) { + if (!_config.resolve) _config.resolve = {}; + if (!_config.resolve.alias) _config.resolve.alias = []; + var alias = _config.resolve.alias; + if (typeof alias === 'object' && !Array.isArray(alias)) { + _config.resolve.alias = Object.entries(alias).map(function (_ref2) { + var find = _ref2[0], + replacement = _ref2[1]; + return { + find: find, + replacement: replacement + }; + }); + } + } +}; + +var normalizeOptimizeDepsPlugin = { + name: 'normalizeOptimizeDeps', + config: function config(_config, _ref) { + var optimizeDeps = _config.optimizeDeps; + if (!optimizeDeps) { + _config.optimizeDeps = {}; + optimizeDeps = _config.optimizeDeps; + } + // todo: fix this workaround + optimizeDeps.force = true; + if (!optimizeDeps.include) optimizeDeps.include = []; + if (!optimizeDeps.needsInterop) optimizeDeps.needsInterop = []; + } +}; + +function federation(mfUserOptions) { + var options = normalizeModuleFederationOptions(mfUserOptions); + initVirtualModules(); + var name = options.name, + shared = options.shared, + filename = options.filename; + if (!name) throw new Error("name is required"); + return [aliasToArrayPlugin, normalizeOptimizeDepsPlugin].concat(addEntry({ + entryName: 'remoteEntry', + entryPath: REMOTE_ENTRY_ID, + fileName: filename + }), addEntry({ + entryName: 'hostInit', + entryPath: getHostAutoInitPath() + }), [pluginProxyRemoteEntry(), pluginProxyRemotes(options)], pluginModuleParseEnd(function (id) { + return id.includes(getHostAutoInitImportId()) || id.includes(REMOTE_ENTRY_ID) || id.includes(getLocalSharedImportMapPath()); + }), proxySharedModule({ + shared: shared + }), [PluginDevProxyModuleTopLevelAwait(), { + name: 'module-federation-vite', + enforce: 'post', + config: function config(_config, _ref) { + var _config$optimizeDeps; + _config.resolve.alias.push({ + find: '@module-federation/runtime', + replacement: require.resolve('@module-federation/runtime') + }); + (_config$optimizeDeps = _config.optimizeDeps) == null || (_config$optimizeDeps = _config$optimizeDeps.include) == null || _config$optimizeDeps.push('@module-federation/runtime'); + } + }], Manifest()); +} + +export { federation }; diff --git a/lib/index.modern.js b/lib/index.modern.js new file mode 100644 index 0000000..fea4934 --- /dev/null +++ b/lib/index.modern.js @@ -0,0 +1,1288 @@ +import * as fs from 'fs'; +import { mkdirSync, writeFileSync, existsSync, writeFile } from 'fs'; +import * as path from 'pathe'; +import path__default, { parse, join, dirname, resolve, relative } from 'pathe'; +import { createFilter } from '@rollup/pluginutils'; +import { walk } from 'estree-walker'; +import MagicString from 'magic-string'; +import { defu } from 'defu'; + +const addEntry = ({ + entryName, + entryPath, + fileName +}) => { + const devEntryPath = entryPath.startsWith("virtual:mf") ? "/@id/" + entryPath : entryPath; + let entryFiles = []; + let htmlFilePath; + let _command; + return [{ + name: 'add-entry', + apply: "serve", + config(config, { + command + }) { + _command = command; + }, + configureServer(server) { + var _server$httpServer; + (_server$httpServer = server.httpServer) == null || _server$httpServer.once == null || _server$httpServer.once('listening', () => { + const { + port + } = server.config.server; + fetch(path.join(`http://localhost:${port}`, `${devEntryPath}`)).catch(e => {}); + }); + server.middlewares.use((req, res, next) => { + if (!fileName) { + next(); + return; + } + if (req.url && req.url.startsWith(fileName.replace(/^\/?/, '/'))) { + req.url = devEntryPath; + } + next(); + }); + }, + transformIndexHtml(c) { + return c.replace('', ``); + } + }, { + name: "add-entry", + enforce: "post", + configResolved(config) { + const inputOptions = config.build.rollupOptions.input; + if (!inputOptions) { + htmlFilePath = path.resolve(config.root, 'index.html'); + } else if (typeof inputOptions === 'string') { + entryFiles = [inputOptions]; + htmlFilePath = path.resolve(config.root, inputOptions); + } else if (Array.isArray(inputOptions)) { + entryFiles = inputOptions; + htmlFilePath = path.resolve(config.root, inputOptions[0]); + } else if (typeof inputOptions === 'object') { + entryFiles = Object.values(inputOptions); + htmlFilePath = path.resolve(config.root, Object.values(inputOptions)[0]); + } + }, + buildStart() { + if (_command === "serve") return; + const hasHash = fileName == null || fileName.includes == null ? void 0 : fileName.includes("[hash"); + this.emitFile({ + name: entryName, + [hasHash ? "name" : "fileName"]: fileName, + type: 'chunk', + id: entryPath, + preserveSignature: 'strict' + }); + if (htmlFilePath) { + const htmlContent = fs.readFileSync(htmlFilePath, 'utf-8'); + const scriptRegex = /]*src=["']([^"']+)["'][^>]*>/gi; + let match; + while ((match = scriptRegex.exec(htmlContent)) !== null) { + entryFiles.push(match[1]); + } + } + }, + transform(code, id) { + if (entryFiles.some(file => id.endsWith(file))) { + const injection = ` + import ${JSON.stringify(entryPath)}; + `; + return injection + code; + } + } + }]; +}; + +/** + * Solve the problem that dev mode dependency prebunding does not support top-level await syntax + */ +function PluginDevProxyModuleTopLevelAwait() { + const filterFunction = createFilter(); + return { + name: "dev-proxy-module-top-level-await", + apply: "serve", + transform(code, id) { + if (!code.includes("/*mf top-level-await placeholder replacement mf*/")) { + return null; + } + if (!filterFunction(id)) return null; + let ast; + try { + ast = this.parse(code, { + allowReturnOutsideFunction: true + }); + } catch (e) { + throw new Error(`${id}: ${e}`); + } + const magicString = new MagicString(code); + walk(ast, { + enter(node) { + if (node.type === 'ExportNamedDeclaration' && node.specifiers) { + const exportSpecifiers = node.specifiers.map(specifier => specifier.exported.name); + const proxyStatements = exportSpecifiers.map(name => ` + const __mfproxy__await${name} = await ${name}(); + const __mfproxy__${name} = () => __mfproxy__await${name}; + `).join('\n'); + const exportStatements = exportSpecifiers.map(name => `__mfproxy__${name} as ${name}`).join(', '); + const start = node.start; + const end = node.end; + const replacement = `${proxyStatements}\nexport { ${exportStatements} };`; + magicString.overwrite(start, end, replacement); + } + if (node.type === 'ExportDefaultDeclaration') { + const declaration = node.declaration; + const start = node.start; + const end = node.end; + let proxyStatement; + let exportStatement = 'default'; + if (declaration.type === 'Identifier') { + // example: export default foo; + proxyStatement = ` + const __mfproxy__awaitdefault = await ${declaration.name}(); + const __mfproxy__default = __mfproxy__awaitdefault; + `; + } else if (declaration.type === 'CallExpression' || declaration.type === 'FunctionDeclaration') { + // example: export default someFunction(); + const declarationCode = code.slice(declaration.start, declaration.end); + proxyStatement = ` + const __mfproxy__awaitdefault = await (${declarationCode}); + const __mfproxy__default = __mfproxy__awaitdefault; + `; + } else { + // other + proxyStatement = ` + const __mfproxy__awaitdefault = await (${code.slice(declaration.start, declaration.end)}); + const __mfproxy__default = __mfproxy__awaitdefault; + `; + } + const replacement = `${proxyStatement}\nexport { __mfproxy__default as ${exportStatement} };`; + magicString.overwrite(start, end, replacement); + } + } + }); + return { + code: magicString.toString(), + map: magicString.generateMap({ + hires: true + }) + }; + } + }; +} + +function normalizeExposesItem(key, item) { + let importPath = ''; + if (typeof item === 'string') { + importPath = item; + } + if (typeof item === 'object') { + importPath = item.import; + } + return { + import: importPath + }; +} +function normalizeExposes(exposes) { + if (!exposes) return {}; + const res = {}; + Object.keys(exposes).forEach(key => { + res[key] = normalizeExposesItem(key, exposes[key]); + }); + return res; +} +function normalizeRemotes(remotes) { + if (!remotes) return {}; + const result = {}; + if (typeof remotes === 'object') { + Object.keys(remotes).forEach(key => { + result[key] = normalizeRemoteItem(key, remotes[key]); + }); + } + return result; +} +function normalizeRemoteItem(key, remote) { + if (typeof remote === 'string') { + const [entryGlobalName] = remote.split('@'); + const entry = remote.replace(entryGlobalName + '@', ''); + return { + type: 'var', + name: key, + entry, + entryGlobalName, + shareScope: 'default' + }; + } + return Object.assign({ + type: 'var', + name: key, + shareScope: 'default', + entryGlobalName: key + }, remote); +} +function removePathFromNpmPackage(packageString) { + // 匹配npm包名的正则表达式,忽略路径部分 + const regex = /^(?:@[^/]+\/)?[^/]+/; + // 使用正则表达式匹配并提取包名 + const match = packageString.match(regex); + // 返回匹配到的包名,如果没有匹配到则返回原字符串 + return match ? match[0] : packageString; +} +function normalizeShareItem(key, shareItem) { + let version; + try { + version = require(path.join(removePathFromNpmPackage(key), 'package.json')).version; + } catch (e) { + console.log(e); + } + if (typeof shareItem === 'string') { + return { + name: shareItem, + version, + scope: 'default', + from: '', + shareConfig: { + singleton: false, + requiredVersion: `^${version}` || '*' + } + }; + } + return { + name: key, + from: '', + version: shareItem.version || version, + scope: shareItem.shareScope || 'default', + shareConfig: { + singleton: shareItem.singleton || false, + requiredVersion: shareItem.requiredVersion || `^${version}` || '*', + strictVersion: !!shareItem.strictVersion + } + }; +} +function normalizeShared(shared) { + if (!shared) return {}; + const result = {}; + if (Array.isArray(shared)) { + shared.forEach(key => { + result[key] = normalizeShareItem(key, key); + }); + return result; + } + if (typeof shared === 'object') { + Object.keys(shared).forEach(key => { + result[key] = normalizeShareItem(key, shared[key]); + }); + } + return result; +} +function normalizeLibrary(library) { + if (!library) return undefined; + return library; +} +function normalizeManifest(manifest = false) { + if (typeof manifest === "boolean") { + return manifest; + } + return Object.assign({ + filePath: "", + disableAssetsAnalyze: false, + fileName: "mf-manifest.json" + }, manifest); +} +let config; +function getNormalizeModuleFederationOptions() { + return config; +} +function getNormalizeShareItem(key) { + const options = getNormalizeModuleFederationOptions(); + const shareItem = options.shared[removePathFromNpmPackage(key)] || options.shared[removePathFromNpmPackage(key) + "/"]; + return shareItem; +} +function normalizeModuleFederationOptions(options) { + return config = { + exposes: normalizeExposes(options.exposes), + filename: options.filename || 'remoteEntry-[hash]', + library: normalizeLibrary(options.library), + name: options.name, + // remoteType: options.remoteType, + remotes: normalizeRemotes(options.remotes), + runtime: options.runtime, + shareScope: options.shareScope || 'default', + shared: normalizeShared(options.shared), + runtimePlugins: options.runtimePlugins || [], + getPublicPath: options.getPublicPath, + implementation: options.implementation, + manifest: normalizeManifest(options.manifest), + dev: options.dev, + dts: options.dts + }; +} + +/** + * Escaping rules: + * Convert using the format __${mapping}__, where _ and $ are not allowed in npm package names but can be used in variable names. + * @ => 1 + * / => 2 + * - => 3 + * . => 4 + */ +/** + * Encodes a package name into a valid file name. + * @param {string} name - The package name, e.g., "@scope/xx-xx.xx". + * @returns {string} - The encoded file name. + */ +function packageNameEncode(name) { + if (typeof name !== "string") throw new Error("A string package name is required"); + return name.replace(/@/g, "_mf_0_").replace(/\//g, "_mf_1_").replace(/-/g, "_mf_2_").replace(/\./g, "_mf_3_"); +} +/** + * Decodes an encoded file name back to the original package name. + * @param {string} encoded - The encoded file name, e.g., "_mf_0_scope_mf_1_xx_mf_2_xx_mf_3_xx". + * @returns {string} - The decoded package name. + */ +function packageNameDecode(encoded) { + if (typeof encoded !== "string") throw new Error("A string encoded file name is required"); + return encoded.replace(/_mf_0_/g, "@").replace(/_mf_1_/g, "/").replace(/_mf_2_/g, "-").replace(/_mf_3_/g, "."); +} + +/** + * https://github.com/module-federation/vite/issues/68 + */ +function getLocalSharedImportMapPath_windows() { + const { + name + } = getNormalizeModuleFederationOptions(); + return path__default.resolve(".__mf__win", packageNameEncode(name), "localSharedImportMap"); +} +function writeLocalSharedImportMap_windows(content) { + const localSharedImportMapId = getLocalSharedImportMapPath_windows(); + createFile(localSharedImportMapId + ".js", "\n// Windows temporarily needs this file, https://github.com/module-federation/vite/issues/68\n" + content); +} +function createFile(filePath, content) { + const dir = path__default.dirname(filePath); + mkdirSync(dir, { + recursive: true + }); + writeFileSync(filePath, content); +} + +const nodeModulesDir = function findNodeModulesDir(startDir = process.cwd()) { + let currentDir = startDir; + while (currentDir !== parse(currentDir).root) { + const nodeModulesPath = join(currentDir, 'node_modules'); + if (existsSync(nodeModulesPath)) { + return nodeModulesPath; + } + currentDir = dirname(currentDir); + } + return ""; +}(); +const virtualPackageName = "__mf__virtual"; +if (!existsSync(resolve(nodeModulesDir, virtualPackageName))) { + mkdirSync(resolve(nodeModulesDir, virtualPackageName)); +} +writeFileSync(resolve(nodeModulesDir, virtualPackageName, "empty.js"), ""); +writeFileSync(resolve(nodeModulesDir, virtualPackageName, "package.json"), JSON.stringify({ + name: virtualPackageName, + main: "empty.js" +})); +const patternMap = {}; +const cacheMap = {}; +/** + * Physically generate files as virtual modules under node_modules/__mf__virtual/* + */ +class VirtualModule { + static findModule(tag, str = "") { + if (!patternMap[tag]) patternMap[tag] = new RegExp(`(.*${packageNameEncode(tag)}(.+?)${packageNameEncode(tag)}.*)`); + const moduleName = (str.match(patternMap[tag]) || [])[2]; + if (moduleName) return cacheMap[tag][packageNameDecode(moduleName)]; + return undefined; + } + constructor(name, tag = '__mf_v__', suffix = "") { + var _name$split$slice$pop; + this.name = void 0; + this.tag = void 0; + this.suffix = void 0; + this.inited = false; + this.name = name; + this.tag = tag; + this.suffix = suffix || ((_name$split$slice$pop = name.split(".").slice(1).pop()) == null ? void 0 : _name$split$slice$pop.replace(/(.)/, ".$1")) || ".js"; + if (!cacheMap[this.tag]) cacheMap[this.tag] = {}; + cacheMap[this.tag][this.name] = this; + } + getPath() { + return resolve(nodeModulesDir, this.getImportId()); + } + getImportId() { + const { + name: mfName + } = getNormalizeModuleFederationOptions(); + return `${virtualPackageName}/${packageNameEncode(`${mfName}${this.tag}${this.name}${this.tag}`)}${this.suffix}`; + } + writeSync(code, force) { + if (!force && this.inited) return; + if (!this.inited) { + this.inited = true; + } + writeFileSync(this.getPath(), code); + } + write(code) { + writeFile(this.getPath(), code, function () {}); + } +} + +const virtualRuntimeInitStatus = new VirtualModule("runtimeInit"); +function writeRuntimeInitStatus() { + virtualRuntimeInitStatus.writeSync(` + let initResolve, initReject + const initPromise = new Promise((re, rj) => { + initResolve = re + initReject = rj + }) + export { + initPromise, + initResolve, + initReject + } + `); +} + +const cacheRemoteMap = {}; +const LOAD_REMOTE_TAG = '__loadRemote__'; +function getRemoteVirtualModule(remote, command) { + if (!cacheRemoteMap[remote]) { + cacheRemoteMap[remote] = new VirtualModule(remote, LOAD_REMOTE_TAG, ".js"); + cacheRemoteMap[remote].writeSync(generateRemotes(remote, command)); + } + const virtual = cacheRemoteMap[remote]; + return virtual; +} +const usedRemotesMap = { + // remote1: {remote1/App, remote1, remote1/Button} +}; +function addUsedRemote(remoteKey, remoteModule) { + if (!usedRemotesMap[remoteKey]) usedRemotesMap[remoteKey] = new Set(); + usedRemotesMap[remoteKey].add(remoteModule); +} +function getUsedRemotesMap() { + return usedRemotesMap; +} +function generateRemotes(id, command) { + return ` + const {loadRemote} = require("@module-federation/runtime") + const {initPromise} = require("${virtualRuntimeInitStatus.getImportId()}") + const res = initPromise.then(_ => loadRemote(${JSON.stringify(id)})) + const exportModule = ${command !== "build" ? "/*mf top-level-await placeholder replacement mf*/" : "await "}initPromise.then(_ => res) + module.exports = exportModule + `; +} + +/** + * Even the resolveId hook cannot interfere with vite pre-build, + * and adding query parameter virtual modules will also fail. + * You can only proxy to the real file through alias + */ +// *** __prebuild__ +const preBuildCacheMap = {}; +const PREBUILD_TAG = "__prebuild__"; +function writePreBuildLibPath(pkg) { + if (!preBuildCacheMap[pkg]) preBuildCacheMap[pkg] = new VirtualModule(pkg, PREBUILD_TAG); + preBuildCacheMap[pkg].writeSync(""); +} +function getPreBuildLibImportId(pkg) { + if (!preBuildCacheMap[pkg]) preBuildCacheMap[pkg] = new VirtualModule(pkg, PREBUILD_TAG); + const importId = preBuildCacheMap[pkg].getImportId(); + return importId; +} +// *** __loadShare__ +const LOAD_SHARE_TAG = "__loadShare__"; +const loadShareCacheMap = {}; +function getLoadShareModulePath(pkg) { + if (!loadShareCacheMap[pkg]) loadShareCacheMap[pkg] = new VirtualModule(pkg, LOAD_SHARE_TAG, ".js"); + const filepath = loadShareCacheMap[pkg].getPath(); + return filepath; +} +function writeLoadShareModule(pkg, shareItem, command) { + loadShareCacheMap[pkg].writeSync(` + + ;() => import(${JSON.stringify(getPreBuildLibImportId(pkg))}).catch(() => {}); + // dev uses dynamic import to separate chunks + ${command !== "build" ? `;() => import(${JSON.stringify(pkg)}).catch(() => {});` : ''} + const {loadShare} = require("@module-federation/runtime") + const {initPromise} = require("${virtualRuntimeInitStatus.getImportId()}") + const res = initPromise.then(_ => loadShare(${JSON.stringify(pkg)}, { + customShareInfo: {shareConfig:{ + singleton: ${shareItem.shareConfig.singleton}, + strictVersion: ${shareItem.shareConfig.strictVersion}, + requiredVersion: ${JSON.stringify(shareItem.shareConfig.requiredVersion)} + }}})) + const exportModule = ${command !== "build" ? "/*mf top-level-await placeholder replacement mf*/" : "await "}res.then(factory => factory()) + module.exports = exportModule + `); +} + +let usedShares = new Set(); +function getUsedShares() { + return usedShares; +} +function addUsedShares(pkg) { + usedShares.add(pkg); +} +// *** Expose locally provided shared modules here +const localSharedImportMapModule = new VirtualModule("localSharedImportMap"); +function getLocalSharedImportMapPath() { + if (process.platform === "win32") { + return getLocalSharedImportMapPath_windows(); + } + return localSharedImportMapModule.getPath(); +} +let prevSharedCount; +function writeLocalSharedImportMap() { + const sharedCount = getUsedShares().size; + if (prevSharedCount !== sharedCount) { + prevSharedCount = sharedCount; + if (process.platform === "win32") { + writeLocalSharedImportMap_windows(generateLocalSharedImportMap()); + } else { + localSharedImportMapModule.writeSync(generateLocalSharedImportMap(), true); + } + } +} +function generateLocalSharedImportMap() { + const options = getNormalizeModuleFederationOptions(); + return ` + const importMap = { + ${Array.from(getUsedShares()).map(pkg => ` + ${JSON.stringify(pkg)}: async () => { + let pkg = await import("${getPreBuildLibImportId(pkg)}") + return pkg + } + `).join(",")} + } + const usedShared = { + ${Array.from(getUsedShares()).map(key => { + const shareItem = getNormalizeShareItem(key); + return ` + ${JSON.stringify(key)}: { + name: ${JSON.stringify(key)}, + version: ${JSON.stringify(shareItem.version)}, + scope: [${JSON.stringify(shareItem.scope)}], + loaded: false, + from: ${JSON.stringify(options.name)}, + async get () { + usedShared[${JSON.stringify(key)}].loaded = true + const {${JSON.stringify(key)}: pkgDynamicImport} = importMap + const res = await pkgDynamicImport() + const exportModule = {...res} + // All npm packages pre-built by vite will be converted to esm + Object.defineProperty(exportModule, "__esModule", { + value: true, + enumerable: false + }) + return function () { + return exportModule + } + }, + shareConfig: { + singleton: ${shareItem.shareConfig.singleton}, + requiredVersion: ${JSON.stringify(shareItem.shareConfig.requiredVersion)} + } + } + `; + }).join(',')} + } + const usedRemotes = [${Object.keys(getUsedRemotesMap()).map(key => { + const remote = options.remotes[key]; + return ` + { + entryGlobalName: ${JSON.stringify(remote.entryGlobalName)}, + name: ${JSON.stringify(remote.name)}, + type: ${JSON.stringify(remote.type)}, + entry: ${JSON.stringify(remote.entry)}, + } + `; + }).join(',')} + ] + export { + usedShared, + usedRemotes + } + `; +} +const REMOTE_ENTRY_ID = 'virtual:mf-REMOTE_ENTRY_ID'; +function generateRemoteEntry(options) { + const pluginImportNames = options.runtimePlugins.map((p, i) => [`$runtimePlugin_${i}`, `import $runtimePlugin_${i} from "${p}";`]); + return ` + import {init as runtimeInit, loadRemote} from "@module-federation/runtime"; + ${pluginImportNames.map(item => item[1]).join('\n')} + + const exposesMap = { + ${Object.keys(options.exposes).map(key => { + return ` + ${JSON.stringify(key)}: async () => { + const importModule = await import(${JSON.stringify(options.exposes[key].import)}) + const exportModule = {} + Object.assign(exportModule, importModule) + Object.defineProperty(exportModule, "__esModule", { + value: true, + enumerable: false + }) + return exportModule + } + `; + }).join(',')} + } + import {usedShared, usedRemotes} from "${getLocalSharedImportMapPath()}" + import { + initResolve + } from "${virtualRuntimeInitStatus.getImportId()}" + async function init(shared = {}) { + const initRes = runtimeInit({ + name: ${JSON.stringify(options.name)}, + remotes: usedRemotes, + shared: usedShared, + plugins: [${pluginImportNames.map(item => `${item[0]}()`).join(', ')}] + }); + initRes.initShareScopeMap('${options.shareScope}', shared); + initResolve(initRes) + return initRes + } + + function getExposes(moduleName) { + if (!(moduleName in exposesMap)) throw new Error(\`Module \${moduleName} does not exist in container.\`) + return (exposesMap[moduleName])().then(res => () => res) + } + export { + init, + getExposes as get + } + `; +} +/** + * Inject entry file, automatically init when used as host, + * and will not inject remoteEntry + */ +const hostAutoInitModule = new VirtualModule("hostAutoInit"); +function writeHostAutoInit() { + hostAutoInitModule.writeSync(` + import {init} from "${REMOTE_ENTRY_ID}" + init() + `); +} +function getHostAutoInitImportId() { + return hostAutoInitModule.getImportId(); +} +function getHostAutoInitPath() { + return hostAutoInitModule.getPath(); +} + +function initVirtualModules() { + writeLocalSharedImportMap(); + writeHostAutoInit(); + writeRuntimeInitStatus(); +} + +const Manifest = () => { + const mfOptions = getNormalizeModuleFederationOptions(); + const { + name, + filename, + manifest: manifestOptions + } = mfOptions; + let mfManifestName = ""; + if (manifestOptions === true) { + mfManifestName = "mf-manifest.json"; + } + if (typeof manifestOptions !== "boolean") { + mfManifestName = join((manifestOptions == null ? void 0 : manifestOptions.filePath) || "", (manifestOptions == null ? void 0 : manifestOptions.fileName) || ""); + } + let extensions; + let root; + let remoteEntryFile; + return [{ + name: 'moddule-federation-manifest', + apply: 'serve', + configureServer(server) { + server.middlewares.use((req, res, next) => { + if (!mfManifestName) { + next(); + return; + } + if (req.url === mfManifestName.replace(/^\/?/, "/")) { + res.setHeader('Content-Type', 'application/json'); + res.setHeader('Access-Control-Allow-Origin', '*'); + res.end(JSON.stringify({ + id: name, + name: name, + metaData: { + name: name, + type: 'app', + buildInfo: { + buildVersion: '1.0.0', + buildName: name + }, + remoteEntry: { + name: filename, + path: '', + type: 'module' + }, + ssrRemoteEntry: { + name: filename, + path: '', + type: 'module' + }, + types: { + path: '', + name: '' + }, + globalName: name, + pluginVersion: '0.2.5', + publicPath: 'auto' + }, + shared: Array.from(getUsedShares()).map(shareKey => { + const shareItem = getNormalizeShareItem(shareKey); + return { + id: `${name}:${shareKey}`, + name: shareKey, + version: shareItem.version, + requiredVersion: shareItem.shareConfig.requiredVersion, + assets: { + js: { + async: [], + sync: [] + }, + css: { + async: [], + sync: [] + } + } + }; + }), + remotes: function () { + const remotes = []; + const usedRemotesMap = getUsedRemotesMap(); + Object.keys(usedRemotesMap).forEach(remoteKey => { + const usedModules = Array.from(usedRemotesMap[remoteKey]); + usedModules.forEach(moduleKey => { + remotes.push({ + federationContainerName: mfOptions.remotes[remoteKey].entry, + moduleName: moduleKey.replace(remoteKey, '').replace('/', ''), + alias: remoteKey, + entry: '*' + }); + }); + }); + return remotes; + }(), + exposes: Object.keys(mfOptions.exposes).map(key => { + const formatKey = key.replace('./', ''); + return { + id: name + ':' + formatKey, + name: formatKey, + assets: { + js: { + async: [], + sync: [] + }, + css: { + sync: [], + async: [] + } + }, + path: key + }; + }) + })); + } else { + next(); + } + }); + } + }, { + name: 'moddule-federation-manifest', + enforce: 'post', + config(config) { + if (!config.build) config.build = {}; + if (!config.build.manifest) config.build.manifest = config.build.manifest || !!manifestOptions; + }, + configResolved(config) { + root = config.root; + extensions = config.resolve.extensions || ['.mjs', '.js', '.mts', '.ts', '.jsx', '.tsx', '.json']; + }, + async generateBundle(options, bundle) { + if (!mfManifestName) return; + const exposesModules = Object.keys(mfOptions.exposes).map(item => mfOptions.exposes[item].import); // 获取你提供的 moduleIds + const filesContainingModules = {}; + // 帮助函数:检查模块路径是否匹配 + const isModuleMatched = (relativeModulePath, preloadModule) => { + // 先尝试直接匹配 + if (relativeModulePath === preloadModule) return true; + // 如果 preloadModule 没有后缀,尝试添加可能的后缀进行匹配 + for (const ext of extensions) { + if (relativeModulePath === `${preloadModule}${ext}`) { + return true; + } + } + return false; + }; + // 遍历打包生成的每个文件 + for (const [fileName, fileData] of Object.entries(bundle)) { + if (mfOptions.filename.replace(/[\[\]]/g, "_") === fileData.name) { + remoteEntryFile = fileData.fileName; + } + if (fileData.type === 'chunk') { + // 遍历该文件的所有模块 + for (const modulePath of Object.keys(fileData.modules)) { + // 将绝对路径转换为相对于 Vite root 的相对路径 + const relativeModulePath = relative(root, modulePath); + // 检查模块是否在 preloadModules 列表中 + for (const preloadModule of exposesModules) { + const formatPreloadModule = preloadModule.replace("./", ""); + if (isModuleMatched(relativeModulePath, formatPreloadModule)) { + if (!filesContainingModules[preloadModule]) { + filesContainingModules[preloadModule] = { + sync: [], + async: [] + }; + } + console.log(Object.keys(fileData.modules)); + filesContainingModules[preloadModule].sync.push(fileName); + filesContainingModules[preloadModule].async.push(...(fileData.dynamicImports || [])); + findSynchronousImports(fileName, filesContainingModules[preloadModule].sync); + break; // 如果找到匹配,跳出循环 + } + } + } + } + } + // 递归查找模块的同步导入文件 + function findSynchronousImports(fileName, array) { + const fileData = bundle[fileName]; + if (fileData && fileData.type === 'chunk') { + array.push(fileName); // 将当前文件加入预加载列表 + // 遍历该文件的同步导入文件 + fileData.imports.forEach(importedFile => { + if (array.indexOf(importedFile) === -1) { + findSynchronousImports(importedFile, array); // 递归查找同步导入的文件 + } + }); + } + } + const fileToShareKey = {}; + await Promise.all(Array.from(getUsedShares()).map(async shareKey => { + const file = (await this.resolve(getPreBuildLibImportId(shareKey))).id.split("?")[0]; + fileToShareKey[file] = shareKey; + })); + // 遍历打包生成的每个文件 + for (const [fileName, fileData] of Object.entries(bundle)) { + if (fileData.type === 'chunk') { + // 遍历该文件的所有模块 + for (const modulePath of Object.keys(fileData.modules)) { + const sharedKey = fileToShareKey[modulePath]; + if (sharedKey) { + if (!filesContainingModules[sharedKey]) { + filesContainingModules[sharedKey] = { + sync: [], + async: [] + }; + } + filesContainingModules[sharedKey].sync.push(fileName); + filesContainingModules[sharedKey].async.push(...(fileData.dynamicImports || [])); + findSynchronousImports(fileName, filesContainingModules[sharedKey].sync); + break; // 如果找到匹配,跳出循环 + } + } + } + } + Object.keys(filesContainingModules).forEach(key => { + filesContainingModules[key].sync = Array.from(new Set(filesContainingModules[key].sync)); + filesContainingModules[key].async = Array.from(new Set(filesContainingModules[key].async)); + }); + this.emitFile({ + type: 'asset', + fileName: mfManifestName, + source: generateMFManifest(filesContainingModules) + }); + } + }]; + function generateMFManifest(preloadMap) { + const options = getNormalizeModuleFederationOptions(); + const { + name + } = options; + const remoteEntry = { + name: remoteEntryFile, + path: '', + type: 'module' + }; + const remotes = []; + const usedRemotesMap = getUsedRemotesMap(); + Object.keys(usedRemotesMap).forEach(remoteKey => { + const usedModules = Array.from(usedRemotesMap[remoteKey]); + usedModules.forEach(moduleKey => { + remotes.push({ + federationContainerName: options.remotes[remoteKey].entry, + moduleName: moduleKey.replace(remoteKey, '').replace('/', ''), + alias: remoteKey, + entry: '*' + }); + }); + }); + // @ts-ignore + const shared = Array.from(getUsedShares()).map(shareKey => { + // assets(.css, .jpg, .svg等)其他资源, 不重要, 暂未处理 + if (!preloadMap[shareKey]) return; + const shareItem = getNormalizeShareItem(shareKey); + return { + id: `${name}:${shareKey}`, + name: shareKey, + version: shareItem.version, + requiredVersion: shareItem.shareConfig.requiredVersion, + assets: { + js: { + async: preloadMap[shareKey].async, + sync: preloadMap[shareKey].sync + }, + css: { + async: [], + sync: [] + } + } + }; + }).filter(item => item); + const exposes = Object.keys(options.exposes).map(key => { + // assets(.css, .jpg, .svg等)其他资源, 不重要, 暂未处理 + const formatKey = key.replace('./', ''); + const sourceFile = options.exposes[key].import; + if (!preloadMap[sourceFile]) return; + return { + id: name + ':' + formatKey, + name: formatKey, + assets: { + js: { + async: preloadMap[sourceFile].async, + sync: preloadMap[sourceFile].sync + }, + css: { + sync: [], + async: [] + } + }, + path: key + }; + }).filter(item => item); // Filter out any null values + const result = { + id: name, + name: name, + metaData: { + name: name, + type: 'app', + buildInfo: { + buildVersion: '1.0.0', + buildName: name + }, + remoteEntry, + ssrRemoteEntry: remoteEntry, + types: { + path: '', + name: '' + // "zip": "@mf-types.zip", + // "api": "@mf-types.d.ts" + }, + globalName: name, + pluginVersion: '0.2.5', + publicPath: 'auto' + }, + shared, + remotes, + exposes + }; + return JSON.stringify(result); + } +}; + +let _resolve, + promise = new Promise((resolve, reject) => { + _resolve = resolve; + }); +let parsePromise = promise; +const parseStartSet = new Set(); +const parseEndSet = new Set(); +function pluginModuleParseEnd (excludeFn) { + return [{ + name: "_", + apply: "serve", + config() { + // No waiting in development mode + _resolve(1); + } + }, { + enforce: "pre", + name: "parseStart", + apply: "build", + load(id) { + if (excludeFn(id)) { + return; + } + parseStartSet.add(id); + } + }, { + enforce: "post", + name: "parseEnd", + apply: "build", + moduleParsed(module) { + const id = module.id; + if (excludeFn(id)) { + return; + } + parseEndSet.add(id); + if (parseStartSet.size === parseEndSet.size) { + _resolve(1); + } + } + }]; +} + +const filter = createFilter(); +function pluginProxyRemoteEntry () { + return { + name: 'proxyRemoteEntry', + enforce: 'post', + resolveId(id) { + if (id === REMOTE_ENTRY_ID) { + return REMOTE_ENTRY_ID; + } + }, + load(id) { + if (id === REMOTE_ENTRY_ID) { + return parsePromise.then(_ => generateRemoteEntry(getNormalizeModuleFederationOptions())); + } + }, + async transform(code, id) { + if (!filter(id)) return; + if (id.includes(REMOTE_ENTRY_ID)) { + return parsePromise.then(_ => generateRemoteEntry(getNormalizeModuleFederationOptions())); + } + } + }; +} + +createFilter(); +function pluginProxyRemotes (options) { + const { + remotes + } = options; + return { + name: "proxyRemotes", + config(config, { + command: _command + }) { + Object.keys(remotes).forEach(key => { + const remote = remotes[key]; + config.resolve.alias.push({ + find: new RegExp(`^(${remote.name}(\/.*|$))`), + replacement: "$1", + customResolver(source) { + const remoteModule = getRemoteVirtualModule(source, _command); + addUsedRemote(remote.name, source); + return remoteModule.getPath(); + } + }); + }); + } + }; +} + +/** + * example: + * const store = new PromiseStore(); + * store.get("example").then((result) => { + * console.log("Result from example:", result); // 42 + * }); + * setTimeout(() => { + * store.set("example", Promise.resolve(42)); + * }, 2000); + */ +class PromiseStore { + constructor() { + this.promiseMap = new Map(); + this.resolveMap = new Map(); + } + set(id, promise) { + if (this.resolveMap.has(id)) { + promise.then(this.resolveMap.get(id)); + this.resolveMap.delete(id); + } + this.promiseMap.set(id, promise); + } + get(id) { + if (this.promiseMap.has(id)) { + return this.promiseMap.get(id); + } + const pendingPromise = new Promise(resolve => { + this.resolveMap.set(id, resolve); + }); + this.promiseMap.set(id, pendingPromise); + return pendingPromise; + } +} + +function proxySharedModule(options) { + let { + shared = {}, + include, + exclude + } = options; + return [{ + name: "generateLocalSharedImportMap", + enforce: "post", + load(id) { + if (id.includes(getLocalSharedImportMapPath())) { + return parsePromise.then(_ => generateLocalSharedImportMap()); + } + }, + transform(code, id) { + if (id.includes(getLocalSharedImportMapPath())) { + return parsePromise.then(_ => generateLocalSharedImportMap()); + } + } + }, { + name: 'proxyPreBuildShared', + enforce: 'post', + config(config, { + command + }) { + config.resolve.alias.push(...Object.keys(shared).map(key => { + const pattern = key.endsWith("/") ? `(^${key.replace(/\/$/, "")}(\/.+)?$)` : `(^${key}$)`; + return { + // Intercept all shared requests and proxy them to loadShare + find: new RegExp(pattern), + replacement: "$1", + customResolver(source, importer) { + const loadSharePath = getLoadShareModulePath(source); + writeLoadShareModule(source, shared[key], command); + writePreBuildLibPath(source); + addUsedShares(source); + writeLocalSharedImportMap(); + return this.resolve(loadSharePath); + } + }; + })); + const savePrebuild = new PromiseStore(); + config.resolve.alias.push(...Object.keys(shared).map(key => { + return command === "build" ? { + find: new RegExp(`(.*${PREBUILD_TAG}.*)`), + replacement: function ($1) { + const pkgName = VirtualModule.findModule(PREBUILD_TAG, $1).name; + return pkgName; + } + } : { + find: new RegExp(`(.*${PREBUILD_TAG}.*)`), + replacement: "$1", + async customResolver(source, importer) { + const pkgName = VirtualModule.findModule(PREBUILD_TAG, source).name; + if (importer.includes(LOAD_SHARE_TAG)) { + // save pre-bunding module id + savePrebuild.set(pkgName, this.resolve(pkgName).then(item => item.id)); + } + // Fix localSharedImportMap import id + return await this.resolve(await savePrebuild.get(pkgName)); + } + }; + })); + } + }, { + name: "watchLocalSharedImportMap", + apply: "serve", + config(config) { + config.optimizeDeps = defu(config.optimizeDeps, { + exclude: [getLocalSharedImportMapPath()] + }); + config.server = defu(config.server, { + watch: { + ignored: [] + } + }); + const watch = config.server.watch; + watch.ignored = [].concat(watch.ignored); + watch.ignored.push(`!**${getLocalSharedImportMapPath()}**`); + } + }]; +} + +var aliasToArrayPlugin = { + name: 'alias-transform-plugin', + config: (config, { + command + }) => { + if (!config.resolve) config.resolve = {}; + if (!config.resolve.alias) config.resolve.alias = []; + const { + alias + } = config.resolve; + if (typeof alias === 'object' && !Array.isArray(alias)) { + config.resolve.alias = Object.entries(alias).map(([find, replacement]) => ({ + find, + replacement + })); + } + } +}; + +var normalizeOptimizeDepsPlugin = { + name: 'normalizeOptimizeDeps', + config: (config, { + command + }) => { + let { + optimizeDeps + } = config; + if (!optimizeDeps) { + config.optimizeDeps = {}; + optimizeDeps = config.optimizeDeps; + } + // todo: fix this workaround + optimizeDeps.force = true; + if (!optimizeDeps.include) optimizeDeps.include = []; + if (!optimizeDeps.needsInterop) optimizeDeps.needsInterop = []; + } +}; + +function federation(mfUserOptions) { + const options = normalizeModuleFederationOptions(mfUserOptions); + initVirtualModules(); + const { + name, + remotes, + shared, + filename + } = options; + if (!name) throw new Error("name is required"); + return [aliasToArrayPlugin, normalizeOptimizeDepsPlugin, ...addEntry({ + entryName: 'remoteEntry', + entryPath: REMOTE_ENTRY_ID, + fileName: filename + }), ...addEntry({ + entryName: 'hostInit', + entryPath: getHostAutoInitPath() + }), pluginProxyRemoteEntry(), pluginProxyRemotes(options), ...pluginModuleParseEnd(id => { + return id.includes(getHostAutoInitImportId()) || id.includes(REMOTE_ENTRY_ID) || id.includes(getLocalSharedImportMapPath()); + }), ...proxySharedModule({ + shared + }), PluginDevProxyModuleTopLevelAwait(), { + name: 'module-federation-vite', + enforce: 'post', + config(config, { + command: _command + }) { + var _config$optimizeDeps; + config.resolve.alias.push({ + find: '@module-federation/runtime', + replacement: require.resolve('@module-federation/runtime') + }); + (_config$optimizeDeps = config.optimizeDeps) == null || (_config$optimizeDeps = _config$optimizeDeps.include) == null || _config$optimizeDeps.push('@module-federation/runtime'); + } + }, ...Manifest()]; +} + +export { federation }; diff --git a/lib/index.umd.js b/lib/index.umd.js new file mode 100644 index 0000000..9b336cf --- /dev/null +++ b/lib/index.umd.js @@ -0,0 +1,1262 @@ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('fs'), require('pathe'), require('@rollup/pluginutils'), require('estree-walker'), require('magic-string'), require('defu')) : + typeof define === 'function' && define.amd ? define(['exports', 'fs', 'pathe', '@rollup/pluginutils', 'estree-walker', 'magic-string', 'defu'], factory) : + (global = global || self, factory(global.vite = {}, global.fs, global.pathe, global.pluginutils, global.estreeWalker, global.magicString, global.defu)); +})(this, (function (exports, fs, path, pluginutils, estreeWalker, MagicString, defu) { + function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } + + function _interopNamespace(e) { + if (e && e.__esModule) return e; + var n = Object.create(null); + if (e) { + Object.keys(e).forEach(function (k) { + if (k !== 'default') { + var d = Object.getOwnPropertyDescriptor(e, k); + Object.defineProperty(n, k, d.get ? d : { + enumerable: true, + get: function () { return e[k]; } + }); + } + }); + } + n["default"] = e; + return n; + } + + var fs__namespace = /*#__PURE__*/_interopNamespace(fs); + var path__namespace = /*#__PURE__*/_interopNamespace(path); + var path__default = /*#__PURE__*/_interopDefaultLegacy(path); + var MagicString__default = /*#__PURE__*/_interopDefaultLegacy(MagicString); + + var addEntry = function addEntry(_ref) { + var entryName = _ref.entryName, + entryPath = _ref.entryPath, + fileName = _ref.fileName; + var devEntryPath = entryPath.startsWith("virtual:mf") ? "/@id/" + entryPath : entryPath; + var entryFiles = []; + var htmlFilePath; + var _command; + return [{ + name: 'add-entry', + apply: "serve", + config: function config(_config, _ref2) { + var command = _ref2.command; + _command = command; + }, + configureServer: function configureServer(server) { + var _server$httpServer; + (_server$httpServer = server.httpServer) == null || _server$httpServer.once == null || _server$httpServer.once('listening', function () { + var port = server.config.server.port; + fetch(path__namespace.join("http://localhost:" + port, "" + devEntryPath))["catch"](function (e) {}); + }); + server.middlewares.use(function (req, res, next) { + if (!fileName) { + next(); + return; + } + if (req.url && req.url.startsWith(fileName.replace(/^\/?/, '/'))) { + req.url = devEntryPath; + } + next(); + }); + }, + transformIndexHtml: function transformIndexHtml(c) { + return c.replace('', ""); + } + }, { + name: "add-entry", + enforce: "post", + configResolved: function configResolved(config) { + var inputOptions = config.build.rollupOptions.input; + if (!inputOptions) { + htmlFilePath = path__namespace.resolve(config.root, 'index.html'); + } else if (typeof inputOptions === 'string') { + entryFiles = [inputOptions]; + htmlFilePath = path__namespace.resolve(config.root, inputOptions); + } else if (Array.isArray(inputOptions)) { + entryFiles = inputOptions; + htmlFilePath = path__namespace.resolve(config.root, inputOptions[0]); + } else if (typeof inputOptions === 'object') { + entryFiles = Object.values(inputOptions); + htmlFilePath = path__namespace.resolve(config.root, Object.values(inputOptions)[0]); + } + }, + buildStart: function buildStart() { + var _this$emitFile; + if (_command === "serve") return; + var hasHash = fileName == null || fileName.includes == null ? void 0 : fileName.includes("[hash"); + this.emitFile((_this$emitFile = { + name: entryName + }, _this$emitFile[hasHash ? "name" : "fileName"] = fileName, _this$emitFile.type = 'chunk', _this$emitFile.id = entryPath, _this$emitFile.preserveSignature = 'strict', _this$emitFile)); + if (htmlFilePath) { + var htmlContent = fs__namespace.readFileSync(htmlFilePath, 'utf-8'); + var scriptRegex = /]*src=["']([^"']+)["'][^>]*>/gi; + var match; + while ((match = scriptRegex.exec(htmlContent)) !== null) { + entryFiles.push(match[1]); + } + } + }, + transform: function transform(code, id) { + if (entryFiles.some(function (file) { + return id.endsWith(file); + })) { + var injection = "\n import " + JSON.stringify(entryPath) + ";\n "; + return injection + code; + } + } + }]; + }; + + /** + * Solve the problem that dev mode dependency prebunding does not support top-level await syntax + */ + function PluginDevProxyModuleTopLevelAwait() { + var filterFunction = pluginutils.createFilter(); + return { + name: "dev-proxy-module-top-level-await", + apply: "serve", + transform: function transform(code, id) { + if (!code.includes("/*mf top-level-await placeholder replacement mf*/")) { + return null; + } + if (!filterFunction(id)) return null; + var ast; + try { + ast = this.parse(code, { + allowReturnOutsideFunction: true + }); + } catch (e) { + throw new Error(id + ": " + e); + } + var magicString = new MagicString__default["default"](code); + estreeWalker.walk(ast, { + enter: function enter(node) { + if (node.type === 'ExportNamedDeclaration' && node.specifiers) { + var exportSpecifiers = node.specifiers.map(function (specifier) { + return specifier.exported.name; + }); + var proxyStatements = exportSpecifiers.map(function (name) { + return "\n const __mfproxy__await" + name + " = await " + name + "();\n const __mfproxy__" + name + " = () => __mfproxy__await" + name + ";\n "; + }).join('\n'); + var exportStatements = exportSpecifiers.map(function (name) { + return "__mfproxy__" + name + " as " + name; + }).join(', '); + var start = node.start; + var end = node.end; + var replacement = proxyStatements + "\nexport { " + exportStatements + " };"; + magicString.overwrite(start, end, replacement); + } + if (node.type === 'ExportDefaultDeclaration') { + var declaration = node.declaration; + var _start = node.start; + var _end = node.end; + var proxyStatement; + var exportStatement = 'default'; + if (declaration.type === 'Identifier') { + // example: export default foo; + proxyStatement = "\n const __mfproxy__awaitdefault = await " + declaration.name + "();\n const __mfproxy__default = __mfproxy__awaitdefault;\n "; + } else if (declaration.type === 'CallExpression' || declaration.type === 'FunctionDeclaration') { + // example: export default someFunction(); + var declarationCode = code.slice(declaration.start, declaration.end); + proxyStatement = "\n const __mfproxy__awaitdefault = await (" + declarationCode + ");\n const __mfproxy__default = __mfproxy__awaitdefault;\n "; + } else { + // other + proxyStatement = "\n const __mfproxy__awaitdefault = await (" + code.slice(declaration.start, declaration.end) + ");\n const __mfproxy__default = __mfproxy__awaitdefault;\n "; + } + var _replacement = proxyStatement + "\nexport { __mfproxy__default as " + exportStatement + " };"; + magicString.overwrite(_start, _end, _replacement); + } + } + }); + return { + code: magicString.toString(), + map: magicString.generateMap({ + hires: true + }) + }; + } + }; + } + + function _arrayLikeToArray(r, a) { + (null == a || a > r.length) && (a = r.length); + for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; + return n; + } + function _createForOfIteratorHelperLoose(r, e) { + var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; + if (t) return (t = t.call(r)).next.bind(t); + if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { + t && (r = t); + var o = 0; + return function () { + return o >= r.length ? { + done: !0 + } : { + done: !1, + value: r[o++] + }; + }; + } + throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); + } + function _unsupportedIterableToArray(r, a) { + if (r) { + if ("string" == typeof r) return _arrayLikeToArray(r, a); + var t = {}.toString.call(r).slice(8, -1); + return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; + } + } + + function normalizeExposesItem(key, item) { + var importPath = ''; + if (typeof item === 'string') { + importPath = item; + } + if (typeof item === 'object') { + importPath = item["import"]; + } + return { + "import": importPath + }; + } + function normalizeExposes(exposes) { + if (!exposes) return {}; + var res = {}; + Object.keys(exposes).forEach(function (key) { + res[key] = normalizeExposesItem(key, exposes[key]); + }); + return res; + } + function normalizeRemotes(remotes) { + if (!remotes) return {}; + var result = {}; + if (typeof remotes === 'object') { + Object.keys(remotes).forEach(function (key) { + result[key] = normalizeRemoteItem(key, remotes[key]); + }); + } + return result; + } + function normalizeRemoteItem(key, remote) { + if (typeof remote === 'string') { + var _remote$split = remote.split('@'), + entryGlobalName = _remote$split[0]; + var entry = remote.replace(entryGlobalName + '@', ''); + return { + type: 'var', + name: key, + entry: entry, + entryGlobalName: entryGlobalName, + shareScope: 'default' + }; + } + return Object.assign({ + type: 'var', + name: key, + shareScope: 'default', + entryGlobalName: key + }, remote); + } + function removePathFromNpmPackage(packageString) { + // 匹配npm包名的正则表达式,忽略路径部分 + var regex = /^(?:@[^/]+\/)?[^/]+/; + // 使用正则表达式匹配并提取包名 + var match = packageString.match(regex); + // 返回匹配到的包名,如果没有匹配到则返回原字符串 + return match ? match[0] : packageString; + } + function normalizeShareItem(key, shareItem) { + var version; + try { + version = require(path__namespace.join(removePathFromNpmPackage(key), 'package.json')).version; + } catch (e) { + console.log(e); + } + if (typeof shareItem === 'string') { + return { + name: shareItem, + version: version, + scope: 'default', + from: '', + shareConfig: { + singleton: false, + requiredVersion: "^" + version || '*' + } + }; + } + return { + name: key, + from: '', + version: shareItem.version || version, + scope: shareItem.shareScope || 'default', + shareConfig: { + singleton: shareItem.singleton || false, + requiredVersion: shareItem.requiredVersion || "^" + version || '*', + strictVersion: !!shareItem.strictVersion + } + }; + } + function normalizeShared(shared) { + if (!shared) return {}; + var result = {}; + if (Array.isArray(shared)) { + shared.forEach(function (key) { + result[key] = normalizeShareItem(key, key); + }); + return result; + } + if (typeof shared === 'object') { + Object.keys(shared).forEach(function (key) { + result[key] = normalizeShareItem(key, shared[key]); + }); + } + return result; + } + function normalizeLibrary(library) { + if (!library) return undefined; + return library; + } + function normalizeManifest(manifest) { + if (manifest === void 0) { + manifest = false; + } + if (typeof manifest === "boolean") { + return manifest; + } + return Object.assign({ + filePath: "", + disableAssetsAnalyze: false, + fileName: "mf-manifest.json" + }, manifest); + } + var config; + function getNormalizeModuleFederationOptions() { + return config; + } + function getNormalizeShareItem(key) { + var options = getNormalizeModuleFederationOptions(); + var shareItem = options.shared[removePathFromNpmPackage(key)] || options.shared[removePathFromNpmPackage(key) + "/"]; + return shareItem; + } + function normalizeModuleFederationOptions(options) { + return config = { + exposes: normalizeExposes(options.exposes), + filename: options.filename || 'remoteEntry-[hash]', + library: normalizeLibrary(options.library), + name: options.name, + // remoteType: options.remoteType, + remotes: normalizeRemotes(options.remotes), + runtime: options.runtime, + shareScope: options.shareScope || 'default', + shared: normalizeShared(options.shared), + runtimePlugins: options.runtimePlugins || [], + getPublicPath: options.getPublicPath, + implementation: options.implementation, + manifest: normalizeManifest(options.manifest), + dev: options.dev, + dts: options.dts + }; + } + + /** + * Escaping rules: + * Convert using the format __${mapping}__, where _ and $ are not allowed in npm package names but can be used in variable names. + * @ => 1 + * / => 2 + * - => 3 + * . => 4 + */ + /** + * Encodes a package name into a valid file name. + * @param {string} name - The package name, e.g., "@scope/xx-xx.xx". + * @returns {string} - The encoded file name. + */ + function packageNameEncode(name) { + if (typeof name !== "string") throw new Error("A string package name is required"); + return name.replace(/@/g, "_mf_0_").replace(/\//g, "_mf_1_").replace(/-/g, "_mf_2_").replace(/\./g, "_mf_3_"); + } + /** + * Decodes an encoded file name back to the original package name. + * @param {string} encoded - The encoded file name, e.g., "_mf_0_scope_mf_1_xx_mf_2_xx_mf_3_xx". + * @returns {string} - The decoded package name. + */ + function packageNameDecode(encoded) { + if (typeof encoded !== "string") throw new Error("A string encoded file name is required"); + return encoded.replace(/_mf_0_/g, "@").replace(/_mf_1_/g, "/").replace(/_mf_2_/g, "-").replace(/_mf_3_/g, "."); + } + + /** + * https://github.com/module-federation/vite/issues/68 + */ + function getLocalSharedImportMapPath_windows() { + var _getNormalizeModuleFe = getNormalizeModuleFederationOptions(), + name = _getNormalizeModuleFe.name; + return path__default["default"].resolve(".__mf__win", packageNameEncode(name), "localSharedImportMap"); + } + function writeLocalSharedImportMap_windows(content) { + var localSharedImportMapId = getLocalSharedImportMapPath_windows(); + createFile(localSharedImportMapId + ".js", "\n// Windows temporarily needs this file, https://github.com/module-federation/vite/issues/68\n" + content); + } + function createFile(filePath, content) { + var dir = path__default["default"].dirname(filePath); + fs.mkdirSync(dir, { + recursive: true + }); + fs.writeFileSync(filePath, content); + } + + var nodeModulesDir = function findNodeModulesDir(startDir) { + if (startDir === void 0) { + startDir = process.cwd(); + } + var currentDir = startDir; + while (currentDir !== path.parse(currentDir).root) { + var nodeModulesPath = path.join(currentDir, 'node_modules'); + if (fs.existsSync(nodeModulesPath)) { + return nodeModulesPath; + } + currentDir = path.dirname(currentDir); + } + return ""; + }(); + var virtualPackageName = "__mf__virtual"; + if (!fs.existsSync(path.resolve(nodeModulesDir, virtualPackageName))) { + fs.mkdirSync(path.resolve(nodeModulesDir, virtualPackageName)); + } + fs.writeFileSync(path.resolve(nodeModulesDir, virtualPackageName, "empty.js"), ""); + fs.writeFileSync(path.resolve(nodeModulesDir, virtualPackageName, "package.json"), JSON.stringify({ + name: virtualPackageName, + main: "empty.js" + })); + var patternMap = {}; + var cacheMap = {}; + /** + * Physically generate files as virtual modules under node_modules/__mf__virtual/* + */ + var VirtualModule = /*#__PURE__*/function () { + function VirtualModule(name, tag, suffix) { + var _name$split$slice$pop; + if (tag === void 0) { + tag = '__mf_v__'; + } + if (suffix === void 0) { + suffix = ""; + } + this.name = void 0; + this.tag = void 0; + this.suffix = void 0; + this.inited = false; + this.name = name; + this.tag = tag; + this.suffix = suffix || ((_name$split$slice$pop = name.split(".").slice(1).pop()) == null ? void 0 : _name$split$slice$pop.replace(/(.)/, ".$1")) || ".js"; + if (!cacheMap[this.tag]) cacheMap[this.tag] = {}; + cacheMap[this.tag][this.name] = this; + } + VirtualModule.findModule = function findModule(tag, str) { + if (str === void 0) { + str = ""; + } + if (!patternMap[tag]) patternMap[tag] = new RegExp("(.*" + packageNameEncode(tag) + "(.+?)" + packageNameEncode(tag) + ".*)"); + var moduleName = (str.match(patternMap[tag]) || [])[2]; + if (moduleName) return cacheMap[tag][packageNameDecode(moduleName)]; + return undefined; + }; + var _proto = VirtualModule.prototype; + _proto.getPath = function getPath() { + return path.resolve(nodeModulesDir, this.getImportId()); + }; + _proto.getImportId = function getImportId() { + var _getNormalizeModuleFe = getNormalizeModuleFederationOptions(), + mfName = _getNormalizeModuleFe.name; + return virtualPackageName + "/" + packageNameEncode("" + mfName + this.tag + this.name + this.tag) + this.suffix; + }; + _proto.writeSync = function writeSync(code, force) { + if (!force && this.inited) return; + if (!this.inited) { + this.inited = true; + } + fs.writeFileSync(this.getPath(), code); + }; + _proto.write = function write(code) { + fs.writeFile(this.getPath(), code, function () {}); + }; + return VirtualModule; + }(); + + var virtualRuntimeInitStatus = new VirtualModule("runtimeInit"); + function writeRuntimeInitStatus() { + virtualRuntimeInitStatus.writeSync("\n let initResolve, initReject\n const initPromise = new Promise((re, rj) => {\n initResolve = re\n initReject = rj\n })\n export {\n initPromise,\n initResolve,\n initReject\n }\n "); + } + + var cacheRemoteMap = {}; + var LOAD_REMOTE_TAG = '__loadRemote__'; + function getRemoteVirtualModule(remote, command) { + if (!cacheRemoteMap[remote]) { + cacheRemoteMap[remote] = new VirtualModule(remote, LOAD_REMOTE_TAG, ".js"); + cacheRemoteMap[remote].writeSync(generateRemotes(remote, command)); + } + var virtual = cacheRemoteMap[remote]; + return virtual; + } + var usedRemotesMap = { + // remote1: {remote1/App, remote1, remote1/Button} + }; + function addUsedRemote(remoteKey, remoteModule) { + if (!usedRemotesMap[remoteKey]) usedRemotesMap[remoteKey] = new Set(); + usedRemotesMap[remoteKey].add(remoteModule); + } + function getUsedRemotesMap() { + return usedRemotesMap; + } + function generateRemotes(id, command) { + return "\n const {loadRemote} = require(\"@module-federation/runtime\")\n const {initPromise} = require(\"" + virtualRuntimeInitStatus.getImportId() + "\")\n const res = initPromise.then(_ => loadRemote(" + JSON.stringify(id) + "))\n const exportModule = " + (command !== "build" ? "/*mf top-level-await placeholder replacement mf*/" : "await ") + "initPromise.then(_ => res)\n module.exports = exportModule\n "; + } + + /** + * Even the resolveId hook cannot interfere with vite pre-build, + * and adding query parameter virtual modules will also fail. + * You can only proxy to the real file through alias + */ + // *** __prebuild__ + var preBuildCacheMap = {}; + var PREBUILD_TAG = "__prebuild__"; + function writePreBuildLibPath(pkg) { + if (!preBuildCacheMap[pkg]) preBuildCacheMap[pkg] = new VirtualModule(pkg, PREBUILD_TAG); + preBuildCacheMap[pkg].writeSync(""); + } + function getPreBuildLibImportId(pkg) { + if (!preBuildCacheMap[pkg]) preBuildCacheMap[pkg] = new VirtualModule(pkg, PREBUILD_TAG); + var importId = preBuildCacheMap[pkg].getImportId(); + return importId; + } + // *** __loadShare__ + var LOAD_SHARE_TAG = "__loadShare__"; + var loadShareCacheMap = {}; + function getLoadShareModulePath(pkg) { + if (!loadShareCacheMap[pkg]) loadShareCacheMap[pkg] = new VirtualModule(pkg, LOAD_SHARE_TAG, ".js"); + var filepath = loadShareCacheMap[pkg].getPath(); + return filepath; + } + function writeLoadShareModule(pkg, shareItem, command) { + loadShareCacheMap[pkg].writeSync("\n \n ;() => import(" + JSON.stringify(getPreBuildLibImportId(pkg)) + ").catch(() => {});\n // dev uses dynamic import to separate chunks\n " + (command !== "build" ? ";() => import(" + JSON.stringify(pkg) + ").catch(() => {});" : '') + "\n const {loadShare} = require(\"@module-federation/runtime\")\n const {initPromise} = require(\"" + virtualRuntimeInitStatus.getImportId() + "\")\n const res = initPromise.then(_ => loadShare(" + JSON.stringify(pkg) + ", {\n customShareInfo: {shareConfig:{\n singleton: " + shareItem.shareConfig.singleton + ",\n strictVersion: " + shareItem.shareConfig.strictVersion + ",\n requiredVersion: " + JSON.stringify(shareItem.shareConfig.requiredVersion) + "\n }}}))\n const exportModule = " + (command !== "build" ? "/*mf top-level-await placeholder replacement mf*/" : "await ") + "res.then(factory => factory())\n module.exports = exportModule\n "); + } + + var usedShares = new Set(); + function getUsedShares() { + return usedShares; + } + function addUsedShares(pkg) { + usedShares.add(pkg); + } + // *** Expose locally provided shared modules here + var localSharedImportMapModule = new VirtualModule("localSharedImportMap"); + function getLocalSharedImportMapPath() { + if (process.platform === "win32") { + return getLocalSharedImportMapPath_windows(); + } + return localSharedImportMapModule.getPath(); + } + var prevSharedCount; + function writeLocalSharedImportMap() { + var sharedCount = getUsedShares().size; + if (prevSharedCount !== sharedCount) { + prevSharedCount = sharedCount; + if (process.platform === "win32") { + writeLocalSharedImportMap_windows(generateLocalSharedImportMap()); + } else { + localSharedImportMapModule.writeSync(generateLocalSharedImportMap(), true); + } + } + } + function generateLocalSharedImportMap() { + var options = getNormalizeModuleFederationOptions(); + return "\n const importMap = {\n " + Array.from(getUsedShares()).map(function (pkg) { + return "\n " + JSON.stringify(pkg) + ": async () => {\n let pkg = await import(\"" + getPreBuildLibImportId(pkg) + "\")\n return pkg\n }\n "; + }).join(",") + "\n }\n const usedShared = {\n " + Array.from(getUsedShares()).map(function (key) { + var shareItem = getNormalizeShareItem(key); + return "\n " + JSON.stringify(key) + ": {\n name: " + JSON.stringify(key) + ",\n version: " + JSON.stringify(shareItem.version) + ",\n scope: [" + JSON.stringify(shareItem.scope) + "],\n loaded: false,\n from: " + JSON.stringify(options.name) + ",\n async get () {\n usedShared[" + JSON.stringify(key) + "].loaded = true\n const {" + JSON.stringify(key) + ": pkgDynamicImport} = importMap \n const res = await pkgDynamicImport()\n const exportModule = {...res}\n // All npm packages pre-built by vite will be converted to esm\n Object.defineProperty(exportModule, \"__esModule\", {\n value: true,\n enumerable: false\n })\n return function () {\n return exportModule\n }\n },\n shareConfig: {\n singleton: " + shareItem.shareConfig.singleton + ",\n requiredVersion: " + JSON.stringify(shareItem.shareConfig.requiredVersion) + "\n }\n }\n "; + }).join(',') + "\n }\n const usedRemotes = [" + Object.keys(getUsedRemotesMap()).map(function (key) { + var remote = options.remotes[key]; + return "\n {\n entryGlobalName: " + JSON.stringify(remote.entryGlobalName) + ",\n name: " + JSON.stringify(remote.name) + ",\n type: " + JSON.stringify(remote.type) + ",\n entry: " + JSON.stringify(remote.entry) + ",\n }\n "; + }).join(',') + "\n ]\n export {\n usedShared,\n usedRemotes\n }\n "; + } + var REMOTE_ENTRY_ID = 'virtual:mf-REMOTE_ENTRY_ID'; + function generateRemoteEntry(options) { + var pluginImportNames = options.runtimePlugins.map(function (p, i) { + return ["$runtimePlugin_" + i, "import $runtimePlugin_" + i + " from \"" + p + "\";"]; + }); + return "\n import {init as runtimeInit, loadRemote} from \"@module-federation/runtime\";\n " + pluginImportNames.map(function (item) { + return item[1]; + }).join('\n') + "\n\n const exposesMap = {\n " + Object.keys(options.exposes).map(function (key) { + return "\n " + JSON.stringify(key) + ": async () => {\n const importModule = await import(" + JSON.stringify(options.exposes[key]["import"]) + ")\n const exportModule = {}\n Object.assign(exportModule, importModule)\n Object.defineProperty(exportModule, \"__esModule\", {\n value: true,\n enumerable: false\n })\n return exportModule\n }\n "; + }).join(',') + "\n }\n import {usedShared, usedRemotes} from \"" + getLocalSharedImportMapPath() + "\"\n import {\n initResolve\n } from \"" + virtualRuntimeInitStatus.getImportId() + "\"\n async function init(shared = {}) {\n const initRes = runtimeInit({\n name: " + JSON.stringify(options.name) + ",\n remotes: usedRemotes,\n shared: usedShared,\n plugins: [" + pluginImportNames.map(function (item) { + return item[0] + "()"; + }).join(', ') + "]\n });\n initRes.initShareScopeMap('" + options.shareScope + "', shared);\n initResolve(initRes)\n return initRes\n }\n\n function getExposes(moduleName) {\n if (!(moduleName in exposesMap)) throw new Error(`Module ${moduleName} does not exist in container.`)\n return (exposesMap[moduleName])().then(res => () => res)\n }\n export {\n init,\n getExposes as get\n }\n "; + } + /** + * Inject entry file, automatically init when used as host, + * and will not inject remoteEntry + */ + var hostAutoInitModule = new VirtualModule("hostAutoInit"); + function writeHostAutoInit() { + hostAutoInitModule.writeSync("\n import {init} from \"" + REMOTE_ENTRY_ID + "\"\n init()\n "); + } + function getHostAutoInitImportId() { + return hostAutoInitModule.getImportId(); + } + function getHostAutoInitPath() { + return hostAutoInitModule.getPath(); + } + + function initVirtualModules() { + writeLocalSharedImportMap(); + writeHostAutoInit(); + writeRuntimeInitStatus(); + } + + var Manifest = function Manifest() { + var mfOptions = getNormalizeModuleFederationOptions(); + var name = mfOptions.name, + filename = mfOptions.filename, + manifestOptions = mfOptions.manifest; + var mfManifestName = ""; + if (manifestOptions === true) { + mfManifestName = "mf-manifest.json"; + } + if (typeof manifestOptions !== "boolean") { + mfManifestName = path.join((manifestOptions == null ? void 0 : manifestOptions.filePath) || "", (manifestOptions == null ? void 0 : manifestOptions.fileName) || ""); + } + var extensions; + var root; + var remoteEntryFile; + return [{ + name: 'moddule-federation-manifest', + apply: 'serve', + configureServer: function configureServer(server) { + server.middlewares.use(function (req, res, next) { + if (!mfManifestName) { + next(); + return; + } + if (req.url === mfManifestName.replace(/^\/?/, "/")) { + res.setHeader('Content-Type', 'application/json'); + res.setHeader('Access-Control-Allow-Origin', '*'); + res.end(JSON.stringify({ + id: name, + name: name, + metaData: { + name: name, + type: 'app', + buildInfo: { + buildVersion: '1.0.0', + buildName: name + }, + remoteEntry: { + name: filename, + path: '', + type: 'module' + }, + ssrRemoteEntry: { + name: filename, + path: '', + type: 'module' + }, + types: { + path: '', + name: '' + }, + globalName: name, + pluginVersion: '0.2.5', + publicPath: 'auto' + }, + shared: Array.from(getUsedShares()).map(function (shareKey) { + var shareItem = getNormalizeShareItem(shareKey); + return { + id: name + ":" + shareKey, + name: shareKey, + version: shareItem.version, + requiredVersion: shareItem.shareConfig.requiredVersion, + assets: { + js: { + async: [], + sync: [] + }, + css: { + async: [], + sync: [] + } + } + }; + }), + remotes: function () { + var remotes = []; + var usedRemotesMap = getUsedRemotesMap(); + Object.keys(usedRemotesMap).forEach(function (remoteKey) { + var usedModules = Array.from(usedRemotesMap[remoteKey]); + usedModules.forEach(function (moduleKey) { + remotes.push({ + federationContainerName: mfOptions.remotes[remoteKey].entry, + moduleName: moduleKey.replace(remoteKey, '').replace('/', ''), + alias: remoteKey, + entry: '*' + }); + }); + }); + return remotes; + }(), + exposes: Object.keys(mfOptions.exposes).map(function (key) { + var formatKey = key.replace('./', ''); + return { + id: name + ':' + formatKey, + name: formatKey, + assets: { + js: { + async: [], + sync: [] + }, + css: { + sync: [], + async: [] + } + }, + path: key + }; + }) + })); + } else { + next(); + } + }); + } + }, { + name: 'moddule-federation-manifest', + enforce: 'post', + config: function config(_config) { + if (!_config.build) _config.build = {}; + if (!_config.build.manifest) _config.build.manifest = _config.build.manifest || !!manifestOptions; + }, + configResolved: function configResolved(config) { + root = config.root; + extensions = config.resolve.extensions || ['.mjs', '.js', '.mts', '.ts', '.jsx', '.tsx', '.json']; + }, + generateBundle: function generateBundle(options, bundle) { + try { + var _this = this; + // 递归查找模块的同步导入文件 + var _findSynchronousImports = function findSynchronousImports(fileName, array) { + var fileData = bundle[fileName]; + if (fileData && fileData.type === 'chunk') { + array.push(fileName); // 将当前文件加入预加载列表 + // 遍历该文件的同步导入文件 + fileData.imports.forEach(function (importedFile) { + if (array.indexOf(importedFile) === -1) { + _findSynchronousImports(importedFile, array); // 递归查找同步导入的文件 + } + }); + } + }; + if (!mfManifestName) return Promise.resolve(); + var exposesModules = Object.keys(mfOptions.exposes).map(function (item) { + return mfOptions.exposes[item]["import"]; + }); // 获取你提供的 moduleIds + var filesContainingModules = {}; + // 帮助函数:检查模块路径是否匹配 + var isModuleMatched = function isModuleMatched(relativeModulePath, preloadModule) { + // 先尝试直接匹配 + if (relativeModulePath === preloadModule) return true; + // 如果 preloadModule 没有后缀,尝试添加可能的后缀进行匹配 + for (var _iterator = _createForOfIteratorHelperLoose(extensions), _step; !(_step = _iterator()).done;) { + var ext = _step.value; + if (relativeModulePath === "" + preloadModule + ext) { + return true; + } + } + return false; + }; + // 遍历打包生成的每个文件 + for (var _i = 0, _Object$entries = Object.entries(bundle); _i < _Object$entries.length; _i++) { + var _Object$entries$_i = _Object$entries[_i], + fileName = _Object$entries$_i[0], + fileData = _Object$entries$_i[1]; + if (mfOptions.filename.replace(/[\[\]]/g, "_") === fileData.name) { + remoteEntryFile = fileData.fileName; + } + if (fileData.type === 'chunk') { + // 遍历该文件的所有模块 + for (var _i2 = 0, _Object$keys = Object.keys(fileData.modules); _i2 < _Object$keys.length; _i2++) { + var modulePath = _Object$keys[_i2]; + // 将绝对路径转换为相对于 Vite root 的相对路径 + var relativeModulePath = path.relative(root, modulePath); + // 检查模块是否在 preloadModules 列表中 + for (var _iterator2 = _createForOfIteratorHelperLoose(exposesModules), _step2; !(_step2 = _iterator2()).done;) { + var preloadModule = _step2.value; + var formatPreloadModule = preloadModule.replace("./", ""); + if (isModuleMatched(relativeModulePath, formatPreloadModule)) { + var _filesContainingModul; + if (!filesContainingModules[preloadModule]) { + filesContainingModules[preloadModule] = { + sync: [], + async: [] + }; + } + console.log(Object.keys(fileData.modules)); + filesContainingModules[preloadModule].sync.push(fileName); + (_filesContainingModul = filesContainingModules[preloadModule].async).push.apply(_filesContainingModul, fileData.dynamicImports || []); + _findSynchronousImports(fileName, filesContainingModules[preloadModule].sync); + break; // 如果找到匹配,跳出循环 + } + } + } + } + } + ; + var fileToShareKey = {}; + return Promise.resolve(Promise.all(Array.from(getUsedShares()).map(function (shareKey) { + try { + return Promise.resolve(_this.resolve(getPreBuildLibImportId(shareKey))).then(function (_this$resolve) { + var file = _this$resolve.id.split("?")[0]; + fileToShareKey[file] = shareKey; + }); + } catch (e) { + return Promise.reject(e); + } + }))).then(function () { + // 遍历打包生成的每个文件 + for (var _i3 = 0, _Object$entries2 = Object.entries(bundle); _i3 < _Object$entries2.length; _i3++) { + var _Object$entries2$_i = _Object$entries2[_i3], + _fileName = _Object$entries2$_i[0], + _fileData = _Object$entries2$_i[1]; + if (_fileData.type === 'chunk') { + // 遍历该文件的所有模块 + for (var _i4 = 0, _Object$keys2 = Object.keys(_fileData.modules); _i4 < _Object$keys2.length; _i4++) { + var _modulePath = _Object$keys2[_i4]; + var sharedKey = fileToShareKey[_modulePath]; + if (sharedKey) { + var _filesContainingModul2; + if (!filesContainingModules[sharedKey]) { + filesContainingModules[sharedKey] = { + sync: [], + async: [] + }; + } + filesContainingModules[sharedKey].sync.push(_fileName); + (_filesContainingModul2 = filesContainingModules[sharedKey].async).push.apply(_filesContainingModul2, _fileData.dynamicImports || []); + _findSynchronousImports(_fileName, filesContainingModules[sharedKey].sync); + break; // 如果找到匹配,跳出循环 + } + } + } + } + Object.keys(filesContainingModules).forEach(function (key) { + filesContainingModules[key].sync = Array.from(new Set(filesContainingModules[key].sync)); + filesContainingModules[key].async = Array.from(new Set(filesContainingModules[key].async)); + }); + _this.emitFile({ + type: 'asset', + fileName: mfManifestName, + source: generateMFManifest(filesContainingModules) + }); + }); + } catch (e) { + return Promise.reject(e); + } + } + }]; + function generateMFManifest(preloadMap) { + var options = getNormalizeModuleFederationOptions(); + var name = options.name; + var remoteEntry = { + name: remoteEntryFile, + path: '', + type: 'module' + }; + var remotes = []; + var usedRemotesMap = getUsedRemotesMap(); + Object.keys(usedRemotesMap).forEach(function (remoteKey) { + var usedModules = Array.from(usedRemotesMap[remoteKey]); + usedModules.forEach(function (moduleKey) { + remotes.push({ + federationContainerName: options.remotes[remoteKey].entry, + moduleName: moduleKey.replace(remoteKey, '').replace('/', ''), + alias: remoteKey, + entry: '*' + }); + }); + }); + // @ts-ignore + var shared = Array.from(getUsedShares()).map(function (shareKey) { + // assets(.css, .jpg, .svg等)其他资源, 不重要, 暂未处理 + if (!preloadMap[shareKey]) return; + var shareItem = getNormalizeShareItem(shareKey); + return { + id: name + ":" + shareKey, + name: shareKey, + version: shareItem.version, + requiredVersion: shareItem.shareConfig.requiredVersion, + assets: { + js: { + async: preloadMap[shareKey].async, + sync: preloadMap[shareKey].sync + }, + css: { + async: [], + sync: [] + } + } + }; + }).filter(function (item) { + return item; + }); + var exposes = Object.keys(options.exposes).map(function (key) { + // assets(.css, .jpg, .svg等)其他资源, 不重要, 暂未处理 + var formatKey = key.replace('./', ''); + var sourceFile = options.exposes[key]["import"]; + if (!preloadMap[sourceFile]) return; + return { + id: name + ':' + formatKey, + name: formatKey, + assets: { + js: { + async: preloadMap[sourceFile].async, + sync: preloadMap[sourceFile].sync + }, + css: { + sync: [], + async: [] + } + }, + path: key + }; + }).filter(function (item) { + return item; + }); // Filter out any null values + var result = { + id: name, + name: name, + metaData: { + name: name, + type: 'app', + buildInfo: { + buildVersion: '1.0.0', + buildName: name + }, + remoteEntry: remoteEntry, + ssrRemoteEntry: remoteEntry, + types: { + path: '', + name: '' + // "zip": "@mf-types.zip", + // "api": "@mf-types.d.ts" + }, + globalName: name, + pluginVersion: '0.2.5', + publicPath: 'auto' + }, + shared: shared, + remotes: remotes, + exposes: exposes + }; + return JSON.stringify(result); + } + }; + + var _resolve, + promise = new Promise(function (resolve, reject) { + _resolve = resolve; + }); + var parsePromise = promise; + var parseStartSet = new Set(); + var parseEndSet = new Set(); + function pluginModuleParseEnd (excludeFn) { + return [{ + name: "_", + apply: "serve", + config: function config() { + // No waiting in development mode + _resolve(1); + } + }, { + enforce: "pre", + name: "parseStart", + apply: "build", + load: function load(id) { + if (excludeFn(id)) { + return; + } + parseStartSet.add(id); + } + }, { + enforce: "post", + name: "parseEnd", + apply: "build", + moduleParsed: function moduleParsed(module) { + var id = module.id; + if (excludeFn(id)) { + return; + } + parseEndSet.add(id); + if (parseStartSet.size === parseEndSet.size) { + _resolve(1); + } + } + }]; + } + + var filter = pluginutils.createFilter(); + function pluginProxyRemoteEntry () { + return { + name: 'proxyRemoteEntry', + enforce: 'post', + resolveId: function resolveId(id) { + if (id === REMOTE_ENTRY_ID) { + return REMOTE_ENTRY_ID; + } + }, + load: function load(id) { + if (id === REMOTE_ENTRY_ID) { + return parsePromise.then(function (_) { + return generateRemoteEntry(getNormalizeModuleFederationOptions()); + }); + } + }, + transform: function transform(code, id) { + try { + if (!filter(id)) return Promise.resolve(); + if (id.includes(REMOTE_ENTRY_ID)) { + return Promise.resolve(parsePromise.then(function (_) { + return generateRemoteEntry(getNormalizeModuleFederationOptions()); + })); + } + return Promise.resolve(); + } catch (e) { + return Promise.reject(e); + } + } + }; + } + + pluginutils.createFilter(); + function pluginProxyRemotes (options) { + var remotes = options.remotes; + return { + name: "proxyRemotes", + config: function config(_config, _ref) { + var _command = _ref.command; + Object.keys(remotes).forEach(function (key) { + var remote = remotes[key]; + _config.resolve.alias.push({ + find: new RegExp("^(" + remote.name + "(/.*|$))"), + replacement: "$1", + customResolver: function customResolver(source) { + var remoteModule = getRemoteVirtualModule(source, _command); + addUsedRemote(remote.name, source); + return remoteModule.getPath(); + } + }); + }); + } + }; + } + + /** + * example: + * const store = new PromiseStore(); + * store.get("example").then((result) => { + * console.log("Result from example:", result); // 42 + * }); + * setTimeout(() => { + * store.set("example", Promise.resolve(42)); + * }, 2000); + */ + var PromiseStore = /*#__PURE__*/function () { + function PromiseStore() { + this.promiseMap = new Map(); + this.resolveMap = new Map(); + } + var _proto = PromiseStore.prototype; + _proto.set = function set(id, promise) { + if (this.resolveMap.has(id)) { + promise.then(this.resolveMap.get(id)); + this.resolveMap["delete"](id); + } + this.promiseMap.set(id, promise); + }; + _proto.get = function get(id) { + var _this = this; + if (this.promiseMap.has(id)) { + return this.promiseMap.get(id); + } + var pendingPromise = new Promise(function (resolve) { + _this.resolveMap.set(id, resolve); + }); + this.promiseMap.set(id, pendingPromise); + return pendingPromise; + }; + return PromiseStore; + }(); + + function proxySharedModule(options) { + var _options$shared = options.shared, + shared = _options$shared === void 0 ? {} : _options$shared; + return [{ + name: "generateLocalSharedImportMap", + enforce: "post", + load: function load(id) { + if (id.includes(getLocalSharedImportMapPath())) { + return parsePromise.then(function (_) { + return generateLocalSharedImportMap(); + }); + } + }, + transform: function transform(code, id) { + if (id.includes(getLocalSharedImportMapPath())) { + return parsePromise.then(function (_) { + return generateLocalSharedImportMap(); + }); + } + } + }, { + name: 'proxyPreBuildShared', + enforce: 'post', + config: function config(_config, _ref) { + var _config$resolve$alias, _config$resolve$alias2; + var command = _ref.command; + (_config$resolve$alias = _config.resolve.alias).push.apply(_config$resolve$alias, Object.keys(shared).map(function (key) { + var pattern = key.endsWith("/") ? "(^" + key.replace(/\/$/, "") + "(/.+)?$)" : "(^" + key + "$)"; + return { + // Intercept all shared requests and proxy them to loadShare + find: new RegExp(pattern), + replacement: "$1", + customResolver: function customResolver(source, importer) { + var loadSharePath = getLoadShareModulePath(source); + writeLoadShareModule(source, shared[key], command); + writePreBuildLibPath(source); + addUsedShares(source); + writeLocalSharedImportMap(); + return this.resolve(loadSharePath); + } + }; + })); + var savePrebuild = new PromiseStore(); + (_config$resolve$alias2 = _config.resolve.alias).push.apply(_config$resolve$alias2, Object.keys(shared).map(function (key) { + return command === "build" ? { + find: new RegExp("(.*" + PREBUILD_TAG + ".*)"), + replacement: function replacement($1) { + var pkgName = VirtualModule.findModule(PREBUILD_TAG, $1).name; + return pkgName; + } + } : { + find: new RegExp("(.*" + PREBUILD_TAG + ".*)"), + replacement: "$1", + customResolver: function customResolver(source, importer) { + try { + var _this = this; + var pkgName = VirtualModule.findModule(PREBUILD_TAG, source).name; + if (importer.includes(LOAD_SHARE_TAG)) { + // save pre-bunding module id + savePrebuild.set(pkgName, _this.resolve(pkgName).then(function (item) { + return item.id; + })); + } + // Fix localSharedImportMap import id + var _resolve = _this.resolve; + return Promise.resolve(savePrebuild.get(pkgName)).then(function (_savePrebuild$get) { + return Promise.resolve(_resolve.call(_this, _savePrebuild$get)); + }); + } catch (e) { + return Promise.reject(e); + } + } + }; + })); + } + }, { + name: "watchLocalSharedImportMap", + apply: "serve", + config: function config(_config2) { + _config2.optimizeDeps = defu.defu(_config2.optimizeDeps, { + exclude: [getLocalSharedImportMapPath()] + }); + _config2.server = defu.defu(_config2.server, { + watch: { + ignored: [] + } + }); + var watch = _config2.server.watch; + watch.ignored = [].concat(watch.ignored); + watch.ignored.push("!**" + getLocalSharedImportMapPath() + "**"); + } + }]; + } + + var aliasToArrayPlugin = { + name: 'alias-transform-plugin', + config: function config(_config, _ref) { + if (!_config.resolve) _config.resolve = {}; + if (!_config.resolve.alias) _config.resolve.alias = []; + var alias = _config.resolve.alias; + if (typeof alias === 'object' && !Array.isArray(alias)) { + _config.resolve.alias = Object.entries(alias).map(function (_ref2) { + var find = _ref2[0], + replacement = _ref2[1]; + return { + find: find, + replacement: replacement + }; + }); + } + } + }; + + var normalizeOptimizeDepsPlugin = { + name: 'normalizeOptimizeDeps', + config: function config(_config, _ref) { + var optimizeDeps = _config.optimizeDeps; + if (!optimizeDeps) { + _config.optimizeDeps = {}; + optimizeDeps = _config.optimizeDeps; + } + // todo: fix this workaround + optimizeDeps.force = true; + if (!optimizeDeps.include) optimizeDeps.include = []; + if (!optimizeDeps.needsInterop) optimizeDeps.needsInterop = []; + } + }; + + function federation(mfUserOptions) { + var options = normalizeModuleFederationOptions(mfUserOptions); + initVirtualModules(); + var name = options.name, + shared = options.shared, + filename = options.filename; + if (!name) throw new Error("name is required"); + return [aliasToArrayPlugin, normalizeOptimizeDepsPlugin].concat(addEntry({ + entryName: 'remoteEntry', + entryPath: REMOTE_ENTRY_ID, + fileName: filename + }), addEntry({ + entryName: 'hostInit', + entryPath: getHostAutoInitPath() + }), [pluginProxyRemoteEntry(), pluginProxyRemotes(options)], pluginModuleParseEnd(function (id) { + return id.includes(getHostAutoInitImportId()) || id.includes(REMOTE_ENTRY_ID) || id.includes(getLocalSharedImportMapPath()); + }), proxySharedModule({ + shared: shared + }), [PluginDevProxyModuleTopLevelAwait(), { + name: 'module-federation-vite', + enforce: 'post', + config: function config(_config, _ref) { + var _config$optimizeDeps; + _config.resolve.alias.push({ + find: '@module-federation/runtime', + replacement: require.resolve('@module-federation/runtime') + }); + (_config$optimizeDeps = _config.optimizeDeps) == null || (_config$optimizeDeps = _config$optimizeDeps.include) == null || _config$optimizeDeps.push('@module-federation/runtime'); + } + }], Manifest()); + } + + exports.federation = federation; + +})); diff --git a/lib/plugins/pluginAddEntry.d.ts b/lib/plugins/pluginAddEntry.d.ts new file mode 100644 index 0000000..4ea5e42 --- /dev/null +++ b/lib/plugins/pluginAddEntry.d.ts @@ -0,0 +1,8 @@ +import { Plugin } from 'vite'; +interface AddEntryOptions { + entryName: string; + entryPath: string; + fileName?: string; +} +declare const addEntry: ({ entryName, entryPath, fileName }: AddEntryOptions) => Plugin[]; +export default addEntry; diff --git a/lib/plugins/pluginDevProxyModuleTopLevelAwait.d.ts b/lib/plugins/pluginDevProxyModuleTopLevelAwait.d.ts new file mode 100644 index 0000000..e223f16 --- /dev/null +++ b/lib/plugins/pluginDevProxyModuleTopLevelAwait.d.ts @@ -0,0 +1,2 @@ +import { Plugin } from "vite"; +export declare function PluginDevProxyModuleTopLevelAwait(): Plugin; diff --git a/lib/plugins/pluginMFManifest.d.ts b/lib/plugins/pluginMFManifest.d.ts new file mode 100644 index 0000000..f3d07d0 --- /dev/null +++ b/lib/plugins/pluginMFManifest.d.ts @@ -0,0 +1,3 @@ +import { Manifest, Plugin } from 'vite'; +declare const Manifest: () => Plugin[]; +export default Manifest; diff --git a/lib/plugins/pluginModuleParseEnd.d.ts b/lib/plugins/pluginModuleParseEnd.d.ts new file mode 100644 index 0000000..054958e --- /dev/null +++ b/lib/plugins/pluginModuleParseEnd.d.ts @@ -0,0 +1,8 @@ +/** + * Dynamic shared modules, such as "react/" and "react-dom/", can only be parsed during the build process; + * This plugin allows me to wait until all modules are built, and then expose them together. + */ +import { Plugin } from "vite"; +declare let parsePromise: Promise; +export default function (excludeFn: Function): Plugin[]; +export { parsePromise }; diff --git a/lib/plugins/pluginProxyRemoteEntry.d.ts b/lib/plugins/pluginProxyRemoteEntry.d.ts new file mode 100644 index 0000000..e0e6b7c --- /dev/null +++ b/lib/plugins/pluginProxyRemoteEntry.d.ts @@ -0,0 +1,2 @@ +import { Plugin } from 'vite'; +export default function (): Plugin; diff --git a/lib/plugins/pluginProxyRemotes.d.ts b/lib/plugins/pluginProxyRemotes.d.ts new file mode 100644 index 0000000..c01e32b --- /dev/null +++ b/lib/plugins/pluginProxyRemotes.d.ts @@ -0,0 +1,3 @@ +import { Plugin } from "vite"; +import { NormalizedModuleFederationOptions } from "../utils/normalizeModuleFederationOptions"; +export default function (options: NormalizedModuleFederationOptions): Plugin; diff --git a/lib/plugins/pluginProxySharedModule_preBuild.d.ts b/lib/plugins/pluginProxySharedModule_preBuild.d.ts new file mode 100644 index 0000000..ce5d9ec --- /dev/null +++ b/lib/plugins/pluginProxySharedModule_preBuild.d.ts @@ -0,0 +1,7 @@ +import { Plugin } from 'vite'; +import { NormalizedShared } from '../utils/normalizeModuleFederationOptions'; +export declare function proxySharedModule(options: { + shared?: NormalizedShared; + include?: string | string[]; + exclude?: string | string[]; +}): Plugin[]; diff --git a/lib/utils/PromiseStore.d.ts b/lib/utils/PromiseStore.d.ts new file mode 100644 index 0000000..574eca2 --- /dev/null +++ b/lib/utils/PromiseStore.d.ts @@ -0,0 +1,16 @@ +/** + * example: + * const store = new PromiseStore(); + * store.get("example").then((result) => { + * console.log("Result from example:", result); // 42 + * }); + * setTimeout(() => { + * store.set("example", Promise.resolve(42)); + * }, 2000); + */ +export declare class PromiseStore { + private promiseMap; + private resolveMap; + set(id: string, promise: Promise): void; + get(id: string): Promise; +} diff --git a/lib/utils/VirtualModule.d.ts b/lib/utils/VirtualModule.d.ts new file mode 100644 index 0000000..3688a9c --- /dev/null +++ b/lib/utils/VirtualModule.d.ts @@ -0,0 +1,16 @@ +export declare const virtualPackageName = "__mf__virtual"; +/** + * Physically generate files as virtual modules under node_modules/__mf__virtual/* + */ +export default class VirtualModule { + name: string; + tag: string; + suffix: string; + inited: boolean; + static findModule(tag: string, str?: string): VirtualModule | undefined; + constructor(name: string, tag?: string, suffix?: string); + getPath(): string; + getImportId(): string; + writeSync(code: string, force?: boolean): void; + write(code: string): void; +} diff --git a/lib/utils/aliasToArrayPlugin.d.ts b/lib/utils/aliasToArrayPlugin.d.ts new file mode 100644 index 0000000..c8aa357 --- /dev/null +++ b/lib/utils/aliasToArrayPlugin.d.ts @@ -0,0 +1,10 @@ +import { UserConfig } from 'vite'; +export interface Command { +} +declare const _default: { + name: string; + config: (config: UserConfig, { command }: { + command: Command; + }) => void; +}; +export default _default; diff --git a/lib/utils/getLocalSharedImportMap_windows.d.ts b/lib/utils/getLocalSharedImportMap_windows.d.ts new file mode 100644 index 0000000..9dfefa8 --- /dev/null +++ b/lib/utils/getLocalSharedImportMap_windows.d.ts @@ -0,0 +1,2 @@ +export declare function getRemoteEntryPath_windows(): string; +export declare function writeRemoteEntry_windows(content: string): void; diff --git a/lib/utils/localSharedImportMap_windows.d.ts b/lib/utils/localSharedImportMap_windows.d.ts new file mode 100644 index 0000000..72a20bf --- /dev/null +++ b/lib/utils/localSharedImportMap_windows.d.ts @@ -0,0 +1,2 @@ +export declare function getLocalSharedImportMapPath_windows(): string; +export declare function writeLocalSharedImportMap_windows(content: string): void; diff --git a/lib/utils/normalizeModuleFederationOptions.d.ts b/lib/utils/normalizeModuleFederationOptions.d.ts new file mode 100644 index 0000000..b0adf39 --- /dev/null +++ b/lib/utils/normalizeModuleFederationOptions.d.ts @@ -0,0 +1,90 @@ +import { SharedConfig } from '@module-federation/runtime/types'; +export type RemoteEntryType = 'var' | 'module' | 'assign' | 'assign-properties' | 'this' | 'window' | 'self' | 'global' | 'commonjs' | 'commonjs2' | 'commonjs-module' | 'commonjs-static' | 'amd' | 'amd-require' | 'umd' | 'umd2' | 'jsonp' | 'system' | string; +interface ExposesItem { + import: string; +} +export interface NormalizedShared { + [key: string]: ShareItem; +} +export declare function normalizeRemotes(remotes: Record | undefined): Record; +export interface ShareItem { + name: string; + version: string | undefined; + scope: string; + from: string; + shareConfig: SharedConfig; +} +interface ManifestOptions { + filePath?: string; + disableAssetsAnalyze?: boolean; + fileName?: string; +} +export type ModuleFederationOptions = { + exposes?: Record | undefined; + filename?: string; + library?: any; + name: string; + remotes?: Record | undefined; + runtime?: any; + shareScope?: string; + shared?: string[] | Record | undefined; + runtimePlugins?: string[]; + getPublicPath?: any; + implementation?: any; + manifest?: ManifestOptions | boolean; + dev?: any; + dts?: any; +}; +export interface NormalizedModuleFederationOptions { + exposes: Record; + filename: string; + library: any; + name: string; + remotes: Record; + runtime: any; + shareScope: string; + shared: NormalizedShared; + runtimePlugins: string[]; + getPublicPath: any; + implementation: any; + manifest: ManifestOptions | boolean; + dev: any; + dts: any; +} +export declare function getNormalizeModuleFederationOptions(): NormalizedModuleFederationOptions; +export declare function getNormalizeShareItem(key: string): ShareItem; +export declare function normalizeModuleFederationOptions(options: ModuleFederationOptions): NormalizedModuleFederationOptions; +export {}; diff --git a/lib/utils/normalizeOptimizeDeps.d.ts b/lib/utils/normalizeOptimizeDeps.d.ts new file mode 100644 index 0000000..329dcfa --- /dev/null +++ b/lib/utils/normalizeOptimizeDeps.d.ts @@ -0,0 +1,9 @@ +import { UserConfig } from 'vite'; +export interface Command { + command: string; +} +declare const _default: { + name: string; + config: (config: UserConfig, { command }: Command) => void; +}; +export default _default; diff --git a/lib/utils/packageNameUtils.d.ts b/lib/utils/packageNameUtils.d.ts new file mode 100644 index 0000000..7f99323 --- /dev/null +++ b/lib/utils/packageNameUtils.d.ts @@ -0,0 +1,22 @@ +/** + * Escaping rules: + * Convert using the format __${mapping}__, where _ and $ are not allowed in npm package names but can be used in variable names. + * @ => 1 + * / => 2 + * - => 3 + * . => 4 + */ +/** + * Encodes a package name into a valid file name. + * @param {string} name - The package name, e.g., "@scope/xx-xx.xx". + * @returns {string} - The encoded file name. + */ +export declare function packageNameEncode(name: string): string; +/** + * Decodes an encoded file name back to the original package name. + * @param {string} encoded - The encoded file name, e.g., "_mf_0_scope_mf_1_xx_mf_2_xx_mf_3_xx". + * @returns {string} - The decoded package name. + */ +export declare function packageNameDecode(encoded: string): string; +export declare function removePathFromNpmPackage(packageString: string): string; +export declare function getExtFromNpmPackage(packageString: string): string | undefined; diff --git a/lib/utils/wrapManualChunks.d.ts b/lib/utils/wrapManualChunks.d.ts new file mode 100644 index 0000000..50d0415 --- /dev/null +++ b/lib/utils/wrapManualChunks.d.ts @@ -0,0 +1 @@ +export declare function wrapManualChunks(output: any, manualChunksCb: Function): void; diff --git a/lib/virtualModules/index.d.ts b/lib/virtualModules/index.d.ts new file mode 100644 index 0000000..3b3f9a3 --- /dev/null +++ b/lib/virtualModules/index.d.ts @@ -0,0 +1,5 @@ +export { addUsedShares, generateLocalSharedImportMap, generateRemoteEntry, getHostAutoInitImportId, getHostAutoInitPath, getLocalSharedImportMapPath, getUsedShares, REMOTE_ENTRY_ID, writeLocalSharedImportMap } from "./virtualRemoteEntry"; +export { addUsedRemote, generateRemotes, getRemoteVirtualModule, getUsedRemotesMap } from "./virtualRemotes"; +export { getLoadShareModulePath, getPreBuildLibImportId, LOAD_SHARE_TAG, PREBUILD_TAG, writeLoadShareModule, writePreBuildLibPath } from "./virtualShared_preBuild"; +export { virtualRuntimeInitStatus } from "./virtualRuntimeInitStatus"; +export declare function initVirtualModules(): void; diff --git a/lib/virtualModules/virtualRemoteEntry.d.ts b/lib/virtualModules/virtualRemoteEntry.d.ts new file mode 100644 index 0000000..fd14b1a --- /dev/null +++ b/lib/virtualModules/virtualRemoteEntry.d.ts @@ -0,0 +1,11 @@ +import { NormalizedModuleFederationOptions } from '../utils/normalizeModuleFederationOptions'; +export declare function getUsedShares(): Set; +export declare function addUsedShares(pkg: string): void; +export declare function getLocalSharedImportMapPath(): string; +export declare function writeLocalSharedImportMap(): void; +export declare function generateLocalSharedImportMap(): string; +export declare const REMOTE_ENTRY_ID = "virtual:mf-REMOTE_ENTRY_ID"; +export declare function generateRemoteEntry(options: NormalizedModuleFederationOptions): string; +export declare function writeHostAutoInit(): void; +export declare function getHostAutoInitImportId(): string; +export declare function getHostAutoInitPath(): string; diff --git a/lib/virtualModules/virtualRemotes.d.ts b/lib/virtualModules/virtualRemotes.d.ts new file mode 100644 index 0000000..fdcfa5e --- /dev/null +++ b/lib/virtualModules/virtualRemotes.d.ts @@ -0,0 +1,6 @@ +import VirtualModule from '../utils/VirtualModule'; +export declare const LOAD_REMOTE_TAG = "__loadRemote__"; +export declare function getRemoteVirtualModule(remote: string, command: string): VirtualModule; +export declare function addUsedRemote(remoteKey: string, remoteModule: string): void; +export declare function getUsedRemotesMap(): Record>; +export declare function generateRemotes(id: string, command: string): string; diff --git a/lib/virtualModules/virtualRuntimeInitStatus.d.ts b/lib/virtualModules/virtualRuntimeInitStatus.d.ts new file mode 100644 index 0000000..d1568eb --- /dev/null +++ b/lib/virtualModules/virtualRuntimeInitStatus.d.ts @@ -0,0 +1,3 @@ +import VirtualModule from "../utils/VirtualModule"; +export declare const virtualRuntimeInitStatus: VirtualModule; +export declare function writeRuntimeInitStatus(): void; diff --git a/lib/virtualModules/virtualShared_preBuild.d.ts b/lib/virtualModules/virtualShared_preBuild.d.ts new file mode 100644 index 0000000..c30c777 --- /dev/null +++ b/lib/virtualModules/virtualShared_preBuild.d.ts @@ -0,0 +1,17 @@ +/** + * Even the resolveId hook cannot interfere with vite pre-build, + * and adding query parameter virtual modules will also fail. + * You can only proxy to the real file through alias + */ +/** +* shared will be proxied: +* 1. __prebuild__: export shareModule (pre-built source code of modules such as vue, react, etc.) +* 2. __loadShare__: load shareModule (mfRuntime.loadShare('vue')) +*/ +import { ShareItem } from "../utils/normalizeModuleFederationOptions"; +export declare const PREBUILD_TAG = "__prebuild__"; +export declare function writePreBuildLibPath(pkg: string): void; +export declare function getPreBuildLibImportId(pkg: string): string; +export declare const LOAD_SHARE_TAG = "__loadShare__"; +export declare function getLoadShareModulePath(pkg: string): string; +export declare function writeLoadShareModule(pkg: string, shareItem: ShareItem, command: string): void; diff --git a/package.json b/package.json index d271ee9..91ee030 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "scripts": { "prepare": "husky install", "format": "pretty-quick", - "dev": "microbundle watch", + "dev": "microbundle watch --no-sourcemap --compress=false", "build": "rimraf lib && microbundle --no-sourcemap --compress=false", "dev-rv": "pnpm -filter 'examples-rust-vite*' run dev", "dev-vv": "pnpm -filter 'examples-vite-vite*' run dev", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a4400a1..cd55a62 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -56,7 +56,27 @@ importers: version: 3.13.0(@parcel/watcher@2.4.1)(@types/node@22.5.0)(ioredis@5.4.1)(magicast@0.3.5)(rollup@4.21.0)(sass-embedded@1.77.8)(terser@5.31.6)(typescript@5.5.3)(vite@5.4.3(@types/node@22.5.0)(sass-embedded@1.77.8)(terser@5.31.6)) vue: specifier: latest - version: 3.4.38(typescript@5.5.3) + version: 3.5.3(typescript@5.5.3) + devDependencies: + vite-plugin-top-level-await: + specifier: ^1.4.4 + version: 1.4.4(@swc/helpers@0.5.3)(rollup@4.21.0)(vite@5.4.3(@types/node@22.5.0)(sass-embedded@1.77.8)(terser@5.31.6)) + + examples/nuxt-vite/nuxt-remote: + dependencies: + '@module-federation/vite': + specifier: workspace:* + version: link:../../.. + nuxt: + specifier: ^3.13.0 + version: 3.13.0(@parcel/watcher@2.4.1)(@types/node@22.5.0)(ioredis@5.4.1)(magicast@0.3.5)(rollup@4.21.0)(sass-embedded@1.77.8)(terser@5.31.6)(typescript@5.5.3)(vite@5.4.3(@types/node@22.5.0)(sass-embedded@1.77.8)(terser@5.31.6)) + vue: + specifier: latest + version: 3.5.3(typescript@5.5.3) + devDependencies: + vite-plugin-top-level-await: + specifier: ^1.4.4 + version: 1.4.4(@swc/helpers@0.5.3)(rollup@4.21.0)(vite@5.4.3(@types/node@22.5.0)(sass-embedded@1.77.8)(terser@5.31.6)) examples/rust-vite/rust-host: dependencies: @@ -2400,15 +2420,27 @@ packages: '@vue/compiler-core@3.4.38': resolution: {integrity: sha512-8IQOTCWnLFqfHzOGm9+P8OPSEDukgg3Huc92qSG49if/xI2SAwLHQO2qaPQbjCWPBcQoO1WYfXfTACUrWV3c5A==} + '@vue/compiler-core@3.5.3': + resolution: {integrity: sha512-adAfy9boPkP233NTyvLbGEqVuIfK/R0ZsBsIOW4BZNfb4BRpRW41Do1u+ozJpsb+mdoy80O20IzAsHaihRb5qA==} + '@vue/compiler-dom@3.4.38': resolution: {integrity: sha512-Osc/c7ABsHXTsETLgykcOwIxFktHfGSUDkb05V61rocEfsFDcjDLH/IHJSNJP+/Sv9KeN2Lx1V6McZzlSb9EhQ==} + '@vue/compiler-dom@3.5.3': + resolution: {integrity: sha512-wnzFArg9zpvk/811CDOZOadJRugf1Bgl/TQ3RfV4nKfSPok4hi0w10ziYUQR6LnnBAUlEXYLUfZ71Oj9ds/+QA==} + '@vue/compiler-sfc@3.4.38': resolution: {integrity: sha512-s5QfZ+9PzPh3T5H4hsQDJtI8x7zdJaew/dCGgqZ2630XdzaZ3AD8xGZfBqpT8oaD/p2eedd+pL8tD5vvt5ZYJQ==} + '@vue/compiler-sfc@3.5.3': + resolution: {integrity: sha512-P3uATLny2tfyvMB04OQFe7Sczteno7SLFxwrOA/dw01pBWQHB5HL15a8PosoNX2aG/EAMGqnXTu+1LnmzFhpTQ==} + '@vue/compiler-ssr@3.4.38': resolution: {integrity: sha512-YXznKFQ8dxYpAz9zLuVvfcXhc31FSPFDcqr0kyujbOwNhlmaNvL2QfIy+RZeJgSn5Fk54CWoEUeW+NVBAogGaw==} + '@vue/compiler-ssr@3.5.3': + resolution: {integrity: sha512-F/5f+r2WzL/2YAPl7UlKcJWHrvoZN8XwEBLnT7S4BXwncH25iDOabhO2M2DWioyTguJAGavDOawejkFXj8EM1w==} + '@vue/devtools-api@6.6.3': resolution: {integrity: sha512-0MiMsFma/HqA6g3KLKn+AGpL1kgKhFWszC9U29NfpWK5LE7bjeXxySWJrOJ77hBz+TBrBQ7o4QJqbPbqbs8rJw==} @@ -2424,20 +2456,37 @@ packages: '@vue/reactivity@3.4.38': resolution: {integrity: sha512-4vl4wMMVniLsSYYeldAKzbk72+D3hUnkw9z8lDeJacTxAkXeDAP1uE9xr2+aKIN0ipOL8EG2GPouVTH6yF7Gnw==} + '@vue/reactivity@3.5.3': + resolution: {integrity: sha512-2w61UnRWTP7+rj1H/j6FH706gRBHdFVpIqEkSDAyIpafBXYH8xt4gttstbbCWdU3OlcSWO8/3mbKl/93/HSMpw==} + '@vue/runtime-core@3.4.38': resolution: {integrity: sha512-21z3wA99EABtuf+O3IhdxP0iHgkBs1vuoCAsCKLVJPEjpVqvblwBnTj42vzHRlWDCyxu9ptDm7sI2ZMcWrQqlA==} + '@vue/runtime-core@3.5.3': + resolution: {integrity: sha512-5b2AQw5OZlmCzSsSBWYoZOsy75N4UdMWenTfDdI5bAzXnuVR7iR8Q4AOzQm2OGoA41xjk53VQKrqQhOz2ktWaw==} + '@vue/runtime-dom@3.4.38': resolution: {integrity: sha512-afZzmUreU7vKwKsV17H1NDThEEmdYI+GCAK/KY1U957Ig2NATPVjCROv61R19fjZNzMmiU03n79OMnXyJVN0UA==} + '@vue/runtime-dom@3.5.3': + resolution: {integrity: sha512-wPR1DEGc3XnQ7yHbmkTt3GoY0cEnVGQnARRdAkDzZ8MbUKEs26gogCQo6AOvvgahfjIcnvWJzkZArQ1fmWjcSg==} + '@vue/server-renderer@3.4.38': resolution: {integrity: sha512-NggOTr82FbPEkkUvBm4fTGcwUY8UuTsnWC/L2YZBmvaQ4C4Jl/Ao4HHTB+l7WnFCt5M/dN3l0XLuyjzswGYVCA==} peerDependencies: vue: 3.4.38 + '@vue/server-renderer@3.5.3': + resolution: {integrity: sha512-28volmaZVG2PGO3V3+gBPKoSHvLlE8FGfG/GKXKkjjfxLuj/50B/0OQGakM/g6ehQeqCrZYM4eHC4Ks48eig1Q==} + peerDependencies: + vue: 3.5.3 + '@vue/shared@3.4.38': resolution: {integrity: sha512-q0xCiLkuWWQLzVrecPb0RMsNWyxICOjPrcrwxTUEHb1fsnvni4dcuyG7RT/Ie7VPTvnjzIaWzRMUBsrqNj/hhw==} + '@vue/shared@3.5.3': + resolution: {integrity: sha512-Jp2v8nylKBT+PlOUjun2Wp/f++TfJVFjshLzNtJDdmFJabJa7noGMncqXRM1vXGX+Yo2V7WykQFNxusSim8SCA==} + abbrev@1.1.1: resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==} @@ -6276,6 +6325,14 @@ packages: typescript: optional: true + vue@3.5.3: + resolution: {integrity: sha512-xvRbd0HpuLovYbOHXRHlSBsSvmUJbo0pzbkKTApWnQGf3/cu5Z39mQeA5cZdLRVIoNf3zI6MSoOgHUT5i2jO+Q==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} @@ -8169,12 +8226,12 @@ snapshots: - rollup - supports-color - '@nuxt/vite-builder@3.13.0(@types/node@22.5.0)(magicast@0.3.5)(rollup@4.21.0)(sass-embedded@1.77.8)(terser@5.31.6)(typescript@5.5.3)(vue@3.4.38(typescript@5.5.3))': + '@nuxt/vite-builder@3.13.0(@types/node@22.5.0)(magicast@0.3.5)(rollup@4.21.0)(sass-embedded@1.77.8)(terser@5.31.6)(typescript@5.5.3)(vue@3.5.3(typescript@5.5.3))': dependencies: '@nuxt/kit': 3.13.0(magicast@0.3.5)(rollup@4.21.0) '@rollup/plugin-replace': 5.0.7(rollup@4.21.0) - '@vitejs/plugin-vue': 5.1.3(vite@5.4.3(@types/node@22.5.0)(sass-embedded@1.77.8)(terser@5.31.6))(vue@3.4.38(typescript@5.5.3)) - '@vitejs/plugin-vue-jsx': 4.0.1(vite@5.4.3(@types/node@22.5.0)(sass-embedded@1.77.8)(terser@5.31.6))(vue@3.4.38(typescript@5.5.3)) + '@vitejs/plugin-vue': 5.1.3(vite@5.4.3(@types/node@22.5.0)(sass-embedded@1.77.8)(terser@5.31.6))(vue@3.5.3(typescript@5.5.3)) + '@vitejs/plugin-vue-jsx': 4.0.1(vite@5.4.3(@types/node@22.5.0)(sass-embedded@1.77.8)(terser@5.31.6))(vue@3.5.3(typescript@5.5.3)) autoprefixer: 10.4.20(postcss@8.4.41) clear: 0.1.0 consola: 3.2.3 @@ -8203,7 +8260,7 @@ snapshots: vite: 5.4.3(@types/node@22.5.0)(sass-embedded@1.77.8)(terser@5.31.6) vite-node: 2.0.5(@types/node@22.5.0)(sass-embedded@1.77.8)(terser@5.31.6) vite-plugin-checker: 0.7.2(typescript@5.5.3)(vite@5.4.3(@types/node@22.5.0)(sass-embedded@1.77.8)(terser@5.31.6)) - vue: 3.4.38(typescript@5.5.3) + vue: 3.5.3(typescript@5.5.3) vue-bundle-renderer: 2.1.0 transitivePeerDependencies: - '@biomejs/biome' @@ -8818,13 +8875,13 @@ snapshots: '@unhead/schema': 1.10.0 '@unhead/shared': 1.10.0 - '@unhead/vue@1.10.0(vue@3.4.38(typescript@5.5.3))': + '@unhead/vue@1.10.0(vue@3.5.3(typescript@5.5.3))': dependencies: '@unhead/schema': 1.10.0 '@unhead/shared': 1.10.0 hookable: 5.5.3 unhead: 1.10.0 - vue: 3.4.38(typescript@5.5.3) + vue: 3.5.3(typescript@5.5.3) '@vercel/nft@0.26.5': dependencies: @@ -8855,22 +8912,22 @@ snapshots: transitivePeerDependencies: - supports-color - '@vitejs/plugin-vue-jsx@4.0.1(vite@5.4.3(@types/node@22.5.0)(sass-embedded@1.77.8)(terser@5.31.6))(vue@3.4.38(typescript@5.5.3))': + '@vitejs/plugin-vue-jsx@4.0.1(vite@5.4.3(@types/node@22.5.0)(sass-embedded@1.77.8)(terser@5.31.6))(vue@3.5.3(typescript@5.5.3))': dependencies: '@babel/core': 7.25.2 '@babel/plugin-transform-typescript': 7.25.2(@babel/core@7.25.2) '@vue/babel-plugin-jsx': 1.2.2(@babel/core@7.25.2) vite: 5.4.3(@types/node@22.5.0)(sass-embedded@1.77.8)(terser@5.31.6) - vue: 3.4.38(typescript@5.5.3) + vue: 3.5.3(typescript@5.5.3) transitivePeerDependencies: - supports-color - '@vitejs/plugin-vue@5.1.3(vite@5.4.3(@types/node@22.5.0)(sass-embedded@1.77.8)(terser@5.31.6))(vue@3.4.38(typescript@5.5.3))': + '@vitejs/plugin-vue@5.1.3(vite@5.4.3(@types/node@22.5.0)(sass-embedded@1.77.8)(terser@5.31.6))(vue@3.5.3(typescript@5.5.3))': dependencies: vite: 5.4.3(@types/node@22.5.0)(sass-embedded@1.77.8)(terser@5.31.6) - vue: 3.4.38(typescript@5.5.3) + vue: 3.5.3(typescript@5.5.3) - '@vue-macros/common@1.12.2(rollup@4.21.0)(vue@3.4.38(typescript@5.5.3))': + '@vue-macros/common@1.12.2(rollup@4.21.0)(vue@3.5.3(typescript@5.5.3))': dependencies: '@babel/types': 7.25.4 '@rollup/pluginutils': 5.1.0(rollup@4.21.0) @@ -8879,7 +8936,7 @@ snapshots: local-pkg: 0.5.0 magic-string-ast: 0.6.2 optionalDependencies: - vue: 3.4.38(typescript@5.5.3) + vue: 3.5.3(typescript@5.5.3) transitivePeerDependencies: - rollup @@ -8920,11 +8977,24 @@ snapshots: estree-walker: 2.0.2 source-map-js: 1.2.0 + '@vue/compiler-core@3.5.3': + dependencies: + '@babel/parser': 7.25.4 + '@vue/shared': 3.5.3 + entities: 4.5.0 + estree-walker: 2.0.2 + source-map-js: 1.2.0 + '@vue/compiler-dom@3.4.38': dependencies: '@vue/compiler-core': 3.4.38 '@vue/shared': 3.4.38 + '@vue/compiler-dom@3.5.3': + dependencies: + '@vue/compiler-core': 3.5.3 + '@vue/shared': 3.5.3 + '@vue/compiler-sfc@3.4.38': dependencies: '@babel/parser': 7.25.4 @@ -8937,11 +9007,28 @@ snapshots: postcss: 8.4.41 source-map-js: 1.2.0 + '@vue/compiler-sfc@3.5.3': + dependencies: + '@babel/parser': 7.25.4 + '@vue/compiler-core': 3.5.3 + '@vue/compiler-dom': 3.5.3 + '@vue/compiler-ssr': 3.5.3 + '@vue/shared': 3.5.3 + estree-walker: 2.0.2 + magic-string: 0.30.11 + postcss: 8.4.44 + source-map-js: 1.2.0 + '@vue/compiler-ssr@3.4.38': dependencies: '@vue/compiler-dom': 3.4.38 '@vue/shared': 3.4.38 + '@vue/compiler-ssr@3.5.3': + dependencies: + '@vue/compiler-dom': 3.5.3 + '@vue/shared': 3.5.3 + '@vue/devtools-api@6.6.3': {} '@vue/devtools-core@7.3.3(vite@5.4.3(@types/node@22.5.0)(sass-embedded@1.77.8)(terser@5.31.6))': @@ -8973,11 +9060,20 @@ snapshots: dependencies: '@vue/shared': 3.4.38 + '@vue/reactivity@3.5.3': + dependencies: + '@vue/shared': 3.5.3 + '@vue/runtime-core@3.4.38': dependencies: '@vue/reactivity': 3.4.38 '@vue/shared': 3.4.38 + '@vue/runtime-core@3.5.3': + dependencies: + '@vue/reactivity': 3.5.3 + '@vue/shared': 3.5.3 + '@vue/runtime-dom@3.4.38': dependencies: '@vue/reactivity': 3.4.38 @@ -8985,14 +9081,29 @@ snapshots: '@vue/shared': 3.4.38 csstype: 3.1.3 + '@vue/runtime-dom@3.5.3': + dependencies: + '@vue/reactivity': 3.5.3 + '@vue/runtime-core': 3.5.3 + '@vue/shared': 3.5.3 + csstype: 3.1.3 + '@vue/server-renderer@3.4.38(vue@3.4.38(typescript@5.5.3))': dependencies: '@vue/compiler-ssr': 3.4.38 '@vue/shared': 3.4.38 vue: 3.4.38(typescript@5.5.3) + '@vue/server-renderer@3.5.3(vue@3.5.3(typescript@5.5.3))': + dependencies: + '@vue/compiler-ssr': 3.5.3 + '@vue/shared': 3.5.3 + vue: 3.5.3(typescript@5.5.3) + '@vue/shared@3.4.38': {} + '@vue/shared@3.5.3': {} + abbrev@1.1.1: {} abort-controller@3.0.0: @@ -11230,10 +11341,10 @@ snapshots: '@nuxt/kit': 3.13.0(magicast@0.3.5)(rollup@4.21.0) '@nuxt/schema': 3.13.0(rollup@4.21.0) '@nuxt/telemetry': 2.5.4(magicast@0.3.5)(rollup@4.21.0) - '@nuxt/vite-builder': 3.13.0(@types/node@22.5.0)(magicast@0.3.5)(rollup@4.21.0)(sass-embedded@1.77.8)(terser@5.31.6)(typescript@5.5.3)(vue@3.4.38(typescript@5.5.3)) + '@nuxt/vite-builder': 3.13.0(@types/node@22.5.0)(magicast@0.3.5)(rollup@4.21.0)(sass-embedded@1.77.8)(terser@5.31.6)(typescript@5.5.3)(vue@3.5.3(typescript@5.5.3)) '@unhead/dom': 1.10.0 '@unhead/ssr': 1.10.0 - '@unhead/vue': 1.10.0(vue@3.4.38(typescript@5.5.3)) + '@unhead/vue': 1.10.0(vue@3.5.3(typescript@5.5.3)) '@vue/shared': 3.4.38 acorn: 8.12.1 c12: 1.11.1(magicast@0.3.5) @@ -11277,13 +11388,13 @@ snapshots: unenv: 1.10.0 unimport: 3.11.1(rollup@4.21.0) unplugin: 1.12.3 - unplugin-vue-router: 0.10.7(rollup@4.21.0)(vue-router@4.4.3(vue@3.4.38(typescript@5.5.3)))(vue@3.4.38(typescript@5.5.3)) + unplugin-vue-router: 0.10.7(rollup@4.21.0)(vue-router@4.4.3(vue@3.5.3(typescript@5.5.3)))(vue@3.5.3(typescript@5.5.3)) unstorage: 1.10.2(ioredis@5.4.1) untyped: 1.4.2 - vue: 3.4.38(typescript@5.5.3) + vue: 3.5.3(typescript@5.5.3) vue-bundle-renderer: 2.1.0 vue-devtools-stub: 0.1.0 - vue-router: 4.4.3(vue@3.4.38(typescript@5.5.3)) + vue-router: 4.4.3(vue@3.5.3(typescript@5.5.3)) optionalDependencies: '@parcel/watcher': 2.4.1 '@types/node': 22.5.0 @@ -13129,11 +13240,11 @@ snapshots: universalify@2.0.1: {} - unplugin-vue-router@0.10.7(rollup@4.21.0)(vue-router@4.4.3(vue@3.4.38(typescript@5.5.3)))(vue@3.4.38(typescript@5.5.3)): + unplugin-vue-router@0.10.7(rollup@4.21.0)(vue-router@4.4.3(vue@3.5.3(typescript@5.5.3)))(vue@3.5.3(typescript@5.5.3)): dependencies: '@babel/types': 7.25.4 '@rollup/pluginutils': 5.1.0(rollup@4.21.0) - '@vue-macros/common': 1.12.2(rollup@4.21.0)(vue@3.4.38(typescript@5.5.3)) + '@vue-macros/common': 1.12.2(rollup@4.21.0)(vue@3.5.3(typescript@5.5.3)) ast-walker-scope: 0.6.2 chokidar: 3.6.0 fast-glob: 3.3.2 @@ -13146,7 +13257,7 @@ snapshots: unplugin: 1.12.3 yaml: 2.5.0 optionalDependencies: - vue-router: 4.4.3(vue@3.4.38(typescript@5.5.3)) + vue-router: 4.4.3(vue@3.5.3(typescript@5.5.3)) transitivePeerDependencies: - rollup - vue @@ -13291,6 +13402,16 @@ snapshots: - '@swc/helpers' - rollup + vite-plugin-top-level-await@1.4.4(@swc/helpers@0.5.3)(rollup@4.21.0)(vite@5.4.3(@types/node@22.5.0)(sass-embedded@1.77.8)(terser@5.31.6)): + dependencies: + '@rollup/plugin-virtual': 3.0.2(rollup@4.21.0) + '@swc/core': 1.7.14(@swc/helpers@0.5.3) + uuid: 10.0.0 + vite: 5.4.3(@types/node@22.5.0)(sass-embedded@1.77.8)(terser@5.31.6) + transitivePeerDependencies: + - '@swc/helpers' + - rollup + vite-plugin-vue-inspector@5.2.0(vite@5.4.3(@types/node@22.5.0)(sass-embedded@1.77.8)(terser@5.31.6)): dependencies: '@babel/core': 7.25.2 @@ -13362,6 +13483,11 @@ snapshots: '@vue/devtools-api': 6.6.3 vue: 3.4.38(typescript@5.5.3) + vue-router@4.4.3(vue@3.5.3(typescript@5.5.3)): + dependencies: + '@vue/devtools-api': 6.6.3 + vue: 3.5.3(typescript@5.5.3) + vue@3.4.38(typescript@5.5.3): dependencies: '@vue/compiler-dom': 3.4.38 @@ -13372,6 +13498,16 @@ snapshots: optionalDependencies: typescript: 5.5.3 + vue@3.5.3(typescript@5.5.3): + dependencies: + '@vue/compiler-dom': 3.5.3 + '@vue/compiler-sfc': 3.5.3 + '@vue/runtime-dom': 3.5.3 + '@vue/server-renderer': 3.5.3(vue@3.5.3(typescript@5.5.3)) + '@vue/shared': 3.5.3 + optionalDependencies: + typescript: 5.5.3 + webidl-conversions@3.0.1: {} webpack-sources@3.2.3: {} diff --git a/src/index.ts b/src/index.ts index bdaa45a..14377a1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,7 @@ import { Plugin } from 'vite'; import addEntry from './plugins/pluginAddEntry'; +import { PluginDevProxyModuleTopLevelAwait } from "./plugins/pluginDevProxyModuleTopLevelAwait"; +import pluginManifest from "./plugins/pluginMFManifest"; import pluginModuleParseEnd from './plugins/pluginModuleParseEnd'; import pluginProxyRemoteEntry from './plugins/pluginProxyRemoteEntry'; import pluginProxyRemotes from './plugins/pluginProxyRemotes'; @@ -38,21 +40,21 @@ function federation(mfUserOptions: ModuleFederationOptions): Plugin[] { ...proxySharedModule({ shared, }), + PluginDevProxyModuleTopLevelAwait(), { name: 'module-federation-vite', enforce: 'post', config(config, { command: _command }: { command: string }) { + // TODO: singleton ; (config.resolve as any).alias.push({ find: '@module-federation/runtime', replacement: require.resolve('@module-federation/runtime'), },) config.optimizeDeps?.include?.push('@module-federation/runtime'); - // Object.keys(shared).forEach((key) => { - // config.optimizeDeps?.include?.push(key); - // }); }, }, + ...pluginManifest(), ]; } diff --git a/src/plugins/pluginAddEntry.ts b/src/plugins/pluginAddEntry.ts index 433844c..1592ea3 100644 --- a/src/plugins/pluginAddEntry.ts +++ b/src/plugins/pluginAddEntry.ts @@ -12,11 +12,15 @@ const addEntry = ({ entryName, entryPath, fileName }: AddEntryOptions): Plugin[] const devEntryPath = entryPath.startsWith("virtual:mf") ? "/@id/" + entryPath : entryPath let entryFiles: string[] = []; let htmlFilePath: string; + let _command: string return [ { name: 'add-entry', apply: "serve", + config(config, { command }) { + _command = command + }, configureServer(server) { server.httpServer?.once?.('listening', () => { const { port } = server.config.server; @@ -45,7 +49,6 @@ const addEntry = ({ entryName, entryPath, fileName }: AddEntryOptions): Plugin[] { name: "add-entry", enforce: "post", - apply: "build", configResolved(config) { const inputOptions = config.build.rollupOptions.input; @@ -63,6 +66,7 @@ const addEntry = ({ entryName, entryPath, fileName }: AddEntryOptions): Plugin[] } }, buildStart() { + if (_command === "serve") return const hasHash = fileName?.includes?.("[hash") this.emitFile({ name: entryName, diff --git a/src/plugins/pluginDevProxyModuleTopLevelAwait.ts b/src/plugins/pluginDevProxyModuleTopLevelAwait.ts new file mode 100644 index 0000000..4ebc7b3 --- /dev/null +++ b/src/plugins/pluginDevProxyModuleTopLevelAwait.ts @@ -0,0 +1,88 @@ +/** + * Solve the problem that dev mode dependency prebunding does not support top-level await syntax + */ +import { createFilter } from "@rollup/pluginutils"; +import { walk } from 'estree-walker'; +import MagicString from "magic-string"; +import { Plugin } from "vite"; + +export function PluginDevProxyModuleTopLevelAwait(): Plugin { + const filterFunction = createFilter(); + return { + name: "dev-proxy-module-top-level-await", + apply: "serve", + transform(code: string, id: string): { code: string; map: any } | null { + if (!(code.includes("/*mf top-level-await placeholder replacement mf*/"))) { + return null + } + if (!filterFunction(id)) return null; + let ast: any; + try { + ast = (this as any).parse(code, { + allowReturnOutsideFunction: true, + }); + } catch (e) { + throw new Error(`${id}: ${e}`); + } + + const magicString = new MagicString(code); + + walk(ast, { + enter(node: any) { + if (node.type === 'ExportNamedDeclaration' && node.specifiers) { + const exportSpecifiers = node.specifiers.map((specifier: any) => specifier.exported.name); + const proxyStatements = exportSpecifiers.map((name: string) => ` + const __mfproxy__await${name} = await ${name}(); + const __mfproxy__${name} = () => __mfproxy__await${name}; + `).join('\n'); + const exportStatements = exportSpecifiers.map((name: string) => `__mfproxy__${name} as ${name}`).join(', '); + + const start = node.start; + const end = node.end; + const replacement = `${proxyStatements}\nexport { ${exportStatements} };`; + + magicString.overwrite(start, end, replacement); + } + + if (node.type === 'ExportDefaultDeclaration') { + const declaration = node.declaration; + const start = node.start; + const end = node.end; + + let proxyStatement; + let exportStatement = 'default'; + + if (declaration.type === 'Identifier') { + // example: export default foo; + proxyStatement = ` + const __mfproxy__awaitdefault = await ${declaration.name}(); + const __mfproxy__default = __mfproxy__awaitdefault; + `; + } else if (declaration.type === 'CallExpression' || declaration.type === 'FunctionDeclaration') { + // example: export default someFunction(); + const declarationCode = code.slice(declaration.start, declaration.end); + proxyStatement = ` + const __mfproxy__awaitdefault = await (${declarationCode}); + const __mfproxy__default = __mfproxy__awaitdefault; + `; + } else { + // other + proxyStatement = ` + const __mfproxy__awaitdefault = await (${code.slice(declaration.start, declaration.end)}); + const __mfproxy__default = __mfproxy__awaitdefault; + `; + } + + const replacement = `${proxyStatement}\nexport { __mfproxy__default as ${exportStatement} };`; + + magicString.overwrite(start, end, replacement); + } + } + }); + return { + code: magicString.toString(), + map: magicString.generateMap({ hires: true }), + }; + }, + } +} \ No newline at end of file diff --git a/src/plugins/pluginMFManifest.ts b/src/plugins/pluginMFManifest.ts new file mode 100644 index 0000000..29f222d --- /dev/null +++ b/src/plugins/pluginMFManifest.ts @@ -0,0 +1,357 @@ +import { join, relative } from 'pathe'; +import { Manifest, Plugin } from 'vite'; +import { + getNormalizeModuleFederationOptions, + getNormalizeShareItem +} from '../utils/normalizeModuleFederationOptions'; +import { getPreBuildLibImportId, getUsedRemotesMap, getUsedShares } from '../virtualModules'; + +const Manifest = (): Plugin[] => { + const mfOptions = getNormalizeModuleFederationOptions() + const { name, filename, manifest: manifestOptions } = mfOptions + let mfManifestName: string = "" + if (manifestOptions === true) { + mfManifestName = "mf-manifest.json" + } + if (typeof manifestOptions !== "boolean") { + mfManifestName = join(manifestOptions?.filePath || "", manifestOptions?.fileName || "") + } + let extensions: string[] + let root: string + type PreloadMap = Record; // 保存模块和文件的映射关系 + let remoteEntryFile: string + return [ + { + name: 'moddule-federation-manifest', + apply: 'serve', + configureServer(server) { + server.middlewares.use((req, res, next) => { + if (!mfManifestName) { + next() + return + } + if (req.url === mfManifestName.replace(/^\/?/, "/")) { + res.setHeader('Content-Type', 'application/json'); + res.setHeader('Access-Control-Allow-Origin', '*'); + res.end( + JSON.stringify({ + id: name, + name: name, + metaData: { + name: name, + type: 'app', + buildInfo: { buildVersion: '1.0.0', buildName: name }, + remoteEntry: { + name: filename, + path: '', + type: 'module', + }, + ssrRemoteEntry: { + name: filename, + path: '', + type: 'module', + }, + types: { path: '', name: '' }, + globalName: name, + pluginVersion: '0.2.5', + publicPath: 'auto', + }, + shared: Array.from(getUsedShares()).map(shareKey => { + const shareItem = getNormalizeShareItem(shareKey); + + return { + id: `${name}:${shareKey}`, + name: shareKey, + version: shareItem.version, + requiredVersion: shareItem.shareConfig.requiredVersion, + assets: { + js: { + async: [], + sync: [], + }, + css: { + async: [], + sync: [], + }, + }, + }; + }), + remotes: (function () { + const remotes = [] as any + const usedRemotesMap = getUsedRemotesMap(); + Object.keys(usedRemotesMap).forEach((remoteKey) => { + const usedModules = Array.from(usedRemotesMap[remoteKey]); + usedModules.forEach((moduleKey) => { + remotes.push({ + federationContainerName: mfOptions.remotes[remoteKey].entry, + moduleName: moduleKey.replace(remoteKey, '').replace('/', ''), + alias: remoteKey, + entry: '*', + }); + }); + }); + return remotes + })(), + exposes: Object.keys(mfOptions.exposes).map(key => { + const formatKey = key.replace('./', ''); + return { + id: name + ':' + formatKey, + name: formatKey, + assets: { + js: { + async: [], + sync: [], + }, + css: { + sync: [], + async: [], + }, + }, + path: key, + }; + }) + }) + ); + } else { + next(); + } + }); + }, + }, + { + name: 'moddule-federation-manifest', + enforce: 'post', + config(config) { + if (!config.build) config.build = {} + if (!config.build.manifest) config.build.manifest = config.build.manifest || !!manifestOptions + }, + configResolved(config) { + root = config.root + extensions = config.resolve.extensions || ['.mjs', '.js', '.mts', '.ts', '.jsx', '.tsx', '.json'] + }, + async generateBundle(options, bundle) { + if (!mfManifestName) return + + const exposesModules = Object.keys(mfOptions.exposes).map(item => mfOptions.exposes[item].import); // 获取你提供的 moduleIds + const filesContainingModules: PreloadMap = {} + // 帮助函数:检查模块路径是否匹配 + const isModuleMatched = (relativeModulePath: string, preloadModule: string) => { + // 先尝试直接匹配 + if (relativeModulePath === preloadModule) return true; + // 如果 preloadModule 没有后缀,尝试添加可能的后缀进行匹配 + for (const ext of extensions) { + if (relativeModulePath === `${preloadModule}${ext}`) { + return true; + } + } + return false; + }; + + // 遍历打包生成的每个文件 + for (const [fileName, fileData] of Object.entries(bundle)) { + if (mfOptions.filename.replace(/[\[\]]/g, "_") === fileData.name) { + remoteEntryFile = fileData.fileName + } + if (fileData.type === 'chunk') { + // 遍历该文件的所有模块 + for (const modulePath of Object.keys(fileData.modules)) { + + // 将绝对路径转换为相对于 Vite root 的相对路径 + const relativeModulePath = relative(root, modulePath); + + // 检查模块是否在 preloadModules 列表中 + for (const preloadModule of exposesModules) { + const formatPreloadModule = preloadModule.replace("./", "") + if (isModuleMatched(relativeModulePath, formatPreloadModule)) { + if (!filesContainingModules[preloadModule]) { + filesContainingModules[preloadModule] = { + sync: [], + async: [] + }; + } + console.log(Object.keys(fileData.modules)) + filesContainingModules[preloadModule].sync.push(fileName); + filesContainingModules[preloadModule].async.push(...fileData.dynamicImports || []); + findSynchronousImports(fileName, filesContainingModules[preloadModule].sync) + break; // 如果找到匹配,跳出循环 + } + } + } + } + } + // 递归查找模块的同步导入文件 + function findSynchronousImports(fileName: string, array: string[]) { + const fileData = bundle[fileName]; + if (fileData && fileData.type === 'chunk') { + array.push(fileName); // 将当前文件加入预加载列表 + + // 遍历该文件的同步导入文件 + fileData.imports.forEach((importedFile) => { + if (array.indexOf(importedFile) === -1) { + findSynchronousImports(importedFile, array); // 递归查找同步导入的文件 + } + }); + } + }; + const fileToShareKey: Record = {} + await Promise.all(Array.from(getUsedShares()).map(async shareKey => { + const file = ((await (this as any).resolve(getPreBuildLibImportId(shareKey))).id).split("?")[0] + fileToShareKey[file] = shareKey + })) + + // 遍历打包生成的每个文件 + for (const [fileName, fileData] of Object.entries(bundle)) { + if (fileData.type === 'chunk') { + // 遍历该文件的所有模块 + for (const modulePath of Object.keys(fileData.modules)) { + const sharedKey = fileToShareKey[modulePath] + if (sharedKey) { + if (!filesContainingModules[sharedKey]) { + filesContainingModules[sharedKey] = { + sync: [], + async: [] + }; + } + filesContainingModules[sharedKey].sync.push(fileName); + filesContainingModules[sharedKey].async.push(...fileData.dynamicImports || []); + findSynchronousImports(fileName, filesContainingModules[sharedKey].sync) + break; // 如果找到匹配,跳出循环 + } + } + } + } + Object.keys(filesContainingModules).forEach(key => { + filesContainingModules[key].sync = Array.from(new Set(filesContainingModules[key].sync)) + filesContainingModules[key].async = Array.from(new Set(filesContainingModules[key].async)) + }) + this.emitFile({ + type: 'asset', + fileName: mfManifestName, + source: generateMFManifest(filesContainingModules), + }); + } + }, + ]; + function generateMFManifest(preloadMap: PreloadMap) { + const options = getNormalizeModuleFederationOptions(); + const { name } = options; + const remoteEntry = { + name: remoteEntryFile, + path: '', + type: 'module', + }; + const remotes: { + federationContainerName: string; + moduleName: string; + alias: string; + entry: string; + }[] = []; + const usedRemotesMap = getUsedRemotesMap(); + Object.keys(usedRemotesMap).forEach((remoteKey) => { + const usedModules = Array.from(usedRemotesMap[remoteKey]); + usedModules.forEach((moduleKey) => { + remotes.push({ + federationContainerName: options.remotes[remoteKey].entry, + moduleName: moduleKey.replace(remoteKey, '').replace('/', ''), + alias: remoteKey, + entry: '*', + }); + }); + }); + type ManifestItem = { + id: string; + name: string; + version: string; + requiredVersion: string; + assets: { + js: { + async: string[]; + sync: string[]; + }; + css: { + async: string[]; + sync: string[]; + }; + }; + }; + // @ts-ignore + const shared: ManifestItem[] = Array.from(getUsedShares()).map((shareKey) => { + // assets(.css, .jpg, .svg等)其他资源, 不重要, 暂未处理 + if (!preloadMap[shareKey]) return + const shareItem = getNormalizeShareItem(shareKey); + + return { + id: `${name}:${shareKey}`, + name: shareKey, + version: shareItem.version, + requiredVersion: shareItem.shareConfig.requiredVersion, + assets: { + js: { + async: preloadMap[shareKey].async, + sync: preloadMap[shareKey].sync, + }, + css: { + async: [], + sync: [], + }, + }, + }; + }).filter(item => item) + const exposes = Object.keys(options.exposes).map((key) => { + // assets(.css, .jpg, .svg等)其他资源, 不重要, 暂未处理 + const formatKey = key.replace('./', ''); + const sourceFile = options.exposes[key].import + if (!preloadMap[sourceFile]) return + return { + id: name + ':' + formatKey, + name: formatKey, + assets: { + js: { + async: preloadMap[sourceFile].async, + sync: preloadMap[sourceFile].sync, + }, + css: { + sync: [], + async: [], + }, + }, + path: key, + }; + }) + .filter((item) => item); // Filter out any null values + + const result = { + id: name, + name: name, + metaData: { + name: name, + type: 'app', + buildInfo: { + buildVersion: '1.0.0', + buildName: name, + }, + remoteEntry, + ssrRemoteEntry: remoteEntry, + types: { + path: '', + name: '', + // "zip": "@mf-types.zip", + // "api": "@mf-types.d.ts" + }, + globalName: name, + pluginVersion: '0.2.5', + publicPath: 'auto', + }, + shared, + remotes, + exposes, + }; + return JSON.stringify(result); + } + +}; + +export default Manifest; diff --git a/src/plugins/pluginProxyRemoteEntry.ts b/src/plugins/pluginProxyRemoteEntry.ts index 949710c..6cb3308 100644 --- a/src/plugins/pluginProxyRemoteEntry.ts +++ b/src/plugins/pluginProxyRemoteEntry.ts @@ -2,6 +2,7 @@ import { createFilter } from '@rollup/pluginutils'; import { Plugin } from 'vite'; import { getNormalizeModuleFederationOptions } from '../utils/normalizeModuleFederationOptions'; import { generateRemoteEntry, REMOTE_ENTRY_ID } from '../virtualModules'; +import { parsePromise } from './pluginModuleParseEnd'; const filter: (id: string) => boolean = createFilter(); @@ -16,13 +17,13 @@ export default function (): Plugin { }, load(id: string) { if (id === REMOTE_ENTRY_ID) { - return generateRemoteEntry(getNormalizeModuleFederationOptions()); + return parsePromise.then(_ => generateRemoteEntry(getNormalizeModuleFederationOptions())) } }, async transform(code: string, id: string) { if (!filter(id)) return; if (id.includes(REMOTE_ENTRY_ID)) { - return generateRemoteEntry(getNormalizeModuleFederationOptions()); + return parsePromise.then(_ => generateRemoteEntry(getNormalizeModuleFederationOptions())) } }, } diff --git a/src/plugins/pluginProxyRemotes.ts b/src/plugins/pluginProxyRemotes.ts index ca74069..3711022 100644 --- a/src/plugins/pluginProxyRemotes.ts +++ b/src/plugins/pluginProxyRemotes.ts @@ -1,13 +1,12 @@ import { createFilter } from "@rollup/pluginutils"; import { Plugin } from "vite"; import { NormalizedModuleFederationOptions } from "../utils/normalizeModuleFederationOptions"; -import { generateRemotes, remoteVirtualModule } from "../virtualModules"; +import { addUsedRemote, getRemoteVirtualModule } from "../virtualModules"; const filter: (id: string) => boolean = createFilter(); export default function (options: NormalizedModuleFederationOptions): Plugin { let command: string const { remotes } = options - const matchRemotesList = Object.keys(remotes).map(item => item.replace(/\//g, "_")); return { name: "proxyRemotes", config(config, { command: _command }) { @@ -15,40 +14,15 @@ export default function (options: NormalizedModuleFederationOptions): Plugin { Object.keys(remotes).forEach((key) => { const remote = remotes[key]; ; (config.resolve as any).alias.push({ - find: new RegExp(`(${remote.name}(\/.*|$)?)`), - replacement: '$1', + find: new RegExp(`^(${remote.name}(\/.*|$))`), + replacement: "$1", customResolver(source: string) { - const requestPath = remoteVirtualModule.getImportId() + '?__moduleRemote__=' + encodeURIComponent(source) - if (!config.optimizeDeps) config.optimizeDeps = {}; - if (!config.optimizeDeps.needsInterop) config.optimizeDeps.needsInterop = []; - if (config.optimizeDeps.needsInterop.indexOf(requestPath) === -1) - config.optimizeDeps.needsInterop.push(requestPath); - return this.resolve( - requestPath - ); + const remoteModule = getRemoteVirtualModule(source, _command) + addUsedRemote(remote.name, source) + return remoteModule.getPath() }, }); }); }, - async transform(code: string, id: string) { - if (!filter(id)) return; - let [devRemoteModuleName] = - (matchRemotesList.length && - id.match(new RegExp(`\/(${matchRemotesList.join('|')})(\_.*\.js|\.js)`))) || - []; - if (devRemoteModuleName) { - return generateRemotes( - devRemoteModuleName.replace('/', '').replace(/_/g, '/').replace('.js', ''), - command - ); - } - let [prodRemoteName] = id.match(/\_\_moduleRemote\_\_=[^&]+/) || []; - if (prodRemoteName) { - return generateRemotes( - decodeURIComponent(prodRemoteName.replace('__moduleRemote__=', '')), - command - ); - } - }, } } \ No newline at end of file diff --git a/src/plugins/pluginProxySharedModule_preBuild.ts b/src/plugins/pluginProxySharedModule_preBuild.ts index 26b2ac3..bf56c92 100644 --- a/src/plugins/pluginProxySharedModule_preBuild.ts +++ b/src/plugins/pluginProxySharedModule_preBuild.ts @@ -1,55 +1,33 @@ -import { createFilter } from '@rollup/pluginutils'; import { defu } from 'defu'; -import { walk } from 'estree-walker'; -import MagicString from 'magic-string'; import { Plugin, UserConfig, WatchOptions } from 'vite'; -import { getNormalizeModuleFederationOptions, NormalizedShared } from '../utils/normalizeModuleFederationOptions'; -import { packageNameDecode } from '../utils/packageNameUtils'; +import { NormalizedShared } from '../utils/normalizeModuleFederationOptions'; import { PromiseStore } from "../utils/PromiseStore"; -import { wrapManualChunks } from '../utils/wrapManualChunks'; -import { addShare, generateLocalSharedImportMap, getLoadShareModulePath, getLocalSharedImportMapPath, LOAD_SHARE_TAG, PREBUILD_TAG, writeLoadShareModule, writeLocalSharedImportMap, writePreBuildLibPath } from '../virtualModules'; +import VirtualModule from '../utils/VirtualModule'; +import { addUsedShares, generateLocalSharedImportMap, getLoadShareModulePath, getLocalSharedImportMapPath, LOAD_SHARE_TAG, PREBUILD_TAG, writeLoadShareModule, writeLocalSharedImportMap, writePreBuildLibPath } from '../virtualModules'; +import { parsePromise } from './pluginModuleParseEnd'; export function proxySharedModule( options: { shared?: NormalizedShared; include?: string | string[]; exclude?: string | string[] } ): Plugin[] { let { shared = {}, include, exclude } = options; - const filterFunction = createFilter(include, exclude); - const { name } = getNormalizeModuleFederationOptions() return [ { name: "generateLocalSharedImportMap", enforce: "post", - resolveId(id) { - if (id.includes(getLocalSharedImportMapPath())) - return id - }, load(id) { if (id.includes(getLocalSharedImportMapPath())) { - return generateLocalSharedImportMap() + return parsePromise.then(_ => generateLocalSharedImportMap()) } }, transform(code, id) { if (id.includes(getLocalSharedImportMapPath())) { - return generateLocalSharedImportMap() + return parsePromise.then(_ => generateLocalSharedImportMap()) } - } + }, }, { name: 'proxyPreBuildShared', enforce: 'post', config(config: UserConfig, { command }) { - if (!config.build) config.build = {}; - if (!config.build.rollupOptions) config.build.rollupOptions = {}; - let { rollupOptions } = config.build; - if (!rollupOptions.output) rollupOptions.output = {}; - wrapManualChunks(config.build.rollupOptions.output, (id: string) => { - // https://github.com/module-federation/vite/issues/40#issuecomment-2311434503 - if (id.includes('/preload-helper.js')) { - return "preload-helper" - } - if (id.includes("node_modules/@module-federation/runtime")) { - return "@module-federation/runtime" - } - }); ; (config.resolve as any).alias.push( ...Object.keys(shared).map((key) => { const pattern = key.endsWith("/") ? `(^${key.replace(/\/$/, "")}(\/.+)?$)` : `(^${key}$)` @@ -57,10 +35,9 @@ export function proxySharedModule( // Intercept all shared requests and proxy them to loadShare find: new RegExp(pattern), replacement: "$1", customResolver(source: string, importer: string) { const loadSharePath = getLoadShareModulePath(source) - config?.optimizeDeps?.needsInterop?.push(loadSharePath); writeLoadShareModule(source, shared[key], command) writePreBuildLibPath(source) - addShare(source) + addUsedShares(source) writeLocalSharedImportMap() return (this as any).resolve(loadSharePath) } @@ -73,16 +50,14 @@ export function proxySharedModule( ...Object.keys(shared).map((key) => { return command === "build" ? { - find: new RegExp(`(.*${PREBUILD_TAG}(.+))`), replacement: function ($1: string) { - $1 = $1.replace(/\.[^.]+$/, "") - const pkgName = packageNameDecode($1.split(PREBUILD_TAG)[1]) - return packageNameDecode(pkgName) - } + find: new RegExp(`(.*${PREBUILD_TAG}.*)`), replacement: function ($1: string) { + const pkgName = (VirtualModule.findModule(PREBUILD_TAG, $1) as VirtualModule).name + return pkgName + }, } : { - find: new RegExp(`(.*${PREBUILD_TAG}(.+))`), replacement: "$1", async customResolver(source: string, importer: string) { - source = source.replace(/\.[^.]+$/, "") - const pkgName = packageNameDecode(source.split(PREBUILD_TAG)[1]) + find: new RegExp(`(.*${PREBUILD_TAG}.*)`), replacement: "$1", async customResolver(source: string, importer: string) { + const pkgName = (VirtualModule.findModule(PREBUILD_TAG, source) as VirtualModule).name if (importer.includes(LOAD_SHARE_TAG)) { // save pre-bunding module id savePrebuild.set(pkgName, (this as any).resolve(pkgName).then((item: any) => item.id)) @@ -112,82 +87,6 @@ export function proxySharedModule( watch.ignored.push(`!**${getLocalSharedImportMapPath()}**`); }, }, - { - name: "prebuild-top-level-await", - apply: "serve", - transform(code: string, id: string): { code: string; map: any } | null { - if (!(code.includes("/*mf top-level-await placeholder replacement mf*/"))) { - return null - } - if (!filterFunction(id)) return null; - let ast: any; - try { - ast = (this as any).parse(code, { - allowReturnOutsideFunction: true, - }); - } catch (e) { - throw new Error(`${id}: ${e}`); - } - - const magicString = new MagicString(code); - - walk(ast, { - enter(node: any) { - if (node.type === 'ExportNamedDeclaration' && node.specifiers) { - const exportSpecifiers = node.specifiers.map((specifier: any) => specifier.exported.name); - const proxyStatements = exportSpecifiers.map((name: string) => ` - const __mfproxy__await${name} = await ${name}(); - const __mfproxy__${name} = () => __mfproxy__await${name}; - `).join('\n'); - const exportStatements = exportSpecifiers.map((name: string) => `__mfproxy__${name} as ${name}`).join(', '); - const start = node.start; - const end = node.end; - const replacement = `${proxyStatements}\nexport { ${exportStatements} };`; - - magicString.overwrite(start, end, replacement); - } - - if (node.type === 'ExportDefaultDeclaration') { - const declaration = node.declaration; - const start = node.start; - const end = node.end; - - let proxyStatement; - let exportStatement = 'default'; - - if (declaration.type === 'Identifier') { - // example: export default foo; - proxyStatement = ` - const __mfproxy__awaitdefault = await ${declaration.name}(); - const __mfproxy__default = __mfproxy__awaitdefault; - `; - } else if (declaration.type === 'CallExpression' || declaration.type === 'FunctionDeclaration') { - // example: export default someFunction(); - const declarationCode = code.slice(declaration.start, declaration.end); - proxyStatement = ` - const __mfproxy__awaitdefault = await (${declarationCode}); - const __mfproxy__default = __mfproxy__awaitdefault; - `; - } else { - // other - proxyStatement = ` - const __mfproxy__awaitdefault = await (${code.slice(declaration.start, declaration.end)}); - const __mfproxy__default = __mfproxy__awaitdefault; - `; - } - - const replacement = `${proxyStatement}\nexport { __mfproxy__default as ${exportStatement} };`; - - magicString.overwrite(start, end, replacement); - } - } - }); - return { - code: magicString.toString(), - map: magicString.generateMap({ hires: true }), - }; - }, - } ] } diff --git a/src/utils/VirtualModule.ts b/src/utils/VirtualModule.ts index 0927cea..ca1b5ad 100644 --- a/src/utils/VirtualModule.ts +++ b/src/utils/VirtualModule.ts @@ -1,6 +1,6 @@ import { existsSync, mkdirSync, writeFile, writeFileSync } from "fs"; import { dirname, join, parse, resolve } from "pathe"; -import { packageNameEncode } from "../utils/packageNameUtils"; +import { packageNameDecode, packageNameEncode } from "../utils/packageNameUtils"; import { getNormalizeModuleFederationOptions } from "./normalizeModuleFederationOptions"; const nodeModulesDir = function findNodeModulesDir(startDir = process.cwd()) { @@ -26,24 +26,43 @@ writeFileSync(resolve(nodeModulesDir, virtualPackageName, "package.json"), JSON. main: "empty.js" })) +const patternMap: { + [tag: string]: RegExp +} = {} + +const cacheMap: { + [tag: string]: { + [name: string]: VirtualModule + } +} = {} /** * Physically generate files as virtual modules under node_modules/__mf__virtual/* */ export default class VirtualModule { - originName: string + name: string + tag: string + suffix: string inited: boolean = false - ext: string - constructor(name: string, ext: string = ".js") { - this.originName = name - this.ext = ext + static findModule(tag: string, str: string = ""): VirtualModule | undefined { + if (!patternMap[tag]) patternMap[tag] = new RegExp(`(.*${packageNameEncode(tag)}(.+?)${packageNameEncode(tag)}.*)`) + const moduleName = (str.match(patternMap[tag]) || [])[2] + if (moduleName) return cacheMap[tag][packageNameDecode(moduleName)] as VirtualModule | undefined + return undefined + } + constructor(name: string, tag: string = '__mf_v__', suffix = "") { + this.name = name + this.tag = tag + this.suffix = suffix || name.split(".").slice(1).pop()?.replace(/(.)/, ".$1") || ".js" + if (!cacheMap[this.tag]) cacheMap[this.tag] = {} + cacheMap[this.tag][this.name] = this } getPath() { return resolve(nodeModulesDir, this.getImportId()) } getImportId() { - const { name } = getNormalizeModuleFederationOptions() + const { name: mfName } = getNormalizeModuleFederationOptions() - return `${virtualPackageName}/${packageNameEncode(name)}-${packageNameEncode(this.originName)}${this.ext}` + return `${virtualPackageName}/${packageNameEncode(`${mfName}${this.tag}${this.name}${this.tag}`)}${this.suffix}` } writeSync(code: string, force?: boolean) { if (!force && this.inited) return diff --git a/src/utils/normalizeModuleFederationOptions.ts b/src/utils/normalizeModuleFederationOptions.ts index 75086c4..77fb85b 100644 --- a/src/utils/normalizeModuleFederationOptions.ts +++ b/src/utils/normalizeModuleFederationOptions.ts @@ -209,6 +209,22 @@ function normalizeLibrary(library: any): any { return library; } +interface ManifestOptions { + filePath?: string; + disableAssetsAnalyze?: boolean; + fileName?: string; +} +function normalizeManifest(manifest: ModuleFederationOptions['manifest'] = false) { + if (typeof manifest === "boolean") { + return manifest + } + return Object.assign({ + filePath: "", + disableAssetsAnalyze: false, + fileName: "mf-manifest.json" + }, manifest) +} + export type ModuleFederationOptions = { exposes?: Record | undefined; filename?: string; @@ -242,7 +258,7 @@ export type ModuleFederationOptions = { runtimePlugins?: string[]; getPublicPath?: any; implementation?: any; - manifest?: any; + manifest?: ManifestOptions | boolean; dev?: any; dts?: any; }; @@ -263,7 +279,7 @@ export interface NormalizedModuleFederationOptions { runtimePlugins: string[]; getPublicPath: any; implementation: any; - manifest: any; + manifest: ManifestOptions | boolean; dev: any; dts: any; } @@ -273,6 +289,12 @@ export function getNormalizeModuleFederationOptions() { return config } +export function getNormalizeShareItem(key: string) { + const options = getNormalizeModuleFederationOptions() + const shareItem = options.shared[removePathFromNpmPackage(key)] || options.shared[removePathFromNpmPackage(key) + "/"] + return shareItem +} + export function normalizeModuleFederationOptions( options: ModuleFederationOptions ): NormalizedModuleFederationOptions { @@ -289,7 +311,7 @@ export function normalizeModuleFederationOptions( runtimePlugins: options.runtimePlugins || [], getPublicPath: options.getPublicPath, implementation: options.implementation, - manifest: options.manifest, + manifest: normalizeManifest(options.manifest), dev: options.dev, dts: options.dts, }; diff --git a/src/utils/packageNameUtils.ts b/src/utils/packageNameUtils.ts index 95dc567..906e9f3 100644 --- a/src/utils/packageNameUtils.ts +++ b/src/utils/packageNameUtils.ts @@ -8,27 +8,31 @@ */ /** - * @param {*} name "@scope/xx-xx.xx" => "__$1__scope__$2__xx__$3__xx$__4__xx" + * Encodes a package name into a valid file name. + * @param {string} name - The package name, e.g., "@scope/xx-xx.xx". + * @returns {string} - The encoded file name. */ export function packageNameEncode(name: string) { if (typeof name !== "string") throw new Error("A string package name is required"); return name - .replace(/\@/g, "__$1__") - .replace(/\//g, "__$2__") - .replace(/\-/g, "__$3__") - .replace(/\./g, "__$4__"); + .replace(/@/g, "_mf_0_") + .replace(/\//g, "_mf_1_") + .replace(/-/g, "_mf_2_") + .replace(/\./g, "_mf_3_"); } /** - * @param {*} global "__$1__scope__$2__xx__$3__xx$__4__xx" => "@scope/xx-xx.xx" + * Decodes an encoded file name back to the original package name. + * @param {string} encoded - The encoded file name, e.g., "_mf_0_scope_mf_1_xx_mf_2_xx_mf_3_xx". + * @returns {string} - The decoded package name. */ -export function packageNameDecode(global: string) { - if (typeof global !== "string") throw new Error("A string global variable name is required"); - return global - .replace(/\_\_\$1\_\_/g, "@") - .replace(/\_\_\$2\_\_/g, "/") - .replace(/\_\_\$3\_\_/g, "-") - .replace(/\_\_\$4\_\_/g, "."); +export function packageNameDecode(encoded: string) { + if (typeof encoded !== "string") throw new Error("A string encoded file name is required"); + return encoded + .replace(/_mf_0_/g, "@") + .replace(/_mf_1_/g, "/") + .replace(/_mf_2_/g, "-") + .replace(/_mf_3_/g, "."); } export function removePathFromNpmPackage(packageString: string): string { diff --git a/src/virtualModules/index.ts b/src/virtualModules/index.ts index 2e4f0c5..4231dbd 100644 --- a/src/virtualModules/index.ts +++ b/src/virtualModules/index.ts @@ -1,23 +1,22 @@ -import { writeHostAutoInit } from "./virtualRemoteEntry"; -import { writeRemote } from "./virtualRemotes"; -import { writeLocalSharedImportMap } from "./virtualShared_preBuild"; +import { writeHostAutoInit, writeLocalSharedImportMap } from "./virtualRemoteEntry"; +import { writeRuntimeInitStatus } from "./virtualRuntimeInitStatus"; export { - generateRemoteEntry, getHostAutoInitImportId, - getHostAutoInitPath, - REMOTE_ENTRY_ID + addUsedShares, generateLocalSharedImportMap, generateRemoteEntry, getHostAutoInitImportId, + getHostAutoInitPath, getLocalSharedImportMapPath, getUsedShares, + REMOTE_ENTRY_ID, writeLocalSharedImportMap } from "./virtualRemoteEntry"; export { - generateRemotes, remoteVirtualModule + addUsedRemote, generateRemotes, getRemoteVirtualModule, getUsedRemotesMap } from "./virtualRemotes"; -export { - addShare, generateLocalSharedImportMap, getLoadShareModulePath, getLocalSharedImportMapPath, getPreBuildLibImportId, LOAD_SHARE_TAG, localSharedImportMapModule, PREBUILD_TAG, writeLoadShareModule, writeLocalSharedImportMap, writePreBuildLibPath -} from "./virtualShared_preBuild"; +export { getLoadShareModulePath, getPreBuildLibImportId, LOAD_SHARE_TAG, PREBUILD_TAG, writeLoadShareModule, writePreBuildLibPath } from "./virtualShared_preBuild"; + +export { virtualRuntimeInitStatus } from "./virtualRuntimeInitStatus"; export function initVirtualModules() { writeLocalSharedImportMap() writeHostAutoInit() - writeRemote() + writeRuntimeInitStatus() } \ No newline at end of file diff --git a/src/virtualModules/virtualRemoteEntry.ts b/src/virtualModules/virtualRemoteEntry.ts index 2220936..20bd4fd 100644 --- a/src/virtualModules/virtualRemoteEntry.ts +++ b/src/virtualModules/virtualRemoteEntry.ts @@ -1,6 +1,102 @@ -import { NormalizedModuleFederationOptions } from '../utils/normalizeModuleFederationOptions'; +import { getLocalSharedImportMapPath_windows, writeLocalSharedImportMap_windows } from '../utils/localSharedImportMap_windows'; +import { getNormalizeModuleFederationOptions, getNormalizeShareItem, NormalizedModuleFederationOptions } from '../utils/normalizeModuleFederationOptions'; import VirtualModule from '../utils/VirtualModule'; -import { getLocalSharedImportMapPath } from './virtualShared_preBuild'; +import { getUsedRemotesMap } from './virtualRemotes'; +import { virtualRuntimeInitStatus } from './virtualRuntimeInitStatus'; +import { getPreBuildLibImportId } from './virtualShared_preBuild'; + +let usedShares: Set = new Set() +export function getUsedShares() { + return usedShares +} +export function addUsedShares(pkg: string) { + usedShares.add(pkg) +} +// *** Expose locally provided shared modules here +const localSharedImportMapModule = new VirtualModule("localSharedImportMap") +export function getLocalSharedImportMapPath() { + if (process.platform === "win32") { + return getLocalSharedImportMapPath_windows() + } + return localSharedImportMapModule.getPath() +} +let prevSharedCount: number | undefined +export function writeLocalSharedImportMap() { + const sharedCount = getUsedShares().size + if (prevSharedCount !== sharedCount) { + prevSharedCount = sharedCount + if (process.platform === "win32") { + writeLocalSharedImportMap_windows(generateLocalSharedImportMap()) + } else { + localSharedImportMapModule.writeSync(generateLocalSharedImportMap(), true) + } + } +} +export function generateLocalSharedImportMap() { + const options = getNormalizeModuleFederationOptions() + return ` + const importMap = { + ${Array.from(getUsedShares()).map(pkg => ` + ${JSON.stringify(pkg)}: async () => { + let pkg = await import("${getPreBuildLibImportId(pkg)}") + return pkg + } + `).join(",")} + } + const usedShared = { + ${Array.from(getUsedShares()) + .map((key) => { + const shareItem = getNormalizeShareItem(key); + return ` + ${JSON.stringify(key)}: { + name: ${JSON.stringify(key)}, + version: ${JSON.stringify(shareItem.version)}, + scope: [${JSON.stringify(shareItem.scope)}], + loaded: false, + from: ${JSON.stringify(options.name)}, + async get () { + usedShared[${JSON.stringify(key)}].loaded = true + const {${JSON.stringify(key)}: pkgDynamicImport} = importMap + const res = await pkgDynamicImport() + const exportModule = {...res} + // All npm packages pre-built by vite will be converted to esm + Object.defineProperty(exportModule, "__esModule", { + value: true, + enumerable: false + }) + return function () { + return exportModule + } + }, + shareConfig: { + singleton: ${shareItem.shareConfig.singleton}, + requiredVersion: ${JSON.stringify(shareItem.shareConfig.requiredVersion)} + } + } + `; + }) + .join(',')} + } + const usedRemotes = [${Object.keys(getUsedRemotesMap()) + .map((key) => { + const remote = options.remotes[key]; + return ` + { + entryGlobalName: ${JSON.stringify(remote.entryGlobalName)}, + name: ${JSON.stringify(remote.name)}, + type: ${JSON.stringify(remote.type)}, + entry: ${JSON.stringify(remote.entry)}, + } + `; + }) + .join(',')} + ] + export { + usedShared, + usedRemotes + } + ` +} export const REMOTE_ENTRY_ID = 'virtual:mf-REMOTE_ENTRY_ID'; export function generateRemoteEntry(options: NormalizedModuleFederationOptions): string { @@ -11,7 +107,6 @@ export function generateRemoteEntry(options: NormalizedModuleFederationOptions): return ` import {init as runtimeInit, loadRemote} from "@module-federation/runtime"; - ${pluginImportNames.map((item) => item[1]).join('\n')} const exposesMap = { @@ -32,28 +127,19 @@ export function generateRemoteEntry(options: NormalizedModuleFederationOptions): }) .join(',')} } - import localSharedImportMap from "${getLocalSharedImportMapPath()}" + import {usedShared, usedRemotes} from "${getLocalSharedImportMapPath()}" + import { + initResolve + } from "${virtualRuntimeInitStatus.getImportId()}" async function init(shared = {}) { const initRes = runtimeInit({ name: ${JSON.stringify(options.name)}, - remotes: [${Object.keys(options.remotes) - .map((key) => { - const remote = options.remotes[key]; - return ` - { - entryGlobalName: ${JSON.stringify(remote.entryGlobalName)}, - name: ${JSON.stringify(remote.name)}, - type: ${JSON.stringify(remote.type)}, - entry: ${JSON.stringify(remote.entry)}, - } - `; - }) - .join(',')} - ], - shared: localSharedImportMap, + remotes: usedRemotes, + shared: usedShared, plugins: [${pluginImportNames.map((item) => `${item[0]}()`).join(', ')}] }); initRes.initShareScopeMap('${options.shareScope}', shared); + initResolve(initRes) return initRes } diff --git a/src/virtualModules/virtualRemotes.ts b/src/virtualModules/virtualRemotes.ts index 12558ec..bb9664e 100644 --- a/src/virtualModules/virtualRemotes.ts +++ b/src/virtualModules/virtualRemotes.ts @@ -1,28 +1,37 @@ -import VirtualModule from "../utils/VirtualModule"; +import VirtualModule from '../utils/VirtualModule'; +import { virtualRuntimeInitStatus } from './virtualRuntimeInitStatus'; -export const remoteVirtualModule = new VirtualModule("remoteModule") -export function writeRemote() { - remoteVirtualModule.writeSync("") +const cacheRemoteMap: { + [remote: string]: VirtualModule +} = {} +export const LOAD_REMOTE_TAG = '__loadRemote__'; +export function getRemoteVirtualModule(remote: string, command: string) { + if (!cacheRemoteMap[remote]) { + cacheRemoteMap[remote] = new VirtualModule(remote, LOAD_REMOTE_TAG, ".js") + cacheRemoteMap[remote].writeSync(generateRemotes(remote, command)); + } + const virtual = cacheRemoteMap[remote] + return virtual; +} +const usedRemotesMap: Record> = { + // remote1: {remote1/App, remote1, remote1/Button} +}; +export function addUsedRemote(remoteKey: string, remoteModule: string) { + if (!usedRemotesMap[remoteKey]) usedRemotesMap[remoteKey] = new Set(); + usedRemotesMap[remoteKey].add(remoteModule); +} +export function getUsedRemotesMap() { + return usedRemotesMap; +} +export function generateRemotes( + id: string, + command: string +) { + return ` + const {loadRemote} = require("@module-federation/runtime") + const {initPromise} = require("${virtualRuntimeInitStatus.getImportId()}") + const res = initPromise.then(_ => loadRemote(${JSON.stringify(id)})) + const exportModule = ${command !== "build" ? "/*mf top-level-await placeholder replacement mf*/" : "await "}initPromise.then(_ => res) + module.exports = exportModule + ` } -export function generateRemotes(id: string, command: string): { code: string; map: null; syntheticNamedExports: string } { - return { - code: ` - import {loadRemote} from "@module-federation/runtime" - const exportModule = await loadRemote(${JSON.stringify(id)}) - ${(command === "build" && - ` - export default 'default' in (exportModule || {}) ? exportModule.default : undefined - export const __mf__dynamicExports = exportModule - ` - ) || ""} - ${(command !== "build" && - ` - export default exportModule - ` - ) || ""} - `, - map: null, - // TODO: vite dev mode invalid, use optimizeDeps.needsInterop - syntheticNamedExports: '__mf__dynamicExports', - }; -} \ No newline at end of file diff --git a/src/virtualModules/virtualRuntimeInitStatus.ts b/src/virtualModules/virtualRuntimeInitStatus.ts new file mode 100644 index 0000000..ba59033 --- /dev/null +++ b/src/virtualModules/virtualRuntimeInitStatus.ts @@ -0,0 +1,16 @@ +import VirtualModule from "../utils/VirtualModule" +export const virtualRuntimeInitStatus = new VirtualModule("runtimeInit") +export function writeRuntimeInitStatus() { + virtualRuntimeInitStatus.writeSync(` + let initResolve, initReject + const initPromise = new Promise((re, rj) => { + initResolve = re + initReject = rj + }) + export { + initPromise, + initResolve, + initReject + } + `) +} \ No newline at end of file diff --git a/src/virtualModules/virtualShared_preBuild.ts b/src/virtualModules/virtualShared_preBuild.ts index 72f7f2e..95b2c84 100644 --- a/src/virtualModules/virtualShared_preBuild.ts +++ b/src/virtualModules/virtualShared_preBuild.ts @@ -9,119 +9,46 @@ * 2. __loadShare__: load shareModule (mfRuntime.loadShare('vue')) */ -import { parsePromise } from "../plugins/pluginModuleParseEnd"; -import { getLocalSharedImportMapPath_windows, writeLocalSharedImportMap_windows } from "../utils/localSharedImportMap_windows"; -import { getNormalizeModuleFederationOptions, ShareItem } from "../utils/normalizeModuleFederationOptions"; -import { getExtFromNpmPackage, removePathFromNpmPackage } from "../utils/packageNameUtils"; +import { ShareItem } from "../utils/normalizeModuleFederationOptions"; import VirtualModule from "../utils/VirtualModule"; +import { virtualRuntimeInitStatus } from "./virtualRuntimeInitStatus"; // *** __prebuild__ const preBuildCacheMap: Record = {} export const PREBUILD_TAG = "__prebuild__" export function writePreBuildLibPath(pkg: string) { - if (!preBuildCacheMap[pkg]) preBuildCacheMap[pkg] = new VirtualModule(PREBUILD_TAG + pkg, getExtFromNpmPackage(pkg)) + if (!preBuildCacheMap[pkg]) preBuildCacheMap[pkg] = new VirtualModule(pkg, PREBUILD_TAG) preBuildCacheMap[pkg].writeSync("") } export function getPreBuildLibImportId(pkg: string): string { - if (!preBuildCacheMap[pkg]) preBuildCacheMap[pkg] = new VirtualModule(PREBUILD_TAG + pkg, getExtFromNpmPackage(pkg)) + if (!preBuildCacheMap[pkg]) preBuildCacheMap[pkg] = new VirtualModule(pkg, PREBUILD_TAG) const importId = preBuildCacheMap[pkg].getImportId() return importId } -let shareds: Set = new Set() -export function addShare(pkg: string) { - shareds.add(pkg) -} - -// *** Expose locally provided shared modules here -export const localSharedImportMapModule = new VirtualModule("localSharedImportMap") -export function getLocalSharedImportMapPath() { - if (process.platform === "win32") { - return getLocalSharedImportMapPath_windows() - } - return localSharedImportMapModule.getPath() -} -let prevSharedCount = 0 -export async function writeLocalSharedImportMap() { - const sharedCount = shareds.size - if (prevSharedCount !== sharedCount) { - prevSharedCount = sharedCount - if (process.platform === "win32") { - return writeLocalSharedImportMap_windows(await generateLocalSharedImportMap()) - } - return localSharedImportMapModule.writeSync(await generateLocalSharedImportMap(), true) - } -} -export async function generateLocalSharedImportMap() { - await parsePromise - const options = getNormalizeModuleFederationOptions() - return ` - const localSharedImportMap = { - ${Array.from(shareds).map(pkg => ` - ${JSON.stringify(pkg)}: async () => { - let pkg = await import("${getPreBuildLibImportId(pkg)}") - return pkg - } - `).join(",")} - } - const localShared = { - ${Array.from(shareds) - .map((key) => { - const shareItem = options.shared[removePathFromNpmPackage(key)] || options.shared[removePathFromNpmPackage(key) + "/"]; - return ` - ${JSON.stringify(key)}: { - name: ${JSON.stringify(key)}, - version: ${JSON.stringify(shareItem.version)}, - scope: [${JSON.stringify(shareItem.scope)}], - loaded: false, - from: ${JSON.stringify(options.name)}, - async get () { - localShared[${JSON.stringify(key)}].loaded = true - const {${JSON.stringify(key)}: pkgDynamicImport} = localSharedImportMap - const res = await pkgDynamicImport() - const exportModule = {...res} - // All npm packages pre-built by vite will be converted to esm - Object.defineProperty(exportModule, "__esModule", { - value: true, - enumerable: false - }) - return function () { - return exportModule - } - }, - shareConfig: { - singleton: ${shareItem.shareConfig.singleton}, - requiredVersion: ${JSON.stringify(shareItem.shareConfig.requiredVersion)} - } - } - `; - }) - .join(',')} - } - export default localShared - ` -} // *** __loadShare__ export const LOAD_SHARE_TAG = "__loadShare__" const loadShareCacheMap: Record = {} export function getLoadShareModulePath(pkg: string): string { - if (!loadShareCacheMap[pkg]) loadShareCacheMap[pkg] = new VirtualModule(LOAD_SHARE_TAG + pkg) + if (!loadShareCacheMap[pkg]) loadShareCacheMap[pkg] = new VirtualModule(pkg, LOAD_SHARE_TAG, ".js") const filepath = loadShareCacheMap[pkg].getPath() return filepath } export function writeLoadShareModule(pkg: string, shareItem: ShareItem, command: string) { loadShareCacheMap[pkg].writeSync(` - () => import(${JSON.stringify(getPreBuildLibImportId(pkg))}).catch(() => {}); + + ;() => import(${JSON.stringify(getPreBuildLibImportId(pkg))}).catch(() => {}); // dev uses dynamic import to separate chunks ${command !== "build" ? `;() => import(${JSON.stringify(pkg)}).catch(() => {});` : ''} const {loadShare} = require("@module-federation/runtime") - const res = loadShare(${JSON.stringify(pkg)}, { + const {initPromise} = require("${virtualRuntimeInitStatus.getImportId()}") + const res = initPromise.then(_ => loadShare(${JSON.stringify(pkg)}, { customShareInfo: {shareConfig:{ singleton: ${shareItem.shareConfig.singleton}, strictVersion: ${shareItem.shareConfig.strictVersion}, requiredVersion: ${JSON.stringify(shareItem.shareConfig.requiredVersion)} - }}}) + }}})) const exportModule = ${command !== "build" ? "/*mf top-level-await placeholder replacement mf*/" : "await "}res.then(factory => factory()) module.exports = exportModule `)