UNPKG

@knapsack/app

Version:

Build Design Systems on top of knapsack, by Basalt

252 lines (222 loc) • 6.18 kB
import chokidar from 'chokidar'; import { JsonSchemaObject } from '@knapsack/core/src/types'; import { compile, JSONSchema } from 'json-schema-to-typescript'; import { pascalCase } from 'change-case'; import { join } from 'path'; import fs from 'fs-extra'; import { knapsackEvents, EVENTS } from './events'; import * as log from '../cli/log'; import { formatCode } from './server-utils'; import { GetHeadParams, GetFootParams, KnapsackTemplateRendererBase, KnapsackConfig, } from '../schemas/knapsack-config'; import { isSlottedText, isDataDemo, isTemplateDemo, isSlottedTemplateDemo, } from '../schemas/patterns'; import { validateSpec } from '../lib/utils'; /* eslint-disable class-methods-use-this, no-empty-function, no-unused-vars */ export class KnapsackRendererBase implements KnapsackTemplateRendererBase { id: string; extension: string; language: string; outputDirName: string; logPrefix: string; cacheDir: string; outputDir: string; constructor({ id, extension, language, }: { id: string; extension: string; language: string; }) { this.id = id; this.extension = extension; this.language = language; this.outputDirName = `knapsack-renderer-${this.id}`; this.logPrefix = `templateRenderer:${this.id}`; } async init({ cacheDir, }: { config: KnapsackConfig; patterns: import('@knapsack/app/src/server/patterns').Patterns; cacheDir: string; }): Promise<void> { this.cacheDir = cacheDir; this.outputDir = join(cacheDir, this.outputDirName); await fs.ensureDir(this.outputDir); } static formatCode = formatCode; static isSlottedText = isSlottedText; static isDataDemo = isDataDemo; static isTemplateDemo = isTemplateDemo; static isSlottedTemplateDemo = isSlottedTemplateDemo; static validateSpec = validateSpec; /** * Each sub-class should implement this themselves, probably using `KnapsackRendererBase.formatCode()` * This base implementation just returns the original code so it can be reliably ran * @see {KnapsackRendererBase.formatCode} */ formatCode(code: string): string { return code?.trim(); } getHead({ cssUrls = [], headJsUrls = [], inlineHead = '', }: GetHeadParams): string { return ` <!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> ${inlineHead} ${cssUrls .map( cssUrl => `<link rel="stylesheet" type="text/css" href="${cssUrl}">`, ) .join('\n')} ${headJsUrls .map( jsUrl => `<script src="${jsUrl}" type="text/javascript"></script>`, ) .join('\n')} </head> <body> `; } getFoot({ jsUrls = [], inlineJs = '', inlineCss = '', inlineFoot = '', }: GetFootParams): string { return ` ${jsUrls .map(jsUrl => `<script src="${jsUrl}" type="text/javascript"></script>`) .join('\n')} <style>${inlineCss}</style> <script>${inlineJs}</script> ${inlineFoot} </body> </html> `; } wrapHtml({ html, cssUrls = [], jsUrls = [], headJsUrls = [], inlineJs = '', inlineCss = '', inlineHead = '', inlineFoot = '', isInIframe, }: { html: string; } & GetHeadParams & GetFootParams): string { return ` ${this.getHead({ cssUrls, headJsUrls, inlineHead, isInIframe })} ${ isInIframe ? `<div class="knapsack-wrapper knapsack-pattern-direct-parent">${html}</div>` : html } ${this.getFoot({ jsUrls, inlineJs, inlineCss, inlineFoot, isInIframe })} `; } onChange({ path }: { path: string }): void { knapsackEvents.emit(EVENTS.PATTERN_TEMPLATE_CHANGED, { path }); } onAdd({ path }: { path: string }): void { knapsackEvents.emit(EVENTS.PATTERN_TEMPLATE_ADDED, { path }); } onRemove({ path }: { path: string }): void { knapsackEvents.emit(EVENTS.PATTERN_TEMPLATE_REMOVED, { path }); } watch({ templatePaths }: { templatePaths: string[] }): Promise<void> { return new Promise((resolve, reject) => { const watcher = chokidar.watch(templatePaths, { ignoreInitial: true, }); watcher .on('add', path => this.onAdd({ path })) .on('change', path => this.onChange({ path })) .on('unlink', path => this.onRemove({ path })) .on('error', error => { log.error('Error watching', error, `templateRender:${this.id}`); reject(error); }); watcher.on('ready', () => { log.silly( 'Watching these files:', watcher.getWatched(), `templateRender:${this.id}`, ); resolve(); }); }); } static async convertSchemaToTypeScriptDefs({ schema, title, description = '', patternId, templateId, preBanner, postBanner, }: { schema: JsonSchemaObject; /** * Will become the `export`-ed `interface` */ title: string; description?: string; patternId: string; templateId: string; preBanner?: string; postBanner?: string; }): Promise<string> { const theSchema = { ...schema, additionalProperties: false, description, title, }; const bannerComment = ` /** * patternId: "${patternId}" templateId: "${templateId}" * This file was automatically generated by Knapsack. * DO NOT MODIFY IT BY HAND. * Instead, adjust it's spec, by either: * 1) go to "/patterns/${patternId}/${templateId}" and use the UI to edit the spec * 2) OR edit the "knapsack.pattern.${patternId}.json" file's "spec.props". * Run Knapsack again to regenerate this file. */`.trim(); const typeDefs = await compile(theSchema as JSONSchema, theSchema.title, { bannerComment: [preBanner, bannerComment, postBanner] .filter(Boolean) .join('\n\n'), style: { singleQuote: true, }, }); return typeDefs .split('\n') .map(line => line.replace('export type', 'type')) .join('\n'); } }