Repository navigation
Conversation
A remote bundle reads each shared module through a facade that throws `Shared module <name> is not registered` when the host has not registered it. The facade runs when it is first required. With eager imports that is while the bundle loads, but a bundler that defers imports until their first use moves the error into whatever screen first touches the module. Add an `intro` to the Rollipop shared-modules plugin that checks every shared module the chunk includes before any module runs, so a missing shared module fails the remote while it loads again.
🦋 Changeset detectedLatest commit: 99b4c05 The changes in this PR will be included in the next version bump. This PR includes changesets to release 31 packages
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 |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
@granite-js/deployment-manager
@granite-js/forge-cli
@granite-js/pulumi-aws
babel-preset-granite
@granite-js/blur-view
@granite-js/brownfield-module
@granite-js/cli
@granite-js/config
@granite-js/cookies
create-granite-app
@granite-js/screen
@granite-js/image
@granite-js/jest
@granite-js/lottie
@granite-js/micro-frontend
@granite-js/mpack
@granite-js/native
@granite-js/naver-map
@granite-js/plugin-core
@granite-js/plugin-env
@granite-js/plugin-hermes
@granite-js/plugin-micro-frontend
@granite-js/plugin-router
@granite-js/plugin-rozenite
@granite-js/plugin-sentry
@granite-js/react-native
@granite-js/rollipop
@granite-js/style-utils
@granite-js/utils
@granite-js/video
@granite-js/vitest
commit: |
A host prelude imported every shared module with `import * as` and passed the namespace to registerShared, so each shared package ran while the host bundle started. With `lazy: true` the prelude registers a loader instead and the module runs on the first get(). Consumers only call get(), so remotes keep working unchanged.
|
Added lazy host shared modules to this PR (721f6aa, docs in 26736d3 / aae4bf1). Why What
Module Federation mapping Tests |
…n Rollipop A lazy shared module was loaded with require(name). Rolldown keeps the barrel and re-export modules of a required ESM package because its namespace object has to exist at runtime, so a lazy host bundle grew. The Rollipop prelude now requires a virtual module that imports the package with `import * as` and re-exports the namespace, so the package is imported the same way as an eager registration. Mpack keeps require(name).
…odules in Rollipop" This reverts commit 9c08e0d.
|
On the question about It does happen, at a smaller scale. In our host bundle (about 20 shared packages), the The ESM-first resolution approach does not address it. These packages already resolve to their ESM builds. The cost comes from the Why What would remove the cost. Register lazy modules through named imports and getters instead of import { A, B } from 'pkg';
registerLazyShared('pkg', () => Object.defineProperties({}, { A: { enumerable: true, get: () => A }, B: { enumerable: true, get: () => B } }));In a small Rolldown fixture this keeps the same modules as eager registration, evaluates nothing at startup, and evaluates only the modules behind the exports that are actually read. Reads through named imports are statically provable, so Rolldown's lazy imports can defer each re-export on its own. The catch is that the plugin has to know each package's export names at build time. |
…imports in Rollipop A lazy shared module was loaded with require(name). Rolldown keeps the barrel and re-export modules of a required ESM package, so a lazy host bundle grew. The Rollipop prelude now finds the package's export names at build time (following export *) and registers a facade whose getters read named imports. Rolldown can prove each read, so the package is tree-shaken like an eager registration and its modules are evaluated when an export is first read. The facade has no __esModule, so registration normalizes it like an import * namespace. CommonJS packages keep require(name) with a warning.
…red facades Find a lazy shared module's namespace names with the GetExportedNames and ResolveExport steps: export * never re-exports default, a name two export * modules provide from different bindings is ambiguous and left out, and an explicit export hides the same name. Type-only exports are left out. The facade is shaped like a module namespace object: null prototype, sorted read-only live exports, a Module tag, and not extensible.
…irst get Importing a module evaluates its whole graph, including side-effect-only imports such as import 'pkg' in a remote. The facade read nothing until a consumer read an export, so a remote that only imported a package for its side effects never ran it. The first get() now reads every export once, so the package is evaluated then, as require(name) did, and the named imports keep its tree-shaking.
Summary
A remote bundle reads each shared module through a facade (
\0granite:shared:<name>) that throwsShared module <name> is not registeredwhen the host has not registered that module. The facade runs when it is first required:This PR adds an
introto the Rollipop shared-modules plugin. It lists the shared modules the chunk includes and checks them against__MICRO_FRONTEND__.__SHARED__before any module runs, so a missing shared module fails the remote while it loads again, regardless of how the bundler evaluates imports.Behavior
introbefore its runtime and module factories, so the check runs before any module.Tests
packages/micro-frontend/src/next/index.spec.ts: the intro lists only the shared modules in the chunk, passes when all are registered, throws for a missing one, and is empty when the chunk has no shared modules.yarn testinpackages/micro-frontend(38 files, 252 tests),yarn typecheck, ESLint and Prettier on the changed files pass.