@umijs/plugins
Version:
359 lines (356 loc) • 10.7 kB
JavaScript
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;
}
}