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: add processor for wyw-in-js #471

Merged
merged 1 commit into from
Dec 8, 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "major",
"comment": "chore: initial release",
"packageName": "@griffel/tag-processor",
"email": "[email protected]",
"dependentChangeType": "patch"
}
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"type-check": "nx affected --target=type-check"
},
"devDependencies": {
"@babel/generator": "^7.23.0",
"@babel/preset-react": "^7.22.15",
"@babel/preset-typescript": "7.23.2",
"@codesandbox/sandpack-react": "1.18.4",
Expand Down Expand Up @@ -73,6 +74,7 @@
"@typescript-eslint/eslint-plugin": "5.47.0",
"@typescript-eslint/parser": "5.47.0",
"@uifabric/merge-styles": "7.19.1",
"@wyw-in-js/processor-utils": "^0.2.2",
"babel-jest": "28.1.3",
"babel-loader": "8.1.0",
"babel-plugin-annotate-pure-calls": "^0.4.0",
Expand Down
18 changes: 18 additions & 0 deletions packages/tag-processor/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"extends": ["../../.eslintrc.json"],
"ignorePatterns": ["!**/*"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.ts", "*.tsx"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"rules": {}
}
]
}
55 changes: 55 additions & 0 deletions packages/tag-processor/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Griffel processor for wyw-in-js

A processor for [wyw-in-js](https://github.com/Anber/wyw-in-js) for that performs build time transforms for `makeStyles` & `makeResetStyles` [`@griffel/react`](../react).

<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->

- [Install](#install)
- [How to use it?](#how-to-use-it)
- [Handling Griffel re-exports](#handling-griffel-re-exports)

<!-- END doctoc generated TOC please keep comment here to allow auto update -->

## Install

```bash
yarn add --dev @griffel/tag-processor
# or
npm install --save-dev @griffel/tag-processor
```

## How to use it?

This package cannot be used solely, it should be paired with `@griffel/babel-preset` or `@griffel/webpack-loader`

- For library developers, please use [`@griffel/babel-preset`](../babel-preset)
- For application developers, please use [`@griffel/webpack-loader`](../webpack-loader)

### Handling Griffel re-exports

```js
import { makeStyles, makeResetStyles } from 'custom-package';
```

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`:

```json
{
"name": "custom-package",
"version": "1.0.0",
"wyw-in-js": {
"tags": {
"makeStyles": "@griffel/tag-processor/make-styles",
"makeResetStyles": "@griffel/tag-processor/make-reset-styles"
}
}
}
```

> **Note**: "custom-package" should re-export following functions from `@griffel/react`:
>
> - `__styles`
> - `__css`
> - `__resetStyles`
> - `__resetCSS`
14 changes: 14 additions & 0 deletions packages/tag-processor/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
module.exports = {
displayName: 'tag-processor',
preset: '../../jest.preset.js',
globals: {
'ts-jest': {
tsconfig: '<rootDir>/tsconfig.spec.json',
},
},
transform: {
'^.+\\.[tj]sx?$': 'ts-jest',
},
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
coverageDirectory: '../../coverage/packages/tag-processor',
};
30 changes: 30 additions & 0 deletions packages/tag-processor/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"name": "@griffel/tag-processor",
"version": "0.0.1",
"description": "wyw-in-js processor for Griffel",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/microsoft/griffel"
},
"dependencies": {
"@griffel/core": "^1.15.1",
"@wyw-in-js/processor-utils": "^0.2.2",
"stylis": "^4.2.0",
"tslib": "^2.1.0"
},
"exports": {
".": {
"types": "./src/index.d.ts",
"default": "./src/index.js"
},
"./make-styles": {
"types": "./src/MakeStylesProcessor.d.ts",
"default": "./src/MakeStylesProcessor.js"
},
"./make-reset-styles": {
"types": "./src/MakeResetStylesProcessor.d.ts",
"default": "./src/MakeResetStylesProcessor.js"
}
}
}
49 changes: 49 additions & 0 deletions packages/tag-processor/project.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
{
"root": "packages/tag-processor",
"sourceRoot": "packages/tag-processor/src",
"projectType": "library",
"targets": {
"lint": {
"executor": "@nrwl/linter:eslint",
"outputs": ["{options.outputFile}"],
"options": {
"lintFilePatterns": ["packages/tag-processor/**/*.ts"]
}
},
"test": {
"executor": "@nrwl/jest:jest",
"outputs": ["coverage/packages/tag-processor"],
"options": {
"jestConfig": "packages/tag-processor/jest.config.js",
"passWithNoTests": true
}
},
"build": {
"executor": "@nrwl/js:tsc",
"outputs": ["{options.outputPath}"],
"options": {
"outputPath": "dist/packages/tag-processor",
"tsConfig": "packages/tag-processor/tsconfig.lib.json",
"packageJson": "packages/tag-processor/package.json",
"main": "packages/tag-processor/src/index.ts",
"assets": [
"packages/tag-processor/README.md",
{
"glob": "LICENSE.md",
"input": ".",
"output": "."
}
]
}
},
"type-check": {
"executor": "@nrwl/workspace:run-commands",
"options": {
"cwd": "packages/tag-processor",
"commands": [{ "command": "tsc -b --pretty" }],
"outputPath": []
}
}
},
"tags": []
}
47 changes: 47 additions & 0 deletions packages/tag-processor/src/BaseGriffelProcessor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import type { Expression } from '@babel/types';
import type { Params, TailProcessorParams } from '@wyw-in-js/processor-utils';
import { BaseProcessor, TaggedTemplateProcessor, validateParams } from '@wyw-in-js/processor-utils';
import * as path from 'path';

export default abstract class BaseGriffelProcessor extends BaseProcessor {
readonly expressionName: string | number | boolean | null = null;

public constructor([tag, callParam]: Params, ...args: TailProcessorParams) {
super([tag], ...args);

validateParams([tag, callParam], ['callee', 'call'], TaggedTemplateProcessor.SKIP);

if (callParam[0] === 'call') {
const { ex } = callParam[1];

if (ex.type === 'Identifier') {
this.dependencies.push(callParam[1]);
this.expressionName = ex.name;
} else if (ex.type === 'NullLiteral') {
this.expressionName = null;
} else {
this.expressionName = ex.value;
}
}
}

public get path() {
return process.platform === 'win32' ? path.win32 : path.posix;
}

public override get asSelector(): string {
throw new Error('The result of makeStyles cannot be used as a selector.');
}

public override doEvaltimeReplacement(): void {
this.replacer(this.value, false);
}

public override get value(): Expression {
return this.astService.nullLiteral();
}

public override toString(): string {
return `${super.toString()}(…)`;
}
}
66 changes: 66 additions & 0 deletions packages/tag-processor/src/MakeResetStylesProcessor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { resolveResetStyleRules } from '@griffel/core';
import type { CSSRulesByBucket, GriffelResetStyle } from '@griffel/core';
import type { ValueCache } from '@wyw-in-js/processor-utils';

import { createRuleLiteral } from './assets/createRuleLiteral';
import { normalizeStyleRules } from './assets/normalizeStyleRules';
import BaseGriffelProcessor from './BaseGriffelProcessor';
import { FileContext } from './types';

export default class MakeResetStylesProcessor extends BaseGriffelProcessor {
#ltrClassName: string | null = null;
#rtlClassName: string | null = null;
#cssRules: CSSRulesByBucket | string[] | null = null;

public override build(valueCache: ValueCache) {
const styles = valueCache.get(this.expressionName) as GriffelResetStyle;

[this.#ltrClassName, this.#rtlClassName, this.#cssRules] = resolveResetStyleRules(
// Heads up!
// Style rules should be normalized *before* they will be resolved to CSS rules to have deterministic
// results across different build targets.
normalizeStyleRules(this.path, this.context as FileContext, styles),
);
}

public override doRuntimeReplacement(): void {
if (!this.#cssRules || !this.#ltrClassName) {
throw new Error('Styles are not extracted yet. Please call `build` first.');
}

const t = this.astService;
const addAssetImport = (path: string) => t.addDefaultImport(path, 'asset');

let rulesExpression;

if (Array.isArray(this.#cssRules)) {
rulesExpression = t.arrayExpression(
this.#cssRules.map(rule => {
return createRuleLiteral(this.path, t, this.context as FileContext, rule, addAssetImport);
}),
);
} else {
rulesExpression = t.objectExpression(
Object.entries(this.#cssRules).map(([bucketName, cssRules]) =>
t.objectProperty(
t.identifier(bucketName),
t.arrayExpression(
cssRules.map(rule => {
return createRuleLiteral(this.path, t, this.context as FileContext, rule as string, addAssetImport);
}),
),
),
),
);
}

const stylesImportIdentifier = t.addNamedImport('__resetStyles', this.tagSource.source);
const stylesCallExpression = t.callExpression(stylesImportIdentifier, [
t.stringLiteral(this.#ltrClassName),
this.#rtlClassName ? t.stringLiteral(this.#rtlClassName) : t.nullLiteral(),
rulesExpression,
]);

this.replacer(stylesCallExpression, true);
}
}
69 changes: 69 additions & 0 deletions packages/tag-processor/src/MakeStylesProcessor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { GriffelStyle, resolveStyleRulesForSlots } from '@griffel/core';
import type { CSSClassesMapBySlot, CSSRulesByBucket } from '@griffel/core';
import type { ValueCache } from '@wyw-in-js/processor-utils';

import { createRuleLiteral } from './assets/createRuleLiteral';
import { normalizeStyleRules } from './assets/normalizeStyleRules';
import BaseGriffelProcessor from './BaseGriffelProcessor';
import type { FileContext } from './types';
import { dedupeCSSRules } from './utils/dedupeCSSRules';

export default class MakeStylesProcessor extends BaseGriffelProcessor {
#cssClassMap: CSSClassesMapBySlot<string> | undefined;
#cssRulesByBucket: CSSRulesByBucket | undefined;

public override build(valueCache: ValueCache) {
const stylesBySlots = valueCache.get(this.expressionName) as Record<string /* slot */, GriffelStyle>;

[this.#cssClassMap, this.#cssRulesByBucket] = resolveStyleRulesForSlots(
// Heads up!
// Style rules should be normalized *before* they will be resolved to CSS rules to have deterministic
// results across different build targets.
normalizeStyleRules(this.path, this.context as FileContext, stylesBySlots),
);
}

public override doRuntimeReplacement(): void {
if (!this.#cssClassMap || !this.#cssRulesByBucket) {
throw new Error('Styles are not extracted yet. Please call `build` first.');
}

const t = this.astService;
const addAssetImport = (path: string) => t.addDefaultImport(path, 'asset');

const uniqueRules = dedupeCSSRules(this.#cssRulesByBucket);
const rulesObjectExpression = t.objectExpression(
Object.entries(uniqueRules).map(([bucketName, cssRules]) =>
t.objectProperty(
t.identifier(bucketName),
t.arrayExpression(
cssRules.map(rule => {
if (typeof rule === 'string') {
return createRuleLiteral(this.path, t, this.context as FileContext, rule, addAssetImport);
}

const [cssRule, metadata] = rule;

return t.arrayExpression([
createRuleLiteral(this.path, t, this.context as FileContext, cssRule, addAssetImport),
t.objectExpression(
Object.entries(metadata).map(([key, value]) =>
t.objectProperty(t.identifier(key), t.stringLiteral(value as string)),
),
),
]);
}),
),
),
),
);

const stylesImportIdentifier = t.addNamedImport('__styles', this.tagSource.source);
const stylesCallExpression = t.callExpression(stylesImportIdentifier, [
t.valueToNode(this.#cssClassMap),
rulesObjectExpression,
]);

this.replacer(stylesCallExpression, true);
}
}
Loading
Loading