Skip to content

Commit

Permalink
feat(dev): enable all CSS bundling features (#6046)
Browse files Browse the repository at this point in the history
  • Loading branch information
markdalgleish authored Apr 12, 2023
1 parent 3b78626 commit e56dc04
Show file tree
Hide file tree
Showing 22 changed files with 842 additions and 1,194 deletions.
118 changes: 118 additions & 0 deletions .changeset/css-bundling.md
Original file line number Diff line number Diff line change
@@ -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
{...props}
ref={ref}
className={styles.root}
/>
);
}
);
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
{...props}
ref={ref}
className={styles.root}
/>
);
}
);
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/,
],
// ...
};
```
59 changes: 6 additions & 53 deletions docs/guides/styling.md
Original file line number Diff line number Diff line change
Expand Up @@ -671,8 +671,6 @@ NOTE: You may run into hydration warnings when using Styled Components. Hopefull
## CSS Bundling
<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>
<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>
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.
Expand Down Expand Up @@ -707,23 +705,9 @@ With this link tag inserted into the page, you're now ready to start using the v
### CSS Modules
<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>
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 {
Expand Down Expand Up @@ -752,31 +736,17 @@ Button.displayName = "Button";
### Vanilla Extract
<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>
[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";
Expand Down Expand Up @@ -807,23 +777,9 @@ Button.displayName = "Button";
### CSS Side-Effect Imports
<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>
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} */
Expand All @@ -833,9 +789,6 @@ module.exports = {
/^@react-spectrum/,
/^@spectrum-icons/,
],
future: {
unstable_cssSideEffectImports: true,
},
// ...
};
```
Expand Down
3 changes: 0 additions & 3 deletions docs/pages/api-development-strategy.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand Down
5 changes: 0 additions & 5 deletions integration/css-modules-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down
5 changes: 0 additions & 5 deletions integration/css-side-effect-imports-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
};
Expand Down
67 changes: 0 additions & 67 deletions integration/deterministic-build-output-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down Expand Up @@ -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 () => <div className={vanilla}>YAY</div>;
`,
"app/images/foo.svg": `
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<circle cx="50" cy="50" r="50" fill="coral" />
</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]))
);
});
});
13 changes: 0 additions & 13 deletions integration/hmr-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand All @@ -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,
Expand Down
3 changes: 0 additions & 3 deletions integration/postcss-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,6 @@ test.describe("PostCSS enabled", () => {
tailwind: true,
future: {
v2_routeConvention: true,
unstable_cssModules: true,
unstable_cssSideEffectImports: true,
unstable_vanillaExtract: true,
},
};
`,
Expand Down
5 changes: 0 additions & 5 deletions integration/tailwind-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
};
Expand Down
Loading

0 comments on commit e56dc04

Please # to comment.