From dbd7764d61f1c81f8511265a6aa6aca4baf7957f Mon Sep 17 00:00:00 2001 From: jonambas Date: Fri, 5 Apr 2024 23:54:49 -0400 Subject: [PATCH 1/4] refactor: store Map instead of runtime lookups --- src/__tests__/codegen.test.ts | 49 ++++++++++++++++++ src/__tests__/ct.test.ts | 47 +++++++++++++++++ src/__tests__/get.test.ts | 72 --------------------------- src/__tests__/index.test.ts | 9 ++++ src/__tests__/map.test.ts | 51 +++++++++++++++++++ src/__tests__/parser.test.ts | 50 +++++++++++++++++++ src/__tests__/tsconfig.json | 6 --- src/__tests__/utils.test.ts | 53 ++++++-------------- src/codegen.ts | 12 ++--- src/{create-project.ts => context.ts} | 12 +++-- src/ct.ts | 25 ++++++++++ src/get.ts | 56 --------------------- src/index.ts | 14 +++--- src/map.ts | 45 +++++++++++++++++ src/parser.ts | 21 ++++---- src/types.ts | 1 + src/utils.ts | 23 +++------ tsconfig.json | 5 +- 18 files changed, 331 insertions(+), 220 deletions(-) create mode 100644 src/__tests__/codegen.test.ts create mode 100644 src/__tests__/ct.test.ts delete mode 100644 src/__tests__/get.test.ts create mode 100644 src/__tests__/index.test.ts create mode 100644 src/__tests__/map.test.ts create mode 100644 src/__tests__/parser.test.ts delete mode 100644 src/__tests__/tsconfig.json rename src/{create-project.ts => context.ts} (66%) create mode 100644 src/ct.ts delete mode 100644 src/get.ts create mode 100644 src/map.ts diff --git a/src/__tests__/codegen.test.ts b/src/__tests__/codegen.test.ts new file mode 100644 index 0000000..e7e481e --- /dev/null +++ b/src/__tests__/codegen.test.ts @@ -0,0 +1,49 @@ +import type { CodegenPrepareHookArgs } from '@pandacss/types'; +import { codegen } from '../codegen'; +import { createContext } from '../context'; + +const context = createContext({ + foo: { 100: { value: { base: 'whitesmoke', lg: 'palegreen' } } }, + bar: { 100: '{colors.green.200}' }, +}); + +const args: CodegenPrepareHookArgs = { + artifacts: [ + { + id: 'css-fn', + files: [ + { file: 'css.mjs', code: '' }, + { file: 'css.d.ts', code: '' }, + ], + }, + ], + changed: [], +}; + +describe('codegen', () => { + it('generates ct runtime code', () => { + const result = codegen(args, context) as any[]; + expect(result[0].files[0]).toMatchInlineSnapshot(` + { + "code": " + const pluginCtMap = new Map(JSON.parse('[["foo.100",{"base":"whitesmoke","lg":"palegreen"}],["bar.100","{colors.green.200}"]]')); + + export const ct = (path) => { + if (!path) return 'panda-plugin-ct-path-empty'; + if (!pluginCtMap.has(path)) return 'panda-plugin-ct-alias-not-found'; + return pluginCtMap.get(path); + }; + ", + "file": "css.mjs", + } + `); + + expect(result[0].files[1]).toMatchInlineSnapshot(` + { + "code": " + export const ct: (alias: "foo.100" | "bar.100") => string;", + "file": "css.d.ts", + } + `); + }); +}); diff --git a/src/__tests__/ct.test.ts b/src/__tests__/ct.test.ts new file mode 100644 index 0000000..85fe33f --- /dev/null +++ b/src/__tests__/ct.test.ts @@ -0,0 +1,47 @@ +import { ct, ctTemplate } from '../ct'; + +const tokens = { + foo: { a: { b: { c: { value: { base: '10px', lg: '20px' } } } } }, + bar: { baz: { 100: { value: {} }, 200: { value: {} } } }, + baz: { 100: 'hello', 200: { value: 'goodbye' } }, +}; + +describe('ct', () => { + it('gets a string', () => { + expect(ct(tokens, 'baz.100')).toBe('hello'); + }); + + it('gets a value object', () => { + expect(ct(tokens, 'foo.a.b.c')).toMatchInlineSnapshot( + ` + { + "base": "10px", + "lg": "20px", + } + `, + ); + }); + + it('gets a value string', () => { + expect(ct(tokens, 'baz.200')).toMatchInlineSnapshot(`"goodbye"`); + }); + + it('gets an undefined token', () => { + expect(ct(tokens, 'nope.nope')).toBeUndefined(); + expect(ct(tokens, 'foo.baz')).toBeUndefined(); + }); +}); + +describe('getTemplate', () => { + it('generates a ct function', () => { + expect(ctTemplate).toMatchInlineSnapshot(` + " + export const ct = (path) => { + if (!path) return 'panda-plugin-ct-path-empty'; + if (!pluginCtMap.has(path)) return 'panda-plugin-ct-alias-not-found'; + return pluginCtMap.get(path); + }; + " + `); + }); +}); diff --git a/src/__tests__/get.test.ts b/src/__tests__/get.test.ts deleted file mode 100644 index 9fb3a7b..0000000 --- a/src/__tests__/get.test.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { get, getTemplate } from '../get'; - -const tokens = { - foo: { a: { b: { c: { value: { base: '10px', lg: '20px' } } } } }, - bar: { baz: { 100: { value: {} }, 200: { value: {} } } }, - baz: { 100: 'hello', 200: { value: 'goodbye' } }, -}; - -describe('get', () => { - it('gets a string', () => { - expect(get(tokens)('baz.100')).toBe('"hello"'); - }); - - it('gets a value object', () => { - expect(get(tokens)('foo.a.b.c')).toMatchInlineSnapshot( - `"{"base":"10px","lg":"20px"}"`, - ); - }); - - it('gets a value string', () => { - expect(get(tokens)('baz.200')).toMatchInlineSnapshot(`""goodbye""`); - }); - - it('gets an undefined token', () => { - expect(get(tokens)('nope.nope')).toBe(`"panda-plugin-ct-alias-not-found"`); - }); - - it('gets an undefined path', () => { - // @ts-expect-error Checking arg omission - expect(get(tokens)()).toBe(`"panda-plugin-ct-alias-not-found"`); - }); -}); - -describe('getTemplate', () => { - it('generates a ct function', () => { - expect(getTemplate({ foo: { value: { base: '#fff', md: '#000' } } })) - .toMatchInlineSnapshot(` - " - const pluginCtTokens = { - "foo": { - "value": { - "base": "#fff", - "md": "#000" - } - } - }; - - export const ct = (path) => { - if (!path) return "panda-plugin-ct-alias-not-found"; - - const parts = path.split('.'); - let current = pluginCtTokens; - - for (const part of parts) { - if (!current[part]) break; - current = current[part]; - } - - if (typeof current === 'string') { - return current; - } - - if (typeof current === 'object' && current != null && !Array.isArray(current) && 'value' in current) { - return current.value; - } - - return "panda-plugin-ct-alias-not-found"; - }; - " - `); - }); -}); diff --git a/src/__tests__/index.test.ts b/src/__tests__/index.test.ts new file mode 100644 index 0000000..86babfd --- /dev/null +++ b/src/__tests__/index.test.ts @@ -0,0 +1,9 @@ +import { pluginComponentTokens } from '..'; + +describe('pluginComponentTokens', () => { + it('returns a PandaPlugin', () => { + expect(pluginComponentTokens).toBeTypeOf('function'); + expect(pluginComponentTokens({}).name).toBeDefined(); + expect(pluginComponentTokens({}).hooks).toBeDefined(); + }); +}); diff --git a/src/__tests__/map.test.ts b/src/__tests__/map.test.ts new file mode 100644 index 0000000..1d8b897 --- /dev/null +++ b/src/__tests__/map.test.ts @@ -0,0 +1,51 @@ +import { makeMap, makePaths, mapTemplate } from '../map'; + +const tokens = { + foo: { 100: { value: '#fff' }, 200: { value: { base: '#000' } } }, + bar: { 100: 'red', 200: 'blue' }, +}; + +describe('makePaths', () => { + it('makes paths', () => { + expect(makePaths(tokens)).toMatchInlineSnapshot(` + [ + "foo.100", + "foo.200", + "bar.100", + "bar.200", + ] + `); + }); +}); + +describe('mapTemplate', () => { + it('serializes a Map', () => { + const map = new Map([ + ['foo.100', '#fff'], + ['foo.200', { base: '#000' }], + ]); + + expect(mapTemplate(map)).toMatchInlineSnapshot( + ` + " + const pluginCtMap = new Map(JSON.parse('[["foo.100","#fff"],["foo.200",{"base":"#000"}]]')); + " + `, + ); + }); +}); + +describe('makeMap', () => { + it('makes a map', () => { + expect(makeMap(tokens)).toMatchInlineSnapshot(` + Map { + "foo.100" => "#fff", + "foo.200" => { + "base": "#000", + }, + "bar.100" => "red", + "bar.200" => "blue", + } + `); + }); +}); diff --git a/src/__tests__/parser.test.ts b/src/__tests__/parser.test.ts new file mode 100644 index 0000000..ba67c22 --- /dev/null +++ b/src/__tests__/parser.test.ts @@ -0,0 +1,50 @@ +import { parser } from '../parser'; +import { createContext } from '../context'; + +const context = createContext({ + foo: { 100: { value: { base: 'whitesmoke', lg: 'palegreen' } } }, + bar: { 100: '{colors.green.200}' }, +}); + +describe('parser', () => { + it('parses', () => { + const res = parser( + { + configure: () => {}, + filePath: 'test.tsx', + content: `
`, + }, + context, + ); + + expect(res).toMatchInlineSnapshot( + `"
"`, + ); + }); + + it('skips without "ct(" in contents', () => { + const res = parser( + { + configure: () => {}, + filePath: 'test.tsx', + content: `
`, + }, + context, + ); + + expect(res).toBeUndefined(); + }); + + it('skips without a path', () => { + const res = parser( + { + configure: () => {}, + filePath: 'test.tsx', + content: `
`, + }, + context, + ); + + expect(res).toMatchInlineSnapshot(`"
"`); + }); +}); diff --git a/src/__tests__/tsconfig.json b/src/__tests__/tsconfig.json deleted file mode 100644 index 4a57ad7..0000000 --- a/src/__tests__/tsconfig.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "compilerOptions": { - "types": ["vitest/globals"] - }, - "include": ["./**/*.ts"] -} diff --git a/src/__tests__/utils.test.ts b/src/__tests__/utils.test.ts index 7f7599c..d51ba7e 100644 --- a/src/__tests__/utils.test.ts +++ b/src/__tests__/utils.test.ts @@ -1,49 +1,24 @@ -import { isObject, makePaths } from '../utils'; +import { isObjectWithValue, isObject } from '../utils'; -describe('isObject', () => { - it('returns true if an object', () => { - expect(isObject({})).toBe(true); - expect(isObject({ foo: 'bar' })).toBe(true); +describe('isObjectWithValue', () => { + it('returns true for an object with a value property', () => { + expect(isObjectWithValue({ value: '' })).toBe(true); + expect(isObjectWithValue({ value: {} })).toBe(true); }); - it('returns false if not an object', () => { - expect(isObject(1)).toBe(false); - expect(isObject('1')).toBe(false); - expect(isObject(undefined)).toBe(false); - expect(isObject(null)).toBe(false); - expect(isObject([1, 2, 3])).toBe(false); + it('returns false for an object without a value property', () => { + expect(isObjectWithValue({})).toBe(false); }); }); -describe('makePaths', () => { - it('makes paths', () => { - expect( - makePaths({ - foo: { 100: { value: '#fff' }, 200: '' }, - bar: { 100: '', 200: '' }, - }), - ).toMatchInlineSnapshot(` - [ - "foo.100", - "foo.200", - "bar.100", - "bar.200", - ] - `); +describe('isObject', () => { + it('returns true for an object', () => { + expect(isObject({})).toBe(true); }); - it('makes paths with object values', () => { - expect( - makePaths({ - foo: { a: { b: { c: { value: { base: '', lg: '' } } } } }, - bar: { baz: { 100: { value: '#fff' }, 200: { value: {} } } }, - }), - ).toMatchInlineSnapshot(` - [ - "foo.a.b.c", - "bar.baz.100", - "bar.baz.200", - ] - `); + it('returns false for not an object', () => { + expect(isObject([1, 2, 3])).toBe(false); + expect(isObject(null)).toBe(false); + expect(isObject(undefined)).toBe(false); }); }); diff --git a/src/codegen.ts b/src/codegen.ts index 90113bb..e6e02e4 100644 --- a/src/codegen.ts +++ b/src/codegen.ts @@ -3,16 +3,15 @@ import type { MaybeAsyncReturn, Artifact, } from '@pandacss/types'; -import { makePaths } from './utils'; +import { makePaths, mapTemplate } from './map'; import type { PluginContext } from './types'; -import { getTemplate } from './get'; +import { ctTemplate } from './ct'; export const codegen = ( args: CodegenPrepareHookArgs, - context: Partial, + context: PluginContext, ): MaybeAsyncReturn => { - const tokens = context.tokens ?? {}; - if (!tokens) return; + const { tokens, map } = context; const cssFn = args.artifacts.find((a) => a.id === 'css-fn'); if (!cssFn) return args.artifacts; @@ -20,7 +19,8 @@ export const codegen = ( const cssFile = cssFn.files.find((f) => f.file.includes('css.mjs')); if (!cssFile) return args.artifacts; - cssFile.code += getTemplate(tokens); + cssFile.code += mapTemplate(map); + cssFile.code += ctTemplate; const cssDtsFile = cssFn.files.find((f) => f.file.includes('css.d.')); if (!cssDtsFile) return args.artifacts; diff --git a/src/create-project.ts b/src/context.ts similarity index 66% rename from src/create-project.ts rename to src/context.ts index befb61f..a50f000 100644 --- a/src/create-project.ts +++ b/src/context.ts @@ -1,7 +1,9 @@ import { Project, ts } from 'ts-morph'; +import type { ComponentTokens, PluginContext } from './types'; +import { makeMap } from './map'; -export const createProject = () => { - return new Project({ +export const createContext = (tokens: ComponentTokens): PluginContext => ({ + project: new Project({ compilerOptions: { jsx: ts.JsxEmit.React, jsxFactory: 'React.createElement', @@ -16,5 +18,7 @@ export const createProject = () => { skipAddingFilesFromTsConfig: true, skipFileDependencyResolution: true, skipLoadingLibFiles: true, - }); -}; + }), + tokens, + map: makeMap(tokens), +}); diff --git a/src/ct.ts b/src/ct.ts new file mode 100644 index 0000000..aea95f0 --- /dev/null +++ b/src/ct.ts @@ -0,0 +1,25 @@ +import type { ComponentTokens } from './types'; +import { isObjectWithValue } from './utils'; + +export const ct = (tokens: ComponentTokens, path: T) => { + const parts = path.split('.'); + let current = tokens; + + for (const part of parts) { + if (!current[part]) break; + current = current[part] as ComponentTokens; + } + + if (typeof current === 'string') return current; + if (isObjectWithValue(current)) return current.value; + + return; +}; + +export const ctTemplate = ` +export const ct = (path) => { + if (!path) return 'panda-plugin-ct-path-empty'; + if (!pluginCtMap.has(path)) return 'panda-plugin-ct-alias-not-found'; + return pluginCtMap.get(path); +}; +`; diff --git a/src/get.ts b/src/get.ts deleted file mode 100644 index fe2fe0d..0000000 --- a/src/get.ts +++ /dev/null @@ -1,56 +0,0 @@ -import type { ComponentTokens } from './types'; -import { isObject } from './utils'; - -const missing = `"panda-plugin-ct-alias-not-found"`; - -export const get = - (tokens: ComponentTokens) => - (path: string): string => { - if (!path) return missing; - - const parts = path.split('.'); - let current = tokens; - - for (const part of parts) { - if (!current[part]) break; - current = current[part] as ComponentTokens; - } - - if (typeof current === 'string') { - return `"${current}"`; - } - - if (isObject(current) && 'value' in current) { - return typeof current.value === 'string' - ? `"${current.value}"` - : JSON.stringify(current.value); - } - - return missing; - }; - -export const getTemplate = (tokens: ComponentTokens) => ` -const pluginCtTokens = ${JSON.stringify(tokens, null, 2)}; - -export const ct = (path) => { - if (!path) return ${missing}; - - const parts = path.split('.'); - let current = pluginCtTokens; - - for (const part of parts) { - if (!current[part]) break; - current = current[part]; - } - - if (typeof current === 'string') { - return current; - } - - if (typeof current === 'object' && current != null && !Array.isArray(current) && 'value' in current) { - return current.value; - } - - return ${missing}; -}; -`; diff --git a/src/index.ts b/src/index.ts index 838f449..44db809 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,25 +1,23 @@ import type { PandaPlugin } from '@pandacss/types'; import { parser } from './parser'; import { codegen } from './codegen'; -import { createProject } from './create-project'; -import type { ComponentTokens, PluginContext } from './types'; +import { createContext } from './context'; +import type { ComponentTokens } from './types'; +import { makeMap } from './map'; /** - * + * @see https://github.com/jonambas/panda-plugin-ct */ const pluginComponentTokens = (tokens: ComponentTokens): PandaPlugin => { - const context: Partial = {}; + const context = createContext(tokens); return { name: 'panda-plugin-ct', hooks: { - 'config:resolved': () => { - context.project = createProject(); - context.tokens = tokens; - }, 'parser:before': (args) => { return parser(args, context); }, 'codegen:prepare': (args) => { + context.map = makeMap(tokens); return codegen(args, context); }, }, diff --git a/src/map.ts b/src/map.ts new file mode 100644 index 0000000..92fc92f --- /dev/null +++ b/src/map.ts @@ -0,0 +1,45 @@ +import { ct } from './ct'; +import type { ComponentTokens } from './types'; +import { isObject } from './utils'; + +// Create an array of all string paths from an object. +export const makePaths = ( + obj: Record, + prefix?: string, +): string[] => { + const pathPrefix = prefix ? prefix + '.' : ''; + const paths = []; + + for (const [key, value] of Object.entries(obj)) { + if (!isObject(value) || 'value' in value) { + paths.push(`${pathPrefix}${key}`); + } else { + paths.push(...makePaths(value, `${pathPrefix}${key}`)); + } + } + + return paths; +}; + +// Create a Map of all alias paths and values. +export const makeMap = (tokens: ComponentTokens) => { + const map = new Map(); + + for (const path of makePaths(tokens)) { + const value = ct(tokens, path); + if (value) { + map.set(path, value); + } + } + + return map; +}; + +// Serialize a Map to a JSON string. +const serializeMap = (map: Map) => { + return JSON.stringify(Array.from(map.entries())); +}; + +// Generate a template string for the token alias Map. +export const mapTemplate = (map: Map) => + `\nconst pluginCtMap = new Map(JSON.parse('${serializeMap(map)}'));\n`; diff --git a/src/parser.ts b/src/parser.ts index ae9df4c..a04e283 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -1,15 +1,12 @@ import type { ParserResultBeforeHookArgs } from '@pandacss/types'; import type { PluginContext } from './types'; -import { get } from './get'; +import { isObject } from './utils'; export const parser = ( args: ParserResultBeforeHookArgs, - context: Partial, + context: PluginContext, ): string | void => { - const tokens = context.tokens ?? {}; - const project = context.project; - - if (!tokens || !project) return; + const { project, map } = context; // TODO: handle `import { ct as xyz }` aliasing const content = args.content; @@ -19,10 +16,8 @@ export const parser = ( overwrite: true, }); - const text = source.getText(); + let text = source.getText(); const calls = text.match(/ct\(['"][\w.]+['"]\)/g) ?? []; - const ct = get(tokens); - let newText = text; for (const call of calls) { const path = call @@ -30,8 +25,12 @@ export const parser = ( ?.toString() .replace(/['"]/g, ''); if (!path) continue; - newText = newText.replace(call, ct(path)); + const value = map.get(path); + text = text.replace( + call, + isObject(value) ? JSON.stringify(value) : `'${value}'`, + ); } - return newText; + return text; }; diff --git a/src/types.ts b/src/types.ts index f620cfa..f8e7238 100644 --- a/src/types.ts +++ b/src/types.ts @@ -5,4 +5,5 @@ export type ComponentTokens = { [k: string]: string | ComponentTokens }; export type PluginContext = { project: Project; tokens: ComponentTokens; + map: Map; }; diff --git a/src/utils.ts b/src/utils.ts index 650c222..2aa9fa6 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -2,20 +2,11 @@ export const isObject = (value: any) => { return typeof value === 'object' && value != null && !Array.isArray(value); }; -export const makePaths = ( - obj: Record, - prefix?: string, -): string[] => { - const pathPrefix = prefix ? prefix + '.' : ''; - const paths = []; - - for (const [key, value] of Object.entries(obj)) { - if (!isObject(value) || 'value' in value) { - paths.push(`${pathPrefix}${key}`); - } else { - paths.push(...makePaths(value, `${pathPrefix}${key}`)); - } - } - - return paths; +export const isObjectWithValue = (obj: any) => { + return ( + typeof obj === 'object' && + obj != null && + !Array.isArray(obj) && + 'value' in obj + ); }; diff --git a/tsconfig.json b/tsconfig.json index 309ba52..0a4bd51 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,8 +6,9 @@ "outDir": "dist", "strict": true, "esModuleInterop": true, - "skipLibCheck": true + "skipLibCheck": true, + "types": ["vitest/globals"] }, "include": ["src/**/*.ts"], - "exclude": ["node_modules", "**/__tests__/**"] + "exclude": ["node_modules"] } From 012200a83881d00160dd8d49c3ee8747ff9a177f Mon Sep 17 00:00:00 2001 From: jonambas Date: Fri, 5 Apr 2024 23:59:46 -0400 Subject: [PATCH 2/4] test: use same token object in tests --- src/__tests__/codegen.test.ts | 8 ++++---- src/__tests__/ct.test.ts | 14 ++++++-------- src/__tests__/parser.test.ts | 21 ++++----------------- 3 files changed, 14 insertions(+), 29 deletions(-) diff --git a/src/__tests__/codegen.test.ts b/src/__tests__/codegen.test.ts index e7e481e..f5d7d36 100644 --- a/src/__tests__/codegen.test.ts +++ b/src/__tests__/codegen.test.ts @@ -3,8 +3,8 @@ import { codegen } from '../codegen'; import { createContext } from '../context'; const context = createContext({ - foo: { 100: { value: { base: 'whitesmoke', lg: 'palegreen' } } }, - bar: { 100: '{colors.green.200}' }, + foo: { 100: { value: '#fff' }, 200: { value: { base: '#000' } } }, + bar: { 100: 'red', 200: 'blue' }, }); const args: CodegenPrepareHookArgs = { @@ -26,7 +26,7 @@ describe('codegen', () => { expect(result[0].files[0]).toMatchInlineSnapshot(` { "code": " - const pluginCtMap = new Map(JSON.parse('[["foo.100",{"base":"whitesmoke","lg":"palegreen"}],["bar.100","{colors.green.200}"]]')); + const pluginCtMap = new Map(JSON.parse('[["foo.100","#fff"],["foo.200",{"base":"#000"}],["bar.100","red"],["bar.200","blue"]]')); export const ct = (path) => { if (!path) return 'panda-plugin-ct-path-empty'; @@ -41,7 +41,7 @@ describe('codegen', () => { expect(result[0].files[1]).toMatchInlineSnapshot(` { "code": " - export const ct: (alias: "foo.100" | "bar.100") => string;", + export const ct: (alias: "foo.100" | "foo.200" | "bar.100" | "bar.200") => string;", "file": "css.d.ts", } `); diff --git a/src/__tests__/ct.test.ts b/src/__tests__/ct.test.ts index 85fe33f..3885e56 100644 --- a/src/__tests__/ct.test.ts +++ b/src/__tests__/ct.test.ts @@ -1,29 +1,27 @@ import { ct, ctTemplate } from '../ct'; const tokens = { - foo: { a: { b: { c: { value: { base: '10px', lg: '20px' } } } } }, - bar: { baz: { 100: { value: {} }, 200: { value: {} } } }, - baz: { 100: 'hello', 200: { value: 'goodbye' } }, + foo: { 100: { value: '#fff' }, 200: { value: { base: '#000' } } }, + bar: { 100: 'red', 200: 'blue' }, }; describe('ct', () => { it('gets a string', () => { - expect(ct(tokens, 'baz.100')).toBe('hello'); + expect(ct(tokens, 'bar.100')).toBe('red'); }); it('gets a value object', () => { - expect(ct(tokens, 'foo.a.b.c')).toMatchInlineSnapshot( + expect(ct(tokens, 'foo.200')).toMatchInlineSnapshot( ` { - "base": "10px", - "lg": "20px", + "base": "#000", } `, ); }); it('gets a value string', () => { - expect(ct(tokens, 'baz.200')).toMatchInlineSnapshot(`"goodbye"`); + expect(ct(tokens, 'foo.100')).toMatchInlineSnapshot(`"#fff"`); }); it('gets an undefined token', () => { diff --git a/src/__tests__/parser.test.ts b/src/__tests__/parser.test.ts index ba67c22..cf148d4 100644 --- a/src/__tests__/parser.test.ts +++ b/src/__tests__/parser.test.ts @@ -2,8 +2,8 @@ import { parser } from '../parser'; import { createContext } from '../context'; const context = createContext({ - foo: { 100: { value: { base: 'whitesmoke', lg: 'palegreen' } } }, - bar: { 100: '{colors.green.200}' }, + foo: { 100: { value: '#fff' }, 200: { value: { base: '#000' } } }, + bar: { 100: 'red', 200: 'blue' }, }); describe('parser', () => { @@ -12,13 +12,13 @@ describe('parser', () => { { configure: () => {}, filePath: 'test.tsx', - content: `
`, + content: `
`, }, context, ); expect(res).toMatchInlineSnapshot( - `"
"`, + `"
"`, ); }); @@ -34,17 +34,4 @@ describe('parser', () => { expect(res).toBeUndefined(); }); - - it('skips without a path', () => { - const res = parser( - { - configure: () => {}, - filePath: 'test.tsx', - content: `
`, - }, - context, - ); - - expect(res).toMatchInlineSnapshot(`"
"`); - }); }); From 7638437d203c716ff5463c921b95db655524ee88 Mon Sep 17 00:00:00 2001 From: jonambas Date: Sat, 6 Apr 2024 00:06:43 -0400 Subject: [PATCH 3/4] refactor: remove ct file --- src/__tests__/codegen.test.ts | 11 ++++----- src/__tests__/ct.test.ts | 45 ----------------------------------- src/__tests__/map.test.ts | 27 ++++++++++++++++++++- src/codegen.ts | 8 +++++-- src/ct.ts | 25 ------------------- src/map.ts | 20 +++++++++++++--- 6 files changed, 54 insertions(+), 82 deletions(-) delete mode 100644 src/__tests__/ct.test.ts delete mode 100644 src/ct.ts diff --git a/src/__tests__/codegen.test.ts b/src/__tests__/codegen.test.ts index f5d7d36..dcbee13 100644 --- a/src/__tests__/codegen.test.ts +++ b/src/__tests__/codegen.test.ts @@ -28,12 +28,11 @@ describe('codegen', () => { "code": " const pluginCtMap = new Map(JSON.parse('[["foo.100","#fff"],["foo.200",{"base":"#000"}],["bar.100","red"],["bar.200","blue"]]')); - export const ct = (path) => { - if (!path) return 'panda-plugin-ct-path-empty'; - if (!pluginCtMap.has(path)) return 'panda-plugin-ct-alias-not-found'; - return pluginCtMap.get(path); - }; - ", + export const ct = (path) => { + if (!pluginCtMap.has(path)) return 'panda-plugin-ct-alias-not-found'; + return pluginCtMap.get(path); + }; + ", "file": "css.mjs", } `); diff --git a/src/__tests__/ct.test.ts b/src/__tests__/ct.test.ts deleted file mode 100644 index 3885e56..0000000 --- a/src/__tests__/ct.test.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { ct, ctTemplate } from '../ct'; - -const tokens = { - foo: { 100: { value: '#fff' }, 200: { value: { base: '#000' } } }, - bar: { 100: 'red', 200: 'blue' }, -}; - -describe('ct', () => { - it('gets a string', () => { - expect(ct(tokens, 'bar.100')).toBe('red'); - }); - - it('gets a value object', () => { - expect(ct(tokens, 'foo.200')).toMatchInlineSnapshot( - ` - { - "base": "#000", - } - `, - ); - }); - - it('gets a value string', () => { - expect(ct(tokens, 'foo.100')).toMatchInlineSnapshot(`"#fff"`); - }); - - it('gets an undefined token', () => { - expect(ct(tokens, 'nope.nope')).toBeUndefined(); - expect(ct(tokens, 'foo.baz')).toBeUndefined(); - }); -}); - -describe('getTemplate', () => { - it('generates a ct function', () => { - expect(ctTemplate).toMatchInlineSnapshot(` - " - export const ct = (path) => { - if (!path) return 'panda-plugin-ct-path-empty'; - if (!pluginCtMap.has(path)) return 'panda-plugin-ct-alias-not-found'; - return pluginCtMap.get(path); - }; - " - `); - }); -}); diff --git a/src/__tests__/map.test.ts b/src/__tests__/map.test.ts index 1d8b897..ec05e5f 100644 --- a/src/__tests__/map.test.ts +++ b/src/__tests__/map.test.ts @@ -1,10 +1,35 @@ -import { makeMap, makePaths, mapTemplate } from '../map'; +import { get, makeMap, makePaths, mapTemplate } from '../map'; const tokens = { foo: { 100: { value: '#fff' }, 200: { value: { base: '#000' } } }, bar: { 100: 'red', 200: 'blue' }, }; +describe('get', () => { + it('gets a string', () => { + expect(get(tokens, 'bar.100')).toBe('red'); + }); + + it('gets a value object', () => { + expect(get(tokens, 'foo.200')).toMatchInlineSnapshot( + ` + { + "base": "#000", + } + `, + ); + }); + + it('gets a value string', () => { + expect(get(tokens, 'foo.100')).toMatchInlineSnapshot(`"#fff"`); + }); + + it('gets an undefined token', () => { + expect(get(tokens, 'nope.nope')).toBeUndefined(); + expect(get(tokens, 'foo.baz')).toBeUndefined(); + }); +}); + describe('makePaths', () => { it('makes paths', () => { expect(makePaths(tokens)).toMatchInlineSnapshot(` diff --git a/src/codegen.ts b/src/codegen.ts index e6e02e4..fb6ce41 100644 --- a/src/codegen.ts +++ b/src/codegen.ts @@ -5,7 +5,6 @@ import type { } from '@pandacss/types'; import { makePaths, mapTemplate } from './map'; import type { PluginContext } from './types'; -import { ctTemplate } from './ct'; export const codegen = ( args: CodegenPrepareHookArgs, @@ -20,7 +19,12 @@ export const codegen = ( if (!cssFile) return args.artifacts; cssFile.code += mapTemplate(map); - cssFile.code += ctTemplate; + cssFile.code += ` + export const ct = (path) => { + if (!pluginCtMap.has(path)) return 'panda-plugin-ct-alias-not-found'; + return pluginCtMap.get(path); + }; + `; const cssDtsFile = cssFn.files.find((f) => f.file.includes('css.d.')); if (!cssDtsFile) return args.artifacts; diff --git a/src/ct.ts b/src/ct.ts deleted file mode 100644 index aea95f0..0000000 --- a/src/ct.ts +++ /dev/null @@ -1,25 +0,0 @@ -import type { ComponentTokens } from './types'; -import { isObjectWithValue } from './utils'; - -export const ct = (tokens: ComponentTokens, path: T) => { - const parts = path.split('.'); - let current = tokens; - - for (const part of parts) { - if (!current[part]) break; - current = current[part] as ComponentTokens; - } - - if (typeof current === 'string') return current; - if (isObjectWithValue(current)) return current.value; - - return; -}; - -export const ctTemplate = ` -export const ct = (path) => { - if (!path) return 'panda-plugin-ct-path-empty'; - if (!pluginCtMap.has(path)) return 'panda-plugin-ct-alias-not-found'; - return pluginCtMap.get(path); -}; -`; diff --git a/src/map.ts b/src/map.ts index 92fc92f..39e2378 100644 --- a/src/map.ts +++ b/src/map.ts @@ -1,6 +1,20 @@ -import { ct } from './ct'; import type { ComponentTokens } from './types'; -import { isObject } from './utils'; +import { isObject, isObjectWithValue } from './utils'; + +export const get = (tokens: ComponentTokens, path: T) => { + const parts = path.split('.'); + let current = tokens; + + for (const part of parts) { + if (!current[part]) break; + current = current[part] as ComponentTokens; + } + + if (typeof current === 'string') return current; + if (isObjectWithValue(current)) return current.value; + + return; +}; // Create an array of all string paths from an object. export const makePaths = ( @@ -26,7 +40,7 @@ export const makeMap = (tokens: ComponentTokens) => { const map = new Map(); for (const path of makePaths(tokens)) { - const value = ct(tokens, path); + const value = get(tokens, path); if (value) { map.set(path, value); } From 7f60e7f9f1c3635ab6eca8cf0ea5beed1cc7f1c3 Mon Sep 17 00:00:00 2001 From: jonambas Date: Sat, 6 Apr 2024 00:08:50 -0400 Subject: [PATCH 4/4] chore: remove a generic --- src/map.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/map.ts b/src/map.ts index 39e2378..f7fda8d 100644 --- a/src/map.ts +++ b/src/map.ts @@ -1,7 +1,7 @@ import type { ComponentTokens } from './types'; import { isObject, isObjectWithValue } from './utils'; -export const get = (tokens: ComponentTokens, path: T) => { +export const get = (tokens: ComponentTokens, path: string) => { const parts = path.split('.'); let current = tokens;