Skip to content

Commit

Permalink
fix: remoteEntry support hash; format dynamic es module (#35)
Browse files Browse the repository at this point in the history
  • Loading branch information
zhangHongEn authored Aug 5, 2024
1 parent dec3b9b commit 7c3ba59
Show file tree
Hide file tree
Showing 8 changed files with 100 additions and 69 deletions.
2 changes: 0 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion examples/rust/rsbuild.config.ts
Original file line number Diff line number Diff line change
@@ -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: {
Expand Down
6 changes: 2 additions & 4 deletions examples/rust/src/App.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div>
rust host
<hr />
<AppComponent />
<ViteApp />
</div>
);
}
3 changes: 1 addition & 2 deletions examples/vite/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
Expand Down
20 changes: 1 addition & 19 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 15 additions & 7 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(',')}
Expand Down Expand Up @@ -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) => {
Expand Down Expand Up @@ -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',
};
}

Expand Down Expand Up @@ -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),
Expand Down
6 changes: 3 additions & 3 deletions src/utils/normalizeModuleFederationOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ function normalizeShareItem(
from: '',
shareConfig: {
singleton: false,
requiredVersion: version || '*',
requiredVersion: `^${version}` || '*',
},
};
}
Expand All @@ -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,
},
};
Expand Down Expand Up @@ -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,
Expand Down
108 changes: 77 additions & 31 deletions src/utils/vitePluginAddEntry.ts
Original file line number Diff line number Diff line change
@@ -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(
'<head>',
`<head><script type="module" src=${JSON.stringify(
entryPath.replace(/.+?\:([/\\])[/\\]?/, '$1').replace(/\\/g, '/')
entryPath.replace(/.+?\:([/\\])[/\\]?/, '$1').replace(/\\\\/g, '/')
)}></script>`
);
return c.replace('<head>', `<head><script type="module" src=${fileName}></script>`);
},
},
};
{
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 = /<script\s+[^>]*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;

0 comments on commit 7c3ba59

Please sign in to comment.