diff --git a/README.md b/README.md index 824959c..cb35c43 100644 --- a/README.md +++ b/README.md @@ -64,8 +64,6 @@ export default defineConfig({ ## roadmap -- fix: vitePluginAddEntry.ts This plugin may need to support the case where base in vite.config.js is not configured as "/" -- fix: remoteEntry and hostInit file names support hash generation - feat: generate mf-manifest.json - feat: support chrome plugin diff --git a/examples/rust/rsbuild.config.ts b/examples/rust/rsbuild.config.ts index e0a1590..83b7151 100644 --- a/examples/rust/rsbuild.config.ts +++ b/examples/rust/rsbuild.config.ts @@ -1,6 +1,6 @@ +import { ModuleFederationPlugin } from '@module-federation/enhanced/rspack'; import { defineConfig } from '@rsbuild/core'; import { pluginReact } from '@rsbuild/plugin-react'; -import { ModuleFederationPlugin } from '@module-federation/enhanced/rspack'; export default defineConfig({ server: { diff --git a/examples/rust/src/App.tsx b/examples/rust/src/App.tsx index 0435f29..37d47fb 100644 --- a/examples/rust/src/App.tsx +++ b/examples/rust/src/App.tsx @@ -1,13 +1,11 @@ -import { applyVueInReact } from 'veaury'; -import App from 'viteRemote/App'; -const AppComponent = App.default; +import ViteApp from 'viteRemote/App'; export default function Button() { return (
rust host
- +
); } diff --git a/examples/vite/package.json b/examples/vite/package.json index 3974f2d..dd7b9e1 100644 --- a/examples/vite/package.json +++ b/examples/vite/package.json @@ -11,13 +11,12 @@ "@module-federation/vite": "workspace:*", "react": "^18.3.1", "react-dom": "^18.2.0", - "vue": "^3.4.29", + "vue": "^3.4.35", "vue-router": "^4.4.0" }, "devDependencies": { "@swc/core": "~1.6.0", "@vitejs/plugin-react": "^4.3.1", - "@vitejs/plugin-vue": "^5.0.5", "vite": "^5.3.1", "vite-plugin-top-level-await": "^1.4.1" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4019f0a..58989cb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -91,7 +91,7 @@ importers: specifier: ^18.2.0 version: 18.3.1(react@18.3.1) vue: - specifier: ^3.4.29 + specifier: ^3.4.35 version: 3.4.35(typescript@5.5.3) vue-router: specifier: ^4.4.0 @@ -103,9 +103,6 @@ importers: '@vitejs/plugin-react': specifier: ^4.3.1 version: 4.3.1(vite@5.3.5(@types/node@22.0.2)(terser@5.20.0)) - '@vitejs/plugin-vue': - specifier: ^5.0.5 - version: 5.1.2(vite@5.3.5(@types/node@22.0.2)(terser@5.20.0))(vue@3.4.35(typescript@5.5.3)) vite: specifier: ^5.3.1 version: 5.3.5(@types/node@22.0.2)(terser@5.20.0) @@ -2616,16 +2613,6 @@ packages: peerDependencies: vite: ^4.2.0 || ^5.0.0 - '@vitejs/plugin-vue@5.1.2': - resolution: - { - integrity: sha512-nY9IwH12qeiJqumTCLJLE7IiNx7HZ39cbHaysEUd+Myvbz9KAqd2yq+U01Kab1R/H1BmiyM2ShTYlNH32Fzo3A==, - } - engines: { node: ^18.0.0 || >=20.0.0 } - peerDependencies: - vite: ^5.0.0 - vue: ^3.2.25 - '@vue/compiler-core@3.4.35': resolution: { @@ -8289,11 +8276,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@vitejs/plugin-vue@5.1.2(vite@5.3.5(@types/node@22.0.2)(terser@5.20.0))(vue@3.4.35(typescript@5.5.3))': - dependencies: - vite: 5.3.5(@types/node@22.0.2)(terser@5.20.0) - vue: 3.4.35(typescript@5.5.3) - '@vue/compiler-core@3.4.35': dependencies: '@babel/parser': 7.25.3 diff --git a/src/index.ts b/src/index.ts index c415d38..fe29634 100644 --- a/src/index.ts +++ b/src/index.ts @@ -43,7 +43,16 @@ function generateRemoteEntry(options: NormalizedModuleFederationOptions): string ${Object.keys(options.exposes) .map((key) => { return ` - ${JSON.stringify(key)}: () => import(${JSON.stringify(options.exposes[key].import)}) + ${JSON.stringify(key)}: async () => { + const importModule = await import(${JSON.stringify(options.exposes[key].import)}) + const exportModule = {} + Object.assign(exportModule, importModule) + Object.defineProperty(exportModule, "__esModule", { + value: true, + enumerable: false + }) + return exportModule + } `; }) .join(',')} @@ -76,7 +85,7 @@ function generateRemoteEntry(options: NormalizedModuleFederationOptions): string }) .join(',')} } - const initRes = await runtimeInit({ + const initRes = runtimeInit({ name: ${JSON.stringify(options.name)}, remotes: [${Object.keys(options.remotes) .map((key) => { @@ -124,10 +133,10 @@ function wrapShare( strictVersion: ${JSON.stringify(shareConfig.strictVersion)}, requiredVersion: ${JSON.stringify(shareConfig.requiredVersion)} }}}) - export ${command !== 'build' ? 'default' : 'const dynamicExport = '} res() + export default res() `, map: null, - syntheticNamedExports: 'dynamicExport', + syntheticNamedExports: 'default', }; } @@ -184,15 +193,14 @@ function federation(mfUserOptions: ModuleFederationOptions): Plugin[] { aliasToArrayPlugin, normalizeOptimizeDepsPlugin, normalizeBuildPlugin([...Object.keys(shared), "@module-federation/runtime"]), - addEntry({ + ...addEntry({ entryName: 'remoteEntry', entryPath: emptyPath + '?__mf__wrapRemoteEntry__', fileName: filename, }), - addEntry({ + ...addEntry({ entryName: 'hostInit', entryPath: emptyPath + '?__mf__isHostInit', - fileName: 'hostInit.js', }), overrideModule({ override: Object.keys(shared), diff --git a/src/utils/normalizeModuleFederationOptions.ts b/src/utils/normalizeModuleFederationOptions.ts index 5873765..934095e 100644 --- a/src/utils/normalizeModuleFederationOptions.ts +++ b/src/utils/normalizeModuleFederationOptions.ts @@ -142,7 +142,7 @@ function normalizeShareItem( from: '', shareConfig: { singleton: false, - requiredVersion: version || '*', + requiredVersion: `^${version}` || '*', }, }; } @@ -153,7 +153,7 @@ function normalizeShareItem( scope: shareItem.shareScope || 'default', shareConfig: { singleton: shareItem.singleton || false, - requiredVersion: shareItem.requiredVersion || version || '*', + requiredVersion: shareItem.requiredVersion || `^${version}` || '*', strictVersion: !!shareItem.strictVersion, }, }; @@ -262,7 +262,7 @@ export function normalizeModuleFederationOptions( ): NormalizedModuleFederationOptions { return { exposes: normalizeExposes(options.exposes), - filename: options.filename || 'remoteEntry.js', + filename: options.filename || 'remoteEntry-[hash]', library: normalizeLibrary(options.library), name: options.name, // remoteType: options.remoteType, diff --git a/src/utils/vitePluginAddEntry.ts b/src/utils/vitePluginAddEntry.ts index 42888a6..1f3d8b2 100644 --- a/src/utils/vitePluginAddEntry.ts +++ b/src/utils/vitePluginAddEntry.ts @@ -1,48 +1,94 @@ +import * as fs from 'fs'; +import * as path from 'path'; import { Plugin } from 'vite'; interface AddEntryOptions { entryName: string; entryPath: string; - fileName: string; + fileName?: string; } -const addEntry = ({ entryName, entryPath, fileName }: AddEntryOptions): Plugin => { - let command: string; +const addEntry = ({ entryName, entryPath, fileName }: AddEntryOptions): Plugin[] => { + let entryFiles: string[] = []; + let htmlFilePath: string; - return { - name: 'add-entry', - configureServer(server) { - server.middlewares.use((req, res, next) => { - if (req.url && req.url.startsWith(fileName.replace(/^\/?/, '/'))) { - req.url = entryPath; - } - next(); - }); - }, - config(config, { command: _command }) { - command = _command; - }, - buildStart() { - if (command !== 'build') return; - // if we don't expose any modules, there is no need to emit file - this.emitFile({ - fileName: `${fileName}`, - type: 'chunk', - id: entryPath, - preserveSignature: 'strict', - }); - }, - transformIndexHtml(c) { - if (command !== 'build') + return [ + { + name: 'add-entry', + apply: "serve", + configureServer(server) { + server.httpServer?.once?.('listening', () => { + const { port } = server.config.server; + fetch(`http://localhost:${port}${entryPath}`) + }); + server.middlewares.use((req, res, next) => { + if (!fileName) { + next() + return + } + if (req.url && req.url.startsWith(fileName.replace(/^\/?/, '/'))) { + req.url = entryPath; + } + next(); + }); + }, + transformIndexHtml(c) { return c.replace( '', `` ); - return c.replace('', ``); + }, }, - }; + { + name: "add-entry", + enforce: "post", + apply: "build", + configResolved(config) { + const inputOptions = config.build.rollupOptions.input; + + if (!inputOptions) { + htmlFilePath = path.resolve(config.root, 'index.html'); + } else if (typeof inputOptions === 'string') { + entryFiles = [inputOptions]; + htmlFilePath = path.resolve(config.root, inputOptions); + } else if (Array.isArray(inputOptions)) { + entryFiles = inputOptions; + htmlFilePath = path.resolve(config.root, inputOptions[0]); + } else if (typeof inputOptions === 'object') { + entryFiles = Object.values(inputOptions); + htmlFilePath = path.resolve(config.root, Object.values(inputOptions)[0]); + } + }, + buildStart() { + const hasHash = fileName?.includes?.("[hash") + this.emitFile({ + [hasHash ? "name" : "fileName"]: fileName, + type: 'chunk', + id: entryPath, + preserveSignature: 'strict', + }); + if (htmlFilePath) { + const htmlContent = fs.readFileSync(htmlFilePath, 'utf-8'); + const scriptRegex = /]*src=["']([^"']+)["'][^>]*>/gi; + let match: RegExpExecArray | null; + + while ((match = scriptRegex.exec(htmlContent)) !== null) { + entryFiles.push(match[1]); + } + } + }, + transform(code, id) { + if (entryFiles.some(file => id.endsWith(file))) { + const injection = ` + import ${JSON.stringify(entryPath)}; + `; + return injection + code + } + } + } + ] }; export default addEntry;