import { ChunkContent, ChunkType, CodeGeneratorError, CodeGeneratorFunction, ICodeBuilder, ICodeChunk, } from '../types'; export default class Builder implements ICodeBuilder { private chunkDefinitions: ICodeChunk[] = []; private generators: { [key: string]: CodeGeneratorFunction } = { [ChunkType.STRING]: (str: string) => str, // no-op for string chunks [ChunkType.JSON]: (json: object) => JSON.stringify(json), // stringify json to string }; constructor(chunkDefinitions: ICodeChunk[] = []) { this.chunkDefinitions = chunkDefinitions; } /** * Links all chunks together based on their requirements. Returns an array * of ordered chunk names which need to be compiled and glued together. */ public link(chunkDefinitions: ICodeChunk[] = []): string { const chunks = chunkDefinitions || this.chunkDefinitions; if (chunks.length <= 0) { return ''; } const unprocessedChunks = chunks.map(chunk => { return { name: chunk.name, type: chunk.type, content: chunk.content, linkAfter: this.cleanupInvalidChunks(chunk.linkAfter, chunks), }; }); const resultingString: string[] = []; while (unprocessedChunks.length > 0) { let indexToRemove = 0; for (let index = 0; index < unprocessedChunks.length; index++) { if (unprocessedChunks[index].linkAfter.length <= 0) { indexToRemove = index; break; } } if (unprocessedChunks[indexToRemove].linkAfter.length > 0) { throw new CodeGeneratorError( 'Operation aborted. Reason: cyclic dependency between chunks.', ); } const { type, content, name } = unprocessedChunks[indexToRemove]; const compiledContent = this.generateByType(type, content); if (compiledContent) { resultingString.push(compiledContent + '\n'); } unprocessedChunks.splice(indexToRemove, 1); unprocessedChunks.forEach( // remove the processed chunk from all the linkAfter arrays from the remaining chunks ch => (ch.linkAfter = ch.linkAfter.filter(after => after !== name)), ); } return resultingString.join('\n'); } public generateByType(type: string, content: unknown): string { if (!content) { return ''; } if (Array.isArray(content)) { return content .map(contentItem => this.generateByType(type, contentItem)) .join('\n'); } if (!this.generators[type]) { throw new Error( `Attempted to generate unknown type ${type}. Please register a generator for this type in builder/index.ts`, ); } return this.generators[type](content); } // remove invalid chunks (which did not end up being created) from the linkAfter fields // one use-case is when you want to remove the import plugin private cleanupInvalidChunks(linkAfter: string[], chunks: ICodeChunk[]) { return linkAfter.filter(chunkName => chunks.some(chunk => chunk.name === chunkName), ); } }