diff --git a/.changeset/real-otters-explode.md b/.changeset/real-otters-explode.md deleted file mode 100644 index ace3d1be84..0000000000 --- a/.changeset/real-otters-explode.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@module-federation/dts-plugin': patch -'@module-federation/sdk': patch ---- - -add `cwd` property to generate types diff --git a/.changeset/six-yaks-teach.md b/.changeset/six-yaks-teach.md new file mode 100644 index 0000000000..9faee34875 --- /dev/null +++ b/.changeset/six-yaks-teach.md @@ -0,0 +1,5 @@ +--- +'@module-federation/rspack': patch +--- + +fix(rspack): import plugin from sub path diff --git a/.changeset/twelve-dingos-ring.md b/.changeset/twelve-dingos-ring.md new file mode 100644 index 0000000000..4202f800a2 --- /dev/null +++ b/.changeset/twelve-dingos-ring.md @@ -0,0 +1,5 @@ +--- +'@module-federation/dts-plugin': patch +--- + +Lazy emit DTS files on hmr rebuilds, do not block compiler pipeline diff --git a/.cursorignore b/.cursorignore index df6eeb993f..4a2b9586d0 100644 --- a/.cursorignore +++ b/.cursorignore @@ -2,12 +2,15 @@ ./tmp ./scripts ./.git -./packages/storybook-addon -./packages/core -./packages/utilities -./packages/typescript -./packages/native-* -./apps +packages/storybook-addon +packages/core +packages/utilities +packages/typescript +packages/native-* +apps **/configCases +**/dist apps/** *.snap +*.js + diff --git a/apps/manifest-demo/3010-rspack-provider/rspack.config.js b/apps/manifest-demo/3010-rspack-provider/rspack.config.js index 00828a45fb..a6aa815f2d 100644 --- a/apps/manifest-demo/3010-rspack-provider/rspack.config.js +++ b/apps/manifest-demo/3010-rspack-provider/rspack.config.js @@ -42,6 +42,7 @@ module.exports = composePlugins( transform: { react: { runtime: 'automatic', + refresh: true, }, }, }, @@ -60,6 +61,18 @@ module.exports = composePlugins( // publicPath must be specific url config.output.publicPath = 'http://localhost:3010/'; + const rspackPlugin = config.plugins.find((plugin) => { + return plugin.name === 'HtmlRspackPlugin'; + }); + + if (rspackPlugin && rspackPlugin._args && rspackPlugin._args[0]) { + rspackPlugin._args[0].excludeChunks = ['rspack_provider']; + } else { + console.warn( + 'HtmlRspackPlugin not found or has unexpected structure. Skipping excludeChunks configuration.', + ); + } + config.plugins.push( new ModuleFederationPlugin({ name: 'rspack_provider', @@ -70,10 +83,10 @@ module.exports = composePlugins( shared: { lodash: {}, antd: {}, - 'react/': { - singleton: true, - requiredVersion: '^18.3.1', - }, + // 'react/': { + // singleton: true, + // requiredVersion: '^18.3.1', + // }, react: { singleton: true, requiredVersion: '^18.3.1', diff --git a/apps/modernjs/CHANGELOG.md b/apps/modernjs/CHANGELOG.md index 52b7ee8d97..088f8e6978 100644 --- a/apps/modernjs/CHANGELOG.md +++ b/apps/modernjs/CHANGELOG.md @@ -1,5 +1,11 @@ # @module-federation/modernjsapp +## 0.1.74 + +### Patch Changes + +- @module-federation/enhanced@0.8.3 + ## 0.1.73 ### Patch Changes diff --git a/apps/modernjs/package.json b/apps/modernjs/package.json index d2d758dee1..332980372f 100644 --- a/apps/modernjs/package.json +++ b/apps/modernjs/package.json @@ -1,7 +1,7 @@ { "name": "@module-federation/modernjsapp", "private": true, - "version": "0.1.73", + "version": "0.1.74", "scripts": { "reset": "npx rimraf ./**/node_modules", "dev": "modern dev", diff --git a/apps/react-ts-host/webpack.config.js b/apps/react-ts-host/webpack.config.js index bea8c49833..d94979294d 100644 --- a/apps/react-ts-host/webpack.config.js +++ b/apps/react-ts-host/webpack.config.js @@ -12,6 +12,8 @@ module.exports = composePlugins( withNx(), withReact(), async (config, context) => { + config.devServer = config.devServer || {}; + config.devServer.host = '127.0.0.1'; // prevent cyclic updates config.watchOptions = { ignored: ['**/node_modules/**', '**/@mf-types/**'], diff --git a/apps/react-ts-nested-remote/webpack.config.js b/apps/react-ts-nested-remote/webpack.config.js index 9602c9ddeb..111c7cf458 100644 --- a/apps/react-ts-nested-remote/webpack.config.js +++ b/apps/react-ts-nested-remote/webpack.config.js @@ -11,6 +11,10 @@ module.exports = composePlugins( withNx(), withReact(), async (config, context) => { + if (!config.devServer) { + config.devServer = {}; + } + config.devServer.host = '127.0.0.1'; config.output.publicPath = 'http://localhost:3005/'; // prevent cyclic updates config.watchOptions = { diff --git a/apps/react-ts-remote/rspack.config.js b/apps/react-ts-remote/rspack.config.js index 3045d124ec..446733ae96 100644 --- a/apps/react-ts-remote/rspack.config.js +++ b/apps/react-ts-remote/rspack.config.js @@ -88,6 +88,7 @@ module.exports = composePlugins( client: { overlay: false, }, + host: '127.0.0.1', port: 3004, devMiddleware: { writeToDisk: true, diff --git a/apps/react-ts-remote/webpack.config.js b/apps/react-ts-remote/webpack.config.js index 929a76487e..d0c8977209 100644 --- a/apps/react-ts-remote/webpack.config.js +++ b/apps/react-ts-remote/webpack.config.js @@ -12,6 +12,10 @@ module.exports = composePlugins( withNx(), withReact(), async (config, context) => { + if (!config.devServer) { + config.devServer = {}; + } + config.devServer.host = '127.0.0.1'; const baseConfig = { name: 'react_ts_remote', filename: 'remoteEntry.js', diff --git a/apps/website-new/CHANGELOG.md b/apps/website-new/CHANGELOG.md index 8a23d6ee48..8fac018a1f 100644 --- a/apps/website-new/CHANGELOG.md +++ b/apps/website-new/CHANGELOG.md @@ -1,5 +1,11 @@ # website-new +## 1.0.16 + +### Patch Changes + +- @module-federation/error-codes@0.8.3 + ## 1.0.15 ### Patch Changes diff --git a/apps/website-new/docs/en/guide/basic/runtime.mdx b/apps/website-new/docs/en/guide/basic/runtime.mdx index 8db9c3f644..fa8ed50a9b 100644 --- a/apps/website-new/docs/en/guide/basic/runtime.mdx +++ b/apps/website-new/docs/en/guide/basic/runtime.mdx @@ -80,6 +80,8 @@ type InitOptions = { shared?: { [pkgName: string]: ShareArgs | ShareArgs[]; }; + // Sharing strategy, which strategy will be used to decide whether to reuse the dependency + shareStrategy?: 'version-first' | 'loaded-first'; }; type ShareArgs = @@ -91,7 +93,6 @@ type SharedBaseArgs = { shareConfig?: SharedConfig; scope?: string | Array; deps?: Array; - strategy?: 'version-first' | 'loaded-first'; loaded?: boolean; }; @@ -104,41 +105,11 @@ type RemoteInfo = (RemotesWithEntry | RemotesWithVersion) & { interface RemotesWithVersion { name: string; version: string; -} +}; interface RemotesWithEntry { name: string; entry: string; -} - -type ShareInfos = { - // The name of the dependency, basic information about the dependency, and sharing strategy - [pkgName: string]: Shared[]; -}; - -type Shared = { - // The version of the shared dependency - version: string; - // Which modules are currently consuming this dependency - useIn: Array; - // From which module does the shared dependency come? - from: string; - // Factory function to get the shared dependency instance. When no other existing dependencies, it will load its own shared dependencies. - lib?: () => Module; - // Sharing strategy, which strategy will be used to decide whether to reuse the dependency - shareConfig: SharedConfig; - // The scope where the shared dependency is located, the default value is default - scope: Array; - // Function to retrieve the shared dependency instance. - get: SharedGetter; - // List of dependencies that this shared module depends on - deps: Array; - // Indicates whether the shared dependency has been loaded - loaded?: boolean; - // Represents the loading state of the shared dependency - loading?: null | Promise; - // Determines if the shared dependency should be loaded eagerly - eager?: boolean; }; ``` @@ -176,6 +147,38 @@ loadRemote('app2/util').then((m) => m.add(1, 2, 3)); - Obtains the `share` dependency. When a "shared" dependency matching the current consumer exists in the global environment, the existing and eligible dependency will be reused first. Otherwise, it loads its own dependency and stores it in the global cache. - This `API` is usually not called directly by users but is used by the build plugin to convert its own dependencies. +```typescript +type ShareInfos = { + // The name of the dependency, basic information about the dependency, and sharing strategy + [pkgName: string]: Shared[]; +}; + +type Shared = { + // The version of the shared dependency + version: string; + // Which modules are currently consuming this dependency + useIn: Array; + // From which module does the shared dependency come? + from: string; + // Factory function to get the shared dependency instance. When no other existing dependencies, it will load its own shared dependencies. + lib?: () => Module; + // Sharing strategy, which strategy will be used to decide whether to reuse the dependency + shareConfig: SharedConfig; + // The scope where the shared dependency is located, the default value is default + scope: Array; + // Function to retrieve the shared dependency instance. + get: SharedGetter; + // List of dependencies that this shared module depends on + deps: Array; + // Indicates whether the shared dependency has been loaded + loaded?: boolean; + // Represents the loading state of the shared dependency + loading?: null | Promise; + // Determines if the shared dependency should be loaded eagerly + eager?: boolean; +}; +``` + - Example ```javascript diff --git a/apps/website-new/package.json b/apps/website-new/package.json index dd36815b06..97dfefea3b 100644 --- a/apps/website-new/package.json +++ b/apps/website-new/package.json @@ -1,6 +1,6 @@ { "name": "website-new", - "version": "1.0.15", + "version": "1.0.16", "private": true, "scripts": { "dev": "rspress dev", diff --git a/package.json b/package.json index e7a4eab325..d3d66e1c2e 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "prepare": "husky install", "changeset": "changeset", "build:packages": "npx nx affected -t build --parallel=10 --exclude='*,!tag:type:pkg'", - "changegen": "./changeset-gen.js --path ./packages/enhanced --staged &&./changeset-gen.js --path ./packages/node --staged && ./changeset-gen.js --path ./packages/runtime --staged && ./changeset-gen.js --path ./packages/data-prefetch --staged && ./changeset-gen.js --path ./packages/nextjs-mf --staged", + "changegen": "./changeset-gen.js --path ./packages/enhanced --staged && ./changeset-gen.js --path ./packages/node --staged && ./changeset-gen.js --path ./packages/runtime --staged && ./changeset-gen.js --path ./packages/data-prefetch --staged && ./changeset-gen.js --path ./packages/nextjs-mf --staged && ./changeset-gen.js --path ./packages/dts-plugin --staged", "commitgen:staged": "./commit-gen.js --path ./packages --staged", "commitgen:main": "./commit-gen.js --path ./packages", "changeset:status": "changeset status" diff --git a/packages/assemble-release-plan/package.json b/packages/assemble-release-plan/package.json index d493aa1017..e7a8433014 100644 --- a/packages/assemble-release-plan/package.json +++ b/packages/assemble-release-plan/package.json @@ -30,7 +30,11 @@ "release": "yarn build && changeset publish" }, "license": "MIT", - "repository": "https://github.com/changesets/changesets/tree/main/packages/assemble-release-plan", + "repository": { + "type": "git", + "url": "https://github.com/module-federation/core/", + "directory": "packages/assemble-release-plan" + }, "dependencies": { "@changesets/errors": "^0.2.0", "@changesets/get-dependents-graph": "^2.1.2", diff --git a/packages/bridge/bridge-react-webpack-plugin/CHANGELOG.md b/packages/bridge/bridge-react-webpack-plugin/CHANGELOG.md index a4d1f4798f..7c8003addc 100644 --- a/packages/bridge/bridge-react-webpack-plugin/CHANGELOG.md +++ b/packages/bridge/bridge-react-webpack-plugin/CHANGELOG.md @@ -1,5 +1,12 @@ # @module-federation/bridge-react-webpack-plugin +## 0.8.3 + +### Patch Changes + +- Updated dependencies [8e172c8] + - @module-federation/sdk@0.8.3 + ## 0.8.2 ### Patch Changes diff --git a/packages/bridge/bridge-react-webpack-plugin/package.json b/packages/bridge/bridge-react-webpack-plugin/package.json index fdafd9c7f6..bbdc74c355 100644 --- a/packages/bridge/bridge-react-webpack-plugin/package.json +++ b/packages/bridge/bridge-react-webpack-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@module-federation/bridge-react-webpack-plugin", - "version": "0.8.2", + "version": "0.8.3", "publishConfig": { "access": "public" }, diff --git a/packages/bridge/bridge-react/CHANGELOG.md b/packages/bridge/bridge-react/CHANGELOG.md index d703d25a6c..6f77fdce04 100644 --- a/packages/bridge/bridge-react/CHANGELOG.md +++ b/packages/bridge/bridge-react/CHANGELOG.md @@ -1,5 +1,13 @@ # @module-federation/bridge-react +## 0.8.3 + +### Patch Changes + +- Updated dependencies [8e172c8] + - @module-federation/sdk@0.8.3 + - @module-federation/bridge-shared@0.8.3 + ## 0.8.2 ### Patch Changes diff --git a/packages/bridge/bridge-react/package.json b/packages/bridge/bridge-react/package.json index 86ac568c72..2c0eff18d9 100644 --- a/packages/bridge/bridge-react/package.json +++ b/packages/bridge/bridge-react/package.json @@ -1,6 +1,6 @@ { "name": "@module-federation/bridge-react", - "version": "0.8.2", + "version": "0.8.3", "publishConfig": { "access": "public" }, diff --git a/packages/bridge/bridge-shared/CHANGELOG.md b/packages/bridge/bridge-shared/CHANGELOG.md index e7a0faf68d..1b17918115 100644 --- a/packages/bridge/bridge-shared/CHANGELOG.md +++ b/packages/bridge/bridge-shared/CHANGELOG.md @@ -1,5 +1,7 @@ # @module-federation/bridge-shared +## 0.8.3 + ## 0.8.2 ## 0.8.1 diff --git a/packages/bridge/bridge-shared/package.json b/packages/bridge/bridge-shared/package.json index aca77927f5..4d3f84419f 100644 --- a/packages/bridge/bridge-shared/package.json +++ b/packages/bridge/bridge-shared/package.json @@ -1,6 +1,6 @@ { "name": "@module-federation/bridge-shared", - "version": "0.8.2", + "version": "0.8.3", "publishConfig": { "access": "public" }, diff --git a/packages/bridge/vue3-bridge/CHANGELOG.md b/packages/bridge/vue3-bridge/CHANGELOG.md index 7c2aea184e..ea43a049b8 100644 --- a/packages/bridge/vue3-bridge/CHANGELOG.md +++ b/packages/bridge/vue3-bridge/CHANGELOG.md @@ -1,5 +1,15 @@ # @module-federation/bridge-vue3 +## 0.8.3 + +### Patch Changes + +- Updated dependencies [f817674] +- Updated dependencies [8e172c8] + - @module-federation/runtime@0.8.3 + - @module-federation/sdk@0.8.3 + - @module-federation/bridge-shared@0.8.3 + ## 0.8.2 ### Patch Changes diff --git a/packages/bridge/vue3-bridge/package.json b/packages/bridge/vue3-bridge/package.json index 794f834d97..fc8cdbdb46 100644 --- a/packages/bridge/vue3-bridge/package.json +++ b/packages/bridge/vue3-bridge/package.json @@ -7,7 +7,7 @@ "url": "https://github.com/module-federation/core", "directory": "packages/vue3-bridge" }, - "version": "0.8.2", + "version": "0.8.3", "publishConfig": { "access": "public" }, diff --git a/packages/chrome-devtools/CHANGELOG.md b/packages/chrome-devtools/CHANGELOG.md index e26fbaaa4a..655bac80db 100644 --- a/packages/chrome-devtools/CHANGELOG.md +++ b/packages/chrome-devtools/CHANGELOG.md @@ -1,5 +1,12 @@ # @module-federation/devtools +## 0.8.3 + +### Patch Changes + +- Updated dependencies [8e172c8] + - @module-federation/sdk@0.8.3 + ## 0.8.2 ### Patch Changes diff --git a/packages/chrome-devtools/package.json b/packages/chrome-devtools/package.json index 24291131d8..11428412d2 100644 --- a/packages/chrome-devtools/package.json +++ b/packages/chrome-devtools/package.json @@ -1,6 +1,6 @@ { "name": "@module-federation/devtools", - "version": "0.8.2", + "version": "0.8.3", "license": "MIT", "repository": { "type": "git", diff --git a/packages/core/package.json b/packages/core/package.json index 4d3bd6b391..9215a693ea 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -12,7 +12,11 @@ "publishConfig": { "access": "public" }, - "repository": "https://github.com/module-federation/core/tree/main/packages/core", + "repository": { + "type": "git", + "url": "https://github.com/module-federation/core/", + "directory": "tree/main/packages/core" + }, "peerDependencies": { "webpack": "^5.40.0" } diff --git a/packages/data-prefetch/CHANGELOG.md b/packages/data-prefetch/CHANGELOG.md index 67935565e2..dbdccd869b 100644 --- a/packages/data-prefetch/CHANGELOG.md +++ b/packages/data-prefetch/CHANGELOG.md @@ -1,5 +1,15 @@ # @module-federation/data-prefetch +## 0.8.3 + +### Patch Changes + +- 5b637c3: fix(data-prefetch): the prefetch exports type is promise or function +- Updated dependencies [f817674] +- Updated dependencies [8e172c8] + - @module-federation/runtime@0.8.3 + - @module-federation/sdk@0.8.3 + ## 0.8.2 ### Patch Changes diff --git a/packages/data-prefetch/package.json b/packages/data-prefetch/package.json index 570ed99c55..8fc1c77afe 100644 --- a/packages/data-prefetch/package.json +++ b/packages/data-prefetch/package.json @@ -1,10 +1,15 @@ { "name": "@module-federation/data-prefetch", "description": "Module Federation Data Prefetch", - "version": "0.8.2", + "version": "0.8.3", "author": "nieyan ", "homepage": "https://github.com/module-federation/core", "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/module-federation/core/", + "directory": "packages/data-prefetch" + }, "scripts": { "test": "jest" }, diff --git a/packages/data-prefetch/src/prefetch.ts b/packages/data-prefetch/src/prefetch.ts index 059ad3dfee..f60f25309c 100644 --- a/packages/data-prefetch/src/prefetch.ts +++ b/packages/data-prefetch/src/prefetch.ts @@ -99,7 +99,7 @@ export class MFDataPrefetch { const exportsPromise = typeof exportsPromiseFn === 'function' ? exportsPromiseFn() - : Promise.resolve({}); + : exportsPromiseFn; const resolve = exportsPromise.then( (exports: Record> = {}) => { // Match prefetch based on the function name suffix so that other capabilities can be expanded later. diff --git a/packages/dts-plugin/CHANGELOG.md b/packages/dts-plugin/CHANGELOG.md index 3bf738772b..fdf62cfdc3 100644 --- a/packages/dts-plugin/CHANGELOG.md +++ b/packages/dts-plugin/CHANGELOG.md @@ -1,5 +1,16 @@ # @module-federation/dts-plugin +## 0.8.3 + +### Patch Changes + +- 8e172c8: add `cwd` property to generate types +- Updated dependencies [8e172c8] + - @module-federation/sdk@0.8.3 + - @module-federation/managers@0.8.3 + - @module-federation/third-party-dts-extractor@0.8.3 + - @module-federation/error-codes@0.8.3 + ## 0.8.2 ### Patch Changes diff --git a/packages/dts-plugin/package.json b/packages/dts-plugin/package.json index f43e73f2e6..80b5ae6978 100644 --- a/packages/dts-plugin/package.json +++ b/packages/dts-plugin/package.json @@ -1,11 +1,16 @@ { "name": "@module-federation/dts-plugin", - "version": "0.8.2", + "version": "0.8.3", "author": "hanric ", "main": "./dist/index.js", "module": "./dist/index.js", "types": "./dist/index.d.ts", "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/module-federation/core/", + "directory": "packages/dts-plugin" + }, "publishConfig": { "access": "public" }, diff --git a/packages/dts-plugin/src/core/lib/DTSManager.general.spec.ts b/packages/dts-plugin/src/core/lib/DTSManager.general.spec.ts new file mode 100644 index 0000000000..b74e51ae76 --- /dev/null +++ b/packages/dts-plugin/src/core/lib/DTSManager.general.spec.ts @@ -0,0 +1,457 @@ +import { describe, expect, it, vi, beforeEach } from 'vitest'; +import { DTSManager } from './DTSManager'; +import axios from 'axios'; +import fs from 'fs'; +import path from 'path'; +import { UpdateMode } from '../../server/constant'; +import { ThirdPartyExtractor } from '@module-federation/third-party-dts-extractor'; +import { HostOptions, RemoteInfo } from '../interfaces/HostOptions'; +import { RemoteOptions } from '../interfaces/RemoteOptions'; +import { downloadTypesArchive } from './archiveHandler'; +import { + retrieveHostConfig, + retrieveRemoteInfo, +} from '../configurations/hostPlugin'; + +vi.mock('axios'); +vi.mock('fs/promises'); +vi.mock('fs'); +vi.mock('./archiveHandler'); +vi.mock('@module-federation/third-party-dts-extractor', () => ({ + ThirdPartyExtractor: vi.fn().mockImplementation(() => ({ + collectTypeImports: vi.fn().mockReturnValue([]), + })), +})); + +const projectRoot = path.join(__dirname, '../../..'); + +vi.mock('../configurations/hostPlugin', () => ({ + retrieveHostConfig: vi.fn().mockImplementation((options) => ({ + hostOptions: { + ...options, + context: projectRoot, + typesFolder: '@mf-types', + remoteTypesFolder: '@mf-types/remotes', + deleteTypesFolder: false, + implementation: 'webpack', + abortOnError: false, + consumeAPITypes: true, + maxRetries: 3, + runtimePkgs: [], + }, + mapRemotesToDownload: { + remote1: { + name: 'remote1', + url: 'http://example.com/remote1', + alias: 'remote1', + zipUrl: 'http://example.com/types.zip', + apiTypeUrl: 'http://example.com/api.d.ts', + }, + }, + })), + retrieveRemoteInfo: vi.fn().mockImplementation(({ remoteAlias, remote }) => ({ + name: remoteAlias, + url: remote, + alias: remoteAlias, + })), +})); + +describe('DTSManager General Tests', () => { + let dtsManager: DTSManager; + + beforeEach(() => { + vi.clearAllMocks(); + const remoteOptions: RemoteOptions = { + moduleFederationConfig: { + name: 'testRemote', + filename: 'remoteEntry.js', + exposes: { + './Component': './src/Component.tsx', + './utils': './src/utils.ts', + }, + shared: { + react: { singleton: true, eager: true }, + 'react-dom': { singleton: true, eager: true }, + }, + }, + typesFolder: '@mf-types', + context: projectRoot, + tsConfigPath: path.join(projectRoot, 'tsconfig.json'), + compiledTypesFolder: 'compiled-types', + deleteTypesFolder: false, + additionalFilesToCompile: [], + generateAPITypes: true, + extractRemoteTypes: true, + extractThirdParty: true, + implementation: 'webpack', + abortOnError: false, + }; + dtsManager = new DTSManager({ remote: remoteOptions }); + + // Add mock implementations + vi.spyOn(dtsManager, 'consumeArchiveTypes').mockResolvedValue(undefined); + vi.spyOn(dtsManager, 'consumeAPITypes').mockResolvedValue(undefined); + + // Add mock for fs.writeFileSync + vi.spyOn(fs, 'writeFileSync').mockImplementation(() => undefined); + vi.spyOn(fs, 'readFileSync').mockReturnValue(` + import type { PackageType as PackageType_0, RemoteKeys as RemoteKeys_0 } from './existing/apis.d.ts'; + `); + }); + + describe('generateAPITypes', () => { + it('should generate correct API types for multiple exposed components', () => { + const exposeMap = { + './Component': './src/Component.tsx', + './utils': './src/utils.ts', + }; + + const result = dtsManager.generateAPITypes(exposeMap); + expect(result).toContain("'REMOTE_ALIAS_IDENTIFIER/Component'"); + expect(result).toContain("'REMOTE_ALIAS_IDENTIFIER/utils'"); + expect(result).toContain('type PackageType'); + }); + + it('should handle empty expose map', () => { + const result = dtsManager.generateAPITypes({}); + expect(result).toContain('export type RemoteKeys ='); + expect(result).toContain('type PackageType'); + }); + }); + + describe('requestRemoteManifest', () => { + it('should handle non-manifest URLs correctly', async () => { + const remoteInfo: RemoteInfo = { + name: 'test', + url: 'http://example.com/remote', + alias: 'test-alias', + }; + + const result = await dtsManager.requestRemoteManifest(remoteInfo); + expect(result).toEqual(remoteInfo); + }); + + it('should handle manifest URLs with auto publicPath', async () => { + const manifestResponse = { + data: { + metaData: { + types: { + zip: 'types.zip', + api: 'api.d.ts', + }, + publicPath: 'auto', + }, + }, + }; + + vi.mocked(axios.get).mockResolvedValueOnce(manifestResponse); + + const remoteInfo: RemoteInfo = { + name: 'test', + url: 'http://example.com/remote.manifest.json', + alias: 'test-alias', + }; + + const result = await dtsManager.requestRemoteManifest(remoteInfo); + expect(result.zipUrl).toBeDefined(); + expect(result.apiTypeUrl).toBeDefined(); + expect(result.zipUrl).toContain('http://example.com/types.zip'); + }); + + it('should handle manifest URLs without API types', async () => { + const manifestResponse = { + data: { + metaData: { + types: { + zip: 'types.zip', + }, + publicPath: 'http://example.com', + }, + }, + }; + + vi.mocked(axios.get).mockResolvedValueOnce(manifestResponse); + + const remoteInfo: RemoteInfo = { + name: 'test', + url: 'http://example.com/remote.manifest.json', + alias: 'test-alias', + }; + + const result = await dtsManager.requestRemoteManifest(remoteInfo); + expect(result.zipUrl).toBeDefined(); + expect(result.apiTypeUrl).toBe(''); + }); + + it('should handle manifest fetch errors', async () => { + vi.mocked(axios.get).mockRejectedValueOnce(new Error('Network error')); + + const remoteInfo: RemoteInfo = { + name: 'test', + url: 'http://example.com/remote.manifest.json', + alias: 'test-alias', + }; + + const result = await dtsManager.requestRemoteManifest(remoteInfo); + expect(result).toEqual(remoteInfo); + }); + + it('should handle manifest with getPublicPath function', async () => { + const manifestResponse = { + data: { + metaData: { + types: { + zip: 'types.zip', + api: 'api.d.ts', + }, + publicPath: 'http://example.com/custom/', + }, + }, + }; + + vi.mocked(axios.get).mockResolvedValueOnce(manifestResponse); + + const remoteInfo: RemoteInfo = { + name: 'test', + url: 'http://example.com/remote.manifest.json', + alias: 'test-alias', + }; + + const result = await dtsManager.requestRemoteManifest(remoteInfo); + expect(result.zipUrl).toContain('http://example.com/custom/types.zip'); + expect(result.apiTypeUrl).toContain('http://example.com/custom/api.d.ts'); + }); + }); + + describe('consumeTargetRemotes', () => { + const baseHostOptions: Required = { + context: projectRoot, + typesFolder: '@mf-types', + runtimePkgs: [], + moduleFederationConfig: { + name: 'host', + filename: 'remoteEntry.js', + remotes: {}, + }, + remoteTypesFolder: '@mf-types/remotes', + deleteTypesFolder: false, + implementation: 'webpack', + abortOnError: false, + consumeAPITypes: true, + maxRetries: 3, + }; + + it('should successfully download types archive', async () => { + const remoteInfo: Required = { + name: 'test', + url: 'http://example.com/remote', + alias: 'test-alias', + zipUrl: 'http://example.com/types.zip', + apiTypeUrl: 'http://example.com/api.d.ts', + }; + + const mockDownloader = vi + .fn() + .mockResolvedValue(['test-alias', '/tmp/types']); + vi.mocked(downloadTypesArchive).mockReturnValue(mockDownloader); + + const result = await dtsManager.consumeTargetRemotes( + baseHostOptions, + remoteInfo, + ); + + expect(result).toEqual(['test-alias', '/tmp/types']); + expect(mockDownloader).toHaveBeenCalledWith([ + 'test-alias', + 'http://example.com/types.zip', + ]); + }); + + it('should throw error when zipUrl is missing', async () => { + const remoteInfo: Required = { + name: 'test', + url: 'http://example.com/remote', + alias: 'test-alias', + zipUrl: '', + apiTypeUrl: '', + }; + + await expect( + dtsManager.consumeTargetRemotes(baseHostOptions, remoteInfo), + ).rejects.toThrow("Can not get test's types archive url!"); + }); + }); + + describe('updateTypes', () => { + it('should handle positive update mode for host', async () => { + const generateTypesSpy = vi.spyOn(dtsManager, 'generateTypes'); + dtsManager.options.host = { + moduleFederationConfig: { + name: 'testRemote', + }, + }; + dtsManager.options.remote = { + moduleFederationConfig: { + name: 'testRemote', + filename: 'remoteEntry.js', + exposes: { + './Component': './src/Component.tsx', + }, + }, + typesFolder: '@mf-types', + context: projectRoot, + tsConfigPath: path.join(projectRoot, 'tsconfig.json'), + }; + + await dtsManager.updateTypes({ + remoteName: 'testRemote', + updateMode: UpdateMode.POSITIVE, + once: true, + }); + + expect(generateTypesSpy).toHaveBeenCalled(); + }); + + it('should handle missing remote options', async () => { + dtsManager = new DTSManager({}); + + await dtsManager.updateTypes({ + remoteName: 'testRemote', + updateMode: UpdateMode.POSITIVE, + once: true, + }); + + // Should not throw and handle gracefully + expect(true).toBe(true); + }); + }); + + describe('downloadAPITypes', () => { + it('should download and save API types with correct alias replacement', async () => { + const remoteInfo: Required = { + name: 'test', + url: 'http://example.com/remote', + alias: 'test-alias', + zipUrl: 'http://example.com/types.zip', + apiTypeUrl: 'http://example.com/api.d.ts', + }; + + const apiTypeContent = ` + export type RemoteKeys = 'REMOTE_ALIAS_IDENTIFIER/Component'; + type PackageType = T extends 'REMOTE_ALIAS_IDENTIFIER/Component' ? typeof import('REMOTE_ALIAS_IDENTIFIER/Component') : any; + `; + + vi.mocked(axios.get).mockResolvedValueOnce({ data: apiTypeContent }); + vi.spyOn(fs, 'writeFileSync'); + + await dtsManager.downloadAPITypes(remoteInfo, '/tmp/types'); + + expect(fs.writeFileSync).toHaveBeenCalled(); + const writeCall = vi.mocked(fs.writeFileSync).mock.calls[0]; + const content = writeCall[1] as string; + + expect(content).toContain('test-alias/Component'); + expect(content).not.toContain('REMOTE_ALIAS_IDENTIFIER'); + expect(content).toContain( + "type PackageType = T extends 'test-alias/Component'", + ); + expect(dtsManager.loadedRemoteAPIAlias.has('test-alias')).toBe(true); + }); + + it('should handle missing apiTypeUrl', async () => { + const remoteInfo: Required = { + name: 'test', + url: 'http://example.com/remote', + alias: 'test-alias', + zipUrl: 'http://example.com/types.zip', + apiTypeUrl: '', + }; + + vi.spyOn(fs, 'writeFileSync'); + + await dtsManager.downloadAPITypes(remoteInfo, '/tmp/types'); + + expect(fs.writeFileSync).not.toHaveBeenCalled(); + expect(dtsManager.loadedRemoteAPIAlias.has('test-alias')).toBe(false); + }); + + it('should handle download errors', async () => { + const remoteInfo: Required = { + name: 'test', + url: 'http://example.com/remote', + alias: 'test-alias', + zipUrl: 'http://example.com/types.zip', + apiTypeUrl: 'http://example.com/api.d.ts', + }; + + vi.mocked(axios.get).mockRejectedValueOnce(new Error('Network error')); + vi.spyOn(fs, 'writeFileSync'); + + await dtsManager.downloadAPITypes(remoteInfo, '/tmp/types'); + + expect(fs.writeFileSync).not.toHaveBeenCalled(); + expect(dtsManager.loadedRemoteAPIAlias.has('test-alias')).toBe(false); + }); + }); + + describe('consumeAPITypes', () => { + const baseHostOptions: Required = { + context: projectRoot, + typesFolder: '@mf-types', + runtimePkgs: ['@custom/runtime'], + moduleFederationConfig: { + name: 'host', + filename: 'remoteEntry.js', + remotes: { + remote1: 'remote1@http://example.com/remote1', + remote2: 'remote2@http://example.com/remote2', + }, + }, + remoteTypesFolder: '@mf-types/remotes', + deleteTypesFolder: false, + implementation: 'webpack', + abortOnError: false, + consumeAPITypes: true, + maxRetries: 3, + }; + + it('should handle no loaded remote API aliases', () => { + vi.spyOn(fs, 'writeFileSync'); + + dtsManager.consumeAPITypes(baseHostOptions); + + expect(fs.writeFileSync).not.toHaveBeenCalled(); + }); + + it('should handle existing API types file', () => { + const existingContent = ` + import type { PackageType as PackageType_0, RemoteKeys as RemoteKeys_0 } from './existing/apis.d.ts'; + `; + vi.spyOn(fs, 'readFileSync').mockReturnValue(existingContent); + vi.spyOn(fs, 'writeFileSync'); + + // Mock the ThirdPartyExtractor to return the existing import + vi.mocked(ThirdPartyExtractor).mockImplementation(() => ({ + collectTypeImports: vi.fn().mockReturnValue(['./existing/apis.d.ts']), + pkgs: {} as Record, + pattern: /.*/, + context: '', + destDir: '', + tsConfigPath: '', + typesFolder: '', + implementation: 'webpack', + addPkgs: vi.fn(), + inferPkgDir: vi.fn(), + collectPkgs: vi.fn(), + copyDts: vi.fn().mockResolvedValue(undefined), + })); + + // Add the existing alias to the loadedRemoteAPIAlias set + dtsManager.loadedRemoteAPIAlias.add('existing'); + + dtsManager.consumeAPITypes(baseHostOptions); + + expect(dtsManager.loadedRemoteAPIAlias.has('existing')).toBe(true); + }); + }); +}); diff --git a/packages/dts-plugin/src/core/lib/DtsWorker.spec.ts b/packages/dts-plugin/src/core/lib/DtsWorker.spec.ts index f77ed4fd8f..82830aeb99 100644 --- a/packages/dts-plugin/src/core/lib/DtsWorker.spec.ts +++ b/packages/dts-plugin/src/core/lib/DtsWorker.spec.ts @@ -1,7 +1,10 @@ -import { describe, expect, it } from 'vitest'; +import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest'; import { join } from 'path'; import dirTree from 'directory-tree'; import { execSync } from 'child_process'; +import { isDebugMode } from './utils'; +import type { DTSManagerOptions } from '../interfaces/DTSManagerOptions'; + const TEST_DIT_DIR = 'dist-test'; describe('generateTypesInChildProcess', () => { @@ -210,3 +213,236 @@ describe('generateTypesInChildProcess', () => { expect(checkProcess()).toEqual(false); }); }); + +describe('DtsWorker Unit Tests', () => { + let dtsWorker: any; + let originalKill: typeof process.kill; + let originalDebugMode: typeof isDebugMode; + let DtsWorkerClass: any; + + const projectRoot = join(__dirname, '../../..'); + const typesFolder = '@mf-types-dts-test'; + + const mockRemoteOptions = { + moduleFederationConfig: { + name: 'dtsWorkerSpecRemote', + filename: 'remoteEntry.js', + exposes: { + './index': join(__dirname, '..', './index.ts'), + }, + shared: { + react: { singleton: true, eager: true }, + 'react-dom': { singleton: true, eager: true }, + }, + manifest: true, + }, + tsConfigPath: join(projectRoot, './tsconfig.spec.json'), + typesFolder: typesFolder, + compiledTypesFolder: 'compiled-types', + deleteTypesFolder: false, + additionalFilesToCompile: [], + context: projectRoot, + extractRemoteTypes: true, + }; + + const mockHostOptions = { + moduleFederationConfig: { + name: 'dtsWorkerSpecHost', + filename: 'remoteEntry.js', + remotes: { + remotes: 'remote@https://foo.it', + }, + shared: { + react: { singleton: true, eager: true }, + 'react-dom': { singleton: true, eager: true }, + }, + manifest: true, + }, + typesFolder: `dist-test/@mf-types-dts-test-consume-types`, + context: projectRoot, + deleteTypesFolder: true, + remoteTypesFolder: 'remote-types', + }; + + const mockOptions: DTSManagerOptions = { + host: mockHostOptions, + remote: mockRemoteOptions, + }; + + beforeEach(() => { + vi.spyOn(console, 'error').mockImplementation(() => {}); + originalKill = process.kill; + originalDebugMode = isDebugMode; + DtsWorkerClass = require('../../../dist/core').DtsWorker; + // Reset isDebugMode for each test + vi.mock('./utils', () => ({ + isDebugMode: () => false, + cloneDeepOptions: (options: any) => JSON.parse(JSON.stringify(options)), + })); + // Mock logger + vi.mock('../../server', () => ({ + logger: { + error: vi.fn(), + info: vi.fn(), + warn: vi.fn(), + debug: vi.fn(), + }, + fileLog: vi.fn(), + })); + }); + + afterEach(async () => { + if (dtsWorker) { + try { + dtsWorker.exit(); + } catch (err) { + // Ignore exit errors + } + dtsWorker = null; + } + process.kill = originalKill; + vi.restoreAllMocks(); + vi.resetModules(); + }); + + describe('initialization', () => { + it('should create a new instance with valid options', () => { + dtsWorker = new DtsWorkerClass(mockOptions); + + expect(dtsWorker).toBeDefined(); + expect(dtsWorker.rpcWorker).toBeDefined(); + expect(dtsWorker._options).toBeDefined(); + }); + + it('should remove unserializable manifest data from options', () => { + const optionsWithManifest = { + ...mockOptions, + remote: { + ...mockOptions.remote, + moduleFederationConfig: { + ...mockOptions.remote.moduleFederationConfig, + manifest: { some: 'data' }, + }, + }, + }; + + vi.mock('./utils', () => ({ + isDebugMode: () => false, + cloneDeepOptions: (options: any) => { + const cloned = JSON.parse(JSON.stringify(options)); + if (cloned.remote?.moduleFederationConfig?.manifest) { + delete cloned.remote.moduleFederationConfig.manifest; + } + if (cloned.host?.moduleFederationConfig?.manifest) { + delete cloned.host.moduleFederationConfig.manifest; + } + return cloned; + }, + })); + + dtsWorker = new DtsWorkerClass(optionsWithManifest); + expect( + dtsWorker._options.remote.moduleFederationConfig.manifest, + ).toBeFalsy(); + }); + }); + + describe('process management', () => { + it('should handle exit gracefully when worker termination fails', async () => { + dtsWorker = new DtsWorkerClass(mockOptions); + + dtsWorker.rpcWorker.terminate = () => { + throw new Error('Termination failed'); + }; + + expect(() => dtsWorker.exit()).not.toThrow(); + }); + + it('should ensure child process exits even when promise rejects', async () => { + vi.mock('../rpc/index', () => ({ + createRpcWorker: () => ({ + connect: () => Promise.resolve(), + terminate: vi.fn(), + process: { + pid: process.pid, + connected: true, + send: (message: any, callback?: (error: Error | null) => void) => { + if (callback) callback(null); + }, + }, + }), + })); + + dtsWorker = new DtsWorkerClass(mockOptions); + dtsWorker._res = Promise.reject(new Error('Test error')); + + await expect(dtsWorker.controlledPromise).resolves.toBeUndefined(); + }); + }); + + describe('debug mode handling', () => { + it('should log errors in debug mode', async () => { + vi.mock('./utils', () => ({ + isDebugMode: () => true, + cloneDeepOptions: (options: any) => JSON.parse(JSON.stringify(options)), + })); + + vi.mock('../rpc/index', () => ({ + createRpcWorker: () => ({ + connect: () => Promise.resolve(), + terminate: vi.fn(), + process: { + pid: process.pid, + connected: true, + send: (message: any, callback?: (error: Error | null) => void) => { + if (callback) callback(null); + }, + }, + }), + })); + + const consoleSpy = vi.spyOn(console, 'error'); + dtsWorker = new DtsWorkerClass(mockOptions); + dtsWorker._res = Promise.reject(new Error('Test error')); + + await dtsWorker.controlledPromise; + expect(consoleSpy).toHaveBeenCalled(); + }); + + it('should not log errors when not in debug mode', async () => { + vi.mock('./utils', () => ({ + isDebugMode: () => false, + cloneDeepOptions: (options: any) => JSON.parse(JSON.stringify(options)), + })); + + vi.mock('../rpc/index', () => ({ + createRpcWorker: () => ({ + connect: () => Promise.resolve(), + terminate: vi.fn(), + process: { + pid: process.pid, + connected: true, + send: (message: any, callback?: (error: Error | null) => void) => { + if (callback) callback(null); + }, + }, + }), + })); + + const consoleSpy = vi.spyOn(console, 'error'); + dtsWorker = new DtsWorkerClass(mockOptions); + + // Mock process.kill to not throw + process.kill = vi.fn(); + + // Mock the promise to resolve normally + dtsWorker._res = Promise.resolve(); + + // Clear any previous calls to console.error + consoleSpy.mockClear(); + + await dtsWorker.controlledPromise; + expect(consoleSpy).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/packages/dts-plugin/src/core/lib/archiveHandler.test.ts b/packages/dts-plugin/src/core/lib/archiveHandler.test.ts index fa66a0a06d..a48a340509 100644 --- a/packages/dts-plugin/src/core/lib/archiveHandler.test.ts +++ b/packages/dts-plugin/src/core/lib/archiveHandler.test.ts @@ -1,65 +1,246 @@ +import type { HostOptions } from '../interfaces/HostOptions'; +import type { RemoteOptions } from '../interfaces/RemoteOptions'; +import type { TsConfigJson } from '../interfaces/TsConfigJson'; + import AdmZip from 'adm-zip'; -import axios from 'axios'; -import { existsSync, mkdirSync, mkdtempSync, rmSync } from 'fs'; +import axios, { AxiosResponse, InternalAxiosRequestConfig } from 'axios'; +import { existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'fs'; import { readJSONSync } from 'fs-extra'; import os from 'os'; import { join } from 'path'; -import { afterAll, describe, expect, it, vi } from 'vitest'; +import { + afterAll, + beforeEach, + describe, + expect, + it, + vi, + MockInstance, +} from 'vitest'; -import { RemoteOptions } from '../interfaces/RemoteOptions'; -import { createTypesArchive, downloadTypesArchive } from './archiveHandler'; -import { HostOptions } from '../interfaces/HostOptions'; +import { + createTypesArchive, + downloadTypesArchive, + retrieveTypesArchiveDestinationPath, + retrieveTypesZipPath, +} from './archiveHandler'; +import { fileLog } from '../../server'; describe('archiveHandler', () => { const tmpDir = mkdtempSync(join(os.tmpdir(), 'archive-handler')); const basicConfig = readJSONSync( join(__dirname, '../../..', './tsconfig.spec.json'), - ); - const tsConfig = { + ) as TsConfigJson; + const tsConfig: TsConfigJson = { ...basicConfig, + compilerOptions: { + ...basicConfig.compilerOptions, + outDir: join(tmpDir, 'typesRemoteFolder', 'compiledTypesFolder'), + }, }; - tsConfig.compilerOptions.outDir = join( - tmpDir, - 'typesRemoteFolder', - 'compiledTypesFolder', - ); + beforeEach(() => { + vi.clearAllMocks(); + // Clean up and recreate the output directory + rmSync(tsConfig.compilerOptions.outDir, { recursive: true, force: true }); + mkdirSync(tsConfig.compilerOptions.outDir, { recursive: true }); + }); + + afterAll(() => { + rmSync(tmpDir, { recursive: true, force: true }); + }); + + describe('retrieveTypesZipPath', () => { + it('should correctly construct zip path', () => { + const mfTypesPath = '/path/to/types/folder'; + const remoteOptions: Required = { + typesFolder: 'folder', + moduleFederationConfig: {}, + context: process.cwd(), + implementation: '', + hostRemoteTypesFolder: 'remoteTypes', + compileInChildProcess: false, + compilerInstance: null, + generateAPITypes: false, + extractThirdParty: false, + extractRemoteTypes: false, + abortOnError: true, + additionalFilesToCompile: [], + compiledTypesFolder: 'compiledTypesFolder', + tsConfigPath: './tsconfig.spec.json', + deleteTypesFolder: false, + }; + + const zipPath = retrieveTypesZipPath(mfTypesPath, remoteOptions); + expect(zipPath).toBe('/path/to/types/folder.zip'); + }); + + it('should handle paths with trailing slashes', () => { + const mfTypesPath = '/path/to/types/folder/'; + const remoteOptions: Required = { + typesFolder: 'folder', + moduleFederationConfig: {}, + context: process.cwd(), + implementation: '', + hostRemoteTypesFolder: 'remoteTypes', + compileInChildProcess: false, + compilerInstance: null, + generateAPITypes: false, + extractThirdParty: false, + extractRemoteTypes: false, + abortOnError: true, + additionalFilesToCompile: [], + compiledTypesFolder: 'compiledTypesFolder', + tsConfigPath: './tsconfig.spec.json', + deleteTypesFolder: false, + }; + + const zipPath = retrieveTypesZipPath(mfTypesPath, remoteOptions); + expect(zipPath).toBe('/path/to/types/folder.zip'); + }); + + it('should handle paths with multiple levels', () => { + const hostOptions: Required = { + context: '/base/path', + typesFolder: 'types/nested', + moduleFederationConfig: {}, + implementation: '', + runtimePkgs: [], + abortOnError: true, + remoteTypesFolder: 'remoteTypes', + deleteTypesFolder: false, + maxRetries: 3, + consumeAPITypes: false, + }; - mkdirSync(tsConfig.compilerOptions.outDir, { recursive: true }); + const path = retrieveTypesArchiveDestinationPath( + hostOptions, + 'remote1/v1', + ); + expect(path).toBe('/base/path/types/nested/remote1/v1'); + }); + }); + + describe('retrieveTypesArchiveDestinationPath', () => { + it('should correctly construct destination path', () => { + const hostOptions: Required = { + context: '/base', + typesFolder: 'types', + moduleFederationConfig: {}, + implementation: '', + runtimePkgs: [], + abortOnError: true, + remoteTypesFolder: 'remoteTypes', + deleteTypesFolder: false, + maxRetries: 3, + consumeAPITypes: false, + }; + + const path = retrieveTypesArchiveDestinationPath(hostOptions, 'remote1'); + expect(path).toBe('/base/types/remote1'); + }); + }); describe('createTypesArchive', () => { - const remoteOptions = { + const remoteOptions: Required = { additionalFilesToCompile: [], compiledTypesFolder: 'compiledTypesFolder', typesFolder: 'typesRemoteFolder', moduleFederationConfig: {}, tsConfigPath: './tsconfig.spec.json', deleteTypesFolder: false, - } as unknown as Required; + context: process.cwd(), + implementation: '', + hostRemoteTypesFolder: 'remoteTypes', + compileInChildProcess: false, + compilerInstance: null, + generateAPITypes: false, + extractThirdParty: false, + extractRemoteTypes: false, + abortOnError: true, + }; - it('correctly creates archive', async () => { - const archivePath = join(tmpDir, `${remoteOptions.typesFolder}.zip`); + it('should correctly create archive with type definitions', async () => { + // Create a sample type definition file + const typePath = join(tsConfig.compilerOptions.outDir, 'sample.d.ts'); + writeFileSync(typePath, 'export declare const foo: string;'); + const archivePath = join(tmpDir, `${remoteOptions.typesFolder}.zip`); const archiveCreated = await createTypesArchive(tsConfig, remoteOptions); expect(archiveCreated).toBeTruthy(); expect(existsSync(archivePath)).toBeTruthy(); + + // Verify archive contents - only check .d.ts files + const zip = new AdmZip(archivePath); + const dtsEntries = zip + .getEntries() + .filter((entry) => entry.entryName.endsWith('.d.ts')); + expect(dtsEntries).toHaveLength(1); + // The entry name includes the compiledTypesFolder since that's how it's stored in the archive + expect(dtsEntries[0].entryName).toBe('compiledTypesFolder/sample.d.ts'); + expect(dtsEntries[0].getData().toString()).toBe( + 'export declare const foo: string;', + ); }); - it('throws for unexisting outDir', async () => { - expect( - createTypesArchive( - { - ...tsConfig, - compilerOptions: { - ...tsConfig.compilerOptions, - outDir: '/foo', - }, - }, - remoteOptions, - ), + it('should throw error for non-existent outDir', async () => { + const invalidConfig: TsConfigJson = { + ...tsConfig, + compilerOptions: { + ...tsConfig.compilerOptions, + outDir: '/foo', + }, + }; + + await expect( + createTypesArchive(invalidConfig, remoteOptions), ).rejects.toThrowError(); }); + + it('should handle empty type definitions directory', async () => { + const archivePath = join(tmpDir, `${remoteOptions.typesFolder}.zip`); + const archiveCreated = await createTypesArchive(tsConfig, remoteOptions); + + expect(archiveCreated).toBeTruthy(); + expect(existsSync(archivePath)).toBeTruthy(); + + // Only check for .d.ts files + const zip = new AdmZip(archivePath); + const dtsEntries = zip + .getEntries() + .filter((entry) => entry.entryName.endsWith('.d.ts')); + expect(dtsEntries).toHaveLength(0); + }); + + it('should handle archive with nested directory structure', async () => { + // Create nested directories with type definitions + const nestedPath = join(tsConfig.compilerOptions.outDir, 'nested/deep'); + mkdirSync(nestedPath, { recursive: true }); + writeFileSync( + join(nestedPath, 'nested.d.ts'), + 'export declare const nested: boolean;', + ); + + const archivePath = join(tmpDir, `${remoteOptions.typesFolder}.zip`); + const archiveCreated = await createTypesArchive(tsConfig, remoteOptions); + + expect(archiveCreated).toBeTruthy(); + expect(existsSync(archivePath)).toBeTruthy(); + + // Verify archive contents including nested structure + const zip = new AdmZip(archivePath); + const dtsEntries = zip + .getEntries() + .filter((entry) => entry.entryName.endsWith('.d.ts')); + expect(dtsEntries).toHaveLength(1); + expect(dtsEntries[0].entryName).toBe( + 'compiledTypesFolder/nested/deep/nested.d.ts', + ); + expect(dtsEntries[0].getData().toString()).toBe( + 'export declare const nested: boolean;', + ); + }); }); describe('downloadTypesArchive', () => { @@ -79,54 +260,161 @@ describe('archiveHandler', () => { const destinationFolder = 'typesHostFolder'; const fileToDownload = 'https://foo.it'; - it('correctly extracts downloaded archive', async () => { + beforeEach(() => { + // Clean up and recreate the destination folder + const archivePath = join(tmpDir, destinationFolder); + rmSync(archivePath, { recursive: true, force: true }); + mkdirSync(archivePath, { recursive: true }); + }); + + it('should correctly extract downloaded archive with type definitions', async () => { const archivePath = join(tmpDir, destinationFolder); const zip = new AdmZip(); - await zip.addLocalFolderPromise(tmpDir, {}); - axios.get = vi.fn().mockResolvedValueOnce({ data: zip.toBuffer() }); + // Add sample type definition to the archive + zip.addFile( + 'sample.d.ts', + Buffer.from('export declare const bar: number;'), + ); - await downloadTypesArchive(hostOptions)([ + const mockResponse: AxiosResponse = { + data: zip.toBuffer(), + status: 200, + statusText: 'OK', + headers: {}, + config: {} as InternalAxiosRequestConfig, + request: {} as XMLHttpRequest, + }; + vi.spyOn(axios, 'get').mockResolvedValueOnce(mockResponse); + + const result = await downloadTypesArchive(hostOptions)([ destinationFolder, fileToDownload, ]); - expect(existsSync(archivePath)).toBeTruthy(); + + expect(result).toEqual([destinationFolder, archivePath]); + expect(existsSync(join(archivePath, 'sample.d.ts'))).toBeTruthy(); expect(axios.get).toHaveBeenCalledTimes(1); + // Only verify the URL and responseType + const axiosGetMock = vi.mocked(axios.get); + const [[url, options]] = axiosGetMock.mock.calls; + expect(url).toBe(fileToDownload); + expect(options.responseType).toBe('arraybuffer'); }); - it('correctly handles exception', async () => { - const message = 'Rejected value'; - - axios.get = vi.fn().mockRejectedValue(new Error(message)); + it('should retry on network failure up to maxRetries', async () => { + const error = new Error('Network error'); + vi.spyOn(axios, 'get').mockRejectedValue(error); await expect(() => downloadTypesArchive(hostOptions)([destinationFolder, fileToDownload]), - ).rejects.toThrowError( - `Network error: Unable to download federated mocks for '${destinationFolder}' from '${fileToDownload}' because '${message}'`, - ); + ).rejects.toThrowError(/Network error/); + expect(axios.get).toHaveBeenCalledTimes(hostOptions.maxRetries); }); - it('not throw error while set abortOnError: false ', async () => { - const message = 'Rejected value'; - const hostOptions: Required = { - moduleFederationConfig: {}, - typesFolder: tmpDir, - remoteTypesFolder: tmpDir, - deleteTypesFolder: true, - maxRetries: 3, - implementation: '', - context: process.cwd(), + it('should handle empty archives gracefully', async () => { + const archivePath = join(tmpDir, destinationFolder); + const zip = new AdmZip(); + // Add an empty directory to the archive + zip.addFile('.keep', Buffer.from('')); + vi.spyOn(axios, 'get').mockResolvedValueOnce({ + data: zip.toBuffer(), + status: 200, + statusText: 'OK', + headers: {}, + config: {} as InternalAxiosRequestConfig, + request: {} as XMLHttpRequest, + } as AxiosResponse); + + const result = await downloadTypesArchive(hostOptions)([ + destinationFolder, + fileToDownload, + ]); + + expect(result).toEqual([destinationFolder, archivePath]); + expect(existsSync(archivePath)).toBeTruthy(); + expect(existsSync(join(archivePath, '.keep'))).toBeTruthy(); + }); + + it('should clean up existing folder when deleteTypesFolder is true', async () => { + const archivePath = join(tmpDir, destinationFolder); + writeFileSync(join(archivePath, 'old.d.ts'), 'old content'); + + const zip = new AdmZip(); + zip.addFile('new.d.ts', Buffer.from('new content')); + vi.spyOn(axios, 'get').mockResolvedValueOnce({ + data: zip.toBuffer(), + status: 200, + statusText: 'OK', + headers: {}, + config: {} as InternalAxiosRequestConfig, + request: {} as XMLHttpRequest, + } as AxiosResponse); + + await downloadTypesArchive(hostOptions)([ + destinationFolder, + fileToDownload, + ]); + + expect(existsSync(join(archivePath, 'old.d.ts'))).toBeFalsy(); + expect(existsSync(join(archivePath, 'new.d.ts'))).toBeTruthy(); + }); + + it('should preserve existing folder when deleteTypesFolder is false', async () => { + const options: Required = { + ...hostOptions, + deleteTypesFolder: false, + }; + const archivePath = join(tmpDir, destinationFolder); + writeFileSync(join(archivePath, 'old.d.ts'), 'old content'); + + const zip = new AdmZip(); + zip.addFile('new.d.ts', Buffer.from('new content')); + vi.spyOn(axios, 'get').mockResolvedValueOnce({ + data: zip.toBuffer(), + status: 200, + statusText: 'OK', + headers: {}, + config: {} as InternalAxiosRequestConfig, + request: {} as XMLHttpRequest, + } as AxiosResponse); + + await downloadTypesArchive(options)([destinationFolder, fileToDownload]); + + expect(existsSync(join(archivePath, 'old.d.ts'))).toBeTruthy(); + expect(existsSync(join(archivePath, 'new.d.ts'))).toBeTruthy(); + }); + + it('should continue without error when abortOnError is false', async () => { + const options: Required = { + ...hostOptions, abortOnError: false, - consumeAPITypes: false, - runtimePkgs: [], }; - axios.get = vi.fn().mockRejectedValue(new Error(message)); - const res = await downloadTypesArchive(hostOptions)([ + vi.spyOn(axios, 'get').mockRejectedValue(new Error('Network error')); + + const result = await downloadTypesArchive(options)([ destinationFolder, fileToDownload, ]); - expect(res).toEqual(undefined); + + expect(result).toBeUndefined(); + expect(axios.get).toHaveBeenCalledTimes(options.maxRetries); + }); + + it('should handle malformed zip data', async () => { + vi.spyOn(axios, 'get').mockResolvedValueOnce({ + data: Buffer.from('not a valid zip file'), + status: 200, + statusText: 'OK', + headers: {}, + config: {} as InternalAxiosRequestConfig, + request: {} as XMLHttpRequest, + } as AxiosResponse); + + await expect(() => + downloadTypesArchive(hostOptions)([destinationFolder, fileToDownload]), + ).rejects.toThrow(/Network error: Unable to download federated mocks/); }); }); }); diff --git a/packages/dts-plugin/src/core/lib/consumeTypes.spec.ts b/packages/dts-plugin/src/core/lib/consumeTypes.spec.ts new file mode 100644 index 0000000000..55da8251ee --- /dev/null +++ b/packages/dts-plugin/src/core/lib/consumeTypes.spec.ts @@ -0,0 +1,62 @@ +import { describe, it, expect, beforeEach, vi } from 'vitest'; +import { consumeTypes } from './consumeTypes'; +import { getDTSManagerConstructor } from './utils'; +import { DTSManagerOptions } from '../interfaces/DTSManagerOptions'; + +// Mock the utils module +vi.mock('./utils'); + +describe('consumeTypes', () => { + const mockConsumeTypes = vi.fn().mockResolvedValue(undefined); + + // Mock implementation of DTSManager + class MockDTSManager { + constructor(public options: DTSManagerOptions) {} + async consumeTypes() { + return mockConsumeTypes(); + } + } + + beforeEach(() => { + vi.clearAllMocks(); + mockConsumeTypes.mockClear(); + (getDTSManagerConstructor as any).mockReturnValue(MockDTSManager); + }); + + it('should create DTSManager with provided options', async () => { + const options: DTSManagerOptions = { + host: { + implementation: 'test-implementation', + moduleFederationConfig: { + name: 'test-host', + remotes: {}, + }, + }, + }; + + await consumeTypes(options); + + expect(getDTSManagerConstructor).toHaveBeenCalledWith( + 'test-implementation', + ); + expect(mockConsumeTypes).toHaveBeenCalled(); + }); + + it('should work with minimal options', async () => { + const options: DTSManagerOptions = {}; + + await consumeTypes(options); + + expect(getDTSManagerConstructor).toHaveBeenCalledWith(undefined); + expect(mockConsumeTypes).toHaveBeenCalled(); + }); + + it('should propagate errors from consumeTypes', async () => { + const error = new Error('Test error'); + const options: DTSManagerOptions = {}; + + mockConsumeTypes.mockRejectedValueOnce(error); + + await expect(consumeTypes(options)).rejects.toThrow(error); + }); +}); diff --git a/packages/dts-plugin/src/plugins/GenerateTypesPlugin.ts b/packages/dts-plugin/src/plugins/GenerateTypesPlugin.ts index 41c7079bd6..6cb0a94137 100644 --- a/packages/dts-plugin/src/plugins/GenerateTypesPlugin.ts +++ b/packages/dts-plugin/src/plugins/GenerateTypesPlugin.ts @@ -73,6 +73,84 @@ export class GenerateTypesPlugin implements WebpackPluginInstance { }; const generateTypesFn = getGenerateTypesFn(); let compiledOnce = false; + + const emitTypesFiles = async () => { + try { + const { zipTypesPath, apiTypesPath, zipName, apiFileName } = + retrieveTypesAssetsInfo(finalOptions.remote); + + await generateTypesFn(finalOptions); + const config = finalOptions.remote.moduleFederationConfig; + let zipPrefix = ''; + if (typeof config.manifest === 'object' && config.manifest.filePath) { + zipPrefix = config.manifest.filePath; + } else if ( + typeof config.manifest === 'object' && + config.manifest.fileName + ) { + zipPrefix = path.dirname(config.manifest.fileName); + } else if (config.filename) { + zipPrefix = path.dirname(config.filename); + } + + if (zipTypesPath) { + const zipContent = fs.readFileSync(zipTypesPath); + const zipOutputPath = path.join( + compiler.outputPath, + zipPrefix, + zipName, + ); + await new Promise((resolve, reject) => { + compiler.outputFileSystem.mkdir( + path.dirname(zipOutputPath), + (err) => { + if (err) reject(err); + else { + compiler.outputFileSystem.writeFile( + zipOutputPath, + zipContent, + (writeErr) => { + if (writeErr) reject(writeErr); + else resolve(); + }, + ); + } + }, + ); + }); + } + + if (apiTypesPath) { + const apiContent = fs.readFileSync(apiTypesPath); + const apiOutputPath = path.join( + compiler.outputPath, + zipPrefix, + apiFileName, + ); + await new Promise((resolve, reject) => { + compiler.outputFileSystem.mkdir( + path.dirname(apiOutputPath), + (err) => { + if (err) reject(err); + else { + compiler.outputFileSystem.writeFile( + apiOutputPath, + apiContent, + (writeErr) => { + if (writeErr) reject(writeErr); + else resolve(); + }, + ); + } + }, + ); + }); + } + } catch (err) { + console.error(err); + } + }; + compiler.hooks.thisCompilation.tap('mf:generateTypes', (compilation) => { compilation.hooks.processAssets.tapPromise( { @@ -82,15 +160,22 @@ export class GenerateTypesPlugin implements WebpackPluginInstance { compilation.constructor.PROCESS_ASSETS_STAGE_OPTIMIZE_TRANSFER, }, async () => { - if (pluginOptions.dev === false && compiledOnce) { - return; - } try { + if (pluginOptions.dev === false && compiledOnce) { + return; + } + + if (compiledOnce) { + emitTypesFiles(); + return; + } + const { zipTypesPath, apiTypesPath, zipName, apiFileName } = retrieveTypesAssetsInfo(finalOptions.remote); if (zipName && compilation.getAsset(zipName)) { return; } + await generateTypesFn(finalOptions); const config = finalOptions.remote.moduleFederationConfig; let zipPrefix = ''; @@ -129,7 +214,7 @@ export class GenerateTypesPlugin implements WebpackPluginInstance { } compiledOnce = true; } catch (err) { - console.error(err); + console.error('Error in mf:generateTypes processAssets hook:', err); } }, ); diff --git a/packages/enhanced/CHANGELOG.md b/packages/enhanced/CHANGELOG.md index abe1ab139a..52e206ef94 100644 --- a/packages/enhanced/CHANGELOG.md +++ b/packages/enhanced/CHANGELOG.md @@ -1,5 +1,20 @@ # @module-federation/enhanced +## 0.8.3 + +### Patch Changes + +- Updated dependencies [5b637c3] +- Updated dependencies [8e172c8] + - @module-federation/data-prefetch@0.8.3 + - @module-federation/dts-plugin@0.8.3 + - @module-federation/sdk@0.8.3 + - @module-federation/runtime-tools@0.8.3 + - @module-federation/manifest@0.8.3 + - @module-federation/rspack@0.8.3 + - @module-federation/bridge-react-webpack-plugin@0.8.3 + - @module-federation/managers@0.8.3 + ## 0.8.2 ### Patch Changes diff --git a/packages/enhanced/package.json b/packages/enhanced/package.json index 1fde338eb3..5891783dcc 100644 --- a/packages/enhanced/package.json +++ b/packages/enhanced/package.json @@ -1,9 +1,13 @@ { "name": "@module-federation/enhanced", - "version": "0.8.2", + "version": "0.8.3", "main": "./dist/src/index.js", "types": "./dist/src/index.d.ts", - "repository": "https://github.com/module-federation/core/tree/main/packages/enhanced", + "repository": { + "type": "git", + "url": "https://github.com/module-federation/core/", + "directory": "packages/enhanced" + }, "files": [ "dist/", "README.md" diff --git a/packages/enhanced/src/rspack.ts b/packages/enhanced/src/rspack.ts index 7e5f44c7e6..98e64b3906 100644 --- a/packages/enhanced/src/rspack.ts +++ b/packages/enhanced/src/rspack.ts @@ -1 +1 @@ -export { ModuleFederationPlugin } from '@module-federation/rspack'; +export { ModuleFederationPlugin } from '@module-federation/rspack/plugin'; diff --git a/packages/error-codes/CHANGELOG.md b/packages/error-codes/CHANGELOG.md index d5fa4d9b7d..56ba4e7ab7 100644 --- a/packages/error-codes/CHANGELOG.md +++ b/packages/error-codes/CHANGELOG.md @@ -1,5 +1,7 @@ # @module-federation/error-codes +## 0.8.3 + ## 0.8.2 ## 0.8.1 diff --git a/packages/error-codes/package.json b/packages/error-codes/package.json index caf090bbb9..5929687c7f 100644 --- a/packages/error-codes/package.json +++ b/packages/error-codes/package.json @@ -4,8 +4,13 @@ "author": "zhanghang ", "public": true, "sideEffects": false, - "version": "0.8.2", + "version": "0.8.3", "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/module-federation/core/", + "directory": "packages/error-codes" + }, "keywords": [ "Module Federation", "error codes" diff --git a/packages/esbuild/CHANGELOG.md b/packages/esbuild/CHANGELOG.md index 8d70784e35..9a6758410b 100644 --- a/packages/esbuild/CHANGELOG.md +++ b/packages/esbuild/CHANGELOG.md @@ -1,5 +1,12 @@ # @module-federation/esbuild +## 0.0.46 + +### Patch Changes + +- Updated dependencies [8e172c8] + - @module-federation/sdk@0.8.3 + ## 0.0.45 ### Patch Changes diff --git a/packages/esbuild/package.json b/packages/esbuild/package.json index c68ea9e256..df42a53284 100644 --- a/packages/esbuild/package.json +++ b/packages/esbuild/package.json @@ -1,11 +1,16 @@ { "name": "@module-federation/esbuild", - "version": "0.0.45", + "version": "0.0.46", "author": "Zack Jackson (@ScriptedAlchemy)", "main": "./dist/index.cjs.js", "module": "./dist/index.esm.js", "types": "./dist/index.cjs.d.ts", "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/module-federation/core/", + "directory": "packages/esbuild" + }, "publishConfig": { "access": "public" }, diff --git a/packages/managers/CHANGELOG.md b/packages/managers/CHANGELOG.md index 2baf054076..f7a8456ed7 100644 --- a/packages/managers/CHANGELOG.md +++ b/packages/managers/CHANGELOG.md @@ -1,5 +1,12 @@ # @module-federation/managers +## 0.8.3 + +### Patch Changes + +- Updated dependencies [8e172c8] + - @module-federation/sdk@0.8.3 + ## 0.8.2 ### Patch Changes diff --git a/packages/managers/package.json b/packages/managers/package.json index a78deda9f7..43e520bab2 100644 --- a/packages/managers/package.json +++ b/packages/managers/package.json @@ -1,6 +1,6 @@ { "name": "@module-federation/managers", - "version": "0.8.2", + "version": "0.8.3", "license": "MIT", "description": "Provide managers for helping handle mf data .", "keywords": [ @@ -14,6 +14,11 @@ "access": "public" }, "author": "hanric ", + "repository": { + "type": "git", + "url": "https://github.com/module-federation/core/", + "directory": "packages/managers" + }, "sideEffects": false, "main": "./dist/index.cjs.js", "module": "./dist/index.esm.js", diff --git a/packages/manifest/CHANGELOG.md b/packages/manifest/CHANGELOG.md index b4f93d1531..3faddcf103 100644 --- a/packages/manifest/CHANGELOG.md +++ b/packages/manifest/CHANGELOG.md @@ -1,5 +1,14 @@ # @module-federation/manifest +## 0.8.3 + +### Patch Changes + +- Updated dependencies [8e172c8] + - @module-federation/dts-plugin@0.8.3 + - @module-federation/sdk@0.8.3 + - @module-federation/managers@0.8.3 + ## 0.8.2 ### Patch Changes diff --git a/packages/manifest/package.json b/packages/manifest/package.json index 890db01ede..bdc5ce3254 100644 --- a/packages/manifest/package.json +++ b/packages/manifest/package.json @@ -1,6 +1,6 @@ { "name": "@module-federation/manifest", - "version": "0.8.2", + "version": "0.8.3", "license": "MIT", "description": "Provide manifest/stats for webpack/rspack MF project .", "keywords": [ @@ -17,6 +17,11 @@ "access": "public" }, "author": "hanric ", + "repository": { + "type": "git", + "url": "https://github.com/module-federation/core/", + "directory": "packages/manifest" + }, "sideEffects": false, "main": "./dist/index.cjs.js", "module": "./dist/index.esm.js", diff --git a/packages/modernjs/CHANGELOG.md b/packages/modernjs/CHANGELOG.md index adf4c33b8b..e19112908b 100644 --- a/packages/modernjs/CHANGELOG.md +++ b/packages/modernjs/CHANGELOG.md @@ -1,5 +1,14 @@ # @module-federation/modern-js +## 0.8.3 + +### Patch Changes + +- Updated dependencies [8e172c8] + - @module-federation/sdk@0.8.3 + - @module-federation/node@2.6.16 + - @module-federation/enhanced@0.8.3 + ## 0.8.2 ### Patch Changes diff --git a/packages/modernjs/package.json b/packages/modernjs/package.json index 1ff7a9ef26..f41434dee4 100644 --- a/packages/modernjs/package.json +++ b/packages/modernjs/package.json @@ -1,6 +1,6 @@ { "name": "@module-federation/modern-js", - "version": "0.8.2", + "version": "0.8.3", "files": [ "dist/", "types.d.ts", @@ -12,6 +12,11 @@ "scripts": { "build": "modern build" }, + "repository": { + "type": "git", + "url": "https://github.com/module-federation/core/", + "directory": "packages/repository" + }, "exports": { ".": { "import": "./dist/esm/cli/index.js", diff --git a/packages/native-federation-tests/package.json b/packages/native-federation-tests/package.json index 2737da08dc..ab685cdc2a 100644 --- a/packages/native-federation-tests/package.json +++ b/packages/native-federation-tests/package.json @@ -50,6 +50,11 @@ "federated tests" ], "author": "Matteo Pietro Dazzi (https://github.com/ilteoood)", + "repository": { + "type": "git", + "url": "https://github.com/module-federation/core/", + "directory": "packages/native-federation-tests" + }, "license": "MIT", "dependencies": { "adm-zip": "^0.5.14", diff --git a/packages/native-federation-typescript/package.json b/packages/native-federation-typescript/package.json index ad5265e193..f51c99cbc1 100644 --- a/packages/native-federation-typescript/package.json +++ b/packages/native-federation-typescript/package.json @@ -50,6 +50,11 @@ "main": "./dist/index.js", "types": "./dist/index.d.ts", "author": "Matteo Pietro Dazzi (https://github.com/ilteoood)", + "repository": { + "type": "git", + "url": "https://github.com/module-federation/core/", + "directory": "packages/native-federation-typescript" + }, "license": "MIT", "dependencies": { "adm-zip": "^0.5.14", diff --git a/packages/nextjs-mf/CHANGELOG.md b/packages/nextjs-mf/CHANGELOG.md index d06e9b50c4..0fa2ff677e 100644 --- a/packages/nextjs-mf/CHANGELOG.md +++ b/packages/nextjs-mf/CHANGELOG.md @@ -1,5 +1,18 @@ # @module-federation/nextjs-mf +## 8.8.6 + +### Patch Changes + +- 4239338: fix broken loading of non nextjs json remotes +- Updated dependencies [f817674] +- Updated dependencies [8e172c8] + - @module-federation/runtime@0.8.3 + - @module-federation/sdk@0.8.3 + - @module-federation/node@2.6.16 + - @module-federation/webpack-bundler-runtime@0.8.3 + - @module-federation/enhanced@0.8.3 + ## 8.8.5 ### Patch Changes diff --git a/packages/nextjs-mf/package.json b/packages/nextjs-mf/package.json index 10caa11fbd..d3a8dd21bf 100644 --- a/packages/nextjs-mf/package.json +++ b/packages/nextjs-mf/package.json @@ -1,12 +1,16 @@ { "name": "@module-federation/nextjs-mf", - "version": "8.8.5", + "version": "8.8.6", "license": "MIT", "main": "dist/src/index.js", "types": "dist/src/index.d.ts", "type": "commonjs", "description": "Module Federation helper for NextJS", - "repository": "https://github.com/module-federation/core/tree/main/packages/nextjs-mf", + "repository": { + "type": "git", + "url": "https://github.com/module-federation/core/", + "directory": "packages/nextjs-mf" + }, "author": "Zack Jackson ", "contributors": [ "Pavel Chertorogov, nodkz (www.ps.kz)" diff --git a/packages/nextjs-mf/src/plugins/NextFederationPlugin/remove-unnecessary-shared-keys.test.ts b/packages/nextjs-mf/src/plugins/NextFederationPlugin/remove-unnecessary-shared-keys.test.ts index 4d16f4e23c..cf39803267 100644 --- a/packages/nextjs-mf/src/plugins/NextFederationPlugin/remove-unnecessary-shared-keys.test.ts +++ b/packages/nextjs-mf/src/plugins/NextFederationPlugin/remove-unnecessary-shared-keys.test.ts @@ -2,7 +2,7 @@ import { removeUnnecessarySharedKeys } from './remove-unnecessary-shared-keys'; describe('removeUnnecessarySharedKeys', () => { beforeEach(() => { - jest.spyOn(console, 'warn').mockImplementation(() => {}); + jest.spyOn(console, 'warn').mockImplementation(jest.fn()); }); afterEach(() => { diff --git a/packages/nextjs-mf/src/plugins/container/InvertedContainerPlugin.ts b/packages/nextjs-mf/src/plugins/container/InvertedContainerPlugin.ts index 34dbc71c4c..75265765e7 100644 --- a/packages/nextjs-mf/src/plugins/container/InvertedContainerPlugin.ts +++ b/packages/nextjs-mf/src/plugins/container/InvertedContainerPlugin.ts @@ -6,8 +6,6 @@ import { } from '@module-federation/enhanced'; class InvertedContainerPlugin { - constructor() {} - public apply(compiler: Compiler): void { compiler.hooks.thisCompilation.tap( 'EmbeddedContainerPlugin', diff --git a/packages/nextjs-mf/src/plugins/container/runtimePlugin.ts b/packages/nextjs-mf/src/plugins/container/runtimePlugin.ts index 049133e6f1..558dbcc0bb 100644 --- a/packages/nextjs-mf/src/plugins/container/runtimePlugin.ts +++ b/packages/nextjs-mf/src/plugins/container/runtimePlugin.ts @@ -1,8 +1,4 @@ import { FederationRuntimePlugin } from '@module-federation/runtime/types'; -import { - ModuleInfo, - ConsumerModuleInfoWithPublicPath, -} from '@module-federation/sdk'; export default function (): FederationRuntimePlugin { return { @@ -199,22 +195,23 @@ export default function (): FederationRuntimePlugin { return args; } - // re-assign publicPath based on remoteEntry location - if (options.inBrowser) { - remoteSnapshot.publicPath = remoteSnapshot.publicPath.substring( + // re-assign publicPath based on remoteEntry location if in browser nextjs remote + const { publicPath } = remoteSnapshot; + if (options.inBrowser && publicPath.includes('/_next/')) { + remoteSnapshot.publicPath = publicPath.substring( 0, - remoteSnapshot.publicPath.lastIndexOf('/_next/') + 7, + publicPath.lastIndexOf('/_next/') + 7, ); } else { const serverPublicPath = manifestUrl.substring( 0, manifestUrl.indexOf('mf-manifest.json'), ); - remoteSnapshot.publicPath = serverPublicPath; - if ('publicPath' in manifestJson.metaData) { - manifestJson.metaData.publicPath = serverPublicPath; - } + } + + if ('publicPath' in manifestJson.metaData) { + manifestJson.metaData.publicPath = remoteSnapshot.publicPath; } return args; diff --git a/packages/nextjs-mf/utils/index.ts b/packages/nextjs-mf/utils/index.ts index 7ed60901de..cf867c8cf6 100644 --- a/packages/nextjs-mf/utils/index.ts +++ b/packages/nextjs-mf/utils/index.ts @@ -22,7 +22,7 @@ export type { FlushedChunksProps } from './flushedChunks'; */ export const revalidate = function ( fetchModule: any = undefined, - force: boolean = false, + force = false, ): Promise { if (typeof window !== 'undefined') { console.error('revalidate should only be called server-side'); diff --git a/packages/node/CHANGELOG.md b/packages/node/CHANGELOG.md index 374837ec43..408dd4c8a4 100644 --- a/packages/node/CHANGELOG.md +++ b/packages/node/CHANGELOG.md @@ -1,5 +1,16 @@ # @module-federation/node +## 2.6.16 + +### Patch Changes + +- Updated dependencies [f817674] +- Updated dependencies [8e172c8] + - @module-federation/runtime@0.8.3 + - @module-federation/sdk@0.8.3 + - @module-federation/enhanced@0.8.3 + - @module-federation/utilities@3.1.34 + ## 2.6.15 ### Patch Changes diff --git a/packages/node/package.json b/packages/node/package.json index 49f2ff0072..52649a03c0 100644 --- a/packages/node/package.json +++ b/packages/node/package.json @@ -1,7 +1,7 @@ { "public": true, "name": "@module-federation/node", - "version": "2.6.15", + "version": "2.6.16", "type": "commonjs", "main": "./dist/src/index.js", "exports": { @@ -57,7 +57,11 @@ ], "types": "src/index.d.ts", "description": "Module Federation helper for Node", - "repository": "https://github.com/module-federation/core/tree/main/packages/node", + "repository": { + "type": "git", + "url": "https://github.com/module-federation/core/", + "directory": "packages/node" + }, "author": "Zack Jackson ", "license": "MIT", "dependencies": { diff --git a/packages/retry-plugin/CHANGELOG.md b/packages/retry-plugin/CHANGELOG.md index 051c8aad17..764b92c75a 100644 --- a/packages/retry-plugin/CHANGELOG.md +++ b/packages/retry-plugin/CHANGELOG.md @@ -1,5 +1,12 @@ # @module-federation/retry-plugin +## 0.8.3 + +### Patch Changes + +- Updated dependencies [8e172c8] + - @module-federation/sdk@0.8.3 + ## 0.8.2 ### Patch Changes diff --git a/packages/retry-plugin/package.json b/packages/retry-plugin/package.json index ba47d52821..2eb8ff608f 100644 --- a/packages/retry-plugin/package.json +++ b/packages/retry-plugin/package.json @@ -1,11 +1,16 @@ { "name": "@module-federation/retry-plugin", - "version": "0.8.2", + "version": "0.8.3", "author": "danpeen ", "main": "./dist/index.js", "module": "./dist/esm/index.js", "types": "./dist/index.d.ts", "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/module-federation/core/", + "directory": "packages/retry-plugin" + }, "publishConfig": { "access": "public" }, diff --git a/packages/rsbuild-plugin/CHANGELOG.md b/packages/rsbuild-plugin/CHANGELOG.md index 221abdc54e..55a6e26d7f 100644 --- a/packages/rsbuild-plugin/CHANGELOG.md +++ b/packages/rsbuild-plugin/CHANGELOG.md @@ -1,5 +1,13 @@ # @module-federation/rsbuild-plugin +## 0.8.3 + +### Patch Changes + +- Updated dependencies [8e172c8] + - @module-federation/sdk@0.8.3 + - @module-federation/enhanced@0.8.3 + ## 0.8.2 ### Patch Changes diff --git a/packages/rsbuild-plugin/package.json b/packages/rsbuild-plugin/package.json index 99793a4025..b19f74ab5b 100644 --- a/packages/rsbuild-plugin/package.json +++ b/packages/rsbuild-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@module-federation/rsbuild-plugin", - "version": "0.8.2", + "version": "0.8.3", "description": "Module Federation plugin for Rsbuild", "homepage": "https://module-federation.io", "bugs": { diff --git a/packages/rspack/CHANGELOG.md b/packages/rspack/CHANGELOG.md index c8b5af2fd5..ebab465841 100644 --- a/packages/rspack/CHANGELOG.md +++ b/packages/rspack/CHANGELOG.md @@ -1,5 +1,17 @@ # @module-federation/rspack +## 0.8.3 + +### Patch Changes + +- Updated dependencies [8e172c8] + - @module-federation/dts-plugin@0.8.3 + - @module-federation/sdk@0.8.3 + - @module-federation/runtime-tools@0.8.3 + - @module-federation/manifest@0.8.3 + - @module-federation/bridge-react-webpack-plugin@0.8.3 + - @module-federation/managers@0.8.3 + ## 0.8.2 ### Patch Changes diff --git a/packages/rspack/package.json b/packages/rspack/package.json index ea97bc96e5..09bd7cb0f4 100644 --- a/packages/rspack/package.json +++ b/packages/rspack/package.json @@ -1,6 +1,6 @@ { "name": "@module-federation/rspack", - "version": "0.8.2", + "version": "0.8.3", "license": "MIT", "keywords": [ "Module Federation", @@ -13,6 +13,11 @@ "publishConfig": { "access": "public" }, + "repository": { + "type": "git", + "url": "https://github.com/module-federation/core/", + "directory": "packages/rspack" + }, "author": "hanric ", "sideEffects": false, "main": "./dist/index.cjs.js", @@ -34,12 +39,20 @@ "import": "./dist/index.esm.js", "require": "./dist/index.cjs.js", "types": "./dist/index.cjs.d.ts" + }, + "./plugin": { + "types": "./dist/plugin.cjs.d.ts", + "import": "./dist/plugin.esm.mjs", + "require": "./dist/plugin.cjs.js" } }, "typesVersions": { "*": { ".": [ "./dist/index.cjs.d.ts" + ], + "plugin": [ + "./dist/plugin.cjs.d.ts" ] } }, diff --git a/packages/rspack/rollup.config.js b/packages/rspack/rollup.config.js index 529f337f44..bb37e988aa 100644 --- a/packages/rspack/rollup.config.js +++ b/packages/rspack/rollup.config.js @@ -1,9 +1,14 @@ const copy = require('rollup-plugin-copy'); const replace = require('@rollup/plugin-replace'); +const path = require('path'); module.exports = (rollupConfig, projectOptions) => { const pkg = require('./package.json'); + rollupConfig.input['plugin'] = path.resolve( + process.cwd(), + './packages/rspack/src/ModuleFederationPlugin.ts', + ); rollupConfig.plugins.push( replace({ __VERSION__: JSON.stringify(pkg.version), diff --git a/packages/rspack/src/ModuleFederationPlugin.ts b/packages/rspack/src/ModuleFederationPlugin.ts index e0f87411bb..b8344efd6c 100644 --- a/packages/rspack/src/ModuleFederationPlugin.ts +++ b/packages/rspack/src/ModuleFederationPlugin.ts @@ -1,4 +1,4 @@ -import { +import type { Compiler, ModuleFederationPluginOptions, RspackPluginInstance, diff --git a/packages/runtime-tools/CHANGELOG.md b/packages/runtime-tools/CHANGELOG.md index 9b8306f584..4d26cbbbf7 100644 --- a/packages/runtime-tools/CHANGELOG.md +++ b/packages/runtime-tools/CHANGELOG.md @@ -1,5 +1,13 @@ # @module-federation/runtime-tools +## 0.8.3 + +### Patch Changes + +- Updated dependencies [f817674] + - @module-federation/runtime@0.8.3 + - @module-federation/webpack-bundler-runtime@0.8.3 + ## 0.8.2 ### Patch Changes diff --git a/packages/runtime-tools/package.json b/packages/runtime-tools/package.json index e301774fee..3fa8d9e2ba 100644 --- a/packages/runtime-tools/package.json +++ b/packages/runtime-tools/package.json @@ -1,11 +1,16 @@ { "name": "@module-federation/runtime-tools", - "version": "0.8.2", + "version": "0.8.3", "author": "zhanghang ", "main": "./dist/index.cjs", "module": "./dist/index.esm.mjs", "types": "./dist/index.cjs.d.ts", "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/module-federation/core/", + "directory": "packages/runtime-tools" + }, "publishConfig": { "access": "public" }, diff --git a/packages/runtime/CHANGELOG.md b/packages/runtime/CHANGELOG.md index 5ab0e81642..5ef5213af6 100644 --- a/packages/runtime/CHANGELOG.md +++ b/packages/runtime/CHANGELOG.md @@ -1,5 +1,14 @@ # @module-federation/runtime +## 0.8.3 + +### Patch Changes + +- f817674: chore(runtime): remove duplicate registerRemotes warn +- Updated dependencies [8e172c8] + - @module-federation/sdk@0.8.3 + - @module-federation/error-codes@0.8.3 + ## 0.8.2 ### Patch Changes diff --git a/packages/runtime/package.json b/packages/runtime/package.json index baa9946dd6..36ca479506 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -1,6 +1,6 @@ { "name": "@module-federation/runtime", - "version": "0.8.2", + "version": "0.8.3", "author": "zhouxiao ", "main": "./dist/index.cjs.js", "module": "./dist/index.esm.mjs", @@ -9,6 +9,11 @@ "publishConfig": { "access": "public" }, + "repository": { + "type": "git", + "url": "https://github.com/module-federation/core/", + "directory": "packages/runtime" + }, "files": [ "dist/", "README.md" diff --git a/packages/runtime/src/remote/index.ts b/packages/runtime/src/remote/index.ts index 2bff2b2fbd..c27b2be6e1 100644 --- a/packages/runtime/src/remote/index.ts +++ b/packages/runtime/src/remote/index.ts @@ -441,9 +441,7 @@ export class RemoteHandler { } else { const messages = [ `The remote "${remote.name}" is already registered.`, - options?.force - ? 'Hope you have known that OVERRIDE it may have some unexpected errors' - : 'If you want to merge the remote, you can set "force: true".', + 'Please note that overriding it may cause unexpected errors.', ]; if (options?.force) { // remove registered remote @@ -451,8 +449,8 @@ export class RemoteHandler { normalizeRemote(); targetRemotes.push(remote); this.hooks.lifecycle.registerRemote.emit({ remote, origin: host }); + warn(messages.join(' ')); } - warn(messages.join(' ')); } } diff --git a/packages/sdk/CHANGELOG.md b/packages/sdk/CHANGELOG.md index de5354364e..03d5b12e98 100644 --- a/packages/sdk/CHANGELOG.md +++ b/packages/sdk/CHANGELOG.md @@ -1,5 +1,11 @@ # @module-federation/sdk +## 0.8.3 + +### Patch Changes + +- 8e172c8: add `cwd` property to generate types + ## 0.8.2 ## 0.8.1 diff --git a/packages/sdk/package.json b/packages/sdk/package.json index b7ef34d1ca..d0c2a3d40f 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -1,6 +1,6 @@ { "name": "@module-federation/sdk", - "version": "0.8.2", + "version": "0.8.3", "license": "MIT", "description": "A sdk for support module federation", "keywords": [ @@ -11,6 +11,11 @@ "dist/", "README.md" ], + "repository": { + "type": "git", + "url": "https://github.com/module-federation/core/", + "directory": "packages/sdk" + }, "publishConfig": { "access": "public" }, diff --git a/packages/storybook-addon/CHANGELOG.md b/packages/storybook-addon/CHANGELOG.md index 8417252799..852f0a9e73 100644 --- a/packages/storybook-addon/CHANGELOG.md +++ b/packages/storybook-addon/CHANGELOG.md @@ -1,5 +1,14 @@ # @module-federation/storybook-addon +## 3.0.14 + +### Patch Changes + +- Updated dependencies [8e172c8] + - @module-federation/sdk@0.8.3 + - @module-federation/enhanced@0.8.3 + - @module-federation/utilities@3.1.34 + ## 3.0.13 ### Patch Changes diff --git a/packages/storybook-addon/package.json b/packages/storybook-addon/package.json index c7cd0fb200..132a49afa7 100644 --- a/packages/storybook-addon/package.json +++ b/packages/storybook-addon/package.json @@ -1,9 +1,13 @@ { "name": "@module-federation/storybook-addon", - "version": "3.0.13", + "version": "3.0.14", "description": "Storybook addon to consume remote module federated apps/components", "license": "MIT", - "repository": "https://github.com/module-federation/core/tree/main/packages/storybook-addon", + "repository": { + "type": "git", + "url": "https://github.com/module-federation/core/", + "directory": "packages/storybook-addon" + }, "publishConfig": { "access": "public" }, @@ -57,7 +61,7 @@ }, "peerDependencies": { "@rsbuild/core": "^1.0.1", - "@module-federation/utilities": "^3.1.33", + "@module-federation/utilities": "^3.1.34", "@nx/react": ">= 16.0.0", "@nx/webpack": ">= 16.0.0", "@storybook/core-common": "^6.5.16 || ^7.0.0 || ^ 8.0.0", diff --git a/packages/third-party-dts-extractor/CHANGELOG.md b/packages/third-party-dts-extractor/CHANGELOG.md index 4199c8158d..5309d0e5b8 100644 --- a/packages/third-party-dts-extractor/CHANGELOG.md +++ b/packages/third-party-dts-extractor/CHANGELOG.md @@ -1,5 +1,7 @@ # @module-federation/third-party-dts-extractor +## 0.8.3 + ## 0.8.2 ## 0.8.1 diff --git a/packages/third-party-dts-extractor/package.json b/packages/third-party-dts-extractor/package.json index bcaa35f53e..a07f852d0b 100644 --- a/packages/third-party-dts-extractor/package.json +++ b/packages/third-party-dts-extractor/package.json @@ -1,10 +1,15 @@ { "name": "@module-federation/third-party-dts-extractor", - "version": "0.8.2", + "version": "0.8.3", "files": [ "dist/", "README.md" ], + "repository": { + "type": "git", + "url": "https://github.com/module-federation/core/", + "directory": "packages/third-party-dts-extractor" + }, "publishConfig": { "access": "public" }, diff --git a/packages/typescript/package.json b/packages/typescript/package.json index 56606c6c63..d031d7e640 100644 --- a/packages/typescript/package.json +++ b/packages/typescript/package.json @@ -22,6 +22,11 @@ "Zack Jackson (https://github.com/ScriptedAlchemy)", "Pavan Divi (https://github.com/pavandv)" ], + "repository": { + "type": "git", + "url": "https://github.com/module-federation/core/", + "directory": "packages/typescript" + }, "scripts": { "build": "tsc" }, diff --git a/packages/utilities/CHANGELOG.md b/packages/utilities/CHANGELOG.md index e8358ac272..7a653ee42a 100644 --- a/packages/utilities/CHANGELOG.md +++ b/packages/utilities/CHANGELOG.md @@ -1,5 +1,12 @@ # @module-federation/utilities +## 3.1.34 + +### Patch Changes + +- Updated dependencies [8e172c8] + - @module-federation/sdk@0.8.3 + ## 3.1.33 ### Patch Changes diff --git a/packages/utilities/package.json b/packages/utilities/package.json index 15fd82a7ac..665e57ac23 100644 --- a/packages/utilities/package.json +++ b/packages/utilities/package.json @@ -1,6 +1,6 @@ { "name": "@module-federation/utilities", - "version": "3.1.33", + "version": "3.1.34", "main": "./dist/index.cjs.js", "module": "./dist/index.esm.js", "types": "./dist/index.cjs.d.ts", @@ -12,7 +12,11 @@ "dist/", "README.md" ], - "repository": "https://github.com/module-federation/core/tree/main/packages/utilities", + "repository": { + "type": "git", + "url": "https://github.com/module-federation/core/", + "directory": "packages/utilities" + }, "devDependencies": { "react": "18.3.1" }, diff --git a/packages/webpack-bundler-runtime/CHANGELOG.md b/packages/webpack-bundler-runtime/CHANGELOG.md index e997a3fd95..5b6569e8cc 100644 --- a/packages/webpack-bundler-runtime/CHANGELOG.md +++ b/packages/webpack-bundler-runtime/CHANGELOG.md @@ -1,5 +1,14 @@ # @module-federation/webpack-bundler-runtime +## 0.8.3 + +### Patch Changes + +- Updated dependencies [f817674] +- Updated dependencies [8e172c8] + - @module-federation/runtime@0.8.3 + - @module-federation/sdk@0.8.3 + ## 0.8.2 ### Patch Changes diff --git a/packages/webpack-bundler-runtime/package.json b/packages/webpack-bundler-runtime/package.json index 6ac6f81550..c7b99544da 100644 --- a/packages/webpack-bundler-runtime/package.json +++ b/packages/webpack-bundler-runtime/package.json @@ -1,7 +1,7 @@ { "public": true, "name": "@module-federation/webpack-bundler-runtime", - "version": "0.8.2", + "version": "0.8.3", "license": "MIT", "description": "Module Federation Runtime for webpack", "keywords": [ @@ -15,6 +15,11 @@ "publishConfig": { "access": "public" }, + "repository": { + "type": "git", + "url": "https://github.com/module-federation/core/", + "directory": "packages/webpack-bundler-runtime" + }, "author": "zhanghang ", "main": "./dist/index.cjs.js", "module": "./dist/index.esm.mjs",