Skip to content

Commit 3268cd4

Browse files
committed
WIP
1 parent ee89769 commit 3268cd4

10 files changed

Lines changed: 135 additions & 78 deletions

File tree

.changeset/eleven-maps-like.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'@vanilla-extract/turbopack-plugin': patch
3+
'@vanilla-extract/vite-plugin': patch
4+
'@vanilla-extract/compiler': patch
5+
---
6+
7+
WIP

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
"test:unit": "vitest run",
1717
"test:playwright": "pnpm test:clean-next && pnpm test:build-next && playwright test",
1818
"test:clean-next": "pnpm --filter=@fixtures/next-* run '/^clean.*/'",
19-
"test:build-next": "node scripts/copy-next-plugin.ts && pnpm --filter=@fixtures/next-* run '/^build.*/'",
19+
"test:build-next": "node scripts/copy-next-plugin.ts && pnpm --filter=@fixtures/next-16-app-pages-router run '/^build.*/'",
2020
"format": "oxlint --fix && prettier --write .",
2121
"lint": "pnpm run --no-bail '/^lint:.*/'",
2222
"lint:manypkg": "manypkg check",

packages/compiler/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
"dependencies": {
1919
"@vanilla-extract/css": "workspace:^",
2020
"@vanilla-extract/integration": "workspace:^",
21-
"vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0",
22-
"vite-node": "^3.2.2 || ^5.0.0 || ^6.0.0"
21+
"vite": "^7.0.0 || ^8.0.0",
22+
"vite-node": "^5.0.0 || ^6.0.0"
2323
}
2424
}

packages/compiler/src/compiler.ts

Lines changed: 57 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,14 @@ import assert from 'assert';
22
import { join, isAbsolute } from 'path';
33
import type { Adapter } from '@vanilla-extract/css';
44
import { transformCss } from '@vanilla-extract/css/transformCss';
5-
import type { ModuleNode, UserConfig as ViteUserConfig } from 'vite';
5+
import {
6+
type ModuleNode,
7+
type UserConfig as ViteUserConfig,
8+
type ViteDevServer,
9+
createServer,
10+
createServerModuleRunner,
11+
} from 'vite';
12+
import { type ModuleRunner, EvaluatedModules } from 'vite/module-runner';
613

714
import {
815
cssFileFilter,
@@ -89,6 +96,11 @@ const createModuleScanner = () => {
8996
return scanModule;
9097
};
9198

99+
type Context = {
100+
server: ViteDevServer;
101+
runner: ModuleRunner;
102+
};
103+
92104
const createViteServer = async ({
93105
root,
94106
identifiers,
@@ -97,11 +109,10 @@ const createViteServer = async ({
97109
}: Required<
98110
Pick<CreateCompilerOptions, 'root' | 'identifiers' | 'viteConfig'>
99111
> &
100-
Pick<CreateCompilerOptions, 'enableFileWatcher'>) => {
112+
Pick<CreateCompilerOptions, 'enableFileWatcher'>): Promise<Context> => {
101113
const pkg = getPackageInfo(root);
102-
const vite = await import('vite');
103114

104-
const server = await vite.createServer({
115+
const server = await createServer({
105116
...viteConfig,
106117
// The vite-node server should not rewrite imported asset URLs within VE stylesheets.
107118
// Doing so interferes with Vite's resolution and bundling of these assets at build time.
@@ -111,6 +122,7 @@ const createViteServer = async ({
111122
// Don't include HTML middlewares
112123
appType: 'custom',
113124
server: {
125+
preTransformRequests: false,
114126
middlewareMode: viteConfig.server?.middlewareMode,
115127
hmr: false,
116128
watch: enableFileWatcher ? viteConfig.server?.watch : null,
@@ -129,7 +141,12 @@ const createViteServer = async ({
129141
assetsInlineLimit: viteConfig.build?.assetsInlineLimit,
130142
},
131143
ssr: {
132-
noExternal: true,
144+
// `createServerModuleRunner` evaluates modules as ESM (`AsyncFunction`). Forcing every
145+
// dependency through the SSR transform (`noExternal: true`) executes arbitrary CJS in that
146+
// context (`module is not defined`). Externalize `node_modules` by default and only pull
147+
// Vanilla Extract packages through Vite.
148+
external: true,
149+
noExternal: [/^@vanilla-extract\//, /^@emotion\//],
133150
},
134151
plugins: [
135152
{
@@ -169,36 +186,18 @@ const createViteServer = async ({
169186
// this is need to initialize the plugins
170187
await server.pluginContainer.buildStart({});
171188

172-
const { ViteNodeRunner } = await import('vite-node/client');
173-
const { ViteNodeServer } = await import('vite-node/server');
174-
175-
const node = new ViteNodeServer(server);
176-
177-
class ViteNodeRunnerWithContext extends ViteNodeRunner {
178-
cssAdapter: Adapter | undefined;
179-
180-
prepareContext(context: Record<string, any>): Record<string, any> {
181-
return {
182-
...super.prepareContext(context),
183-
[globalAdapterIdentifier]: this.cssAdapter,
184-
};
185-
}
186-
}
187-
188-
const runner = new ViteNodeRunnerWithContext({
189-
root,
190-
base: server.config.base,
191-
fetchModule(id) {
192-
return node.fetchModule(id);
193-
},
194-
resolveId(id, importer) {
195-
return node.resolveId(id, importer);
196-
},
189+
const ssr = server.environments.ssr;
190+
const runner = createServerModuleRunner(ssr, {
191+
hmr: false,
192+
sourcemapInterceptor: false,
197193
});
198194

199195
if (enableFileWatcher) {
200196
server.watcher.on('change', (filePath) => {
201-
runner.moduleCache.invalidateDepTree([filePath]);
197+
const mod = runner.evaluatedModules.getModuleById(filePath);
198+
if (mod) {
199+
runner.evaluatedModules.invalidateModule(mod);
200+
}
202201
});
203202
}
204203

@@ -407,12 +406,20 @@ export const createCompiler = ({
407406

408407
const { fileExports, cssImports, watchFiles, lastInvalidationTimestamp } =
409408
await lock(async () => {
410-
runner.cssAdapter = cssAdapter;
411-
412-
const fileExports = (await runner.executeFile(filePath)) as Record<
413-
string,
414-
unknown
415-
>;
409+
const cache = new EvaluatedModules();
410+
runner.evaluatedModules = cache;
411+
412+
const globalForAdapter = globalThis as typeof globalThis &
413+
Record<typeof globalAdapterIdentifier, Adapter | undefined>;
414+
globalForAdapter[globalAdapterIdentifier] = cssAdapter;
415+
416+
let fileExports: Record<string, unknown>;
417+
try {
418+
fileExports =
419+
await runner.import<Record<string, unknown>>(filePath);
420+
} finally {
421+
delete globalForAdapter[globalAdapterIdentifier];
422+
}
416423

417424
const moduleId = normalizePath(filePath);
418425
const moduleNode = server.moduleGraph.getModuleById(moduleId);
@@ -517,9 +524,13 @@ export const createCompiler = ({
517524
async unstable_invalidateAllModules() {
518525
const { server, runner } = await vitePromise;
519526

520-
for (const [key] of runner.moduleCache.entries()) {
521-
if (!key.includes('node_modules')) {
522-
runner.moduleCache.delete(key);
527+
const evaluatedIds = runner.evaluatedModules.idToModuleMap.keys();
528+
for (const id of evaluatedIds) {
529+
if (!id.includes('node_modules')) {
530+
const mod = runner.evaluatedModules.getModuleById(id);
531+
if (mod) {
532+
runner.evaluatedModules.invalidateModule(mod);
533+
}
523534
}
524535
}
525536

@@ -545,9 +556,13 @@ export const createCompiler = ({
545556
};
546557
},
547558
async close() {
548-
const { server } = await vitePromise;
559+
const { server, runner } = await vitePromise;
549560

550-
await server.close();
561+
try {
562+
await runner.close();
563+
} finally {
564+
await server.close();
565+
}
551566
},
552567
getAllCss() {
553568
let allCss = '';

packages/turbopack-plugin/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,14 @@
2121
"author": "SEEK",
2222
"license": "MIT",
2323
"dependencies": {
24+
"@rolldown/pluginutils": "1.0.0-rc.11",
2425
"@swc/core": "^1.13.5",
2526
"@vanilla-extract/compiler": "workspace:^",
2627
"@vanilla-extract/integration": "workspace:^"
2728
},
2829
"devDependencies": {
2930
"next": "12.3.4",
30-
"vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0"
31+
"vite": "^7.0.0 || ^8.0.0"
3132
},
3233
"peerDependencies": {
3334
"next": ">=12.1.7"

packages/turbopack-plugin/src/index.ts

Lines changed: 48 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import * as path from 'node:path';
1111
import { createNextFontVePlugin } from './next-font/plugin';
1212
import type fs from 'node:fs';
1313
import { injectFontImports } from './next-font/inject';
14+
import { prefixRegex } from '@rolldown/pluginutils';
1415

1516
export type TurboLoaderContext<OptionsType> = {
1617
getOptions: {
@@ -58,6 +59,10 @@ const getOrMakeCompiler = async ({
5859
}): Promise<VeCompiler> => {
5960
if (sharedCompiler) return sharedCompiler;
6061

62+
// const nextImageVirtualModulePrefix = 'virtual:vanilla-extract-next-image?';
63+
const resolvedNextImageVirtualModulePrefix =
64+
'\0virtual:vanilla-extract-next-image?';
65+
6166
const defineEnv: Record<string, string> = {};
6267
for (const [key, value] of Object.entries(nextEnv ?? {})) {
6368
defineEnv[`process.env.${key}`] = JSON.stringify(value);
@@ -81,18 +86,22 @@ const getOrMakeCompiler = async ({
8186
// route vite file reads through turbopack's fs to handle dependency tracking automatically
8287
name: 'vanilla-extract-turbo-fs',
8388
enforce: 'pre',
84-
async load(id: string) {
85-
return new Promise((resolve, reject) => {
86-
// we can reuse this fs instance across all loader calls
87-
// turbopack will associate dependencies to the file currently being loaded
88-
loaderContext.fs.readFile(id, (error, data) => {
89-
if (error) {
90-
reject(error);
91-
} else if (typeof data === 'string') {
92-
resolve({ code: data });
93-
} else resolve(null);
89+
load: {
90+
// oxlint-disable-next-line no-control-regex No way around this I think
91+
filter: { id: { exclude: /^\0/ } },
92+
async handler(id: string) {
93+
return new Promise((resolve, reject) => {
94+
// we can reuse this fs instance across all loader calls
95+
// turbopack will associate dependencies to the file currently being loaded
96+
loaderContext.fs.readFile(id, (error, data) => {
97+
if (error) {
98+
reject(error);
99+
} else if (typeof data === 'string') {
100+
resolve({ code: data });
101+
} else resolve(null);
102+
});
94103
});
95-
});
104+
},
96105
},
97106
},
98107
{
@@ -112,27 +121,47 @@ const getOrMakeCompiler = async ({
112121
source.endsWith('.ico') ||
113122
source.endsWith('.bmp')
114123
) {
115-
const sourceImage = path.isAbsolute(source)
116-
? path.join(loaderContext.rootContext, source)
117-
: path.join(path.dirname(importer), source);
124+
return (
125+
resolvedNextImageVirtualModulePrefix +
126+
new URLSearchParams({ spec: source, importer }).toString()
127+
);
128+
}
129+
130+
return null;
131+
},
132+
load: {
133+
filter: { id: prefixRegex(resolvedNextImageVirtualModulePrefix) },
134+
async handler(id: string) {
135+
const params = new URLSearchParams(
136+
id.slice(resolvedNextImageVirtualModulePrefix.length),
137+
);
138+
const spec = params.get('spec');
139+
const importer = params.get('importer');
140+
if (!spec || !importer) {
141+
return null;
142+
}
143+
144+
const sourceImage = path.isAbsolute(spec)
145+
? path.join(loaderContext.rootContext, spec)
146+
: path.join(path.dirname(importer), spec);
118147

119148
// since we'll be using the image in our final css file, we must craft an import path that will resolve to the image file from the css file
120149
const referenceFile = require.resolve(
121-
'@vanilla-extract/css/vanilla.virtual.css?ve-css=unknown',
150+
'@vanilla-extract/css/vanilla.virtual.css?ve-css=placeholder',
122151
{ paths: [importer] },
123152
);
124153
const relativeImport = path.relative(
125154
path.dirname(referenceFile),
126155
sourceImage,
127156
);
128157

129-
// determine the dimensions of the image
130158
const imageAsBuffer = new Promise<Buffer>((resolve, reject) => {
131159
loaderContext.fs.readFile(sourceImage, (error, data) => {
132160
if (error) reject(error);
133161
resolve(data);
134162
});
135163
});
164+
136165
const { getImageSize } =
137166
await import('next/dist/server/image-optimizer.js');
138167
const imageSize: { width?: number; height?: number } =
@@ -142,7 +171,7 @@ const getOrMakeCompiler = async ({
142171
throw new Error(message);
143172
});
144173

145-
const moduleContent = `export default {
174+
const code = `export default {
146175
src: '${relativeImport}',
147176
height: ${imageSize.height},
148177
width: ${imageSize.width},
@@ -151,13 +180,8 @@ const getOrMakeCompiler = async ({
151180
blurHeight: undefined,
152181
}`;
153182

154-
return (
155-
'data:text/javascript;base64,' +
156-
Buffer.from(moduleContent).toString('base64')
157-
);
158-
}
159-
160-
return null;
183+
return { code };
184+
},
161185
},
162186
},
163187
{

packages/vite-plugin/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@
2020
"@vanilla-extract/integration": "workspace:^"
2121
},
2222
"devDependencies": {
23-
"vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0"
23+
"vite": "^7.0.0 || ^8.0.0"
2424
},
2525
"peerDependencies": {
26-
"vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0"
26+
"vite": "^7.0.0 || ^8.0.0"
2727
}
2828
}

0 commit comments

Comments
 (0)