Skip to content

Commit

Permalink
feat(griffel): proof of concept
Browse files Browse the repository at this point in the history
  • Loading branch information
Anber committed Jul 22, 2022
1 parent f0cddda commit c6e301e
Show file tree
Hide file tree
Showing 17 changed files with 320 additions and 14 deletions.
1 change: 1 addition & 0 deletions packages/babel/package.json
Expand Up @@ -6,6 +6,7 @@
"dependencies": {
"@babel/core": "^7.18.9",
"@babel/generator": "^7.18.9",
"@babel/helper-module-imports": "^7.18.6",
"@babel/template": "^7.18.6",
"@babel/traverse": "^7.18.9",
"@babel/types": "^7.18.9",
Expand Down
11 changes: 11 additions & 0 deletions packages/babel/src/helper-module-imports.d.ts
@@ -0,0 +1,11 @@
declare module '@babel/helper-module-imports' {
import type { NodePath } from '@babel/traverse';
import type { Identifier } from '@babel/types';

function addNamed(
path: NodePath,
name: string,
importedSource: string,
opts?: { nameHint: string }
): Identifier;
}
9 changes: 8 additions & 1 deletion packages/babel/src/utils/getTagProcessor.ts
Expand Up @@ -2,6 +2,7 @@ import { readFileSync } from 'fs';
import { basename, dirname, join } from 'path';

import { types as t } from '@babel/core';
import { addNamed } from '@babel/helper-module-imports';
import type { NodePath } from '@babel/traverse';
import type { Expression, SourceLocation, Identifier } from '@babel/types';
import findUp from 'find-up';
Expand Down Expand Up @@ -306,9 +307,15 @@ function getBuilderForIdentifier(
});
};

const astService = {
...t,
addNamedImport: (name: string, importedSource: string) =>
addNamed(path, name, importedSource),
};

return (...args: BuilderArgs) =>
new Processor(
t,
astService,
params,
(tagPath as NodePath<Expression>).node,
tagPath.node.loc ?? null,
Expand Down
3 changes: 3 additions & 0 deletions packages/griffel/babel.config.js
@@ -0,0 +1,3 @@
const config = require('../../babel.config');

module.exports = config;
55 changes: 55 additions & 0 deletions packages/griffel/package.json
@@ -0,0 +1,55 @@
{
"name": "@linaria/griffel",
"description": "Blazing fast zero-runtime CSS in JS library",
"version": "3.0.0-beta.21",
"bugs": "https://github.com/callstack/linaria/issues",
"dependencies": {
"@griffel/core": "^1.5.0",
"@linaria/logger": "workspace:^",
"@linaria/tags": "workspace:^",
"@linaria/utils": "workspace:^",
"ts-invariant": "^0.10.3"
},
"devDependencies": {
"@babel/types": "^7.18.9"
},
"engines": {
"node": "^12.16.0 || >=13.7.0"
},
"files": [
"esm/",
"lib/",
"processors/",
"types/"
],
"homepage": "https://github.com/callstack/linaria#readme",
"keywords": [
"css",
"css-in-js",
"linaria",
"react",
"styled-components"
],
"license": "MIT",
"linaria": {
"tags": {
"makeStyles": "./lib/processors/makeStyles.js"
}
},
"main": "lib/index.js",
"module": "esm/index.js",
"publishConfig": {
"access": "public"
},
"repository": "git@github.com:callstack/linaria.git",
"scripts": {
"build": "npm run build:lib && npm run build:esm && npm run build:declarations",
"build:declarations": "tsc --emitDeclarationOnly --outDir types",
"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",
"typecheck": "tsc --noEmit --composite false",
"watch": "npm run build --watch"
},
"sideEffects": false,
"types": "types/index.d.ts"
}
5 changes: 5 additions & 0 deletions packages/griffel/processors/makeStyles.js
@@ -0,0 +1,5 @@
Object.defineProperty(exports, '__esModule', {
value: true,
});

exports.default = require('../lib/processors/makeStyles').default;
1 change: 1 addition & 0 deletions packages/griffel/src/index.ts
@@ -0,0 +1 @@
export { default as makeStyles } from './makeStyles';
5 changes: 5 additions & 0 deletions packages/griffel/src/makeStyles.ts
@@ -0,0 +1,5 @@
export default function makeStyles<Slots extends string | number>(
stylesBySlots: Record<Slots, unknown>
): () => Record<Slots, string> {
throw new Error('Cannot be called in runtime');
}
107 changes: 107 additions & 0 deletions packages/griffel/src/processors/makeStyles.ts
@@ -0,0 +1,107 @@
/* 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 { Rules, ValueCache, ProcessorParams } from '@linaria/tags';
import { BaseProcessor, isCallParam } from '@linaria/tags';

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

#cssRulesByBucket: CSSRulesByBucket | undefined;

readonly #slotsExpName: string;

constructor(...args: ProcessorParams) {
super(...args);

const callParam = this.params.find(isCallParam);
if (!callParam || this.params.length !== 1) {
throw new Error('Invalid usage of `makeStyles` tag');
}

this.#slotsExpName = callParam[1].ex.name;
}

public override addInterpolation(): string {
throw new Error('Not implemented');
}

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

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(
rules.map((rule) => t.stringLiteral(rule as string))
)
);
})
);

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

protected get tagExpression(): Expression {
throw new Error('Not implemented');
}

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

extractRules(): Rules {
throw new Error('Not implemented');
}
}
5 changes: 5 additions & 0 deletions packages/griffel/tsconfig.json
@@ -0,0 +1,5 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": { "paths": {}, "rootDir": "src/" },
"references": [{ "path": "../core" }, { "path": "../logger" }, { "path": "../react" }, { "path": "../utils" }]
}
21 changes: 11 additions & 10 deletions packages/tags/src/BaseProcessor.ts
@@ -1,21 +1,25 @@
/* eslint-disable class-methods-use-this */
import type { types as t } from '@babel/core';
import type { SourceLocation, Expression, TemplateElement } from '@babel/types';
import type {
SourceLocation,
Expression,
TemplateElement,
Identifier,
} from '@babel/types';

import type {
ExpressionValue,
IInterpolation,
IPlaceholder,
Param,
Params,
Rules,
Value,
ValueCache,
Artifact,
TemplateParam,
} from './types';
import getClassNameAndSlug from './utils/getClassNameAndSlug';
import hasMeta from './utils/hasMeta';
import { isTemplateParam } from './utils/params';
import templateProcessor from './utils/templateProcessor';
import { isCSSable } from './utils/toCSS';
import type { IFileContext, IOptions } from './utils/types';
Expand All @@ -24,9 +28,6 @@ export { Expression };

export type ProcessorParams = ConstructorParameters<typeof BaseProcessor>;

const isTemplateParam = (param: Param): param is TemplateParam =>
param[0] === 'template';

export default abstract class BaseProcessor {
public readonly artifacts: Artifact[] = [];

Expand All @@ -45,7 +46,9 @@ export default abstract class BaseProcessor {
| undefined;

public constructor(
protected readonly astService: typeof t,
protected readonly astService: typeof t & {
addNamedImport: (name: string, source: string) => Identifier;
},
protected readonly params: Params,
protected readonly tagExp: Expression,
public readonly location: SourceLocation | null,
Expand Down Expand Up @@ -74,7 +77,7 @@ export default abstract class BaseProcessor {
return this.params.find(isTemplateParam)?.[1] ?? [];
}

public build(values: ValueCache): Artifact[] {
public build(values: ValueCache) {
if (this.artifacts.length > 0) {
// FIXME: why it was called twice?
throw new Error('Tag is already built');
Expand All @@ -84,8 +87,6 @@ export default abstract class BaseProcessor {
if (artifact) {
this.artifacts.push(['css', artifact]);
}

return this.artifacts;
}

public isValidValue(value: unknown): value is Value {
Expand Down
1 change: 1 addition & 0 deletions packages/tags/src/index.ts
@@ -1,6 +1,7 @@
export * from './BaseProcessor';
export * from './types';
export { default as isSerializable } from './utils/isSerializable';
export * from './utils/params';
export * from './utils/types';
export { default as BaseProcessor } from './BaseProcessor';
export { default as hasMeta } from './utils/hasMeta';
10 changes: 10 additions & 0 deletions packages/tags/src/utils/params.ts
@@ -0,0 +1,10 @@
import type { Param, TemplateParam, CallParam, MemberParam } from '../types';

export const isCallParam = (param: Param): param is CallParam =>
param[0] === 'call';

export const isMemberParam = (param: Param): param is MemberParam =>
param[0] === 'member';

export const isTemplateParam = (param: Param): param is TemplateParam =>
param[0] === 'template';
1 change: 1 addition & 0 deletions packages/testkit/package.json
Expand Up @@ -22,6 +22,7 @@
"@babel/types": "^7.18.9",
"@linaria/atomic": "workspace:^",
"@linaria/core": "workspace:^",
"@linaria/griffel": "workspace:^",
"@linaria/logger": "workspace:^",
"@linaria/utils": "workspace:^",
"@types/babel__core": "^7.1.19",
Expand Down
26 changes: 26 additions & 0 deletions packages/testkit/src/__snapshots__/babel.test.ts.snap
Expand Up @@ -1854,6 +1854,32 @@ CSS:
}
}
Dependencies: NA
`;
exports[`strategy shaker should process griffel makeStyles 1`] = `
"import { __styles as _styles } from \\"@griffel/react\\";
export const useStyles = /*#__PURE__*/_styles({
root: {
mc9l5x: \\"f22iagw\\",
Bi91k9c: \\"faf35ka\\",
Bb9khzn: \\"f17t1d3d\\",
Btk3f3y: \\"fh8e7tb\\"
}
}, {
d: [\\".f22iagw{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;}\\", \\".fh8e7tb .foo:hover{color:green;}\\"],
h: [\\".faf35ka:hover{color:red;}\\"],
f: [\\".f17t1d3d:focus:hover{color:blue;}\\"]
});"
`;
exports[`strategy shaker should process griffel makeStyles 2`] = `
CSS:
Dependencies: NA
`;
Expand Down
23 changes: 23 additions & 0 deletions packages/testkit/src/babel.test.ts
Expand Up @@ -2424,4 +2424,27 @@ describe('strategy shaker', () => {
expect(code).toMatchSnapshot();
expect(metadata).toMatchSnapshot();
});

it('should process griffel makeStyles', async () => {
const { code, metadata } = await transform(
dedent`
import { makeStyles } from '@linaria/griffel';
export const useStyles = makeStyles({
root: {
display: 'flex',
':hover': { color: 'red' },
':focus': { ':hover': { color: 'blue' } },
'& .foo': { ':hover': { color: 'green' } },
},
});
`,
[evaluator]
);

expect(code).toMatchSnapshot();
expect(metadata).toMatchSnapshot();
});
});

0 comments on commit c6e301e

Please sign in to comment.