diff --git a/.changeset/css-bundling.md b/.changeset/css-bundling.md
new file mode 100644
index 00000000000..b918ae92e1e
--- /dev/null
+++ b/.changeset/css-bundling.md
@@ -0,0 +1,118 @@
+---
+"@remix-run/dev": minor
+"@remix-run/react": minor
+"@remix-run/server-runtime": minor
+"@remix-run/testing": minor
+---
+
+Enable support for [CSS Modules](https://github.com/css-modules/css-modules), [Vanilla Extract](http://vanilla-extract.style) and CSS side-effect imports
+
+These CSS bundling features were previously only available via `future.unstable_cssModules`, `future.unstable_vanillaExtract` and `future.unstable_cssSideEffectImports` options in `remix.config.js`, but they have now been stabilized.
+
+**CSS Bundle Setup**
+
+In order to use these features, you first need to set up CSS bundling in your project. First install the `@remix-run/css-bundle` package.
+
+```sh
+npm i @remix-run/css-bundle
+```
+
+Then return the exported `cssBundleHref` as a stylesheet link descriptor from the `links` function at the root of your app.
+
+```tsx
+import type { LinksFunction } from "@remix-run/node"; // or cloudflare/deno
+import { cssBundleHref } from "@remix-run/css-bundle";
+
+export const links: LinksFunction = () => {
+ return [
+ ...(cssBundleHref
+ ? [{ rel: "stylesheet", href: cssBundleHref }]
+ : []),
+ // ...
+ ];
+};
+```
+
+**CSS Modules**
+
+To use [CSS Modules](https://github.com/css-modules/css-modules), you can opt in via the `.module.css` file name convention. For example:
+
+```css
+.root {
+ border: solid 1px;
+ background: white;
+ color: #454545;
+}
+```
+
+```tsx
+import styles from "./styles.module.css";
+
+export const Button = React.forwardRef(
+ ({ children, ...props }, ref) => {
+ return (
+
+ );
+ }
+);
+Button.displayName = "Button";
+```
+
+**Vanilla Extract**
+
+To use [Vanilla Extract](http://vanilla-extract.style), first install its `css` package as a dev dependency.
+
+```sh
+npm install -D @vanilla-extract/css
+```
+
+You can then opt in via the `.css.ts`/`.css.js` file name convention. For example:
+
+```ts
+import { style } from "@vanilla-extract/css";
+
+export const root = style({
+ border: "solid 1px",
+ background: "white",
+ color: "#454545",
+});
+```
+
+```tsx
+import * as styles from "./styles.css"; // Note that `.ts` is omitted here
+
+export const Button = React.forwardRef(
+ ({ children, ...props }, ref) => {
+ return (
+
+ );
+ }
+);
+Button.displayName = "Button";
+```
+
+**CSS Side-Effect Imports**
+
+Any CSS files that are imported as side-effects (e.g. `import "./styles.css"`) will be automatically included in the CSS bundle.
+
+Since JavaScript runtimes don't support importing CSS in this way, you'll also need to add any packages using CSS side-effect imports to the [`serverDependenciesToBundle`](https://remix.run/docs/en/main/file-conventions/remix-config#serverdependenciestobundle) option in your `remix.config.js` file. This ensures that any CSS imports are compiled out of your code before running it on the server. For example, to use [React Spectrum](https://react-spectrum.adobe.com/react-spectrum/index.html):
+
+```js filename=remix.config.js
+// remix.config.js
+module.exports = {
+ serverDependenciesToBundle: [
+ /^@adobe\/react-spectrum/,
+ /^@react-spectrum/,
+ /^@spectrum-icons/,
+ ],
+ // ...
+};
+```
diff --git a/docs/guides/styling.md b/docs/guides/styling.md
index dce8ffc4a7d..56821ed3ea9 100644
--- a/docs/guides/styling.md
+++ b/docs/guides/styling.md
@@ -671,8 +671,6 @@ NOTE: You may run into hydration warnings when using Styled Components. Hopefull
## CSS Bundling
-CSS-bundling features are unstable and currently only available behind feature flags. We're confident in the use cases they solve, but the API and implementation may change in the future.
-
When using CSS-bundling features, you should avoid using `export *` due to an [issue with esbuild's CSS tree shaking][esbuild-css-tree-shaking-issue].
Many common approaches to CSS within the React community are only possible when bundling CSS, meaning that the CSS files you write during development are collected into a separate bundle as part of the build process.
@@ -707,23 +705,9 @@ With this link tag inserted into the page, you're now ready to start using the v
### CSS Modules
-This feature is unstable and currently only available behind a feature flag. We're confident in the use cases it solves but the API and implementation may change in the future.
-
-First, ensure you've set up [CSS bundling][css-bundling] in your application.
-
-Then, to enable [CSS Modules], set the `future.unstable_cssModules` feature flag in `remix.config.js`.
-
-```js filename=remix.config.js
-/** @type {import('@remix-run/dev').AppConfig} */
-module.exports = {
- future: {
- unstable_cssModules: true,
- },
- // ...
-};
-```
+To use the built-in CSS Modules support, first ensure you've set up [CSS bundling][css-bundling] in your application.
-With this feature flag enabled, you can now opt into CSS Modules via the `.module.css` file name convention. For example:
+You can then opt into [CSS Modules] via the `.module.css` file name convention. For example:
```css filename=app/components/button/styles.module.css
.root {
@@ -752,31 +736,17 @@ Button.displayName = "Button";
### Vanilla Extract
-This feature is unstable and currently only available behind a feature flag. We're confident in the use cases it solves, but the API and implementation may change in the future.
-
[Vanilla Extract][vanilla-extract] is a zero-runtime CSS-in-TypeScript (or JavaScript) library that lets you use TypeScript as your CSS preprocessor. Styles are written in separate `*.css.ts` (or `*.css.js`) files and all code within them is executed during the build process rather than in your user's browser. If you want to keep your CSS bundle size to a minimum, Vanilla Extract also provides an official library called [Sprinkles][sprinkles] that lets you define a custom set of utility classes and a type-safe function for accessing them at runtime.
-First, ensure you've set up [CSS bundling][css-bundling] in your application.
+To use the built-in Vanilla Extract support, first ensure you've set up [CSS bundling][css-bundling] in your application.
-Next, install Vanilla Extract's core styling package as a dev dependency.
+Then, install Vanilla Extract's core styling package as a dev dependency.
```sh
npm install -D @vanilla-extract/css
```
-Then, to enable Vanilla Extract, set the `future.unstable_vanillaExtract` feature flag in `remix.config.js`.
-
-```js filename=remix.config.js
-/** @type {import('@remix-run/dev').AppConfig} */
-module.exports = {
- future: {
- unstable_vanillaExtract: true,
- },
- // ...
-};
-```
-
-With this feature flag enabled, you can now opt into Vanilla Extract via the `.css.ts`/`.css.js` file name convention. For example:
+You can then opt into Vanilla Extract via the `.css.ts`/`.css.js` file name convention. For example:
```ts filename=app/components/button/styles.css.ts
import { style } from "@vanilla-extract/css";
@@ -807,23 +777,9 @@ Button.displayName = "Button";
### CSS Side-Effect Imports
-This feature is unstable and currently only available behind a feature flag. We're confident in the use cases it solves, but the API and implementation may change in the future.
-
Some NPM packages use side-effect imports of plain CSS files (e.g. `import "./styles.css"`) to declare the CSS dependencies of JavaScript files. If you want to consume one of these packages, first ensure you've set up [CSS bundling][css-bundling] in your application.
-Then, set the `future.unstable_cssSideEffectImports` feature flag in `remix.config.js`.
-
-```js filename=remix.config.js
-/** @type {import('@remix-run/dev').AppConfig} */
-module.exports = {
- future: {
- unstable_cssSideEffectImports: true,
- },
- // ...
-};
-```
-
-Finally, since JavaScript runtimes don't support importing CSS in this way, you'll also need to add any relevant packages to the [`serverDependenciesToBundle`][server-dependencies-to-bundle] option in your `remix.config.js` file. This ensures that any CSS imports are compiled out of your code before running it on the server. For example, to use React Spectrum:
+Since JavaScript runtimes don't support importing CSS in this way, you'll need to add any relevant packages to the [`serverDependenciesToBundle`][server-dependencies-to-bundle] option in your `remix.config.js` file. This ensures that any CSS imports are compiled out of your code before running it on the server. For example, to use React Spectrum:
```js filename=remix.config.js
/** @type {import('@remix-run/dev').AppConfig} */
@@ -833,9 +789,6 @@ module.exports = {
/^@react-spectrum/,
/^@spectrum-icons/,
],
- future: {
- unstable_cssSideEffectImports: true,
- },
// ...
};
```
diff --git a/docs/pages/api-development-strategy.md b/docs/pages/api-development-strategy.md
index deb1e7cb6cc..828ebf8a7da 100644
--- a/docs/pages/api-development-strategy.md
+++ b/docs/pages/api-development-strategy.md
@@ -54,10 +54,7 @@ Here's the current future flags in Remix v1 today:
| Flag | Description |
| ------------------------------- | --------------------------------------------------------------------- |
-| `unstable_cssModules` | Enable CSS Modules Support |
-| `unstable_cssSideEffectImports` | Enable CSS Side Effect imports |
| `unstable_dev` | Enable the new development server (including HMR/HDR support) |
-| `unstable_vanillaExtract` | Enable Vanilla Extract Support |
| `v2_errorBoundary` | Combine `ErrorBoundary`/`CatchBoundary` into a single `ErrorBoundary` |
| `v2_meta` | Enable the new API for your `meta` functions |
| `v2_normalizeFormMethod` | Normalize `useNavigation().formMethod` to be an uppercase HTTP Method |
diff --git a/integration/css-modules-test.ts b/integration/css-modules-test.ts
index bb8b664b642..e8a1b7c9467 100644
--- a/integration/css-modules-test.ts
+++ b/integration/css-modules-test.ts
@@ -21,11 +21,6 @@ test.describe("CSS Modules", () => {
fixture = await createFixture({
future: {
v2_routeConvention: true,
- // Enable all CSS future flags to
- // ensure features don't clash
- unstable_cssModules: true,
- unstable_cssSideEffectImports: true,
- unstable_vanillaExtract: true,
},
files: {
"app/root.jsx": js`
diff --git a/integration/css-side-effect-imports-test.ts b/integration/css-side-effect-imports-test.ts
index 33b97ba2868..165cdb1f61e 100644
--- a/integration/css-side-effect-imports-test.ts
+++ b/integration/css-side-effect-imports-test.ts
@@ -23,11 +23,6 @@ test.describe("CSS side-effect imports", () => {
module.exports = {
serverDependenciesToBundle: [/@test-package/],
future: {
- // Enable all CSS future flags to
- // ensure features don't clash
- unstable_cssModules: true,
- unstable_cssSideEffectImports: true,
- unstable_vanillaExtract: true,
v2_routeConvention: true,
},
};
diff --git a/integration/deterministic-build-output-test.ts b/integration/deterministic-build-output-test.ts
index 451f63b89cc..8dfb52c732f 100644
--- a/integration/deterministic-build-output-test.ts
+++ b/integration/deterministic-build-output-test.ts
@@ -27,10 +27,6 @@ test("builds deterministically under different paths", async () => {
// * vanillaExtractPlugin (via app/routes/foo.tsx' .css.ts file import)
let init: FixtureInit = {
future: {
- unstable_cssModules: true,
- unstable_cssSideEffectImports: true,
- unstable_postcss: true,
- unstable_vanillaExtract: true,
v2_routeConvention: true,
},
files: {
@@ -116,66 +112,3 @@ test("builds deterministically under different paths", async () => {
);
});
});
-
-test("builds Vanilla Extract files deterministically under different paths with Vanilla Extract cache enabled", async () => {
- let init: FixtureInit = {
- future: {
- unstable_vanillaExtract: { cache: true },
- v2_routeConvention: true,
- },
- files: {
- "app/routes/foo.tsx": js`
- import { vanilla } from "~/styles/vanilla.css";
- export default () =>
YAY
;
- `,
- "app/images/foo.svg": `
-
-
-
- `,
- "app/styles/vanilla.css.ts": js`
- import { style } from "@vanilla-extract/css";
- import { chocolate } from "./chocolate.css";
- import imageUrl from "~/images/foo.svg";
-
- export const vanilla = style([
- chocolate,
- {
- backgroundImage: [
- "url(" + imageUrl + ")",
- "url(~/images/foo.svg)",
- ],
- }
- ]);
- `,
- "app/styles/chocolate.css.ts": js`
- import { style } from "@vanilla-extract/css";
-
- export const chocolate = style({
- color: "chocolate",
- });
- `,
- },
- };
- let dir1 = await createFixtureProject(init);
- let dir2 = await createFixtureProject(init);
-
- expect(dir1).not.toEqual(dir2);
-
- let files1 = await globby(["build/index.js", "public/build/**/*.{js,css}"], {
- cwd: dir1,
- });
- files1 = files1.sort();
- let files2 = await globby(["build/index.js", "public/build/**/*.{js,css}"], {
- cwd: dir2,
- });
- files2 = files2.sort();
-
- expect(files1.length).toBeGreaterThan(0);
- expect(files1).toEqual(files2);
- files1.forEach((file, i) => {
- expect(fs.readFileSync(path.join(dir1, file))).toEqual(
- fs.readFileSync(path.join(dir2, files2[i]))
- );
- });
-});
diff --git a/integration/hmr-test.ts b/integration/hmr-test.ts
index bd262696936..61858c787cc 100644
--- a/integration/hmr-test.ts
+++ b/integration/hmr-test.ts
@@ -8,18 +8,6 @@ import getPort, { makeRange } from "get-port";
import { createFixtureProject, css, js, json } from "./helpers/create-fixture";
let fixture = (options: { port: number; appServerPort: number }) => ({
- future: {
- unstable_dev: {
- port: options.port,
- appServerPort: options.appServerPort,
- },
- unstable_cssModules: true,
- unstable_tailwind: true,
- v2_routeConvention: true,
- v2_errorBoundary: true,
- v2_normalizeFormMethod: true,
- v2_meta: true,
- },
files: {
"remix.config.js": js`
module.exports = {
@@ -29,7 +17,6 @@ let fixture = (options: { port: number; appServerPort: number }) => ({
port: ${options.port},
appServerPort: ${options.appServerPort},
},
- unstable_cssModules: true,
v2_routeConvention: true,
v2_errorBoundary: true,
v2_normalizeFormMethod: true,
diff --git a/integration/postcss-test.ts b/integration/postcss-test.ts
index 87869004cd5..b476f90aad2 100644
--- a/integration/postcss-test.ts
+++ b/integration/postcss-test.ts
@@ -40,9 +40,6 @@ test.describe("PostCSS enabled", () => {
tailwind: true,
future: {
v2_routeConvention: true,
- unstable_cssModules: true,
- unstable_cssSideEffectImports: true,
- unstable_vanillaExtract: true,
},
};
`,
diff --git a/integration/tailwind-test.ts b/integration/tailwind-test.ts
index 9daa6a0ba78..12e53209b95 100644
--- a/integration/tailwind-test.ts
+++ b/integration/tailwind-test.ts
@@ -48,11 +48,6 @@ function runTests(ext: typeof extensions[number]) {
module.exports = {
tailwind: true,
future: {
- // Enable all CSS future flags to
- // ensure features don't clash
- unstable_cssModules: true,
- unstable_cssSideEffectImports: true,
- unstable_vanillaExtract: true,
v2_routeConvention: true,
},
};
diff --git a/integration/vanilla-extract-test.ts b/integration/vanilla-extract-test.ts
index 400adef10ad..119f974f5cd 100644
--- a/integration/vanilla-extract-test.ts
+++ b/integration/vanilla-extract-test.ts
@@ -6,657 +6,641 @@ import { createAppFixture, createFixture, js } from "./helpers/create-fixture";
const TEST_PADDING_VALUE = "20px";
-const configurations = [
- { name: "Cached", config: { cache: true } },
- { name: "Uncached", config: true },
-] as const;
-
test.describe("Vanilla Extract", () => {
- configurations.forEach((configuration) => {
- test.describe(configuration.name, () => {
- let fixture: Fixture;
- let appFixture: AppFixture;
-
- test.beforeAll(async () => {
- fixture = await createFixture({
- future: {
- v2_routeConvention: true,
- // Enable all CSS future flags to
- // ensure features don't clash
- unstable_cssModules: true,
- unstable_cssSideEffectImports: true,
- unstable_vanillaExtract: configuration.config,
- },
- files: {
- "app/root.jsx": js`
- import { Links, Outlet } from "@remix-run/react";
- import { cssBundleHref } from "@remix-run/css-bundle";
- export function links() {
- return [{ rel: "stylesheet", href: cssBundleHref }];
- }
- export default function Root() {
- return (
-
-
-
-
-
-
-
-
- )
- }
- `,
- ...typeScriptFixture(),
- ...javaScriptFixture(),
- ...classCompositionFixture(),
- ...rootRelativeClassCompositionFixture(),
- ...sideEffectImportsFixture(),
- ...sideEffectImportsWithinChildCompilationFixture(),
- ...stableIdentifiersFixture(),
- ...imageUrlsViaCssUrlFixture(),
- ...imageUrlsViaRootRelativeCssUrlFixture(),
- ...imageUrlsViaAbsoluteCssUrlFixture(),
- ...imageUrlsViaJsImportFixture(),
- ...imageUrlsViaRootRelativeJsImportFixture(),
- ...imageUrlsViaClassCompositionFixture(),
- ...imageUrlsViaJsImportClassCompositionFixture(),
- },
- });
- appFixture = await createAppFixture(fixture);
- });
-
- test.afterAll(() => appFixture.close());
+ let fixture: Fixture;
+ let appFixture: AppFixture;
- let typeScriptFixture = () => ({
- "app/fixtures/typescript/styles.css.ts": js`
- import { style } from "@vanilla-extract/css";
-
- export const root = style({
- background: 'peachpuff',
- padding: ${JSON.stringify(TEST_PADDING_VALUE)}
- });
- `,
- "app/routes/typescript-test.jsx": js`
- import * as styles from "../fixtures/typescript/styles.css";
-
- export default function() {
- return (
-
- TypeScript test
-
- )
+ test.beforeAll(async () => {
+ fixture = await createFixture({
+ future: {
+ v2_routeConvention: true,
+ },
+ files: {
+ "app/root.jsx": js`
+ import { Links, Outlet } from "@remix-run/react";
+ import { cssBundleHref } from "@remix-run/css-bundle";
+ export function links() {
+ return [{ rel: "stylesheet", href: cssBundleHref }];
}
- `,
- });
- test("TypeScript", async ({ page }) => {
- let app = new PlaywrightFixture(appFixture, page);
- await app.goto("/typescript-test");
- let locator = await page.locator("[data-testid='typescript']");
- let padding = await locator.evaluate(
- (element) => window.getComputedStyle(element).padding
- );
- expect(padding).toBe(TEST_PADDING_VALUE);
- });
-
- let javaScriptFixture = () => ({
- "app/fixtures/javascript/styles.css.js": js`
- import { style } from "@vanilla-extract/css";
-
- export const root = style({
- background: 'peachpuff',
- padding: ${JSON.stringify(TEST_PADDING_VALUE)}
- });
- `,
- "app/routes/javascript-test.jsx": js`
- import * as styles from "../fixtures/javascript/styles.css";
-
- export default function() {
+ export default function Root() {
return (
-
- javaScript test
-
+
+
+
+
+
+
+
+
)
}
`,
- });
- test("JavaScript", async ({ page }) => {
- let app = new PlaywrightFixture(appFixture, page);
- await app.goto("/javascript-test");
- let locator = await page.locator("[data-testid='javascript']");
- let padding = await locator.evaluate(
- (element) => window.getComputedStyle(element).padding
- );
- expect(padding).toBe(TEST_PADDING_VALUE);
- });
+ ...typeScriptFixture(),
+ ...javaScriptFixture(),
+ ...classCompositionFixture(),
+ ...rootRelativeClassCompositionFixture(),
+ ...sideEffectImportsFixture(),
+ ...sideEffectImportsWithinChildCompilationFixture(),
+ ...stableIdentifiersFixture(),
+ ...imageUrlsViaCssUrlFixture(),
+ ...imageUrlsViaRootRelativeCssUrlFixture(),
+ ...imageUrlsViaAbsoluteCssUrlFixture(),
+ ...imageUrlsViaJsImportFixture(),
+ ...imageUrlsViaRootRelativeJsImportFixture(),
+ ...imageUrlsViaClassCompositionFixture(),
+ ...imageUrlsViaJsImportClassCompositionFixture(),
+ },
+ });
+ appFixture = await createAppFixture(fixture);
+ });
- let classCompositionFixture = () => ({
- "app/fixtures/class-composition/styles.css.ts": js`
- import { style } from "@vanilla-extract/css";
- import { padding } from "./padding.css";
-
- export const root = style([
- padding,
- { background: 'peachpuff' },
- ]);
- `,
- "app/fixtures/class-composition/padding.css.ts": js`
- import { style } from "@vanilla-extract/css";
-
- export const padding = style({
- padding: ${JSON.stringify(TEST_PADDING_VALUE)}
- });
- `,
- "app/routes/class-composition-test.jsx": js`
- import * as styles from "../fixtures/class-composition/styles.css";
-
- export default function() {
- return (
-
- Class composition test
-
- )
- }
- `,
- });
- test("class composition", async ({ page }) => {
- let app = new PlaywrightFixture(appFixture, page);
- await app.goto("/class-composition-test");
- let locator = await page.locator("[data-testid='class-composition']");
- let padding = await locator.evaluate(
- (element) => window.getComputedStyle(element).padding
- );
- expect(padding).toBe(TEST_PADDING_VALUE);
- });
+ test.afterAll(() => appFixture.close());
- let rootRelativeClassCompositionFixture = () => ({
- "app/fixtures/root-relative-class-composition/styles.css.ts": js`
- import { style } from "@vanilla-extract/css";
- import { padding } from "~/fixtures/root-relative-class-composition/padding.css";
-
- export const root = style([
- padding,
- { background: 'peachpuff' },
- ]);
- `,
- "app/fixtures/root-relative-class-composition/padding.css.ts": js`
- import { style } from "@vanilla-extract/css";
-
- export const padding = style({
- padding: ${JSON.stringify(TEST_PADDING_VALUE)}
- });
- `,
- "app/routes/root-relative-class-composition-test.jsx": js`
- import * as styles from "../fixtures/root-relative-class-composition/styles.css";
-
- export default function() {
- return (
-
- Root-relative class composition test
-
- )
- }
- `,
- });
- test("root-relative class composition", async ({ page }) => {
- let app = new PlaywrightFixture(appFixture, page);
- await app.goto("/root-relative-class-composition-test");
- let locator = await page.locator(
- "[data-testid='root-relative-class-composition']"
- );
- let padding = await locator.evaluate(
- (element) => window.getComputedStyle(element).padding
- );
- expect(padding).toBe(TEST_PADDING_VALUE);
- });
+ let typeScriptFixture = () => ({
+ "app/fixtures/typescript/styles.css.ts": js`
+ import { style } from "@vanilla-extract/css";
- let sideEffectImportsFixture = () => ({
- "app/fixtures/side-effect-imports/styles.css.ts": js`
- import { globalStyle } from "@vanilla-extract/css";
-
- globalStyle(".side-effect-imports", {
- padding: ${JSON.stringify(TEST_PADDING_VALUE)}
- });
- `,
- "app/routes/side-effect-imports-test.jsx": js`
- import "../fixtures/side-effect-imports/styles.css";
-
- export default function() {
- return (
-
- Side-effect imports test
-
- )
- }
- `,
- });
- test("side-effect imports", async ({ page }) => {
- let app = new PlaywrightFixture(appFixture, page);
- await app.goto("/side-effect-imports-test");
- let locator = await page.locator("[data-testid='side-effect-imports']");
- let padding = await locator.evaluate(
- (element) => window.getComputedStyle(element).padding
- );
- expect(padding).toBe(TEST_PADDING_VALUE);
+ export const root = style({
+ background: 'peachpuff',
+ padding: ${JSON.stringify(TEST_PADDING_VALUE)}
});
+ `,
+ "app/routes/typescript-test.jsx": js`
+ import * as styles from "../fixtures/typescript/styles.css";
- let sideEffectImportsWithinChildCompilationFixture = () => ({
- "app/fixtures/side-effect-imports-within-child-compilation/styles.css.ts": js`
- import "./nested-side-effect.css";
- `,
- "app/fixtures/side-effect-imports-within-child-compilation/nested-side-effect.css.ts": js`
- import { globalStyle } from "@vanilla-extract/css";
-
- globalStyle(".side-effect-imports-within-child-compilation", {
- padding: ${JSON.stringify(TEST_PADDING_VALUE)}
- });
- `,
- "app/routes/side-effect-imports-within-child-compilation-test.jsx": js`
- import "../fixtures/side-effect-imports-within-child-compilation/styles.css";
-
- export default function() {
- return (
-
- Side-effect imports within child compilation test
-
- )
- }
- `,
- });
- test("side-effect imports within child compilation", async ({ page }) => {
- let app = new PlaywrightFixture(appFixture, page);
- await app.goto("/side-effect-imports-within-child-compilation-test");
- let locator = await page.locator(
- "[data-testid='side-effect-imports-within-child-compilation']"
- );
- let padding = await locator.evaluate(
- (element) => window.getComputedStyle(element).padding
- );
- expect(padding).toBe(TEST_PADDING_VALUE);
- });
+ export default function() {
+ return (
+
+ TypeScript test
+
+ )
+ }
+ `,
+ });
+ test("TypeScript", async ({ page }) => {
+ let app = new PlaywrightFixture(appFixture, page);
+ await app.goto("/typescript-test");
+ let locator = await page.locator("[data-testid='typescript']");
+ let padding = await locator.evaluate(
+ (element) => window.getComputedStyle(element).padding
+ );
+ expect(padding).toBe(TEST_PADDING_VALUE);
+ });
- let stableIdentifiersFixture = () => ({
- "app/fixtures/stable-identifiers/styles_a.css.ts": js`
- import { style } from "@vanilla-extract/css";
- import { shared } from "./shared.css";
-
- export const root = shared;
- `,
- "app/fixtures/stable-identifiers/styles_b.css.ts": js`
- import { style } from "@vanilla-extract/css";
- import { shared } from "./shared.css";
-
- export const root = shared;
- `,
- "app/fixtures/stable-identifiers/shared.css.ts": js`
- import { style } from "@vanilla-extract/css";
-
- export const shared = style({
- padding: ${JSON.stringify(TEST_PADDING_VALUE)},
- background: 'peachpuff',
- });
- `,
- "app/routes/stable-identifiers-test.jsx": js`
- import * as styles_a from "../fixtures/stable-identifiers/styles_a.css";
- import * as styles_b from "../fixtures/stable-identifiers/styles_b.css";
-
- const styles = new Set([styles_a.root, styles_b.root]);
-
- export default function() {
- return (
-
- Stable identifiers test
-
- )
- }
- `,
- });
- test("stable identifiers", async ({ page }) => {
- // This test ensures that file scoping is working as expected and
- // identifiers are stable across different .css.ts contexts. We test this by
- // using the same shared style in two different .css.ts files and then
- // asserting that it's the same class name.
- let app = new PlaywrightFixture(appFixture, page);
- await app.goto("/stable-identifiers-test");
- let locator = await page.locator("[data-testid='stable-identifiers']");
- let { padding, classList } = await locator.evaluate((element) => ({
- padding: window.getComputedStyle(element).padding,
- classList: Array.from(element.classList),
- }));
- expect(padding).toBe(TEST_PADDING_VALUE);
- expect(classList.length).toBe(1);
- });
+ let javaScriptFixture = () => ({
+ "app/fixtures/javascript/styles.css.js": js`
+ import { style } from "@vanilla-extract/css";
- let imageUrlsViaCssUrlFixture = () => ({
- "app/fixtures/imageUrlsViaCssUrl/styles.css.ts": js`
- import { style } from "@vanilla-extract/css";
-
- export const root = style({
- backgroundColor: 'peachpuff',
- backgroundImage: 'url("./image.svg")',
- padding: ${JSON.stringify(TEST_PADDING_VALUE)}
- });
- `,
- "app/fixtures/imageUrlsViaCssUrl/image.svg": `
-
-
-
- `,
- "app/routes/image-urls-via-css-url-test.jsx": js`
- import * as styles from "../fixtures/imageUrlsViaCssUrl/styles.css";
-
- export default function() {
- return (
-
- Image URLs via CSS URL test
-
- )
- }
- `,
- });
- test("image URLs via CSS URL", async ({ page }) => {
- let app = new PlaywrightFixture(appFixture, page);
- let imgStatus: number | null = null;
- app.page.on("response", (res) => {
- if (res.url().endsWith(".svg")) imgStatus = res.status();
- });
- await app.goto("/image-urls-via-css-url-test");
- let locator = await page.locator(
- "[data-testid='image-urls-via-css-url']"
- );
- let backgroundImage = await locator.evaluate(
- (element) => window.getComputedStyle(element).backgroundImage
- );
- expect(backgroundImage).toContain(".svg");
- expect(imgStatus).toBe(200);
+ export const root = style({
+ background: 'peachpuff',
+ padding: ${JSON.stringify(TEST_PADDING_VALUE)}
});
+ `,
+ "app/routes/javascript-test.jsx": js`
+ import * as styles from "../fixtures/javascript/styles.css";
- let imageUrlsViaRootRelativeCssUrlFixture = () => ({
- "app/fixtures/imageUrlsViaRootRelativeCssUrl/styles.css.ts": js`
- import { style } from "@vanilla-extract/css";
-
- export const root = style({
- backgroundColor: 'peachpuff',
- backgroundImage: 'url("~/fixtures/imageUrlsViaRootRelativeCssUrl/image.svg")',
- padding: ${JSON.stringify(TEST_PADDING_VALUE)}
- });
- `,
- "app/fixtures/imageUrlsViaRootRelativeCssUrl/image.svg": `
-
-
-
- `,
- "app/routes/image-urls-via-root-relative-css-url-test.jsx": js`
- import * as styles from "../fixtures/imageUrlsViaRootRelativeCssUrl/styles.css";
-
- export default function() {
- return (
-
- Image URLs via CSS URL test
-
- )
- }
- `,
+ export default function() {
+ return (
+
+ javaScript test
+
+ )
+ }
+ `,
+ });
+ test("JavaScript", async ({ page }) => {
+ let app = new PlaywrightFixture(appFixture, page);
+ await app.goto("/javascript-test");
+ let locator = await page.locator("[data-testid='javascript']");
+ let padding = await locator.evaluate(
+ (element) => window.getComputedStyle(element).padding
+ );
+ expect(padding).toBe(TEST_PADDING_VALUE);
+ });
+
+ let classCompositionFixture = () => ({
+ "app/fixtures/class-composition/styles.css.ts": js`
+ import { style } from "@vanilla-extract/css";
+ import { padding } from "./padding.css";
+
+ export const root = style([
+ padding,
+ { background: 'peachpuff' },
+ ]);
+ `,
+ "app/fixtures/class-composition/padding.css.ts": js`
+ import { style } from "@vanilla-extract/css";
+
+ export const padding = style({
+ padding: ${JSON.stringify(TEST_PADDING_VALUE)}
});
- test("image URLs via root-relative CSS URL", async ({ page }) => {
- let app = new PlaywrightFixture(appFixture, page);
- let imgStatus: number | null = null;
- app.page.on("response", (res) => {
- if (res.url().endsWith(".svg")) imgStatus = res.status();
- });
- await app.goto("/image-urls-via-root-relative-css-url-test");
- let locator = await page.locator(
- "[data-testid='image-urls-via-root-relative-css-url']"
- );
- let backgroundImage = await locator.evaluate(
- (element) => window.getComputedStyle(element).backgroundImage
- );
- expect(backgroundImage).toContain(".svg");
- expect(imgStatus).toBe(200);
+ `,
+ "app/routes/class-composition-test.jsx": js`
+ import * as styles from "../fixtures/class-composition/styles.css";
+
+ export default function() {
+ return (
+
+ Class composition test
+
+ )
+ }
+ `,
+ });
+ test("class composition", async ({ page }) => {
+ let app = new PlaywrightFixture(appFixture, page);
+ await app.goto("/class-composition-test");
+ let locator = await page.locator("[data-testid='class-composition']");
+ let padding = await locator.evaluate(
+ (element) => window.getComputedStyle(element).padding
+ );
+ expect(padding).toBe(TEST_PADDING_VALUE);
+ });
+
+ let rootRelativeClassCompositionFixture = () => ({
+ "app/fixtures/root-relative-class-composition/styles.css.ts": js`
+ import { style } from "@vanilla-extract/css";
+ import { padding } from "~/fixtures/root-relative-class-composition/padding.css";
+
+ export const root = style([
+ padding,
+ { background: 'peachpuff' },
+ ]);
+ `,
+ "app/fixtures/root-relative-class-composition/padding.css.ts": js`
+ import { style } from "@vanilla-extract/css";
+
+ export const padding = style({
+ padding: ${JSON.stringify(TEST_PADDING_VALUE)}
});
+ `,
+ "app/routes/root-relative-class-composition-test.jsx": js`
+ import * as styles from "../fixtures/root-relative-class-composition/styles.css";
- let imageUrlsViaAbsoluteCssUrlFixture = () => ({
- "app/fixtures/imageUrlsViaAbsoluteCssUrl/styles.css.ts": js`
- import { style } from "@vanilla-extract/css";
-
- export const root = style({
- backgroundColor: 'peachpuff',
- backgroundImage: 'url("/imageUrlsViaAbsoluteCssUrl/image.svg")',
- padding: ${JSON.stringify(TEST_PADDING_VALUE)}
- });
- `,
- "public/imageUrlsViaAbsoluteCssUrl/image.svg": `
-
-
-
- `,
- "app/routes/image-urls-via-absolute-css-url-test.jsx": js`
- import * as styles from "../fixtures/imageUrlsViaAbsoluteCssUrl/styles.css";
-
- export default function() {
- return (
-
- Image URLs via absolute CSS URL test
-
- )
- }
- `,
+ export default function() {
+ return (
+
+ Root-relative class composition test
+
+ )
+ }
+ `,
+ });
+ test("root-relative class composition", async ({ page }) => {
+ let app = new PlaywrightFixture(appFixture, page);
+ await app.goto("/root-relative-class-composition-test");
+ let locator = await page.locator(
+ "[data-testid='root-relative-class-composition']"
+ );
+ let padding = await locator.evaluate(
+ (element) => window.getComputedStyle(element).padding
+ );
+ expect(padding).toBe(TEST_PADDING_VALUE);
+ });
+
+ let sideEffectImportsFixture = () => ({
+ "app/fixtures/side-effect-imports/styles.css.ts": js`
+ import { globalStyle } from "@vanilla-extract/css";
+
+ globalStyle(".side-effect-imports", {
+ padding: ${JSON.stringify(TEST_PADDING_VALUE)}
});
- test("image URLs via absolute CSS URL", async ({ page }) => {
- let app = new PlaywrightFixture(appFixture, page);
- let imgStatus: number | null = null;
- app.page.on("response", (res) => {
- if (res.url().endsWith(".svg")) imgStatus = res.status();
- });
- await app.goto("/image-urls-via-absolute-css-url-test");
- let locator = await page.locator(
- "[data-testid='image-urls-via-absolute-css-url']"
- );
- let backgroundImage = await locator.evaluate(
- (element) => window.getComputedStyle(element).backgroundImage
- );
- expect(backgroundImage).toContain(".svg");
- expect(imgStatus).toBe(200);
+ `,
+ "app/routes/side-effect-imports-test.jsx": js`
+ import "../fixtures/side-effect-imports/styles.css";
+
+ export default function() {
+ return (
+
+ Side-effect imports test
+
+ )
+ }
+ `,
+ });
+ test("side-effect imports", async ({ page }) => {
+ let app = new PlaywrightFixture(appFixture, page);
+ await app.goto("/side-effect-imports-test");
+ let locator = await page.locator("[data-testid='side-effect-imports']");
+ let padding = await locator.evaluate(
+ (element) => window.getComputedStyle(element).padding
+ );
+ expect(padding).toBe(TEST_PADDING_VALUE);
+ });
+
+ let sideEffectImportsWithinChildCompilationFixture = () => ({
+ "app/fixtures/side-effect-imports-within-child-compilation/styles.css.ts": js`
+ import "./nested-side-effect.css";
+ `,
+ "app/fixtures/side-effect-imports-within-child-compilation/nested-side-effect.css.ts": js`
+ import { globalStyle } from "@vanilla-extract/css";
+
+ globalStyle(".side-effect-imports-within-child-compilation", {
+ padding: ${JSON.stringify(TEST_PADDING_VALUE)}
});
+ `,
+ "app/routes/side-effect-imports-within-child-compilation-test.jsx": js`
+ import "../fixtures/side-effect-imports-within-child-compilation/styles.css";
- let imageUrlsViaJsImportFixture = () => ({
- "app/fixtures/imageUrlsViaJsImport/styles.css.ts": js`
- import { style } from "@vanilla-extract/css";
- import href from "./image.svg";
-
- export const root = style({
- backgroundColor: 'peachpuff',
- backgroundImage: 'url(' + href + ')',
- padding: ${JSON.stringify(TEST_PADDING_VALUE)}
- });
- `,
- "app/fixtures/imageUrlsViaJsImport/image.svg": `
-
-
-
- `,
- "app/routes/image-urls-via-js-import-test.jsx": js`
- import * as styles from "../fixtures/imageUrlsViaJsImport/styles.css";
-
- export default function() {
- return (
-
- Image URLs via JS import test
-
- )
- }
- `,
+ export default function() {
+ return (
+
+ Side-effect imports within child compilation test
+
+ )
+ }
+ `,
+ });
+ test("side-effect imports within child compilation", async ({ page }) => {
+ let app = new PlaywrightFixture(appFixture, page);
+ await app.goto("/side-effect-imports-within-child-compilation-test");
+ let locator = await page.locator(
+ "[data-testid='side-effect-imports-within-child-compilation']"
+ );
+ let padding = await locator.evaluate(
+ (element) => window.getComputedStyle(element).padding
+ );
+ expect(padding).toBe(TEST_PADDING_VALUE);
+ });
+
+ let stableIdentifiersFixture = () => ({
+ "app/fixtures/stable-identifiers/styles_a.css.ts": js`
+ import { style } from "@vanilla-extract/css";
+ import { shared } from "./shared.css";
+
+ export const root = shared;
+ `,
+ "app/fixtures/stable-identifiers/styles_b.css.ts": js`
+ import { style } from "@vanilla-extract/css";
+ import { shared } from "./shared.css";
+
+ export const root = shared;
+ `,
+ "app/fixtures/stable-identifiers/shared.css.ts": js`
+ import { style } from "@vanilla-extract/css";
+
+ export const shared = style({
+ padding: ${JSON.stringify(TEST_PADDING_VALUE)},
+ background: 'peachpuff',
});
- test("image URLs via JS import", async ({ page }) => {
- let app = new PlaywrightFixture(appFixture, page);
- let imgStatus: number | null = null;
- app.page.on("response", (res) => {
- if (res.url().endsWith(".svg")) imgStatus = res.status();
- });
- await app.goto("/image-urls-via-js-import-test");
- let locator = await page.locator(
- "[data-testid='image-urls-via-js-import']"
- );
- let backgroundImage = await locator.evaluate(
- (element) => window.getComputedStyle(element).backgroundImage
- );
- expect(backgroundImage).toContain(".svg");
- expect(imgStatus).toBe(200);
+ `,
+ "app/routes/stable-identifiers-test.jsx": js`
+ import * as styles_a from "../fixtures/stable-identifiers/styles_a.css";
+ import * as styles_b from "../fixtures/stable-identifiers/styles_b.css";
+
+ const styles = new Set([styles_a.root, styles_b.root]);
+
+ export default function() {
+ return (
+
+ Stable identifiers test
+
+ )
+ }
+ `,
+ });
+ test("stable identifiers", async ({ page }) => {
+ // This test ensures that file scoping is working as expected and
+ // identifiers are stable across different .css.ts contexts. We test this by
+ // using the same shared style in two different .css.ts files and then
+ // asserting that it's the same class name.
+ let app = new PlaywrightFixture(appFixture, page);
+ await app.goto("/stable-identifiers-test");
+ let locator = await page.locator("[data-testid='stable-identifiers']");
+ let { padding, classList } = await locator.evaluate((element) => ({
+ padding: window.getComputedStyle(element).padding,
+ classList: Array.from(element.classList),
+ }));
+ expect(padding).toBe(TEST_PADDING_VALUE);
+ expect(classList.length).toBe(1);
+ });
+
+ let imageUrlsViaCssUrlFixture = () => ({
+ "app/fixtures/imageUrlsViaCssUrl/styles.css.ts": js`
+ import { style } from "@vanilla-extract/css";
+
+ export const root = style({
+ backgroundColor: 'peachpuff',
+ backgroundImage: 'url("./image.svg")',
+ padding: ${JSON.stringify(TEST_PADDING_VALUE)}
});
+ `,
+ "app/fixtures/imageUrlsViaCssUrl/image.svg": `
+
+
+
+ `,
+ "app/routes/image-urls-via-css-url-test.jsx": js`
+ import * as styles from "../fixtures/imageUrlsViaCssUrl/styles.css";
- let imageUrlsViaRootRelativeJsImportFixture = () => ({
- "app/fixtures/imageUrlsViaRootRelativeJsImport/styles.css.ts": js`
- import { style } from "@vanilla-extract/css";
- import href from "~/fixtures/imageUrlsViaRootRelativeJsImport/image.svg";
-
- export const root = style({
- backgroundColor: 'peachpuff',
- backgroundImage: 'url(' + href + ')',
- padding: ${JSON.stringify(TEST_PADDING_VALUE)}
- });
- `,
- "app/fixtures/imageUrlsViaRootRelativeJsImport/image.svg": `
-
-
-
- `,
- "app/routes/image-urls-via-root-relative-js-import-test.jsx": js`
- import * as styles from "../fixtures/imageUrlsViaRootRelativeJsImport/styles.css";
-
- export default function() {
- return (
-
- Image URLs via root-relative JS import test
-
- )
- }
- `,
+ export default function() {
+ return (
+
+ Image URLs via CSS URL test
+
+ )
+ }
+ `,
+ });
+ test("image URLs via CSS URL", async ({ page }) => {
+ let app = new PlaywrightFixture(appFixture, page);
+ let imgStatus: number | null = null;
+ app.page.on("response", (res) => {
+ if (res.url().endsWith(".svg")) imgStatus = res.status();
+ });
+ await app.goto("/image-urls-via-css-url-test");
+ let locator = await page.locator("[data-testid='image-urls-via-css-url']");
+ let backgroundImage = await locator.evaluate(
+ (element) => window.getComputedStyle(element).backgroundImage
+ );
+ expect(backgroundImage).toContain(".svg");
+ expect(imgStatus).toBe(200);
+ });
+
+ let imageUrlsViaRootRelativeCssUrlFixture = () => ({
+ "app/fixtures/imageUrlsViaRootRelativeCssUrl/styles.css.ts": js`
+ import { style } from "@vanilla-extract/css";
+
+ export const root = style({
+ backgroundColor: 'peachpuff',
+ backgroundImage: 'url("~/fixtures/imageUrlsViaRootRelativeCssUrl/image.svg")',
+ padding: ${JSON.stringify(TEST_PADDING_VALUE)}
});
- test("image URLs via root-relative JS import", async ({ page }) => {
- let app = new PlaywrightFixture(appFixture, page);
- let imgStatus: number | null = null;
- app.page.on("response", (res) => {
- if (res.url().endsWith(".svg")) imgStatus = res.status();
- });
- await app.goto("/image-urls-via-root-relative-js-import-test");
- let locator = await page.locator(
- "[data-testid='image-urls-via-root-relative-js-import']"
- );
- let backgroundImage = await locator.evaluate(
- (element) => window.getComputedStyle(element).backgroundImage
- );
- expect(backgroundImage).toContain(".svg");
- expect(imgStatus).toBe(200);
+ `,
+ "app/fixtures/imageUrlsViaRootRelativeCssUrl/image.svg": `
+
+
+
+ `,
+ "app/routes/image-urls-via-root-relative-css-url-test.jsx": js`
+ import * as styles from "../fixtures/imageUrlsViaRootRelativeCssUrl/styles.css";
+
+ export default function() {
+ return (
+
+ Image URLs via CSS URL test
+
+ )
+ }
+ `,
+ });
+ test("image URLs via root-relative CSS URL", async ({ page }) => {
+ let app = new PlaywrightFixture(appFixture, page);
+ let imgStatus: number | null = null;
+ app.page.on("response", (res) => {
+ if (res.url().endsWith(".svg")) imgStatus = res.status();
+ });
+ await app.goto("/image-urls-via-root-relative-css-url-test");
+ let locator = await page.locator(
+ "[data-testid='image-urls-via-root-relative-css-url']"
+ );
+ let backgroundImage = await locator.evaluate(
+ (element) => window.getComputedStyle(element).backgroundImage
+ );
+ expect(backgroundImage).toContain(".svg");
+ expect(imgStatus).toBe(200);
+ });
+
+ let imageUrlsViaAbsoluteCssUrlFixture = () => ({
+ "app/fixtures/imageUrlsViaAbsoluteCssUrl/styles.css.ts": js`
+ import { style } from "@vanilla-extract/css";
+
+ export const root = style({
+ backgroundColor: 'peachpuff',
+ backgroundImage: 'url("/imageUrlsViaAbsoluteCssUrl/image.svg")',
+ padding: ${JSON.stringify(TEST_PADDING_VALUE)}
});
+ `,
+ "public/imageUrlsViaAbsoluteCssUrl/image.svg": `
+
+
+
+ `,
+ "app/routes/image-urls-via-absolute-css-url-test.jsx": js`
+ import * as styles from "../fixtures/imageUrlsViaAbsoluteCssUrl/styles.css";
- let imageUrlsViaClassCompositionFixture = () => ({
- "app/fixtures/imageUrlsViaClassComposition/styles.css.ts": js`
- import { style } from "@vanilla-extract/css";
- import { backgroundImage } from "./nested/backgroundImage.css";
-
- export const root = style([
- backgroundImage,
- {
- backgroundColor: 'peachpuff',
- padding: ${JSON.stringify(TEST_PADDING_VALUE)}
- }
- ]);
- `,
- "app/fixtures/imageUrlsViaClassComposition/nested/backgroundImage.css.ts": js`
- import { style } from "@vanilla-extract/css";
-
- export const backgroundImage = style({
- backgroundImage: 'url(../image.svg)',
- });
- `,
- "app/fixtures/imageUrlsViaClassComposition/image.svg": `
-
-
-
- `,
- "app/routes/image-urls-via-class-composition-test.jsx": js`
- import * as styles from "../fixtures/imageUrlsViaClassComposition/styles.css";
-
- export default function() {
- return (
-
- Image URLs via class composition test
-
- )
- }
- `,
+ export default function() {
+ return (
+
+ Image URLs via absolute CSS URL test
+
+ )
+ }
+ `,
+ });
+ test("image URLs via absolute CSS URL", async ({ page }) => {
+ let app = new PlaywrightFixture(appFixture, page);
+ let imgStatus: number | null = null;
+ app.page.on("response", (res) => {
+ if (res.url().endsWith(".svg")) imgStatus = res.status();
+ });
+ await app.goto("/image-urls-via-absolute-css-url-test");
+ let locator = await page.locator(
+ "[data-testid='image-urls-via-absolute-css-url']"
+ );
+ let backgroundImage = await locator.evaluate(
+ (element) => window.getComputedStyle(element).backgroundImage
+ );
+ expect(backgroundImage).toContain(".svg");
+ expect(imgStatus).toBe(200);
+ });
+
+ let imageUrlsViaJsImportFixture = () => ({
+ "app/fixtures/imageUrlsViaJsImport/styles.css.ts": js`
+ import { style } from "@vanilla-extract/css";
+ import href from "./image.svg";
+
+ export const root = style({
+ backgroundColor: 'peachpuff',
+ backgroundImage: 'url(' + href + ')',
+ padding: ${JSON.stringify(TEST_PADDING_VALUE)}
});
- test("image URLs via class composition", async ({ page }) => {
- let app = new PlaywrightFixture(appFixture, page);
- let imgStatus: number | null = null;
- app.page.on("response", (res) => {
- if (res.url().endsWith(".svg")) imgStatus = res.status();
- });
- await app.goto("/image-urls-via-class-composition-test");
- let locator = await page.locator(
- "[data-testid='image-urls-via-class-composition']"
- );
- let backgroundImage = await locator.evaluate(
- (element) => window.getComputedStyle(element).backgroundImage
- );
- expect(backgroundImage).toContain(".svg");
- expect(imgStatus).toBe(200);
+ `,
+ "app/fixtures/imageUrlsViaJsImport/image.svg": `
+
+
+
+ `,
+ "app/routes/image-urls-via-js-import-test.jsx": js`
+ import * as styles from "../fixtures/imageUrlsViaJsImport/styles.css";
+
+ export default function() {
+ return (
+
+ Image URLs via JS import test
+
+ )
+ }
+ `,
+ });
+ test("image URLs via JS import", async ({ page }) => {
+ let app = new PlaywrightFixture(appFixture, page);
+ let imgStatus: number | null = null;
+ app.page.on("response", (res) => {
+ if (res.url().endsWith(".svg")) imgStatus = res.status();
+ });
+ await app.goto("/image-urls-via-js-import-test");
+ let locator = await page.locator(
+ "[data-testid='image-urls-via-js-import']"
+ );
+ let backgroundImage = await locator.evaluate(
+ (element) => window.getComputedStyle(element).backgroundImage
+ );
+ expect(backgroundImage).toContain(".svg");
+ expect(imgStatus).toBe(200);
+ });
+
+ let imageUrlsViaRootRelativeJsImportFixture = () => ({
+ "app/fixtures/imageUrlsViaRootRelativeJsImport/styles.css.ts": js`
+ import { style } from "@vanilla-extract/css";
+ import href from "~/fixtures/imageUrlsViaRootRelativeJsImport/image.svg";
+
+ export const root = style({
+ backgroundColor: 'peachpuff',
+ backgroundImage: 'url(' + href + ')',
+ padding: ${JSON.stringify(TEST_PADDING_VALUE)}
});
+ `,
+ "app/fixtures/imageUrlsViaRootRelativeJsImport/image.svg": `
+
+
+
+ `,
+ "app/routes/image-urls-via-root-relative-js-import-test.jsx": js`
+ import * as styles from "../fixtures/imageUrlsViaRootRelativeJsImport/styles.css";
- let imageUrlsViaJsImportClassCompositionFixture = () => ({
- "app/fixtures/imageUrlsViaJsImportClassComposition/styles.css.ts": js`
- import { style } from "@vanilla-extract/css";
- import { backgroundImage } from "./nested/backgroundImage.css";
-
- export const root = style([
- backgroundImage,
- {
- backgroundColor: 'peachpuff',
- padding: ${JSON.stringify(TEST_PADDING_VALUE)}
- }
- ]);
- `,
- "app/fixtures/imageUrlsViaJsImportClassComposition/nested/backgroundImage.css.ts": js`
- import { style } from "@vanilla-extract/css";
- import href from "../image.svg";
-
- export const backgroundImage = style({
- backgroundImage: 'url(' + href + ')',
- });
- `,
- "app/fixtures/imageUrlsViaJsImportClassComposition/image.svg": `
-
-
-
- `,
- "app/routes/image-urls-via-js-import-class-composition-test.jsx": js`
- import * as styles from "../fixtures/imageUrlsViaJsImportClassComposition/styles.css";
-
- export default function() {
- return (
-
- Image URLs via class composition test
-
- )
- }
- `,
+ export default function() {
+ return (
+
+ Image URLs via root-relative JS import test
+
+ )
+ }
+ `,
+ });
+ test("image URLs via root-relative JS import", async ({ page }) => {
+ let app = new PlaywrightFixture(appFixture, page);
+ let imgStatus: number | null = null;
+ app.page.on("response", (res) => {
+ if (res.url().endsWith(".svg")) imgStatus = res.status();
+ });
+ await app.goto("/image-urls-via-root-relative-js-import-test");
+ let locator = await page.locator(
+ "[data-testid='image-urls-via-root-relative-js-import']"
+ );
+ let backgroundImage = await locator.evaluate(
+ (element) => window.getComputedStyle(element).backgroundImage
+ );
+ expect(backgroundImage).toContain(".svg");
+ expect(imgStatus).toBe(200);
+ });
+
+ let imageUrlsViaClassCompositionFixture = () => ({
+ "app/fixtures/imageUrlsViaClassComposition/styles.css.ts": js`
+ import { style } from "@vanilla-extract/css";
+ import { backgroundImage } from "./nested/backgroundImage.css";
+
+ export const root = style([
+ backgroundImage,
+ {
+ backgroundColor: 'peachpuff',
+ padding: ${JSON.stringify(TEST_PADDING_VALUE)}
+ }
+ ]);
+ `,
+ "app/fixtures/imageUrlsViaClassComposition/nested/backgroundImage.css.ts": js`
+ import { style } from "@vanilla-extract/css";
+
+ export const backgroundImage = style({
+ backgroundImage: 'url(../image.svg)',
});
- test("image URLs via JS import class composition", async ({ page }) => {
- let app = new PlaywrightFixture(appFixture, page);
- let imgStatus: number | null = null;
- app.page.on("response", (res) => {
- if (res.url().endsWith(".svg")) imgStatus = res.status();
- });
- await app.goto("/image-urls-via-js-import-class-composition-test");
- let locator = await page.locator(
- "[data-testid='image-urls-via-js-import-class-composition']"
- );
- let backgroundImage = await locator.evaluate(
- (element) => window.getComputedStyle(element).backgroundImage
- );
- expect(backgroundImage).toContain(".svg");
- expect(imgStatus).toBe(200);
+ `,
+ "app/fixtures/imageUrlsViaClassComposition/image.svg": `
+
+
+
+ `,
+ "app/routes/image-urls-via-class-composition-test.jsx": js`
+ import * as styles from "../fixtures/imageUrlsViaClassComposition/styles.css";
+
+ export default function() {
+ return (
+
+ Image URLs via class composition test
+
+ )
+ }
+ `,
+ });
+ test("image URLs via class composition", async ({ page }) => {
+ let app = new PlaywrightFixture(appFixture, page);
+ let imgStatus: number | null = null;
+ app.page.on("response", (res) => {
+ if (res.url().endsWith(".svg")) imgStatus = res.status();
+ });
+ await app.goto("/image-urls-via-class-composition-test");
+ let locator = await page.locator(
+ "[data-testid='image-urls-via-class-composition']"
+ );
+ let backgroundImage = await locator.evaluate(
+ (element) => window.getComputedStyle(element).backgroundImage
+ );
+ expect(backgroundImage).toContain(".svg");
+ expect(imgStatus).toBe(200);
+ });
+
+ let imageUrlsViaJsImportClassCompositionFixture = () => ({
+ "app/fixtures/imageUrlsViaJsImportClassComposition/styles.css.ts": js`
+ import { style } from "@vanilla-extract/css";
+ import { backgroundImage } from "./nested/backgroundImage.css";
+
+ export const root = style([
+ backgroundImage,
+ {
+ backgroundColor: 'peachpuff',
+ padding: ${JSON.stringify(TEST_PADDING_VALUE)}
+ }
+ ]);
+ `,
+ "app/fixtures/imageUrlsViaJsImportClassComposition/nested/backgroundImage.css.ts": js`
+ import { style } from "@vanilla-extract/css";
+ import href from "../image.svg";
+
+ export const backgroundImage = style({
+ backgroundImage: 'url(' + href + ')',
});
+ `,
+ "app/fixtures/imageUrlsViaJsImportClassComposition/image.svg": `
+
+
+
+ `,
+ "app/routes/image-urls-via-js-import-class-composition-test.jsx": js`
+ import * as styles from "../fixtures/imageUrlsViaJsImportClassComposition/styles.css";
+
+ export default function() {
+ return (
+
+ Image URLs via class composition test
+
+ )
+ }
+ `,
+ });
+ test("image URLs via JS import class composition", async ({ page }) => {
+ let app = new PlaywrightFixture(appFixture, page);
+ let imgStatus: number | null = null;
+ app.page.on("response", (res) => {
+ if (res.url().endsWith(".svg")) imgStatus = res.status();
});
+ await app.goto("/image-urls-via-js-import-class-composition-test");
+ let locator = await page.locator(
+ "[data-testid='image-urls-via-js-import-class-composition']"
+ );
+ let backgroundImage = await locator.evaluate(
+ (element) => window.getComputedStyle(element).backgroundImage
+ );
+ expect(backgroundImage).toContain(".svg");
+ expect(imgStatus).toBe(200);
});
});
diff --git a/packages/remix-dev/__tests__/readConfig-test.ts b/packages/remix-dev/__tests__/readConfig-test.ts
index 7a504fdb2f9..e1f795537c7 100644
--- a/packages/remix-dev/__tests__/readConfig-test.ts
+++ b/packages/remix-dev/__tests__/readConfig-test.ts
@@ -31,11 +31,8 @@ describe("readConfig", () => {
entryServerFilePath: expect.any(String),
tsconfigPath: expect.any(String),
future: {
- unstable_cssModules: expect.any(Boolean),
- unstable_cssSideEffectImports: expect.any(Boolean),
unstable_postcss: expect.any(Boolean),
unstable_tailwind: expect.any(Boolean),
- unstable_vanillaExtract: expect.any(Boolean),
v2_errorBoundary: expect.any(Boolean),
v2_meta: expect.any(Boolean),
v2_normalizeFormMethod: expect.any(Boolean),
@@ -54,12 +51,9 @@ describe("readConfig", () => {
"entryServerFile": "entry.server.tsx",
"entryServerFilePath": Any,
"future": Object {
- "unstable_cssModules": Any,
- "unstable_cssSideEffectImports": Any,
"unstable_dev": false,
"unstable_postcss": Any,
"unstable_tailwind": Any,
- "unstable_vanillaExtract": Any,
"v2_errorBoundary": Any,
"v2_meta": Any,
"v2_normalizeFormMethod": Any,
diff --git a/packages/remix-dev/compiler/assets/css.ts b/packages/remix-dev/compiler/assets/css.ts
index 40c93712474..5184136b13b 100644
--- a/packages/remix-dev/compiler/assets/css.ts
+++ b/packages/remix-dev/compiler/assets/css.ts
@@ -24,17 +24,6 @@ import {
} from "./plugins/cssBundleEntry";
import type { WriteChannel } from "../../channel";
-function isNotNull(value: Value): value is Exclude {
- return value !== null;
-}
-
-const isCssBundlingEnabled = (config: RemixConfig): boolean =>
- Boolean(
- config.future.unstable_cssModules ||
- config.future.unstable_cssSideEffectImports ||
- config.future.unstable_vanillaExtract
- );
-
const getExternals = (remixConfig: RemixConfig): string[] => {
// For the browser build, exclude node built-ins that don't have a
// browser-safe alternative installed in node_modules. Nothing should
@@ -61,16 +50,10 @@ const createEsbuildConfig = (
let { mode } = options;
let plugins: esbuild.Plugin[] = [
- isCssBundlingEnabled(config) ? cssBundleEntryModulePlugin(config) : null,
- config.future.unstable_cssModules
- ? cssModulesPlugin({ config, mode, outputCss: true })
- : null,
- config.future.unstable_vanillaExtract
- ? vanillaExtractPlugin({ config, mode, outputCss: true })
- : null,
- config.future.unstable_cssSideEffectImports
- ? cssSideEffectImportsPlugin({ config, options })
- : null,
+ cssBundleEntryModulePlugin(config),
+ cssModulesPlugin({ config, mode, outputCss: true }),
+ vanillaExtractPlugin({ config, mode, outputCss: true }),
+ cssSideEffectImportsPlugin({ config, options }),
cssFilePlugin({ config, options }),
absoluteCssUrlsPlugin(),
externalPlugin(/^https?:\/\//, { sideEffects: false }),
@@ -78,7 +61,7 @@ const createEsbuildConfig = (
emptyModulesPlugin(config, /\.server(\.[jt]sx?)?$/),
NodeModulesPolyfillPlugin(),
externalPlugin(/^node:.*/, { sideEffects: false }),
- ].filter(isNotNull);
+ ];
return {
entryPoints: {
@@ -87,17 +70,7 @@ const createEsbuildConfig = (
outdir: config.assetsBuildDirectory,
platform: "browser",
format: "esm",
- external: [
- // This allows Vanilla Extract to bundle asset imports, e.g. `import href
- // from './image.svg'` resolves to a string like "/build/_assets/XXXX.svg"
- // which will then appear in the compiled CSS, e.g. `background:
- // url("/build/_assets/XXXX.svg")`. If we don't mark this path as
- // external, esbuild will try to bundle it again but won't find it.
- config.future.unstable_vanillaExtract
- ? `${config.publicPath}_assets/*`
- : null,
- ...getExternals(config),
- ].filter(isNotNull),
+ external: getExternals(config),
loader: loaders,
bundle: true,
logLevel: "silent",
@@ -139,10 +112,6 @@ export let create = async (
write: false,
});
let compile = async () => {
- if (!isCssBundlingEnabled(remixConfig)) {
- return;
- }
-
try {
let { outputFiles } = await ctx.rebuild();
diff --git a/packages/remix-dev/compiler/assets/js.ts b/packages/remix-dev/compiler/assets/js.ts
index cf251babcd5..c0982c9d71e 100644
--- a/packages/remix-dev/compiler/assets/js.ts
+++ b/packages/remix-dev/compiler/assets/js.ts
@@ -69,13 +69,6 @@ const getExternals = (remixConfig: RemixConfig): string[] => {
return nodeBuiltins.filter((mod) => !dependencies.includes(mod));
};
-const isCssBundlingEnabled = (config: RemixConfig): boolean =>
- Boolean(
- config.future.unstable_cssModules ||
- config.future.unstable_cssSideEffectImports ||
- config.future.unstable_vanillaExtract
- );
-
const createEsbuildConfig = (
config: RemixConfig,
options: CompileOptions,
@@ -109,15 +102,9 @@ const createEsbuildConfig = (
let plugins: esbuild.Plugin[] = [
deprecatedRemixPackagePlugin(options.onWarning),
- config.future.unstable_cssModules
- ? cssModulesPlugin({ config, mode, outputCss: false })
- : null,
- config.future.unstable_vanillaExtract
- ? vanillaExtractPlugin({ config, mode, outputCss: false })
- : null,
- config.future.unstable_cssSideEffectImports
- ? cssSideEffectImportsPlugin({ config, options })
- : null,
+ cssModulesPlugin({ config, mode, outputCss: false }),
+ vanillaExtractPlugin({ config, mode, outputCss: false }),
+ cssSideEffectImportsPlugin({ config, options }),
cssFilePlugin({ config, options }),
absoluteCssUrlsPlugin(),
externalPlugin(/^https?:\/\//, { sideEffects: false }),
@@ -169,7 +156,7 @@ const createEsbuildConfig = (
});
},
} as esbuild.Plugin,
- ].filter(isNotNull);
+ ];
if (mode === "development" && config.future.unstable_dev) {
// TODO prebundle deps instead of chunking just these ones
@@ -189,10 +176,7 @@ const createEsbuildConfig = (
};
plugins.push(hmrPlugin({ remixConfig: config }));
-
- if (isCssBundlingEnabled(config)) {
- plugins.push(cssBundleUpdatePlugin(channels));
- }
+ plugins.push(cssBundleUpdatePlugin(channels));
}
return {
@@ -200,17 +184,7 @@ const createEsbuildConfig = (
outdir: config.assetsBuildDirectory,
platform: "browser",
format: "esm",
- external: [
- // This allows Vanilla Extract to bundle asset imports, e.g. `import href
- // from './image.svg'` resolves to a string like "/build/_assets/XXXX.svg"
- // which will then appear in the compiled CSS, e.g. `background:
- // url("/build/_assets/XXXX.svg")`. If we don't mark this path as
- // external, esbuild will try to bundle it again but won't find it.
- config.future.unstable_vanillaExtract
- ? `${config.publicPath}_assets/*`
- : null,
- ...getExternals(config),
- ].filter(isNotNull),
+ external: getExternals(config),
loader: loaders,
bundle: true,
logLevel: "silent",
@@ -289,7 +263,3 @@ export const create = async (
dispose: ctx.dispose,
};
};
-
-function isNotNull(value: Value): value is Exclude {
- return value !== null;
-}
diff --git a/packages/remix-dev/compiler/plugins/vanillaExtract/cached.ts b/packages/remix-dev/compiler/plugins/vanillaExtract.ts
similarity index 50%
rename from packages/remix-dev/compiler/plugins/vanillaExtract/cached.ts
rename to packages/remix-dev/compiler/plugins/vanillaExtract.ts
index 2ae9498d5b6..3dbc9c24dbf 100644
--- a/packages/remix-dev/compiler/plugins/vanillaExtract/cached.ts
+++ b/packages/remix-dev/compiler/plugins/vanillaExtract.ts
@@ -3,13 +3,12 @@ import type { Compiler } from "@vanilla-extract/integration";
import { cssFileFilter, createCompiler } from "@vanilla-extract/integration";
import type { Plugin } from "esbuild";
-import type { RemixConfig } from "../../../config";
-import type { CompileOptions } from "../../options";
-import { loaders } from "../../utils/loaders";
-import { getPostcssProcessor } from "../../utils/postcss";
-import { vanillaExtractSideEffectsPlugin } from "./sideEffects";
+import type { RemixConfig } from "../../config";
+import type { CompileOptions } from "../options";
+import { loaders } from "../utils/loaders";
+import { getPostcssProcessor } from "../utils/postcss";
-const pluginName = "vanilla-extract-plugin-cached";
+const pluginName = "vanilla-extract-plugin";
const namespace = `${pluginName}-ns`;
const virtualCssFileFilter = /\.vanilla.css$/;
@@ -19,9 +18,42 @@ const staticAssetRegexp = new RegExp(
.join("|")})$`
);
-let compiler: Compiler;
+let compiler: Compiler | undefined;
+function getCompiler(root: string, mode: CompileOptions["mode"]) {
+ compiler =
+ compiler ||
+ createCompiler({
+ root,
+ identifiers: mode === "production" ? "short" : "debug",
+ vitePlugins: [
+ {
+ name: "remix-assets",
+ enforce: "pre",
+ async resolveId(source) {
+ // Handle root-relative imports within Vanilla Extract files
+ if (source.startsWith("~")) {
+ return await this.resolve(source.replace("~", ""));
+ }
+ // Handle static asset JS imports
+ if (source.startsWith("/") && staticAssetRegexp.test(source)) {
+ return {
+ external: true,
+ id: "~" + source,
+ };
+ }
+ },
+ transform(code) {
+ // Translate Vite's fs import format for root-relative imports
+ return code.replace(/\/@fs\/~\//g, "~/");
+ },
+ },
+ ],
+ });
+
+ return compiler;
+}
-export function vanillaExtractPluginCached({
+export function vanillaExtractPlugin({
config,
mode,
outputCss,
@@ -35,36 +67,6 @@ export function vanillaExtractPluginCached({
async setup(build) {
let root = config.appDirectory;
- compiler =
- compiler ||
- createCompiler({
- root,
- identifiers: mode === "production" ? "short" : "debug",
- vitePlugins: [
- {
- name: "remix-assets",
- enforce: "pre",
- async resolveId(source) {
- // Handle root-relative imports within Vanilla Extract files
- if (source.startsWith("~")) {
- return await this.resolve(source.replace("~", ""));
- }
- // Handle static asset JS imports
- if (source.startsWith("/") && staticAssetRegexp.test(source)) {
- return {
- external: true,
- id: "~" + source,
- };
- }
- },
- transform(code) {
- // Translate Vite's fs import format for root-relative imports
- return code.replace(/\/@fs\/~\//g, "~/");
- },
- },
- ],
- });
-
let postcssProcessor = await getPostcssProcessor({
config,
context: {
@@ -82,12 +84,41 @@ export function vanillaExtractPluginCached({
};
});
- vanillaExtractSideEffectsPlugin.setup(build);
+ // Mark all .css.ts/js files as having side effects. This is to ensure
+ // that all usages of `globalStyle` are included in the CSS bundle, even
+ // if a .css.ts/js file has no exports or is otherwise tree-shaken.
+ let preventInfiniteLoop = {};
+ build.onResolve(
+ { filter: /\.css(\.(j|t)sx?)?(\?.*)?$/, namespace: "file" },
+ async (args) => {
+ if (args.pluginData === preventInfiniteLoop) {
+ return null;
+ }
+
+ let resolvedPath = (
+ await build.resolve(args.path, {
+ resolveDir: args.resolveDir,
+ kind: args.kind,
+ pluginData: preventInfiniteLoop,
+ })
+ ).path;
+
+ if (!cssFileFilter.test(resolvedPath)) {
+ return null;
+ }
+
+ return {
+ path: resolvedPath,
+ sideEffects: true,
+ };
+ }
+ );
build.onLoad(
{ filter: virtualCssFileFilter, namespace },
async ({ path }) => {
let [relativeFilePath] = path.split(".vanilla.css");
+ let compiler = getCompiler(root, mode);
let { css, filePath } = compiler.getCssForFile(relativeFilePath);
let resolveDir = dirname(resolve(root, filePath));
@@ -109,6 +140,7 @@ export function vanillaExtractPluginCached({
);
build.onLoad({ filter: cssFileFilter }, async ({ path: filePath }) => {
+ let compiler = getCompiler(root, mode);
let { source, watchFiles } = await compiler.processVanillaFile(
filePath,
{ outputCss }
diff --git a/packages/remix-dev/compiler/plugins/vanillaExtract/index.ts b/packages/remix-dev/compiler/plugins/vanillaExtract/index.ts
deleted file mode 100644
index 9607fbf0b4c..00000000000
--- a/packages/remix-dev/compiler/plugins/vanillaExtract/index.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-import type { Plugin } from "esbuild";
-
-import type { RemixConfig, VanillaExtractOptions } from "../../../config";
-import type { CompileOptions } from "../../options";
-import { vanillaExtractPluginCached } from "./cached";
-import { vanillaExtractPluginUncached } from "./uncached";
-
-export function vanillaExtractPlugin(options: {
- config: RemixConfig;
- mode: CompileOptions["mode"];
- outputCss: boolean;
-}): Plugin {
- let defaultPluginOptions: Required = {
- cache: false,
- };
-
- let futureFlag = options.config.future.unstable_vanillaExtract;
- let pluginOptions = typeof futureFlag === "object" ? futureFlag : {};
-
- let { cache } = {
- ...defaultPluginOptions,
- ...pluginOptions,
- };
-
- let resolvedPlugin = cache
- ? vanillaExtractPluginCached
- : vanillaExtractPluginUncached;
-
- return resolvedPlugin(options);
-}
diff --git a/packages/remix-dev/compiler/plugins/vanillaExtract/sideEffects.ts b/packages/remix-dev/compiler/plugins/vanillaExtract/sideEffects.ts
deleted file mode 100644
index c560bedc701..00000000000
--- a/packages/remix-dev/compiler/plugins/vanillaExtract/sideEffects.ts
+++ /dev/null
@@ -1,40 +0,0 @@
-import type { Plugin } from "esbuild";
-import { cssFileFilter } from "@vanilla-extract/integration";
-
-/**
- * This plugin marks all .css.ts/js files as having side effects. This is
- * to ensure that all usages of `globalStyle` are included in the CSS bundle,
- * even if a .css.ts/js file has no exports or is otherwise tree-shaken.
- */
-export const vanillaExtractSideEffectsPlugin: Plugin = {
- name: "vanilla-extract-side-effects-plugin",
- setup(build) {
- let preventInfiniteLoop = {};
-
- build.onResolve(
- { filter: /\.css(\.(j|t)sx?)?(\?.*)?$/, namespace: "file" },
- async (args) => {
- if (args.pluginData === preventInfiniteLoop) {
- return null;
- }
-
- let resolvedPath = (
- await build.resolve(args.path, {
- resolveDir: args.resolveDir,
- kind: args.kind,
- pluginData: preventInfiniteLoop,
- })
- ).path;
-
- if (!cssFileFilter.test(resolvedPath)) {
- return null;
- }
-
- return {
- path: resolvedPath,
- sideEffects: true,
- };
- }
- );
- },
-};
diff --git a/packages/remix-dev/compiler/plugins/vanillaExtract/uncached.ts b/packages/remix-dev/compiler/plugins/vanillaExtract/uncached.ts
deleted file mode 100644
index 06a50a14cd8..00000000000
--- a/packages/remix-dev/compiler/plugins/vanillaExtract/uncached.ts
+++ /dev/null
@@ -1,182 +0,0 @@
-import { dirname, join, extname } from "path";
-import type { IdentifierOption } from "@vanilla-extract/integration";
-import {
- cssFileFilter,
- virtualCssFileFilter,
- processVanillaFile,
- getSourceFromVirtualCssFile,
- transform,
-} from "@vanilla-extract/integration";
-import * as fse from "fs-extra";
-import * as esbuild from "esbuild";
-
-import type { RemixConfig } from "../../../config";
-import type { CompileOptions } from "../../options";
-import { loaders } from "../../utils/loaders";
-import { getPostcssProcessor } from "../../utils/postcss";
-import { vanillaExtractSideEffectsPlugin } from "./sideEffects";
-
-const pluginName = "vanilla-extract-plugin-uncached";
-const namespace = `${pluginName}-ns`;
-
-export function vanillaExtractPluginUncached({
- config,
- mode,
- outputCss,
-}: {
- config: RemixConfig;
- mode: CompileOptions["mode"];
- outputCss: boolean;
-}): esbuild.Plugin {
- return {
- name: pluginName,
- async setup(build) {
- let postcssProcessor = await getPostcssProcessor({
- config,
- context: {
- vanillaExtract: true,
- },
- });
- let { rootDirectory } = config;
-
- // Resolve virtual CSS files first to avoid resolving the same
- // file multiple times since this filter is more specific and
- // doesn't require a file system lookup.
- build.onResolve({ filter: virtualCssFileFilter }, (args) => {
- return {
- path: args.path,
- namespace,
- };
- });
-
- vanillaExtractSideEffectsPlugin.setup(build);
-
- build.onLoad(
- { filter: virtualCssFileFilter, namespace },
- async ({ path }) => {
- let { source, fileName } = await getSourceFromVirtualCssFile(path);
- let resolveDir = dirname(join(rootDirectory, fileName));
-
- if (postcssProcessor) {
- source = (
- await postcssProcessor.process(source, {
- from: path,
- to: path,
- })
- ).css;
- }
-
- return {
- contents: source,
- loader: "css",
- resolveDir,
- };
- }
- );
-
- build.onLoad({ filter: cssFileFilter }, async ({ path: filePath }) => {
- let identOption: IdentifierOption =
- mode === "production" ? "short" : "debug";
-
- let { outputFiles } = await esbuild.build({
- entryPoints: [filePath],
- outdir: config.assetsBuildDirectory,
- assetNames: build.initialOptions.assetNames,
- bundle: true,
- external: ["@vanilla-extract"],
- platform: "node",
- write: false,
- plugins: [
- vanillaExtractSideEffectsPlugin,
- vanillaExtractTransformPlugin({ rootDirectory, identOption }),
- ],
- loader: loaders,
- absWorkingDir: rootDirectory,
- publicPath: config.publicPath,
- });
-
- let source = outputFiles.find((file) =>
- file.path.endsWith(".js")
- )?.text;
-
- if (!source) {
- return null;
- }
-
- let [contents] = await Promise.all([
- processVanillaFile({
- source,
- filePath,
- outputCss,
- identOption,
- }),
- outputCss && writeAssets(outputFiles),
- ]);
-
- return {
- contents,
- resolveDir: dirname(filePath),
- loader: "js",
- };
- });
- },
- };
-}
-
-async function writeAssets(
- outputFiles: Array
-): Promise {
- await Promise.all(
- outputFiles
- .filter((file) => !file.path.endsWith(".js"))
- .map(async (file) => {
- await fse.ensureDir(dirname(file.path));
- await fse.writeFile(file.path, file.contents);
- })
- );
-}
-
-const loaderForExtension: Record = {
- ".js": "js",
- ".jsx": "jsx",
- ".ts": "ts",
- ".tsx": "tsx",
-};
-
-/**
- * This plugin is used within the child compilation. It applies the Vanilla
- * Extract file transform to all .css.ts/js files. This is used to add "file
- * scope" annotations, which is done via function calls at the beginning and end
- * of each file so that we can tell which CSS file the styles belong to when
- * evaluating the JS. It's also done to automatically apply debug IDs.
- */
-function vanillaExtractTransformPlugin({
- rootDirectory,
- identOption,
-}: {
- identOption: IdentifierOption;
- rootDirectory: string;
-}): esbuild.Plugin {
- return {
- name: "vanilla-extract-transform-plugin",
- setup(build) {
- build.onLoad({ filter: cssFileFilter }, async ({ path }) => {
- let source = await fse.readFile(path, "utf-8");
-
- let contents = await transform({
- source,
- filePath: path,
- rootPath: rootDirectory,
- packageName: "remix-app", // This option is designed to support scoping hashes for libraries, we can hard code an arbitrary value for simplicity
- identOption,
- });
-
- return {
- contents,
- loader: loaderForExtension[extname(path)],
- resolveDir: dirname(path),
- };
- });
- },
- };
-}
diff --git a/packages/remix-dev/compiler/server/index.ts b/packages/remix-dev/compiler/server/index.ts
index 95c57419c12..eed672b6d87 100644
--- a/packages/remix-dev/compiler/server/index.ts
+++ b/packages/remix-dev/compiler/server/index.ts
@@ -47,19 +47,12 @@ const createEsbuildConfig = (
}
let { mode } = options;
- let outputCss = false;
let plugins: esbuild.Plugin[] = [
deprecatedRemixPackagePlugin(options.onWarning),
- config.future.unstable_cssModules
- ? cssModulesPlugin({ config, mode, outputCss })
- : null,
- config.future.unstable_vanillaExtract
- ? vanillaExtractPlugin({ config, mode, outputCss })
- : null,
- config.future.unstable_cssSideEffectImports
- ? cssSideEffectImportsPlugin({ config, options })
- : null,
+ cssModulesPlugin({ config, mode, outputCss: false }),
+ vanillaExtractPlugin({ config, mode, outputCss: false }),
+ cssSideEffectImportsPlugin({ config, options }),
cssFilePlugin({ config, options }),
absoluteCssUrlsPlugin(),
externalPlugin(/^https?:\/\//, { sideEffects: false }),
@@ -70,7 +63,7 @@ const createEsbuildConfig = (
serverAssetsManifestPlugin(channels),
serverBareModulesPlugin(config, options.onWarning),
externalPlugin(/^node:.*/, { sideEffects: false }),
- ].filter(isNotNull);
+ ];
if (config.serverPlatform !== "node") {
plugins.unshift(NodeModulesPolyfillPlugin());
@@ -181,7 +174,3 @@ export const create = async (
dispose: () => undefined,
};
};
-
-function isNotNull(value: Value): value is Exclude {
- return value !== null;
-}
diff --git a/packages/remix-dev/config.ts b/packages/remix-dev/config.ts
index a6b8e340463..0aaf2579eeb 100644
--- a/packages/remix-dev/config.ts
+++ b/packages/remix-dev/config.ts
@@ -44,19 +44,12 @@ type Dev = {
rebuildPollIntervalMs?: number;
};
-export type VanillaExtractOptions = {
- cache?: boolean;
-};
-
interface FutureConfig {
- unstable_cssModules: boolean;
- unstable_cssSideEffectImports: boolean;
unstable_dev: boolean | Dev;
/** @deprecated Use the `postcss` config option instead */
unstable_postcss: boolean;
/** @deprecated Use the `tailwind` config option instead */
unstable_tailwind: boolean;
- unstable_vanillaExtract: boolean | VanillaExtractOptions;
v2_errorBoundary: boolean;
v2_meta: boolean;
v2_normalizeFormMethod: boolean;
@@ -481,16 +474,36 @@ export async function readConfig(
serverModuleFormat === "esm" ? ["module", "main"] : ["main", "module"];
serverMinify ??= false;
- if (appConfig.future?.unstable_postcss !== undefined) {
- warnOnce(
- 'The "future.unstable_postcss" config option is deprecated as this feature is now considered stable. Use the "postcss" config option instead.'
- );
- }
+ if (appConfig.future) {
+ if ("unstable_cssModules" in appConfig.future) {
+ warnOnce(
+ 'The "future.unstable_cssModules" config option has been removed as this feature is now enabled automatically.'
+ );
+ }
- if (appConfig.future?.unstable_tailwind !== undefined) {
- warnOnce(
- 'The "future.unstable_tailwind" config option is deprecated as this feature is now considered stable. Use the "tailwind" config option instead.'
- );
+ if ("unstable_cssSideEffectImports" in appConfig.future) {
+ warnOnce(
+ 'The "future.unstable_cssSideEffectImports" config option has been removed as this feature is now enabled automatically.'
+ );
+ }
+
+ if ("unstable_vanillaExtract" in appConfig.future) {
+ warnOnce(
+ 'The "future.unstable_vanillaExtract" config option has been removed as this feature is now enabled automatically.'
+ );
+ }
+
+ if (appConfig.future.unstable_postcss !== undefined) {
+ warnOnce(
+ 'The "future.unstable_postcss" config option has been deprecated as this feature is now considered stable. Use the "postcss" config option instead.'
+ );
+ }
+
+ if (appConfig.future.unstable_tailwind !== undefined) {
+ warnOnce(
+ 'The "future.unstable_tailwind" config option has been deprecated as this feature is now considered stable. Use the "tailwind" config option instead.'
+ );
+ }
}
let mdx = appConfig.mdx;
@@ -716,13 +729,9 @@ export async function readConfig(
}
let future: FutureConfig = {
- unstable_cssModules: appConfig.future?.unstable_cssModules === true,
- unstable_cssSideEffectImports:
- appConfig.future?.unstable_cssSideEffectImports === true,
unstable_dev: appConfig.future?.unstable_dev ?? false,
unstable_postcss: appConfig.future?.unstable_postcss === true,
unstable_tailwind: appConfig.future?.unstable_tailwind === true,
- unstable_vanillaExtract: appConfig.future?.unstable_vanillaExtract ?? false,
v2_errorBoundary: appConfig.future?.v2_errorBoundary === true,
v2_meta: appConfig.future?.v2_meta === true,
v2_normalizeFormMethod: appConfig.future?.v2_normalizeFormMethod === true,
diff --git a/packages/remix-react/entry.ts b/packages/remix-react/entry.ts
index 62dbf42251a..4df10fc98db 100644
--- a/packages/remix-react/entry.ts
+++ b/packages/remix-react/entry.ts
@@ -26,19 +26,12 @@ type Dev = {
rebuildPollIntervalMs?: number;
};
-type VanillaExtractOptions = {
- cache?: boolean;
-};
-
export interface FutureConfig {
- unstable_cssModules: boolean;
- unstable_cssSideEffectImports: boolean;
unstable_dev: boolean | Dev;
/** @deprecated Use the `postcss` config option instead */
unstable_postcss: boolean;
/** @deprecated Use the `tailwind` config option instead */
unstable_tailwind: boolean;
- unstable_vanillaExtract: boolean | VanillaExtractOptions;
v2_errorBoundary: boolean;
v2_meta: boolean;
v2_normalizeFormMethod: boolean;
diff --git a/packages/remix-server-runtime/entry.ts b/packages/remix-server-runtime/entry.ts
index d9721842512..1191e01ccfc 100644
--- a/packages/remix-server-runtime/entry.ts
+++ b/packages/remix-server-runtime/entry.ts
@@ -18,19 +18,12 @@ type Dev = {
rebuildPollIntervalMs?: number;
};
-type VanillaExtractOptions = {
- cache?: boolean;
-};
-
export interface FutureConfig {
- unstable_cssModules: boolean;
- unstable_cssSideEffectImports: boolean;
unstable_dev: boolean | Dev;
/** @deprecated Use the `postcss` config option instead */
unstable_postcss: boolean;
/** @deprecated Use the `tailwind` config option instead */
unstable_tailwind: boolean;
- unstable_vanillaExtract: boolean | VanillaExtractOptions;
v2_errorBoundary: boolean;
v2_meta: boolean;
v2_normalizeFormMethod: boolean;
diff --git a/packages/remix-testing/create-remix-stub.tsx b/packages/remix-testing/create-remix-stub.tsx
index 3d9749e6812..93f7e684ff2 100644
--- a/packages/remix-testing/create-remix-stub.tsx
+++ b/packages/remix-testing/create-remix-stub.tsx
@@ -63,12 +63,9 @@ export function createRemixStub(routes: (RouteObject | DataRouteObject)[]) {
if (remixContextRef.current == null) {
remixContextRef.current = {
future: {
- unstable_cssModules: false,
- unstable_cssSideEffectImports: false,
unstable_dev: false,
unstable_postcss: false,
unstable_tailwind: false,
- unstable_vanillaExtract: false,
v2_errorBoundary: false,
v2_meta: false,
v2_normalizeFormMethod: false,