Files
cloudflare__vinext/tests/node-modules-css.test.ts
Stephen Zhou c17d6941be chore: migrate to vite plus (#535)
* chore: migrate to vite plus

* Disable typeAware and typeCheck

* Update CI

* Fix CI

* Fix test

* Clean

* Run test with vp

* Try revert

* react: false In test

* Fix test

* Revert "Try revert"

This reverts commit 009da10473.

* Update

* Update

* Try revert ci changes

* revert

* Run vp migrate

* Disable typeAware and typeCheck for now

* Better resolve for test

* Use vp dev instead of vite

* Update expect

* Fix NormalizeManifestModuleId

* Try increase timeout

* Update to use vp

* Try new check

* Bring back npx vp

* Migrate CI

* Make next-intl resolvable

* Update

* Update

* Update
2026-03-15 10:50:13 +00:00

164 lines
5.7 KiB
TypeScript

/**
* Tests that CSS imports from node_modules packages don't crash SSR.
*
* When a node_modules package imports a `.css` file, Vite must process it
* through its transform pipeline (not Node's native ESM loader, which can't
* handle non-JS extensions). The `noExternal: true` config ensures this.
*
* Relates to: https://github.com/cloudflare/vinext/issues/270
*/
import { describe, it, expect, beforeAll, afterAll } from "vite-plus/test";
import { createServer, type ViteDevServer } from "vite-plus";
import vinext from "../packages/vinext/src/index.js";
import path from "node:path";
import fs from "node:fs/promises";
import os from "node:os";
import { APP_FIXTURE_DIR, startFixtureServer, fetchHtml } from "./helpers.js";
// ── App Router: node_modules CSS import ─────────────────────
describe("node_modules CSS import (App Router)", () => {
let server: ViteDevServer;
let baseUrl: string;
beforeAll(async () => {
({ server, baseUrl } = await startFixtureServer(APP_FIXTURE_DIR, {
appRouter: true,
}));
// Warm up
await fetch(`${baseUrl}/`).catch(() => {});
}, 60_000);
afterAll(async () => {
await server?.close();
});
it("renders page that imports CSS from node_modules without crashing", async () => {
const { res, html } = await fetchHtml(baseUrl, "/nextjs-compat/node-modules-css");
expect(res.status).toBe(200);
expect(html).toContain("node-modules-css-works");
expect(html).toContain("fake-css-lib-rendered");
expect(html).toContain("fake-css-module-rendered");
});
});
// ── Pages Router: node_modules CSS import ────────────────────
describe("node_modules CSS import (Pages Router)", () => {
let server: ViteDevServer;
let baseUrl: string;
let tmpDir: string;
beforeAll(async () => {
tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "vinext-nm-css-pages-"));
// Create a real node_modules dir (not a symlink, to avoid mutating workspace)
const nmDir = path.join(tmpDir, "node_modules");
await fs.mkdir(nmDir, { recursive: true });
// Symlink only the packages this test actually needs from the repo root.
// Symlinking only what's needed (rather than all entries) keeps the test
// hermetic and avoids touching unrelated packages.
const rootNodeModules = path.resolve(import.meta.dirname, "../node_modules");
const requiredPackages = ["react", "react-dom", "vite", "@vitejs", "vinext"];
for (const pkg of requiredPackages) {
const src = path.join(rootNodeModules, pkg);
const dest = path.join(nmDir, pkg);
await fs.symlink(src, dest, "junction").catch((err) => {
if (err.code !== "EEXIST") throw err;
});
}
// Create fake packages that import .css and .module.css
const fakeCssDir = path.join(nmDir, "fake-css-lib");
await fs.mkdir(fakeCssDir, { recursive: true });
await fs.writeFile(
path.join(fakeCssDir, "package.json"),
JSON.stringify({ name: "fake-css-lib", version: "1.0.0", type: "module", main: "index.js" }),
);
await fs.writeFile(path.join(fakeCssDir, "styles.css"), `.fake-css-lib { color: red; }`);
await fs.writeFile(
path.join(fakeCssDir, "index.js"),
`import "./styles.css";\nexport function FakeComponent() { return "fake-css-lib-rendered"; }\n`,
);
const fakeCssModuleDir = path.join(nmDir, "fake-css-module-lib");
await fs.mkdir(fakeCssModuleDir, { recursive: true });
await fs.writeFile(
path.join(fakeCssModuleDir, "package.json"),
JSON.stringify({
name: "fake-css-module-lib",
version: "1.0.0",
type: "module",
main: "index.js",
}),
);
await fs.writeFile(
path.join(fakeCssModuleDir, "styles.module.css"),
`.item { font-weight: bold; }`,
);
await fs.writeFile(
path.join(fakeCssModuleDir, "index.js"),
`import classes from "./styles.module.css";\nexport const styles = classes;\nexport function ModComponent() { return "fake-css-module-rendered"; }\n`,
);
// Pages Router structure
const pagesDir = path.join(tmpDir, "pages");
await fs.mkdir(pagesDir, { recursive: true });
await fs.writeFile(
path.join(pagesDir, "index.tsx"),
`import { FakeComponent } from "fake-css-lib";
import { ModComponent } from "fake-css-module-lib";
export default function Page() {
return (
<div>
<h1 id="nm-css-test">node-modules-css-pages-works</h1>
<p>{FakeComponent()}</p>
<p>{ModComponent()}</p>
</div>
);
}`,
);
// `appDir` points to the tmp project root. For a Pages Router test (no
// `app/` directory) this just gives vinext a root to resolve from; the
// plugin ignores the absence of `app/`.
const plugins: any[] = [vinext({ appDir: tmpDir })];
server = await createServer({
root: tmpDir,
configFile: false,
plugins,
optimizeDeps: { holdUntilCrawlEnd: true },
server: { port: 0, cors: false },
logLevel: "silent",
});
await server.listen();
const addr = server.httpServer?.address();
if (addr && typeof addr === "object") {
baseUrl = `http://localhost:${addr.port}`;
}
// Warm up
await fetch(`${baseUrl}/`).catch(() => {});
}, 60_000);
afterAll(async () => {
await server?.close();
await fs.rm(tmpDir, { recursive: true, force: true }).catch(() => {});
});
it("renders page that imports CSS and CSS modules from node_modules without crashing", async () => {
const res = await fetch(`${baseUrl}/`);
expect(res.status).toBe(200);
const html = await res.text();
expect(html).toContain("node-modules-css-pages-works");
expect(html).toContain("fake-css-lib-rendered");
expect(html).toContain("fake-css-module-rendered");
});
});