Skip to content

Commit

Permalink
chore: example of object syntax (#5)
Browse files Browse the repository at this point in the history
  • Loading branch information
Anber authored Oct 3, 2023
1 parent bdabeec commit 141dbaf
Show file tree
Hide file tree
Showing 14 changed files with 478 additions and 6 deletions.
3 changes: 3 additions & 0 deletions examples/object-syntax/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
extends: ['@wyw-in-js/eslint-config/library'],
};
3 changes: 3 additions & 0 deletions examples/object-syntax/babel.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const config = require('@wyw-in-js/babel-config');

module.exports = config;
14 changes: 14 additions & 0 deletions examples/object-syntax/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
testMatch: ['**/__tests__/**/*.test.ts'],
transform: {
'^.+\\.ts$': [
'ts-jest',
{
tsconfig: 'tsconfig.spec.json',
},
],
},
};
64 changes: 64 additions & 0 deletions examples/object-syntax/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
{
"name": "@wyw-in-js/object-syntax",
"private": true,
"version": "0.0.1",
"dependencies": {
"@griffel/core": "1.5.0",
"@wyw-in-js/processor-utils": "workspace:*"
},
"devDependencies": {
"@babel/core": "^7.23.0",
"@babel/traverse": "^7.23.0",
"@babel/types": "^7.23.0",
"@types/babel__core": "^7.20.2",
"@types/babel__traverse": "^7.20.2",
"@types/node": "^16.18.55",
"@wyw-in-js/babel-config": "workspace:*",
"@wyw-in-js/eslint-config": "workspace:*",
"@wyw-in-js/shared": "workspace:*",
"@wyw-in-js/transform": "workspace:*",
"@wyw-in-js/ts-config": "workspace:*",
"dedent": "^1.5.1"
},
"engines": {
"node": ">=16.0.0"
},
"exports": {
"./package.json": "./package.json",
".": {
"types": "./types/index.d.ts",
"import": "./esm/index.js",
"default": "./lib/index.js"
},
"./*": {
"types": "./types/*.d.ts",
"import": "./esm/*.js",
"default": "./lib/*.js"
}
},
"files": [
"esm/",
"lib/",
"processors/",
"types/"
],
"license": "MIT",
"linaria": {
"tags": {
"makeStyles": "./lib/processors/makeStyles.js"
}
},
"main": "lib/index.js",
"module": "esm/index.js",
"publishConfig": {
"access": "public"
},
"scripts": {
"build:esm": "babel src --out-dir esm --extensions '.js,.jsx,.ts,.tsx' --source-maps --delete-dir-on-start",
"build:lib": "cross-env NODE_ENV=legacy babel src --out-dir lib --extensions '.js,.jsx,.ts,.tsx' --source-maps --delete-dir-on-start",
"build:types": "tsc",
"lint": "eslint --ext .js,.ts .",
"test": "jest --config ./jest.config.js --rootDir src"
},
"types": "types/index.d.ts"
}
5 changes: 5 additions & 0 deletions examples/object-syntax/processors/makeStyles.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Object.defineProperty(exports, '__esModule', {
value: true,
});

exports.default = require('../lib/processors/makeStyles').default;
168 changes: 168 additions & 0 deletions examples/object-syntax/src/__tests__/getTagProcessor.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
import { join } from 'path';

import { parseSync } from '@babel/core';
import traverse from '@babel/traverse';
import dedent from 'dedent';

import type { BaseProcessor } from '@wyw-in-js/processor-utils';
import { getTagProcessor } from '@wyw-in-js/transform';

interface IRunOptions {
ts?: boolean;
}

const run = (code: string, options: IRunOptions = {}): BaseProcessor | null => {
const opts = {
filename: join(__dirname, options.ts ? 'test.ts' : 'test.js'),
root: '.',
code: true,
ast: true,
presets: options.ts ? ['@babel/preset-typescript'] : [],
};
const rootNode = parseSync(code, opts)!;
let result: BaseProcessor | null = null;
traverse(rootNode, {
Identifier(path) {
const processor = getTagProcessor(path, opts, {
displayName: true,
evaluate: true,
});

if (processor) {
result = processor;
}
},
});

return result;
};

function tagToString(processor: BaseProcessor | null): string | undefined {
if (!processor) return undefined;
return processor.toString();
}

describe('getTagProcessor', () => {
it('should find correct import', () => {
const result = run(
dedent`
import { makeStyles } from "@wyw-in-js/object-syntax";
export const Square = makeStyles({});
`
);

expect(tagToString(result)).toBe('makeStyles(…)');
expect(result?.tagSource).toEqual({
imported: 'makeStyles',
source: '@wyw-in-js/object-syntax',
});
});

it('renamed({})', () => {
const result = run(
dedent`
import { makeStyles as renamed } from "@wyw-in-js/object-syntax";
export const Square = renamed({});
`
);

expect(tagToString(result)).toBe('renamed(…)');
expect(result?.tagSource).toEqual({
imported: 'makeStyles',
source: '@wyw-in-js/object-syntax',
});
});

it('(0, objectSyntax.makeStyles)()', () => {
const result = run(
dedent`
const objectSyntax = require("@wyw-in-js/object-syntax");
export const Square = (0, objectSyntax.makeStyles)({});
`
);

expect(tagToString(result)).toBe('objectSyntax.makeStyles(…)');
expect(result?.tagSource).toEqual({
imported: 'makeStyles',
source: '@wyw-in-js/object-syntax',
});
});

it('imported from file', () => {
const result = run(
dedent`
import { makeStyles } from '../makeStyles';
export const square = makeStyles({});
`
);

expect(tagToString(result)).toBe('makeStyles(…)');
expect(result?.tagSource).toEqual({
imported: 'makeStyles',
source: '../makeStyles',
});
});

it('require and access with prop', () => {
const result = run(
dedent`
const renamed = require('@wyw-in-js/object-syntax').makeStyles;
export const Square = renamed({});
`
);

expect(tagToString(result)).toBe('renamed(…)');
});

it('require and destructing', () => {
const result = run(
dedent`
const { makeStyles } = require('@wyw-in-js/object-syntax');
export const Square = makeStyles({});
`
);

expect(tagToString(result)).toBe('makeStyles(…)');
});

describe('invalid usage', () => {
it('makeStyles``', () => {
const runner = () =>
run(
dedent`import { makeStyles } from "@wyw-in-js/object-syntax"; export const square = makeStyles\`\`;`
);

expect(runner).toThrow('Invalid usage of `makeStyles` function');
});

it('makeStyles.div``', () => {
const runner = () =>
run(
dedent`import { makeStyles } from "@wyw-in-js/object-syntax"; export const square = makeStyles.div\`\`;`
);

expect(runner).toThrow('Invalid usage of `makeStyles` function');
});

it('makeStyles("div")``', () => {
const runner = () =>
run(
dedent`import { makeStyles } from "@wyw-in-js/object-syntax"; export const square = makeStyles("div")\`\`;`
);

expect(runner).toThrow('Invalid usage of `makeStyles` function');
});

it('do not throw if css is not a call', () => {
const runner = () =>
run(dedent`export { makeStyles } from "@wyw-in-js/object-syntax";`);

expect(runner).not.toThrow();
});
});
});
1 change: 1 addition & 0 deletions examples/object-syntax/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { makeStyles } from './makeStyles';
6 changes: 6 additions & 0 deletions examples/object-syntax/src/makeStyles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export function makeStyles<Slots extends string | number>(
// eslint-disable-next-line @typescript-eslint/no-unused-vars
stylesBySlots: Record<Slots, unknown>
): () => Record<Slots, string> {
throw new Error('Cannot be called in runtime');
}
114 changes: 114 additions & 0 deletions examples/object-syntax/src/processors/makeStyles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/* eslint-disable class-methods-use-this */
import type { Expression } from '@babel/types';
import { resolveStyleRulesForSlots } from '@griffel/core';
import type {
StylesBySlots,
CSSClassesMapBySlot,
CSSRulesByBucket,
} from '@griffel/core/types';

import type {
ValueCache,
Params,
TailProcessorParams,
} from '@wyw-in-js/processor-utils';
import { BaseProcessor, validateParams } from '@wyw-in-js/processor-utils';

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

#cssRulesByBucket: CSSRulesByBucket | undefined;

readonly #slotsExpName: string | number | boolean | null;

public constructor(params: Params, ...args: TailProcessorParams) {
validateParams(
params,
['callee', 'call'],
'Invalid usage of `makeStyles` function'
);
const [callee, callParam] = params;

super([callee], ...args);

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

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

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

public override build(valueCache: ValueCache) {
const slots = valueCache.get(this.#slotsExpName) as StylesBySlots<string>;
[this.#cssClassMap, this.#cssRulesByBucket] =
resolveStyleRulesForSlots(slots);
}

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

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 importedStyles = t.addNamedImport('__styles', '@griffel/react');

const cssClassMap = t.objectExpression(
Object.entries(this.#cssClassMap).map(([slot, classesMap]) => {
return t.objectProperty(
t.identifier(slot),
t.objectExpression(
Object.entries(classesMap).map(([className, classValue]) =>
t.objectProperty(
t.identifier(className),
Array.isArray(classValue)
? t.arrayExpression(classValue.map((i) => t.stringLiteral(i)))
: t.stringLiteral(classValue)
)
)
)
);
})
);

const cssRulesByBucket = t.objectExpression(
Object.entries(this.#cssRulesByBucket).map(([bucket, rules]) => {
return t.objectProperty(
t.identifier(bucket),
t.arrayExpression(
// FIXME: rule can be [string, Record<string, unknown>]
rules.map((rule) => t.stringLiteral(rule as string))
)
);
})
);

const stylesCall = t.callExpression(importedStyles, [
cssClassMap,
cssRulesByBucket,
]);
this.replacer(stylesCall, true);
}

public override toString(): string {
return `${super.toString()}(…)`;
}
}
4 changes: 4 additions & 0 deletions examples/object-syntax/tsconfig.eslint.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"extends": "./tsconfig.json",
"exclude": []
}
Loading

0 comments on commit 141dbaf

Please sign in to comment.