UNPKG

@umijs/plugins

Version:
359 lines (356 loc) 10.7 kB
var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/mf.ts var mf_exports = {}; __export(mf_exports, { default: () => mf }); module.exports = __toCommonJS(mf_exports); var import_bundler_utils = require("@umijs/bundler-utils"); var import_fs = require("fs"); var import_path = require("path"); var import_plugin_utils = require("umi/plugin-utils"); var import_constants = require("./constants"); var import_mfUtils = require("./utils/mfUtils"); var { isEmpty } = import_plugin_utils.lodash; var mfSetupPathFileName = "_mf_setup-public-path.js"; var mfAsyncEntryFileName = "asyncEntry.ts"; var MF_TEMPLATES_DIR = (0, import_path.join)(import_constants.TEMPLATES_DIR, "mf"); function mf(api) { api.describe({ key: "mf", config: { schema({ zod }) { return zod.object({ name: zod.string(), remotes: zod.array( zod.object({ aliasName: zod.string().optional(), // string 上没有 required name: zod.string(), entry: zod.string().optional(), entries: zod.object({}).optional(), keyResolver: zod.string().optional() }) ), shared: zod.record(zod.any()), library: zod.record(zod.any()), remoteHash: zod.boolean() }).partial(); } }, enableBy: api.EnableBy.config }); api.modifyWebpackConfig(async (config, { webpack }) => { const exposes = await constructExposes(); const remotes = formatRemotes(); const shared = getShared(); if (isEmpty(remotes) && isEmpty(exposes)) { api.logger.warn( "ModuleFederation exposes and remotes are empty, plugin will not work" ); return config; } if (!isEmpty(remotes)) { if (api.env === "production" || !api.config.mfsu) { changeUmiEntry(config); } } let name = "_"; if (!isEmpty(exposes)) { name = mfName(); addMFEntry( config, name, (0, import_path.join)(api.paths.absTmpPath, "plugin-mf", mfSetupPathFileName) ); } const useHash = typeof api.config.mf.remoteHash === "boolean" ? api.config.mf.remoteHash : api.config.hash && api.env !== "development"; const mfConfig = { name, remotes, filename: useHash ? "remote.[contenthash:8].js" : "remote.js", exposes, shared, library: api.config.mf.library }; const { ModuleFederationPlugin } = webpack.container; config.plugins.push(new ModuleFederationPlugin(mfConfig)); api.logger.debug( `ModuleFederationPlugin is enabled with config ${JSON.stringify( mfConfig )}` ); return config; }); api.modifyDefaultConfig(async (memo) => { if (memo.mfsu) { const exposes = await constructExposes(); if (!isEmpty(exposes)) { memo.mfsu.remoteName = mfName(); memo.mfsu.mfName = `mf_${memo.mfsu.remoteName}`; } const remotes = formatRemotes(); memo.mfsu.remoteAliases = Object.keys(remotes); memo.mfsu.shared = getShared(); } return memo; }); api.onGenerateFiles(() => { api.writeTmpFile({ // ref https://webpack.js.org/concepts/module-federation/#infer-publicpath-from-script content: `/* infer remote public */; __webpack_public_path__ = document.currentScript.src + '/../';`, path: mfSetupPathFileName }); const { remotes = [] } = api.config.mf; api.writeTmpFile({ path: "index.tsx", context: { remoteCodeString: (0, import_mfUtils.toRemotesCodeString)(remotes) }, tplPath: (0, import_plugin_utils.winPath)((0, import_path.join)(MF_TEMPLATES_DIR, "runtime.ts.tpl")) }); }); api.register({ key: "onGenerateFiles", // ensure after generate tpm files stage: 10001, fn: async () => { if (api.env === "development" && api.config.mfsu) { return; } const entry = (0, import_path.join)(api.paths.absTmpPath, "umi.ts"); const content = (0, import_fs.readFileSync)( (0, import_path.join)(api.paths.absTmpPath, "umi.ts"), "utf-8" ); const [_imports, exports2] = await (0, import_bundler_utils.parseModule)({ content, path: entry }); const mfEntryContent = []; let hasDefaultExport = false; if (exports2.length) { mfEntryContent.push( `const umiExports = await import('${(0, import_plugin_utils.winPath)(entry)}')` ); for (const exportName of exports2) { if (exportName === "default") { hasDefaultExport = true; mfEntryContent.push(`export default umiExports.${exportName}`); } else { mfEntryContent.push( `export const ${exportName} = umiExports.${exportName}` ); } } } else { mfEntryContent.push(`import('${(0, import_plugin_utils.winPath)(entry)}')`); } if (!hasDefaultExport) { mfEntryContent.push("export default 1"); } api.writeTmpFile({ content: mfEntryContent.join("\n"), path: mfAsyncEntryFileName }); } }); function formatRemotes() { const { remotes = [] } = api.userConfig.mf; const memo = {}; remotes.forEach((remote) => { const aliasName = remote.aliasName || remote.name; const r = formatRemote(remote); if (memo[aliasName]) { return api.logger.error( `${aliasName} already set as ${memo[aliasName]}, new value ${r} will be ignored` ); } memo[aliasName] = r; }); return memo; } function formatRemote(remote) { if (remote.entry) { return `${remote.name}@${remote.entry}`; } if (remote.entries && remote.keyResolver) { const dynamicUrl = `promise new Promise(resolve => { const entries = ${JSON.stringify(remote.entries)}; const key = ${remote.keyResolver}; const remoteUrlWithVersion = entries[key]; const script = document.createElement('script') script.src = remoteUrlWithVersion script.onload = () => { // the injected script has loaded and is available on window // we can now resolve this Promise const proxy = { get: (request) => window.${remote.name}.get(request), init: (arg) => { try { return window.${remote.name}.init(arg) } catch(e) { console.log('remote container already initialized') } } } resolve(proxy) } // inject this script with the src set to the versioned remoteEntry.js document.head.appendChild(script); }) `; return dynamicUrl; } else { api.logger.error("you should provider entry or entries and keyResolver"); throw Error("Wrong MF#remotes config"); } } async function constructExposes() { const exposes = {}; const exposesPath = (0, import_path.join)(api.paths.absSrcPath, "exposes"); if (!(0, import_fs.existsSync)(exposesPath)) { return exposes; } const dir = (0, import_fs.opendirSync)(exposesPath); for await (const dirent of dir) { if (dirent.isDirectory()) { exposes["./" + dirent.name] = (0, import_plugin_utils.winPath)( (0, import_path.join)(api.paths.absSrcPath, "exposes", dirent.name) ); } else { api.logger.warn( `${dirent.name} is not a directory, ignore in ModuleFederation expose` ); } } return exposes; } function mfName() { const name = api.userConfig.mf.name; if (!name) { api.logger.warn( `module federation name is not defined , "unNamedMF" will be used` ); } if (!isValidIdentifyName(name)) { throw new Error( `module federation name '${name}' is not valid javascript identifier.` ); } return name || "unNamedMF"; } function getShared() { const { shared = {} } = api.userConfig.mf; return shared; } function changeUmiEntry(config) { const { entry } = config; const asyncEntryPath = (0, import_plugin_utils.winPath)( (0, import_path.join)(api.paths.absTmpPath, "plugin-mf", mfAsyncEntryFileName) ); if (entry.umi) { if (typeof entry.umi === "string") { entry.umi = asyncEntryPath; } else if (Array.isArray(entry.umi)) { const i = entry.umi.findIndex((f) => f.endsWith("umi.ts")); if (i >= 0) { entry.umi[i] = asyncEntryPath; } else { api.logger.info( `umi.ts not found in entry.umi ${JSON.stringify(entry.umi)}` ); } } } else { api.logger.warn("umi entry not found"); } } function addMFEntry(config, mfName2, path) { config.entry[mfName2] = path; } function isValidIdentifyName(name) { const reservedKeywords = [ "abstract", "await", "boolean", "break", "byte", "case", "catch", "char", "class", "const", "continue", "debugger", "default", "delete", "do", "double", "else", "enum", "export", "extends", "false", "final", "finally", "float", "for", "function", "goto", "if", "implements", "import", "in", "instanceof", "int", "interface", "let", "long", "native", "new", "null", "package", "private", "protected", "public", "return", "short", "static", "super", "switch", "synchronized", "this", "throw", "transient", "true", "try", "typeof", "var", "void", "volatile", "while", "with", "yield" ]; const regexIdentifierName = /^(?:[$_\p{ID_Start}])(?:[$_\u200C\u200D\p{ID_Continue}])*$/u; if (reservedKeywords.includes(name) || !regexIdentifierName.test(name)) { return false; } return true; } }