@rollipop/plugin-module-federation 0.0.0 → 0.1.0-alpha.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/README.md +127 -1
- package/dist/index.d.ts +72 -0
- package/dist/index.js +607 -0
- package/dist/runtime.d.ts +17 -0
- package/dist/runtime.js +1 -0
- package/package.json +50 -2
- package/.editorconfig +0 -10
- package/.gitattributes +0 -4
package/CHANGELOG.md
ADDED
package/README.md
CHANGED
|
@@ -1 +1,127 @@
|
|
|
1
|
-
# rollipop-
|
|
1
|
+
# @rollipop/plugin-module-federation
|
|
2
|
+
|
|
3
|
+
Module Federation for Rollipop. Wires `@module-federation/runtime` into the host bundle and emits a self-contained IIFE for each remote, with shared dependencies resolved through a host-owned global registry.
|
|
4
|
+
|
|
5
|
+
## Status
|
|
6
|
+
|
|
7
|
+
> **Initial implementation.** This plugin is an early-stage feature. The public
|
|
8
|
+
> API and the bundle layout may change, and the role model is intentionally
|
|
9
|
+
> narrower than standard Module Federation — see [Roles](#roles) and
|
|
10
|
+
> [Constraints](#constraints).
|
|
11
|
+
>
|
|
12
|
+
> Notably, a single config takes exactly one role (host **or** remote). Standard
|
|
13
|
+
> (web) Module Federation lets one build both `expose` modules and `consume`
|
|
14
|
+
> remotes; that bidirectional / chained-federation model is **not supported
|
|
15
|
+
> yet**. Lifting it requires emitting the federation container as a separate
|
|
16
|
+
> artifact from the app bundle, which is not implemented at this stage.
|
|
17
|
+
|
|
18
|
+
## Roles
|
|
19
|
+
|
|
20
|
+
A single Rollipop config takes one role. Defining both `remotes` and `exposes` throws.
|
|
21
|
+
|
|
22
|
+
| Role | Has | Bundle output |
|
|
23
|
+
| ------ | --------- | ---------------------------------------------------------------------------- |
|
|
24
|
+
| Host | `remotes` | Normal Rollipop bundle. `import('<remote>/<expose>')` is rewritten at build. |
|
|
25
|
+
| Remote | `exposes` | IIFE that registers a federation container on `globalThis`. |
|
|
26
|
+
|
|
27
|
+
## Host
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
// rollipop.host.config.ts
|
|
31
|
+
import { federation } from '@rollipop/plugin-module-federation';
|
|
32
|
+
import { defineConfig } from 'rollipop';
|
|
33
|
+
|
|
34
|
+
export default defineConfig({
|
|
35
|
+
entry: 'src/host/index.js',
|
|
36
|
+
plugins: [
|
|
37
|
+
federation({
|
|
38
|
+
name: 'host_app',
|
|
39
|
+
remotes: {
|
|
40
|
+
remote_app: 'remote_app@http://localhost:8082/index.bundle?platform=ios',
|
|
41
|
+
},
|
|
42
|
+
shared: {
|
|
43
|
+
react: { singleton: true, eager: true, requiredVersion: '19.2.3' },
|
|
44
|
+
'react-native': { singleton: true, eager: true, requiredVersion: '0.84.1' },
|
|
45
|
+
},
|
|
46
|
+
runtime: {
|
|
47
|
+
// Register a `ModuleFederationScriptLoader` on `globalThis.__rollipop_script_loader__`.
|
|
48
|
+
// Typically delegates to a native TurboModule that does fetch + JSI evaluate.
|
|
49
|
+
implement: `
|
|
50
|
+
globalThis.__rollipop_script_loader__ = {
|
|
51
|
+
async loadScript({ scriptId, url }) {
|
|
52
|
+
await NativeScriptManager.loadScript(scriptId, { url });
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
`,
|
|
56
|
+
},
|
|
57
|
+
}),
|
|
58
|
+
],
|
|
59
|
+
});
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
In your app, consume the remote with dynamic import:
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
const RemoteNavigator = React.lazy(() =>
|
|
66
|
+
import('remote_app/RemoteNavigator').then((m) => ({ default: m.default ?? m })),
|
|
67
|
+
);
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
The plugin rewrites the `import()` call into a `loadRemote` invocation against the federation runtime — no native `import()` evaluation happens at runtime.
|
|
71
|
+
|
|
72
|
+
## Remote
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
// rollipop.remote.config.ts
|
|
76
|
+
import { federation } from '@rollipop/plugin-module-federation';
|
|
77
|
+
import { defineConfig } from 'rollipop';
|
|
78
|
+
|
|
79
|
+
export default defineConfig({
|
|
80
|
+
entry: 'src/remote/index.js',
|
|
81
|
+
plugins: [
|
|
82
|
+
federation({
|
|
83
|
+
name: 'remote_app',
|
|
84
|
+
exposes: {
|
|
85
|
+
'./RemoteNavigator': './src/remote/exposed/RemoteNavigator.tsx',
|
|
86
|
+
},
|
|
87
|
+
shared: {
|
|
88
|
+
react: { singleton: true, requiredVersion: '19.2.3' },
|
|
89
|
+
'react-native': { singleton: true, requiredVersion: '0.84.1' },
|
|
90
|
+
},
|
|
91
|
+
}),
|
|
92
|
+
],
|
|
93
|
+
});
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
The bundle is emitted as IIFE. Each shared dep import (e.g. `import RN from 'react-native'`) is replaced with a stub that reads from the host's `globalThis.__rollipop_shared__` registry and throws if the dep is not registered. Rollipop's prelude / polyfills are skipped because the host has already initialized the React Native runtime.
|
|
97
|
+
|
|
98
|
+
## Script loader contract
|
|
99
|
+
|
|
100
|
+
Defined at `@rollipop/plugin-module-federation/runtime`:
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
export interface ModuleFederationScriptLoader {
|
|
104
|
+
loadScript(args: { scriptId: string; url: string; parentUrl?: string }): Promise<void>;
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
The host registers an implementation on `globalThis.__rollipop_script_loader__`. The plugin's MF runtime adapter delegates `loadEntry` to it. After the script is evaluated the container is read back from `globalThis[<entryGlobalName>]`.
|
|
109
|
+
|
|
110
|
+
## Globals exposed at runtime
|
|
111
|
+
|
|
112
|
+
| Global | Owner | Purpose |
|
|
113
|
+
| ---------------------------- | ------ | ----------------------------------------------------------------------- |
|
|
114
|
+
| `__rollipop_script_loader__` | user | `ModuleFederationScriptLoader` implementation. |
|
|
115
|
+
| `__rollipop_shared__` | host | Shared dependency registry. Lazily populated; missing keys throw. |
|
|
116
|
+
| `__rollipop_load_remote__` | plugin | `(id: string) => Promise<unknown>` — wraps the federation `loadRemote`. |
|
|
117
|
+
|
|
118
|
+
## Constraints
|
|
119
|
+
|
|
120
|
+
- A single config cannot define both `remotes` and `exposes` — this is a
|
|
121
|
+
current-implementation limitation, not an inherent Module Federation rule.
|
|
122
|
+
Split a host and a remote into two configs and run them as separate Rollipop
|
|
123
|
+
processes. See [Status](#status).
|
|
124
|
+
- React Native does not support native `import()`. Static imports of remote modules are intentionally not supported — use dynamic `import('<remote>/<expose>')`.
|
|
125
|
+
- Subpath imports of shared deps (`react/jsx-dev-runtime`, etc.) are bundled into the remote and consume the parent shared instance through `__rollipop_shared__`.
|
|
126
|
+
|
|
127
|
+
See `examples/module-federation` for a working RN 0.84 host + remote setup with a Pure C++/Obj-C++ TurboModule script loader.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { Plugin } from "rollipop";
|
|
2
|
+
|
|
3
|
+
//#region src/types.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Module Federation plugin configuration.
|
|
6
|
+
*
|
|
7
|
+
* A single config takes one role at a time:
|
|
8
|
+
*
|
|
9
|
+
* - **Host** — declare `remotes`. The bundle consumes federated modules via `import('<remote>/<expose>')`,
|
|
10
|
+
* which the plugin rewrites to a runtime `loadRemote` call against the host's federation instance.
|
|
11
|
+
* - **Remote** — declare `exposes`. The plugin emits an IIFE bundle whose container is registered on `globalThis`
|
|
12
|
+
* so the host can read it after the script is evaluated by the user-provided script loader.
|
|
13
|
+
*
|
|
14
|
+
* Defining both `remotes` and `exposes` in the same config throws.
|
|
15
|
+
* Split into two configs and run them as separate Rollipop processes.
|
|
16
|
+
*/
|
|
17
|
+
interface ModuleFederationConfig {
|
|
18
|
+
/**
|
|
19
|
+
* Federation name. Must be a non-empty string and stable across builds.
|
|
20
|
+
*/
|
|
21
|
+
name: string;
|
|
22
|
+
/**
|
|
23
|
+
* Remotes consumed by this bundle (host role).
|
|
24
|
+
*
|
|
25
|
+
* The value is either the remote entry URL or an object form. Either way,
|
|
26
|
+
* `entry` is the URL the user-provided script loader fetches at runtime.
|
|
27
|
+
*/
|
|
28
|
+
remotes?: Record<string, string | ModuleFederationRemoteConfig>;
|
|
29
|
+
/**
|
|
30
|
+
* Modules exposed to other federated bundles (remote role).
|
|
31
|
+
*
|
|
32
|
+
* Each key is the public path (must start with `'./'`).
|
|
33
|
+
* The value is the source file to expose, resolved relative to the project root.
|
|
34
|
+
*/
|
|
35
|
+
exposes?: Record<string, string>;
|
|
36
|
+
/**
|
|
37
|
+
* Shared dependencies that the host owns and remotes consume from the shared registry.
|
|
38
|
+
*
|
|
39
|
+
* Use the array form for the simplest case (versions are read from `node_modules`).
|
|
40
|
+
* The object form lets each entry tune `requiredVersion`, `singleton`, and `eager`.
|
|
41
|
+
*/
|
|
42
|
+
shared?: string[] | Record<string, string | ModuleFederationSharedDependencyConfig>;
|
|
43
|
+
/**
|
|
44
|
+
* Share resolution strategy passed through to `@module-federation/runtime`.
|
|
45
|
+
*/
|
|
46
|
+
shareStrategy?: 'version-first' | 'loaded-first';
|
|
47
|
+
/**
|
|
48
|
+
* Runtime configuration for the host bundle.
|
|
49
|
+
*
|
|
50
|
+
* `implement` is raw source that registers `globalThis.__rollipop_script_loader__` with a `ModuleFederationScriptLoader` implementation.
|
|
51
|
+
* Injected as a polyfill so it runs before any module init.
|
|
52
|
+
*/
|
|
53
|
+
runtime?: ModuleFederationRuntimeConfig;
|
|
54
|
+
}
|
|
55
|
+
interface ModuleFederationRemoteConfig {
|
|
56
|
+
name: string;
|
|
57
|
+
entry: string;
|
|
58
|
+
type?: 'var';
|
|
59
|
+
}
|
|
60
|
+
interface ModuleFederationSharedDependencyConfig {
|
|
61
|
+
requiredVersion?: string;
|
|
62
|
+
singleton?: boolean;
|
|
63
|
+
eager?: boolean;
|
|
64
|
+
}
|
|
65
|
+
interface ModuleFederationRuntimeConfig {
|
|
66
|
+
implement: string;
|
|
67
|
+
}
|
|
68
|
+
//#endregion
|
|
69
|
+
//#region src/plugin.d.ts
|
|
70
|
+
declare function moduleFederationPlugin(config: ModuleFederationConfig): Plugin;
|
|
71
|
+
//#endregion
|
|
72
|
+
export { type ModuleFederationConfig, type ModuleFederationRemoteConfig, type ModuleFederationRuntimeConfig, type ModuleFederationSharedDependencyConfig, moduleFederationPlugin as federation };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,607 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import MagicString from "magic-string";
|
|
4
|
+
import { id, include, prefixRegex } from "rollipop/pluginutils";
|
|
5
|
+
import baseDedent from "dedent";
|
|
6
|
+
import fs from "node:fs";
|
|
7
|
+
//#region src/constants.ts
|
|
8
|
+
const PLUGIN_NAME = "rollipop:module-federation";
|
|
9
|
+
const VIRTUAL_PREFIX = "\0rollipop:module-federation:";
|
|
10
|
+
const VIRTUAL_HOST_INIT_ID = `${VIRTUAL_PREFIX}host-init`;
|
|
11
|
+
const VIRTUAL_RUNTIME_ADAPTER_ID = `${VIRTUAL_PREFIX}runtime-adapter`;
|
|
12
|
+
const VIRTUAL_SHARE_SCOPE_ID = `${VIRTUAL_PREFIX}share-scope`;
|
|
13
|
+
`${VIRTUAL_PREFIX}`;
|
|
14
|
+
const VIRTUAL_SHARED_SHIM_PREFIX = `${VIRTUAL_PREFIX}shared:`;
|
|
15
|
+
const VIRTUAL_REMOTE_PROXY_PREFIX = `${VIRTUAL_PREFIX}remote:`;
|
|
16
|
+
const SCRIPT_LOADER_GLOBAL = "__rollipop_script_loader__";
|
|
17
|
+
const SHARED_REGISTRY_GLOBAL = "__rollipop_shared__";
|
|
18
|
+
const REMOTE_CACHE_GLOBAL = "__rollipop_module_federation_cache__";
|
|
19
|
+
const HMR_HOT_PATH = "/hot";
|
|
20
|
+
const HMR_EVENT = "mf:remote-update";
|
|
21
|
+
//#endregion
|
|
22
|
+
//#region src/virtual/_dedent.ts
|
|
23
|
+
const dedent = baseDedent.withOptions({ escapeSpecialCharacters: false });
|
|
24
|
+
const Q = "\\u0027";
|
|
25
|
+
//#endregion
|
|
26
|
+
//#region src/virtual/host-init.ts
|
|
27
|
+
function generateHostInitCode(config) {
|
|
28
|
+
const remotesArray = Object.values(config.remotes).map((remote) => ({
|
|
29
|
+
name: remote.name,
|
|
30
|
+
entry: remote.entry,
|
|
31
|
+
type: remote.type,
|
|
32
|
+
entryGlobalName: remote.entryGlobalName
|
|
33
|
+
}));
|
|
34
|
+
const sharedEntries = Object.entries(config.shared);
|
|
35
|
+
const sharedRegistryPropExprs = sharedEntries.map(([name]) => ` ${JSON.stringify(name)}: require(${JSON.stringify(name)}),`).join("\n");
|
|
36
|
+
const sharedRuntimeMap = sharedEntries.reduce((acc, [name, info]) => ({
|
|
37
|
+
...acc,
|
|
38
|
+
[name]: {
|
|
39
|
+
version: info.version,
|
|
40
|
+
shareConfig: {
|
|
41
|
+
singleton: info.singleton,
|
|
42
|
+
requiredVersion: info.requiredVersion ?? false,
|
|
43
|
+
eager: info.eager
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}), {});
|
|
47
|
+
return dedent`
|
|
48
|
+
import adapter from ${JSON.stringify(VIRTUAL_RUNTIME_ADAPTER_ID)};
|
|
49
|
+
import { createInstance } from '@module-federation/runtime';
|
|
50
|
+
|
|
51
|
+
globalThis.${SHARED_REGISTRY_GLOBAL} = globalThis.${SHARED_REGISTRY_GLOBAL} || {
|
|
52
|
+
${sharedRegistryPropExprs}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const sharedConfig = ${JSON.stringify(sharedRuntimeMap, null, 2)};
|
|
56
|
+
for (const sharedName of Object.keys(sharedConfig)) {
|
|
57
|
+
sharedConfig[sharedName].lib = () => globalThis.${SHARED_REGISTRY_GLOBAL}[sharedName];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const instance = createInstance({
|
|
61
|
+
name: ${JSON.stringify(config.name)},
|
|
62
|
+
remotes: ${JSON.stringify(remotesArray, null, 2)},
|
|
63
|
+
shared: sharedConfig,
|
|
64
|
+
plugins: [adapter],
|
|
65
|
+
shareStrategy: ${JSON.stringify(config.shareStrategy)},
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const remoteList = ${JSON.stringify(remotesArray)};
|
|
69
|
+
|
|
70
|
+
if (globalThis.${REMOTE_CACHE_GLOBAL} == null) {
|
|
71
|
+
const cache = {
|
|
72
|
+
modules: Object.create(null),
|
|
73
|
+
pending: Object.create(null),
|
|
74
|
+
// Per-id invalidation: only ids actually present in the cache when
|
|
75
|
+
// HMR fired need to bypass the federation runtime. Cleared after
|
|
76
|
+
// each successful bypass load — new ids fall back to the normal
|
|
77
|
+
// loadRemote flow with full shared-module negotiation.
|
|
78
|
+
invalidatedIds: new Set(),
|
|
79
|
+
subscribers: new Set(),
|
|
80
|
+
load(id) {
|
|
81
|
+
if (this.modules[id] !== undefined) {
|
|
82
|
+
return Promise.resolve(this.modules[id]);
|
|
83
|
+
}
|
|
84
|
+
if (this.pending[id]) {
|
|
85
|
+
return this.pending[id];
|
|
86
|
+
}
|
|
87
|
+
const isInvalidated = this.invalidatedIds.has(id);
|
|
88
|
+
let fetcher;
|
|
89
|
+
if (isInvalidated) {
|
|
90
|
+
const slash = id.indexOf('/');
|
|
91
|
+
const remoteName = slash === -1 ? id : id.slice(0, slash);
|
|
92
|
+
const exposePath = slash === -1 ? '.' : './' + id.slice(slash + 1);
|
|
93
|
+
fetcher = Promise.resolve().then(() => {
|
|
94
|
+
const container = globalThis[remoteName];
|
|
95
|
+
if (container == null) {
|
|
96
|
+
throw new Error('[rollipop:module-federation] container ${Q}' + remoteName + '${Q} not registered');
|
|
97
|
+
}
|
|
98
|
+
return container.get(exposePath).then((factory) => {
|
|
99
|
+
return typeof factory === 'function' ? factory() : factory;
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
} else {
|
|
103
|
+
fetcher = instance.loadRemote(id);
|
|
104
|
+
}
|
|
105
|
+
this.pending[id] = fetcher.then((mod) => {
|
|
106
|
+
this.modules[id] = mod;
|
|
107
|
+
delete this.pending[id];
|
|
108
|
+
if (isInvalidated) {
|
|
109
|
+
this.invalidatedIds.delete(id);
|
|
110
|
+
}
|
|
111
|
+
return mod;
|
|
112
|
+
});
|
|
113
|
+
return this.pending[id];
|
|
114
|
+
},
|
|
115
|
+
invalidate(remoteName) {
|
|
116
|
+
for (const key of Object.keys(this.modules)) {
|
|
117
|
+
if (key === remoteName || key.startsWith(remoteName + '/')) {
|
|
118
|
+
this.invalidatedIds.add(key);
|
|
119
|
+
delete this.modules[key];
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
for (const cb of this.subscribers) {
|
|
123
|
+
try {
|
|
124
|
+
cb(remoteName);
|
|
125
|
+
} catch (_e) {}
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
globalThis.${REMOTE_CACHE_GLOBAL} = cache;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const __cache = globalThis.${REMOTE_CACHE_GLOBAL};
|
|
133
|
+
|
|
134
|
+
async function applyRemoteUpdate(remote) {
|
|
135
|
+
const generation = (remote.__mfHmrGen = (remote.__mfHmrGen || 0) + 1);
|
|
136
|
+
const cacheBust = (remote.entry.indexOf('?') === -1 ? '?' : '&') + '_mf_hmr=' + generation;
|
|
137
|
+
try {
|
|
138
|
+
await globalThis.${SCRIPT_LOADER_GLOBAL}.loadScript({
|
|
139
|
+
scriptId: remote.name + '@' + generation,
|
|
140
|
+
url: remote.entry + cacheBust,
|
|
141
|
+
});
|
|
142
|
+
} catch (error) {
|
|
143
|
+
console.error(
|
|
144
|
+
'[rollipop:module-federation] failed to reload remote ${Q}' + remote.name + '${Q} during HMR. App is running stale code until next successful update.',
|
|
145
|
+
error,
|
|
146
|
+
);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
__cache.invalidate(remote.name);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function subscribeRemoteHmr(remote) {
|
|
153
|
+
let wsUrl;
|
|
154
|
+
try {
|
|
155
|
+
const url = new URL(remote.entry);
|
|
156
|
+
const wsProtocol = url.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
157
|
+
wsUrl = wsProtocol + '//' + url.host + ${JSON.stringify(HMR_HOT_PATH)};
|
|
158
|
+
} catch (_e) {
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const connect = () => {
|
|
163
|
+
let socket;
|
|
164
|
+
try {
|
|
165
|
+
socket = new WebSocket(wsUrl);
|
|
166
|
+
} catch (_e) {
|
|
167
|
+
setTimeout(connect, 1000);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
socket.onmessage = (event) => {
|
|
171
|
+
let parsed;
|
|
172
|
+
try {
|
|
173
|
+
parsed = JSON.parse(typeof event.data === 'string' ? event.data : '');
|
|
174
|
+
} catch (_e) {
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
if (parsed == null || parsed.type !== ${JSON.stringify(HMR_EVENT)}) {
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
if (parsed.payload != null && parsed.payload.name !== remote.name) {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
applyRemoteUpdate(remote);
|
|
184
|
+
};
|
|
185
|
+
socket.onclose = () => setTimeout(connect, 1000);
|
|
186
|
+
socket.onerror = () => {
|
|
187
|
+
try {
|
|
188
|
+
socket.close();
|
|
189
|
+
} catch (_e) {}
|
|
190
|
+
};
|
|
191
|
+
};
|
|
192
|
+
connect();
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Defer to the next tick so React Native's InitializeCore has set up
|
|
196
|
+
// WebSocket / setTimeout before the subscription starts.
|
|
197
|
+
if (typeof __DEV__ !== 'undefined' && __DEV__) {
|
|
198
|
+
Promise.resolve().then(() => {
|
|
199
|
+
if (typeof WebSocket === 'undefined') {
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
for (const r of remoteList) {
|
|
203
|
+
subscribeRemoteHmr(r);
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
`;
|
|
208
|
+
}
|
|
209
|
+
//#endregion
|
|
210
|
+
//#region src/virtual/runtime-adapter.ts
|
|
211
|
+
function generateRuntimeAdapterCode() {
|
|
212
|
+
return dedent`
|
|
213
|
+
const adapter = {
|
|
214
|
+
name: 'rollipop-script-loader-adapter',
|
|
215
|
+
async loadEntry({ remoteInfo }) {
|
|
216
|
+
const loader = globalThis.${SCRIPT_LOADER_GLOBAL};
|
|
217
|
+
if (loader == null) {
|
|
218
|
+
throw new Error(
|
|
219
|
+
'[${PLUGIN_NAME}] ${Q}globalThis.${SCRIPT_LOADER_GLOBAL}${Q} is not registered. Provide ${Q}runtime.implement${Q} in plugin config.'
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
await loader.loadScript({
|
|
223
|
+
scriptId: remoteInfo.name,
|
|
224
|
+
url: remoteInfo.entry,
|
|
225
|
+
});
|
|
226
|
+
const container = globalThis[remoteInfo.entryGlobalName];
|
|
227
|
+
if (container == null) {
|
|
228
|
+
throw new Error(
|
|
229
|
+
'[${PLUGIN_NAME}] Remote container ${Q}' + remoteInfo.entryGlobalName + '${Q} was not registered after script load.'
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
return container;
|
|
233
|
+
},
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
export default adapter;
|
|
237
|
+
`;
|
|
238
|
+
}
|
|
239
|
+
//#endregion
|
|
240
|
+
//#region src/virtual/share-scope.ts
|
|
241
|
+
function generateShareScopeCode() {
|
|
242
|
+
return dedent`
|
|
243
|
+
export { loadRemote, loadShare, loadShareSync } from '@module-federation/runtime';
|
|
244
|
+
`;
|
|
245
|
+
}
|
|
246
|
+
//#endregion
|
|
247
|
+
//#region src/host/load.ts
|
|
248
|
+
function loadVirtualModule(id, config) {
|
|
249
|
+
switch (id) {
|
|
250
|
+
case VIRTUAL_RUNTIME_ADAPTER_ID: return generateRuntimeAdapterCode();
|
|
251
|
+
case VIRTUAL_HOST_INIT_ID: return generateHostInitCode(config);
|
|
252
|
+
case VIRTUAL_SHARE_SCOPE_ID: return generateShareScopeCode();
|
|
253
|
+
default: return null;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
//#endregion
|
|
257
|
+
//#region src/host/resolve.ts
|
|
258
|
+
function resolveVirtualId(source) {
|
|
259
|
+
if (source.startsWith("\0rollipop:module-federation:")) return source;
|
|
260
|
+
return null;
|
|
261
|
+
}
|
|
262
|
+
//#endregion
|
|
263
|
+
//#region src/host/transform.ts
|
|
264
|
+
function transformHostEntry(code, id) {
|
|
265
|
+
const magicString = new MagicString(code);
|
|
266
|
+
magicString.prepend(`import ${JSON.stringify(VIRTUAL_HOST_INIT_ID)};\n`);
|
|
267
|
+
return {
|
|
268
|
+
code: magicString.toString(),
|
|
269
|
+
map: magicString.generateMap({
|
|
270
|
+
hires: true,
|
|
271
|
+
source: id
|
|
272
|
+
})
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
//#endregion
|
|
276
|
+
//#region src/shared/resolve-version.ts
|
|
277
|
+
function resolveSharedVersion(packageName, projectRoot) {
|
|
278
|
+
try {
|
|
279
|
+
const packageJsonPath = createRequire(`${projectRoot}/__placeholder__.js`).resolve(`${packageName}/package.json`);
|
|
280
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
|
|
281
|
+
return typeof packageJson.version === "string" ? packageJson.version : void 0;
|
|
282
|
+
} catch {
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
//#endregion
|
|
287
|
+
//#region src/normalize.ts
|
|
288
|
+
const DEFAULT_SHARE_STRATEGY = "version-first";
|
|
289
|
+
function normalizeConfig(config, projectRoot) {
|
|
290
|
+
validateConfig(config);
|
|
291
|
+
const remotes = normalizeRemotes(config.remotes ?? {});
|
|
292
|
+
const exposes = config.exposes ?? {};
|
|
293
|
+
const shared = normalizeShared(config.shared ?? [], projectRoot);
|
|
294
|
+
return {
|
|
295
|
+
name: config.name,
|
|
296
|
+
remotes,
|
|
297
|
+
exposes,
|
|
298
|
+
shared,
|
|
299
|
+
shareStrategy: config.shareStrategy ?? DEFAULT_SHARE_STRATEGY,
|
|
300
|
+
runtime: config.runtime,
|
|
301
|
+
hasRemotes: Object.keys(remotes).length > 0,
|
|
302
|
+
hasExposes: Object.keys(exposes).length > 0
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
function normalizeRemotes(remotes) {
|
|
306
|
+
return Object.entries(remotes).reduce((acc, [key, value]) => {
|
|
307
|
+
const remote = typeof value === "string" ? parseRemoteString(key, value) : value;
|
|
308
|
+
return {
|
|
309
|
+
...acc,
|
|
310
|
+
[key]: {
|
|
311
|
+
name: remote.name,
|
|
312
|
+
entry: remote.entry,
|
|
313
|
+
type: remote.type ?? "var",
|
|
314
|
+
entryGlobalName: remote.name
|
|
315
|
+
}
|
|
316
|
+
};
|
|
317
|
+
}, {});
|
|
318
|
+
}
|
|
319
|
+
function parseRemoteString(key, value) {
|
|
320
|
+
const at = value.indexOf("@");
|
|
321
|
+
if (at <= 0) return {
|
|
322
|
+
name: key,
|
|
323
|
+
entry: value
|
|
324
|
+
};
|
|
325
|
+
return {
|
|
326
|
+
name: value.slice(0, at),
|
|
327
|
+
entry: value.slice(at + 1)
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
function normalizeShared(shared, projectRoot) {
|
|
331
|
+
return (Array.isArray(shared) ? shared.map((name) => [name, {}]) : Object.entries(shared).map(([name, value]) => typeof value === "string" ? [name, { requiredVersion: value }] : [name, value])).reduce((acc, [name, opts]) => ({
|
|
332
|
+
...acc,
|
|
333
|
+
[name]: {
|
|
334
|
+
version: resolveSharedVersion(name, projectRoot),
|
|
335
|
+
requiredVersion: opts.requiredVersion,
|
|
336
|
+
singleton: opts.singleton ?? false,
|
|
337
|
+
eager: opts.eager ?? false
|
|
338
|
+
}
|
|
339
|
+
}), {});
|
|
340
|
+
}
|
|
341
|
+
function validateConfig(config) {
|
|
342
|
+
if (!config.name || typeof config.name !== "string") throw new Error(`[${PLUGIN_NAME}] 'name' is required and must be a non-empty string`);
|
|
343
|
+
if (config.remotes != null) for (const [key, value] of Object.entries(config.remotes)) {
|
|
344
|
+
if (typeof value === "string") continue;
|
|
345
|
+
if (!value.name || !value.entry) throw new Error(`[${PLUGIN_NAME}] Remote '${key}' must have both 'name' and 'entry'`);
|
|
346
|
+
}
|
|
347
|
+
if (config.exposes != null) for (const [key, value] of Object.entries(config.exposes)) {
|
|
348
|
+
if (!key.startsWith("./")) throw new Error(`[${PLUGIN_NAME}] Expose key '${key}' must start with './'`);
|
|
349
|
+
if (typeof value !== "string") throw new Error(`[${PLUGIN_NAME}] Expose value for '${key}' must be a string file path`);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
//#endregion
|
|
353
|
+
//#region src/virtual/remote-entry.ts
|
|
354
|
+
function generateRemoteEntryCode(options) {
|
|
355
|
+
const exposeEntries = Object.entries(options.exposes);
|
|
356
|
+
return dedent`
|
|
357
|
+
${exposeEntries.map(([, filePath], index) => `import * as __expose_${index} from ${JSON.stringify(filePath)};`).join("\n")}
|
|
358
|
+
|
|
359
|
+
const moduleMap = {
|
|
360
|
+
${exposeEntries.map(([key], index) => ` ${JSON.stringify(key)}: () => __expose_${index},`).join("\n")}
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
const container = {
|
|
364
|
+
init() {},
|
|
365
|
+
async get(path) {
|
|
366
|
+
const factory = moduleMap[path];
|
|
367
|
+
if (factory == null) {
|
|
368
|
+
throw new Error('[${PLUGIN_NAME}] Module ${Q}' + path + '${Q} is not exposed by ${Q}${options.name}${Q}');
|
|
369
|
+
}
|
|
370
|
+
return factory;
|
|
371
|
+
},
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
globalThis[${JSON.stringify(options.name)}] = container;
|
|
375
|
+
globalThis.${SHARED_REGISTRY_GLOBAL} = globalThis.${SHARED_REGISTRY_GLOBAL} || {};
|
|
376
|
+
`;
|
|
377
|
+
}
|
|
378
|
+
//#endregion
|
|
379
|
+
//#region src/virtual/remote-proxy.ts
|
|
380
|
+
function generateRemoteProxyCode({ remoteId, reactAware }) {
|
|
381
|
+
const idLiteral = JSON.stringify(remoteId);
|
|
382
|
+
if (reactAware) return dedent`
|
|
383
|
+
import * as __mfReact from 'react';
|
|
384
|
+
|
|
385
|
+
const __cache = globalThis.${REMOTE_CACHE_GLOBAL};
|
|
386
|
+
const __id = ${idLiteral};
|
|
387
|
+
|
|
388
|
+
function __ensureLoaded() {
|
|
389
|
+
if (__cache.modules[__id] !== undefined) {
|
|
390
|
+
return null;
|
|
391
|
+
}
|
|
392
|
+
return __cache.load(__id);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function __getMod() {
|
|
396
|
+
return __cache.modules[__id];
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function __FederatedProxy(props) {
|
|
400
|
+
const [, setVersion] = __mfReact.useState(0);
|
|
401
|
+
__mfReact.useEffect(() => {
|
|
402
|
+
const listener = () => setVersion((v) => v + 1);
|
|
403
|
+
__cache.subscribers.add(listener);
|
|
404
|
+
return () => {
|
|
405
|
+
__cache.subscribers.delete(listener);
|
|
406
|
+
};
|
|
407
|
+
}, []);
|
|
408
|
+
|
|
409
|
+
const mod = __getMod();
|
|
410
|
+
if (mod === undefined) {
|
|
411
|
+
throw __ensureLoaded();
|
|
412
|
+
}
|
|
413
|
+
const fn = mod.default ?? mod;
|
|
414
|
+
return __mfReact.createElement(fn, props);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
const __proxy = new Proxy(__FederatedProxy, {
|
|
418
|
+
get(target, prop) {
|
|
419
|
+
if (prop === '__esModule') {
|
|
420
|
+
return true;
|
|
421
|
+
}
|
|
422
|
+
if (prop === 'then') {
|
|
423
|
+
return undefined;
|
|
424
|
+
}
|
|
425
|
+
const mod = __getMod();
|
|
426
|
+
if (mod === undefined) {
|
|
427
|
+
throw __ensureLoaded();
|
|
428
|
+
}
|
|
429
|
+
if (prop in mod) {
|
|
430
|
+
return mod[prop];
|
|
431
|
+
}
|
|
432
|
+
if (mod.default != null && prop in mod.default) {
|
|
433
|
+
return mod.default[prop];
|
|
434
|
+
}
|
|
435
|
+
return target[prop];
|
|
436
|
+
},
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
export default __proxy;
|
|
440
|
+
`;
|
|
441
|
+
return dedent`
|
|
442
|
+
const __cache = globalThis.${REMOTE_CACHE_GLOBAL};
|
|
443
|
+
const __id = ${idLiteral};
|
|
444
|
+
|
|
445
|
+
function __getMod() {
|
|
446
|
+
if (__cache.modules[__id] !== undefined) {
|
|
447
|
+
return __cache.modules[__id];
|
|
448
|
+
}
|
|
449
|
+
throw __cache.load(__id);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
function __invoke(...args) {
|
|
453
|
+
const mod = __getMod();
|
|
454
|
+
const fn = mod.default ?? mod;
|
|
455
|
+
return fn.apply(this, args);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
const __proxy = new Proxy(__invoke, {
|
|
459
|
+
get(target, prop) {
|
|
460
|
+
if (prop === '__esModule') {
|
|
461
|
+
return true;
|
|
462
|
+
}
|
|
463
|
+
if (prop === 'then') {
|
|
464
|
+
return undefined;
|
|
465
|
+
}
|
|
466
|
+
const mod = __getMod();
|
|
467
|
+
if (prop in mod) {
|
|
468
|
+
return mod[prop];
|
|
469
|
+
}
|
|
470
|
+
if (mod.default != null && prop in mod.default) {
|
|
471
|
+
return mod.default[prop];
|
|
472
|
+
}
|
|
473
|
+
return target[prop];
|
|
474
|
+
},
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
export default __proxy;
|
|
478
|
+
`;
|
|
479
|
+
}
|
|
480
|
+
//#endregion
|
|
481
|
+
//#region src/virtual/shared-shim.ts
|
|
482
|
+
function generateSharedShimCode(sharedName) {
|
|
483
|
+
return dedent`
|
|
484
|
+
const __mod = globalThis.${SHARED_REGISTRY_GLOBAL} && globalThis.${SHARED_REGISTRY_GLOBAL}[${JSON.stringify(sharedName)}];
|
|
485
|
+
if (__mod == null) {
|
|
486
|
+
throw new Error('[${PLUGIN_NAME}] shared module ${Q}${sharedName}${Q} is not registered on the host. Add it to the host config${Q}s ${Q}shared${Q} field.');
|
|
487
|
+
}
|
|
488
|
+
module.exports = __mod;
|
|
489
|
+
`;
|
|
490
|
+
}
|
|
491
|
+
//#endregion
|
|
492
|
+
//#region src/plugin.ts
|
|
493
|
+
function moduleFederationPlugin(config) {
|
|
494
|
+
const federationConfig = config;
|
|
495
|
+
const hasRemotes = Object.keys(federationConfig.remotes ?? {}).length > 0;
|
|
496
|
+
const hasExposes = Object.keys(federationConfig.exposes ?? {}).length > 0;
|
|
497
|
+
if (hasRemotes && hasExposes) throw new Error(`[${PLUGIN_NAME}] A single config cannot define both 'remotes' and 'exposes'. Split into two configs (one host, one remote) and run them as separate Rollipop processes.`);
|
|
498
|
+
let resolvedConfig = null;
|
|
499
|
+
let normalized = null;
|
|
500
|
+
let broadcast = null;
|
|
501
|
+
const exposesAbsolute = {};
|
|
502
|
+
const sharedRoots = new Set(collectSharedNames(federationConfig));
|
|
503
|
+
let debounceTimer = null;
|
|
504
|
+
return {
|
|
505
|
+
name: PLUGIN_NAME,
|
|
506
|
+
config(pluginConfig) {
|
|
507
|
+
const serializer = pluginConfig.serializer ??= {};
|
|
508
|
+
if (hasExposes) {
|
|
509
|
+
serializer.prelude = [];
|
|
510
|
+
serializer.polyfills = [];
|
|
511
|
+
return { dangerously_overrideRolldownOptions: (opts) => ({
|
|
512
|
+
input: opts.input,
|
|
513
|
+
output: {
|
|
514
|
+
...opts.output,
|
|
515
|
+
format: "iife"
|
|
516
|
+
}
|
|
517
|
+
}) };
|
|
518
|
+
}
|
|
519
|
+
if (hasRemotes && config.runtime?.implement != null) (serializer.polyfills ??= []).push({
|
|
520
|
+
type: "iife",
|
|
521
|
+
code: config.runtime.implement
|
|
522
|
+
});
|
|
523
|
+
},
|
|
524
|
+
configResolved(config) {
|
|
525
|
+
resolvedConfig = config;
|
|
526
|
+
normalized = normalizeConfig(federationConfig, config.root);
|
|
527
|
+
if (hasExposes) for (const [key, filePath] of Object.entries(normalized.exposes)) exposesAbsolute[key] = path.resolve(config.root, filePath);
|
|
528
|
+
},
|
|
529
|
+
resolveId: { handler(source) {
|
|
530
|
+
if (hasExposes && sharedRoots.has(source)) return { id: VIRTUAL_SHARED_SHIM_PREFIX + source };
|
|
531
|
+
if (hasRemotes && normalized != null) {
|
|
532
|
+
const remoteNames = Object.keys(normalized.remotes);
|
|
533
|
+
for (const name of remoteNames) if (source === name || source.startsWith(`${name}/`)) return { id: VIRTUAL_REMOTE_PROXY_PREFIX + source };
|
|
534
|
+
}
|
|
535
|
+
const resolved = resolveVirtualId(source);
|
|
536
|
+
if (resolved != null) return { id: resolved };
|
|
537
|
+
return null;
|
|
538
|
+
} },
|
|
539
|
+
load: {
|
|
540
|
+
filter: [include(id(prefixRegex(VIRTUAL_PREFIX)))],
|
|
541
|
+
handler(id) {
|
|
542
|
+
if (id.startsWith(VIRTUAL_SHARED_SHIM_PREFIX)) return {
|
|
543
|
+
code: generateSharedShimCode(id.slice(VIRTUAL_SHARED_SHIM_PREFIX.length)),
|
|
544
|
+
moduleType: "js"
|
|
545
|
+
};
|
|
546
|
+
if (id.startsWith(VIRTUAL_REMOTE_PROXY_PREFIX)) return {
|
|
547
|
+
code: generateRemoteProxyCode({
|
|
548
|
+
remoteId: id.slice(VIRTUAL_REMOTE_PROXY_PREFIX.length),
|
|
549
|
+
reactAware: resolvedConfig?.mode !== "production"
|
|
550
|
+
}),
|
|
551
|
+
moduleType: "js"
|
|
552
|
+
};
|
|
553
|
+
if (normalized == null) return null;
|
|
554
|
+
const code = loadVirtualModule(id, normalized);
|
|
555
|
+
if (code != null) return {
|
|
556
|
+
code,
|
|
557
|
+
moduleType: "js"
|
|
558
|
+
};
|
|
559
|
+
return null;
|
|
560
|
+
}
|
|
561
|
+
},
|
|
562
|
+
transform: { handler(code, id) {
|
|
563
|
+
if (normalized == null || resolvedConfig == null) return null;
|
|
564
|
+
if (id.startsWith("\0rollipop:module-federation:")) return null;
|
|
565
|
+
const isEntry = this.getModuleInfo(id)?.isEntry ?? false;
|
|
566
|
+
if (hasExposes) {
|
|
567
|
+
if (!isEntry) return null;
|
|
568
|
+
const containerCode = generateRemoteEntryCode({
|
|
569
|
+
name: normalized.name,
|
|
570
|
+
exposes: exposesAbsolute
|
|
571
|
+
});
|
|
572
|
+
const ms = new MagicString(code);
|
|
573
|
+
ms.append("\n" + containerCode);
|
|
574
|
+
return {
|
|
575
|
+
code: ms.toString(),
|
|
576
|
+
map: ms.generateMap({
|
|
577
|
+
hires: true,
|
|
578
|
+
source: id
|
|
579
|
+
})
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
if (hasRemotes && isEntry) return transformHostEntry(code, id);
|
|
583
|
+
return null;
|
|
584
|
+
} },
|
|
585
|
+
watchChange() {
|
|
586
|
+
if (!hasExposes || broadcast == null) return;
|
|
587
|
+
if (debounceTimer != null) clearTimeout(debounceTimer);
|
|
588
|
+
debounceTimer = setTimeout(() => {
|
|
589
|
+
debounceTimer = null;
|
|
590
|
+
broadcast?.();
|
|
591
|
+
}, 100);
|
|
592
|
+
},
|
|
593
|
+
configureServer(server) {
|
|
594
|
+
if (!hasExposes) return;
|
|
595
|
+
broadcast = () => {
|
|
596
|
+
server.hot.sendAll(HMR_EVENT, { name: federationConfig.name });
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
};
|
|
600
|
+
}
|
|
601
|
+
function collectSharedNames(config) {
|
|
602
|
+
const shared = config.shared;
|
|
603
|
+
if (shared == null) return [];
|
|
604
|
+
return Array.isArray(shared) ? shared : Object.keys(shared);
|
|
605
|
+
}
|
|
606
|
+
//#endregion
|
|
607
|
+
export { moduleFederationPlugin as federation };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
//#region src/runtime.d.ts
|
|
2
|
+
interface ModuleFederationScriptLoader {
|
|
3
|
+
loadScript(args: {
|
|
4
|
+
scriptId: string;
|
|
5
|
+
url: string;
|
|
6
|
+
parentUrl?: string;
|
|
7
|
+
}): Promise<void>;
|
|
8
|
+
}
|
|
9
|
+
interface RemoteEntryExports {
|
|
10
|
+
init(shareScope: unknown, initScope?: unknown[]): void | Promise<void>;
|
|
11
|
+
get(modulePath: string): () => Promise<unknown>;
|
|
12
|
+
}
|
|
13
|
+
declare global {
|
|
14
|
+
var __rollipop_script_loader__: ModuleFederationScriptLoader | undefined;
|
|
15
|
+
}
|
|
16
|
+
//#endregion
|
|
17
|
+
export { ModuleFederationScriptLoader, RemoteEntryExports };
|
package/dist/runtime.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,4 +1,52 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rollipop/plugin-module-federation",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
|
|
3
|
+
"version": "0.1.0-alpha.20",
|
|
4
|
+
"homepage": "https://github.com/leegeunhyeok/rollipop#readme",
|
|
5
|
+
"bugs": {
|
|
6
|
+
"url": "https://github.com/leegeunhyeok/rollipop/issues"
|
|
7
|
+
},
|
|
8
|
+
"license": "MIT",
|
|
9
|
+
"author": "leegeunhyeok <dev.ghlee@gmail.com> (https://github.com/leegeunhyeok)",
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "git+https://github.com/leegeunhyeok/rollipop.git",
|
|
13
|
+
"directory": "packages/plugin-module-federation"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"type": "module",
|
|
19
|
+
"main": "./dist/index.js",
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"exports": {
|
|
22
|
+
".": {
|
|
23
|
+
"types": "./dist/index.d.ts",
|
|
24
|
+
"default": "./dist/index.js"
|
|
25
|
+
},
|
|
26
|
+
"./runtime": {
|
|
27
|
+
"types": "./dist/runtime.d.ts",
|
|
28
|
+
"default": "./dist/runtime.js"
|
|
29
|
+
},
|
|
30
|
+
"./package.json": "./package.json"
|
|
31
|
+
},
|
|
32
|
+
"scripts": {
|
|
33
|
+
"prepack": "yarn build",
|
|
34
|
+
"typecheck": "tsc --noEmit",
|
|
35
|
+
"test": "vp test --run",
|
|
36
|
+
"build": "vp pack"
|
|
37
|
+
},
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"@module-federation/runtime": "^2.4.0",
|
|
40
|
+
"@module-federation/sdk": "^2.4.0",
|
|
41
|
+
"dedent": "^1.7.2",
|
|
42
|
+
"magic-string": "^0.30.21"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"rollipop": "0.1.0-alpha.20",
|
|
46
|
+
"typescript": "6.0.3",
|
|
47
|
+
"vite-plus": "latest"
|
|
48
|
+
},
|
|
49
|
+
"peerDependencies": {
|
|
50
|
+
"rollipop": "0.1.0-alpha.20"
|
|
51
|
+
}
|
|
52
|
+
}
|
package/.editorconfig
DELETED
package/.gitattributes
DELETED