module federation + svelte-kit + single-spa

main
Laurin Weger 2 months ago
parent 4acbd9900f
commit a1e37fe1aa
Signed by: laurin
GPG Key ID: C62769CE726A5F92
  1. 4
      .gitignore
  2. 1454
      host-svelte/package-lock.json
  3. 8
      host-svelte/package.json
  4. 4
      host-svelte/src/App.svelte
  5. 14
      host-svelte/src/app.html
  6. 17
      host-svelte/src/host-svelte.single-spa.ts
  7. 23
      host-svelte/src/injectReactPreamble.ts
  8. 8
      host-svelte/src/lib/Parcel.svelte
  9. 25
      host-svelte/src/main.ts
  10. 28
      host-svelte/src/microfrontend-layout.html
  11. 1
      host-svelte/src/routes/+layout.js
  12. 18
      host-svelte/src/routes/+layout.svelte
  13. 5
      host-svelte/src/routes/+page.svelte
  14. 5
      host-svelte/src/utils.ts
  15. 13
      host-svelte/svelte.config.js
  16. 4
      host-svelte/tsconfig.json
  17. 18
      host-svelte/vite.config.mts
  18. 18
      remote-react/vite.config.ts
  19. 18
      remote-react2/vite.config.ts

4
.gitignore vendored

@ -24,4 +24,6 @@ dist-ssr
*.sln
*.sw?
tsconfig.tsbuildinfo
tsconfig.tsbuildinfo
.svelte-kit
build

File diff suppressed because it is too large Load Diff

@ -1,6 +1,7 @@
{
"name": "root-config",
"version": "1.0.0",
"type": "module",
"description": "Single-spa host app running svelte.",
"scripts": {
"dev": "vite",
@ -8,10 +9,15 @@
"preview": "vite build && vite preview --port 5000"
},
"dependencies": {
"@ark-ui/react": "^5.21.2",
"@ark-ui/svelte": "^5.21.2",
"@ark-ui/vue": "^5.23.0",
"@module-federation/enhanced": "^2.4.0",
"@module-federation/runtime": "^2.4.0",
"@sveltejs/adapter-static": "^3.0.10",
"@sveltejs/kit": "^2.60.1",
"@sveltejs/vite-plugin-svelte": "^7.1.2",
"@vitejs/plugin-react": "^6.0.2",
"react": "^19.2.6",
"react-dom": "^19.2.6",
"single-spa": "^6.0.1",
@ -24,7 +30,7 @@
"svelte": "^5.55.5",
"tailwindcss": "^4.2.4",
"typescript": "^6.0.0 || ^5",
"vite": "^7 || ^8",
"vite": "^8",
"vite-plugin-single-spa": "^1.1.1"
}
}

@ -72,10 +72,10 @@
<Parcel {mountParcel} config={switchParcelConfig} />
</section> -->
<section id="center" class="grid gap-6 md:grid-cols-2">
<Parcel {mountParcel} config={switchParcelConfig} />
<Parcel {mountParcel} config={reactAppConfig} />
<Parcel {mountParcel} config={reactAppConfig} />
<Parcel {mountParcel} config={switchParcelConfig} />
</section>
</div>
</main>

@ -0,0 +1,14 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div>
</body>
</html>

@ -1,17 +0,0 @@
import App from "./App.svelte";
import singleSpaSvelte from "single-spa-svelte";
import { mount as mountSvelte } from "svelte";
function MountableApp(options: any) {
return mountSvelte(App, options);
}
const svelteLifecycles = singleSpaSvelte({
component: MountableApp,
domElementGetter: () => document.getElementById("svelte-app"),
props: {},
});
export const bootstrap = svelteLifecycles.bootstrap;
export const mount = svelteLifecycles.mount;
export const unmount = svelteLifecycles.unmount;

@ -0,0 +1,23 @@
export default function injectReactPreamble(origins: string[]) {
// Hack to inject react preamble.
function appendReactRefreshScript(origin: string) {
const reactRefreshInteropabilityScript = document.createElement("script");
reactRefreshInteropabilityScript.type = "module";
reactRefreshInteropabilityScript.textContent = `
import RefreshRuntime from '${origin}/@react-refresh';
RefreshRuntime.injectIntoGlobalHook(window);
`;
document.head.appendChild(reactRefreshInteropabilityScript);
}
// @ts-ignore
window.$RefreshReg$ = () => {};
// @ts-ignore
window.$RefreshSig$ = () => (type) => type;
// @ts-ignore
window.__vite_plugin_react_preamble_installed__ = true;
origins.forEach((origin) => {
appendReactRefreshScript(origin);
});
}

@ -1,16 +1,14 @@
<script lang="ts">
import { untrack } from "svelte";
import { mountRootParcel } from "single-spa";
type ParcelProps = Record<string, unknown>;
type MountParcel = (config: unknown, props: ParcelProps) => any;
const {
mountParcel,
config,
parcelProps = {},
domElement = null,
} = $props<{
mountParcel: MountParcel;
config: unknown;
parcelProps?: ParcelProps;
domElement?: HTMLElement | null;
@ -24,13 +22,13 @@
$effect(() => {
const element = getDomElement();
if (!mountParcel || !config || !element) {
if (!config || !element) {
return;
}
let cancelled = false;
const initialProps = untrack(() => parcelProps);
const activeParcel = mountParcel(config, {
const activeParcel = mountRootParcel(config, {
domElement: element,
...initialProps,
});

@ -1,25 +0,0 @@
import "./app.css";
import { registerApplication, start } from "single-spa";
import * as singleSpaSvelte from "./host-svelte.single-spa";
import { init } from "@module-federation/enhanced/runtime";
init({ name: "host-svelte", remotes: [] });
registerApplication(
"host-svelte",
singleSpaSvelte,
(location) => true || location.pathname.startsWith("/app2"),
{ some: "value" },
);
start();
// import { mount } from 'svelte'
// import './app.css'
// import App from './App.svelte'
// const app = mount(App, {
// target: document.getElementById('app')!,
// })
// export default app

@ -1,28 +0,0 @@
<single-spa-router mode="hash">
<!--
This is the single-spa Layout Definition for your microfrontends.
See https://single-spa.js.org/docs/layout-definition/ for more information.
-->
<!-- Example layouts you might find helpful:
<nav>
<application name="@org/navbar"></application>
</nav>
<route path="settings">
<application name="@org/settings"></application>
</route>
-->
<main>
<route default>
<application name="@single-spa/welcome"></application>
</route>
<route path="welcome">
<application name="@single-spa/welcome"></application>
</route>
</main>
</single-spa-router>

@ -0,0 +1 @@
export const ssr = false;

@ -0,0 +1,18 @@
<script>
// Import shared dependencies so they are included in the build.
import "react";
import "react-dom";
import "react-dom/client";
import "react/jsx-runtime";
import "react/jsx-dev-runtime";
import "../app.css";
import injectReactPreamble from "../injectReactPreamble";
if (import.meta.env.DEV) {
injectReactPreamble(["http://localhost:5001", "http://localhost:5002"]);
}
</script>
<slot />

@ -0,0 +1,5 @@
<script lang="ts">
import App from "../App.svelte";
</script>
<App />

@ -27,13 +27,14 @@ export function loadModule(entryUri: string, moduleName: string) {
registerRemotes([
{
entry: entryUri,
name: remoteKey,
name: moduleName,
version: entryUri,
alias: entryUri,
type: "module",
},
]);
remoteKeys.push(remoteKey);
}
return loadRemote(remoteKey + "/" + moduleName);
return loadRemote(entryUri + "/" + moduleName);
}

@ -0,0 +1,13 @@
import adapter from "@sveltejs/adapter-static";
/** @type {import('@sveltejs/kit').Config} */
const config = {
kit: {
// adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list.
// If your environment is not supported, or you settled on a specific environment, switch out the adapter.
// See https://svelte.dev/docs/kit/adapters for more information about adapters.
adapter: adapter({ fallback: "app.html" }),
},
};
export default config;

@ -1,4 +1,5 @@
{
"extends": "./.svelte-kit/tsconfig.json",
"compilerOptions": {
"declarationDir": "dist",
"isolatedModules": true,
@ -14,5 +15,6 @@
"declaration": true,
"emitDeclarationOnly": true
}
},
"include": ["src/**/*"]
}

@ -1,8 +1,10 @@
import { defineConfig } from "vite";
import { defineConfig, Plugin } from "vite";
import { svelte } from "@sveltejs/vite-plugin-svelte";
import tailwindcss from "@tailwindcss/vite";
import vitePluginSingleSpa from "vite-plugin-single-spa";
import { federation } from "@module-federation/vite";
import { sveltekit } from "@sveltejs/kit/vite";
import react from "@vitejs/plugin-react";
// https://vite.dev/config/
export default defineConfig({
@ -10,20 +12,16 @@ export default defineConfig({
dedupe: ["react", "react-dom"],
},
plugins: [
svelte(),
react({}),
sveltekit(),
tailwindcss(),
vitePluginSingleSpa({
type: "root",
imo: "3.1.0",
importMaps: {
type: "importmap",
},
}),
federation({
name: "host-svelte",
remotes: {},
dev: { remoteHmr: true },
shared: {
"@ark-ui/svelte": {}, // Takes 2mb unzipped, maybe leave since tree-shaking should do its job?
"@ark-ui/svelte": { singleton: true }, // Takes 2mb unzipped, maybe leave since tree-shaking should do its job?
react: {
singleton: true,
},

@ -30,8 +30,6 @@ export default defineConfig({
serverPort: 5001,
spaEntryPoints: [],
cssStrategy: "multiMife",
// TODO:
assetFileNames: "",
}),
federation({
name: "remote-react",
@ -41,24 +39,16 @@ export default defineConfig({
"./switch-parcel": "./src/spa/switch-parcel.tsx",
},
shared: sharedConfig,
dev: { remoteHmr: true },
}),
],
optimizeDeps: {
exclude: ["@ark-ui/react"],
},
build: {
modulePreload: false,
target: "esnext",
minify: false,
rollupOptions: {
// external: [
// "single-spa",
// "react",
// "react/jsx-dev-runtime",
// "react/jsx-runtime",
// "react-dom",
// "react-dom/client",
// "@example/root-config",
// ],
},
// cssCodeSplit: false,
},
server: {
port: 5001,

@ -30,8 +30,6 @@ export default defineConfig({
serverPort: 5002,
spaEntryPoints: [],
cssStrategy: "multiMife",
// TODO:
assetFileNames: "",
}),
federation({
name: "remote-react2",
@ -41,24 +39,16 @@ export default defineConfig({
"./switch-parcel": "./src/spa/switch-parcel.tsx",
},
shared: sharedConfig,
dev: { remoteHmr: true },
}),
],
optimizeDeps: {
exclude: ["@ark-ui/react"],
},
build: {
modulePreload: false,
target: "esnext",
minify: false,
rollupOptions: {
// external: [
// "single-spa",
// "react",
// "react/jsx-dev-runtime",
// "react/jsx-runtime",
// "react-dom",
// "react-dom/client",
// "@example/root-config",
// ],
},
// cssCodeSplit: false,
},
server: {
port: 5002,

Loading…
Cancel
Save