Skip to content

Commit e56dc04

Browse files
feat(dev): enable all CSS bundling features (#6046)
1 parent 3b78626 commit e56dc04

22 files changed

+842
-1194
lines changed

.changeset/css-bundling.md

+118
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
---
2+
"@remix-run/dev": minor
3+
"@remix-run/react": minor
4+
"@remix-run/server-runtime": minor
5+
"@remix-run/testing": minor
6+
---
7+
8+
Enable support for [CSS Modules](https://github.com/css-modules/css-modules), [Vanilla Extract](http://vanilla-extract.style) and CSS side-effect imports
9+
10+
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.
11+
12+
**CSS Bundle Setup**
13+
14+
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.
15+
16+
```sh
17+
npm i @remix-run/css-bundle
18+
```
19+
20+
Then return the exported `cssBundleHref` as a stylesheet link descriptor from the `links` function at the root of your app.
21+
22+
```tsx
23+
import type { LinksFunction } from "@remix-run/node"; // or cloudflare/deno
24+
import { cssBundleHref } from "@remix-run/css-bundle";
25+
26+
export const links: LinksFunction = () => {
27+
return [
28+
...(cssBundleHref
29+
? [{ rel: "stylesheet", href: cssBundleHref }]
30+
: []),
31+
// ...
32+
];
33+
};
34+
```
35+
36+
**CSS Modules**
37+
38+
To use [CSS Modules](https://github.com/css-modules/css-modules), you can opt in via the `.module.css` file name convention. For example:
39+
40+
```css
41+
.root {
42+
border: solid 1px;
43+
background: white;
44+
color: #454545;
45+
}
46+
```
47+
48+
```tsx
49+
import styles from "./styles.module.css";
50+
51+
export const Button = React.forwardRef(
52+
({ children, ...props }, ref) => {
53+
return (
54+
<button
55+
{...props}
56+
ref={ref}
57+
className={styles.root}
58+
/>
59+
);
60+
}
61+
);
62+
Button.displayName = "Button";
63+
```
64+
65+
**Vanilla Extract**
66+
67+
To use [Vanilla Extract](http://vanilla-extract.style), first install its `css` package as a dev dependency.
68+
69+
```sh
70+
npm install -D @vanilla-extract/css
71+
```
72+
73+
You can then opt in via the `.css.ts`/`.css.js` file name convention. For example:
74+
75+
```ts
76+
import { style } from "@vanilla-extract/css";
77+
78+
export const root = style({
79+
border: "solid 1px",
80+
background: "white",
81+
color: "#454545",
82+
});
83+
```
84+
85+
```tsx
86+
import * as styles from "./styles.css"; // Note that `.ts` is omitted here
87+
88+
export const Button = React.forwardRef(
89+
({ children, ...props }, ref) => {
90+
return (
91+
<button
92+
{...props}
93+
ref={ref}
94+
className={styles.root}
95+
/>
96+
);
97+
}
98+
);
99+
Button.displayName = "Button";
100+
```
101+
102+
**CSS Side-Effect Imports**
103+
104+
Any CSS files that are imported as side-effects (e.g. `import "./styles.css"`) will be automatically included in the CSS bundle.
105+
106+
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):
107+
108+
```js filename=remix.config.js
109+
// remix.config.js
110+
module.exports = {
111+
serverDependenciesToBundle: [
112+
/^@adobe\/react-spectrum/,
113+
/^@react-spectrum/,
114+
/^@spectrum-icons/,
115+
],
116+
// ...
117+
};
118+
```

docs/guides/styling.md

+6-53
Original file line numberDiff line numberDiff line change
@@ -671,8 +671,6 @@ NOTE: You may run into hydration warnings when using Styled Components. Hopefull
671671
672672
## CSS Bundling
673673
674-
<docs-warning>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.</docs-warning>
675-
676674
<docs-warning>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].</docs-warning>
677675
678676
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
707705
708706
### CSS Modules
709707
710-
<docs-warning>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.</docs-warning>
711-
712-
First, ensure you've set up [CSS bundling][css-bundling] in your application.
713-
714-
Then, to enable [CSS Modules], set the `future.unstable_cssModules` feature flag in `remix.config.js`.
715-
716-
```js filename=remix.config.js
717-
/** @type {import('@remix-run/dev').AppConfig} */
718-
module.exports = {
719-
future: {
720-
unstable_cssModules: true,
721-
},
722-
// ...
723-
};
724-
```
708+
To use the built-in CSS Modules support, first ensure you've set up [CSS bundling][css-bundling] in your application.
725709
726-
With this feature flag enabled, you can now opt into CSS Modules via the `.module.css` file name convention. For example:
710+
You can then opt into [CSS Modules] via the `.module.css` file name convention. For example:
727711
728712
```css filename=app/components/button/styles.module.css
729713
.root {
@@ -752,31 +736,17 @@ Button.displayName = "Button";
752736
753737
### Vanilla Extract
754738
755-
<docs-warning>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.</docs-warning>
756-
757739
[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.
758740
759-
First, ensure you've set up [CSS bundling][css-bundling] in your application.
741+
To use the built-in Vanilla Extract support, first ensure you've set up [CSS bundling][css-bundling] in your application.
760742
761-
Next, install Vanilla Extract's core styling package as a dev dependency.
743+
Then, install Vanilla Extract's core styling package as a dev dependency.
762744
763745
```sh
764746
npm install -D @vanilla-extract/css
765747
```
766748
767-
Then, to enable Vanilla Extract, set the `future.unstable_vanillaExtract` feature flag in `remix.config.js`.
768-
769-
```js filename=remix.config.js
770-
/** @type {import('@remix-run/dev').AppConfig} */
771-
module.exports = {
772-
future: {
773-
unstable_vanillaExtract: true,
774-
},
775-
// ...
776-
};
777-
```
778-
779-
With this feature flag enabled, you can now opt into Vanilla Extract via the `.css.ts`/`.css.js` file name convention. For example:
749+
You can then opt into Vanilla Extract via the `.css.ts`/`.css.js` file name convention. For example:
780750
781751
```ts filename=app/components/button/styles.css.ts
782752
import { style } from "@vanilla-extract/css";
@@ -807,23 +777,9 @@ Button.displayName = "Button";
807777
808778
### CSS Side-Effect Imports
809779
810-
<docs-warning>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.</docs-warning>
811-
812780
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.
813781
814-
Then, set the `future.unstable_cssSideEffectImports` feature flag in `remix.config.js`.
815-
816-
```js filename=remix.config.js
817-
/** @type {import('@remix-run/dev').AppConfig} */
818-
module.exports = {
819-
future: {
820-
unstable_cssSideEffectImports: true,
821-
},
822-
// ...
823-
};
824-
```
825-
826-
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:
782+
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:
827783
828784
```js filename=remix.config.js
829785
/** @type {import('@remix-run/dev').AppConfig} */
@@ -833,9 +789,6 @@ module.exports = {
833789
/^@react-spectrum/,
834790
/^@spectrum-icons/,
835791
],
836-
future: {
837-
unstable_cssSideEffectImports: true,
838-
},
839792
// ...
840793
};
841794
```

docs/pages/api-development-strategy.md

-3
Original file line numberDiff line numberDiff line change
@@ -54,10 +54,7 @@ Here's the current future flags in Remix v1 today:
5454

5555
| Flag | Description |
5656
| ------------------------------- | --------------------------------------------------------------------- |
57-
| `unstable_cssModules` | Enable CSS Modules Support |
58-
| `unstable_cssSideEffectImports` | Enable CSS Side Effect imports |
5957
| `unstable_dev` | Enable the new development server (including HMR/HDR support) |
60-
| `unstable_vanillaExtract` | Enable Vanilla Extract Support |
6158
| `v2_errorBoundary` | Combine `ErrorBoundary`/`CatchBoundary` into a single `ErrorBoundary` |
6259
| `v2_meta` | Enable the new API for your `meta` functions |
6360
| `v2_normalizeFormMethod` | Normalize `useNavigation().formMethod` to be an uppercase HTTP Method |

integration/css-modules-test.ts

-5
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,6 @@ test.describe("CSS Modules", () => {
2121
fixture = await createFixture({
2222
future: {
2323
v2_routeConvention: true,
24-
// Enable all CSS future flags to
25-
// ensure features don't clash
26-
unstable_cssModules: true,
27-
unstable_cssSideEffectImports: true,
28-
unstable_vanillaExtract: true,
2924
},
3025
files: {
3126
"app/root.jsx": js`

integration/css-side-effect-imports-test.ts

-5
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,6 @@ test.describe("CSS side-effect imports", () => {
2323
module.exports = {
2424
serverDependenciesToBundle: [/@test-package/],
2525
future: {
26-
// Enable all CSS future flags to
27-
// ensure features don't clash
28-
unstable_cssModules: true,
29-
unstable_cssSideEffectImports: true,
30-
unstable_vanillaExtract: true,
3126
v2_routeConvention: true,
3227
},
3328
};

integration/deterministic-build-output-test.ts

-67
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,6 @@ test("builds deterministically under different paths", async () => {
2727
// * vanillaExtractPlugin (via app/routes/foo.tsx' .css.ts file import)
2828
let init: FixtureInit = {
2929
future: {
30-
unstable_cssModules: true,
31-
unstable_cssSideEffectImports: true,
32-
unstable_postcss: true,
33-
unstable_vanillaExtract: true,
3430
v2_routeConvention: true,
3531
},
3632
files: {
@@ -116,66 +112,3 @@ test("builds deterministically under different paths", async () => {
116112
);
117113
});
118114
});
119-
120-
test("builds Vanilla Extract files deterministically under different paths with Vanilla Extract cache enabled", async () => {
121-
let init: FixtureInit = {
122-
future: {
123-
unstable_vanillaExtract: { cache: true },
124-
v2_routeConvention: true,
125-
},
126-
files: {
127-
"app/routes/foo.tsx": js`
128-
import { vanilla } from "~/styles/vanilla.css";
129-
export default () => <div className={vanilla}>YAY</div>;
130-
`,
131-
"app/images/foo.svg": `
132-
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
133-
<circle cx="50" cy="50" r="50" fill="coral" />
134-
</svg>
135-
`,
136-
"app/styles/vanilla.css.ts": js`
137-
import { style } from "@vanilla-extract/css";
138-
import { chocolate } from "./chocolate.css";
139-
import imageUrl from "~/images/foo.svg";
140-
141-
export const vanilla = style([
142-
chocolate,
143-
{
144-
backgroundImage: [
145-
"url(" + imageUrl + ")",
146-
"url(~/images/foo.svg)",
147-
],
148-
}
149-
]);
150-
`,
151-
"app/styles/chocolate.css.ts": js`
152-
import { style } from "@vanilla-extract/css";
153-
154-
export const chocolate = style({
155-
color: "chocolate",
156-
});
157-
`,
158-
},
159-
};
160-
let dir1 = await createFixtureProject(init);
161-
let dir2 = await createFixtureProject(init);
162-
163-
expect(dir1).not.toEqual(dir2);
164-
165-
let files1 = await globby(["build/index.js", "public/build/**/*.{js,css}"], {
166-
cwd: dir1,
167-
});
168-
files1 = files1.sort();
169-
let files2 = await globby(["build/index.js", "public/build/**/*.{js,css}"], {
170-
cwd: dir2,
171-
});
172-
files2 = files2.sort();
173-
174-
expect(files1.length).toBeGreaterThan(0);
175-
expect(files1).toEqual(files2);
176-
files1.forEach((file, i) => {
177-
expect(fs.readFileSync(path.join(dir1, file))).toEqual(
178-
fs.readFileSync(path.join(dir2, files2[i]))
179-
);
180-
});
181-
});

integration/hmr-test.ts

-13
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,6 @@ import getPort, { makeRange } from "get-port";
88
import { createFixtureProject, css, js, json } from "./helpers/create-fixture";
99

1010
let fixture = (options: { port: number; appServerPort: number }) => ({
11-
future: {
12-
unstable_dev: {
13-
port: options.port,
14-
appServerPort: options.appServerPort,
15-
},
16-
unstable_cssModules: true,
17-
unstable_tailwind: true,
18-
v2_routeConvention: true,
19-
v2_errorBoundary: true,
20-
v2_normalizeFormMethod: true,
21-
v2_meta: true,
22-
},
2311
files: {
2412
"remix.config.js": js`
2513
module.exports = {
@@ -29,7 +17,6 @@ let fixture = (options: { port: number; appServerPort: number }) => ({
2917
port: ${options.port},
3018
appServerPort: ${options.appServerPort},
3119
},
32-
unstable_cssModules: true,
3320
v2_routeConvention: true,
3421
v2_errorBoundary: true,
3522
v2_normalizeFormMethod: true,

integration/postcss-test.ts

-3
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,6 @@ test.describe("PostCSS enabled", () => {
4040
tailwind: true,
4141
future: {
4242
v2_routeConvention: true,
43-
unstable_cssModules: true,
44-
unstable_cssSideEffectImports: true,
45-
unstable_vanillaExtract: true,
4643
},
4744
};
4845
`,

integration/tailwind-test.ts

-5
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,6 @@ function runTests(ext: typeof extensions[number]) {
4848
module.exports = {
4949
tailwind: true,
5050
future: {
51-
// Enable all CSS future flags to
52-
// ensure features don't clash
53-
unstable_cssModules: true,
54-
unstable_cssSideEffectImports: true,
55-
unstable_vanillaExtract: true,
5651
v2_routeConvention: true,
5752
},
5853
};

0 commit comments

Comments
 (0)