Skip to content

Commit 26e0870

Browse files
WIP: Remix Example (#3296)
* remix * WUO: add remix example * WUO: add remix example * WIP: implement full loading chain * remix * WUO: add remix example * WUO: add remix example * WIP: implement full loading chain * Update _index.tsx Co-authored-by: fyodorovandrei <[email protected]> * rename package conflicts * WIP: Remix Rspack * WIP: Remix Rspack * WIP: Remix Rspack * update lock * rename package conflicts * update lock * fix remix example * fix remix example * fix remix example * Update dependency svelte-loader to v3.1.9 * Update dependency rollup to v2.79.1 * fix remix example * rspack example * update locks * abstract build stuff * abstract build stuff * lock file update * abstract configs * fix deprecations * fix deprecations * fix deprecations * fix deprecations * update remix example * update remix example * update rspack remix example --------- Co-authored-by: ScriptedAlchemy <[email protected]> Co-authored-by: fyodorovandrei <[email protected]>
1 parent 9323ade commit 26e0870

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

70 files changed

+11576
-1022
lines changed

pnpm-lock.yaml

+3,010-1,022
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pnpm-workspace.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ packages:
22
- '**/**'
33
- '!docs'
44
- '!cypress'
5+
- '!remix/*'
56
- '!angular14-react/*'
67
- '!test'
78
- '!multi-threaded-ssr/website2'

remix/app1/.gitignore

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
.DS_Store
2+
.cache
3+
node_modules
4+
build
5+
public/build

remix/app1/app/root.tsx

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import {
2+
Links,
3+
Meta,
4+
Outlet,
5+
Scripts,
6+
ScrollRestoration,
7+
} from "@remix-run/react";
8+
9+
export function loader() {
10+
return "Hello, World!";
11+
}
12+
//dynamic import data uri, force chunk handlers to be registered in webpack (since theres no import(), in this example)
13+
import("data:text/javascript,console.log('hello from app1')");
14+
15+
export default function App() {
16+
return (
17+
<html lang="en">
18+
<head>
19+
<meta charSet="utf-8" />
20+
<meta name="viewport" content="width=device-width, initial-scale=1" />
21+
<Meta />
22+
<Links />
23+
</head>
24+
<body>
25+
<Outlet />
26+
<ScrollRestoration />
27+
<Scripts />
28+
</body>
29+
</html>
30+
);
31+
}

remix/app1/app/routes/_index.tsx

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { useLoaderData } from "@remix-run/react";
2+
import {lazy,Suspense} from "react";
3+
import Button from 'app2/button'
4+
export function loader() {
5+
return { message: "Hello, World!" };
6+
}
7+
// const Button = lazy(() => import('app2/button'));
8+
export default function Home() {
9+
const { message } = useLoaderData<typeof loader>();
10+
return (
11+
<div>
12+
<h1>Home</h1>
13+
<p>{message}</p>
14+
<Suspense fallback={"loading remote"}>
15+
<Button/>
16+
</Suspense>
17+
</div>
18+
);
19+
}

remix/app1/components/Button.jsx

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export default () => {
2+
return <button>Federated Button from App1</button>
3+
}

remix/app1/package.json

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
{
2+
"name": "remix-app1",
3+
"version": "0.0.0",
4+
"type": "module",
5+
"scripts": {
6+
"build:browser": "webpack --mode=development --config webpack.browser.js",
7+
"build:server": "webpack --mode=development --config webpack.server.js",
8+
"start":"rm -rf ./public/build; pnpm run build:browser && pnpm run build:server && sleep 5 && node ./build/index.js",
9+
"server":"node ./build/index.js"
10+
},
11+
"author": "Jacob Ebey",
12+
"license": "ISC",
13+
"devDependencies": {
14+
"@babel/core": "^7.23.2",
15+
"@remix-run/dev": "^2.2.0",
16+
"@types/express": "^4.17.20",
17+
"@types/react": "^18.2.34",
18+
"@types/react-dom": "^18.2.14",
19+
"babel-loader": "^9.1.3",
20+
"babel-plugin-eliminator": "^1.0.1",
21+
"esbuild": "^0.19.5",
22+
"esbuild-loader": "^4.0.2",
23+
"webpack": "^5.89.0",
24+
"webpack-cli": "^5.1.4",
25+
"webpack-node-externals": "^3.0.0",
26+
"@module-federation/node": "^2.1.1",
27+
"@module-federation/enhanced": "^0.1.1"
28+
},
29+
"dependencies": {
30+
"cors": "2.8.5",
31+
"@remix-run/express": "^2.2.0",
32+
"@remix-run/node": "^2.2.0",
33+
"@remix-run/react": "^2.2.0",
34+
"express": "^4.18.2",
35+
"isbot": "^3.7.0",
36+
"react": "^18.2.0",
37+
"react-dom": "^18.2.0"
38+
}
39+
}

remix/app1/remix.config.js

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
/** @type {import("@remix-run/dev").AppConfig} */
2+
export default {
3+
server: "server.ts",
4+
serverModuleFormat: 'commonjs'
5+
};

remix/app1/server.ts

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import express from "express";
2+
import { createRequestHandler } from "@remix-run/express";
3+
import cors from 'cors';
4+
5+
import * as build from "@remix-run/dev/server-build.js";
6+
7+
const app = express();
8+
app.use(cors());
9+
app.use(express.static("public"));
10+
app.use('/server', express.static("build"));
11+
app.all("*", createRequestHandler({ build }));
12+
13+
app.listen(3000, () => {
14+
console.log(`Server started at http://localhost:3000`);
15+
});

remix/app1/tsconfig.json

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"compilerOptions": {
3+
"module": "Node16",
4+
"moduleResolution": "Node16",
5+
"strict": true,
6+
"jsx": "react-jsx"
7+
}
8+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
class HoistContainerReferences {
2+
apply(compiler) {
3+
compiler.hooks.thisCompilation.tap('HoistContainerReferences', compilation => {
4+
compilation.hooks.afterOptimizeChunks.tap('HoistContainerReferences', chunks => {
5+
const chunkSet = new Map();
6+
const externalRequests = new Set();
7+
for (const chunk of chunks) {
8+
chunkSet.set(chunk.id || chunk.name, chunk);
9+
}
10+
// console.log(chunkSet)
11+
for (const chunk of chunks) {
12+
const remoteModules = compilation.chunkGraph.getChunkModulesIterableBySourceType(
13+
chunk,
14+
'remote',
15+
);
16+
if (!remoteModules) continue;
17+
for (const remoteModule of remoteModules) {
18+
remoteModule.dependencies.forEach(dep => {
19+
const mod = compilation.moduleGraph.getModule(dep);
20+
externalRequests.add(mod);
21+
const runtimeChunk = chunkSet.get(chunk.runtime);
22+
compilation.chunkGraph.connectChunkAndModule(runtimeChunk, mod);
23+
});
24+
}
25+
}
26+
console.log(externalRequests);
27+
});
28+
});
29+
}
30+
}
31+
32+
export {HoistContainerReferences}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import {toManifest, writeManifest} from "./manifest.js";
2+
3+
class RemixAssetsManifestPlugin {
4+
constructor(remixConfig) {
5+
this.remixConfig = remixConfig;
6+
}
7+
/**
8+
* @param {import("webpack").Compiler} compiler
9+
*/
10+
apply(compiler) {
11+
compiler.hooks.emit.tapPromise(
12+
"RemixAssetsManifest",
13+
async (compilation) => {
14+
const stats = compilation.getStats();
15+
const manifest = await toManifest(this.remixConfig, stats);
16+
writeManifest(this.remixConfig, manifest);
17+
}
18+
);
19+
}
20+
}
21+
22+
export {RemixAssetsManifestPlugin}

remix/app1/utils/get-exports.js

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import os from "os";
2+
import esbuild from "esbuild";
3+
4+
export function getExports(routePath, remixConfig) {
5+
const { metafile, errors } = esbuild.buildSync({
6+
sourceRoot: remixConfig.appDirectory,
7+
entryPoints: [routePath],
8+
target: "esnext",
9+
bundle: false,
10+
metafile: true,
11+
write: false,
12+
outdir: os.tmpdir(),
13+
});
14+
if ((errors === null || errors === void 0 ? void 0 : errors.length) > 0) {
15+
throw new Error(
16+
esbuild.formatMessagesSync(errors, { kind: "error" }).join("\n")
17+
);
18+
}
19+
const outputs = Object.values(metafile.outputs);
20+
if (outputs.length !== 1) {
21+
throw Error();
22+
}
23+
const output = outputs[0];
24+
return output.exports;
25+
}

remix/app1/utils/get-routes.js

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import path from "node:path";
2+
3+
export const routeSet = new Set();
4+
export const getRoutes= (remixConfig)=> Object.fromEntries(
5+
Object.entries(remixConfig.routes).map(([key, route]) => {
6+
const fullPath = path.resolve(remixConfig.appDirectory, route.file);
7+
routeSet.add(fullPath);
8+
return [key, fullPath];
9+
})
10+
);

remix/app1/utils/manifest.js

+142
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
import * as fs from "node:fs";
2+
import * as path from "node:path";
3+
4+
import { getExports } from "./get-exports.js";
5+
6+
function createUrl(publicPath, file) {
7+
return (
8+
publicPath.split(path.win32.sep).join("/") +
9+
(file || "").split(path.win32.sep).join("/")
10+
);
11+
}
12+
13+
/**
14+
*
15+
* @param {import("webpack").StatsCompilation} stats
16+
* @param {string} publicPath
17+
* @returns {(group: string) => string[]}
18+
*/
19+
function createNamedChunkGroupFactory(stats, publicPath) {
20+
const chunksById = new Map(stats.chunks?.map((chunk) => [chunk.id, chunk]));
21+
return (group) => {
22+
/** @type {Set<string>} */
23+
const files = new Set();
24+
stats.namedChunkGroups?.[group].chunks?.forEach((chunkId) => {
25+
const chunk = chunksById.get(chunkId);
26+
chunk?.files?.forEach((file) => files.add(createUrl(publicPath, file)));
27+
});
28+
return [...files];
29+
};
30+
}
31+
32+
/**
33+
* @param {webpack.StatsCompilation} param0
34+
* @param {string} entrypointId
35+
*/
36+
const getAssets = ({ entrypoints }, entrypointId) => {
37+
if (entrypoints === undefined) throw Error("todo");
38+
const { assets } = entrypoints[entrypointId];
39+
if (assets === undefined) throw Error("todo");
40+
return assets;
41+
};
42+
43+
/**
44+
* @param {import("@remix-run/dev").ResolvedRemixConfig} remixConfig
45+
* @param {import("webpack").Stats} stats
46+
* @returns {import("@remix-run/dev").AssetsManifest}
47+
*/
48+
export async function toManifest(remixConfig, stats) {
49+
const compilationStats = stats.toJson({
50+
modules: true,
51+
entrypoints: true,
52+
assets: true,
53+
groupAssetsByChunk: true,
54+
hash: true,
55+
});
56+
const getByNamedChunkGroup = createNamedChunkGroupFactory(
57+
compilationStats,
58+
remixConfig.publicPath
59+
);
60+
61+
const entryImports = getByNamedChunkGroup("entry.client");
62+
const entryModule = createUrl(
63+
remixConfig.publicPath,
64+
getAssets(compilationStats, "entry.client").slice(-1)[0].name
65+
);
66+
const rootImports = getByNamedChunkGroup("root");
67+
68+
// TODO: what are runtime imports? dynamic imports?
69+
// let runtimeImports = compilationStats.assetsByChunkName["runtime"].map(
70+
// (asset) => createUrl(remixConfig.publicPath, asset)
71+
// );
72+
73+
const routes = Object.fromEntries(
74+
Object.entries(remixConfig.routes).map(([routeId, route]) => {
75+
const assets = getAssets(compilationStats, routeId);
76+
const routeImports = assets
77+
.slice(0, -1)
78+
.map((asset) => createUrl(remixConfig.publicPath, asset.name));
79+
const routeModule = createUrl(
80+
remixConfig.publicPath,
81+
assets.slice(-1)[0].name
82+
);
83+
const routePath = path.resolve(remixConfig.appDirectory, route.file);
84+
const routeExports = getExports(routePath, remixConfig);
85+
return [
86+
routeId,
87+
{
88+
id: route.id,
89+
parentId: route.parentId,
90+
path: route.path,
91+
index: route.index,
92+
caseSensitive: route.caseSensitive,
93+
module: routeModule,
94+
imports: routeImports,
95+
hasAction: routeExports.includes("action"),
96+
hasLoader: routeExports.includes("loader"),
97+
hasCatchBoundary: routeExports.includes("CatchBoundary"),
98+
hasErrorBoundary: routeExports.includes("ErrorBoundary"),
99+
},
100+
];
101+
})
102+
);
103+
104+
const version = compilationStats.hash;
105+
if (version === undefined) throw Error("todo");
106+
return {
107+
version,
108+
url: createUrl(
109+
remixConfig.publicPath,
110+
`manifest-${version.toUpperCase()}.js`
111+
),
112+
entry: {
113+
imports: [
114+
...new Set([/* ...runtimeImports, */ ...entryImports, ...rootImports]),
115+
],
116+
module: entryModule,
117+
},
118+
routes,
119+
};
120+
}
121+
122+
export function writeManifest(config, manifest) {
123+
fs.mkdirSync("./.cache", { recursive: true });
124+
fs.writeFileSync(
125+
"./.cache/manifest.json",
126+
JSON.stringify(manifest, null, 2),
127+
"utf8"
128+
);
129+
130+
fs.mkdirSync(config.assetsBuildDirectory, { recursive: true });
131+
fs.writeFileSync(
132+
path.resolve(config.assetsBuildDirectory, path.basename(manifest.url)),
133+
`window.__remixManifest=${JSON.stringify(manifest)};`
134+
);
135+
}
136+
137+
/**
138+
* @returns {import("@remix-run/dev").AssetsManifest}
139+
*/
140+
export function getManifest() {
141+
return JSON.parse(fs.readFileSync("./.cache/manifest.json", "utf8"));
142+
}

0 commit comments

Comments
 (0)