diff --git a/.changeset/better-ts-types.md b/.changeset/better-ts-types.md new file mode 100644 index 0000000..45f9e54 --- /dev/null +++ b/.changeset/better-ts-types.md @@ -0,0 +1,6 @@ +--- +"@rhds/tokens": minor +--- + +Improved exported typescript types, use string references in media tokens' +javascript module representation diff --git a/build.js b/build.js index da58df3..de2c00d 100644 --- a/build.js +++ b/build.js @@ -46,6 +46,7 @@ export function build() { .registerFormat(Formats.hexokinase) .registerFormat(Formats.docsPage) .registerAction(Actions.copyAssets) + .registerAction(Actions.copyTypes) .registerAction(Actions.writeEsMapDeclaration) .registerAction(Actions.writeVSIXManifest) .registerAction(Actions.descriptionFile) diff --git a/lib/actions.js b/lib/actions.js index 4d04feb..6ddacc5 100644 --- a/lib/actions.js +++ b/lib/actions.js @@ -1,4 +1,12 @@ -import { readFileSync, copyFileSync, mkdirSync, rmSync, writeFileSync, rmdirSync, readdirSync } from 'node:fs'; +import { + readFileSync, + copyFileSync, + mkdirSync, + rmSync, + writeFileSync, + rmdirSync, + readdirSync, +} from 'node:fs'; import { dirname, join } from 'node:path'; import { fileURLToPath } from 'node:url'; @@ -7,6 +15,8 @@ import { fileURLToPath } from 'node:url'; const rel = path => new URL(path, import.meta.url); const DOCS_STYLES_IN = fileURLToPath(rel('../docs/styles.css')); const DOCS_STYLES_OUT = fileURLToPath(rel('../build/styles.css')); +const TYPES_IN = fileURLToPath(rel('./types.ts')); +const TYPES_OUT = fileURLToPath(rel('../js/types.ts')); const ASSETS_IN_DIR = rel('../docs/assets/'); const ASSETS_OUT_DIR = rel('../build/assets/'); const PACKAGE_JSON_URL = rel('../package.json'); @@ -48,6 +58,20 @@ export const copyAssets = { } }; +/** + * Copy base TS types + * @type {Action} + */ +export const copyTypes = { + name: 'copyTypes', + do() { + copyFileSync(TYPES_IN, TYPES_OUT); + }, + undo() { + rmSync(TYPES_OUT, { force: true }); + } +}; + /** * Write declaration file for JS token map * @type {Action} diff --git a/lib/formats/modules.js b/lib/formats/modules.js index 2fd7f05..e16666c 100644 --- a/lib/formats/modules.js +++ b/lib/formats/modules.js @@ -7,8 +7,31 @@ import { colorFormats } from '../transforms.js'; const { fileHeader } = StyleDictionary.formatHelpers; function deserializeShadow(x) { - const [offsetX, offsetY, blur, spread, color] = x.split(' '); - return { offsetX, offsetY, blur, spread, color }; + const [offsetX, offsetY, blur, spread, color] = x.$value.split(' '); + return JSON.stringify({ offsetX, offsetY, blur, spread, color }); +} + +const capitalize = x => `${x.at(0).toUpperCase()}${x.slice(1)}`; + +function colorRef(x) { + const r = JSON.stringify(colorFormats.transformer({ ...x })); + return r; +} + +function mediaRef(x) { + const values = []; + const stringified = JSON.stringify(x.original.$value, (_, value) => { + const [, inner] = value?.match?.(/^\{(.*)\}$/) ?? []; + if (inner) { + const v = inner.split('.').map(capitalize).join(''); + values.push(v); + return v.replace(/"(.*)"/, '$1'); + } else { + return value; + } + }); + const r = `${stringified.replace(new RegExp(`"(${values.join('|')})"`), '$1')} as const`; + return r; } /** @@ -23,19 +46,28 @@ export const modules = { formatter({ file, dictionary, platform }) { const categories = new Set(dictionary.allTokens.map(x => x.attributes.category)); for (const name of categories) { - const category = dictionary.allTokens.filter(x => x.attributes?.category === name); - const outpath = join(process.cwd(), platform.buildPath, `${name}.js`); - const content = category - .filter(x => !Predicates.isColor(x) || !x.name.match(/(rgb|hsl)$/i)) + const outpath = join(process.cwd(), platform.buildPath, `${name}.ts`); + const category = dictionary + .allTokens + .filter(x => x.attributes?.category === name) + // don't output -rgb and -hsl tokens, because colours here are structured data which includes hsl and rgb values + .filter(x => !Predicates.isColor(x) || !x.name.match(/(rgb|hsl)$/i)); + const defs = category .map(x => { const value = - Predicates.isColor(x) ? colorFormats.transformer({ ...x }) - : Predicates.isShadow(x) ? deserializeShadow(x.$value) - : x.$value; - return `export const ${x.name} = ${JSON.stringify(value)};`; - }) - .join('\n'); - writeFileSync(outpath, content, 'utf8'); + Predicates.isColor(x) ? colorRef(x) + : Predicates.isShadow(x) ? deserializeShadow(x) + : Predicates.isMediaQuery(x) ? mediaRef(x) + : JSON.stringify(x.$value); + return `export const ${x.name}${Predicates.isColor(x) ? ': Color' : ''} = ${value};`; + }); + const hasColors = category.some(x => Predicates.isColor(x)); + const contents = [ + fileHeader({ file }), + ...!hasColors ? [] : ['import type { Color } from "./types.js";'], + ...defs + ].join('\n'); + writeFileSync(outpath, contents, 'utf8'); } return [ fileHeader({ file }), diff --git a/lib/types.ts b/lib/types.ts new file mode 100644 index 0000000..6f7f9d9 --- /dev/null +++ b/lib/types.ts @@ -0,0 +1,22 @@ +export interface Color { + isLight: boolean; + hex: string; + rgb: { + r: number; + g: number; + b: number; + a: number; + }; + hsl: { + h: number; + s: number; + l: number; + a: number; + }; + hsv: { + h: number; + s: number; + v: number; + a: number; + }; +} diff --git a/package-lock.json b/package-lock.json index aa0c546..dd4e33e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@rhds/tokens", - "version": "1.0.0-beta.16", + "version": "1.0.0-beta.21", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@rhds/tokens", - "version": "1.0.0-beta.16", + "version": "1.0.0-beta.21", "dependencies": { "@11ty/eleventy-plugin-syntaxhighlight": "^4.2.0", "highlight.js": "^11.7.0", @@ -26,6 +26,7 @@ "tap-difflet": "^0.7.2", "tape-describe": "^1.0.3", "tape-es": "^1.2.17", + "typescript": "^5.0.2", "vsce": "^2.15.0", "wireit": "^0.9.5", "yaml": "^2.2.1" @@ -10934,17 +10935,16 @@ } }, "node_modules/typescript": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", - "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.2.tgz", + "integrity": "sha512-wVORMBGO/FAs/++blGNeAVdbNKtIh1rbBL2EyQ1+J9lClJ93KiiKe8PmFIVdXhHcyv44SL9oglmfeSsndo0jRw==", "dev": true, - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" }, "engines": { - "node": ">=4.2.0" + "node": ">=12.20" } }, "node_modules/uc.micro": { @@ -20069,11 +20069,10 @@ } }, "typescript": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", - "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", - "dev": true, - "peer": true + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.2.tgz", + "integrity": "sha512-wVORMBGO/FAs/++blGNeAVdbNKtIh1rbBL2EyQ1+J9lClJ93KiiKe8PmFIVdXhHcyv44SL9oglmfeSsndo0jRw==", + "dev": true }, "uc.micro": { "version": "1.0.6", diff --git a/package.json b/package.json index 50b98e5..2ce04f0 100644 --- a/package.json +++ b/package.json @@ -18,34 +18,65 @@ "exports": { ".": { "import": "./js/tokens.js", - "require": "./js/tokens.cjs" + "require": "./js/tokens.cjs", + "types": "./js/tokens.d.ts" }, "./json/*": { "require": "./json/*" }, - "./plugins/11ty.cjs": "./plugins/11ty.cjs", "./plugins/stylelint.cjs": "./plugins/stylelint.cjs", "./css/*": "./css/*", "./values.js": { "import": "./js/values.js", - "require": "./js/values.cjs" - }, - "./animation.js": "./js/animation.js", - "./border.js": "./js/border.js", - "./box-shadow.js": "./js/box-shadow.js", - "./color.js": "./js/color.js", - "./icon.js": "./js/icon.js", - "./length.js": "./js/length.js", - "./media.js": "./js/media.js", - "./opacity.js": "./js/opacity.js", - "./space.js": "./js/space.js", - "./typography.js": "./js/typography.js" + "require": "./js/values.cjs", + "types": "./js/values.d.ts" + }, + "./animation.js": { + "import": "./js/animation.js", + "types": "./js/animation.d.ts" + }, + "./border.js": { + "import": "./js/border.js", + "types": "./js/border.d.ts" + }, + "./box-shadow.js": { + "import": "./js/box-shadow.js", + "types": "./js/box-shadow.d.ts" + }, + "./color.js": { + "import": "./js/color.js", + "types": "./js/color.d.ts" + }, + "./icon.js": { + "import": "./js/icon.js", + "types": "./js/icon.d.ts" + }, + "./length.js": { + "import": "./js/length.js", + "types": "./js/length.d.ts" + }, + "./media.js": { + "import": "./js/media.js", + "types": "./js/media.d.ts" + }, + "./opacity.js": { + "import": "./js/opacity.js", + "types": "./js/opacity.d.ts" + }, + "./space.js": { + "import": "./js/space.js", + "types": "./js/space.d.ts" + }, + "./typography.js": { + "import": "./js/typography.js", + "types": "./js/typography.d.ts" + } }, "files": [ "css", "editor", "plugins", - "js", + "js/**/*.{js,d.ts}", "json", "scss" ], @@ -57,6 +88,7 @@ "build": { "dependencies": [ "style-dictionary", + "types", "package", "11ty" ] @@ -97,6 +129,12 @@ "style-dictionary" ] }, + "types": { + "command": "tsc -m es2022 --target es2022 --moduleResolution nodenext -d ./js/*.ts", + "dependencies": [ + "style-dictionary" + ] + }, "package": { "command": "cd editor/vscode && vsce package", "dependencies": [ @@ -124,6 +162,7 @@ "tap-difflet": "^0.7.2", "tape-describe": "^1.0.3", "tape-es": "^1.2.17", + "typescript": "^5.0.2", "vsce": "^2.15.0", "wireit": "^0.9.5", "yaml": "^2.2.1" diff --git a/platforms.yaml b/platforms.yaml index 4e5d2d2..14cb901 100644 --- a/platforms.yaml +++ b/platforms.yaml @@ -67,6 +67,8 @@ map: js: transformGroup: js buildPath: js/ + actions: + - copyTypes files: - destination: values.js format: javascript/modules