Skip to content

Commit a8492b6

Browse files
committed
add support for importing css files in fixture
1 parent d5a7af9 commit a8492b6

File tree

5 files changed

+185
-1
lines changed

5 files changed

+185
-1
lines changed

fixtures/flight-vite/scripts/build.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,11 @@ async function build() {
168168
external: ['react', 'react-dom', 'react-server-dom-vite'],
169169
},
170170
});
171+
172+
// copy assets from react-server build to static build, this includes stylesheets improted from server components
173+
await fs.promises.cp('build/react-server/assets', 'build/static/assets', {
174+
recursive: true,
175+
});
171176
}
172177

173178
build();
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
'use strict';
2+
3+
/**
4+
* Traverses the module graph and collects assets for a given chunk
5+
*
6+
* @param manifest Client manifest
7+
* @param id Chunk id
8+
* @param assetMap Cache of assets
9+
* @returns Array of asset URLs
10+
*/
11+
const findAssetsInManifest = (manifest, id, assetMap = new Map()) => {
12+
function traverse(id) {
13+
const cached = assetMap.get(id);
14+
if (cached) {
15+
return cached;
16+
}
17+
const chunk = manifest[id];
18+
if (!chunk) {
19+
return [];
20+
}
21+
const assets = [
22+
...(chunk.assets || []),
23+
...(chunk.css || []),
24+
...(chunk.imports?.flatMap(traverse) || []),
25+
];
26+
const imports = chunk.imports?.flatMap(traverse) || [];
27+
const all = [...assets, ...imports].filter(Boolean);
28+
all.push(chunk.file);
29+
assetMap.set(id, all);
30+
return Array.from(new Set(all));
31+
}
32+
return traverse(id);
33+
};
34+
35+
const findAssetsInModuleNode = moduleNode => {
36+
const seen = new Set();
37+
function traverse(node) {
38+
if (seen.has(node.url)) {
39+
return [];
40+
}
41+
seen.add(node.url);
42+
43+
const imports = [...node.importedModules].flatMap(traverse) || [];
44+
imports.push(node.url);
45+
return Array.from(new Set(imports));
46+
}
47+
return traverse(moduleNode);
48+
};
49+
50+
module.exports = {
51+
findAssetsInManifest,
52+
};

fixtures/flight-vite/server/region.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,11 @@ async function createApp() {
6464
return viteServer.ssrLoadModule(id);
6565
};
6666

67+
const {collectStyles} = require('./styles.js');
68+
globalThis.__vite_find_assets__ = async entries => {
69+
return Object.keys(await collectStyles(viteServer, entries));
70+
};
71+
6772
loadModule = async entry => {
6873
return await viteServer.ssrLoadModule(
6974
path.isAbsolute(entry)
@@ -92,10 +97,18 @@ async function createApp() {
9297

9398
globalThis.__vite_module_cache__ = new Map();
9499
globalThis.__vite_require__ = id => {
100+
console.log({id});
95101
return import(
96102
path.join(process.cwd(), 'build', 'react-server', id + '.js')
97103
);
98104
};
105+
const {findAssetsInManifest} = require('./manifest.js');
106+
107+
globalThis.__vite_find_assets__ = async entries => {
108+
return findAssetsInManifest(reactServerManifest, entries[0]).filter(
109+
asset => asset.endsWith('.css')
110+
);
111+
};
99112
}
100113

101114
async function renderApp(res, returnValue) {
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
'use strict';
2+
3+
const path = require('node:path');
4+
5+
async function findDeps(vite, node, deps) {
6+
// since `ssrTransformResult.deps` contains URLs instead of `ModuleNode`s, this process is asynchronous.
7+
// instead of using `await`, we resolve all branches in parallel.
8+
const branches = [];
9+
10+
async function add(node) {
11+
if (!deps.has(node)) {
12+
deps.add(node);
13+
await findDeps(vite, node, deps);
14+
}
15+
}
16+
17+
async function add_by_url(url) {
18+
const node = await vite.moduleGraph.getModuleByUrl(url);
19+
20+
if (node) {
21+
await add(node);
22+
}
23+
}
24+
25+
if (node.ssrTransformResult) {
26+
if (node.ssrTransformResult.deps) {
27+
node.ssrTransformResult.deps.forEach(url =>
28+
branches.push(add_by_url(url))
29+
);
30+
}
31+
32+
// if (node.ssrTransformResult.dynamicDeps) {
33+
// node.ssrTransformResult.dynamicDeps.forEach(url => branches.push(add_by_url(url)));
34+
// }
35+
} else {
36+
node.importedModules.forEach(node => branches.push(add(node)));
37+
}
38+
39+
await Promise.all(branches);
40+
}
41+
42+
// Vite doesn't expose this so we just copy the list for now
43+
// https://github.com/vitejs/vite/blob/3edd1af56e980aef56641a5a51cf2932bb580d41/packages/vite/src/node/plugins/css.ts#L96
44+
const STYLE_ASSET_REGEX = /\.(css|less|sass|scss|styl|stylus|pcss|postcss)$/;
45+
const MODULE_STYLE_ASSET_REGEX =
46+
/\.module\.(css|less|sass|scss|styl|stylus|pcss|postcss)$/;
47+
48+
async function collectStyles(devServer, match) {
49+
const styles = {};
50+
const deps = new Set();
51+
try {
52+
for (const file of match) {
53+
const resolvedId = await devServer.pluginContainer.resolveId(file);
54+
55+
if (!resolvedId) {
56+
console.log('not found');
57+
continue;
58+
}
59+
60+
const id = resolvedId.id;
61+
62+
const normalizedPath = path.resolve(id).replace(/\\/g, '/');
63+
let node = devServer.moduleGraph.getModuleById(normalizedPath);
64+
if (!node) {
65+
const absolutePath = path.resolve(file);
66+
await devServer.ssrLoadModule(absolutePath);
67+
node = await devServer.moduleGraph.getModuleByUrl(absolutePath);
68+
69+
if (!node) {
70+
console.log('not found');
71+
return;
72+
}
73+
}
74+
75+
await findDeps(devServer, node, deps);
76+
}
77+
} catch (e) {
78+
console.error(e);
79+
}
80+
81+
for (const dep of deps) {
82+
const parsed = new URL(dep.url, 'http://localhost/');
83+
const query = parsed.searchParams;
84+
85+
if (STYLE_ASSET_REGEX.test(dep.file ?? '')) {
86+
try {
87+
const mod = await devServer.ssrLoadModule(dep.url);
88+
// if (module_STYLE_ASSET_REGEX.test(dep.file)) {
89+
// styles[dep.url] = env.cssModules?.[dep.file];
90+
// } else {
91+
styles[dep.url] = mod.default;
92+
// }
93+
} catch {
94+
// this can happen with dynamically imported modules, I think
95+
// because the Vite module graph doesn't distinguish between
96+
// static and dynamic imports? TODO investigate, submit fix
97+
}
98+
}
99+
}
100+
return styles;
101+
}
102+
103+
module.exports = {collectStyles};

fixtures/flight-vite/src/App.jsx

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,17 @@ const REACT_REFRESH_PREAMBLE = `
1414
window.__vite_plugin_react_preamble_installed__ = true
1515
`;
1616

17+
async function Assets() {
18+
const styles = await __vite_find_assets__(['src/App.jsx']);
19+
return (
20+
<>
21+
{styles.map(key => (
22+
<link key={key} rel="stylesheet" href={key} />
23+
))}
24+
</>
25+
);
26+
}
27+
1728
export default async function App() {
1829
const res = await fetch('http://localhost:3001/todos');
1930
const todos = await res.json();
@@ -23,7 +34,6 @@ export default async function App() {
2334
<meta charSet="utf-8" />
2435
<meta name="viewport" content="width=device-width, initial-scale=1" />
2536
<title>Flight</title>
26-
<link rel="stylesheet" href="/src/style.css" />
2737
{import.meta.env.DEV ? (
2838
<>
2939
<script type="module" src="@vite/client" />
@@ -35,6 +45,7 @@ export default async function App() {
3545
/>
3646
</>
3747
) : null}
48+
<Assets />
3849
</head>
3950
<body>
4051
<div>

0 commit comments

Comments
 (0)