From 1d9b246d2c37375b3868f151060a7071fde40c28 Mon Sep 17 00:00:00 2001 From: 2heal1 Date: Tue, 11 Mar 2025 17:56:37 +0800 Subject: [PATCH 1/3] feat: add mf cli --- .changeset/config.json | 3 +- .../docs/en/guide/basic/_meta.json | 2 +- apps/website-new/docs/en/guide/basic/cli.mdx | 52 ++++++ .../docs/zh/guide/basic/_meta.json | 2 +- apps/website-new/docs/zh/guide/basic/cli.mdx | 52 ++++++ packages/cli/CHANGELOG.md | 1 + packages/cli/LICENSE | 21 +++ packages/cli/README.md | 63 +++++++ packages/cli/bin/mf.js | 4 + packages/cli/package.json | 42 +++++ packages/cli/project.json | 62 +++++++ packages/cli/rollup.config.js | 21 +++ packages/cli/src/cli.ts | 71 +++++++ packages/cli/src/commands/dts.ts | 96 ++++++++++ packages/cli/src/env.d.ts | 1 + packages/cli/src/index.ts | 1 + packages/cli/src/types.ts | 23 +++ .../cli/src/utils/apply-common-options.ts | 8 + packages/cli/src/utils/logger.ts | 8 + packages/cli/src/utils/prepare.ts | 17 ++ packages/cli/tsconfig.json | 27 +++ .../dts-plugin/src/core/lib/archiveHandler.ts | 4 + packages/dts-plugin/src/index.ts | 11 +- .../src/plugins/ConsumeTypesPlugin.ts | 128 ++++++++----- packages/dts-plugin/src/plugins/DtsPlugin.ts | 54 +++--- .../src/plugins/GenerateTypesPlugin.ts | 173 +++++++++++------- packages/dts-plugin/src/server/constant.ts | 2 +- pnpm-lock.yaml | 113 +++++++++--- 28 files changed, 889 insertions(+), 173 deletions(-) create mode 100644 apps/website-new/docs/en/guide/basic/cli.mdx create mode 100644 apps/website-new/docs/zh/guide/basic/cli.mdx create mode 100644 packages/cli/CHANGELOG.md create mode 100644 packages/cli/LICENSE create mode 100644 packages/cli/README.md create mode 100755 packages/cli/bin/mf.js create mode 100644 packages/cli/package.json create mode 100644 packages/cli/project.json create mode 100644 packages/cli/rollup.config.js create mode 100644 packages/cli/src/cli.ts create mode 100644 packages/cli/src/commands/dts.ts create mode 100644 packages/cli/src/env.d.ts create mode 100644 packages/cli/src/index.ts create mode 100644 packages/cli/src/types.ts create mode 100644 packages/cli/src/utils/apply-common-options.ts create mode 100644 packages/cli/src/utils/logger.ts create mode 100644 packages/cli/src/utils/prepare.ts create mode 100644 packages/cli/tsconfig.json diff --git a/.changeset/config.json b/.changeset/config.json index 48ecd9e0391..6a62d9a7350 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -26,7 +26,8 @@ "@module-federation/error-codes", "@module-federation/inject-external-runtime-core-plugin", "@module-federation/runtime-core", - "create-module-federation" + "create-module-federation", + "@module-federation/cli" ] ], "ignorePatterns": ["^alpha|^beta"], diff --git a/apps/website-new/docs/en/guide/basic/_meta.json b/apps/website-new/docs/en/guide/basic/_meta.json index 0c6e0164841..70b2cb33df2 100644 --- a/apps/website-new/docs/en/guide/basic/_meta.json +++ b/apps/website-new/docs/en/guide/basic/_meta.json @@ -1 +1 @@ -["runtime", "rsbuild", "rspack", "webpack", "vite", "chrome-devtool", "type-prompt"] +["runtime", "rsbuild", "rspack", "webpack", "vite", "chrome-devtool", "type-prompt","cli"] diff --git a/apps/website-new/docs/en/guide/basic/cli.mdx b/apps/website-new/docs/en/guide/basic/cli.mdx new file mode 100644 index 00000000000..bc11c3ff2d1 --- /dev/null +++ b/apps/website-new/docs/en/guide/basic/cli.mdx @@ -0,0 +1,52 @@ +# CLI + +Module Federation provides a lightweight CLI. + +## All commands + +To view all available CLI commands, run the following command in the project directory: + +```bash +npx @module-federation/cli -h +``` + +The output is shown below: + +```text +Usage: mf [options] + +Options: + -V, --version output the version number + -h, --help display help for command + +Commands: + dts [options] generate or fetch the mf types + help [command] display help for command +``` + +## Common flags + +Module Federation CLI provides several common flags that can be used with all commands: + +| Flag | Description | +| -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | +| `-c, --config ` | Specify the configuration file, can be a relative or absolute path, default value is `module-federation.ts` | +| `-h, --help` | Display help for command | + +## mf dts + +The `mf dts` command is used to generate or fetch remote types. + +```bash +Usage: mf dts [options] + +generate or fetch the mf types + +Options: + --root specify the project root directory + --output specify the generated dts output directory + --fetch fetch types from remote, default is true (default: true) + --generate generate types, default is true (default: true) + -c --config specify the configuration file, can be a relative or absolute path + -h, --help display help for command +``` diff --git a/apps/website-new/docs/zh/guide/basic/_meta.json b/apps/website-new/docs/zh/guide/basic/_meta.json index 0c6e0164841..70b2cb33df2 100644 --- a/apps/website-new/docs/zh/guide/basic/_meta.json +++ b/apps/website-new/docs/zh/guide/basic/_meta.json @@ -1 +1 @@ -["runtime", "rsbuild", "rspack", "webpack", "vite", "chrome-devtool", "type-prompt"] +["runtime", "rsbuild", "rspack", "webpack", "vite", "chrome-devtool", "type-prompt","cli"] diff --git a/apps/website-new/docs/zh/guide/basic/cli.mdx b/apps/website-new/docs/zh/guide/basic/cli.mdx new file mode 100644 index 00000000000..bfc5836c3ab --- /dev/null +++ b/apps/website-new/docs/zh/guide/basic/cli.mdx @@ -0,0 +1,52 @@ +# 命令行工具 + +Module Federation 提供了轻量的命令行工具。 + +## 查看所有命令 + +如果你需要查看所有可用的 CLI 命令,请在项目目录中运行以下命令: + +```bash +npx @module-federation/cli -h +``` + +输出如下: + +```text +Usage: mf [options] + +Options: + -V, --version output the version number + -h, --help display help for command + +Commands: + dts [options] generate or fetch the mf types + help [command] display help for command +``` + +## 公共选项 + +Module Federation CLI 提供了一些公共选项,可以用于所有命令: + +| 选项 | 描述 | +| -------------------------- | ----------------------------------------------------------------------------------------------------------------- | +| `-c, --config ` | 指定配置文件路径,可以为相对路径或绝对路径,默认值为 `module-federation.config.ts` | +| `-h, --help` | 显示命令帮助 | + +## mf dts + +`mf dts` 命令用于拉取或生成 TypeScript 类型声明文件。 + +```bash +Usage: mf dts [options] + +generate or fetch the mf types + +Options: + --root specify the project root directory + --output specify the generated dts output directory + --fetch fetch types from remote, default is true (default: true) + --generate generate types, default is true (default: true) + -c --config specify the configuration file, can be a relative or absolute path + -h, --help display help for command +``` diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md new file mode 100644 index 00000000000..70a6e7d0a4d --- /dev/null +++ b/packages/cli/CHANGELOG.md @@ -0,0 +1 @@ +# @module-federation/cli diff --git a/packages/cli/LICENSE b/packages/cli/LICENSE new file mode 100644 index 00000000000..ed1b325d093 --- /dev/null +++ b/packages/cli/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025-present hanric(2heal1) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/cli/README.md b/packages/cli/README.md new file mode 100644 index 00000000000..17922c5831b --- /dev/null +++ b/packages/cli/README.md @@ -0,0 +1,63 @@ +

+ Module Federation 2.0 +

+ +# @module-federation/cli + +Module Federation CLI + +

+ + npm version + + license + downloads +

+ +## All commands + +To view all available CLI commands, run the following command in the project directory: + +```bash +npx @module-federation/cli -h +``` + +The output is shown below: + +```bash +Usage: mf [options] + +Options: + -V, --version output the version number + -h, --help display help for command + +Commands: + dts [options] generate or fetch the mf types + help [command] display help for command +``` + +### mf dts + +The mf dts command is used to generate or fetch remote types. + +```bash +Usage: mf dts [options] + +generate or fetch the mf types + +Options: + --root specify the project root directory + --output specify the generated dts output directory + --fetch fetch types from remote, default is true (default: true) + --generate generate types, default is true (default: true) + -c --config specify the configuration file, can be a relative or absolute path + -h, --help display help for command +``` + +## Documentation + +See [Documentation](https://module-federation.io/guide/start/quick-start). + +## License + +[MIT](https://github.com/module-federation/core/blob/main/LICENSE). diff --git a/packages/cli/bin/mf.js b/packages/cli/bin/mf.js new file mode 100755 index 00000000000..62f70a3dd05 --- /dev/null +++ b/packages/cli/bin/mf.js @@ -0,0 +1,4 @@ +#!/usr/bin/env node +const { runCli } = require('../dist/index.cjs.js'); + +runCli(); diff --git a/packages/cli/package.json b/packages/cli/package.json new file mode 100644 index 00000000000..8b9b0d05315 --- /dev/null +++ b/packages/cli/package.json @@ -0,0 +1,42 @@ +{ + "name": "@module-federation/cli", + "version": "0.10.0", + "type": "commonjs", + "description": "Module Federation CLI", + "homepage": "https://module-federation.io", + "bugs": { + "url": "https://github.com/module-federation/core/issues" + }, + "repository": { + "type": "git", + "url": "https://github.com/module-federation/core", + "directory": "packages/cli" + }, + "bin": { + "mf": "./bin/mf.js" + }, + "license": "MIT", + "main": "./dist/index.cjs.js", + "files": [ + "dist", + "bin" + ], + "dependencies": { + "@module-federation/sdk": "workspace:*", + "@module-federation/dts-plugin": "workspace:*", + "commander": "11.1.0", + "chalk": "3.0.0", + "@modern-js/node-bundle-require": "2.65.1" + }, + "devDependencies": { + "@types/node": "~16.11.7" + }, + "engines": { + "node": ">=16.0.0" + }, + "publishConfig": { + "access": "public", + "provenance": true, + "registry": "https://registry.npmjs.org/" + } +} diff --git a/packages/cli/project.json b/packages/cli/project.json new file mode 100644 index 00000000000..879859f75ab --- /dev/null +++ b/packages/cli/project.json @@ -0,0 +1,62 @@ +{ + "name": "cli", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/cli/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/rollup:rollup", + "outputs": ["{options.outputPath}"], + "options": { + "outputPath": "packages/cli/dist", + "main": "packages/cli/src/index.ts", + "tsConfig": "packages/cli/tsconfig.json", + "assets": [], + "project": "packages/cli/package.json", + "rollupConfig": "packages/cli/rollup.config.js", + "compiler": "swc", + "format": ["cjs"], + "generatePackageJson": false + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": ["{options.outputFile}"], + "options": { + "lintFilePatterns": [ + "packages/cli/**/*.ts", + "packages/cli/package.json" + ] + } + }, + "test": { + "executor": "nx:run-commands", + "options": { + "parallel": false, + "commands": [ + { + "command": "echo 'waiting for adding test case...'", + "forwardAllArgs": false + } + ] + } + }, + "pre-release": { + "executor": "nx:run-commands", + "options": { + "parallel": false, + "commands": [ + { + "command": "nx run cli:test", + "forwardAllArgs": false + }, + { + "command": "nx run cli:build", + "forwardAllArgs": false + } + ] + } + } + }, + "tags": ["type:pkg"] +} diff --git a/packages/cli/rollup.config.js b/packages/cli/rollup.config.js new file mode 100644 index 00000000000..56fc1d396bf --- /dev/null +++ b/packages/cli/rollup.config.js @@ -0,0 +1,21 @@ +const copy = require('rollup-plugin-copy'); +const replace = require('@rollup/plugin-replace'); +const pkg = require('./package.json'); + +module.exports = (rollupConfig, _projectOptions) => { + rollupConfig.plugins.push( + replace({ + __VERSION__: JSON.stringify(pkg.version), + }), + copy({ + targets: [ + { + src: 'packages/cli/LICENSE', + dest: 'packages/cli/dist', + }, + ], + }), + ); + + return rollupConfig; +}; diff --git a/packages/cli/src/cli.ts b/packages/cli/src/cli.ts new file mode 100644 index 00000000000..46869be23c8 --- /dev/null +++ b/packages/cli/src/cli.ts @@ -0,0 +1,71 @@ +import { program } from 'commander'; +import { applyCommonOptions } from './utils/apply-common-options'; +import { dts } from './commands/dts'; +import { prepareCli } from './utils/prepare'; + +import type { DtsOptions, CliOptions } from './types'; + +function cli({ + name, + version, + applyCommands, + config, +}: Required): void { + program.name(name).usage(' [options]').version(version); + + const dtsCommand = program.command('dts'); + + dtsCommand + .option('--root ', 'specify the project root directory') + .option('--output ', 'specify the generated dts output directory') + .option( + '--fetch ', + 'fetch types from remote, default is true', + (value) => value === 'true', + true, + ) + .option( + '--generate ', + 'generate types, default is true', + (value) => value === 'true', + true, + ); + + applyCommonOptions(dtsCommand); + + dtsCommand + .description('generate or fetch the mf types') + .action(async (options: DtsOptions) => { + try { + await dts(options, { defaultConfig: config }); + } catch (err) { + console.error(err); + process.exit(1); + } + }); + + if (typeof applyCommands === 'function') { + applyCommands(program, applyCommonOptions); + } + + program.parse(); +} + +export function runCli(options: CliOptions) { + const normalizedOptions: Required = { + welcomeMsg: `${`Module Federation v${__VERSION__}`}\n`, + name: 'mf', + config: 'module-federation.config.ts', + version: __VERSION__, + applyCommands: () => {}, + ...options, + }; + + prepareCli(normalizedOptions); + + try { + cli(normalizedOptions); + } catch (err) { + console.error(err); + } +} diff --git a/packages/cli/src/commands/dts.ts b/packages/cli/src/commands/dts.ts new file mode 100644 index 00000000000..bfa92df0781 --- /dev/null +++ b/packages/cli/src/commands/dts.ts @@ -0,0 +1,96 @@ +import path from 'path'; +import logger from '../utils/logger'; +import { + isTSProject, + normalizeDtsOptions, + generateTypesAPI, + consumeTypesAPI, + normalizeGenerateTypesOptions, + normalizeConsumeTypesOptions, +} from '@module-federation/dts-plugin'; +import { bundle } from '@modern-js/node-bundle-require'; + +import type { DtsOptions } from '../types'; +import { moduleFederationPlugin } from '@module-federation/sdk'; + +export async function dts( + options: DtsOptions, + { defaultConfig }: { defaultConfig: string }, +) { + const defaultPath = path.resolve(process.cwd(), defaultConfig); + const { + fetch = true, + generate = true, + root = process.cwd(), + output, + config = defaultPath, + } = options; + + const configPath = path.isAbsolute(config) + ? config + : path.resolve(process.cwd(), config); + + const preBundlePath = await bundle(configPath); + const mfConfig = (await import(preBundlePath)).default + .default as unknown as moduleFederationPlugin.ModuleFederationPluginOptions; + + if (!isTSProject(mfConfig.dts, root)) { + logger.error('dts is only supported for TypeScript projects'); + return; + } + + const normalizedDtsOptions = normalizeDtsOptions(mfConfig, root, { + defaultGenerateOptions: { + generateAPITypes: true, + compileInChildProcess: false, + abortOnError: true, + extractThirdParty: false, + extractRemoteTypes: false, + }, + defaultConsumeOptions: { + abortOnError: true, + consumeAPITypes: true, + }, + }); + + if (!normalizedDtsOptions) { + logger.error('dts is not enabled in module-federation.config.ts'); + return; + } + + if (fetch) { + const dtsManagerOptions = normalizeConsumeTypesOptions({ + context: root, + dtsOptions: normalizedDtsOptions, + pluginOptions: mfConfig, + }); + if (!dtsManagerOptions) { + logger.warn( + 'dts.consumeTypes is not enabled in module-federation.config.ts, skip fetching remote types', + ); + } else { + logger.debug('start fetching remote types...'); + await consumeTypesAPI(dtsManagerOptions); + logger.debug('fetch remote types success!'); + } + } + + if (generate) { + const dtsManagerOptions = normalizeGenerateTypesOptions({ + context: root, + outputDir: output, + dtsOptions: normalizedDtsOptions, + pluginOptions: mfConfig, + }); + if (!dtsManagerOptions) { + logger.warn( + 'dts.generateTypes is not enabled in module-federation.config.ts, skip generating types', + ); + return; + } + + logger.debug('start generating types...'); + await generateTypesAPI({ dtsManagerOptions }); + logger.debug('generate types success!'); + } +} diff --git a/packages/cli/src/env.d.ts b/packages/cli/src/env.d.ts new file mode 100644 index 00000000000..415c2c8274f --- /dev/null +++ b/packages/cli/src/env.d.ts @@ -0,0 +1 @@ +declare const __VERSION__: string; diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts new file mode 100644 index 00000000000..87937f10c30 --- /dev/null +++ b/packages/cli/src/index.ts @@ -0,0 +1 @@ +export { runCli } from './cli'; diff --git a/packages/cli/src/types.ts b/packages/cli/src/types.ts new file mode 100644 index 00000000000..85de8533f65 --- /dev/null +++ b/packages/cli/src/types.ts @@ -0,0 +1,23 @@ +import type { Command } from 'commander'; + +export type CommonOptions = { + config?: string; +}; + +export type DtsOptions = { + fetch?: boolean; + generate?: boolean; + root?: string; + output?: string; +} & CommonOptions; + +export type CliOptions = { + welcomeMsg?: string; + name?: string; + version?: string; + config?: string; + applyCommands?: ( + command: Command, + applyCommonOptions: (command: Command) => void, + ) => void; +}; diff --git a/packages/cli/src/utils/apply-common-options.ts b/packages/cli/src/utils/apply-common-options.ts new file mode 100644 index 00000000000..e42994d83cc --- /dev/null +++ b/packages/cli/src/utils/apply-common-options.ts @@ -0,0 +1,8 @@ +import type { Command } from 'commander'; + +export const applyCommonOptions = (command: Command) => { + command.option( + '-c --config ', + 'specify the configuration file, can be a relative or absolute path', + ); +}; diff --git a/packages/cli/src/utils/logger.ts b/packages/cli/src/utils/logger.ts new file mode 100644 index 00000000000..abe8d20a4ba --- /dev/null +++ b/packages/cli/src/utils/logger.ts @@ -0,0 +1,8 @@ +import { type Logger, createLogger } from '@module-federation/sdk'; +import chalk from 'chalk'; + +const PREFIX = '[ Module Federation CLI ]'; + +const logger: Logger = createLogger(chalk`{bold {cyan ${PREFIX}}}`); + +export default logger; diff --git a/packages/cli/src/utils/prepare.ts b/packages/cli/src/utils/prepare.ts new file mode 100644 index 00000000000..f9b3ba3045a --- /dev/null +++ b/packages/cli/src/utils/prepare.ts @@ -0,0 +1,17 @@ +import logger from './logger'; +import type { CliOptions } from '../types'; + +export function prepareCli({ welcomeMsg }: Required): void { + // Print a blank line to keep the greet log nice. + // Some package managers automatically output a blank line, some do not. + const { npm_execpath } = process.env; + if ( + !npm_execpath || + npm_execpath.includes('npx-cli.js') || + npm_execpath.includes('.bun') + ) { + console.log(); + } + + logger.info(welcomeMsg); +} diff --git a/packages/cli/tsconfig.json b/packages/cli/tsconfig.json new file mode 100644 index 00000000000..fcf1b213e09 --- /dev/null +++ b/packages/cli/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "rootDir": "./", + "outDir": "dist", + "sourceMap": false, + "module": "commonjs", + "target": "ES2015", + "skipLibCheck": true, + "moduleResolution": "node", + "allowJs": false, + "strict": true, + "types": ["jest", "node"], + "experimentalDecorators": true, + "resolveJsonModule": true, + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + "removeComments": true, + "declaration": true, + "paths": { + "@/*": ["./*"], + "@src/*": ["./src/*"] + } + }, + "include": ["src", "../../global.d.ts", "__tests__/**/*"], + "exclude": ["node_modules/**/*", "../node_modules"] +} diff --git a/packages/dts-plugin/src/core/lib/archiveHandler.ts b/packages/dts-plugin/src/core/lib/archiveHandler.ts index 9975bd902c0..0f47d7defb5 100644 --- a/packages/dts-plugin/src/core/lib/archiveHandler.ts +++ b/packages/dts-plugin/src/core/lib/archiveHandler.ts @@ -8,6 +8,7 @@ import { retrieveMfTypesPath } from './typeScriptCompiler'; import { fileLog } from '../../server'; import { axiosGet } from './utils'; import { TsConfigJson } from '../interfaces/TsConfigJson'; +import { logger } from '../../server'; export const retrieveTypesZipPath = ( mfTypesPath: string, @@ -105,6 +106,9 @@ export const downloadTypesArchive = (hostOptions: Required) => { 'error', ); if (retries >= hostOptions.maxRetries) { + logger.error( + `Failed to download ${fileToDownload}, you can set FEDERATION_DEBUG=true to see detail message.`, + ); if (hostOptions.abortOnError !== false) { throw error; } diff --git a/packages/dts-plugin/src/index.ts b/packages/dts-plugin/src/index.ts index 5c35e25af36..1310ecace5b 100644 --- a/packages/dts-plugin/src/index.ts +++ b/packages/dts-plugin/src/index.ts @@ -1 +1,10 @@ -export { DtsPlugin } from './plugins/DtsPlugin'; +export { DtsPlugin, normalizeDtsOptions } from './plugins/DtsPlugin'; +export { + consumeTypesAPI, + normalizeConsumeTypesOptions, +} from './plugins/ConsumeTypesPlugin'; +export { + generateTypesAPI, + normalizeGenerateTypesOptions, +} from './plugins/GenerateTypesPlugin'; +export { isTSProject } from './core'; diff --git a/packages/dts-plugin/src/plugins/ConsumeTypesPlugin.ts b/packages/dts-plugin/src/plugins/ConsumeTypesPlugin.ts index d36744bbcd0..4b84aecf437 100644 --- a/packages/dts-plugin/src/plugins/ConsumeTypesPlugin.ts +++ b/packages/dts-plugin/src/plugins/ConsumeTypesPlugin.ts @@ -3,15 +3,82 @@ import { normalizeOptions, type moduleFederationPlugin, } from '@module-federation/sdk'; -import { validateOptions, consumeTypes } from '../core/index'; +import { + validateOptions, + consumeTypes, + DTSManagerOptions, +} from '../core/index'; import { isPrd } from './utils'; import type { Compiler, WebpackPluginInstance } from 'webpack'; +export const DEFAULT_CONSUME_TYPES = { + abortOnError: false, + consumeAPITypes: true, +}; + +export const normalizeConsumeTypesOptions = ({ + context, + dtsOptions, + pluginOptions, +}: { + context?: string; + dtsOptions: moduleFederationPlugin.PluginDtsOptions; + pluginOptions: moduleFederationPlugin.ModuleFederationPluginOptions; +}) => { + const normalizedConsumeTypes = + normalizeOptions( + true, + DEFAULT_CONSUME_TYPES, + 'mfOptions.dts.consumeTypes', + )(dtsOptions.consumeTypes); + + if (!normalizedConsumeTypes) { + return; + } + const dtsManagerOptions = { + host: { + implementation: dtsOptions.implementation, + context, + moduleFederationConfig: pluginOptions, + ...normalizedConsumeTypes, + }, + extraOptions: dtsOptions.extraOptions || {}, + displayErrorInTerminal: dtsOptions.displayErrorInTerminal, + }; + validateOptions(dtsManagerOptions.host); + + return dtsManagerOptions; +}; + +export const consumeTypesAPI = async ( + dtsManagerOptions: DTSManagerOptions, + cb?: (options: moduleFederationPlugin.RemoteTypeUrls) => void, +) => { + const fetchRemoteTypeUrlsPromise = + typeof dtsManagerOptions.host.remoteTypeUrls === 'function' + ? dtsManagerOptions.host.remoteTypeUrls() + : Promise.resolve(dtsManagerOptions.host.remoteTypeUrls); + return fetchRemoteTypeUrlsPromise.then((remoteTypeUrls) => { + consumeTypes({ + ...dtsManagerOptions, + host: { + ...dtsManagerOptions.host, + remoteTypeUrls, + }, + }) + .then(() => { + typeof cb === 'function' && cb(remoteTypeUrls); + }) + .catch(() => { + typeof cb === 'function' && cb(remoteTypeUrls); + }); + }); +}; + export class ConsumeTypesPlugin implements WebpackPluginInstance { pluginOptions: moduleFederationPlugin.ModuleFederationPluginOptions; dtsOptions: moduleFederationPlugin.PluginDtsOptions; - defaultOptions: moduleFederationPlugin.DtsHostOptions; callback: () => void; fetchRemoteTypeUrlsResolve: ( options: moduleFederationPlugin.RemoteTypeUrls, @@ -20,74 +87,39 @@ export class ConsumeTypesPlugin implements WebpackPluginInstance { constructor( pluginOptions: moduleFederationPlugin.ModuleFederationPluginOptions, dtsOptions: moduleFederationPlugin.PluginDtsOptions, - defaultOptions: moduleFederationPlugin.DtsHostOptions, fetchRemoteTypeUrlsResolve: ( options: moduleFederationPlugin.RemoteTypeUrls, ) => void, ) { this.pluginOptions = pluginOptions; this.dtsOptions = dtsOptions; - this.defaultOptions = defaultOptions; this.fetchRemoteTypeUrlsResolve = fetchRemoteTypeUrlsResolve; } apply(compiler: Compiler) { - const { - dtsOptions, - defaultOptions, - pluginOptions, - fetchRemoteTypeUrlsResolve, - } = this; + const { dtsOptions, pluginOptions, fetchRemoteTypeUrlsResolve } = this; if (isPrd()) { fetchRemoteTypeUrlsResolve(undefined); return; } - const normalizedConsumeTypes = - normalizeOptions( - true, - defaultOptions, - 'mfOptions.dts.consumeTypes', - )(dtsOptions.consumeTypes); + const dtsManagerOptions = normalizeConsumeTypesOptions({ + context: compiler.context, + dtsOptions, + pluginOptions, + }); - if (!normalizedConsumeTypes) { + if (!dtsManagerOptions) { fetchRemoteTypeUrlsResolve(undefined); return; } - const finalOptions = { - host: { - implementation: dtsOptions.implementation, - context: compiler.context, - moduleFederationConfig: pluginOptions, - ...normalizedConsumeTypes, - }, - extraOptions: dtsOptions.extraOptions || {}, - displayErrorInTerminal: dtsOptions.displayErrorInTerminal, - }; - - validateOptions(finalOptions.host); - const fetchRemoteTypeUrlsPromise = - typeof normalizedConsumeTypes.remoteTypeUrls === 'function' - ? normalizedConsumeTypes.remoteTypeUrls() - : Promise.resolve(normalizedConsumeTypes.remoteTypeUrls); logger.debug('start fetching remote types...'); - const promise = fetchRemoteTypeUrlsPromise.then((remoteTypeUrls) => { - consumeTypes({ - ...finalOptions, - host: { - ...finalOptions.host, - remoteTypeUrls, - }, - }) - .then(() => { - fetchRemoteTypeUrlsResolve(remoteTypeUrls); - }) - .catch(() => { - fetchRemoteTypeUrlsResolve(remoteTypeUrls); - }); - }); + const promise = consumeTypesAPI( + dtsManagerOptions, + fetchRemoteTypeUrlsResolve, + ); compiler.hooks.thisCompilation.tap('mf:generateTypes', (compilation) => { compilation.hooks.processAssets.tapPromise( diff --git a/packages/dts-plugin/src/plugins/DtsPlugin.ts b/packages/dts-plugin/src/plugins/DtsPlugin.ts index 1289b8252d1..9edcefb0a92 100644 --- a/packages/dts-plugin/src/plugins/DtsPlugin.ts +++ b/packages/dts-plugin/src/plugins/DtsPlugin.ts @@ -1,12 +1,40 @@ import { DevPlugin } from './DevPlugin'; import { normalizeOptions } from '@module-federation/sdk'; -import { ConsumeTypesPlugin } from './ConsumeTypesPlugin'; -import { GenerateTypesPlugin } from './GenerateTypesPlugin'; +import { + ConsumeTypesPlugin, + DEFAULT_CONSUME_TYPES, +} from './ConsumeTypesPlugin'; +import { + GenerateTypesPlugin, + DEFAULT_GENERATE_TYPES, +} from './GenerateTypesPlugin'; import { isTSProject } from '../core'; import { type moduleFederationPlugin } from '@module-federation/sdk'; import type { Compiler, WebpackPluginInstance } from 'webpack'; +export const normalizeDtsOptions = ( + options: moduleFederationPlugin.ModuleFederationPluginOptions, + context: string, + defaultOptions?: { + defaultGenerateOptions?: moduleFederationPlugin.DtsRemoteOptions; + defaultConsumeOptions?: moduleFederationPlugin.DtsHostOptions; + }, +) => { + return normalizeOptions( + isTSProject(options.dts, context), + { + generateTypes: + defaultOptions?.defaultGenerateOptions || DEFAULT_GENERATE_TYPES, + consumeTypes: + defaultOptions?.defaultConsumeOptions || DEFAULT_CONSUME_TYPES, + extraOptions: {}, + displayErrorInTerminal: true, + }, + 'mfOptions.dts', + )(options.dts); +}; + export class DtsPlugin implements WebpackPluginInstance { options: moduleFederationPlugin.ModuleFederationPluginOptions; constructor(options: moduleFederationPlugin.ModuleFederationPluginOptions) { @@ -16,25 +44,7 @@ export class DtsPlugin implements WebpackPluginInstance { apply(compiler: Compiler) { const { options } = this; - const defaultGenerateTypes = { - generateAPITypes: true, - compileInChildProcess: true, - abortOnError: false, - extractThirdParty: false, - extractRemoteTypes: false, - }; - const defaultConsumeTypes = { abortOnError: false, consumeAPITypes: true }; - const normalizedDtsOptions = - normalizeOptions( - isTSProject(options.dts, compiler.context), - { - generateTypes: defaultGenerateTypes, - consumeTypes: defaultConsumeTypes, - extraOptions: {}, - displayErrorInTerminal: true, - }, - 'mfOptions.dts', - )(options.dts); + const normalizedDtsOptions = normalizeDtsOptions(options, compiler.context); if (typeof normalizedDtsOptions !== 'object') { return; @@ -67,7 +77,6 @@ export class DtsPlugin implements WebpackPluginInstance { new GenerateTypesPlugin( options, normalizedDtsOptions, - defaultGenerateTypes, fetchRemoteTypeUrlsPromise, generateTypesPromiseResolve, ).apply(compiler); @@ -75,7 +84,6 @@ export class DtsPlugin implements WebpackPluginInstance { new ConsumeTypesPlugin( options, normalizedDtsOptions, - defaultConsumeTypes, fetchRemoteTypeUrlsResolve, ).apply(compiler); } diff --git a/packages/dts-plugin/src/plugins/GenerateTypesPlugin.ts b/packages/dts-plugin/src/plugins/GenerateTypesPlugin.ts index db95db25ece..95178b6ad53 100644 --- a/packages/dts-plugin/src/plugins/GenerateTypesPlugin.ts +++ b/packages/dts-plugin/src/plugins/GenerateTypesPlugin.ts @@ -16,10 +16,97 @@ import { import type { Compilation, Compiler, WebpackPluginInstance } from 'webpack'; +export const DEFAULT_GENERATE_TYPES = { + generateAPITypes: true, + compileInChildProcess: true, + abortOnError: false, + extractThirdParty: false, + extractRemoteTypes: false, +}; + +export const normalizeGenerateTypesOptions = ({ + context, + outputDir, + dtsOptions, + pluginOptions, +}: { + context?: string; + outputDir?: string; + dtsOptions: moduleFederationPlugin.PluginDtsOptions; + pluginOptions: moduleFederationPlugin.ModuleFederationPluginOptions; +}) => { + const normalizedGenerateTypes = + normalizeOptions( + true, + DEFAULT_GENERATE_TYPES, + 'mfOptions.dts.generateTypes', + )(dtsOptions.generateTypes); + + if (!normalizedGenerateTypes) { + return; + } + + const normalizedConsumeTypes = + normalizeOptions( + true, + {}, + 'mfOptions.dts.consumeTypes', + )(dtsOptions.consumeTypes); + + const finalOptions: DTSManagerOptions = { + remote: { + implementation: dtsOptions.implementation, + context, + outputDir, + moduleFederationConfig: pluginOptions, + ...normalizedGenerateTypes, + }, + host: + normalizedConsumeTypes === false + ? undefined + : { + context, + moduleFederationConfig: pluginOptions, + ...normalizedGenerateTypes, + }, + extraOptions: dtsOptions.extraOptions || {}, + displayErrorInTerminal: dtsOptions.displayErrorInTerminal, + }; + + if (dtsOptions.tsConfigPath && !finalOptions.remote.tsConfigPath) { + finalOptions.remote.tsConfigPath = dtsOptions.tsConfigPath; + } + + validateOptions(finalOptions.remote); + + return finalOptions; +}; + +export const generateTypesAPI = async ({ + dtsManagerOptions, +}: { + dtsManagerOptions: DTSManagerOptions; +}) => { + const isProd = !isDev(); + const getGenerateTypesFn = () => { + let fn: typeof generateTypes | typeof generateTypesInChildProcess = + generateTypes; + let res: ReturnType; + if (dtsManagerOptions.remote.compileInChildProcess) { + fn = generateTypesInChildProcess; + } + if (isProd) { + res = fn(dtsManagerOptions); + return () => res; + } + return fn; + }; + return getGenerateTypesFn(); +}; + export class GenerateTypesPlugin implements WebpackPluginInstance { pluginOptions: moduleFederationPlugin.ModuleFederationPluginOptions; dtsOptions: moduleFederationPlugin.PluginDtsOptions; - defaultOptions: moduleFederationPlugin.DtsRemoteOptions; fetchRemoteTypeUrlsPromise: Promise< moduleFederationPlugin.DtsHostOptions['remoteTypeUrls'] | undefined >; @@ -28,7 +115,6 @@ export class GenerateTypesPlugin implements WebpackPluginInstance { constructor( pluginOptions: moduleFederationPlugin.ModuleFederationPluginOptions, dtsOptions: moduleFederationPlugin.PluginDtsOptions, - defaultOptions: moduleFederationPlugin.DtsRemoteOptions, fetchRemoteTypeUrlsPromise: Promise< moduleFederationPlugin.DtsHostOptions['remoteTypeUrls'] | undefined >, @@ -36,85 +122,36 @@ export class GenerateTypesPlugin implements WebpackPluginInstance { ) { this.pluginOptions = pluginOptions; this.dtsOptions = dtsOptions; - this.defaultOptions = defaultOptions; this.fetchRemoteTypeUrlsPromise = fetchRemoteTypeUrlsPromise; this.callback = callback; } apply(compiler: Compiler) { - const { + const { dtsOptions, pluginOptions, fetchRemoteTypeUrlsPromise, callback } = + this; + + const outputDir = getCompilerOutputDir(compiler); + const context = compiler.context; + + const dtsManagerOptions = normalizeGenerateTypesOptions({ + context, + outputDir, dtsOptions, - defaultOptions, pluginOptions, - fetchRemoteTypeUrlsPromise, - callback, - } = this; - - const normalizedGenerateTypes = - normalizeOptions( - true, - defaultOptions, - 'mfOptions.dts.generateTypes', - )(dtsOptions.generateTypes); + }); - if (!normalizedGenerateTypes) { + if (!dtsManagerOptions) { callback(); return; } - const normalizedConsumeTypes = - normalizeOptions( - true, - defaultOptions, - 'mfOptions.dts.consumeTypes', - )(dtsOptions.consumeTypes); - - const finalOptions: DTSManagerOptions = { - remote: { - implementation: dtsOptions.implementation, - context: compiler.context, - outputDir: getCompilerOutputDir(compiler), - moduleFederationConfig: pluginOptions, - ...normalizedGenerateTypes, - }, - host: - normalizedConsumeTypes === false - ? undefined - : { - context: compiler.context, - moduleFederationConfig: pluginOptions, - ...normalizedGenerateTypes, - }, - extraOptions: dtsOptions.extraOptions || {}, - displayErrorInTerminal: dtsOptions.displayErrorInTerminal, - }; - - if (dtsOptions.tsConfigPath && !finalOptions.remote.tsConfigPath) { - finalOptions.remote.tsConfigPath = dtsOptions.tsConfigPath; - } - - validateOptions(finalOptions.remote); const isProd = !isDev(); - const getGenerateTypesFn = () => { - let fn: typeof generateTypes | typeof generateTypesInChildProcess = - generateTypes; - let res: ReturnType; - if (finalOptions.remote.compileInChildProcess) { - fn = generateTypesInChildProcess; - } - if (isProd) { - res = fn(finalOptions); - return () => res; - } - return fn; - }; - const generateTypesFn = getGenerateTypesFn(); const emitTypesFiles = async (compilation: Compilation) => { // Dev types will be generated by DevPlugin, the archive filename usually is dist/.dev-server.zip try { const { zipTypesPath, apiTypesPath, zipName, apiFileName } = - retrieveTypesAssetsInfo(finalOptions.remote); + retrieveTypesAssetsInfo(dtsManagerOptions.remote); if (isProd && zipName && compilation.getAsset(zipName)) { callback(); @@ -122,9 +159,9 @@ export class GenerateTypesPlugin implements WebpackPluginInstance { } logger.debug('start generating types...'); - await generateTypesFn(finalOptions); + await generateTypesAPI({ dtsManagerOptions }); logger.debug('generate types success!'); - const config = finalOptions.remote.moduleFederationConfig; + const config = dtsManagerOptions.remote.moduleFederationConfig; let zipPrefix = ''; if (typeof config.manifest === 'object' && config.manifest.filePath) { zipPrefix = config.manifest.filePath; @@ -165,7 +202,7 @@ export class GenerateTypesPlugin implements WebpackPluginInstance { return err.code == 'EEXIST'; }; if (zipTypesPath) { - const zipContent = fs.readFileSync(zipTypesPath); + const zipContent = fs.readFileSync(zipTypesPath, 'utf-8'); const zipOutputPath = path.join( compiler.outputPath, zipPrefix, @@ -196,7 +233,7 @@ export class GenerateTypesPlugin implements WebpackPluginInstance { } if (apiTypesPath) { - const apiContent = fs.readFileSync(apiTypesPath); + const apiContent = fs.readFileSync(apiTypesPath, 'utf-8'); const apiOutputPath = path.join( compiler.outputPath, zipPrefix, @@ -230,7 +267,7 @@ export class GenerateTypesPlugin implements WebpackPluginInstance { } } catch (err) { callback(); - if (finalOptions.displayErrorInTerminal) { + if (dtsManagerOptions.displayErrorInTerminal) { console.error('Error in mf:generateTypes processAssets hook:', err); } logger.debug('generate types fail!'); diff --git a/packages/dts-plugin/src/server/constant.ts b/packages/dts-plugin/src/server/constant.ts index 1b22c3a4444..92954009aba 100644 --- a/packages/dts-plugin/src/server/constant.ts +++ b/packages/dts-plugin/src/server/constant.ts @@ -1,6 +1,6 @@ export const DEFAULT_WEB_SOCKET_PORT = 16322; export const WEB_SOCKET_CONNECT_MAGIC_ID = '1hpzW-zo2z-o8io-gfmV1-2cb1d82'; -export const MF_SERVER_IDENTIFIER = 'Module Federation Dev Server'; +export const MF_SERVER_IDENTIFIER = 'Module Federation DTS'; export const WEB_CLIENT_OPTIONS_IDENTIFIER = '__WEB_CLIENT_OPTIONS__'; export const DEFAULT_TAR_NAME = '@mf-types.zip'; export const enum UpdateMode { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c65443f5212..ef601488511 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2037,6 +2037,28 @@ importers: specifier: 1.2.2 version: 1.2.2(@types/node@20.12.14)(@vitest/ui@1.6.0)(less@4.2.2)(stylus@0.64.0) + packages/cli: + dependencies: + '@modern-js/node-bundle-require': + specifier: 2.65.1 + version: 2.65.1 + '@module-federation/dts-plugin': + specifier: workspace:* + version: link:../dts-plugin + '@module-federation/sdk': + specifier: workspace:* + version: link:../sdk + chalk: + specifier: 3.0.0 + version: 3.0.0 + commander: + specifier: 11.1.0 + version: 11.1.0 + devDependencies: + '@types/node': + specifier: ~16.11.7 + version: 16.11.68 + packages/core: dependencies: webpack: @@ -3113,7 +3135,7 @@ packages: '@babel/traverse': 7.25.7 '@babel/types': 7.25.7 convert-source-map: 2.0.0 - debug: 4.3.7(supports-color@9.3.1) + debug: 4.3.7(supports-color@8.1.1) gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -14999,7 +15021,7 @@ packages: nx: 20.1.1(@swc-node/register@1.10.9)(@swc/core@1.7.26) semver: 7.6.3 tmp: 0.2.3 - tslib: 2.6.3 + tslib: 2.8.1 yargs-parser: 21.1.1 dev: false @@ -15015,7 +15037,7 @@ packages: nx: 20.1.4(@swc-node/register@1.10.9)(@swc/core@1.7.26) semver: 7.6.3 tmp: 0.2.3 - tslib: 2.6.3 + tslib: 2.8.1 yargs-parser: 21.1.1 dev: false @@ -15111,7 +15133,7 @@ packages: '@nx/js': 20.1.1(@swc-node/register@1.10.9)(@swc/core@1.7.26)(@types/node@20.12.14)(nx@20.1.4)(typescript@5.4.5)(verdaccio@5.29.2) eslint: 8.57.1 semver: 7.6.3 - tslib: 2.6.3 + tslib: 2.8.1 typescript: 5.4.5 transitivePeerDependencies: - '@babel/traverse' @@ -15254,7 +15276,7 @@ packages: source-map-support: 0.5.19 ts-node: 10.9.1(@swc/core@1.7.26)(@types/node@20.12.14)(typescript@5.4.5) tsconfig-paths: 4.2.0 - tslib: 2.6.3 + tslib: 2.8.1 verdaccio: 5.29.2(encoding@0.1.13)(typanion@3.14.0) transitivePeerDependencies: - '@babel/traverse' @@ -15305,7 +15327,7 @@ packages: source-map-support: 0.5.19 ts-node: 10.9.1(@swc/core@1.7.26)(@types/node@20.12.14)(typescript@5.5.2) tsconfig-paths: 4.2.0 - tslib: 2.6.3 + tslib: 2.8.1 verdaccio: 5.29.2(encoding@0.1.13)(typanion@3.14.0) transitivePeerDependencies: - '@babel/traverse' @@ -15903,7 +15925,7 @@ packages: detect-port: 1.6.1 http-server: 14.1.1 picocolors: 1.1.1 - tslib: 2.6.3 + tslib: 2.8.1 transitivePeerDependencies: - '@babel/traverse' - '@swc-node/register' @@ -16113,7 +16135,7 @@ packages: chalk: 4.1.2 enquirer: 2.3.6 nx: 20.1.4(@swc-node/register@1.10.9)(@swc/core@1.7.26) - tslib: 2.6.3 + tslib: 2.8.1 yargs-parser: 21.1.1 transitivePeerDependencies: - '@swc-node/register' @@ -18693,7 +18715,7 @@ packages: resolution: {integrity: sha512-FwTm11DP7KxQKT2mWLvwe80O5KpikgMSlqnw9CQhBaIHSYEypdJU9ZotbNsXsHdML3xcqg+S9ae3bpovC7KlwQ==} dependencies: '@rspack/core': 0.7.5(@swc/helpers@0.5.3) - caniuse-lite: 1.0.30001667 + caniuse-lite: 1.0.30001700 html-webpack-plugin: /html-rspack-plugin@5.7.2(@rspack/core@0.7.5) postcss: 8.4.47 optionalDependencies: @@ -20297,7 +20319,7 @@ packages: dependencies: '@semantic-release/error': 3.0.0 aggregate-error: 3.1.0 - debug: 4.3.7(supports-color@9.3.1) + debug: 4.3.7(supports-color@8.1.1) execa: 5.1.1 lodash: 4.17.21 parse-json: 5.2.0 @@ -20314,7 +20336,7 @@ packages: dependencies: '@semantic-release/error': 3.0.0 aggregate-error: 3.1.0 - debug: 4.3.7(supports-color@9.3.1) + debug: 4.3.7(supports-color@8.1.1) dir-glob: 3.0.1 execa: 5.1.1 lodash: 4.17.21 @@ -20325,6 +20347,33 @@ packages: - supports-color dev: true + /@semantic-release/github@11.0.0(semantic-release@24.2.3): + resolution: {integrity: sha512-Uon6G6gJD8U1JNvPm7X0j46yxNRJ8Ui6SgK4Zw5Ktu8RgjEft3BGn+l/RX1TTzhhO3/uUcKuqM+/9/ETFxWS/Q==} + engines: {node: '>=20.8.1'} + peerDependencies: + semantic-release: '>=24.1.0' + dependencies: + '@octokit/core': 6.1.2 + '@octokit/plugin-paginate-rest': 11.3.5(@octokit/core@6.1.2) + '@octokit/plugin-retry': 7.1.2(@octokit/core@6.1.2) + '@octokit/plugin-throttling': 9.3.1(@octokit/core@6.1.2) + '@semantic-release/error': 4.0.0 + aggregate-error: 5.0.0 + debug: 4.3.7(supports-color@8.1.1) + dir-glob: 3.0.1 + globby: 14.0.2 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.5 + issue-parser: 7.0.1 + lodash-es: 4.17.21 + mime: 4.0.4 + p-filter: 4.1.0 + semantic-release: 24.2.3(typescript@5.5.2) + url-join: 5.0.0 + transitivePeerDependencies: + - supports-color + dev: true + /@semantic-release/github@11.0.1(semantic-release@24.2.3): resolution: {integrity: sha512-Z9cr0LgU/zgucbT9cksH0/pX9zmVda9hkDPcgIE0uvjMQ8w/mElDivGjx1w1pEQ+MuQJ5CBq3VCF16S6G4VH3A==} engines: {node: '>=20.8.1'} @@ -22265,7 +22314,7 @@ packages: '@swc-node/sourcemap-support': 0.5.1 '@swc/core': 1.7.26(@swc/helpers@0.5.13) colorette: 2.0.20 - debug: 4.3.7(supports-color@9.3.1) + debug: 4.3.7(supports-color@8.1.1) oxc-resolver: 1.12.0 pirates: 4.0.6 tslib: 2.6.3 @@ -22278,7 +22327,7 @@ packages: resolution: {integrity: sha512-JxIvIo/Hrpv0JCHSyRpetAdQ6lB27oFYhv0PKCNf1g2gUXOjpeR1exrXccRxLMuAV5WAmGFBwRnNOJqN38+qtg==} dependencies: source-map-support: 0.5.21 - tslib: 2.6.3 + tslib: 2.8.1 /@swc/cli@0.5.0(@swc/core@1.7.26): resolution: {integrity: sha512-eFsrNt85SbHTeX6svpBNcA5DQLP/wrSyCs3KVZjbuEHWD7JGpajZOIwH74lVhyrmrXOcGxgbnxXEbDIfRlLcSw==} @@ -22546,7 +22595,7 @@ packages: /@swc/helpers@0.5.3: resolution: {integrity: sha512-FaruWX6KdudYloq1AHD/4nU+UsMTdNE8CKyrseXWEcgjDAbvkwJg2QGPAnfIJLIWsjZOSPLOAykK6fuYp4vp4A==} dependencies: - tslib: 2.6.3 + tslib: 2.8.1 dev: true /@swc/helpers@0.5.5: @@ -23738,7 +23787,7 @@ packages: '@typescript-eslint/types': 7.18.0 '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.5.2) '@typescript-eslint/visitor-keys': 7.18.0 - debug: 4.3.7(supports-color@9.3.1) + debug: 4.3.7(supports-color@8.1.1) eslint: 8.57.1 typescript: 5.5.2 transitivePeerDependencies: @@ -24344,7 +24393,7 @@ packages: peerDependencies: vitest: 1.6.0 dependencies: - debug: 4.3.7(supports-color@9.3.1) + debug: 4.3.7(supports-color@8.1.1) istanbul-lib-coverage: 3.2.2 istanbul-lib-instrument: 6.0.3 istanbul-lib-report: 3.0.1 @@ -24365,7 +24414,7 @@ packages: dependencies: '@ampproject/remapping': 2.3.0 '@bcoe/v8-coverage': 0.2.3 - debug: 4.3.7(supports-color@9.3.1) + debug: 4.3.7(supports-color@8.1.1) istanbul-lib-coverage: 3.2.2 istanbul-lib-report: 3.0.1 istanbul-lib-source-maps: 5.0.6 @@ -26121,7 +26170,7 @@ packages: postcss: ^8.1.0 dependencies: browserslist: 4.24.0 - caniuse-lite: 1.0.30001668 + caniuse-lite: 1.0.30001700 fraction.js: 4.3.7 normalize-range: 0.1.2 picocolors: 1.1.1 @@ -27739,6 +27788,11 @@ packages: engines: {node: '>=14'} dev: true + /commander@11.1.0: + resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==} + engines: {node: '>=16'} + dev: false + /commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} @@ -29292,7 +29346,6 @@ packages: dependencies: ms: 2.1.3 supports-color: 8.1.1 - dev: true /debug@4.3.7(supports-color@9.3.1): resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==} @@ -29305,6 +29358,7 @@ packages: dependencies: ms: 2.1.3 supports-color: 9.3.1 + dev: true /debug@4.4.0(supports-color@5.5.0): resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} @@ -35894,7 +35948,7 @@ packages: dependencies: copy-anything: 2.0.6 parse-node-version: 1.0.1 - tslib: 2.6.3 + tslib: 2.8.1 optionalDependencies: errno: 0.1.8 graceful-fs: 4.2.11 @@ -44033,7 +44087,7 @@ packages: fs-extra: 10.1.0 rollup: 4.24.0 semver: 7.6.3 - tslib: 2.6.3 + tslib: 2.8.1 typescript: 5.5.2 dev: true @@ -46335,6 +46389,7 @@ packages: /supports-color@9.3.1: resolution: {integrity: sha512-knBY82pjmnIzK3NifMo3RxEIRD9E0kIzV4BKcyTZ9+9kWgLMxd4PrsTSMoFQUabgRBbF8KOLRDCyKgNV+iK44Q==} engines: {node: '>=12'} + dev: true /supports-hyperlinks@2.3.0: resolution: {integrity: sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==} @@ -46426,7 +46481,7 @@ packages: engines: {node: ^14.18.0 || >=16.0.0} dependencies: '@pkgr/core': 0.1.1 - tslib: 2.6.3 + tslib: 2.8.1 dev: true /table-layout@1.0.2: @@ -47600,7 +47655,7 @@ packages: bundle-require: 4.2.1(esbuild@0.18.20) cac: 6.7.14 chokidar: 3.6.0 - debug: 4.3.7(supports-color@9.3.1) + debug: 4.3.7(supports-color@8.1.1) esbuild: 0.18.20 execa: 5.1.1 globby: 11.1.0 @@ -47642,7 +47697,7 @@ packages: cac: 6.7.14 chokidar: 3.6.0 consola: 3.2.3 - debug: 4.3.7(supports-color@9.3.1) + debug: 4.3.7(supports-color@8.1.1) esbuild: 0.23.0 execa: 5.1.1 joycon: 3.1.1 @@ -48456,7 +48511,7 @@ packages: compression: 1.7.4 cookies: 0.9.1 cors: 2.8.5 - debug: 4.3.7(supports-color@9.3.1) + debug: 4.3.7(supports-color@8.1.1) envinfo: 7.11.0 express: 4.18.2 express-rate-limit: 5.5.1 @@ -48624,7 +48679,7 @@ packages: '@volar/typescript': 2.4.5 '@vue/language-core': 2.1.6(typescript@5.5.2) compare-versions: 6.1.1 - debug: 4.3.7(supports-color@9.3.1) + debug: 4.4.0(supports-color@8.1.1) kolorist: 1.8.0 local-pkg: 0.5.0 magic-string: 0.30.17 @@ -48671,7 +48726,7 @@ packages: vite: optional: true dependencies: - debug: 4.3.7(supports-color@9.3.1) + debug: 4.3.7(supports-color@8.1.1) globrex: 0.1.2 tsconfck: 2.1.2(typescript@5.5.2) vite: 5.4.12(@types/node@18.16.9)(less@4.2.2)(stylus@0.64.0) @@ -48850,7 +48905,7 @@ packages: acorn-walk: 8.3.4 cac: 6.7.14 chai: 4.5.0 - debug: 4.3.7(supports-color@9.3.1) + debug: 4.3.7(supports-color@8.1.1) execa: 8.0.1 local-pkg: 0.5.0 magic-string: 0.30.11 @@ -48908,7 +48963,7 @@ packages: '@vitest/utils': 1.6.0 acorn-walk: 8.3.4 chai: 4.5.0 - debug: 4.3.7(supports-color@9.3.1) + debug: 4.3.7(supports-color@8.1.1) execa: 8.0.1 local-pkg: 0.5.0 magic-string: 0.30.11 From cd48f4a9cbb0d2497f7e49ee0bb13906fb6eb610 Mon Sep 17 00:00:00 2001 From: 2heal1 Date: Fri, 21 Mar 2025 11:24:36 +0800 Subject: [PATCH 2/3] chore: inline cli to enhanced and modernjs --- apps/website-new/docs/en/configure/dts.mdx | 69 +++++++++++++++++++ apps/website-new/docs/en/guide/basic/cli.mdx | 16 ++++- apps/website-new/docs/zh/configure/dts.mdx | 68 ++++++++++++++++++ apps/website-new/docs/zh/guide/basic/cli.mdx | 15 +++- packages/cli/README.md | 48 ------------- packages/cli/src/cli.ts | 17 ++--- packages/cli/src/commands/dts.ts | 20 ++---- packages/cli/src/types.ts | 7 +- .../cli/src/utils/apply-common-options.ts | 22 ++++++ packages/cli/src/utils/logger.ts | 2 +- packages/cli/src/utils/prepare.ts | 3 + packages/cli/src/utils/readConfig.ts | 21 ++++++ .../core/configurations/hostPlugin.test.ts | 57 +++++++++++++++ .../src/core/configurations/hostPlugin.ts | 48 ++++++++++--- .../src/plugins/GenerateTypesPlugin.ts | 34 +++++---- packages/enhanced/README.md | 40 +++++++++++ packages/enhanced/bin/mf.js | 4 ++ packages/enhanced/package.json | 5 ++ packages/enhanced/src/index.ts | 2 + packages/modernjs/bin/mf.js | 4 ++ packages/modernjs/package.json | 1 + packages/sdk/src/logger.ts | 5 ++ .../types/plugins/ModuleFederationPlugin.ts | 1 + pnpm-lock.yaml | 41 +++-------- 24 files changed, 415 insertions(+), 135 deletions(-) create mode 100644 packages/cli/src/utils/readConfig.ts create mode 100755 packages/enhanced/bin/mf.js create mode 100755 packages/modernjs/bin/mf.js diff --git a/apps/website-new/docs/en/configure/dts.mdx b/apps/website-new/docs/en/configure/dts.mdx index 4d7e29604a2..a007a500768 100644 --- a/apps/website-new/docs/en/configure/dts.mdx +++ b/apps/website-new/docs/en/configure/dts.mdx @@ -209,6 +209,75 @@ Before loading type files, whether to delete the previously loaded `typesFolder` `typesFolder` corresponding to `remotes` directory configuration +#### remoteTypeUrls + +- Type: `(() => Promise) | RemoteTypeUrls` +- Required: No +- Default value:`undefined` + +Used for getting the address of the `remote` type file. + +Application scenarios: + +* Only the runtime API is used to load the producer, and no build plugin is used. The MF type file address is informed by creating a `module-federation.config.ts` configuration file and setting this configuration. + +```ts title="module-federation.config.ts" +import { createModuleFederationConfig, type moduleFederationPlugin } from '@module-federation/enhanced'; + +export default createModuleFederationConfig({ + // ... + remotes: { + 'remote1-alias': 'remote1@http://localhost:80801/remoteEntry.js' + }, + dts:{ + consumeTypes:{ + remoteTypeUrls: async()=>{ + // Simulate the request interface to obtain the type file address + const data = await new Promise(resolve=>{ + setTimeout(()=>{ + resolve({ + remote1:{ + alias: 'remote1-alias', + api:'http://localhost:8081/custom-dir/@mf-types.d.ts', + zip:'http://localhost:8081/custom-dir/@mf-types.zip' + } + } ) + },1000) + }); + + return data; + } + } + } +}); +``` + + +* When remote is `remoteEntry.js`, the type file address usually directly replaces the js file with the corresponding type file, such as `@mf-types.zip`, but the actual uploaded type file address is not this, so you can tell MF the real type file address by setting this configuration. + +```ts title="module-federation.config.ts" +import { createModuleFederationConfig } from '@module-federation/enhanced'; + +export default createModuleFederationConfig({ + // ... + remotes: { + 'remote1-alias': 'remote1@http://localhost:80801/remoteEntry.js' + }, + dts:{ + consumeTypes:{ + remoteTypeUrls: { + // remote name + remote1:{ + alias: 'remote1-alias', + api:'http://localhost:8081/custom-dir/@mf-types.d.ts', + zip:'http://localhost:8081/custom-dir/@mf-types.zip' + } + } + } + } +}); +``` + ### tsConfigPath - Type: `string` diff --git a/apps/website-new/docs/en/guide/basic/cli.mdx b/apps/website-new/docs/en/guide/basic/cli.mdx index bc11c3ff2d1..fd9ce752a95 100644 --- a/apps/website-new/docs/en/guide/basic/cli.mdx +++ b/apps/website-new/docs/en/guide/basic/cli.mdx @@ -1,13 +1,13 @@ # CLI -Module Federation provides a lightweight CLI. +`@module-federation/enhanced` and `@module-federation/modern-js` provides a lightweight CLI. ## All commands To view all available CLI commands, run the following command in the project directory: ```bash -npx @module-federation/cli -h +npx mf -h ``` The output is shown below: @@ -31,6 +31,7 @@ Module Federation CLI provides several common flags that can be used with all co | Flag | Description | | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | | `-c, --config ` | Specify the configuration file, can be a relative or absolute path, default value is `module-federation.ts` | +| `-m, --mode ` | Specify the runtime environment. You can choose "dev" or "prod". The default value is "dev". After setting, the `process.env.NODE_ENV` environment variable will be automatically injected with "development" or "production" according to the value. | | `-h, --help` | Display help for command | ## mf dts @@ -48,5 +49,16 @@ Options: --fetch fetch types from remote, default is true (default: true) --generate generate types, default is true (default: true) -c --config specify the configuration file, can be a relative or absolute path + -m --mode Specify the runtime environment. You can choose "dev" or "prod". The default value is "dev". After setting, the process.env.NODE_ENV environment variable will be + automatically injected with "development" or "production" according to the value. (default: "dev") -h, --help display help for command ``` + +:::info Note + +The `mf dts` command will automatically generate or pull type declaration files based on the configuration in `module-federation.config.ts`. This means you must provide a valid configuration file, otherwise the command will not work properly. + +If you only use the runtime API, you need to create a temporary `module-federation.config.ts` file, write the remote configuration you need to get the type, and then run the `mf dts` command. +If you are only using the runtime API, you need to create a temporary `module-federation.config.ts` file, configure [dts.consumeTypes.remoteTypeUrls](../../configure/dts#remotetypeurls), and then run the `mf dts` command. + +::: \ No newline at end of file diff --git a/apps/website-new/docs/zh/configure/dts.mdx b/apps/website-new/docs/zh/configure/dts.mdx index a3445e08f52..a0253e86325 100644 --- a/apps/website-new/docs/zh/configure/dts.mdx +++ b/apps/website-new/docs/zh/configure/dts.mdx @@ -209,6 +209,74 @@ interface DtsHostOptions { 对应 `remotes` 目录配置的 `typesFolder` +#### remoteTypeUrls + +- 类型:`(() => Promise) | RemoteTypeUrls` +- 是否必填:否 +- 默认值:`undefined` + +用于获取 `remote` 类型文件的地址。 + +应用场景: + +* 只使用了 runtime API 加载生产者,没使用构建插件,通过创建 `module-federation.config.ts` 配置文件,并设置此配置,来告知 MF 类型文件地址。 + +```ts title="module-federation.config.ts" +import { createModuleFederationConfig, type moduleFederationPlugin } from '@module-federation/enhanced'; + +export default createModuleFederationConfig({ + // ... + remotes: { + 'remote1-alias': 'remote1@http://localhost:80801/remoteEntry.js' + }, + dts:{ + consumeTypes:{ + remoteTypeUrls: async()=>{ + // 模拟请求接口获取类型文件地址 + const data = await new Promise(resolve=>{ + setTimeout(()=>{ + resolve({ + remote1:{ + alias: 'remote1-alias', + api:'http://localhost:8081/custom-dir/@mf-types.d.ts', + zip:'http://localhost:8081/custom-dir/@mf-types.zip' + } + } ) + },1000) + }); + + return data; + } + } + } +}); +``` + +* 当 remote 为 `remoteEntry.js` 时,类型文件地址通常会直接替换 js 文件为对应的类型文件,例如 `@mf-types.zip` ,但是实际上传的类型文件地址不是这个,那么可以通过设置此配置来告知 MF 真正的类型文件地址。 + +```ts title="module-federation.config.ts" +import { createModuleFederationConfig } from '@module-federation/enhanced'; + +export default createModuleFederationConfig({ + // ... + remotes: { + 'remote1-alias': 'remote1@http://localhost:80801/remoteEntry.js' + }, + dts:{ + consumeTypes:{ + remoteTypeUrls: { + // remote name + remote1:{ + alias: 'remote1-alias', + api:'http://localhost:8081/custom-dir/@mf-types.d.ts', + zip:'http://localhost:8081/custom-dir/@mf-types.zip' + } + } + } + } +}); +``` + ### tsConfigPath - 类型:`string` diff --git a/apps/website-new/docs/zh/guide/basic/cli.mdx b/apps/website-new/docs/zh/guide/basic/cli.mdx index bfc5836c3ab..0889b09abbc 100644 --- a/apps/website-new/docs/zh/guide/basic/cli.mdx +++ b/apps/website-new/docs/zh/guide/basic/cli.mdx @@ -1,13 +1,13 @@ # 命令行工具 -Module Federation 提供了轻量的命令行工具。 +`@module-federation/enhanced` 和 `@module-federation/modern-js` 提供了轻量的命令行工具。 ## 查看所有命令 如果你需要查看所有可用的 CLI 命令,请在项目目录中运行以下命令: ```bash -npx @module-federation/cli -h +npx mf -h ``` 输出如下: @@ -31,6 +31,7 @@ Module Federation CLI 提供了一些公共选项,可以用于所有命令: | 选项 | 描述 | | -------------------------- | ----------------------------------------------------------------------------------------------------------------- | | `-c, --config ` | 指定配置文件路径,可以为相对路径或绝对路径,默认值为 `module-federation.config.ts` | +| `-m, --mode ` | 指定运行环境,可以选择 "dev" 或 "prod" ,默认值为 "dev" ,设置后会按照值自动往 `process.env.NODE_ENV` 环境变量注入 "development" 或 "production" | | `-h, --help` | 显示命令帮助 | ## mf dts @@ -48,5 +49,15 @@ Options: --fetch fetch types from remote, default is true (default: true) --generate generate types, default is true (default: true) -c --config specify the configuration file, can be a relative or absolute path + -m --mode Specify the runtime environment. You can choose "dev" or "prod". The default value is "dev". After setting, the process.env.NODE_ENV environment variable will be + automatically injected with "development" or "production" according to the value. (default: "dev") -h, --help display help for command ``` + +:::info 注意 + +`mf dts` 命令会自动根据 `module-federation.config.ts` 中的配置生成或拉取类型声明文件。 这意味着你必须提供一个有效的配置文件,否则命令将无法正常运行。 + +如果你是只使用了 runtime API,那么你需要创建一份临时的 `module-federation.config.ts` 文件,配置 [dts.consumeTypes.remoteTypeUrls](../../configure/dts#remotetypeurls),然后运行 `mf dts` 命令。 + +::: \ No newline at end of file diff --git a/packages/cli/README.md b/packages/cli/README.md index 17922c5831b..54444c195d1 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -13,51 +13,3 @@ Module Federation CLI license downloads

- -## All commands - -To view all available CLI commands, run the following command in the project directory: - -```bash -npx @module-federation/cli -h -``` - -The output is shown below: - -```bash -Usage: mf [options] - -Options: - -V, --version output the version number - -h, --help display help for command - -Commands: - dts [options] generate or fetch the mf types - help [command] display help for command -``` - -### mf dts - -The mf dts command is used to generate or fetch remote types. - -```bash -Usage: mf dts [options] - -generate or fetch the mf types - -Options: - --root specify the project root directory - --output specify the generated dts output directory - --fetch fetch types from remote, default is true (default: true) - --generate generate types, default is true (default: true) - -c --config specify the configuration file, can be a relative or absolute path - -h, --help display help for command -``` - -## Documentation - -See [Documentation](https://module-federation.io/guide/start/quick-start). - -## License - -[MIT](https://github.com/module-federation/core/blob/main/LICENSE). diff --git a/packages/cli/src/cli.ts b/packages/cli/src/cli.ts index 46869be23c8..6385bafec29 100644 --- a/packages/cli/src/cli.ts +++ b/packages/cli/src/cli.ts @@ -2,15 +2,13 @@ import { program } from 'commander'; import { applyCommonOptions } from './utils/apply-common-options'; import { dts } from './commands/dts'; import { prepareCli } from './utils/prepare'; +import logger, { PREFIX } from './utils/logger'; +import { readConfig } from './utils/readConfig'; import type { DtsOptions, CliOptions } from './types'; -function cli({ - name, - version, - applyCommands, - config, -}: Required): void { +function cli(cliOptions: Required): void { + const { name, version, applyCommands } = cliOptions; program.name(name).usage(' [options]').version(version); const dtsCommand = program.command('dts'); @@ -37,7 +35,7 @@ function cli({ .description('generate or fetch the mf types') .action(async (options: DtsOptions) => { try { - await dts(options, { defaultConfig: config }); + await dts(options, cliOptions); } catch (err) { console.error(err); process.exit(1); @@ -53,14 +51,17 @@ function cli({ export function runCli(options: CliOptions) { const normalizedOptions: Required = { + loggerPrefix: PREFIX, welcomeMsg: `${`Module Federation v${__VERSION__}`}\n`, name: 'mf', - config: 'module-federation.config.ts', + readConfig, version: __VERSION__, applyCommands: () => {}, ...options, }; + logger.setPrefix(normalizedOptions.loggerPrefix); + prepareCli(normalizedOptions); try { diff --git a/packages/cli/src/commands/dts.ts b/packages/cli/src/commands/dts.ts index bfa92df0781..009f3770ea7 100644 --- a/packages/cli/src/commands/dts.ts +++ b/packages/cli/src/commands/dts.ts @@ -1,5 +1,3 @@ -import path from 'path'; -import logger from '../utils/logger'; import { isTSProject, normalizeDtsOptions, @@ -8,31 +6,23 @@ import { normalizeGenerateTypesOptions, normalizeConsumeTypesOptions, } from '@module-federation/dts-plugin'; -import { bundle } from '@modern-js/node-bundle-require'; +import logger from '../utils/logger'; -import type { DtsOptions } from '../types'; -import { moduleFederationPlugin } from '@module-federation/sdk'; +import type { CliOptions, DtsOptions } from '../types'; export async function dts( options: DtsOptions, - { defaultConfig }: { defaultConfig: string }, + { readConfig }: Required, ) { - const defaultPath = path.resolve(process.cwd(), defaultConfig); const { fetch = true, generate = true, root = process.cwd(), output, - config = defaultPath, + config, } = options; - const configPath = path.isAbsolute(config) - ? config - : path.resolve(process.cwd(), config); - - const preBundlePath = await bundle(configPath); - const mfConfig = (await import(preBundlePath)).default - .default as unknown as moduleFederationPlugin.ModuleFederationPluginOptions; + const mfConfig = await readConfig(config); if (!isTSProject(mfConfig.dts, root)) { logger.error('dts is only supported for TypeScript projects'); diff --git a/packages/cli/src/types.ts b/packages/cli/src/types.ts index 85de8533f65..e187770d4c4 100644 --- a/packages/cli/src/types.ts +++ b/packages/cli/src/types.ts @@ -1,7 +1,9 @@ import type { Command } from 'commander'; +import type { moduleFederationPlugin } from '@module-federation/sdk'; export type CommonOptions = { config?: string; + mode?: string; }; export type DtsOptions = { @@ -13,9 +15,12 @@ export type DtsOptions = { export type CliOptions = { welcomeMsg?: string; + loggerPrefix?: string; name?: string; version?: string; - config?: string; + readConfig?: ( + userConfigPath?: string, + ) => Promise; applyCommands?: ( command: Command, applyCommonOptions: (command: Command) => void, diff --git a/packages/cli/src/utils/apply-common-options.ts b/packages/cli/src/utils/apply-common-options.ts index e42994d83cc..0e8be2490f1 100644 --- a/packages/cli/src/utils/apply-common-options.ts +++ b/packages/cli/src/utils/apply-common-options.ts @@ -5,4 +5,26 @@ export const applyCommonOptions = (command: Command) => { '-c --config ', 'specify the configuration file, can be a relative or absolute path', ); + command.option( + '-m --mode ', + 'Specify the runtime environment. You can choose "dev" or "prod". The default value is "dev". After setting, the process.env.NODE_ENV environment variable will be automatically injected with "development" or "production" according to the value.', + (value) => { + const validChoices = { + dev: 'development', + prod: 'production', + }; + if (!Object.keys(validChoices).includes(value)) { + throw new Error( + `Invalid mode: ${value}. Valid choices are: ${Object.keys(validChoices).join(', ')}`, + ); + } + const targetEnv = validChoices[value as keyof typeof validChoices]; + if (process.env.NODE_ENV !== targetEnv) { + console.log(`process.env.NODE_ENV is set to ${targetEnv}`); + } + process.env.NODE_ENV = targetEnv; + return value; + }, + 'dev', + ); }; diff --git a/packages/cli/src/utils/logger.ts b/packages/cli/src/utils/logger.ts index abe8d20a4ba..eba6e5c7b5e 100644 --- a/packages/cli/src/utils/logger.ts +++ b/packages/cli/src/utils/logger.ts @@ -1,7 +1,7 @@ import { type Logger, createLogger } from '@module-federation/sdk'; import chalk from 'chalk'; -const PREFIX = '[ Module Federation CLI ]'; +export const PREFIX = '[ Module Federation CLI ]'; const logger: Logger = createLogger(chalk`{bold {cyan ${PREFIX}}}`); diff --git a/packages/cli/src/utils/prepare.ts b/packages/cli/src/utils/prepare.ts index f9b3ba3045a..6c3791a33bc 100644 --- a/packages/cli/src/utils/prepare.ts +++ b/packages/cli/src/utils/prepare.ts @@ -2,6 +2,9 @@ import logger from './logger'; import type { CliOptions } from '../types'; export function prepareCli({ welcomeMsg }: Required): void { + if (!process.env.NODE_ENV) { + process.env.NODE_ENV = 'development'; + } // Print a blank line to keep the greet log nice. // Some package managers automatically output a blank line, some do not. const { npm_execpath } = process.env; diff --git a/packages/cli/src/utils/readConfig.ts b/packages/cli/src/utils/readConfig.ts new file mode 100644 index 00000000000..ebbcb78e4a8 --- /dev/null +++ b/packages/cli/src/utils/readConfig.ts @@ -0,0 +1,21 @@ +import path from 'path'; +import { bundle } from '@modern-js/node-bundle-require'; +import type { moduleFederationPlugin } from '@module-federation/sdk'; + +const DEFAULT_CONFIG_PATH = 'module-federation.config.ts'; + +export const getConfigPath = (userConfigPath?: string) => { + const defaultPath = path.resolve(process.cwd(), DEFAULT_CONFIG_PATH); + const filepath = userConfigPath ?? defaultPath; + return path.isAbsolute(filepath) + ? filepath + : path.resolve(process.cwd(), filepath); +}; + +export async function readConfig(userConfigPath?: string) { + const configPath = getConfigPath(userConfigPath); + const preBundlePath = await bundle(configPath); + const mfConfig = (await import(preBundlePath)).default + .default as unknown as moduleFederationPlugin.ModuleFederationPluginOptions; + return mfConfig; +} diff --git a/packages/dts-plugin/src/core/configurations/hostPlugin.test.ts b/packages/dts-plugin/src/core/configurations/hostPlugin.test.ts index 8027e8bb7ee..e23d9be0f25 100644 --- a/packages/dts-plugin/src/core/configurations/hostPlugin.test.ts +++ b/packages/dts-plugin/src/core/configurations/hostPlugin.test.ts @@ -147,5 +147,62 @@ describe('hostPlugin', () => { }, }); }); + + it('correctly resolve remoteTypeUrls', () => { + const mfConfigWithRemoteTypeUrls = { + ...moduleFederationConfig, + remotes: { + 'remote1-alias': 'remote1@http://localhost:3001/remoteEntry.js', + 'remote2-alias': 'remote2@http://localhost:3002/remoteEntry.js', + }, + }; + + const { mapRemotesToDownload } = retrieveHostConfig({ + moduleFederationConfig: mfConfigWithRemoteTypeUrls, + remoteTypeUrls: { + remote1: { + alias: 'remote1-alias', + zip: 'http://localhost:3001/custom-dir/@mf-types.zip', + api: 'http://localhost:3001/custom-dir/@mf-types.d.ts', + }, + }, + }); + + expect(mapRemotesToDownload).toStrictEqual({ + 'remote1-alias': { + name: 'remote1', + alias: 'remote1-alias', + url: '', + zipUrl: 'http://localhost:3001/custom-dir/@mf-types.zip', + apiTypeUrl: 'http://localhost:3001/custom-dir/@mf-types.d.ts', + }, + 'remote2-alias': { + name: 'remote2', + alias: 'remote2-alias', + url: 'http://localhost:3002/remoteEntry.js', + zipUrl: 'http://localhost:3002/@mf-types.zip', + apiTypeUrl: 'http://localhost:3002/@mf-types.d.ts', + }, + }); + }); + + it('throw error if typeof remoteTypeUrls is not object', () => { + const options = { + moduleFederationConfig, + remoteTypeUrls: () => + Promise.resolve({ + remote1: { + alias: 'remote1-alias', + zip: 'http://localhost:3001/custom-dir/@mf-types.zip', + api: 'http://localhost:3001/custom-dir/@mf-types.d.ts', + }, + }), + }; + + const invokeRetrieve = () => retrieveHostConfig(options); + expect(invokeRetrieve).toThrowError( + 'remoteTypeUrls must be consumed before resolveRemotes', + ); + }); }); }); diff --git a/packages/dts-plugin/src/core/configurations/hostPlugin.ts b/packages/dts-plugin/src/core/configurations/hostPlugin.ts index e10406d416f..19510040d87 100644 --- a/packages/dts-plugin/src/core/configurations/hostPlugin.ts +++ b/packages/dts-plugin/src/core/configurations/hostPlugin.ts @@ -64,10 +64,13 @@ export const retrieveRemoteInfo = (options: { let zipUrl = ''; let apiTypeUrl = ''; const name = parsedInfo.name || remoteAlias; + + // for updated remote if (typeof remoteTypeUrls === 'object' && remoteTypeUrls[name]) { zipUrl = remoteTypeUrls[name].zip; apiTypeUrl = remoteTypeUrls[name].api; } + if (!zipUrl && url) { zipUrl = buildZipUrl(hostOptions, url); } @@ -98,18 +101,45 @@ const resolveRemotes = (hostOptions: Required) => { }), ); - return parsedOptions.reduce( - (accumulator, item) => { - const { key, remote } = item[1]; - accumulator[key] = retrieveRemoteInfo({ - hostOptions, - remoteAlias: key, - remote, - }); - return accumulator; + const remoteTypeUrls = hostOptions.remoteTypeUrls ?? {}; + if (typeof remoteTypeUrls !== 'object') { + throw new Error('remoteTypeUrls must be consumed before resolveRemotes'); + } + const remoteInfos = Object.keys(remoteTypeUrls).reduce( + (sum, remoteName) => { + const { zip, api, alias } = remoteTypeUrls[remoteName]; + sum[alias] = { + name: remoteName, + url: '', + zipUrl: zip, + apiTypeUrl: api, + alias: alias || remoteName, + }; + return sum; }, {} as Record, ); + + return parsedOptions.reduce((accumulator, item) => { + const { key, remote } = item[1]; + const res = retrieveRemoteInfo({ + hostOptions, + remoteAlias: key, + remote, + }); + + if (accumulator[key]) { + accumulator[key] = { + ...accumulator[key], + url: res.url, + apiTypeUrl: accumulator[key].apiTypeUrl || res.apiTypeUrl, + }; + return accumulator; + } + + accumulator[key] = res; + return accumulator; + }, remoteInfos); }; export const retrieveHostConfig = (options: HostOptions) => { diff --git a/packages/dts-plugin/src/plugins/GenerateTypesPlugin.ts b/packages/dts-plugin/src/plugins/GenerateTypesPlugin.ts index 95178b6ad53..c014fc29cda 100644 --- a/packages/dts-plugin/src/plugins/GenerateTypesPlugin.ts +++ b/packages/dts-plugin/src/plugins/GenerateTypesPlugin.ts @@ -82,26 +82,22 @@ export const normalizeGenerateTypesOptions = ({ return finalOptions; }; -export const generateTypesAPI = async ({ +const getGenerateTypesFn = (dtsManagerOptions: DTSManagerOptions) => { + let fn: typeof generateTypes | typeof generateTypesInChildProcess = + generateTypes; + if (dtsManagerOptions.remote.compileInChildProcess) { + fn = generateTypesInChildProcess; + } + return fn; +}; + +export const generateTypesAPI = ({ dtsManagerOptions, }: { dtsManagerOptions: DTSManagerOptions; }) => { - const isProd = !isDev(); - const getGenerateTypesFn = () => { - let fn: typeof generateTypes | typeof generateTypesInChildProcess = - generateTypes; - let res: ReturnType; - if (dtsManagerOptions.remote.compileInChildProcess) { - fn = generateTypesInChildProcess; - } - if (isProd) { - res = fn(dtsManagerOptions); - return () => res; - } - return fn; - }; - return getGenerateTypesFn(); + const fn = getGenerateTypesFn(dtsManagerOptions); + return fn(dtsManagerOptions); }; export class GenerateTypesPlugin implements WebpackPluginInstance { @@ -202,7 +198,7 @@ export class GenerateTypesPlugin implements WebpackPluginInstance { return err.code == 'EEXIST'; }; if (zipTypesPath) { - const zipContent = fs.readFileSync(zipTypesPath, 'utf-8'); + const zipContent = fs.readFileSync(zipTypesPath); const zipOutputPath = path.join( compiler.outputPath, zipPrefix, @@ -217,6 +213,7 @@ export class GenerateTypesPlugin implements WebpackPluginInstance { } else { compiler.outputFileSystem.writeFile( zipOutputPath, + // @ts-ignore zipContent, (writeErr) => { if (writeErr && !isEEXIST(writeErr)) { @@ -233,7 +230,7 @@ export class GenerateTypesPlugin implements WebpackPluginInstance { } if (apiTypesPath) { - const apiContent = fs.readFileSync(apiTypesPath, 'utf-8'); + const apiContent = fs.readFileSync(apiTypesPath); const apiOutputPath = path.join( compiler.outputPath, zipPrefix, @@ -248,6 +245,7 @@ export class GenerateTypesPlugin implements WebpackPluginInstance { } else { compiler.outputFileSystem.writeFile( apiOutputPath, + // @ts-ignore apiContent, (writeErr) => { if (writeErr && !isEEXIST(writeErr)) { diff --git a/packages/enhanced/README.md b/packages/enhanced/README.md index 9129c2060de..78874298f26 100644 --- a/packages/enhanced/README.md +++ b/packages/enhanced/README.md @@ -171,3 +171,43 @@ Once set, the runtime plugin is automatically injected and used at build time. - Default: `undefined` Used to modify the actual bundler runtime version. Path with value `@module-federation/runtime-tools`. + +## CLI + +To view all available CLI commands, run the following command in the project directory: + +```bash +npx mf -h +``` + +The output is shown below: + +```bash +Usage: mf [options] + +Options: + -V, --version output the version number + -h, --help display help for command + +Commands: + dts [options] generate or fetch the mf types + help [command] display help for command +``` + +### mf dts + +The mf dts command is used to generate or fetch remote types. + +```bash +Usage: mf dts [options] + +generate or fetch the mf types + +Options: + --root specify the project root directory + --output specify the generated dts output directory + --fetch fetch types from remote, default is true (default: true) + --generate generate types, default is true (default: true) + -c --config specify the configuration file, can be a relative or absolute path + -h, --help display help for command +``` diff --git a/packages/enhanced/bin/mf.js b/packages/enhanced/bin/mf.js new file mode 100755 index 00000000000..6ac43ef8105 --- /dev/null +++ b/packages/enhanced/bin/mf.js @@ -0,0 +1,4 @@ +#!/usr/bin/env node +const { runCli } = require('@module-federation/cli'); + +runCli(); diff --git a/packages/enhanced/package.json b/packages/enhanced/package.json index c6bec2cd9e1..eadd24eb0e3 100644 --- a/packages/enhanced/package.json +++ b/packages/enhanced/package.json @@ -9,9 +9,13 @@ "directory": "packages/enhanced" }, "files": [ + "bin", "dist/", "README.md" ], + "bin": { + "mf": "./bin/mf.js" + }, "license": "MIT", "publishConfig": { "access": "public" @@ -96,6 +100,7 @@ "@module-federation/rspack": "workspace:*", "@module-federation/runtime-tools": "workspace:*", "@module-federation/sdk": "workspace:*", + "@module-federation/cli": "workspace:*", "btoa": "^1.2.1", "upath": "2.0.1" } diff --git a/packages/enhanced/src/index.ts b/packages/enhanced/src/index.ts index b383f748dc0..be7892e39fd 100644 --- a/packages/enhanced/src/index.ts +++ b/packages/enhanced/src/index.ts @@ -32,3 +32,5 @@ export const createModuleFederationConfig = ( ): moduleFederationPlugin.ModuleFederationPluginOptions => { return options; }; + +export type { moduleFederationPlugin }; diff --git a/packages/modernjs/bin/mf.js b/packages/modernjs/bin/mf.js new file mode 100755 index 00000000000..6ac43ef8105 --- /dev/null +++ b/packages/modernjs/bin/mf.js @@ -0,0 +1,4 @@ +#!/usr/bin/env node +const { runCli } = require('@module-federation/cli'); + +runCli(); diff --git a/packages/modernjs/package.json b/packages/modernjs/package.json index b073d4a06a7..20ac9d83462 100644 --- a/packages/modernjs/package.json +++ b/packages/modernjs/package.json @@ -93,6 +93,7 @@ "@module-federation/enhanced": "workspace:*", "@module-federation/node": "workspace:*", "@module-federation/sdk": "workspace:*", + "@module-federation/cli": "workspace:*", "@swc/helpers": "0.5.13", "node-fetch": "~3.3.0", "react-error-boundary": "4.1.2" diff --git a/packages/sdk/src/logger.ts b/packages/sdk/src/logger.ts index 35db772bdc3..5368eb0f3d1 100644 --- a/packages/sdk/src/logger.ts +++ b/packages/sdk/src/logger.ts @@ -7,6 +7,11 @@ class Logger { constructor(prefix: string) { this.prefix = prefix; } + + setPrefix(prefix: string) { + this.prefix = prefix; + } + log(...args: any[]) { console.log(this.prefix, ...args); } diff --git a/packages/sdk/src/types/plugins/ModuleFederationPlugin.ts b/packages/sdk/src/types/plugins/ModuleFederationPlugin.ts index d8de9623556..6abf771277f 100644 --- a/packages/sdk/src/types/plugins/ModuleFederationPlugin.ts +++ b/packages/sdk/src/types/plugins/ModuleFederationPlugin.ts @@ -137,6 +137,7 @@ export interface PluginDevOptions { } interface RemoteTypeUrl { + alias?: string; api: string; zip: string; } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ef601488511..320c85c2643 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2244,6 +2244,9 @@ importers: '@module-federation/bridge-react-webpack-plugin': specifier: workspace:* version: link:../bridge/bridge-react-webpack-plugin + '@module-federation/cli': + specifier: workspace:* + version: link:../cli '@module-federation/data-prefetch': specifier: workspace:* version: link:../data-prefetch @@ -2392,6 +2395,9 @@ importers: '@modern-js/utils': specifier: 2.65.1 version: 2.65.1 + '@module-federation/cli': + specifier: workspace:* + version: link:../cli '@module-federation/enhanced': specifier: workspace:* version: link:../enhanced @@ -18715,7 +18721,7 @@ packages: resolution: {integrity: sha512-FwTm11DP7KxQKT2mWLvwe80O5KpikgMSlqnw9CQhBaIHSYEypdJU9ZotbNsXsHdML3xcqg+S9ae3bpovC7KlwQ==} dependencies: '@rspack/core': 0.7.5(@swc/helpers@0.5.3) - caniuse-lite: 1.0.30001700 + caniuse-lite: 1.0.30001667 html-webpack-plugin: /html-rspack-plugin@5.7.2(@rspack/core@0.7.5) postcss: 8.4.47 optionalDependencies: @@ -20347,33 +20353,6 @@ packages: - supports-color dev: true - /@semantic-release/github@11.0.0(semantic-release@24.2.3): - resolution: {integrity: sha512-Uon6G6gJD8U1JNvPm7X0j46yxNRJ8Ui6SgK4Zw5Ktu8RgjEft3BGn+l/RX1TTzhhO3/uUcKuqM+/9/ETFxWS/Q==} - engines: {node: '>=20.8.1'} - peerDependencies: - semantic-release: '>=24.1.0' - dependencies: - '@octokit/core': 6.1.2 - '@octokit/plugin-paginate-rest': 11.3.5(@octokit/core@6.1.2) - '@octokit/plugin-retry': 7.1.2(@octokit/core@6.1.2) - '@octokit/plugin-throttling': 9.3.1(@octokit/core@6.1.2) - '@semantic-release/error': 4.0.0 - aggregate-error: 5.0.0 - debug: 4.3.7(supports-color@8.1.1) - dir-glob: 3.0.1 - globby: 14.0.2 - http-proxy-agent: 7.0.2 - https-proxy-agent: 7.0.5 - issue-parser: 7.0.1 - lodash-es: 4.17.21 - mime: 4.0.4 - p-filter: 4.1.0 - semantic-release: 24.2.3(typescript@5.5.2) - url-join: 5.0.0 - transitivePeerDependencies: - - supports-color - dev: true - /@semantic-release/github@11.0.1(semantic-release@24.2.3): resolution: {integrity: sha512-Z9cr0LgU/zgucbT9cksH0/pX9zmVda9hkDPcgIE0uvjMQ8w/mElDivGjx1w1pEQ+MuQJ5CBq3VCF16S6G4VH3A==} engines: {node: '>=20.8.1'} @@ -26170,7 +26149,7 @@ packages: postcss: ^8.1.0 dependencies: browserslist: 4.24.0 - caniuse-lite: 1.0.30001700 + caniuse-lite: 1.0.30001668 fraction.js: 4.3.7 normalize-range: 0.1.2 picocolors: 1.1.1 @@ -36305,7 +36284,7 @@ packages: engines: {node: '>=8.0'} dependencies: date-format: 4.0.14 - debug: 4.3.7(supports-color@9.3.1) + debug: 4.3.7(supports-color@8.1.1) flatted: 3.3.1 rfdc: 1.4.1 streamroller: 3.1.5 @@ -48706,7 +48685,7 @@ packages: '@volar/typescript': 2.4.5 '@vue/language-core': 2.1.6(typescript@5.5.2) compare-versions: 6.1.1 - debug: 4.3.7(supports-color@9.3.1) + debug: 4.3.7(supports-color@8.1.1) kolorist: 1.8.0 local-pkg: 0.5.0 magic-string: 0.30.17 From 591aba98c0995f1bc1479f4a2d2a7ec2b581169a Mon Sep 17 00:00:00 2001 From: 2heal1 Date: Mon, 24 Mar 2025 21:22:51 +0800 Subject: [PATCH 3/3] fix: dts ut --- packages/dts-plugin/src/core/configurations/hostPlugin.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/dts-plugin/src/core/configurations/hostPlugin.test.ts b/packages/dts-plugin/src/core/configurations/hostPlugin.test.ts index e23d9be0f25..c7aa232927f 100644 --- a/packages/dts-plugin/src/core/configurations/hostPlugin.test.ts +++ b/packages/dts-plugin/src/core/configurations/hostPlugin.test.ts @@ -172,7 +172,7 @@ describe('hostPlugin', () => { 'remote1-alias': { name: 'remote1', alias: 'remote1-alias', - url: '', + url: 'http://localhost:3001/remoteEntry.js', zipUrl: 'http://localhost:3001/custom-dir/@mf-types.zip', apiTypeUrl: 'http://localhost:3001/custom-dir/@mf-types.d.ts', },