diff --git a/.changeset/giant-kings-repair.md b/.changeset/giant-kings-repair.md
new file mode 100644
index 00000000000..0afc9d0b354
--- /dev/null
+++ b/.changeset/giant-kings-repair.md
@@ -0,0 +1,8 @@
+---
+"@remix-run/dev": minor
+"@remix-run/react": minor
+"@remix-run/server-runtime": minor
+"@remix-run/testing": minor
+---
+
+Add unstable support for [Vanilla Extract](https://vanilla-extract.style) via the `future.unstable_vanillaExtract` feature flag
diff --git a/.changeset/hungry-owls-greet.md b/.changeset/hungry-owls-greet.md
index 3257dafd368..a40b1fdcef3 100644
--- a/.changeset/hungry-owls-greet.md
+++ b/.changeset/hungry-owls-greet.md
@@ -5,4 +5,4 @@
"@remix-run/testing": minor
---
-Add unstable support for CSS side-effect imports via `future.unstable_cssSideEffectImports` feature flag
+Add unstable support for CSS side-effect imports via the `future.unstable_cssSideEffectImports` feature flag
diff --git a/.changeset/little-avocados-fetch.md b/.changeset/little-avocados-fetch.md
index a090ecb9575..b8873ce2946 100644
--- a/.changeset/little-avocados-fetch.md
+++ b/.changeset/little-avocados-fetch.md
@@ -6,4 +6,4 @@
"@remix-run/testing": minor
---
-Add unstable support for CSS Modules via `future.unstable_cssModules` feature flag
+Add unstable support for CSS Modules via the `future.unstable_cssModules` feature flag
diff --git a/docs/guides/styling.md b/docs/guides/styling.md
index ff57b457700..672be5f35db 100644
--- a/docs/guides/styling.md
+++ b/docs/guides/styling.md
@@ -772,7 +772,7 @@ NOTE: You may run into hydration warnings when using Styled Components. Hopefull
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.
-When using CSS bundling features, the Remix compiler will generate a single CSS file containing all bundled styles in your application. Note that any [regular stylesheet imports][regular-stylesheet-imports-2] will remain as separate files.
+When using CSS bundling features, the Remix compiler will generate a single CSS file containing all bundled styles in your application. Note that any [regular stylesheet imports][regular-stylesheet-imports] will remain as separate files.
Unlike many other tools in the React ecosystem, we do not insert the CSS bundle into the page automatically. Instead, we ensure that you always have control over the link tags on your page. This lets you decide where the CSS file is loaded relative to other stylesheets in your app.
@@ -790,7 +790,7 @@ import { cssBundleHref } from "@remix-run/css-bundle";
export const links: LinksFunction = () => {
return [
- { rel: "stylesheet", href: cssBundleHref },
+ ...(cssBundleHref ? [{ rel: "stylesheet", href: cssBundleHref }] : []),
// ...
];
};
@@ -843,6 +843,61 @@ export const Button = React.forwardRef(
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.
+
+Next, 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:
+
+```ts filename=app/components/button/styles.css.ts
+import { style } from "@vanilla-extract/css";
+
+export const root = style({
+ border: 'solid 1px',
+ background: 'white',
+ color: '#454545',
+});
+```
+
+```tsx filename=app/components/button/index.js lines=[1,9]
+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
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.
@@ -889,5 +944,6 @@ module.exports = {
[css modules]: https://github.com/css-modules/css-modules
[regular-stylesheet-imports]: #regular-stylesheets
[server-dependencies-to-bundle]: ../file-conventions/remix-config#serverdependenciestobundle
-[regular-stylesheet-imports-2]: #regular-stylesheets
[css-bundling]: #css-bundling
+[vanilla-extract]: https://vanilla-extract.style
+[sprinkles]: https://vanilla-extract.style/documentation/packages/sprinkles
\ No newline at end of file
diff --git a/integration/css-modules-test.ts b/integration/css-modules-test.ts
index ecba714980e..b173a9b299b 100644
--- a/integration/css-modules-test.ts
+++ b/integration/css-modules-test.ts
@@ -27,6 +27,7 @@ test.describe("CSS Modules", () => {
// ensure features don't clash
unstable_cssModules: true,
unstable_cssSideEffectImports: true,
+ unstable_vanillaExtract: true,
},
};
`,
diff --git a/integration/css-side-effect-imports-test.ts b/integration/css-side-effect-imports-test.ts
index fc4c0997382..4f51ff64d76 100644
--- a/integration/css-side-effect-imports-test.ts
+++ b/integration/css-side-effect-imports-test.ts
@@ -26,6 +26,7 @@ test.describe("CSS side-effect imports", () => {
// ensure features don't clash
unstable_cssModules: true,
unstable_cssSideEffectImports: true,
+ unstable_vanillaExtract: true,
},
};
`,
diff --git a/integration/deterministic-build-output-test.ts b/integration/deterministic-build-output-test.ts
index 0e47ebbe6e2..3089996411b 100644
--- a/integration/deterministic-build-output-test.ts
+++ b/integration/deterministic-build-output-test.ts
@@ -23,6 +23,7 @@ test("builds deterministically under different paths", async () => {
// * serverAssetsManifestPlugin (implicitly tested by build)
// * serverEntryModulePlugin (implicitly tested by build)
// * serverRouteModulesPlugin (implicitly tested by build)
+ // * vanillaExtractPlugin (via app/routes/foo.tsx' .css.ts file import)
let init = {
files: {
"remix.config.js": js`
@@ -30,6 +31,7 @@ test("builds deterministically under different paths", async () => {
future: {
unstable_cssModules: true,
unstable_cssSideEffectImports: true,
+ unstable_vanillaExtract: true,
},
};
`,
@@ -37,8 +39,9 @@ test("builds deterministically under different paths", async () => {
"app/routes/foo.tsx": js`
export * from "~/foo/bar.server";
import styles from "~/styles/foo.module.css";
+ import { vanilla } from "~/styles/vanilla.css";
import "~/styles/side-effect.css";
- export default () =>
YAY
;
+ export default () => YAY
;
`,
"app/foo/bar.server.ts": "export const meta = () => []",
"app/styles/foo.module.css": css`
@@ -63,6 +66,28 @@ test("builds deterministically under different paths", async () => {
`,
+ "app/styles/vanilla.css.ts": css`
+ 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": css`
+ import { style } from "@vanilla-extract/css";
+
+ export const chocolate = style({
+ color: "chocolate",
+ });
+ `,
"app/styles/side-effect.css": css`
.side-effect {
color: mintcream;
diff --git a/integration/vanilla-extract-test.ts b/integration/vanilla-extract-test.ts
new file mode 100644
index 00000000000..7cc199eb20f
--- /dev/null
+++ b/integration/vanilla-extract-test.ts
@@ -0,0 +1,640 @@
+import { test, expect } from "@playwright/test";
+
+import { PlaywrightFixture } from "./helpers/playwright-fixture";
+import type { Fixture, AppFixture } from "./helpers/create-fixture";
+import { createAppFixture, createFixture, js } from "./helpers/create-fixture";
+
+const TEST_PADDING_VALUE = "20px";
+
+test.describe("Vanilla Extract", () => {
+ let fixture: Fixture;
+ let appFixture: AppFixture;
+
+ test.beforeAll(async () => {
+ fixture = await createFixture({
+ files: {
+ "remix.config.js": js`
+ module.exports = {
+ future: {
+ // Enable all CSS future flags to
+ // ensure features don't clash
+ unstable_cssModules: true,
+ unstable_cssSideEffectImports: true,
+ unstable_vanillaExtract: true,
+ },
+ };
+ `,
+ "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(),
+ ...stableIdentifiersFixture(),
+ ...imageUrlsViaCssUrlFixture(),
+ ...imageUrlsViaRootRelativeCssUrlFixture(),
+ ...imageUrlsViaJsImportFixture(),
+ ...imageUrlsViaRootRelativeJsImportFixture(),
+ ...imageUrlsViaClassCompositionFixture(),
+ ...imageUrlsViaJsImportClassCompositionFixture(),
+ ...standardImageUrlsViaJsImportFixture(),
+ ...standardImageUrlsViaRootRelativeJsImportFixture(),
+ },
+ });
+ appFixture = await createAppFixture(fixture);
+ });
+
+ test.afterAll(async () => {
+ await appFixture.close();
+ });
+
+ 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("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() {
+ 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)}
+ });
+ `,
+ "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";
+
+ 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 stableIdentifiersFixture = () => ({
+ "app/fixtures/stable-identifiers/styles_a.css.ts": js`
+ import { style } from "@vanilla-extract/css";
+ import { shared } from "./shared.css";
+
+ export const root = style([shared]);
+ `,
+ "app/fixtures/stable-identifiers/styles_b.css.ts": js`
+ import { style } from "@vanilla-extract/css";
+ import { shared } from "./shared.css";
+
+ export const root = style([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 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);
+ });
+
+ 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
+
+ )
+ }
+ `,
+ });
+ 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 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
+
+ )
+ }
+ `,
+ });
+ 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";
+
+ 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)',
+ });
+ `,
+ "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);
+ });
+
+ let standardImageUrlsViaJsImportFixture = () => ({
+ "app/fixtures/standardImageUrlsViaJsImport/styles.css.ts": js`
+ import { style } from "@vanilla-extract/css";
+
+ export { default as src } from "./image.svg";
+
+ export const root = style({
+ width: 200,
+ height: 200,
+ });
+ `,
+ "app/fixtures/standardImageUrlsViaJsImport/image.svg": `
+
+
+
+ `,
+ "app/routes/standard-image-urls-via-js-import-test.jsx": js`
+ import { root, src } from "../fixtures/standardImageUrlsViaJsImport/styles.css";
+
+ export default function() {
+ return (
+
+ )
+ }
+ `,
+ });
+ test("standard image URLs via JS import", async ({ page }) => {
+ // This ensures that image URLs are fully resolved within the CSS file
+ // rather than using some intermediary format that needs to be resolved
+ // later. This is important to ensure that image import semantics are the
+ // same throughout the app, regardless of whether it's in a JS file or a
+ // Vanilla Extract context, e.g. you might want to export the image URL
+ // from the CSS file and use it for preloading.
+ 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("/standard-image-urls-via-js-import-test");
+ let element = await app.getElement(
+ "[data-testid='standard-image-urls-via-js-import']"
+ );
+ expect(element.attr("src")).toContain(".svg");
+ expect(imgStatus).toBe(200);
+ });
+
+ let standardImageUrlsViaRootRelativeJsImportFixture = () => ({
+ "app/fixtures/standardImageUrlsViaRootRelativeJsImport/styles.css.ts": js`
+ import { style } from "@vanilla-extract/css";
+
+ export { default as src } from "~/fixtures/standardImageUrlsViaRootRelativeJsImport/image.svg";
+
+ export const root = style({
+ width: 200,
+ height: 200,
+ });
+ `,
+ "app/fixtures/standardImageUrlsViaRootRelativeJsImport/image.svg": `
+
+
+
+ `,
+ "app/routes/standard-image-urls-via-root-relative-js-import-test.jsx": js`
+ import { root, src } from "../fixtures/standardImageUrlsViaRootRelativeJsImport/styles.css";
+
+ export default function() {
+ return (
+
+ )
+ }
+ `,
+ });
+ test("standard 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("/standard-image-urls-via-root-relative-js-import-test");
+ let element = await app.getElement(
+ "[data-testid='standard-image-urls-via-root-relative-js-import']"
+ );
+ expect(element.attr("src")).toContain(".svg");
+ expect(imgStatus).toBe(200);
+ });
+});
diff --git a/package.json b/package.json
index ee176c5237e..b8d6c4ac797 100644
--- a/package.json
+++ b/package.json
@@ -85,6 +85,7 @@
"@types/semver": "^7.3.4",
"@types/ssri": "^7.1.0",
"@types/use-sync-external-store": "^0.0.3",
+ "@vanilla-extract/css": "^1.9.2",
"abort-controller": "^3.0.0",
"abortcontroller-polyfill": "^1.7.3",
"aws-sdk": "^2.1055.0",
diff --git a/packages/remix-dev/__tests__/readConfig-test.ts b/packages/remix-dev/__tests__/readConfig-test.ts
index 761ec836e33..a88bb37f599 100644
--- a/packages/remix-dev/__tests__/readConfig-test.ts
+++ b/packages/remix-dev/__tests__/readConfig-test.ts
@@ -25,6 +25,7 @@ describe("readConfig", () => {
future: {
unstable_cssModules: expect.any(Boolean),
unstable_cssSideEffectImports: expect.any(Boolean),
+ unstable_vanillaExtract: expect.any(Boolean),
v2_meta: expect.any(Boolean),
v2_routeConvention: expect.any(Boolean),
},
@@ -41,6 +42,7 @@ describe("readConfig", () => {
"future": Object {
"unstable_cssModules": Any,
"unstable_cssSideEffectImports": Any,
+ "unstable_vanillaExtract": Any,
"v2_meta": Any,
"v2_routeConvention": Any,
},
diff --git a/packages/remix-dev/compiler/compileBrowser.ts b/packages/remix-dev/compiler/compileBrowser.ts
index c6b38d2f913..add67e8fdd0 100644
--- a/packages/remix-dev/compiler/compileBrowser.ts
+++ b/packages/remix-dev/compiler/compileBrowser.ts
@@ -21,6 +21,7 @@ import { mdxPlugin } from "./plugins/mdx";
import { urlImportsPlugin } from "./plugins/urlImportsPlugin";
import { cssModulesPlugin } from "./plugins/cssModulesPlugin";
import { cssSideEffectImportsPlugin } from "./plugins/cssSideEffectImportsPlugin";
+import { vanillaExtractPlugin } from "./plugins/vanillaExtractPlugin";
import {
cssBundleEntryModulePlugin,
cssBundleEntryModuleId,
@@ -69,7 +70,8 @@ const writeAssetsManifest = async (
const isCssBundlingEnabled = (config: RemixConfig) =>
config.future.unstable_cssModules ||
- config.future.unstable_cssSideEffectImports;
+ config.future.unstable_cssSideEffectImports ||
+ config.future.unstable_vanillaExtract;
const createEsbuildConfig = (
build: "app" | "css",
@@ -98,13 +100,18 @@ const createEsbuildConfig = (
let { mode } = options;
let { rootDirectory } = config;
+ let outputCss = isCssBuild;
+
let plugins: esbuild.Plugin[] = [
deprecatedRemixPackagePlugin(options.onWarning),
isCssBundlingEnabled(config) && isCssBuild
? cssBundleEntryModulePlugin(config)
: null,
config.future.unstable_cssModules
- ? cssModulesPlugin({ mode, rootDirectory, outputCss: isCssBuild })
+ ? cssModulesPlugin({ mode, rootDirectory, outputCss })
+ : null,
+ config.future.unstable_vanillaExtract
+ ? vanillaExtractPlugin({ config, mode, outputCss })
: null,
config.future.unstable_cssSideEffectImports
? cssSideEffectImportsPlugin({ rootDirectory })
@@ -122,7 +129,17 @@ const createEsbuildConfig = (
outdir: config.assetsBuildDirectory,
platform: "browser",
format: "esm",
- external: getExternals(config),
+ 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),
loader: loaders,
bundle: true,
logLevel: "silent",
diff --git a/packages/remix-dev/compiler/compilerServer.ts b/packages/remix-dev/compiler/compilerServer.ts
index 7040e315bad..9026f82495b 100644
--- a/packages/remix-dev/compiler/compilerServer.ts
+++ b/packages/remix-dev/compiler/compilerServer.ts
@@ -10,6 +10,7 @@ import { loaders } from "./loaders";
import type { CompileOptions } from "./options";
import { cssModulesPlugin } from "./plugins/cssModulesPlugin";
import { cssSideEffectImportsPlugin } from "./plugins/cssSideEffectImportsPlugin";
+import { vanillaExtractPlugin } from "./plugins/vanillaExtractPlugin";
import { cssFilePlugin } from "./plugins/cssFilePlugin";
import { deprecatedRemixPackagePlugin } from "./plugins/deprecatedRemixPackagePlugin";
import { emptyModulesPlugin } from "./plugins/emptyModulesPlugin";
@@ -51,10 +52,15 @@ const createEsbuildConfig = (
let { mode } = options;
let { rootDirectory } = config;
+ let outputCss = false;
+
let plugins: esbuild.Plugin[] = [
deprecatedRemixPackagePlugin(options.onWarning),
config.future.unstable_cssModules
- ? cssModulesPlugin({ mode, rootDirectory, outputCss: false })
+ ? cssModulesPlugin({ mode, rootDirectory, outputCss })
+ : null,
+ config.future.unstable_vanillaExtract
+ ? vanillaExtractPlugin({ config, mode, outputCss })
: null,
config.future.unstable_cssSideEffectImports
? cssSideEffectImportsPlugin({ rootDirectory })
diff --git a/packages/remix-dev/compiler/plugins/vanillaExtractPlugin.ts b/packages/remix-dev/compiler/plugins/vanillaExtractPlugin.ts
new file mode 100644
index 00000000000..9af8a589972
--- /dev/null
+++ b/packages/remix-dev/compiler/plugins/vanillaExtractPlugin.ts
@@ -0,0 +1,159 @@
+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 "../loaders";
+
+const pluginName = "vanilla-extract-plugin";
+const namespace = `${pluginName}-ns`;
+
+export function vanillaExtractPlugin({
+ config,
+ mode,
+ outputCss,
+}: {
+ config: RemixConfig;
+ mode: CompileOptions["mode"];
+ outputCss: boolean;
+}): esbuild.Plugin {
+ return {
+ name: pluginName,
+ setup(build) {
+ let { rootDirectory } = config;
+
+ build.onResolve({ filter: virtualCssFileFilter }, (args) => {
+ return {
+ path: args.path,
+ namespace,
+ };
+ });
+
+ build.onLoad(
+ { filter: virtualCssFileFilter, namespace },
+ async ({ path }) => {
+ let { source, fileName } = await getSourceFromVirtualCssFile(path);
+ let resolveDir = dirname(join(rootDirectory, fileName));
+
+ 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: [
+ 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/config.ts b/packages/remix-dev/config.ts
index 53839fd126b..1733a292604 100644
--- a/packages/remix-dev/config.ts
+++ b/packages/remix-dev/config.ts
@@ -35,6 +35,7 @@ export type ServerPlatform = "node" | "neutral";
interface FutureConfig {
unstable_cssModules: boolean;
unstable_cssSideEffectImports: boolean;
+ unstable_vanillaExtract: boolean;
v2_meta: boolean;
v2_routeConvention: boolean;
}
@@ -493,6 +494,7 @@ export async function readConfig(
unstable_cssModules: appConfig.future?.unstable_cssModules === true,
unstable_cssSideEffectImports:
appConfig.future?.unstable_cssSideEffectImports === true,
+ unstable_vanillaExtract: appConfig.future?.unstable_vanillaExtract === true,
v2_meta: appConfig.future?.v2_meta === true,
v2_routeConvention: appConfig.future?.v2_routeConvention === true,
};
diff --git a/packages/remix-dev/package.json b/packages/remix-dev/package.json
index 9fde13ffc0a..f0e6465598d 100644
--- a/packages/remix-dev/package.json
+++ b/packages/remix-dev/package.json
@@ -30,6 +30,7 @@
"@esbuild-plugins/node-modules-polyfill": "^0.1.4",
"@npmcli/package-json": "^2.0.0",
"@remix-run/server-runtime": "1.10.0",
+ "@vanilla-extract/integration": "^6.0.1",
"arg": "^5.0.1",
"cacache": "^15.0.5",
"chalk": "^4.1.2",
diff --git a/packages/remix-react/entry.ts b/packages/remix-react/entry.ts
index 79f489e0cdb..77f17b4988b 100644
--- a/packages/remix-react/entry.ts
+++ b/packages/remix-react/entry.ts
@@ -20,6 +20,7 @@ export interface EntryContext extends RemixContextObject {
export interface FutureConfig {
unstable_cssModules: boolean;
unstable_cssSideEffectImports: boolean;
+ unstable_vanillaExtract: boolean;
v2_meta: boolean;
}
diff --git a/packages/remix-server-runtime/entry.ts b/packages/remix-server-runtime/entry.ts
index af0958b48f7..152bf2ad13a 100644
--- a/packages/remix-server-runtime/entry.ts
+++ b/packages/remix-server-runtime/entry.ts
@@ -14,6 +14,7 @@ export interface EntryContext {
export interface FutureConfig {
unstable_cssModules: true;
unstable_cssSideEffectImports: boolean;
+ unstable_vanillaExtract: boolean;
v2_meta: boolean;
}
diff --git a/packages/remix-testing/create-remix-stub.tsx b/packages/remix-testing/create-remix-stub.tsx
index 31b7e610f68..fef6ea3021b 100644
--- a/packages/remix-testing/create-remix-stub.tsx
+++ b/packages/remix-testing/create-remix-stub.tsx
@@ -71,6 +71,7 @@ export function createRemixStub(routes: AgnosticDataRouteObject[]) {
v2_meta: false,
unstable_cssModules: false,
unstable_cssSideEffectImports: false,
+ unstable_vanillaExtract: false,
...remixConfigFuture,
},
manifest: createManifest(routes),
diff --git a/yarn.lock b/yarn.lock
index 5f60f00388f..40defc812a5 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -72,6 +72,27 @@
json5 "^2.2.1"
semver "^6.3.0"
+"@babel/core@^7.13.10":
+ version "7.20.5"
+ resolved "https://registry.npmjs.org/@babel/core/-/core-7.20.5.tgz#45e2114dc6cd4ab167f81daf7820e8fa1250d113"
+ integrity sha512-UdOWmk4pNWTm/4DlPUl/Pt4Gz4rcEMb7CY0Y3eJl5Yz1vI8ZJGmHWaVE55LoxRjdpx0z259GE9U5STA9atUinQ==
+ dependencies:
+ "@ampproject/remapping" "^2.1.0"
+ "@babel/code-frame" "^7.18.6"
+ "@babel/generator" "^7.20.5"
+ "@babel/helper-compilation-targets" "^7.20.0"
+ "@babel/helper-module-transforms" "^7.20.2"
+ "@babel/helpers" "^7.20.5"
+ "@babel/parser" "^7.20.5"
+ "@babel/template" "^7.18.10"
+ "@babel/traverse" "^7.20.5"
+ "@babel/types" "^7.20.5"
+ convert-source-map "^1.7.0"
+ debug "^4.1.0"
+ gensync "^1.0.0-beta.2"
+ json5 "^2.2.1"
+ semver "^6.3.0"
+
"@babel/core@^7.13.16", "@babel/core@^7.18.6", "@babel/core@^7.8.0":
version "7.20.2"
resolved "https://registry.npmjs.org/@babel/core/-/core-7.20.2.tgz"
@@ -132,7 +153,7 @@
"@jridgewell/gen-mapping" "^0.3.2"
jsesc "^2.5.1"
-"@babel/generator@^7.18.6":
+"@babel/generator@^7.18.6", "@babel/generator@^7.20.5":
version "7.20.5"
resolved "https://registry.npmjs.org/@babel/generator/-/generator-7.20.5.tgz#cb25abee3178adf58d6814b68517c62bdbfdda95"
integrity sha512-jl7JY2Ykn9S0yj4DQP82sYvPU+T3g0HFcWTqDLqiuA9tGRNIj9VfbtXGAYTTkyNEnQk1jkMGOdYka8aG/lulCA==
@@ -427,6 +448,15 @@
"@babel/traverse" "^7.19.0"
"@babel/types" "^7.19.0"
+"@babel/helpers@^7.20.5":
+ version "7.20.6"
+ resolved "https://registry.npmjs.org/@babel/helpers/-/helpers-7.20.6.tgz#e64778046b70e04779dfbdf924e7ebb45992c763"
+ integrity sha512-Pf/OjgfgFRW5bApskEz5pvidpim7tEDPlFtKcNRXWmfHGn9IEI2W2flqRQXTFb7gIPTyK++N6rVHuwKut4XK6w==
+ dependencies:
+ "@babel/template" "^7.18.10"
+ "@babel/traverse" "^7.20.5"
+ "@babel/types" "^7.20.5"
+
"@babel/highlight@^7.16.7", "@babel/highlight@^7.18.6":
version "7.18.6"
resolved "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz"
@@ -441,7 +471,7 @@
resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.20.2.tgz"
integrity sha512-afk318kh2uKbo7BEj2QtEi8HVCGrwHUffrYDy7dgVcSa2j9lY3LDjPzcyGdpX7xgm35aWqvciZJ4WKmdF/SxYg==
-"@babel/parser@^7.18.6":
+"@babel/parser@^7.18.6", "@babel/parser@^7.20.5":
version "7.20.5"
resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.20.5.tgz#7f3c7335fe417665d929f34ae5dceae4c04015e8"
integrity sha512-r27t/cy/m9uKLXQNWWebeCUHgnAZq0CpG1OwKRxzJMP1vpSU4bSIK2hq+/cp0bQxetkXx38n09rNu8jVkcK/zA==
@@ -1255,6 +1285,22 @@
debug "^4.1.0"
globals "^11.1.0"
+"@babel/traverse@^7.20.5":
+ version "7.20.5"
+ resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.5.tgz#78eb244bea8270fdda1ef9af22a5d5e5b7e57133"
+ integrity sha512-WM5ZNN3JITQIq9tFZaw1ojLU3WgWdtkxnhM1AegMS+PvHjkM5IXjmYEGY7yukz5XS4sJyEf2VzWjI8uAavhxBQ==
+ dependencies:
+ "@babel/code-frame" "^7.18.6"
+ "@babel/generator" "^7.20.5"
+ "@babel/helper-environment-visitor" "^7.18.9"
+ "@babel/helper-function-name" "^7.19.0"
+ "@babel/helper-hoist-variables" "^7.18.6"
+ "@babel/helper-split-export-declaration" "^7.18.6"
+ "@babel/parser" "^7.20.5"
+ "@babel/types" "^7.20.5"
+ debug "^4.1.0"
+ globals "^11.1.0"
+
"@babel/types@^7.0.0", "@babel/types@^7.16.7", "@babel/types@^7.18.0", "@babel/types@^7.18.10", "@babel/types@^7.18.2", "@babel/types@^7.18.6", "@babel/types@^7.19.0", "@babel/types@^7.20.0", "@babel/types@^7.20.2", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4":
version "7.20.2"
resolved "https://registry.npmjs.org/@babel/types/-/types-7.20.2.tgz"
@@ -1556,6 +1602,11 @@
dependencies:
"@edge-runtime/primitives" "^1.0.1"
+"@emotion/hash@^0.9.0":
+ version "0.9.0"
+ resolved "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.0.tgz#c5153d50401ee3c027a57a177bc269b16d889cb7"
+ integrity sha512-14FtKiHhy2QoPIzdTcvh//8OyBlknNs2nXRwIhG904opCby3l+9Xaf/wuPvICBF0rc1ZCNBd3nKe9cd2mecVkQ==
+
"@esbuild-plugins/node-modules-polyfill@^0.1.4":
version "0.1.4"
resolved "https://registry.npmjs.org/@esbuild-plugins/node-modules-polyfill/-/node-modules-polyfill-0.1.4.tgz"
@@ -3175,6 +3226,51 @@
"@typescript-eslint/types" "5.38.0"
eslint-visitor-keys "^3.3.0"
+"@vanilla-extract/babel-plugin-debug-ids@^1.0.0":
+ version "1.0.0"
+ resolved "https://registry.npmjs.org/@vanilla-extract/babel-plugin-debug-ids/-/babel-plugin-debug-ids-1.0.0.tgz#82e18dc1b9aa897e3a5798720d6b14cdfeffe908"
+ integrity sha512-Q2Nh/0FEAENfcphAv+fvcMoKfl3bhPWO/2x3MPviNAhsTsvuvYPuRtLjcXwoe4aJ8MxxI46JLY33j8NBEzpTIg==
+ dependencies:
+ "@babel/core" "^7.13.10"
+
+"@vanilla-extract/css@^1.9.2":
+ version "1.9.2"
+ resolved "https://registry.npmjs.org/@vanilla-extract/css/-/css-1.9.2.tgz#2c4bcc58f3c7441a1d041083c76bf5c3e2c77675"
+ integrity sha512-CE5+R89LOl9XG5dRwEIvVyl/YcS2GkqjdE/XnGJ+p7Fp6Exu08fifv7tY87XxFeCIRAbc9psM+h4lF+wC3Y0fg==
+ dependencies:
+ "@emotion/hash" "^0.9.0"
+ "@vanilla-extract/private" "^1.0.3"
+ ahocorasick "1.0.2"
+ chalk "^4.1.1"
+ css-what "^5.0.1"
+ cssesc "^3.0.0"
+ csstype "^3.0.7"
+ deep-object-diff "^1.1.0"
+ deepmerge "^4.2.2"
+ media-query-parser "^2.0.2"
+ outdent "^0.8.0"
+
+"@vanilla-extract/integration@^6.0.1":
+ version "6.0.1"
+ resolved "https://registry.npmjs.org/@vanilla-extract/integration/-/integration-6.0.1.tgz#bd2493cad9b94d62b3e409562a89e70675b2325b"
+ integrity sha512-8D2JdBTH6UEao5Tm50m1qtY63JP4hDxiv/sNvgj2+ix/9M5RML9sa0diS80u0hW/r26+/ZsdzoA5YIbg6ghOMw==
+ dependencies:
+ "@babel/core" "^7.13.10"
+ "@babel/plugin-syntax-typescript" "^7.18.6"
+ "@vanilla-extract/babel-plugin-debug-ids" "^1.0.0"
+ "@vanilla-extract/css" "^1.9.2"
+ esbuild "^0.11.16"
+ eval "0.1.6"
+ find-up "^5.0.0"
+ javascript-stringify "^2.0.1"
+ lodash "^4.17.21"
+ outdent "^0.8.0"
+
+"@vanilla-extract/private@^1.0.3":
+ version "1.0.3"
+ resolved "https://registry.npmjs.org/@vanilla-extract/private/-/private-1.0.3.tgz#7ec72bc2ff6fe51f9d650f962e8d1989b073690f"
+ integrity sha512-17kVyLq3ePTKOkveHxXuIJZtGYs+cSoev7BlP+Lf4916qfDhk/HBjvlYDe8egrea7LNPHKwSZJK/bzZC+Q6AwQ==
+
"@vercel/build-utils@5.0.1":
version "5.0.1"
resolved "https://registry.npmjs.org/@vercel/build-utils/-/build-utils-5.0.1.tgz"
@@ -3308,6 +3404,11 @@ aggregate-error@^3.0.0:
clean-stack "^2.0.0"
indent-string "^4.0.0"
+ahocorasick@1.0.2:
+ version "1.0.2"
+ resolved "https://registry.npmjs.org/ahocorasick/-/ahocorasick-1.0.2.tgz#9eee93aef9d02bfb476d9b648d9b7a40ef2fd500"
+ integrity sha512-hCOfMzbFx5IDutmWLAt6MZwOUjIfSM9G9FyVxytmE4Rs/5YDPWQrD/+IR1w+FweD9H2oOZEnv36TmkjhNURBVA==
+
ajv@8.6.3:
version "8.6.3"
resolved "https://registry.npmjs.org/ajv/-/ajv-8.6.3.tgz"
@@ -4845,6 +4946,11 @@ csstype@^3.0.2:
resolved "https://registry.npmjs.org/csstype/-/csstype-3.0.9.tgz"
integrity sha512-rpw6JPxK6Rfg1zLOYCSwle2GFOOsnjmDYDaBwEcwoOg4qlsIVCN789VkBZDJAGi4T07gI4YSutR43t9Zz4Lzuw==
+csstype@^3.0.7:
+ version "3.1.1"
+ resolved "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz#841b532c45c758ee546a11d5bd7b7b473c8c30b9"
+ integrity sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==
+
csv-generate@^3.4.3:
version "3.4.3"
resolved "https://registry.npmjs.org/csv-generate/-/csv-generate-3.4.3.tgz"
@@ -5035,6 +5141,11 @@ deep-is@^0.1.3, deep-is@~0.1.3:
resolved "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz"
integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==
+deep-object-diff@^1.1.0:
+ version "1.1.9"
+ resolved "https://registry.npmjs.org/deep-object-diff/-/deep-object-diff-1.1.9.tgz#6df7ef035ad6a0caa44479c536ed7b02570f4595"
+ integrity sha512-Rn+RuwkmkDwCi2/oXOFS9Gsr5lJZu/yTGpK7wAaAIE75CC+LCGEZHpY6VQJa/RoJcrmaA/docWJZvYohlNkWPA==
+
deepmerge@^4.2.2:
version "4.2.2"
resolved "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz"
@@ -5574,6 +5685,11 @@ esbuild@0.16.3:
"@esbuild/win32-ia32" "0.16.3"
"@esbuild/win32-x64" "0.16.3"
+esbuild@^0.11.16:
+ version "0.11.23"
+ resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.11.23.tgz#c42534f632e165120671d64db67883634333b4b8"
+ integrity sha512-iaiZZ9vUF5wJV8ob1tl+5aJTrwDczlvGP0JoMmnpC2B0ppiMCu8n8gmy5ZTGl5bcG081XBVn+U+jP+mPFm5T5Q==
+
escalade@^3.1.1:
version "3.1.1"
resolved "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz"
@@ -5980,6 +6096,13 @@ etag@~1.8.1:
resolved "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz"
integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=
+eval@0.1.6:
+ version "0.1.6"
+ resolved "https://registry.npmjs.org/eval/-/eval-0.1.6.tgz#9620d7d8c85515e97e6b47c5814f46ae381cb3cc"
+ integrity sha512-o0XUw+5OGkXw4pJZzQoXUk+H87DHuC+7ZE//oSrRGtatTmr12oTnLfg6QOq9DyTt0c/p4TwzgmkKrBzWTSizyQ==
+ dependencies:
+ require-like ">= 0.1.1"
+
event-target-shim@^5.0.0:
version "5.0.1"
resolved "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz"
@@ -7670,6 +7793,11 @@ istanbul-reports@^3.1.3:
html-escaper "^2.0.0"
istanbul-lib-report "^3.0.0"
+javascript-stringify@^2.0.1:
+ version "2.1.0"
+ resolved "https://registry.npmjs.org/javascript-stringify/-/javascript-stringify-2.1.0.tgz#27c76539be14d8bd128219a2d731b09337904e79"
+ integrity sha512-JVAfqNPTvNq3sB/VHQJAFxN/sPgKnsKrCwyRt15zwNCdrMMJDdcEOdubuy+DuJYYdm0ox1J4uzEuYKkN+9yhVg==
+
jest-changed-files@^27.5.1:
version "27.5.1"
resolved "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-27.5.1.tgz"
@@ -8909,6 +9037,13 @@ mdurl@^1.0.0:
resolved "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz"
integrity sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=
+media-query-parser@^2.0.2:
+ version "2.0.2"
+ resolved "https://registry.npmjs.org/media-query-parser/-/media-query-parser-2.0.2.tgz#ff79e56cee92615a304a1c2fa4f2bd056c0a1d29"
+ integrity sha512-1N4qp+jE0pL5Xv4uEcwVUhIkwdUO3S/9gML90nqKA7v7FcOS5vUtatfzok9S9U1EJU8dHWlcv95WLnKmmxZI9w==
+ dependencies:
+ "@babel/runtime" "^7.12.5"
+
media-typer@0.3.0:
version "0.3.0"
resolved "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz"
@@ -9932,6 +10067,11 @@ outdent@^0.5.0:
resolved "https://registry.npmjs.org/outdent/-/outdent-0.5.0.tgz"
integrity sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q==
+outdent@^0.8.0:
+ version "0.8.0"
+ resolved "https://registry.npmjs.org/outdent/-/outdent-0.8.0.tgz#2ebc3e77bf49912543f1008100ff8e7f44428eb0"
+ integrity sha512-KiOAIsdpUTcAXuykya5fnVVT+/5uS0Q1mrkRHcF89tpieSmY33O/tmc54CqwA+bfhbtEfZUNLHaPUiB9X3jt1A==
+
outvariant@^1.2.1:
version "1.3.0"
resolved "https://registry.npmjs.org/outvariant/-/outvariant-1.3.0.tgz"
@@ -10993,6 +11133,11 @@ require-from-string@^2.0.2:
resolved "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz"
integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==
+"require-like@>= 0.1.1":
+ version "0.1.2"
+ resolved "https://registry.npmjs.org/require-like/-/require-like-0.1.2.tgz#ad6f30c13becd797010c468afa775c0c0a6b47fa"
+ integrity sha512-oyrU88skkMtDdauHDuKVrgR+zuItqr6/c//FXzvmxRGMexSDc6hNvJInGW3LL46n+8b50RykrvwSUIIQH2LQ5A==
+
require-main-filename@^2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz"