Skip to content

Commit

Permalink
reuse compilation result of ts-loader (#107)
Browse files Browse the repository at this point in the history
Co-authored-by: Luca Schneider <[email protected]>
  • Loading branch information
jantimon and Mad-Kat authored Jul 10, 2024
1 parent 5778919 commit 0fc8271
Show file tree
Hide file tree
Showing 22 changed files with 2,222 additions and 3,023 deletions.
3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@
"prettier": "pnpm run --recursive --if-present prettier"
},
"engines": {
"node": ">=20",
"pnpm": "8"
"node": ">=20"
},
"packageManager": "[email protected]",
"author": "Jan Nicklas",
Expand Down
2 changes: 1 addition & 1 deletion packages/benchmark/letters/gen.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { writeFile } from "fs";
import tsLoader from "../../next-yak/loaders/tsloader";
import tsLoader from "../../next-yak/loaders/ts-loader";

// Function to generate the content of JapaneseLetterComponent.tsx for Kanji characters
async function generateKanjiComponentFile() {
Expand Down
2 changes: 1 addition & 1 deletion packages/benchmark/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"codspeed:run": "NODE_ENV=production node ./codspeed/dist/index.bench.mjs"
},
"dependencies": {
"next": "canary",
"next": "14.2.4",
"react": "18.2.0",
"react-dom": "18.2.0",
"next-yak": "workspace:*",
Expand Down
92 changes: 9 additions & 83 deletions packages/docs/docs/pages/how-does-it-work.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# How does it work?

This is for the curious minds who want to know how `next-yak` works under the hood and it's not necessary
to understand it in order to use it.
to understand it in order to use it. A live playground to see the code transformation can be found [here](/playground).

## The basics

Expand All @@ -17,13 +17,12 @@ the runtime part. The runtime part is responsible for merging styles and props.
## The compile time part

`next-yak` uses two webpack loaders to transform your code. The first loader is responsible for transforming the
usages of the tagged template literals (like `styled` and `css`) and the second loader is responsible for transforming
the styles into real CSS.
usages of the tagged template literals (like `styled` and `css`), the second loader reads the results from the first loader, resolves cross module dependencies and writes the finished css in a css module file.

### The first loader [(tsloader.cjs)](https://github.com/jantimon/next-yak/blob/main/packages/next-yak/loaders/tsloader.cjs)
### The first loader [(ts-loader.ts)](https://github.com/jantimon/next-yak/blob/main/packages/next-yak/loaders/ts-loader.ts)

The first loader takes in the source code and transforms the tagged template literals into `next-yak` runtime function
calls with the imported styles (from the second loader) as arguments. It also transforms the dynamic parts of the
calls with the imported styles (from the final css module output) as arguments. It also transforms the dynamic parts of the
tagged template literals into function calls with the properties as argument and adds it as css value to the style object.

#### Add style import
Expand Down Expand Up @@ -114,87 +113,14 @@ const Button = styled.button(
:::


### The second loader [(cssloader.cjs)](https://github.com/jantimon/next-yak/blob/main/packages/next-yak/loaders/cssloader.cjs)
### The second loader [(css-loader.ts)](https://github.com/jantimon/next-yak/blob/main/packages/next-yak/loaders/ts-post-loader.ts)

The second loader runs after the first loader changed the import, takes in the styles and transforms it into real
CSS inside CSS-Modules. It also generates a class name for each static and dynamic style in order to reference it in the first loader. It translates dynamic property values into CSS variables and adds it to the class selector.
The second loader takes the output of the first loader and resolves the cross module dependencies. The result is valid CSS that will be written to the context.

#### Remove imports
#### Resolve cross module dependencies

The first step of the transformation is to remove all import statements as the input is the normal JS file and we need to output a CSS file.

#### Transform static styles

The static styles are transformed into CSS classes. The class names are generated based on the component name and referenced
in the first loader.

:::code-group

```tsx [input]
import { styled } from "next-yak";

const Button = styled.button`
font-size: 2rem;
font-weight: bold;
color: blue;
&:hover {
color: red;
}
`;
```

```css [output]
.Button {
font-size: 2rem;
font-weight: bold;
color: blue;
&:hover {
color: red;
}
}
```

:::

#### Transform dynamic styles

The dynamic property styles are transformed into nested `:where` selectors that don't alter the specificity of the selector. The class names are generated based on the condition of the properties and used in the first loader. For dynamic property
values, the value is transformed into a CSS variable.

:::code-group

```tsx [input]
import { styled, css } from "next-yak";

const Button = styled.button`
font-size: 2rem;
font-weight: bold;
color: ${props => props.$primary ? "white" : "blue"}; // [!code focus]
${props => props.$primary ? css`background: black;` : css`background: white;`} // [!code focus]
&:hover {
color: ${props => props.$primary ? "red" : "blue"}; // [!code focus]
}
`;
```

```css [output]
.Button {
font-size: 2rem;
font-weight: bold;
color: var(--🦬18fi82j0); /* [!code focus] */
&:where(.propsprimary_0) { /* [!code focus] */
background: black; /* [!code focus] */
} /* [!code focus] */
&:where(.not_propsprimary_1) { /* [!code focus] */
background: white; /* [!code focus] */
} /* [!code focus] */
&:hover { /* [!code focus] */
color: var(--🦬18fi82j1); /* [!code focus] */
} /* [!code focus] */
}
```

:::
To resolve cross module dependencies, the first loader generates a unique string to signal the second loader that this is a dependency with a specific name. The second loader then reads the generated CSS from the first loader and resolves the dependencies.
Once the final css is generated the loader hands it over as a CSS module file so it can be processed by Next.js like a normal CSS module file.

## The runtime part

Expand Down
2 changes: 1 addition & 1 deletion packages/example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"@types/node": "20.4.5",
"@types/react": "18.2.28",
"@types/react-dom": "18.2.7",
"next": "14.0.0",
"next": "14.2.4",
"react": "18.2.0",
"react-dom": "18.2.0",
"typescript": "5.5.2",
Expand Down
Loading

0 comments on commit 0fc8271

Please sign in to comment.