UNPKG

@miyagi/core

Version:

miyagi is a component development tool for JavaScript template engines.

407 lines (375 loc) 12 kB
const anymatch = require("anymatch"); const path = require("path"); const jsonToYaml = require("js-yaml"); const config = require("../../../config.json"); const helpers = require("../../../helpers.js"); const validateMocks = require("../../../validator/mocks.js"); const { getVariationData, getComponentData } = require("../../../mocks"); const { getDataForRenderFunction, getThemeMode, getComponentTextDirection, } = require("../../helpers"); const log = require("../../../logger.js"); const { getTemplateFilePathFromDirectoryPath } = require("../../../helpers.js"); /** * @param {object} object - parameter object * @param {object} object.app - the express instance * @param {object} object.res - the express response object * @param {string} object.file - the component path * @param {Function} [object.cb] - callback function * @param {object} object.cookies */ module.exports = async function renderIframeComponent({ app, res, file, cb, cookies, noCli, }) { file = getTemplateFilePathFromDirectoryPath(app, file); const templateFilePath = helpers.getFullPathFromShortPath(app, file); const hasTemplate = Object.values(app.get("state").partials).includes( templateFilePath ); const componentJson = await getComponentData(app, file); const componentDocumentation = app.get("state").fileContents[ helpers.getDocumentationPathFromTemplatePath(app, templateFilePath) ]; const componentInfo = app.get("state").fileContents[ helpers.getInfoPathFromTemplatePath(app, templateFilePath) ]; const schemaFilePath = helpers.getSchemaPathFromTemplatePath( app, templateFilePath ); const componentSchema = app.get("state").fileContents[schemaFilePath]; const mockFilePath = helpers.getDataPathFromTemplatePath( app, templateFilePath ); const componentMocks = app.get("state").fileContents[mockFilePath]; const componentTemplate = app.get("state").fileContents[templateFilePath]; let componentSchemaString; if (componentSchema) { if (app.get("config").files.schema.extension === "yaml") { componentSchemaString = jsonToYaml.dump(componentSchema); } else { componentSchemaString = JSON.stringify(componentSchema, null, 2); } } let componentMocksString; if (componentMocks) { if (app.get("config").files.mocks.extension === "yaml") { componentMocksString = jsonToYaml.dump(componentMocks); } else { componentMocksString = JSON.stringify(componentMocks, null, 2); } } const fileContents = { schema: componentSchema ? { string: componentSchemaString, type: app.get("config").files.schema.extension, file: path.join( app.get("config").components.folder, helpers.getShortPathFromFullPath(app, schemaFilePath) ), } : null, mocks: componentMocks ? { string: componentMocksString, type: app.get("config").files.mocks.extension, file: path.join( app.get("config").components.folder, helpers.getShortPathFromFullPath(app, mockFilePath) ), } : null, template: componentTemplate ? { string: componentTemplate, type: app.get("config").engine.name, file: path.join( app.get("config").components.folder, helpers.getShortPathFromFullPath(app, templateFilePath) ), } : null, }; let componentName = path.basename(path.dirname(file)); if (componentInfo) { if (componentInfo.name) { componentName = componentInfo.name; } } let context; if (componentJson.length > 0) { context = componentJson.filter((entry) => entry !== null); } else { const componentData = hasTemplate ? await getVariationData(app, file) : { raw: {}, extended: {} }; context = []; if (componentData) { context.push({ component: file, data: componentData.extended, rawData: componentData.raw, name: config.defaultVariationName, }); } } await renderVariations({ app, res, file, context, componentDocumentation, fileContents, name: componentName, cb, templateFilePath: hasTemplate ? templateFilePath : null, cookies, noCli, }); }; /** * @typedef {object} FileContents * @property {object} schema - schema object * @property {string} schema.string - string with schema * @property {("yaml"|"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"|"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.app - the express instance * @param {object} object.res - the express response object * @param {string} object.file - short component path * @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 * @returns {Promise} */ async function renderVariations({ app, res, file, context, componentDocumentation, fileContents, name, cb, templateFilePath, cookies, noCli, }) { const variations = []; const promises = []; const validatedMocks = validateMocks(app, file, context, noCli); const baseName = path.dirname(file); const renderInIframe = getRenderInIframe( baseName, app.get("config").components.renderInIframe ); if (templateFilePath) { for (let i = 0, len = context.length; i < len; i += 1) { const entry = context[i]; variations[i] = getData( app, context[i].name, file, baseName, entry, validatedMocks, i ); if (!renderInIframe) { promises.push( new Promise((resolve, reject) => { app.render( templateFilePath, getDataForRenderFunction(app, entry.data), (err, result) => { if (err) { if (typeof err === "string") { log("error", err); } else if (err.message) { log("error", err.message); err = err.message; } if (app.get("config").isBuild) { reject(); } } variations[i].html = result; variations[i].error = err; resolve(result); } ); }) ); } } } else { promises.push(Promise.resolve()); } return Promise.all(promises) .then(async () => { const { ui } = app.get("config"); const themeMode = getThemeMode(app, cookies); const componentTextDirection = getComponentTextDirection(app, cookies); const renderFileTabs = !!( fileContents.schema || fileContents.mocks || fileContents.template ); const componentsEntry = app .get("state") .components.find(({ shortPath }) => shortPath === baseName); await res.render( "iframe_component.hbs", { variations, 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, }, renderInIframe, dev: process.env.NODE_ENV === "development", prod: process.env.NODE_ENV === "production", a11yTestsPreload: ui.validations.accessibility, projectName: config.projectName, userProjectName: app.get("config").projectName, isBuild: app.get("config").isBuild, theme: themeMode ? Object.assign(app.get("config").ui.theme, { mode: themeMode }) : app.get("config").ui.theme, documentation: componentDocumentation, schema: fileContents.schema, schemaError: typeof validatedMocks === "string" ? validatedMocks : null, mocks: fileContents.mocks, template: fileContents.template, renderInformation: renderFileTabs || variations.length > 0, renderFileTabs, folder: path.join( app.get("config").components.folder, file.split(path.sep).slice(0, -1).join("/") ), name, componentTextDirection: componentTextDirection || app.get("config").components.textDirection, uiTextDirection: app.get("config").ui.textDirection, componentLanguage: app.get("config").components.lang, }, (err, html) => { if (res.send) { if (err) { res.send(err); } else { res.send(html); } } if (cb) { cb(err, html); } } ); }) .catch(() => { if (cb) { cb(true); } }); } /** * @param {string} baseName * @param {object} object * @param {boolean} object.default * @param {Array} object.except * @returns {boolean} */ function getRenderInIframe( baseName, { default: byDefaultRenderInIframe, except } ) { if (byDefaultRenderInIframe) { return !anymatch(except, baseName); } return anymatch(except, baseName); } /** * @param {object} app * @param {string} variation * @param {string} file * @param {string} baseName * @param {object} entry * @param {Array} validatedMocks * @param {number} i * @returns {object} */ function getData(app, variation, file, baseName, entry, validatedMocks, i) { let standaloneUrl; if (app.get("config").isBuild) { standaloneUrl = `component-${helpers.normalizeString( path.dirname(file) )}-variation-${helpers.normalizeString(variation)}.html`; } else { standaloneUrl = `/component?file=${path.dirname( file )}&variation=${encodeURIComponent(variation)}`; } const data = { url: app.get("config").isBuild ? `component-${helpers.normalizeString( baseName )}-variation-${helpers.normalizeString(variation)}-embedded.html` : `/component?file=${baseName}&variation=${variation}&embedded=true`, file, variation, normalizedVariation: helpers.normalizeString(variation), standaloneUrl, mockData: app.get("config").files.schema.extension === "yaml" ? jsonToYaml.dump(entry.rawData) : JSON.stringify(entry.rawData, null, 2), }; if (validatedMocks && Array.isArray(validatedMocks)) { data.mockValidation = { valid: validatedMocks[i], copy: config.messages.validator.mocks[ validatedMocks[i] ? "valid" : "invalid" ], }; } return data; }