Skip to content

Commit

Permalink
feat: add processor for wyw-in-js
Browse files Browse the repository at this point in the history
  • Loading branch information
layershifter committed Dec 8, 2023
1 parent adf1a0d commit 0404401
Show file tree
Hide file tree
Showing 27 changed files with 990 additions and 15 deletions.
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] as any);

Check warning on line 18 in packages/tag-processor/src/BaseGriffelProcessor.ts

View workflow job for this annotation

GitHub Actions / main

Unexpected any. Specify a different type
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

0 comments on commit 0404401

Please sign in to comment.