/* * react pattern engine for patternlab-node - v0.1.0 - 2016 * * Geoffrey Pursell, Brian Muenzenmeyer, and the web community. * Licensed under the MIT license. * * Many thanks to Brad Frost and Dave Olsen for inspiration, encouragement, and advice. * */ 'use strict'; const fs = require('fs'); const path = require('path'); const React = require('react'); const ReactDOMServer = require('react-dom/server'); const Babel = require('babel-core'); const Hogan = require('hogan'); const beautify = require('js-beautify'); const cheerio = require('cheerio'); const _require = require; var errorStyling = ` `; // This holds the config from from core. The core has to call // usePatternLabConfig() at load time for this to be populated. let patternLabConfig = {}; let enableRuntimeCode = true; const outputTemplate = Hogan.compile( fs.readFileSync(path.join(__dirname, './outputTemplate.mustache'), 'utf8') ); let registeredComponents = { byPatternPartial: {}, }; function moduleCodeString(pattern) { return pattern.template || pattern.extendedTemplate; } function babelTransform(pattern) { let transpiledModule = Babel.transform(moduleCodeString(pattern), { presets: [require('babel-preset-react')], plugins: [require('babel-plugin-transform-es2015-modules-commonjs')], }); // eval() module code in this little scope that injects our // custom wrap of require(); (require => { /* eslint-disable no-eval */ transpiledModule = eval(transpiledModule.code); })(customRequire); return transpiledModule; } function customRequire(id) { const registeredPattern = registeredComponents.byPatternPartial[id]; if (registeredPattern) { return babelTransform(registeredPattern); } else { return _require(id); } } var engine_react = { engine: React, engineName: 'react', engineFileExtension: ['.jsx', '.js'], // hell no expandPartials: false, // regexes, stored here so they're only compiled once findPartialsRE: /import .* from '[^']+'/g, findPartialsWithStyleModifiersRE: null, findPartialsWithPatternParametersRE: null, findListItemsRE: null, findPartialRE: /from '([^']+)'/, // render it renderPattern(pattern, data, partials) { let renderedHTML = ''; const transpiledModule = babelTransform(pattern); const staticMarkup = ReactDOMServer.renderToStaticMarkup( React.createFactory(transpiledModule)(data) ); renderedHTML = outputTemplate.render({ htmlOutput: staticMarkup, }); return Promise.resolve(renderedHTML).catch(e => { var errorMessage = `Error rendering React pattern "${ pattern.patternName }" (${pattern.relPath}): [${e.toString()}]`; console.log(errorMessage); renderedHTML = `${errorStyling}

Error rendering React pattern "${pattern.patternName}"

Message
${e.toString()}
Partial name
${pattern.patternName}
Template path
${pattern.relPath}
`; }); }, registerPartial(pattern) { // add to registry registeredComponents.byPatternPartial[pattern.patternPartial] = pattern; }, /** * Find regex matches within both pattern strings and pattern objects. * * @param {string|object} pattern Either a string or a pattern object. * @param {object} regex A JavaScript RegExp object. * @returns {array|null} An array if a match is found, null if not. */ patternMatcher(pattern, regex) { var matches; if (typeof pattern === 'string') { matches = pattern.match(regex); } else if ( typeof pattern === 'object' && typeof pattern.template === 'string' ) { matches = pattern.template.match(regex); } return matches; }, // find and return any `import X from 'template-name'` within pattern findPartials(pattern) { const self = this; const matches = pattern.template.match(this.findPartialsRE); if (!matches) { return []; } // Remove unregistered imports from the matches matches.map(m => { const key = self.findPartial(m); if (!registeredComponents.byPatternPartial[key]) { const i = matches.indexOf(m); if (i > -1) { matches.splice(i, 1); } } }); return matches; }, findPartialsWithStyleModifiers(pattern) { return []; }, // returns any patterns that match {{> value(foo:'bar') }} or {{> // value:mod(foo:'bar') }} within the pattern findPartialsWithPatternParameters(pattern) { return []; }, findListItems(pattern) { return []; }, // given a pattern, and a partial string, tease out the "pattern key" and // return it. findPartial(partialString) { let partial = partialString.match(this.findPartialRE)[1]; return partial; }, rawTemplateCodeFormatter(unformattedString) { return beautify(unformattedString, { e4x: true, indent_size: 2 }); }, renderedCodeFormatter(unformattedString) { return unformattedString; }, markupOnlyCodeFormatter(unformattedString, pattern) { // const $ = cheerio.load(unformattedString); // return beautify.html($('.reactPatternContainer').html(), {indent_size: 2}); return unformattedString; }, /** * Add custom output files to the pattern output * @param {object} patternlab - the global state object * @returns {(object|object[])} - an object or array of objects, * each with two properties: path, and content */ addOutputFiles(paths, patternlab) { return []; }, /** * Accept a Pattern Lab config object from the core and put it in * this module's closure scope so we can configure engine behavior. * * @param {object} config - the global config object from core */ usePatternLabConfig: function(config) { patternLabConfig = config; try { enableRuntimeCode = patternLabConfig.engines.react.enableRuntimeCode; } catch (error) { console.log( 'You’re missing the engines.react.enableRuntimeCode setting in your config file.' ); } }, }; module.exports = engine_react;