From 1681cefb05c6e2572372319944ad6a93f80662d8 Mon Sep 17 00:00:00 2001 From: Roberto Redradix Date: Tue, 12 Sep 2023 18:16:46 +0200 Subject: [PATCH] refactor(core): add better semantics with wrapper internal types Additionally, throw runtime internal errors when TypeScript assumptions are not fulfilled. --- packages/core/src/lib/errors.ts | 7 +++ packages/core/src/lib/index.ts | 37 ++++++------ packages/core/src/lib/outputters/css/index.ts | 59 ++++++++++--------- packages/core/src/lib/outputters/errors.ts | 7 +++ packages/core/src/lib/outputters/json.ts | 17 ++++-- .../core/src/lib/outputters/scss/index.ts | 48 +++++++-------- packages/core/src/lib/outputters/types.ts | 11 +--- packages/core/src/lib/types.ts | 30 ++++++++-- 8 files changed, 120 insertions(+), 96 deletions(-) create mode 100644 packages/core/src/lib/errors.ts create mode 100644 packages/core/src/lib/outputters/errors.ts diff --git a/packages/core/src/lib/errors.ts b/packages/core/src/lib/errors.ts new file mode 100644 index 0000000..7ff07f9 --- /dev/null +++ b/packages/core/src/lib/errors.ts @@ -0,0 +1,7 @@ +export class PenpotExportInternalError extends Error { + constructor(message: string) { + super( + `${message}. This is an error in penpot-export code. Please contact their authors.`, + ) + } +} diff --git a/packages/core/src/lib/index.ts b/packages/core/src/lib/index.ts index d9e7316..066d0a2 100644 --- a/packages/core/src/lib/index.ts +++ b/packages/core/src/lib/index.ts @@ -12,6 +12,7 @@ import { normalizePenpotExportUserConfig, AssetConfig, } from './config' +import { PenpotExportInternalError } from './errors' import { writeTextFile, cssOutputter, @@ -19,22 +20,16 @@ import { jsonOutputter, OutputterFunction, } from './outputters' -import { - CSSClassDefinition, - CSSCustomPropertyDefinition, - FontsSummary, -} from './types' +import { PenpotExportAssets } from './types' const processOutput = ({ outputFormat = 'css', outputPath, - content, - metadata, + assets, }: { outputFormat: AssetConfig['format'] outputPath: string - content: CSSClassDefinition[] | CSSCustomPropertyDefinition[] - metadata?: FontsSummary + assets: PenpotExportAssets }) => { const outputter: OutputterFunction | null = outputFormat === 'css' @@ -46,11 +41,9 @@ const processOutput = ({ : null if (outputter === null) - throw new Error( - 'Unable to process output format. This is an error in penpot-export code, please contact their authors.', - ) + throw new PenpotExportInternalError('Unable to process output format') - const textContents = outputter(content, metadata) + const textContents = outputter(assets) return writeTextFile(outputPath, textContents) } @@ -77,7 +70,9 @@ export default async function penpotExport( processOutput({ outputFormat: colorsConfig.format, outputPath: path.resolve(rootProjectPath, colorsConfig.output), - content: adaptColorsToCssVariables(penpotFile), + assets: { + colors: adaptColorsToCssVariables(penpotFile), + }, }) console.log('βœ… Colors: %s', colorsConfig.output) @@ -87,8 +82,10 @@ export default async function penpotExport( processOutput({ outputFormat: typographiesConfig.format, outputPath: path.resolve(rootProjectPath, typographiesConfig.output), - content: adaptTypographiesToCssClassDefinitions(penpotFile), - metadata: summarizeTypographies(penpotFile), + assets: { + typographies: adaptTypographiesToCssClassDefinitions(penpotFile), + typographiesSummary: summarizeTypographies(penpotFile), + }, }) console.log('βœ… Typographies: %s', typographiesConfig.output) @@ -98,9 +95,11 @@ export default async function penpotExport( processOutput({ outputFormat: pagesConfig.format, outputPath: path.resolve(rootProjectPath, pagesConfig.output), - content: adaptPageComponentsToCssClassDefinitions(penpotFile, { - pageId: pagesConfig.pageId, - }), + assets: { + pageComponents: adaptPageComponentsToCssClassDefinitions(penpotFile, { + pageId: pagesConfig.pageId, + }), + }, }) console.log('βœ… Page components: %s', pagesConfig.output) diff --git a/packages/core/src/lib/outputters/css/index.ts b/packages/core/src/lib/outputters/css/index.ts index f3e7120..05592d1 100644 --- a/packages/core/src/lib/outputters/css/index.ts +++ b/packages/core/src/lib/outputters/css/index.ts @@ -1,10 +1,13 @@ import { CSSClassDefinition, CSSCustomPropertyDefinition, + ColorAssets, FontsSummary, - isCssClassDefinition, + PageComponentAssets, + TypographyAssets, } from '../../types' +import { PenpotExportInvalidAssetsError } from '../errors' import { describeFontsRequirements } from '../fileHeader' import { OutputterFunction } from '../types' @@ -14,12 +17,6 @@ import { textToCssCustomPropertyName, } from './syntax' -const areCssCustomPropertiesDefinitions = ( - objects: Array, -): objects is Array => { - return !objects.every(isCssClassDefinition) -} - const serializeCssClass = (cssClassDefinition: CSSClassDefinition): string => { const selector = textToCssClassSelector( `${cssClassDefinition.scope}--${cssClassDefinition.name}`, @@ -42,36 +39,40 @@ const serializeCssCustomProperty = ( return `${padding}${key}: ${value};` } +const serializeCssCustomPropertiesRoot = ( + cssDefinitions: CSSCustomPropertyDefinition[], +) => { + const pad = 2 + const cssDeclarations = cssDefinitions.map((customPropertyDefinition) => + serializeCssCustomProperty(customPropertyDefinition, pad), + ) + return [`:root {`, ...cssDeclarations, '}'].join('\n') +} + const composeFileHeader = (fontsSummary: FontsSummary) => { const message = describeFontsRequirements(fontsSummary) return ['/*', ...message.map((line) => ' * ' + line), '*/'].join('\n') } -const composeFileBody = ( - cssDefinitions: CSSClassDefinition[] | CSSCustomPropertyDefinition[], -) => { - if (areCssCustomPropertiesDefinitions(cssDefinitions)) { - const pad = 2 - const cssDeclarations = cssDefinitions.map((customPropertyDefinition) => - serializeCssCustomProperty(customPropertyDefinition, pad), - ) - return [`:root {`, ...cssDeclarations, '}'].join('\n') - } else { - return cssDefinitions.map(serializeCssClass).join('\n\n') +const serializeCss: OutputterFunction = ({ + colors, + typographies, + typographiesSummary, + pageComponents, +}: ColorAssets | TypographyAssets | PageComponentAssets): string => { + if (colors) { + return serializeCssCustomPropertiesRoot(colors) + } else if (typographies) { + const body = typographies.map(serializeCssClass).join('\n\n') + const header: string = composeFileHeader(typographiesSummary) + + return header + '\n\n' + body + } else if (pageComponents) { + return pageComponents.map(serializeCssClass).join('\n\n') } -} -const serializeCss: OutputterFunction = ( - cssDefinitions: CSSClassDefinition[] | CSSCustomPropertyDefinition[], - fontsSummary?: FontsSummary, -): string => { - const body: string = composeFileBody(cssDefinitions) - - if (fontsSummary === undefined) return body - - const header: string = composeFileHeader(fontsSummary) - return header + '\n\n' + body + throw new PenpotExportInvalidAssetsError() } export default serializeCss diff --git a/packages/core/src/lib/outputters/errors.ts b/packages/core/src/lib/outputters/errors.ts new file mode 100644 index 0000000..d0dc3ad --- /dev/null +++ b/packages/core/src/lib/outputters/errors.ts @@ -0,0 +1,7 @@ +import { PenpotExportInternalError } from '../errors' + +export class PenpotExportInvalidAssetsError extends PenpotExportInternalError { + constructor() { + super('Invalid penpot-export assets') + } +} diff --git a/packages/core/src/lib/outputters/json.ts b/packages/core/src/lib/outputters/json.ts index 7339079..dfda9ef 100644 --- a/packages/core/src/lib/outputters/json.ts +++ b/packages/core/src/lib/outputters/json.ts @@ -1,11 +1,18 @@ -import { CSSClassDefinition, CSSCustomPropertyDefinition } from '../types' +import { ColorAssets, PageComponentAssets, TypographyAssets } from '../types' +import { PenpotExportInvalidAssetsError } from './errors' import { OutputterFunction } from './types' -const serializeJson: OutputterFunction = ( - cssClassDefinitions: CSSClassDefinition[] | CSSCustomPropertyDefinition[], -): string => { - return JSON.stringify(cssClassDefinitions, null, 2) +const serializeJson: OutputterFunction = ({ + colors, + typographies, + pageComponents, +}: ColorAssets | TypographyAssets | PageComponentAssets): string => { + const assets = colors ?? typographies ?? pageComponents ?? null + + if (assets === null) throw new PenpotExportInvalidAssetsError() + + return JSON.stringify(assets, null, 2) } export default serializeJson diff --git a/packages/core/src/lib/outputters/scss/index.ts b/packages/core/src/lib/outputters/scss/index.ts index 89162f2..f0aa2f9 100644 --- a/packages/core/src/lib/outputters/scss/index.ts +++ b/packages/core/src/lib/outputters/scss/index.ts @@ -1,23 +1,20 @@ import { CSSClassDefinition, CSSCustomPropertyDefinition, + ColorAssets, FontsSummary, - isCssClassDefinition, + PageComponentAssets, + TypographyAssets, } from '../../types' import { camelToKebab } from '../css/syntax' +import { PenpotExportInvalidAssetsError } from '../errors' import { describeFontsRequirements } from '../fileHeader' import { OutputterFunction } from '../types' import { textToScssVariableName } from './syntax' -const areCssCustomPropertiesDefinitions = ( - objects: Array, -): objects is Array => { - return !objects.every(isCssClassDefinition) -} - /** * From: https://sass-lang.com/documentation/values/maps/ * Most of the time, it’s a good idea to use quoted strings rather than unquoted strings for map keys. This is because @@ -47,29 +44,24 @@ const composeFileHeader = (fontsSummary: FontsSummary) => { return message.map((line) => '// ' + line).join('\n') } -const composeFileBody = ( - cssDefinitions: CSSClassDefinition[] | CSSCustomPropertyDefinition[], -) => { - if (areCssCustomPropertiesDefinitions(cssDefinitions)) { - const cssDeclarations = cssDefinitions.map((customPropertyDefinition) => - serializeScssVariable(customPropertyDefinition), - ) - return cssDeclarations.join('\n') - } else { - return cssDefinitions.map(serializeScssMap).join('\n\n') +const serializeScss: OutputterFunction = ({ + colors, + typographies, + typographiesSummary, + pageComponents, +}: ColorAssets | TypographyAssets | PageComponentAssets): string => { + if (colors) { + return colors.map(serializeScssVariable).join('\n') + } else if (typographies) { + const body = typographies.map(serializeScssMap).join('\n\n') + const header: string = composeFileHeader(typographiesSummary) + + return header + '\n\n' + body + } else if (pageComponents) { + return pageComponents.map(serializeScssMap).join('\n\n') } -} -const serializeScss: OutputterFunction = ( - cssDefinitions: CSSClassDefinition[] | CSSCustomPropertyDefinition[], - fontsSummary?: FontsSummary, -): string => { - const body: string = composeFileBody(cssDefinitions) - - if (fontsSummary === undefined) return body - - const header: string = composeFileHeader(fontsSummary) - return header + '\n\n' + body + throw new PenpotExportInvalidAssetsError() } export default serializeScss diff --git a/packages/core/src/lib/outputters/types.ts b/packages/core/src/lib/outputters/types.ts index 68c2f56..cca9313 100644 --- a/packages/core/src/lib/outputters/types.ts +++ b/packages/core/src/lib/outputters/types.ts @@ -1,10 +1,3 @@ -import { - CSSClassDefinition, - CSSCustomPropertyDefinition, - FontsSummary, -} from '../types' +import { PenpotExportAssets } from '../types' -export type OutputterFunction = ( - cssDefinitions: CSSClassDefinition[] | CSSCustomPropertyDefinition[], - metadata?: FontsSummary, -) => string +export type OutputterFunction = (assets: PenpotExportAssets) => string diff --git a/packages/core/src/lib/types.ts b/packages/core/src/lib/types.ts index 865bf58..1334193 100644 --- a/packages/core/src/lib/types.ts +++ b/packages/core/src/lib/types.ts @@ -4,12 +4,6 @@ export interface CSSClassDefinition { cssProps: Record } -export const isCssClassDefinition = ( - object: object, -): object is CSSClassDefinition => { - return 'cssProps' in object -} - export interface CSSCustomPropertyDefinition { scope: string name: string @@ -24,3 +18,27 @@ export interface FontsSummary { googleFonts: FontsDetails userCustomFonts: FontsDetails } + +interface BaseAssets { + colors?: never + typographies?: never + typographiesSummary?: never + pageComponents?: never +} +export interface ColorAssets extends Omit { + colors: CSSCustomPropertyDefinition[] +} +export interface TypographyAssets + extends Omit { + typographies: CSSClassDefinition[] + typographiesSummary: FontsSummary +} +export interface PageComponentAssets + extends Omit { + pageComponents: CSSClassDefinition[] +} + +export type PenpotExportAssets = + | ColorAssets + | TypographyAssets + | PageComponentAssets