diff --git a/.changeset/cool-rats-poke.md b/.changeset/cool-rats-poke.md
new file mode 100644
index 000000000..8ccfd0160
--- /dev/null
+++ b/.changeset/cool-rats-poke.md
@@ -0,0 +1,9 @@
+---
+'@vanilla-extract/esbuild-plugin': minor
+'@vanilla-extract/rollup-plugin': minor
+'@vanilla-extract/sprinkles': minor
+'@vanilla-extract/vite-plugin': minor
+---
+
+makes changes to the plugins integration (vite/webpack/esbuild/rollup) by allowing to pass those new callbacks to the `processVanillaFile` call
+also exports the ProcessVanillaFileOptions & SprinklesProperties type
diff --git a/.changeset/great-grapes-complain.md b/.changeset/great-grapes-complain.md
new file mode 100644
index 000000000..f7d5396f2
--- /dev/null
+++ b/.changeset/great-grapes-complain.md
@@ -0,0 +1,6 @@
+---
+'@vanilla-extract/css': patch
+---
+
+fix(css): opti getDevPrefix by removing filePath regex
+which was especially slow when used against filePath located somewhere in node_modules
diff --git a/.changeset/moody-ears-study.md b/.changeset/moody-ears-study.md
new file mode 100644
index 000000000..06ef9da29
--- /dev/null
+++ b/.changeset/moody-ears-study.md
@@ -0,0 +1,5 @@
+---
+'@vanilla-extract/vite-plugin': minor
+---
+
+allows customizing the forceEmitCssInSsrBuild option for the vite plugin (needed to make the HMR work with SSR frameworks for those not hard-coded, like vite-plugin-ssr)
diff --git a/.changeset/perfect-rocks-check.md b/.changeset/perfect-rocks-check.md
new file mode 100644
index 000000000..a4197a395
--- /dev/null
+++ b/.changeset/perfect-rocks-check.md
@@ -0,0 +1,7 @@
+---
+'@vanilla-extract/integration': minor
+---
+
+creates an AdapterContext (which is just a reference to the result of the adapter after the evalCode of processVanillaFile so anyone can retrieve the generated class names / CSS rules by file scopes
+
+adding a onEvaluated callback to processVanillaFile to retrieve the resulting mapping of classNames by property+value+condition using the AdapterContext
diff --git a/.changeset/strong-windows-fail.md b/.changeset/strong-windows-fail.md
new file mode 100644
index 000000000..186d061ef
--- /dev/null
+++ b/.changeset/strong-windows-fail.md
@@ -0,0 +1,6 @@
+---
+'@vanilla-extract/integration': minor
+---
+
+adding a serializeVanillaModule callback like the current serializeVirtualCssPath callback, it allows customizing the behaviour
+also exports the default implementation of serializeVanillaModule
diff --git a/.changeset/tidy-gifts-tap.md b/.changeset/tidy-gifts-tap.md
new file mode 100644
index 000000000..7a6f73792
--- /dev/null
+++ b/.changeset/tidy-gifts-tap.md
@@ -0,0 +1,5 @@
+---
+'@vanilla-extract/vite-plugin': minor
+---
+
+fix HMR
diff --git a/.changeset/twelve-suns-dream.md b/.changeset/twelve-suns-dream.md
new file mode 100644
index 000000000..259cd7628
--- /dev/null
+++ b/.changeset/twelve-suns-dream.md
@@ -0,0 +1,5 @@
+---
+'@vanilla-extract/vite-plugin': minor
+---
+
+using raw .css.ts from another package than config.root
diff --git a/packages/css/src/identifier.ts b/packages/css/src/identifier.ts
index 5dfe5b1ad..2bda08b05 100644
--- a/packages/css/src/identifier.ts
+++ b/packages/css/src/identifier.ts
@@ -15,12 +15,12 @@ function getDevPrefix({
if (debugFileName) {
const { filePath } = getFileScope();
- const matches = filePath.match(
- /(?
[^\/\\]*)?[\/\\]?(?[^\/\\]*)\.css\.(ts|js|tsx|jsx|cjs|mjs)$/,
- );
+ const pathParts = filePath.split('/');
+ const dir = pathParts[pathParts.length - 2];
+ const fileNameParts = pathParts[pathParts.length - 1].split('.');
+ const file = fileNameParts.length > 1 ? fileNameParts[0] : undefined;
- if (matches && matches.groups) {
- const { dir, file } = matches.groups;
+ if (dir || file) {
parts.unshift(file && file !== 'index' ? file : dir);
}
}
diff --git a/packages/esbuild-plugin/src/index.ts b/packages/esbuild-plugin/src/index.ts
index c86aa777b..05275faba 100644
--- a/packages/esbuild-plugin/src/index.ts
+++ b/packages/esbuild-plugin/src/index.ts
@@ -9,12 +9,17 @@ import {
vanillaExtractTransformPlugin,
IdentifierOption,
CompileOptions,
+ ProcessVanillaFileOptions,
} from '@vanilla-extract/integration';
import type { Plugin } from 'esbuild';
const vanillaCssNamespace = 'vanilla-extract-css-ns';
-interface VanillaExtractPluginOptions {
+interface VanillaExtractPluginOptions
+ extends Pick<
+ ProcessVanillaFileOptions,
+ 'onEvaluated' | 'serializeVanillaModule'
+ > {
outputCss?: boolean;
/**
* @deprecated Use `esbuildOptions.external` instead.
@@ -32,6 +37,8 @@ export function vanillaExtractPlugin({
processCss,
identifiers,
esbuildOptions,
+ onEvaluated,
+ serializeVanillaModule,
}: VanillaExtractPluginOptions = {}): Plugin {
if (runtime) {
// If using runtime CSS then just apply fileScopes and debug IDs to code
@@ -96,6 +103,8 @@ export function vanillaExtractPlugin({
filePath: path,
outputCss,
identOption,
+ onEvaluated,
+ serializeVanillaModule,
});
return {
diff --git a/packages/integration/src/index.ts b/packages/integration/src/index.ts
index 9ed56d10d..05b510b2e 100644
--- a/packages/integration/src/index.ts
+++ b/packages/integration/src/index.ts
@@ -1,4 +1,5 @@
export {
+ defaultSerializeVanillaModule,
processVanillaFile,
parseFileScope,
stringifyFileScope,
@@ -15,4 +16,8 @@ export * from './filters';
export type { IdentifierOption } from './types';
export type { PackageInfo } from './packageInfo';
+export type {
+ AdapterContext,
+ ProcessVanillaFileOptions,
+} from './processVanillaFile';
export type { CompileOptions } from './compile';
diff --git a/packages/integration/src/processVanillaFile.ts b/packages/integration/src/processVanillaFile.ts
index 3090710de..c0dc10610 100644
--- a/packages/integration/src/processVanillaFile.ts
+++ b/packages/integration/src/processVanillaFile.ts
@@ -28,7 +28,7 @@ export function parseFileScope(serialisedFileScope: string): FileScope {
};
}
-interface ProcessVanillaFileOptions {
+export interface ProcessVanillaFileOptions {
source: string;
filePath: string;
outputCss?: boolean;
@@ -38,41 +38,66 @@ interface ProcessVanillaFileOptions {
fileScope: FileScope;
source: string;
}) => string | Promise;
+ onEvaluated?: (args: {
+ source: string;
+ context: AdapterContext;
+ evalResult: Record;
+ filePath: string;
+ }) => void;
+ serializeVanillaModule?: (
+ cssImports: Array,
+ exports: Record,
+ context: AdapterContext,
+ filePath: string,
+ ) => string;
+}
+
+export interface AdapterContext {
+ cssByFileScope: Map;
+ localClassNames: Set;
+ composedClassLists: Composition[];
+ usedCompositions: Set;
}
+
+type Css = Parameters[0];
+type Composition = Parameters[0];
+
export async function processVanillaFile({
source,
filePath,
outputCss = true,
identOption = process.env.NODE_ENV === 'production' ? 'short' : 'debug',
serializeVirtualCssPath,
+ serializeVanillaModule,
+ onEvaluated,
}: ProcessVanillaFileOptions) {
- type Css = Parameters[0];
- type Composition = Parameters[0];
-
- const cssByFileScope = new Map>();
- const localClassNames = new Set();
- const composedClassLists: Array = [];
- const usedCompositions = new Set();
+ const context: AdapterContext = {
+ cssByFileScope: new Map>(),
+ localClassNames: new Set(),
+ composedClassLists: [],
+ usedCompositions: new Set(),
+ };
const cssAdapter: Adapter = {
appendCss: (css, fileScope) => {
if (outputCss) {
const serialisedFileScope = stringifyFileScope(fileScope);
- const fileScopeCss = cssByFileScope.get(serialisedFileScope) ?? [];
+ const fileScopeCss =
+ context.cssByFileScope.get(serialisedFileScope) ?? [];
fileScopeCss.push(css);
- cssByFileScope.set(serialisedFileScope, fileScopeCss);
+ context.cssByFileScope.set(serialisedFileScope, fileScopeCss);
}
},
registerClassName: (className) => {
- localClassNames.add(className);
+ context.localClassNames.add(className);
},
registerComposition: (composedClassList) => {
- composedClassLists.push(composedClassList);
+ context.composedClassLists.push(composedClassList);
},
markCompositionUsed: (identifier) => {
- usedCompositions.add(identifier);
+ context.usedCompositions.add(identifier);
},
onEndFileScope: () => {},
getIdentOption: () => identOption,
@@ -96,16 +121,17 @@ export async function processVanillaFile({
{ console, process, __adapter__: cssAdapter },
true,
);
+ onEvaluated?.({ source, context, evalResult, filePath });
process.env.NODE_ENV = currentNodeEnv;
const cssImports = [];
- for (const [serialisedFileScope, fileScopeCss] of cssByFileScope) {
+ for (const [serialisedFileScope, fileScopeCss] of context.cssByFileScope) {
const fileScope = parseFileScope(serialisedFileScope);
const css = transformCss({
- localClassNames: Array.from(localClassNames),
- composedClassLists,
+ localClassNames: Array.from(context.localClassNames),
+ composedClassLists: context.composedClassLists,
cssObjs: fileScopeCss,
}).join('\n');
@@ -148,16 +174,12 @@ export async function processVanillaFile({
true,
);
- const unusedCompositions = composedClassLists
- .filter(({ identifier }) => !usedCompositions.has(identifier))
- .map(({ identifier }) => identifier);
-
- const unusedCompositionRegex =
- unusedCompositions.length > 0
- ? RegExp(`(${unusedCompositions.join('|')})\\s`, 'g')
- : null;
-
- return serializeVanillaModule(cssImports, evalResult, unusedCompositionRegex);
+ return (serializeVanillaModule ?? defaultSerializeVanillaModule)(
+ cssImports,
+ evalResult,
+ context,
+ filePath,
+ );
}
export function stringifyExports(
@@ -246,11 +268,20 @@ export function stringifyExports(
);
}
-function serializeVanillaModule(
+export function defaultSerializeVanillaModule(
cssImports: Array,
exports: Record,
- unusedCompositionRegex: RegExp | null,
+ context: AdapterContext,
) {
+ const unusedCompositions = context.composedClassLists
+ .filter(({ identifier }) => !context.usedCompositions.has(identifier))
+ .map(({ identifier }) => identifier);
+
+ const unusedCompositionRegex =
+ unusedCompositions.length > 0
+ ? RegExp(`(${unusedCompositions.join('|')})\\s`, 'g')
+ : null;
+
const recipeImports = new Set();
const moduleExports = Object.keys(exports).map((key) =>
diff --git a/packages/rollup-plugin/src/index.ts b/packages/rollup-plugin/src/index.ts
index 95c7ab4af..ec139c3b1 100644
--- a/packages/rollup-plugin/src/index.ts
+++ b/packages/rollup-plugin/src/index.ts
@@ -7,12 +7,17 @@ import {
getSourceFromVirtualCssFile,
virtualCssFileFilter,
CompileOptions,
+ ProcessVanillaFileOptions,
} from '@vanilla-extract/integration';
import { posix } from 'path';
const { relative, normalize, dirname } = posix;
-interface Options {
+interface Options
+ extends Pick<
+ ProcessVanillaFileOptions,
+ 'onEvaluated' | 'serializeVanillaModule'
+ > {
identifiers?: IdentifierOption;
cwd?: string;
esbuildOptions?: CompileOptions['esbuildOptions'];
@@ -21,6 +26,8 @@ export function vanillaExtractPlugin({
identifiers,
cwd = process.cwd(),
esbuildOptions,
+ onEvaluated,
+ serializeVanillaModule,
}: Options = {}): Plugin {
const emittedFiles = new Map();
const isProduction = process.env.NODE_ENV === 'production';
@@ -55,6 +62,8 @@ export function vanillaExtractPlugin({
source,
filePath,
identOption,
+ onEvaluated,
+ serializeVanillaModule,
});
return {
code: output,
diff --git a/packages/sprinkles/src/index.ts b/packages/sprinkles/src/index.ts
index b082881d3..98581a60e 100644
--- a/packages/sprinkles/src/index.ts
+++ b/packages/sprinkles/src/index.ts
@@ -12,6 +12,7 @@ import {
createSprinkles as internalCreateSprinkles,
} from './createSprinkles';
import { SprinklesProperties, ResponsiveArrayConfig } from './types';
+export type { SprinklesProperties } from './types';
export { createNormalizeValueFn, createMapValueFn } from './createUtils';
export type { ConditionalValue, RequiredConditionalValue } from './createUtils';
diff --git a/packages/vite-plugin/src/index.ts b/packages/vite-plugin/src/index.ts
index 21195e75d..06271e0ae 100644
--- a/packages/vite-plugin/src/index.ts
+++ b/packages/vite-plugin/src/index.ts
@@ -10,6 +10,7 @@ import {
IdentifierOption,
getPackageInfo,
CompileOptions,
+ ProcessVanillaFileOptions,
transform,
} from '@vanilla-extract/integration';
import { PostCSSConfigResult, resolvePostcssConfig } from './postcss';
@@ -19,22 +20,32 @@ const styleUpdateEvent = (fileId: string) =>
const virtualExtCss = '.vanilla.css';
const virtualExtJs = '.vanilla.js';
+const virtualRE = /.vanilla.(css|js)$/;
-interface Options {
+export interface VanillaExtractPluginOptions
+ extends Pick<
+ ProcessVanillaFileOptions,
+ 'onEvaluated' | 'serializeVanillaModule'
+ > {
identifiers?: IdentifierOption;
esbuildOptions?: CompileOptions['esbuildOptions'];
+ forceEmitCssInSsrBuild?: boolean;
}
export function vanillaExtractPlugin({
identifiers,
esbuildOptions,
-}: Options = {}): Plugin {
+ onEvaluated,
+ serializeVanillaModule,
+ forceEmitCssInSsrBuild: _forceEmitCssInSsrBuild,
+}: VanillaExtractPluginOptions = {}): Plugin {
let config: ResolvedConfig;
let server: ViteDevServer;
let postCssConfig: PostCSSConfigResult | null;
const cssMap = new Map();
- let forceEmitCssInSsrBuild: boolean = !!process.env.VITE_RSC_BUILD;
- let packageName: string;
+ let forceEmitCssInSsrBuild: boolean =
+ _forceEmitCssInSsrBuild || !!process.env.VITE_RSC_BUILD;
+ let packageInfos: ReturnType;
const getAbsoluteVirtualFileId = (source: string) =>
normalizePath(path.join(config.root, source));
@@ -62,7 +73,7 @@ export function vanillaExtractPlugin({
},
async configResolved(resolvedConfig) {
config = resolvedConfig;
- packageName = getPackageInfo(config.root).name;
+ packageInfos = getPackageInfo(config.root);
if (config.command === 'serve') {
postCssConfig = await resolvePostcssConfig(config);
@@ -78,12 +89,39 @@ export function vanillaExtractPlugin({
forceEmitCssInSsrBuild = true;
}
},
- resolveId(source) {
+ // Re-parse .css.ts files when they change
+ async handleHotUpdate({ file, modules }) {
+ if (!cssFileFilter.test(file)) return;
+ try {
+ const virtuals: any[] = [];
+ const invalidate = (type: string) => {
+ const found = server.moduleGraph.getModulesByFile(`${file}${type}`);
+ found?.forEach((m) => {
+ virtuals.push(m);
+ return server.moduleGraph.invalidateModule(m);
+ });
+ };
+ invalidate(virtualExtCss);
+ invalidate(virtualExtJs);
+ // load new CSS
+ await server.ssrLoadModule(file);
+ return [...modules, ...virtuals];
+ } catch (e) {
+ // eslint-disable-next-line no-console
+ console.error(e);
+ throw e;
+ }
+ },
+ // Convert .vanilla.(js|css) URLs to their absolute version
+ resolveId(source, importer) {
const [validId, query] = source.split('?');
if (!validId.endsWith(virtualExtCss) && !validId.endsWith(virtualExtJs)) {
return;
}
+ // while source is .css.ts.vanilla.(js|css), the importer should always be a .css.ts or .html file
+ if (!importer) return;
+
// Absolute paths seem to occur often in monorepos, where files are
// imported from outside the config root.
const absoluteId = source.startsWith(config.root)
@@ -93,18 +131,25 @@ export function vanillaExtractPlugin({
// There should always be an entry in the `cssMap` here.
// The only valid scenario for a missing one is if someone had written
// a file in their app using the .vanilla.js/.vanilla.css extension
- if (cssMap.has(absoluteId)) {
- // Keep the original query string for HMR.
- return absoluteId + (query ? `?${query}` : '');
- }
+ // Keep the original query string for HMR.
+ return absoluteId + (query ? `?${query}` : '');
},
- load(id) {
+ // Provide virtual CSS content
+ async load(id) {
const [validId] = id.split('?');
- if (!cssMap.has(validId)) {
+ if (!virtualRE.test(validId)) {
return;
}
+ if (!cssMap.has(validId)) {
+ // Try to parse the parent
+ const parentId = validId.replace(virtualRE, '');
+ await server.ssrLoadModule(parentId);
+ // Now we should have the CSS
+ if (!cssMap.has(validId)) return;
+ }
+
const css = cssMap.get(validId);
if (typeof css !== 'string') {
@@ -150,19 +195,25 @@ export function vanillaExtractPlugin({
ssr = ssrParam?.ssr;
}
+ let filePackageInfos = packageInfos;
+ const fileDirectory = path.dirname(validId);
+ if (!isSubDir(packageInfos.dirname, fileDirectory)) {
+ filePackageInfos = getPackageInfo(fileDirectory);
+ }
+
if (ssr && !forceEmitCssInSsrBuild) {
return transform({
source: code,
filePath: normalizePath(validId),
- rootPath: config.root,
- packageName,
+ rootPath: filePackageInfos.dirname,
+ packageName: filePackageInfos.name,
identOption,
});
}
const { source, watchFiles } = await compile({
filePath: validId,
- cwd: config.root,
+ cwd: filePackageInfos.dirname,
esbuildOptions,
identOption,
});
@@ -179,9 +230,11 @@ export function vanillaExtractPlugin({
source,
filePath: validId,
identOption,
+ onEvaluated,
+ serializeVanillaModule,
serializeVirtualCssPath: async ({ fileScope, source }) => {
const rootRelativeId = `${fileScope.filePath}${
- config.command === 'build' || (ssr && forceEmitCssInSsrBuild)
+ config.command === 'build' || forceEmitCssInSsrBuild
? virtualExtCss
: virtualExtJs
}`;
@@ -204,7 +257,7 @@ export function vanillaExtractPlugin({
if (
server &&
cssMap.has(absoluteId) &&
- cssMap.get(absoluteId) !== source
+ cssMap.get(absoluteId) !== cssSource
) {
const { moduleGraph } = server;
const [module] = Array.from(
@@ -241,3 +294,10 @@ export function vanillaExtractPlugin({
},
};
}
+
+function isSubDir(parent: string, dir: string) {
+ const relative = path.relative(parent, dir);
+ return Boolean(
+ relative && !relative.startsWith('..') && !path.isAbsolute(relative),
+ );
+}
diff --git a/packages/webpack-plugin/src/index.ts b/packages/webpack-plugin/src/index.ts
index beebad067..b9351d63a 100644
--- a/packages/webpack-plugin/src/index.ts
+++ b/packages/webpack-plugin/src/index.ts
@@ -1,4 +1,8 @@
-import { cssFileFilter, IdentifierOption } from '@vanilla-extract/integration';
+import {
+ cssFileFilter,
+ IdentifierOption,
+ ProcessVanillaFileOptions,
+} from '@vanilla-extract/integration';
import type { Compiler, RuleSetRule } from 'webpack';
import { ChildCompiler } from './compiler';
@@ -58,6 +62,8 @@ export class VanillaExtractPlugin {
allowRuntime: boolean;
childCompiler: ChildCompiler;
identifiers?: IdentifierOption;
+ onEvaluated?: ProcessVanillaFileOptions['onEvaluated'];
+ serializeVanillaModule?: ProcessVanillaFileOptions['onEvaluated'];
constructor(options: PluginOptions = {}) {
const {
@@ -95,6 +101,8 @@ export class VanillaExtractPlugin {
outputCss: this.outputCss,
childCompiler: this.childCompiler,
identifiers: this.identifiers,
+ onEvaluated: this.onEvaluated,
+ serializeVanillaModule: this.serializeVanillaModule,
},
},
],
diff --git a/packages/webpack-plugin/src/loader.ts b/packages/webpack-plugin/src/loader.ts
index 0812b6838..a9cf1b003 100644
--- a/packages/webpack-plugin/src/loader.ts
+++ b/packages/webpack-plugin/src/loader.ts
@@ -7,6 +7,7 @@ import {
transform,
serializeCss,
getPackageInfo,
+ ProcessVanillaFileOptions,
} from '@vanilla-extract/integration';
import type { LoaderContext } from './types';
@@ -25,7 +26,11 @@ const emptyCssExtractionFile = path.join(
'extracted.js',
);
-interface LoaderOptions {
+interface LoaderOptions
+ extends Pick<
+ ProcessVanillaFileOptions,
+ 'onEvaluated' | 'serializeVanillaModule'
+ > {
outputCss: boolean;
identifiers?: IdentifierOption;
}
@@ -63,9 +68,13 @@ export default function (this: LoaderContext, source: string) {
}
export function pitch(this: LoaderContext) {
- const { childCompiler, outputCss, identifiers } = loaderUtils.getOptions(
- this,
- ) as InternalLoaderOptions;
+ const {
+ childCompiler,
+ outputCss,
+ identifiers,
+ onEvaluated,
+ serializeVanillaModule,
+ } = loaderUtils.getOptions(this) as InternalLoaderOptions;
const log = debug(
`vanilla-extract:loader:${formatResourcePath(this.resourcePath)}`,
@@ -95,6 +104,8 @@ export function pitch(this: LoaderContext) {
outputCss,
filePath: this.resourcePath,
identOption: defaultIdentifierOption(this.mode, identifiers),
+ onEvaluated,
+ serializeVanillaModule,
serializeVirtualCssPath: async ({ fileName, source }) => {
const serializedCss = await serializeCss(source);
const virtualResourceLoader = `${virtualLoader}?${JSON.stringify({