Skip to content
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

feat: shareable runtime #3018

Open
wants to merge 51 commits into
base: main
Choose a base branch
from
Open

feat: shareable runtime #3018

wants to merge 51 commits into from

Conversation

ScriptedAlchemy
Copy link
Member

Description

Related Issue

Types of changes

  • Docs change / refactoring / dependency upgrade
  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)

Checklist

  • I have added tests to cover my changes.
  • All new and existing tests passed.
  • I have updated the documentation.

ScriptedAlchemy and others added 26 commits September 9, 2024 15:22
…lugin.ts

Co-authored-by: squadronai[bot] <170149692+squadronai[bot]@users.noreply.github.com>
…/federation-hooks

# Conflicts:
#	.github/workflows/devtools.yml
Copy link

changeset-bot bot commented Sep 30, 2024

🦋 Changeset detected

Latest commit: 37ed459

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 39 packages
Name Type
@module-federation/enhanced Minor
@module-federation/runtime Minor
@module-federation/nextjs-mf Minor
@module-federation/modern-js Minor
@module-federation/node Patch
@module-federation/rsbuild-plugin Major
3008-runtime-remote Patch
host Patch
host-v5 Patch
host-vue3 Patch
@module-federation/modernjs Patch
@module-federation/devtools Minor
@module-federation/data-prefetch Minor
@module-federation/dts-plugin Minor
@module-federation/retry-plugin Minor
@module-federation/runtime-tools Minor
@module-federation/webpack-bundler-runtime Minor
modernjs-ssr-dynamic-nested-remote Patch
modernjs-ssr-dynamic-remote-new-version Patch
modernjs-ssr-dynamic-remote Patch
modernjs-ssr-host Patch
modernjs-ssr-nested-remote Patch
modernjs-ssr-remote-new-version Patch
modernjs-ssr-remote Patch
remote1 Patch
remote2 Patch
remote3 Patch
remote4 Patch
@module-federation/rspack Minor
@module-federation/sdk Minor
@module-federation/managers Minor
@module-federation/manifest Minor
@module-federation/third-party-dts-extractor Minor
@module-federation/bridge-react Minor
@module-federation/bridge-vue3 Minor
@module-federation/bridge-shared Minor
@module-federation/bridge-react-webpack-plugin Minor
@module-federation/esbuild Patch
@module-federation/utilities Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Copy link

netlify bot commented Sep 30, 2024

Deploy Preview for module-federation-docs ready!

Name Link
🔨 Latest commit 37ed459
🔍 Latest deploy log https://app.netlify.com/sites/module-federation-docs/deploys/670ec908762ee20008ac59a3
😎 Deploy Preview https://deploy-preview-3018--module-federation-docs.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site configuration.

@module-federation module-federation deleted a comment from squadronai bot Oct 8, 2024
@module-federation module-federation deleted a comment from squadronai bot Oct 8, 2024
@module-federation module-federation deleted a comment from squadronai bot Oct 8, 2024
@module-federation module-federation deleted a comment from squadronai bot Oct 8, 2024
@module-federation module-federation deleted a comment from squadronai bot Oct 8, 2024
@module-federation module-federation deleted a comment from squadronai bot Oct 8, 2024
@module-federation module-federation deleted a comment from squadronai bot Oct 8, 2024
@module-federation module-federation deleted a comment from squadronai bot Oct 8, 2024
@module-federation module-federation deleted a comment from squadronai bot Oct 8, 2024
@module-federation module-federation deleted a comment from squadronai bot Oct 8, 2024
@module-federation module-federation deleted a comment from squadronai bot Oct 8, 2024
@module-federation module-federation deleted a comment from squadronai bot Oct 8, 2024
@module-federation module-federation deleted a comment from squadronai bot Oct 8, 2024
@module-federation module-federation deleted a comment from squadronai bot Oct 10, 2024
@module-federation module-federation deleted a comment from squadronai bot Oct 10, 2024
@module-federation module-federation deleted a comment from squadronai bot Oct 10, 2024
@module-federation module-federation deleted a comment from squadronai bot Oct 10, 2024
@module-federation module-federation deleted a comment from squadronai bot Oct 10, 2024
@module-federation module-federation deleted a comment from squadronai bot Oct 10, 2024
@module-federation module-federation deleted a comment from squadronai bot Oct 10, 2024
@RussellCanfield
Copy link
Collaborator

@squadronai review

Copy link
Contributor

@squadronai squadronai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Incremental Review

Comments posted: 16

Configuration

Squadron Mode: essential

Commits Reviewed

5abd1ba0b482f118bfef681c1d3bb102f7c9e70f...37ed459ded9f14e5b500bd271f27cfd96ce82211

Files Reviewed
  • packages/nextjs-mf/src/plugins/NextFederationPlugin/index.ts
  • packages/runtime/src/global.ts
  • packages/runtime/src/index.ts
  • packages/enhanced/src/lib/container/HoistContainerReferencesPlugin.ts
Files Ignored

These files were ignored due to the filter in the squadron.yaml file.

  • .changeset/ai-eager-cat.md
  • .changeset/ai-eager-tiger.md
  • .changeset/ai-gentle-eagle.md
  • .changeset/ai-noisy-fox.md
  • .changeset/ai-sleepy-bear.md
  • .changeset/ai-sleepy-fox.md
  • .changeset/real-baboons-complain.md
  • apps/3000-home/next-env.d.ts
  • apps/3001-shop/next-env.d.ts
  • apps/3002-checkout/next-env.d.ts
  • packages/runtime/tests/snapshots/preload-remote.spec.ts.snap
  • packages/runtime/tests/globa.spec.ts
  • packages/runtime/tests/global.spec.ts
  • packages/runtime/tests/sync.spec.ts
  • packages/webpack-bundler-runtime/package.json
  • packages/webpack-bundler-runtime/project.json
  • packages/webpack-bundler-runtime/src/embedded.ts

Comment on lines +216 to +217
federationRuntime:
this._options.experiments?.federationRuntime || 'hoisted',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The default value for federationRuntime is set to 'hoisted'. Consider making this configurable or documenting the reasoning behind this default choice. If 'hoisted' is the recommended setting, it might be helpful to add a comment explaining why.

Suggested change
federationRuntime:
this._options.experiments?.federationRuntime || 'hoisted',
experiments: {
federationRuntime:
this._options.experiments?.federationRuntime || 'hoisted', // Default to 'hoisted' for better performance and compatibility
},

@@ -213,7 +213,8 @@
dts: this._options.dts ?? false,
shareStrategy: this._options.shareStrategy ?? 'loaded-first',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The shareStrategy is set to 'loaded-first' by default. It would be beneficial to add a comment explaining the implications of this strategy and why it's chosen as the default.

Suggested change
shareStrategy: this._options.shareStrategy ?? 'loaded-first',
shareStrategy: this._options.shareStrategy ?? 'loaded-first', // Use 'loaded-first' strategy for efficient module sharing

Comment on lines 213 to 219
dts: this._options.dts ?? false,
shareStrategy: this._options.shareStrategy ?? 'loaded-first',
experiments: {
federationRuntime: 'hoisted',
federationRuntime:
this._options.experiments?.federationRuntime || 'hoisted',
},
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider grouping related options together and adding type annotations for better code readability and maintainability. This can help developers understand the structure of the options object more easily.

Suggested change
dts: this._options.dts ?? false,
shareStrategy: this._options.shareStrategy ?? 'loaded-first',
experiments: {
federationRuntime: 'hoisted',
federationRuntime:
this._options.experiments?.federationRuntime || 'hoisted',
},
};
const pluginOptions: NextFederationPluginOptions = {
dts: this._options.dts ?? false,
shareStrategy: this._options.shareStrategy ?? 'loaded-first',
experiments: {
federationRuntime: this._options.experiments?.federationRuntime || 'hoisted',
},
};

Comment on lines +333 to +340
export function setGlobalShareableRuntime(
runtimeExports: ShareableRuntime,
): void {
if (nativeGlobal.__FEDERATION__.__SHAREABLE_RUNTIME__) {
return;
}
nativeGlobal.__FEDERATION__.__SHAREABLE_RUNTIME__ = runtimeExports;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The setGlobalShareableRuntime function could benefit from additional error handling and logging. Consider adding a warning if the runtime is already set, and potentially a type check for the runtimeExports parameter. Here's a suggested implementation:

Suggested change
export function setGlobalShareableRuntime(
runtimeExports: ShareableRuntime,
): void {
if (nativeGlobal.__FEDERATION__.__SHAREABLE_RUNTIME__) {
return;
}
nativeGlobal.__FEDERATION__.__SHAREABLE_RUNTIME__ = runtimeExports;
}
export function setGlobalShareableRuntime(
runtimeExports: ShareableRuntime,
): void {
if (nativeGlobal.__FEDERATION__.__SHAREABLE_RUNTIME__) {
warn('Shareable runtime is already set. Ignoring new runtime.');
return;
}
if (typeof runtimeExports !== 'object' || runtimeExports === null) {
throw new Error('Invalid shareable runtime provided.');
}
nativeGlobal.__FEDERATION__.__SHAREABLE_RUNTIME__ = runtimeExports;
}

This change improves error handling, provides more informative warnings, and ensures type safety.

Comment on lines +19 to +28
type ShareableRuntime = {
FederationManager: typeof import('./index').FederationManager;
FederationHost: typeof import('./index').FederationHost;
loadScript: typeof import('./index').loadScript;
loadScriptNode: typeof import('./index').loadScriptNode;
registerGlobalPlugins: typeof import('./index').registerGlobalPlugins;
getRemoteInfo: typeof import('./index').getRemoteInfo;
getRemoteEntry: typeof import('./index').getRemoteEntry;
Module: typeof import('./index').Module;
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ShareableRuntime type is currently using typeof import('./index') for each property. This approach might lead to circular dependencies and make the code harder to maintain. Consider defining these types explicitly or importing them from a separate types file. For example:

Suggested change
type ShareableRuntime = {
FederationManager: typeof import('./index').FederationManager;
FederationHost: typeof import('./index').FederationHost;
loadScript: typeof import('./index').loadScript;
loadScriptNode: typeof import('./index').loadScriptNode;
registerGlobalPlugins: typeof import('./index').registerGlobalPlugins;
getRemoteInfo: typeof import('./index').getRemoteInfo;
getRemoteEntry: typeof import('./index').getRemoteEntry;
Module: typeof import('./index').Module;
};
import { FederationManager, FederationHost, loadScript, loadScriptNode, registerGlobalPlugins, getRemoteInfo, getRemoteEntry, Module } from './types';
type ShareableRuntime = {
FederationManager: FederationManager;
FederationHost: FederationHost;
loadScript: loadScript;
loadScriptNode: loadScriptNode;
registerGlobalPlugins: registerGlobalPlugins;
getRemoteInfo: getRemoteInfo;
getRemoteEntry: getRemoteEntry;
Module: Module;
};

This change would improve code maintainability and reduce the risk of circular dependencies.

Comment on lines 49 to 55
},
(chunks: Iterable<Chunk>) => {
const runtimeChunks = this.getRuntimeChunks(compilation);
this.hoistModulesInChunks(
compilation,
runtimeChunks,
chunks,
logger,
containerEntryDependencies,
);
this.hoistModulesInChunks(compilation, containerEntryDependencies);
},
);
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The hoistModulesInChunks method is called inside the optimizeChunks hook, which runs after the SplitChunksPlugin. Consider adding a comment explaining why this specific stage is chosen and how it interacts with other optimization plugins. This would improve the code's maintainability and help future developers understand the plugin's behavior in the context of Webpack's compilation process.

Comment on lines 60 to 77
private hoistModulesInChunks(
compilation: Compilation,
runtimeChunks: Set<Chunk>,
chunks: Iterable<Chunk>,
logger: ReturnType<Compilation['getLogger']>,
containerEntryDependencies: Set<Dependency>,
): void {
const { chunkGraph, moduleGraph } = compilation;
// when runtimeChunk: single is set - ContainerPlugin will create a "partial" chunk we can use to
// move modules into the runtime chunk

// First, handle the minimal check and remove included modules from the chunk
this.handleMinimalCheck(
compilation,
containerEntryDependencies,
chunkGraph,
moduleGraph,
);

// Now, perform the global hoist over all chunks
for (const dep of containerEntryDependencies) {
const containerEntryModule = moduleGraph.getModule(dep);
if (!containerEntryModule) continue;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The hoistModulesInChunks method seems to be doing multiple operations. Consider splitting it into smaller, more focused methods to improve readability and maintainability. For example, you could create separate methods for handling the minimal check and performing the global hoist. This would make the code easier to understand and test.

Comment on lines 79 to 92
compilation,
containerEntryModule,
'initial',
true,
);

const allRemoteReferences = getAllReferencedModules(
compilation,
containerEntryModule,
'external',
true,
);

for (const remote of allRemoteReferences) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The getAllReferencedModules function is called twice with different parameters. Consider combining these calls into a single operation to reduce redundancy and improve performance. You could create a helper method that handles both 'initial' and 'external' types in one pass.

Comment on lines +125 to +177
private handleMinimalCheck(
compilation: Compilation,
containerEntryDependencies: Set<Dependency>,
chunkGraph: Compilation['chunkGraph'],
moduleGraph: Compilation['moduleGraph'],
): void {
let minimal;
for (const dep of containerEntryDependencies as Set<FederationRuntimeDependency>) {
if (dep.minimal) {
minimal = moduleGraph.getModule(dep);
}
}
if (minimal) {
for (const dep of containerEntryDependencies as Set<FederationRuntimeDependency>) {
if (dep.minimal) continue;
const containerEntryModule = moduleGraph.getModule(dep);
if (!containerEntryModule) continue;
const allReferencedModules = getAllReferencedModules(
compilation,
containerEntryModule,
'initial',
);

const containerRuntimes =
chunkGraph.getModuleRuntimes(containerEntryModule);
const runtimes = new Set<string>();

for (const runtimeSpec of containerRuntimes) {
compilation.compiler.webpack.util.runtime.forEachRuntime(
runtimeSpec,
(runtimeKey) => {
if (runtimeKey) {
runtimes.add(runtimeKey);
}
},
);
}

for (const runtime of runtimes) {
const runtimeChunk = compilation.namedChunks.get(runtime);
if (!runtimeChunk) continue;
// if there is no minimal chunk in the runtime module, skip it.
if (!chunkGraph.isModuleInChunk(minimal, runtimeChunk)) continue;

for (const module of allReferencedModules) {
if (chunkGraph.isModuleInChunk(module, runtimeChunk)) {
chunkGraph.disconnectChunkAndModule(runtimeChunk, module);
}
}
}
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The handleMinimalCheck method is quite long and complex. Consider breaking it down into smaller, more focused methods. This would improve readability and make the code easier to maintain. Additionally, consider adding comments to explain the purpose of each major step in the process, especially for complex operations like the runtime handling.

Comment on lines 219 to 228
compilation: Compilation,
module: Module,
type?: 'all' | 'initial' | 'external',
withInitialModule?: boolean,
): Set<Module> {
const collectedModules = new Set<Module>([module]);
const visitedModules = new WeakSet<Module>([module]);
const collectedModules = new Set<Module>(withInitialModule ? [module] : []);
const visitedModules = new WeakSet<Module>(withInitialModule ? [module] : []);
const stack = [module];

while (stack.length > 0) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The getAllReferencedModules function uses a recursive approach with a stack. While this works, for very large module graphs, it could potentially lead to stack overflow issues. Consider implementing an iterative approach instead, which would be more memory-efficient for large projects. Also, add a comment explaining the algorithm used for future maintainers.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants