UNPKG

@miyagi/core

Version:

miyagi is a component development tool for JavaScript template engines.

339 lines (312 loc) 9.54 kB
import path from "path"; import jsonToYaml from "js-yaml"; import config from "../../../default-config.js"; import { t } from "../../../i18n/index.js"; import * as helpers from "../../../helpers.js"; import validateMocks from "../../../validator/mocks.js"; import { getComponentData } from "../../../mocks/index.js"; import { getUserUiConfig, getThemeMode } from "../../helpers.js"; import log from "../../../logger.js"; /** * @param {object} object - parameter object * @param {object} object.res - the express response object * @param {object} object.component * @param {Function} [object.cb] - callback function * @param {object} [object.cookies] * @param {boolean} [object.noCli] */ export default async function renderIframeComponent({ res, component, cb, cookies, noCli, }) { const hasTemplate = component.paths.tpl && Object.values(global.state.partials).includes(component.paths.tpl.full); const componentJson = (await getComponentData(component)) || []; const componentDocumentation = global.state.fileContents[path.join(component.paths.dir.full, "README.md")]; const componentSchema = global.state.fileContents[component.paths.schema.full]; const componentTemplate = hasTemplate ? global.state.fileContents[component.paths.tpl.full] : null; let mockFilePath; const defaultMockDataPath = component.paths.mocks.full( global.config.files.mocks.extension[0], ); const jsMockDataPath = component.paths.mocks.full( global.config.files.mocks.extension[1], ); const jsMockData = global.state.fileContents[jsMockDataPath]; if (jsMockData) { mockFilePath = jsMockDataPath; } else { mockFilePath = defaultMockDataPath; } const componentMocks = global.state.fileContents[mockFilePath]; let componentSchemaString; if (componentSchema) { if (["yaml", "yml"].includes(global.config.files.schema.extension)) { componentSchemaString = jsonToYaml.dump(componentSchema); } else { componentSchemaString = JSON.stringify(componentSchema, null, 2); } } let componentMocksString; if (componentMocks) { if (["yaml", "yml"].includes(global.config.files.mocks.extension[0])) { componentMocksString = jsonToYaml.dump(componentMocks); } else { componentMocksString = JSON.stringify(componentMocks, null, 2); } } const fileContents = { schema: componentSchema ? { string: componentSchemaString, type: global.config.files.schema.extension, file: path.join( global.config.components.folder, component.paths.schema.short, ), } : null, mocks: componentMocks ? { string: componentMocksString, type: global.config.files.mocks.extension[0], file: path.join( global.config.components.folder, helpers.getShortPathFromFullPath(mockFilePath), ), } : null, template: componentTemplate ? { string: componentTemplate, type: global.config.files.templates.extension, file: path.join( global.config.components.folder, helpers.getShortPathFromFullPath(component.paths.tpl.full), ), } : null, }; await renderVariations({ res, component, context: componentJson.filter((entry) => entry !== null), componentDocumentation, fileContents, name: component.name, cb, templateFilePath: hasTemplate ? component.paths.tpl.full : null, cookies, noCli, }); } /** * @typedef {object} FileContents * @property {object} schema - schema object * @property {string} schema.string - string with schema * @property {("yaml"|"yml"|"json")} schema.type - the file type of the schema file * @property {boolean} [schema.selected] - true if the schema tab should initially be visible * @property {string} schema.file - the schema file path * @property {object} mocks - mocks object * @property {string} mocks.string - string with mocks * @property {("yaml"|"yml"|"js"|"json")} mocks.type - the file type of the mocks file * @property {boolean} [mocks.selected] - true if the mocks tab should initially be visible * @property {string} mocks.file - the mock file path * @property {object} template - template object * @property {string} template.string - string with template * @property {string} template.type - the file type of the template file * @property {boolean} [template.selected] - true if the template tab should initially be visible * @property {string} template.file - the template file path */ /** * @param {object} object - parameter object * @param {object} object.res - the express response object * @param {object} object.component * @param {Array} object.context - mock data for each variation * @param {string} object.componentDocumentation - html string with documentation * @param {FileContents} object.fileContents - file contents object * @param {string} object.name - component name * @param {Function} object.cb - callback function * @param {string} object.templateFilePath - the absolute component file path * @param {object} [object.cookies] * @param {boolean} object.noCli * @returns {Promise} */ async function renderVariations({ res, component, context, componentDocumentation, fileContents, name, cb, templateFilePath, cookies, noCli, }) { const variations = []; const promises = []; const validatedMocks = validateMocks(component, context, noCli); if (templateFilePath) { if (context.length > 0) { for (let i = 0, len = context.length; i < len; i += 1) { const entry = context[i]; variations[i] = getData( context[i].name, component.paths.dir.short, entry, validatedMocks, ); } } } else { promises.push(Promise.resolve()); } let schemaJson; if (fileContents?.schema?.string) { try { schemaJson = JSON.stringify(jsonToYaml.load(fileContents.schema.string)); } catch (err) { log("error", null, err); } } let mocksJson; if (fileContents?.mocks?.string) { try { mocksJson = JSON.stringify(jsonToYaml.load(fileContents.mocks.string)); } catch (err) { log("error", null, err); } } return Promise.all(promises) .then(async () => { const themeMode = getThemeMode(cookies); const renderFileTabs = !!( fileContents.schema || fileContents.mocks || fileContents.template ); const componentsEntry = global.state.components.find( ({ shortPath }) => shortPath === component.paths.dir.short, ); await res.render( "iframe_component.twig.miyagi", { lang: global.config.ui.lang, variations, cssFiles: global.config.assets.css, jsFilesHead: global.config.assets.js.filter( (entry) => entry.position === "head" || !entry.position, ), jsFilesBody: global.config.assets.js.filter( (entry) => entry.position === "body", ), assets: { css: componentsEntry ? componentsEntry.assets.css ? path.join("/", componentsEntry.assets.css) : false : false, js: componentsEntry ? componentsEntry.assets.js ? path.join("/", componentsEntry.assets.js) : false : false, }, miyagiDev: !!process.env.MIYAGI_DEVELOPMENT, prod: process.env.NODE_ENV === "production", projectName: config.projectName, userProjectName: global.config.projectName, isBuild: global.config.isBuild, userUiConfig: getUserUiConfig(cookies), theme: themeMode ? Object.assign(global.config.ui.theme, { mode: themeMode }) : global.config.ui.theme, documentation: componentDocumentation, schema: schemaJson, schemaError: validatedMocks?.length > 0 && validatedMocks[0].type === "schema" ? validatedMocks[0].data.map((error) => error.message).join("\n") : null, mocks: mocksJson, template: fileContents.template, renderInformation: renderFileTabs || variations.length > 0, renderFileTabs, folder: path.join( global.config.components.folder, component.paths.dir.short, ), name: componentDocumentation?.includes("<h1>") ? null : name, uiTextDirection: global.config.ui.textDirection, componentLanguage: global.config.components.lang, }, (html) => { if (res.send) { res.send(html); } if (cb) { cb(null, html); } }, ); }) .catch(() => { if (cb) { cb(true); } }); } /** * @param {string} variation * @param {string} shortPath * @param {object} entry * @param {Array} validatedMocks * @returns {object} */ function getData(variation, shortPath, entry, validatedMocks) { let standaloneUrl; if (global.config.isBuild) { standaloneUrl = `component-${helpers.normalizeString( shortPath, )}-variation-${helpers.normalizeString(variation)}.html`; } else { standaloneUrl = `/component?file=${shortPath}&variation=${encodeURIComponent( variation, )}`; } const data = { url: global.config.isBuild ? `component-${helpers.normalizeString( shortPath, )}-variation-${helpers.normalizeString(variation)}-embedded.html` : `/component?file=${shortPath}&variation=${variation}&embedded=true`, file: shortPath, variation, normalizedVariation: helpers.normalizeString(variation), standaloneUrl, mockData: JSON.stringify(entry.raw), mockDataResolved: JSON.stringify(entry.resolved), }; const validationEntry = validatedMocks?.find( (item) => item.variant === entry.name, ); if (validationEntry) { data.mockValidation = { valid: false, copy: t("validator.mocks.invalid"), }; } else { data.mockValidation = { valid: true, }; } return data; }