-
Notifications
You must be signed in to change notification settings - Fork 82
feat: new varFilename option - allow generate additional "var" remoteEntry.js file (#247) #353
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| import { createInstance } from '@module-federation/runtime'; | ||
| import React from 'react'; | ||
|
|
||
| const mf = createInstance({ | ||
| name: 'viteViteHost', | ||
| remotes: [ | ||
| { | ||
| name: '@namespace/viteViteRemote', | ||
| entry: 'http://localhost:5176/testbase/varRemoteEntry.js', | ||
| type: 'var', | ||
| }, | ||
| ], | ||
| shared: { | ||
| react: { | ||
| version: React.version, | ||
| lib: () => React, | ||
| shareConfig: { | ||
| singleton: true, | ||
| requiredVersion: false, | ||
| }, | ||
| }, | ||
| }, | ||
| }); | ||
|
|
||
| export { mf }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,132 @@ | ||
| import { Plugin } from 'vite'; | ||
| import { findRemoteEntryFile } from '../utils/bundleHelpers'; | ||
| import { warn } from '../utils/logUtils'; | ||
| import { getNormalizeModuleFederationOptions } from '../utils/normalizeModuleFederationOptions'; | ||
|
|
||
| const VarRemoteEntry = (): Plugin[] => { | ||
| const mfOptions = getNormalizeModuleFederationOptions(); | ||
| const { name, varFilename, filename } = mfOptions; | ||
|
|
||
| let viteConfig: any; | ||
|
|
||
| return [ | ||
| { | ||
| name: 'module-federation-var-remote-entry', | ||
| apply: 'serve', | ||
| /** | ||
| * Stores resolved Vite config for later use | ||
| */ | ||
| /** | ||
| * Finalizes configuration after all plugins are resolved | ||
| * @param config - Fully resolved Vite config | ||
| */ | ||
| configResolved(config) { | ||
| viteConfig = config; | ||
| }, | ||
| /** | ||
| * Configures dev server middleware to handle varRemoteEntry requests | ||
| * @param server - Vite dev server instance | ||
| */ | ||
| configureServer(server) { | ||
| server.middlewares.use((req, res, next) => { | ||
| if (!varFilename) { | ||
| next(); | ||
| return; | ||
| } | ||
| if ( | ||
| req.url?.replace(/\?.*/, '') === (viteConfig.base + varFilename).replace(/^\/?/, '/') | ||
| ) { | ||
| res.setHeader('Content-Type', 'text/javascript'); | ||
| res.setHeader('Access-Control-Allow-Origin', '*'); | ||
| console.log({ filename }); | ||
| res.end(generateVarRemoteEntry(filename)); | ||
| } else { | ||
| next(); | ||
| } | ||
| }); | ||
| }, | ||
| }, | ||
| { | ||
| name: 'module-federation-var-remote-entry', | ||
| enforce: 'post', | ||
| /** | ||
| * Initial plugin configuration | ||
| * @param config - Vite config object | ||
| * @param command - Current Vite command (serve/build) | ||
| */ | ||
| config(config, { command }) { | ||
| if (!config.build) config.build = {}; | ||
| }, | ||
| /** | ||
| * Generates the module federation "var" remote entry file | ||
| * @param options - Rollup output options | ||
| * @param bundle - Generated bundle assets | ||
| */ | ||
| async generateBundle(options, bundle) { | ||
| if (!varFilename) return; | ||
|
|
||
| const isValidName = isValidVarName(name); | ||
|
|
||
| if (!isValidName) { | ||
| warn( | ||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we fail if provided remotes: {
'@app/remote': '@app/remote@http://localhost:3000/varRemoteEntry.js',
}But, as my example shows, this still works with dynamic |
||
| `Provided remote name "${name}" is not valid for "var" remoteEntry type, thus it's placed in globalThis['${name}'].\nIt may cause problems, so you would better want to use valid var name (see https://www.w3schools.com/js/js_variables.asp).` | ||
| ); | ||
| } | ||
|
|
||
| const remoteEntryFile = findRemoteEntryFile(mfOptions.filename, bundle); | ||
|
|
||
| if (!remoteEntryFile) | ||
| throw new Error( | ||
| `Couldn't find a remoteEntry chunk file for ${mfOptions.filename}, can't generate varRemoteEntry file` | ||
| ); | ||
|
|
||
| this.emitFile({ | ||
| type: 'asset', | ||
| fileName: varFilename, | ||
| source: generateVarRemoteEntry(remoteEntryFile), | ||
| }); | ||
| }, | ||
| }, | ||
| ]; | ||
|
|
||
| function isValidVarName(name: string) { | ||
| return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name); | ||
| } | ||
|
|
||
| /** | ||
| * Generates the final "var" remote entry file | ||
| * @param remoteEntryFile - Path to esm remote entry file | ||
| * @returns Complete "var" remoteEntry.js file source | ||
| */ | ||
| function generateVarRemoteEntry(remoteEntryFile: string) { | ||
| const options = getNormalizeModuleFederationOptions(); | ||
|
|
||
| const { name, varFilename } = options; | ||
|
|
||
| const isValidName = isValidVarName(name); | ||
|
|
||
| // @TODO: implement publicPath/getPublicPath support | ||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't handle |
||
| return ` | ||
| ${isValidName ? `var ${name};` : ''} | ||
| ${isValidName ? name : `globalThis['${name}']`} = (function () { | ||
| function getScriptUrl() { | ||
| const currentScript = document.currentScript; | ||
| if (!currentScript) { | ||
| console.error("[VarRemoteEntry] ${varFilename} script should be called from sync <script> tag (document.currentScript is undefined)") | ||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure about this logic. Maybe there is another way to indicate path of original |
||
| return '/'; | ||
| } | ||
| return document.currentScript.src.replace(/\\/[^/]*$/, '/'); | ||
| } | ||
|
|
||
| const entry = getScriptUrl() + '${remoteEntryFile}'; | ||
|
|
||
| return { | ||
| get: (...args) => import(entry).then(m => m.get(...args)), | ||
| init: (...args) => import(entry).then(m => m.init(...args)), | ||
| }; | ||
| })(); | ||
| `; | ||
| } | ||
| }; | ||
|
|
||
| export default VarRemoteEntry; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| import { OutputBundle } from 'rollup'; | ||
|
|
||
| export function findRemoteEntryFile(filename: string, bundle: OutputBundle) { | ||
| for (const [_, fileData] of Object.entries(bundle)) { | ||
| if ( | ||
| filename.replace(/[\[\]]/g, '_').replace(/\.[^/.]+$/, '') === fileData.name || | ||
| fileData.name === 'remoteEntry' | ||
| ) { | ||
| return fileData.fileName; // We can return early since we only need to find remoteEntry once | ||
| } | ||
| } | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ScriptedAlchemy wdyt about additional
varRemoteEntryfield inmanifest.json?Or maybe we should generate the second item in
manifest.jsonarray. Sry, I couldn't find the manifest specification...