Skip to content

Commit 051bc30

Browse files
arHSMascorbic
andauthored
feat(vite-server): handle vite virtual module ids (#13902)
* feat(vite-server): handle vite virtual module ids fixes #13879 * feat(e2e): add tests * chore(changeset): create a changeset --------- Co-authored-by: Matt Kane <[email protected]>
1 parent 8246bcc commit 051bc30

File tree

9 files changed

+121
-3
lines changed

9 files changed

+121
-3
lines changed

.changeset/open-melons-start.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'astro': patch
3+
---
4+
5+
Fixes a bug where vite virtual module ids were incorrectly added in the dev server
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { defineConfig } from 'astro/config';
2+
3+
import virtual from "./src/plugins/virtual";
4+
5+
// https://astro.build/config
6+
export default defineConfig({
7+
vite: {
8+
plugins: [virtual],
9+
},
10+
});
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"name": "@e2e/vite-virtual-modules",
3+
"type": "module",
4+
"version": "0.0.0",
5+
"private": true,
6+
"dependencies": {
7+
"astro": "workspace:*"
8+
}
9+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
import "virtual:dynamic.css";
3+
---
4+
5+
<html lang="en">
6+
<head>
7+
<!-- Head Stuff -->
8+
</head>
9+
<body>
10+
<h1>Astro</h1>
11+
</body>
12+
</html>
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import type { Plugin } from "vite";
2+
3+
const VIRTUAL_MODULE_ID = "virtual:dynamic.css";
4+
const RESOLVED_VIRTUAL_MODULE_ID = `\0${VIRTUAL_MODULE_ID}`;
5+
6+
export default {
7+
name: VIRTUAL_MODULE_ID,
8+
resolveId(source) {
9+
if (!source.startsWith(VIRTUAL_MODULE_ID)) return;
10+
11+
return RESOLVED_VIRTUAL_MODULE_ID;
12+
},
13+
load(id) {
14+
if (!id.startsWith(RESOLVED_VIRTUAL_MODULE_ID)) return;
15+
16+
return "body { background: red; }";
17+
},
18+
} satisfies Plugin;
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { expect } from '@playwright/test';
2+
import { testFactory } from './test-utils.js';
3+
4+
const test = testFactory(import.meta.url, { root: './fixtures/vite-virtual-modules/' });
5+
const VIRTUAL_MODULE_ID = '/@id/__x00__virtual:dynamic.css';
6+
7+
let devServer;
8+
9+
test.beforeAll(async ({ astro }) => {
10+
devServer = await astro.startDevServer();
11+
});
12+
13+
test.afterAll(async () => {
14+
await devServer.stop();
15+
});
16+
17+
/**
18+
*
19+
* @param {import("@playwright/test").Page} page
20+
* @param {string} element
21+
* @param {string} attribute
22+
* @returns {Promise<import("@playwright/test").Locator>}
23+
*/
24+
async function getElemForVirtual(page, element, attribute) {
25+
const elements = await page.locator(element).all();
26+
27+
for (const elem of elements) {
28+
const attr = await elem.getAttribute(attribute);
29+
30+
if (attr !== VIRTUAL_MODULE_ID) continue;
31+
32+
return elem;
33+
}
34+
}
35+
36+
test.describe('Vite Virtual Modules', () => {
37+
test('contains style tag with virtual module id', async ({ page, astro }) => {
38+
await page.goto(astro.resolveUrl('/'));
39+
40+
const style = await getElemForVirtual(page, 'style', 'data-vite-dev-id');
41+
42+
expect(style).not.toBeUndefined();
43+
});
44+
45+
test('contains script tag with virtual module id', async ({ page, astro }) => {
46+
await page.goto(astro.resolveUrl('/'));
47+
48+
const script = await getElemForVirtual(page, 'script', 'src');
49+
50+
expect(script).not.toBeUndefined();
51+
});
52+
});

packages/astro/src/core/util.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ export function viteID(filePath: URL): string {
101101

102102
export const VALID_ID_PREFIX = `/@id/`;
103103
const NULL_BYTE_PLACEHOLDER = `__x00__`;
104+
const NULL_BYTE_REGEX = /^\0/;
104105

105106
// Strip valid id prefix and replace null byte placeholder. Both are prepended to resolved ids
106107
// as they are not valid browser import specifiers (by the Vite's importAnalysis plugin)
@@ -110,6 +111,11 @@ export function unwrapId(id: string): string {
110111
: id;
111112
}
112113

114+
// Reverses `unwrapId` function
115+
export function wrapId(id: string): string {
116+
return id.replace(NULL_BYTE_REGEX, `${VALID_ID_PREFIX}${NULL_BYTE_PLACEHOLDER}`);
117+
}
118+
113119
export function resolvePages(config: AstroConfig) {
114120
return new URL('./pages', config.srcDir);
115121
}

packages/astro/src/vite-plugin-astro-server/css.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { ModuleLoader } from '../core/module-loader/index.js';
2-
import { viteID } from '../core/util.js';
2+
import { viteID, wrapId } from '../core/util.js';
33
import { isBuildableCSSRequest } from './util.js';
44
import { crawlGraph } from './vite.js';
55

@@ -55,8 +55,8 @@ export async function getStylesForURL(
5555
}
5656

5757
importedStylesMap.set(importedModule.url, {
58-
id: importedModule.id ?? importedModule.url,
59-
url: importedModule.url,
58+
id: wrapId(importedModule.id ?? importedModule.url),
59+
url: wrapId(importedModule.url),
6060
content: css,
6161
});
6262
}

pnpm-lock.yaml

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)