@mainset/bundler-webpack 0.3.7 → 0.4.1
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/dist/esm/index.mjs +3 -0
- package/dist/esm/module-federation/index.mjs +2 -0
- package/dist/esm/module-federation/module-federation-node.mjs +16 -0
- package/dist/esm/module-federation/module-federation.chunk.mjs +48 -0
- package/dist/esm/module-federation/module-federation.fragment.mjs +161 -0
- package/dist/esm/module-federation/module-federation.types.mjs +1 -0
- package/dist/esm/module-federation/remote-stylesheets.plugin.mjs +54 -0
- package/dist/esm/webpack-config/config-fragments/module-rules/module-rules.fragment.mjs +12 -4
- package/dist/types/index.d.mts +1 -0
- package/dist/types/module-federation/index.d.mts +3 -0
- package/dist/types/module-federation/module-federation-node.d.mts +3 -0
- package/dist/types/module-federation/module-federation.chunk.d.mts +9 -0
- package/dist/types/module-federation/module-federation.fragment.d.mts +7 -0
- package/dist/types/module-federation/module-federation.types.d.mts +16 -0
- package/dist/types/module-federation/remote-stylesheets.plugin.d.mts +5 -0
- package/package.json +8 -2
- package/src/@types/bundler.d.ts +24 -0
package/dist/esm/index.mjs
CHANGED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export { DOCKER_SAFE__MODULE_FEDERATION_CONFIG_CHUNK, SSR_STYLESHEETS_FILENAME, } from './module-federation.chunk.mjs';
|
|
2
|
+
export { generateModuleFederationCommonWebpackConfigFragment, generateModuleFederationSSRContainerWebpackConfig, generateModuleFederationSSRServerWebpackConfigFragment, generateModuleFederationWebappWebpackConfigFragment, } from './module-federation.fragment.mjs';
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { createRequire } from 'module';
|
|
2
|
+
// NOTE: {@module-federation/node} is loaded through its CommonJS build on
|
|
3
|
+
// purpose. Its ESM build aliases its own internal {require} calls to
|
|
4
|
+
// {__require} (a helper of its bundler), and StreamingTargetPlugin embeds some
|
|
5
|
+
// of its source functions verbatim ({fn.toString()}) into the GENERATED
|
|
6
|
+
// chunk-loading runtime — so the ESM build leaks a free {__require} into the
|
|
7
|
+
// app's bundle ("__require is not defined" at runtime). The CJS build's
|
|
8
|
+
// equivalents use plain {require}, which resolves fine.
|
|
9
|
+
const requireFromBundler = createRequire(import.meta.url);
|
|
10
|
+
const { NodeFederationPlugin, StreamingTargetPlugin } = requireFromBundler('@module-federation/node');
|
|
11
|
+
// NOTE: passed to NodeFederationPlugin as {runtimePlugins} explicitly, instead
|
|
12
|
+
// of its own {useRuntimePlugin: true} — that option resolves a path missing
|
|
13
|
+
// from the package's ESM build ({../runtimePlugin.mjs}). The package's
|
|
14
|
+
// exports map exposes the CJS variant, resolved here.
|
|
15
|
+
const NODE_FEDERATION_RUNTIME_PLUGIN_PATH = requireFromBundler.resolve('@module-federation/node/runtimePlugin');
|
|
16
|
+
export { NODE_FEDERATION_RUNTIME_PLUGIN_PATH, NodeFederationPlugin, StreamingTargetPlugin, };
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// The file names a remote's build emits — part of its contract with whatever
|
|
2
|
+
// deploys and loads it.
|
|
3
|
+
const REMOTE_ENTRY_FILENAME__WEBAPP = 'remoteEntry.js';
|
|
4
|
+
const REMOTE_ENTRY_FILENAME__SSR = 'ssr-remoteEntry.js';
|
|
5
|
+
// NOTE: written next to the webapp build, but read by the hosts' SSR — deploy
|
|
6
|
+
// it with the server build (ssr-remoteEntry.js), not with the browser files,
|
|
7
|
+
// so it switches together with the server code that renders the markup.
|
|
8
|
+
const SSR_STYLESHEETS_FILENAME = 'ssr-stylesheets.json';
|
|
9
|
+
// NOTE: without this, the webapp entry consumes the shared singletons
|
|
10
|
+
// synchronously before Module Federation's share-scope negotiation gets an
|
|
11
|
+
// async tick to run, throwing RUNTIME-006 ("Invalid loadShareSync function
|
|
12
|
+
// call") on every load. {asyncStartup} wraps entry startup in an async
|
|
13
|
+
// boundary so the negotiation can complete first.
|
|
14
|
+
const ASYNC_STARTUP__MODULE_FEDERATION_CONFIG_CHUNK = {
|
|
15
|
+
experiments: { asyncStartup: true },
|
|
16
|
+
};
|
|
17
|
+
// NOTE: Module Federation's tooling writes into all three folders on every
|
|
18
|
+
// compile — the DTS plugin a scratch {tsconfig.<hash>.json} into
|
|
19
|
+
// node_modules/.federation, the manifest plugin {mf-manifest.json} into dist,
|
|
20
|
+
// and {@module-federation/error-codes} a diagnostics dump into .mf/ (on every
|
|
21
|
+
// reported error, at a hardcoded path). Each rewrite bumps mtime even with
|
|
22
|
+
// identical content, which a polling watcher reads as a source change: a
|
|
23
|
+
// self-sustaining rebuild loop, and a TS error in an exposed module surfaces
|
|
24
|
+
// as that loop instead of an error message. None of them is ever a source
|
|
25
|
+
// input, so they are excluded outright.
|
|
26
|
+
const WATCH_OPTIONS__MODULE_FEDERATION__WEBPACK_CONFIG_CHUNK = {
|
|
27
|
+
watchOptions: {
|
|
28
|
+
ignored: ['**/node_modules/**', '**/dist/**', '**/.mf/**'],
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
// Opt-in preset (pass it as {pluginOptions}) for running the dev build inside
|
|
32
|
+
// Docker on macOS/Windows: bind-mount file sharing doesn't propagate native fs
|
|
33
|
+
// events reliably, so webpack falls back to polling and can't tell "my own
|
|
34
|
+
// generated output changed" from a real edit.
|
|
35
|
+
// - dts: the default types folder ({@mf-types}) lands in the app root — the
|
|
36
|
+
// bind mount. Redirected into node_modules, which Docker setups usually
|
|
37
|
+
// mask with an anonymous volume.
|
|
38
|
+
// - dev: "hot types reload" hardcodes its output to {<app root>/.dev-server}
|
|
39
|
+
// (no option to move it), so it is turned off. Live reload of the remote's
|
|
40
|
+
// code is unaffected.
|
|
41
|
+
const DOCKER_SAFE__MODULE_FEDERATION_CONFIG_CHUNK = {
|
|
42
|
+
dts: {
|
|
43
|
+
generateTypes: { typesFolder: 'node_modules/.mf-types' },
|
|
44
|
+
consumeTypes: { typesFolder: 'node_modules/.mf-types' },
|
|
45
|
+
},
|
|
46
|
+
dev: { disableHotTypesReload: true },
|
|
47
|
+
};
|
|
48
|
+
export { ASYNC_STARTUP__MODULE_FEDERATION_CONFIG_CHUNK, DOCKER_SAFE__MODULE_FEDERATION_CONFIG_CHUNK, REMOTE_ENTRY_FILENAME__SSR, REMOTE_ENTRY_FILENAME__WEBAPP, SSR_STYLESHEETS_FILENAME, WATCH_OPTIONS__MODULE_FEDERATION__WEBPACK_CONFIG_CHUNK, };
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
2
|
+
var t = {};
|
|
3
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
4
|
+
t[p] = s[p];
|
|
5
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
6
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
7
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
8
|
+
t[p[i]] = s[p[i]];
|
|
9
|
+
}
|
|
10
|
+
return t;
|
|
11
|
+
};
|
|
12
|
+
import { runtimePathById } from '@mainset/cli/runtime';
|
|
13
|
+
import { ModuleFederationPlugin } from '@module-federation/enhanced/webpack';
|
|
14
|
+
import fs from 'fs';
|
|
15
|
+
import path from 'path';
|
|
16
|
+
import { merge } from 'webpack-merge';
|
|
17
|
+
import ssrServerEnvBasedConfig from '../webpack-config/server.ssr.config.mjs';
|
|
18
|
+
import { NODE_FEDERATION_RUNTIME_PLUGIN_PATH, NodeFederationPlugin, StreamingTargetPlugin, } from './module-federation-node.mjs';
|
|
19
|
+
import { ASYNC_STARTUP__MODULE_FEDERATION_CONFIG_CHUNK, REMOTE_ENTRY_FILENAME__SSR, REMOTE_ENTRY_FILENAME__WEBAPP, WATCH_OPTIONS__MODULE_FEDERATION__WEBPACK_CONFIG_CHUNK, } from './module-federation.chunk.mjs';
|
|
20
|
+
import { RemoteStylesheetsPlugin } from './remote-stylesheets.plugin.mjs';
|
|
21
|
+
// NOTE: an SSR host's server entry has to reach its SSR config through a
|
|
22
|
+
// dynamic import — a static import of the shared deps (e.g. react-dom/server)
|
|
23
|
+
// fails Module Federation's share-scope negotiation ({loadShareSync failed}).
|
|
24
|
+
// That async boundary is app code, so it lives next to the SSR config by
|
|
25
|
+
// convention, the same way {ssr-server.config.mts} does.
|
|
26
|
+
const SSR_SERVER_ENTRY_PATH = path.join(runtimePathById.config, 'ssr-server.entry.mts');
|
|
27
|
+
// { landing: { webappEntry: 'https://…/remoteEntry.js' } }
|
|
28
|
+
// → { landing: 'landing@https://…/remoteEntry.js' }
|
|
29
|
+
const generatePluginRemotes = (remotes = {}, entryKey) => Object.fromEntries(Object.entries(remotes).map(([remoteName, remote]) => {
|
|
30
|
+
const remoteEntry = remote[entryKey];
|
|
31
|
+
if (!remoteEntry) {
|
|
32
|
+
throw new Error(`[bundler-webpack] Module Federation remote "${remoteName}" has no {${entryKey}}.`);
|
|
33
|
+
}
|
|
34
|
+
return [remoteName, `${remoteName}@${remoteEntry}`];
|
|
35
|
+
}));
|
|
36
|
+
// NOTE: eager on the server only — nothing there is lazy-loaded for payload
|
|
37
|
+
// size, and the SSR server consumes the shared deps synchronously as soon as it
|
|
38
|
+
// starts. Required together with {webpackMode: "eager"} on the dynamic import
|
|
39
|
+
// in {ssr-server.entry.mts}: that one alone inlines the module, so the shared
|
|
40
|
+
// dep is consumed before negotiation (RUNTIME-006); this one alone leaves a
|
|
41
|
+
// separate chunk the server would then have to load. The browser keeps the
|
|
42
|
+
// non-eager deps — there the async negotiation is both correct and wanted.
|
|
43
|
+
const generateEagerShared = (shared = {}) => Object.fromEntries(Object.entries(shared).map(([sharedName, sharedConfig]) => [
|
|
44
|
+
sharedName,
|
|
45
|
+
Object.assign(Object.assign({}, sharedConfig), { eager: true }),
|
|
46
|
+
]));
|
|
47
|
+
// Merge into the config passed to {generateEnvBasedWebpackConfig} — it is
|
|
48
|
+
// needed by every compilation of an app that uses Module Federation.
|
|
49
|
+
const generateModuleFederationCommonWebpackConfigFragment = ({ name, }) => merge(WATCH_OPTIONS__MODULE_FEDERATION__WEBPACK_CONFIG_CHUNK, {
|
|
50
|
+
output: {
|
|
51
|
+
// NOTE: must differ between every app on the page — webpack names its
|
|
52
|
+
// global chunk array after it ({self.webpackChunk<uniqueName>}) and
|
|
53
|
+
// defaults it to package.json {name}. Two apps on the same array: the
|
|
54
|
+
// runtime loaded last takes it over, the other app's chunks register in
|
|
55
|
+
// the wrong runtime, and hydration fails ("Cannot read properties of
|
|
56
|
+
// undefined (reading 'call')").
|
|
57
|
+
uniqueName: name,
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
// Merge into the webapp (browser) compilation — in CSR the only one there is.
|
|
61
|
+
// NOTE: a remote built for SSR also publishes {ssr-stylesheets.json} for its
|
|
62
|
+
// hosts' SSR (see RemoteStylesheetsPlugin). Not in CSR — there is no server
|
|
63
|
+
// HTML to link it from.
|
|
64
|
+
const generateModuleFederationWebappWebpackConfigFragment = ({ name, exposes, remotes, shared, pluginOptions = {}, }) => {
|
|
65
|
+
const isSsrRemote = Boolean(exposes) && process.env.MS_CLI__WEBPACK_SERVE_MODE === 'ssr';
|
|
66
|
+
return {
|
|
67
|
+
plugins: [
|
|
68
|
+
new ModuleFederationPlugin(merge(Object.assign(Object.assign(Object.assign({ name }, (exposes && {
|
|
69
|
+
filename: REMOTE_ENTRY_FILENAME__WEBAPP,
|
|
70
|
+
exposes,
|
|
71
|
+
})), (remotes && {
|
|
72
|
+
remotes: generatePluginRemotes(remotes, 'webappEntry'),
|
|
73
|
+
})), { shared }), ASYNC_STARTUP__MODULE_FEDERATION_CONFIG_CHUNK, pluginOptions)),
|
|
74
|
+
...(isSsrRemote ? [new RemoteStylesheetsPlugin()] : []),
|
|
75
|
+
],
|
|
76
|
+
};
|
|
77
|
+
};
|
|
78
|
+
// Merge into an SSR HOST's server compilation (the one that renders pages and
|
|
79
|
+
// loads its remotes' server code). A remote that hosts nothing leaves its own
|
|
80
|
+
// server compilation as it is.
|
|
81
|
+
const generateModuleFederationSSRServerWebpackConfigFragment = ({ name, remotes, shared, pluginOptions = {}, }) => {
|
|
82
|
+
if (!fs.existsSync(SSR_SERVER_ENTRY_PATH)) {
|
|
83
|
+
throw new Error(`[bundler-webpack] an SSR host needs ${SSR_SERVER_ENTRY_PATH}, which imports ./ssr-server.config.mts dynamically ({webpackMode: "eager"}) — see generateModuleFederationSSRServerWebpackConfigFragment.`);
|
|
84
|
+
}
|
|
85
|
+
const pluginRemotes = generatePluginRemotes(remotes, 'ssrServerEntry');
|
|
86
|
+
const eagerShared = generateEagerShared(shared);
|
|
87
|
+
return {
|
|
88
|
+
// NOTE: 'async-node' (not plain 'node') is required by
|
|
89
|
+
// {@module-federation/node}'s runtime plugin.
|
|
90
|
+
target: 'async-node',
|
|
91
|
+
entry: { 'ssr-server.config': SSR_SERVER_ENTRY_PATH },
|
|
92
|
+
plugins: [
|
|
93
|
+
new NodeFederationPlugin(merge({
|
|
94
|
+
name,
|
|
95
|
+
remotes: pluginRemotes,
|
|
96
|
+
shared: eagerShared,
|
|
97
|
+
runtimePlugins: [NODE_FEDERATION_RUNTIME_PLUGIN_PATH],
|
|
98
|
+
}, pluginOptions), {}),
|
|
99
|
+
// NOTE: patches how async chunks the remotes split off are loaded in
|
|
100
|
+
// Node — fetched over HTTP instead of read from this app's disk.
|
|
101
|
+
new StreamingTargetPlugin({
|
|
102
|
+
name,
|
|
103
|
+
remotes: pluginRemotes,
|
|
104
|
+
shared: eagerShared,
|
|
105
|
+
}),
|
|
106
|
+
],
|
|
107
|
+
};
|
|
108
|
+
};
|
|
109
|
+
// A REMOTE's extra compilation for SSR hosts: its exposed modules built as a
|
|
110
|
+
// CommonJS container ({ssr-remoteEntry.js}) a Node process can fetch and run.
|
|
111
|
+
// Returns a whole config (not a fragment): it reuses the SSR server base (its
|
|
112
|
+
// TS/SCSS loaders, CSS nulled out) WITHOUT its entry — the container must not
|
|
113
|
+
// build the app's own SSR server — and webpack-merge can't remove a key.
|
|
114
|
+
// Pass the same fragment as to {generateEnvBasedWebpackConfig}, extended with
|
|
115
|
+
// the server-facing {output.publicPath}.
|
|
116
|
+
const generateModuleFederationSSRContainerWebpackConfig = (extendedWebpackConfig, { name, exposes, remotes, shared, pluginOptions = {}, }) => {
|
|
117
|
+
if (!exposes) {
|
|
118
|
+
throw new Error(`[bundler-webpack] "${name}" exposes nothing — an SSR container is only built for a remote.`);
|
|
119
|
+
}
|
|
120
|
+
const { entry: _ssrServerEntry } = ssrServerEnvBasedConfig, ssrServerConfigWithoutEntry = __rest(ssrServerEnvBasedConfig, ["entry"]);
|
|
121
|
+
const pluginRemotes = generatePluginRemotes(remotes, 'ssrServerEntry');
|
|
122
|
+
return merge(ssrServerConfigWithoutEntry, extendedWebpackConfig, {
|
|
123
|
+
entry: {},
|
|
124
|
+
// NOTE: not the base's 'node' — a hardcoded 'node' target bakes in
|
|
125
|
+
// filesystem {require()} chunk loading before StreamingTargetPlugin can
|
|
126
|
+
// switch it to HTTP, which breaks loading an exposed module's async chunk
|
|
127
|
+
// from another process. Matches how {@module-federation/node}'s own
|
|
128
|
+
// server-side remote examples set up their compiler.
|
|
129
|
+
target: false,
|
|
130
|
+
output: {
|
|
131
|
+
// NOTE: next to the browser build, not in dist/private — the host's
|
|
132
|
+
// Node process fetches it over HTTP, so it has to be served.
|
|
133
|
+
path: path.join(runtimePathById.dist, 'public'),
|
|
134
|
+
library: { type: 'commonjs-module' },
|
|
135
|
+
},
|
|
136
|
+
experiments: { outputModule: false },
|
|
137
|
+
plugins: [
|
|
138
|
+
// NOTE: NodeFederationPlugin (not plain ModuleFederationPlugin) even
|
|
139
|
+
// for a remote that consumes nothing: the plain one leaves the exposed
|
|
140
|
+
// module's async chunk on filesystem loading, which fails for a
|
|
141
|
+
// consumer in another process.
|
|
142
|
+
new NodeFederationPlugin(merge({
|
|
143
|
+
name,
|
|
144
|
+
filename: REMOTE_ENTRY_FILENAME__SSR,
|
|
145
|
+
// NOTE: Node hosts fetch a remote entry and {require()}-wrap it,
|
|
146
|
+
// reading {module.exports} — the browser container's bare
|
|
147
|
+
// {var <name> = …} global evaporates once wrapped that way.
|
|
148
|
+
library: { type: 'commonjs-module' },
|
|
149
|
+
remotes: pluginRemotes,
|
|
150
|
+
exposes,
|
|
151
|
+
shared,
|
|
152
|
+
}, pluginOptions), {}),
|
|
153
|
+
new StreamingTargetPlugin({
|
|
154
|
+
name,
|
|
155
|
+
library: { type: 'commonjs-module' },
|
|
156
|
+
remotes: pluginRemotes,
|
|
157
|
+
}),
|
|
158
|
+
],
|
|
159
|
+
});
|
|
160
|
+
};
|
|
161
|
+
export { generateModuleFederationCommonWebpackConfigFragment, generateModuleFederationSSRContainerWebpackConfig, generateModuleFederationSSRServerWebpackConfigFragment, generateModuleFederationWebappWebpackConfigFragment, };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { SSR_STYLESHEETS_FILENAME } from './module-federation.chunk.mjs';
|
|
2
|
+
const PLUGIN_NAME = 'RemoteStylesheetsPlugin';
|
|
3
|
+
const MODULE_FEDERATION_MANIFEST_FILENAME = 'mf-manifest.json';
|
|
4
|
+
// Records, per exposed module, the stylesheets an SSR host must have in its
|
|
5
|
+
// page <head> before that module's markup is first painted — the file the
|
|
6
|
+
// host's SSR reads to avoid a flash of unstyled content (FOUC).
|
|
7
|
+
//
|
|
8
|
+
// Why a host needs it: the exposed module's CSS lives in the remote's async
|
|
9
|
+
// chunk and is only requested by Module Federation's browser runtime AFTER JS
|
|
10
|
+
// runs, while the host's SSR HTML already carries the markup. Nothing in that
|
|
11
|
+
// HTML points at the CSS, so the page paints unstyled until the chunk loads.
|
|
12
|
+
//
|
|
13
|
+
// NOTE: derived from Module Federation's own manifest (its `assets.css.sync`
|
|
14
|
+
// per expose) rather than re-walking the chunk graph, so "which CSS belongs to
|
|
15
|
+
// which expose" stays MF's answer. Not shipped as-is because the manifest is
|
|
16
|
+
// not a stable contract: the webapp and the SSR container are two
|
|
17
|
+
// compilations writing the SAME `mf-manifest.json` into the same folder (last
|
|
18
|
+
// one wins), and the SSR one has no CSS at all (CSS is nulled out on the
|
|
19
|
+
// server). Runs on the WEBAPP compilation only.
|
|
20
|
+
//
|
|
21
|
+
// Output ({ssr-stylesheets.json}), URLs exactly `publicPath + file` (so
|
|
22
|
+
// output.publicPath must be absolute, not 'auto'), which is what webpack's CSS
|
|
23
|
+
// loader compares against — a <link> with the same href is reused after
|
|
24
|
+
// hydration instead of being fetched twice:
|
|
25
|
+
// { "./routes": ["https://cdn.example/landing/prod/css/806.0116.min.css"] }
|
|
26
|
+
//
|
|
27
|
+
// NOTE: only `sync` CSS — what the module needs to render. CSS of its lazily
|
|
28
|
+
// loaded chunks (`async`) is requested when that chunk is.
|
|
29
|
+
class RemoteStylesheetsPlugin {
|
|
30
|
+
apply(compiler) {
|
|
31
|
+
const { Compilation, WebpackError, sources } = compiler.webpack;
|
|
32
|
+
compiler.hooks.thisCompilation.tap(PLUGIN_NAME, (compilation) => {
|
|
33
|
+
compilation.hooks.processAssets.tap({
|
|
34
|
+
name: PLUGIN_NAME,
|
|
35
|
+
// NOTE: Module Federation emits its manifest at
|
|
36
|
+
// PROCESS_ASSETS_STAGE_OPTIMIZE_TRANSFER (3000) — this has to run after.
|
|
37
|
+
stage: Compilation.PROCESS_ASSETS_STAGE_REPORT,
|
|
38
|
+
}, () => {
|
|
39
|
+
const manifestAsset = compilation.getAsset(MODULE_FEDERATION_MANIFEST_FILENAME);
|
|
40
|
+
if (!manifestAsset) {
|
|
41
|
+
compilation.errors.push(new WebpackError(`${PLUGIN_NAME}: ${MODULE_FEDERATION_MANIFEST_FILENAME} was not emitted — is Module Federation's manifest disabled?`));
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
const { metaData, exposes } = JSON.parse(manifestAsset.source.source().toString());
|
|
45
|
+
const stylesheetsByExpose = Object.fromEntries(exposes.map(({ path, assets }) => [
|
|
46
|
+
path,
|
|
47
|
+
assets.css.sync.map((file) => `${metaData.publicPath}${file}`),
|
|
48
|
+
]));
|
|
49
|
+
compilation.emitAsset(SSR_STYLESHEETS_FILENAME, new sources.RawSource(`${JSON.stringify(stylesheetsByExpose, null, 2)}\n`));
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
export { RemoteStylesheetsPlugin };
|
|
@@ -14,14 +14,22 @@ const BABEL_LOADER__RULE__WEBPACK_CONFIG_FRAGMENT = createBabelLoaderRule(BABEL_
|
|
|
14
14
|
const SSR_SERVER_BABEL_LOADER__RULE__WEBPACK_CONFIG_FRAGMENT = createBabelLoaderRule(BABEL_CONFIG__NODE);
|
|
15
15
|
const IMAGES__RULE__WEBPACK_CONFIG_FRAGMENTS = [
|
|
16
16
|
{
|
|
17
|
-
test: /\.(svg|
|
|
17
|
+
test: /\.(avif|gif|ico|jpe?g|png|svg|webp)$/i,
|
|
18
18
|
exclude: /\.source\.svg$/,
|
|
19
19
|
type: 'asset/resource',
|
|
20
20
|
generator: {
|
|
21
|
-
|
|
21
|
+
// NOTE: written to `<output.path>/assets/images/`, loaded from
|
|
22
|
+
// `<output.publicPath>assets/images/`. `[ext]` already includes the dot.
|
|
23
|
+
filename: 'assets/images/[name].[contenthash:8][ext]',
|
|
24
|
+
// NOTE: replaced by the folder in {filename} above — the only option
|
|
25
|
+
// webpack applies to both the file and its URL. {outputPath} never
|
|
26
|
+
// reaches the URL, and a {publicPath} here REPLACES {output.publicPath}:
|
|
27
|
+
// a relative one resolves against the page's URL, which breaks on nested
|
|
28
|
+
// routes (/en/docninja → /en/assets/images/…) and for Module Federation
|
|
29
|
+
// remotes, whose images live on their own origin (CDN), not the host's.
|
|
22
30
|
// NOTE: the following paths are relative to the webpack output path (e.g. dist/)
|
|
23
|
-
outputPath: 'assets/images/',
|
|
24
|
-
publicPath: 'assets/images/',
|
|
31
|
+
// outputPath: 'assets/images/',
|
|
32
|
+
// publicPath: 'assets/images/',
|
|
25
33
|
},
|
|
26
34
|
},
|
|
27
35
|
{
|
package/dist/types/index.d.mts
CHANGED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { DOCKER_SAFE__MODULE_FEDERATION_CONFIG_CHUNK, SSR_STYLESHEETS_FILENAME, } from './module-federation.chunk.mjs';
|
|
2
|
+
export { generateModuleFederationCommonWebpackConfigFragment, generateModuleFederationSSRContainerWebpackConfig, generateModuleFederationSSRServerWebpackConfigFragment, generateModuleFederationWebappWebpackConfigFragment, } from './module-federation.fragment.mjs';
|
|
3
|
+
export type { ModuleFederationConfig, ModuleFederationRemote, ModuleFederationSharedConfig, } from './module-federation.types.mjs';
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
declare const NodeFederationPlugin: typeof import("@module-federation/node").NodeFederationPlugin, StreamingTargetPlugin: typeof import("@module-federation/node").StreamingTargetPlugin;
|
|
2
|
+
declare const NODE_FEDERATION_RUNTIME_PLUGIN_PATH: string;
|
|
3
|
+
export { NODE_FEDERATION_RUNTIME_PLUGIN_PATH, NodeFederationPlugin, StreamingTargetPlugin, };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Configuration } from 'webpack';
|
|
2
|
+
import type { ModuleFederationPluginOptions } from './module-federation.types.mjs';
|
|
3
|
+
declare const REMOTE_ENTRY_FILENAME__WEBAPP = "remoteEntry.js";
|
|
4
|
+
declare const REMOTE_ENTRY_FILENAME__SSR = "ssr-remoteEntry.js";
|
|
5
|
+
declare const SSR_STYLESHEETS_FILENAME = "ssr-stylesheets.json";
|
|
6
|
+
declare const ASYNC_STARTUP__MODULE_FEDERATION_CONFIG_CHUNK: Partial<ModuleFederationPluginOptions>;
|
|
7
|
+
declare const WATCH_OPTIONS__MODULE_FEDERATION__WEBPACK_CONFIG_CHUNK: Configuration;
|
|
8
|
+
declare const DOCKER_SAFE__MODULE_FEDERATION_CONFIG_CHUNK: Partial<ModuleFederationPluginOptions>;
|
|
9
|
+
export { ASYNC_STARTUP__MODULE_FEDERATION_CONFIG_CHUNK, DOCKER_SAFE__MODULE_FEDERATION_CONFIG_CHUNK, REMOTE_ENTRY_FILENAME__SSR, REMOTE_ENTRY_FILENAME__WEBAPP, SSR_STYLESHEETS_FILENAME, WATCH_OPTIONS__MODULE_FEDERATION__WEBPACK_CONFIG_CHUNK, };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Configuration } from 'webpack';
|
|
2
|
+
import type { ModuleFederationConfig } from './module-federation.types.mjs';
|
|
3
|
+
declare const generateModuleFederationCommonWebpackConfigFragment: ({ name, }: Pick<ModuleFederationConfig, "name">) => Configuration;
|
|
4
|
+
declare const generateModuleFederationWebappWebpackConfigFragment: ({ name, exposes, remotes, shared, pluginOptions, }: ModuleFederationConfig) => Configuration;
|
|
5
|
+
declare const generateModuleFederationSSRServerWebpackConfigFragment: ({ name, remotes, shared, pluginOptions, }: ModuleFederationConfig) => Configuration;
|
|
6
|
+
declare const generateModuleFederationSSRContainerWebpackConfig: (extendedWebpackConfig: Configuration, { name, exposes, remotes, shared, pluginOptions, }: ModuleFederationConfig) => Configuration;
|
|
7
|
+
export { generateModuleFederationCommonWebpackConfigFragment, generateModuleFederationSSRContainerWebpackConfig, generateModuleFederationSSRServerWebpackConfigFragment, generateModuleFederationWebappWebpackConfigFragment, };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { ModuleFederationPlugin } from '@module-federation/enhanced/webpack';
|
|
2
|
+
type ModuleFederationPluginOptions = ConstructorParameters<typeof ModuleFederationPlugin>[0];
|
|
3
|
+
type ModuleFederationSharedObject = Exclude<NonNullable<ModuleFederationPluginOptions['shared']>, unknown[]>;
|
|
4
|
+
type ModuleFederationSharedConfig = Exclude<ModuleFederationSharedObject[string], string>;
|
|
5
|
+
type ModuleFederationRemote = {
|
|
6
|
+
webappEntry: string;
|
|
7
|
+
ssrServerEntry?: string;
|
|
8
|
+
};
|
|
9
|
+
type ModuleFederationConfig = {
|
|
10
|
+
name: string;
|
|
11
|
+
exposes?: Record<string, string>;
|
|
12
|
+
remotes?: Record<string, ModuleFederationRemote>;
|
|
13
|
+
shared?: Record<string, ModuleFederationSharedConfig>;
|
|
14
|
+
pluginOptions?: Partial<ModuleFederationPluginOptions>;
|
|
15
|
+
};
|
|
16
|
+
export type { ModuleFederationConfig, ModuleFederationPluginOptions, ModuleFederationRemote, ModuleFederationSharedConfig, };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mainset/bundler-webpack",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "Bundler for web apps",
|
|
5
5
|
"homepage": "https://github.com/mainset/dev-stack-fe/tree/main/packages/bundler-webpack",
|
|
6
6
|
"bugs": {
|
|
@@ -23,6 +23,10 @@
|
|
|
23
23
|
"types": "./dist/types/index.d.mts",
|
|
24
24
|
"import": "./dist/esm/index.mjs"
|
|
25
25
|
},
|
|
26
|
+
"./module-federation": {
|
|
27
|
+
"types": "./dist/types/module-federation/index.d.mts",
|
|
28
|
+
"import": "./dist/esm/module-federation/index.mjs"
|
|
29
|
+
},
|
|
26
30
|
"./global-types": "./src/@types/bundler.d.ts"
|
|
27
31
|
},
|
|
28
32
|
"dependencies": {
|
|
@@ -31,6 +35,8 @@
|
|
|
31
35
|
"@babel/preset-env": "^8.0.2",
|
|
32
36
|
"@babel/preset-react": "^8.0.1",
|
|
33
37
|
"@babel/preset-typescript": "^8.0.1",
|
|
38
|
+
"@module-federation/enhanced": "^2.9.2",
|
|
39
|
+
"@module-federation/node": "^2.7.52",
|
|
34
40
|
"babel-loader": "^10.1.1",
|
|
35
41
|
"copy-webpack-plugin": "^14.0.0",
|
|
36
42
|
"css-loader": "^7.1.4",
|
|
@@ -49,7 +55,7 @@
|
|
|
49
55
|
"webpack-merge": "^6.0.1"
|
|
50
56
|
},
|
|
51
57
|
"devDependencies": {
|
|
52
|
-
"@mainset/cli": "^0.7.
|
|
58
|
+
"@mainset/cli": "^0.7.4",
|
|
53
59
|
"@mainset/dev-stack-fe": "^0.2.3"
|
|
54
60
|
},
|
|
55
61
|
"peerDependencies": {
|
package/src/@types/bundler.d.ts
CHANGED
|
@@ -37,6 +37,26 @@ declare module '*.scss';
|
|
|
37
37
|
declare module '*.sass';
|
|
38
38
|
|
|
39
39
|
// NOTE: {asset/resource} — emits the file and exports its public URL
|
|
40
|
+
declare module '*.avif' {
|
|
41
|
+
const src: string;
|
|
42
|
+
export default src;
|
|
43
|
+
}
|
|
44
|
+
declare module '*.gif' {
|
|
45
|
+
const src: string;
|
|
46
|
+
export default src;
|
|
47
|
+
}
|
|
48
|
+
declare module '*.ico' {
|
|
49
|
+
const src: string;
|
|
50
|
+
export default src;
|
|
51
|
+
}
|
|
52
|
+
declare module '*.jpeg' {
|
|
53
|
+
const src: string;
|
|
54
|
+
export default src;
|
|
55
|
+
}
|
|
56
|
+
declare module '*.jpg' {
|
|
57
|
+
const src: string;
|
|
58
|
+
export default src;
|
|
59
|
+
}
|
|
40
60
|
declare module '*.png' {
|
|
41
61
|
const src: string;
|
|
42
62
|
export default src;
|
|
@@ -45,3 +65,7 @@ declare module '*.svg' {
|
|
|
45
65
|
const src: string;
|
|
46
66
|
export default src;
|
|
47
67
|
}
|
|
68
|
+
declare module '*.webp' {
|
|
69
|
+
const src: string;
|
|
70
|
+
export default src;
|
|
71
|
+
}
|