Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(dev): enable all CSS bundling features #6046

Merged
merged 1 commit into from
Apr 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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