Skip to content

Commit 57b4508

Browse files
committed
feat: add processor for wyw-in-js
1 parent 5169d73 commit 57b4508

27 files changed

+990
-15
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "major",
3+
"comment": "chore: initial release",
4+
"packageName": "@griffel/tag-processor",
5+
"email": "[email protected]",
6+
"dependentChangeType": "patch"
7+
}

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
"type-check": "nx affected --target=type-check"
2727
},
2828
"devDependencies": {
29+
"@babel/generator": "^7.23.0",
2930
"@babel/preset-react": "^7.22.15",
3031
"@babel/preset-typescript": "7.23.2",
3132
"@codesandbox/sandpack-react": "1.18.4",
@@ -73,6 +74,7 @@
7374
"@typescript-eslint/eslint-plugin": "5.47.0",
7475
"@typescript-eslint/parser": "5.47.0",
7576
"@uifabric/merge-styles": "7.19.1",
77+
"@wyw-in-js/processor-utils": "^0.2.2",
7678
"babel-jest": "28.1.3",
7779
"babel-loader": "8.1.0",
7880
"babel-plugin-annotate-pure-calls": "^0.4.0",

packages/tag-processor/.eslintrc.json

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"extends": ["../../.eslintrc.json"],
3+
"ignorePatterns": ["!**/*"],
4+
"overrides": [
5+
{
6+
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
7+
"rules": {}
8+
},
9+
{
10+
"files": ["*.ts", "*.tsx"],
11+
"rules": {}
12+
},
13+
{
14+
"files": ["*.js", "*.jsx"],
15+
"rules": {}
16+
}
17+
]
18+
}

packages/tag-processor/README.md

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# Griffel processor for Linaria
2+
3+
A processor for [Linaria](https://github.com/callstack/linaria) for that performs build time transforms for `makeStyles` & `makeResetStyles` [`@griffel/react`](../react).
4+
5+
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
6+
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
7+
8+
- [Install](#install)
9+
- [How to use it?](#how-to-use-it)
10+
- [Handling Griffel re-exports](#handling-griffel-re-exports)
11+
12+
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
13+
14+
## Install
15+
16+
```bash
17+
yarn add --dev @griffel/tag-processor
18+
# or
19+
npm install --save-dev @griffel/tag-processor
20+
```
21+
22+
## How to use it?
23+
24+
This package cannot be used solely, it should be paired with `@griffel/babel-preset` or `@griffel/webpack-loader`
25+
26+
- For library developers, please use [`@griffel/babel-preset`](../babel-preset)
27+
- For application developers, please use [`@griffel/webpack-loader`](../webpack-loader)
28+
29+
### Handling Griffel re-exports
30+
31+
```js
32+
import { makeStyles, makeResetStyles } from 'custom-package';
33+
```
34+
35+
By default, the processor handles imports from `@griffel/react` & `@fluentui/react-components`, to handle imports from custom packages settings you need to include meta information to a matching `package.json`:
36+
37+
```json
38+
{
39+
"name": "custom-package",
40+
"version": "1.0.0",
41+
"linaria": {
42+
"tags": {
43+
"makeStyles": "@griffel/tag-processor/make-styles",
44+
"makeResetStyles": "@griffel/tag-processor/make-reset-styles"
45+
}
46+
}
47+
}
48+
```
49+
50+
> **Note**: "custom-package" should re-export following functions from `@griffel/react`:
51+
>
52+
> - `__styles`
53+
> - `__css`
54+
> - `__resetStyles`
55+
> - `__resetCSS`

packages/tag-processor/jest.config.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
module.exports = {
2+
displayName: 'tag-processor',
3+
preset: '../../jest.preset.js',
4+
globals: {
5+
'ts-jest': {
6+
tsconfig: '<rootDir>/tsconfig.spec.json',
7+
},
8+
},
9+
transform: {
10+
'^.+\\.[tj]sx?$': 'ts-jest',
11+
},
12+
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
13+
coverageDirectory: '../../coverage/packages/tag-processor',
14+
};

packages/tag-processor/package.json

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{
2+
"name": "@griffel/tag-processor",
3+
"version": "0.0.1",
4+
"description": "Linaria processor for Griffel",
5+
"license": "MIT",
6+
"repository": {
7+
"type": "git",
8+
"url": "https://github.com/microsoft/griffel"
9+
},
10+
"dependencies": {
11+
"@griffel/core": "^1.14.2",
12+
"@linaria/tags": "^5.0.1",
13+
"stylis": "^4.2.0",
14+
"tslib": "^2.1.0"
15+
},
16+
"exports": {
17+
".": {
18+
"types": "./src/index.d.ts",
19+
"default": "./src/index.js"
20+
},
21+
"./make-styles": {
22+
"types": "./src/MakeStylesProcessor.d.ts",
23+
"default": "./src/MakeStylesProcessor.js"
24+
},
25+
"./make-reset-styles": {
26+
"types": "./src/MakeResetStylesProcessor.d.ts",
27+
"default": "./src/MakeResetStylesProcessor.js"
28+
}
29+
}
30+
}

packages/tag-processor/project.json

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
{
2+
"root": "packages/tag-processor",
3+
"sourceRoot": "packages/tag-processor/src",
4+
"projectType": "library",
5+
"targets": {
6+
"lint": {
7+
"executor": "@nrwl/linter:eslint",
8+
"outputs": ["{options.outputFile}"],
9+
"options": {
10+
"lintFilePatterns": ["packages/tag-processor/**/*.ts"]
11+
}
12+
},
13+
"test": {
14+
"executor": "@nrwl/jest:jest",
15+
"outputs": ["coverage/packages/tag-processor"],
16+
"options": {
17+
"jestConfig": "packages/tag-processor/jest.config.js",
18+
"passWithNoTests": true
19+
}
20+
},
21+
"build": {
22+
"executor": "@nrwl/js:tsc",
23+
"outputs": ["{options.outputPath}"],
24+
"options": {
25+
"outputPath": "dist/packages/tag-processor",
26+
"tsConfig": "packages/tag-processor/tsconfig.lib.json",
27+
"packageJson": "packages/tag-processor/package.json",
28+
"main": "packages/tag-processor/src/index.ts",
29+
"assets": [
30+
"packages/tag-processor/README.md",
31+
{
32+
"glob": "LICENSE.md",
33+
"input": ".",
34+
"output": "."
35+
}
36+
]
37+
}
38+
},
39+
"type-check": {
40+
"executor": "@nrwl/workspace:run-commands",
41+
"options": {
42+
"cwd": "packages/tag-processor",
43+
"commands": [{ "command": "tsc -b --pretty" }],
44+
"outputPath": []
45+
}
46+
}
47+
},
48+
"tags": []
49+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import type { Expression } from '@babel/types';
2+
import type { Params, TailProcessorParams } from '@wyw-in-js/processor-utils';
3+
import { BaseProcessor, TaggedTemplateProcessor, validateParams } from '@wyw-in-js/processor-utils';
4+
import * as path from 'path';
5+
6+
export default abstract class BaseGriffelProcessor extends BaseProcessor {
7+
readonly expressionName: string | number | boolean | null = null;
8+
9+
public constructor([tag, callParam]: Params, ...args: TailProcessorParams) {
10+
super([tag], ...args);
11+
12+
validateParams([tag, callParam], ['callee', 'call'], TaggedTemplateProcessor.SKIP);
13+
14+
if (callParam[0] === 'call') {
15+
const { ex } = callParam[1];
16+
17+
if (ex.type === 'Identifier') {
18+
this.dependencies.push(callParam[1] as any);
19+
this.expressionName = ex.name;
20+
} else if (ex.type === 'NullLiteral') {
21+
this.expressionName = null;
22+
} else {
23+
this.expressionName = ex.value;
24+
}
25+
}
26+
}
27+
28+
public get path() {
29+
return process.platform === 'win32' ? path.win32 : path.posix;
30+
}
31+
32+
public override get asSelector(): string {
33+
throw new Error('The result of makeStyles cannot be used as a selector.');
34+
}
35+
36+
public override doEvaltimeReplacement(): void {
37+
this.replacer(this.value, false);
38+
}
39+
40+
public override get value(): Expression {
41+
return this.astService.nullLiteral();
42+
}
43+
44+
public override toString(): string {
45+
return `${super.toString()}(…)`;
46+
}
47+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { resolveResetStyleRules } from '@griffel/core';
2+
import type { CSSRulesByBucket, GriffelResetStyle } from '@griffel/core';
3+
import type { ValueCache } from '@wyw-in-js/processor-utils';
4+
5+
import { createRuleLiteral } from './assets/createRuleLiteral';
6+
import { normalizeStyleRules } from './assets/normalizeStyleRules';
7+
import BaseGriffelProcessor from './BaseGriffelProcessor';
8+
import { FileContext } from './types';
9+
10+
export default class MakeResetStylesProcessor extends BaseGriffelProcessor {
11+
#ltrClassName: string | null = null;
12+
#rtlClassName: string | null = null;
13+
#cssRules: CSSRulesByBucket | string[] | null = null;
14+
15+
public override build(valueCache: ValueCache) {
16+
const styles = valueCache.get(this.expressionName) as GriffelResetStyle;
17+
18+
[this.#ltrClassName, this.#rtlClassName, this.#cssRules] = resolveResetStyleRules(
19+
// Heads up!
20+
// Style rules should be normalized *before* they will be resolved to CSS rules to have deterministic
21+
// results across different build targets.
22+
normalizeStyleRules(this.path, this.context as FileContext, styles),
23+
);
24+
}
25+
26+
public override doRuntimeReplacement(): void {
27+
if (!this.#cssRules || !this.#ltrClassName) {
28+
throw new Error('Styles are not extracted yet. Please call `build` first.');
29+
}
30+
31+
const t = this.astService;
32+
const addAssetImport = (path: string) => t.addDefaultImport(path, 'asset');
33+
34+
let rulesExpression;
35+
36+
if (Array.isArray(this.#cssRules)) {
37+
rulesExpression = t.arrayExpression(
38+
this.#cssRules.map(rule => {
39+
return createRuleLiteral(this.path, t, this.context as FileContext, rule, addAssetImport);
40+
}),
41+
);
42+
} else {
43+
rulesExpression = t.objectExpression(
44+
Object.entries(this.#cssRules).map(([bucketName, cssRules]) =>
45+
t.objectProperty(
46+
t.identifier(bucketName),
47+
t.arrayExpression(
48+
cssRules.map(rule => {
49+
return createRuleLiteral(this.path, t, this.context as FileContext, rule as string, addAssetImport);
50+
}),
51+
),
52+
),
53+
),
54+
);
55+
}
56+
57+
const stylesImportIdentifier = t.addNamedImport('__resetStyles', this.tagSource.source);
58+
const stylesCallExpression = t.callExpression(stylesImportIdentifier, [
59+
t.stringLiteral(this.#ltrClassName),
60+
this.#rtlClassName ? t.stringLiteral(this.#rtlClassName) : t.nullLiteral(),
61+
rulesExpression,
62+
]);
63+
64+
this.replacer(stylesCallExpression, true);
65+
}
66+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import { GriffelStyle, resolveStyleRulesForSlots } from '@griffel/core';
2+
import type { CSSClassesMapBySlot, CSSRulesByBucket } from '@griffel/core';
3+
import type { ValueCache } from '@wyw-in-js/processor-utils';
4+
5+
import { createRuleLiteral } from './assets/createRuleLiteral';
6+
import { normalizeStyleRules } from './assets/normalizeStyleRules';
7+
import BaseGriffelProcessor from './BaseGriffelProcessor';
8+
import type { FileContext } from './types';
9+
import { dedupeCSSRules } from './utils/dedupeCSSRules';
10+
11+
export default class MakeStylesProcessor extends BaseGriffelProcessor {
12+
#cssClassMap: CSSClassesMapBySlot<string> | undefined;
13+
#cssRulesByBucket: CSSRulesByBucket | undefined;
14+
15+
public override build(valueCache: ValueCache) {
16+
const stylesBySlots = valueCache.get(this.expressionName) as Record<string /* slot */, GriffelStyle>;
17+
18+
[this.#cssClassMap, this.#cssRulesByBucket] = resolveStyleRulesForSlots(
19+
// Heads up!
20+
// Style rules should be normalized *before* they will be resolved to CSS rules to have deterministic
21+
// results across different build targets.
22+
normalizeStyleRules(this.path, this.context as FileContext, stylesBySlots),
23+
);
24+
}
25+
26+
public override doRuntimeReplacement(): void {
27+
if (!this.#cssClassMap || !this.#cssRulesByBucket) {
28+
throw new Error('Styles are not extracted yet. Please call `build` first.');
29+
}
30+
31+
const t = this.astService;
32+
const addAssetImport = (path: string) => t.addDefaultImport(path, 'asset');
33+
34+
const uniqueRules = dedupeCSSRules(this.#cssRulesByBucket);
35+
const rulesObjectExpression = t.objectExpression(
36+
Object.entries(uniqueRules).map(([bucketName, cssRules]) =>
37+
t.objectProperty(
38+
t.identifier(bucketName),
39+
t.arrayExpression(
40+
cssRules.map(rule => {
41+
if (typeof rule === 'string') {
42+
return createRuleLiteral(this.path, t, this.context as FileContext, rule, addAssetImport);
43+
}
44+
45+
const [cssRule, metadata] = rule;
46+
47+
return t.arrayExpression([
48+
createRuleLiteral(this.path, t, this.context as FileContext, cssRule, addAssetImport),
49+
t.objectExpression(
50+
Object.entries(metadata).map(([key, value]) =>
51+
t.objectProperty(t.identifier(key), t.stringLiteral(value as string)),
52+
),
53+
),
54+
]);
55+
}),
56+
),
57+
),
58+
),
59+
);
60+
61+
const stylesImportIdentifier = t.addNamedImport('__styles', this.tagSource.source);
62+
const stylesCallExpression = t.callExpression(stylesImportIdentifier, [
63+
t.valueToNode(this.#cssClassMap),
64+
rulesObjectExpression,
65+
]);
66+
67+
this.replacer(stylesCallExpression, true);
68+
}
69+
}

0 commit comments

Comments
 (0)