mirror of
https://github.com/penpot/penpot-export.git
synced 2025-02-12 18:18:01 -05:00
feat(core): describe typography requirements and Google Fonts implementation aid
This commit is contained in:
parent
562e1d77e3
commit
1fcb0e0860
8 changed files with 150 additions and 26 deletions
|
@ -0,0 +1,36 @@
|
|||
import { PenpotApiFile } from '../../api'
|
||||
import { FontsSummary } from '../../types'
|
||||
|
||||
export const summarizeTypographies = (
|
||||
penpotFile: PenpotApiFile,
|
||||
): FontsSummary => {
|
||||
const typographies = Object.values(penpotFile.data.typographies ?? {})
|
||||
|
||||
const separator = '|>'
|
||||
const dedupedFontsKeys = Array.from(
|
||||
typographies.reduce((set, typography) => {
|
||||
const { fontId, fontFamily, fontWeight } = typography
|
||||
const typographyKey = [fontId, fontFamily, fontWeight].join(separator)
|
||||
|
||||
set.add(typographyKey)
|
||||
return set
|
||||
}, new Set<string>()),
|
||||
)
|
||||
|
||||
const fontsSummary = dedupedFontsKeys.reduce<FontsSummary>(
|
||||
(summary, typographyKey) => {
|
||||
const [fontId, fontFamily, fontWeight] = typographyKey.split(separator)
|
||||
const fontSource = fontId.startsWith('gfont-')
|
||||
? 'googleFonts'
|
||||
: 'userCustomFonts'
|
||||
|
||||
summary[fontSource][fontFamily] ??= []
|
||||
summary[fontSource][fontFamily].push(fontWeight)
|
||||
|
||||
return summary
|
||||
},
|
||||
{ googleFonts: {}, userCustomFonts: {} },
|
||||
)
|
||||
|
||||
return fontsSummary
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
import path from 'node:path'
|
||||
|
||||
import { adaptTypographiesToCssClassDefinitions } from './adapters/inbound/typographiesToCssClasses'
|
||||
import { summarizeTypographies } from './adapters/inbound/typographiesToFontSummary'
|
||||
import { adaptColorsToCssVariables } from './adapters/inbound/colorsToCssVariables'
|
||||
import { adaptPageComponentsToCssClassDefinitions } from './adapters/inbound/pageComponentsToCssClasses'
|
||||
|
||||
|
@ -16,30 +17,41 @@ import {
|
|||
cssOutputter,
|
||||
scssOutputter,
|
||||
jsonOutputter,
|
||||
OutputterFunction,
|
||||
} from './outputters'
|
||||
import { CSSClassDefinition, CSSCustomPropertyDefinition } from './types'
|
||||
import {
|
||||
CSSClassDefinition,
|
||||
CSSCustomPropertyDefinition,
|
||||
FontsSummary,
|
||||
} from './types'
|
||||
|
||||
const processOutput = ({
|
||||
outputFormat = 'css',
|
||||
outputPath,
|
||||
content,
|
||||
metadata,
|
||||
}: {
|
||||
outputFormat: AssetConfig['format']
|
||||
outputPath: string
|
||||
content: CSSClassDefinition[] | CSSCustomPropertyDefinition[]
|
||||
metadata?: FontsSummary
|
||||
}) => {
|
||||
if (outputFormat === 'css') {
|
||||
return writeTextFile(outputPath, cssOutputter, content)
|
||||
}
|
||||
if (outputFormat === 'scss') {
|
||||
return writeTextFile(outputPath, scssOutputter, content)
|
||||
}
|
||||
if (outputFormat === 'json') {
|
||||
return writeTextFile(outputPath, jsonOutputter, content)
|
||||
}
|
||||
throw new Error(
|
||||
'Unable to process output format. This is an error in penpot-export code, please contact their authors.',
|
||||
)
|
||||
const outputter: OutputterFunction | null =
|
||||
outputFormat === 'css'
|
||||
? cssOutputter
|
||||
: outputFormat === 'scss'
|
||||
? scssOutputter
|
||||
: outputFormat === 'json'
|
||||
? jsonOutputter
|
||||
: null
|
||||
|
||||
if (outputter === null)
|
||||
throw new Error(
|
||||
'Unable to process output format. This is an error in penpot-export code, please contact their authors.',
|
||||
)
|
||||
|
||||
const textContents = outputter(content, metadata)
|
||||
return writeTextFile(outputPath, textContents)
|
||||
}
|
||||
|
||||
export type * from './types'
|
||||
|
@ -78,6 +90,7 @@ export default async function penpotExport(
|
|||
outputFormat: typographiesConfig.format,
|
||||
outputPath: path.resolve(rootProjectPath, typographiesConfig.output),
|
||||
content: adaptTypographiesToCssClassDefinitions(penpotFile),
|
||||
metadata: summarizeTypographies(penpotFile),
|
||||
})
|
||||
|
||||
console.log('✅ Typographies: %s', typographiesConfig.output)
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
import {
|
||||
CSSClassDefinition,
|
||||
CSSCustomPropertyDefinition,
|
||||
FontsSummary,
|
||||
isCssClassDefinition,
|
||||
} from '../../types'
|
||||
|
||||
import { describeFontsRequirements } from '../fileHeader'
|
||||
import { OutputterFunction } from '../types'
|
||||
|
||||
import {
|
||||
|
@ -40,9 +42,15 @@ const serializeCssCustomProperty = (
|
|||
return `${padding}${key}: ${value};`
|
||||
}
|
||||
|
||||
const serializeCss: OutputterFunction = (
|
||||
const composeFileHeader = (fontsSummary: FontsSummary) => {
|
||||
const message = describeFontsRequirements(fontsSummary)
|
||||
|
||||
return ['/*', ...message.map((line) => ' * ' + line), '*/'].join('\n')
|
||||
}
|
||||
|
||||
const composeFileBody = (
|
||||
cssDefinitions: CSSClassDefinition[] | CSSCustomPropertyDefinition[],
|
||||
): string => {
|
||||
) => {
|
||||
if (areCssCustomPropertiesDefinitions(cssDefinitions)) {
|
||||
const pad = 2
|
||||
const cssDeclarations = cssDefinitions.map((customPropertyDefinition) =>
|
||||
|
@ -54,4 +62,16 @@ const serializeCss: OutputterFunction = (
|
|||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
export default serializeCss
|
||||
|
|
26
packages/core/src/lib/outputters/fileHeader.ts
Normal file
26
packages/core/src/lib/outputters/fileHeader.ts
Normal file
|
@ -0,0 +1,26 @@
|
|||
import { FontFamily, FontWeights, FontsSummary } from '../types'
|
||||
|
||||
const createGoogleFontLink = (
|
||||
fontFamily: FontFamily,
|
||||
fontWeights: FontWeights,
|
||||
) => {
|
||||
const family = encodeURIComponent(fontFamily)
|
||||
const weights = fontWeights.join(',')
|
||||
|
||||
return `<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=${family}:${weights}">`
|
||||
}
|
||||
|
||||
export const describeFontsRequirements = (
|
||||
fontSummary: FontsSummary,
|
||||
): string[] => {
|
||||
const userFontsCount = Object.keys(fontSummary.userCustomFonts).length
|
||||
const googleFontsCount = Object.keys(fontSummary.googleFonts).length
|
||||
return [
|
||||
'These generated typography declarations rely on the given font being already available in your front end code.',
|
||||
`There are ${userFontsCount} custom fonts and ${googleFontsCount} fonts from Google Fonts.`,
|
||||
`You can choose to load the latter from the Google Fonts CSS API in a HTML document using:`,
|
||||
...Object.entries(fontSummary.googleFonts).map(([family, weights]) =>
|
||||
createGoogleFontLink(family, weights),
|
||||
),
|
||||
]
|
||||
}
|
|
@ -1,18 +1,13 @@
|
|||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
|
||||
import { OutputterFunction } from './types'
|
||||
|
||||
export { default as cssOutputter } from './css'
|
||||
export { default as scssOutputter } from './scss'
|
||||
export { default as jsonOutputter } from './json'
|
||||
|
||||
export function writeTextFile(
|
||||
outputPath: string,
|
||||
outputter: OutputterFunction,
|
||||
...outputterParams: Parameters<OutputterFunction>
|
||||
) {
|
||||
const textContents = outputter(...outputterParams)
|
||||
export type { OutputterFunction } from './types'
|
||||
|
||||
export function writeTextFile(outputPath: string, textContents: string) {
|
||||
const dirname = path.dirname(outputPath)
|
||||
|
||||
if (!fs.existsSync(dirname)) {
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
import {
|
||||
CSSClassDefinition,
|
||||
CSSCustomPropertyDefinition,
|
||||
FontsSummary,
|
||||
isCssClassDefinition,
|
||||
} from '../../types'
|
||||
|
||||
import { camelToKebab } from '../css/syntax'
|
||||
|
||||
import { describeFontsRequirements } from '../fileHeader'
|
||||
import { OutputterFunction } from '../types'
|
||||
|
||||
import { textToScssVariableName } from './syntax'
|
||||
|
@ -39,9 +41,15 @@ const serializeScssVariable = (
|
|||
return `${property}: ${value};`
|
||||
}
|
||||
|
||||
const serializeScss: OutputterFunction = (
|
||||
const composeFileHeader = (fontsSummary: FontsSummary) => {
|
||||
const message = describeFontsRequirements(fontsSummary)
|
||||
|
||||
return message.map((line) => '// ' + line).join('\n')
|
||||
}
|
||||
|
||||
const composeFileBody = (
|
||||
cssDefinitions: CSSClassDefinition[] | CSSCustomPropertyDefinition[],
|
||||
): string => {
|
||||
) => {
|
||||
if (areCssCustomPropertiesDefinitions(cssDefinitions)) {
|
||||
const cssDeclarations = cssDefinitions.map((customPropertyDefinition) =>
|
||||
serializeScssVariable(customPropertyDefinition),
|
||||
|
@ -52,4 +60,16 @@ const serializeScss: OutputterFunction = (
|
|||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
export default serializeScss
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
import { CSSClassDefinition, CSSCustomPropertyDefinition } from '../types'
|
||||
import {
|
||||
CSSClassDefinition,
|
||||
CSSCustomPropertyDefinition,
|
||||
FontsSummary,
|
||||
} from '../types'
|
||||
|
||||
export type OutputterFunction = (
|
||||
cssDefinitions: CSSClassDefinition[] | CSSCustomPropertyDefinition[],
|
||||
metadata?: FontsSummary,
|
||||
) => string
|
||||
|
|
|
@ -15,3 +15,12 @@ export interface CSSCustomPropertyDefinition {
|
|||
name: string
|
||||
value: string
|
||||
}
|
||||
|
||||
export type FontFamily = string
|
||||
export type FontWeights = string[]
|
||||
export type FontsDetails = Record<FontFamily, FontWeights>
|
||||
|
||||
export interface FontsSummary {
|
||||
googleFonts: FontsDetails
|
||||
userCustomFonts: FontsDetails
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue