UNPKG

@terrazzo/plugin-js

Version:

Use DTCG tokens in Node.js for server-rendered applications.

134 lines 5.46 kB
import { calculatePermutations, } from '@terrazzo/parser'; import { pascalCase } from 'scule'; import { FILE_HEADER, TYPE_MAP } from './lib.js'; const RESOLVER_JSDOC_COMMENT = '/** Produce a token set from a given input. */'; export function buildJS({ resolver, properties, logger, contexts, }) { const entry = { group: 'plugin', label: '@terrazzo/plugin-js' }; const typeMap = {}; // here, output not being an array drastically reduces memory usage for large token sets let output = FILE_HEADER; output += '\n'; // 1. Permutations // todo: replace internal usage of listPermutations const permutations = (contexts ? calculatePermutations(Object.entries(contexts)) : (resolver.listPermutations?.() ?? [])).map((value) => ({ value, // Note: id MUST have modifiers sorted alphabetically, so we can index them shallowly id: JSON.stringify(Object.fromEntries(Object.entries(value).toSorted((a, b) => a[0].localeCompare(b[0], 'en-us', { numeric: true })))), })); output += 'export const PERMUTATIONS = {\n'; let permutationI = 1; for (const { value, id: permutationID } of permutations) { const start = performance.now(); try { const tokens = resolver.apply(value); output += ` ${JSON.stringify(permutationID)}: {\n`; for (const id of Object.keys(tokens)) { output += ` ${serializeToken(tokens[id], properties)},\n`; if (!(id in typeMap)) { typeMap[id] = tokens[id].$type; } } output += ' },\n'; } catch (error) { logger.error({ ...entry, message: error.message }); } const timing = performance.now() - start; logger.info({ ...entry, message: `Permutation ${permutationI}/${permutations.length} done (${permutationID})`, timing, }); permutationI++; } output += '};\n'; // 2. Input defaults const inputDefaults = Object.fromEntries(resolver.source.resolutionOrder .filter((i) => i.type === 'modifier' && 'default' in i) .map((m) => [m.name, m.default])); output += `const INPUT_DEFAULTS = ${JSON.stringify(inputDefaults)};\n`; // 3. Resolver output += RESOLVER_JSDOC_COMMENT; output += '\n'; output += `export const resolver = { apply(userInput) { if (!userInput || typeof userInput !== "object") { throw new Error(\`invalid input: \${userInput}\`); } const input = { ...INPUT_DEFAULTS, ...userInput }; const inputKey = JSON.stringify(Object.fromEntries(Object.entries(input).sort((a, b) => a[0].localeCompare(b[0], "en-us", { numeric: true })))); return PERMUTATIONS[inputKey]; }, listPermutations() { return [${permutations.map((p) => p.id).join(', ')}]; }, };\n`; return { code: output, typeMap }; } export function buildDTS({ resolver, contexts, properties, typeMap, }) { const unique$type = new Set(Object.values(typeMap)); let output = FILE_HEADER; output += '\n'; output += 'import type {\n'; output += ' TokenNormalizedSet,\n'; for (const $type of unique$type) { if ($type in TYPE_MAP) { output += ` ${TYPE_MAP[$type]},\n`; } } output += '} from "@terrazzo/token-types";'; output += '\n\n'; output += 'export const PERMUTATIONS: Record<string, TokenNormalizedSet>;\n\n'; output += `type InputType = ${buildInputType(resolver, contexts)}\n\n`; for (const $type of unique$type) { if (!($type in TYPE_MAP)) { continue; } output += `export type ${localTypeName($type)} = Pick<${TYPE_MAP[$type]}, ${[...properties].map((p) => JSON.stringify(p)).join(' | ')}>;\n`; } output += '\nexport interface Tokens {\n'; for (const [id, $type] of Object.entries(typeMap)) { output += ` ${JSON.stringify(id)}: ${$type in TYPE_MAP ? localTypeName($type) : 'any'};\n`; } output += '}\n'; output += '\n'; output += RESOLVER_JSDOC_COMMENT; output += '\n'; output += `export const resolver: { apply(input: InputType): Tokens; listPermutations(): InputType[]; }; `; return output; } /** Generate TypeScript definition of valid inputs given a resolver */ function buildInputType(resolver, contexts) { const validContexts = contexts ?? {}; if (Object.keys(validContexts).length === 0) { for (const [name, m] of Object.entries(resolver.source.modifiers ?? {})) { validContexts[name] = Object.keys(m.contexts); } for (const m of resolver.source.resolutionOrder) { if (m?.type !== 'modifier') { continue; } validContexts[m.name] = Object.keys(m.contexts); } } let output = '{'; for (const [name, values] of Object.entries(validContexts)) { output += `\n ${JSON.stringify(name)}: ${values.length > 0 ? values.map((v) => JSON.stringify(v)).join(' | ') : 'never'};`; } output += '\n};'; return output; } function localTypeName($type) { return pascalCase($type); } /** Serialize normalized Tokens set into a string */ function serializeToken(token, includeProperties) { return `${JSON.stringify(token.id)}:${JSON.stringify(Object.fromEntries(Object.entries(token).filter(([k]) => includeProperties.has(k))))}`; } //# sourceMappingURL=build.js.map