diff --git a/packages/patternengine-node-react/README.md b/packages/patternengine-node-react/README.md index 96a899a75..0a519be55 100644 --- a/packages/patternengine-node-react/README.md +++ b/packages/patternengine-node-react/README.md @@ -1,19 +1,21 @@ # The React engine for Pattern Lab / Node -This is the **very preliminary barely worth mentioning** React engine for Patternlab/Node. It's more or less a proof of concept. +This is the **very preliminary** React engine for Patternlab/Node. ## Status -You can author standalone React components that include only the main React module, which I know isn't much yet. We're still working out how React components will resolve and load the modules they depend on, including other patterns. We believe this is tricky, but doable. +You can author standalone React components that include only the main React module, which I know isn't much yet. +We're still working out how React components will resolve and load the modules they depend on. We believe this is tricky, but doable. -## Supported features -Kind of nothing works yet. **Very early in development.** +## Supported Pattern Lab -- [ ] [Includes](http://patternlab.io/docs/pattern-including.html) -- [ ] Lineage -- [ ] [Hidden Patterns](http://patternlab.io/docs/pattern-hiding.html) -- [ ] [Pseudo-Patterns](http://patternlab.io/docs/pattern-pseudo-patterns.html) -- [ ] [Pattern States](http://patternlab.io/docs/pattern-states.html) -- [ ] [Pattern Parameters](http://patternlab.io/docs/pattern-parameters.html) -- [ ] [Style Modifiers](http://patternlab.io/docs/pattern-stylemodifier.html) +- [x] [Includes](http://patternlab.io/docs/pattern-including.html) +- [x] Data inheritance: This can be achieved by combining react `props` & `defaultProps` +- [x] [Hidden Patterns](http://patternlab.io/docs/pattern-hiding.html) +- [x] [Pseudo-Patterns](http://patternlab.io/docs/pattern-pseudo-patterns.html) +- [x] [Pattern States](http://patternlab.io/docs/pattern-states.html#node) +- [x] [Pattern Parameters](http://patternlab.io/docs/pattern-parameters.html): With react props +- [x] [Style Modifiers](http://patternlab.io/docs/pattern-stylemodifier.html): With react props +- [x] Lineage +- [x] Incremental builds ## Usage * `*.jsx` files are detected as patterns. diff --git a/packages/patternengine-node-react/lib/engine_react.js b/packages/patternengine-node-react/lib/engine_react.js index 089bbb774..e2081d180 100644 --- a/packages/patternengine-node-react/lib/engine_react.js +++ b/packages/patternengine-node-react/lib/engine_react.js @@ -7,9 +7,7 @@ * Many thanks to Brad Frost and Dave Olsen for inspiration, encouragement, and advice. * */ - - -"use strict"; +'use strict'; const fs = require('fs'); const path = require('path'); @@ -19,13 +17,14 @@ const Babel = require('babel-core'); const Hogan = require('hogan.js'); const beautify = require('js-beautify'); const cheerio = require('cheerio'); -const webpack = require('webpack'); const _require = require; // 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'), @@ -34,14 +33,39 @@ const outputTemplate = Hogan.compile( ); let registeredComponents = { - byModuleName: {}, - byGroup: {} + byPatternPartial: {} }; -function moduleCodeString(pattern) { +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', @@ -51,65 +75,37 @@ var engine_react = { expandPartials: false, // regexes, stored here so they're only compiled once - findPartialsRE: null, + findPartialsRE: /import .* from '[^']+'/g, findPartialsWithStyleModifiersRE: null, findPartialsWithPatternParametersRE: null, findListItemsRE: null, - findPartialRE: null, + findPartialRE: /from '([^']+)'/, // render it - renderPattern(pattern, data, partials) { + renderPattern (pattern, data, partials) { try { - let runtimeCode = []; - - // the all-important Babel transform, runtime version - runtimeCode.push(Babel.transform(moduleCodeString(pattern), { - presets: [ require('babel-preset-react') ], - plugins: [[require('babel-plugin-transform-es2015-modules-umd'), { - globals: { - "react": "React" - } - }]] - }).code); + const transpiledModule = babelTransform(pattern); + + const staticMarkup = ReactDOMServer.renderToStaticMarkup( + React.createFactory(transpiledModule)(data) + ); return outputTemplate.render({ - patternPartial: pattern.patternPartial, - json: JSON.stringify(data), - htmlOutput: ReactDOMServer.renderToStaticMarkup( - React.createFactory(pattern.module)(data) - ), - runtimeCode: runtimeCode.join(';') + htmlOutput: staticMarkup }); } catch (e) { - console.log("Error rendering React pattern.", e); - return ""; + console.log('Error rendering React pattern.', e); + return ''; } }, - registerPartial(pattern) { - const customRequire = function (id) { - const registeredPattern = registeredComponents.byModuleName[id]; - return registeredPattern ? registeredPattern.module : _require(id); - }; - - // the all-important Babel transform, server-side version - const compiledModule = Babel.transform(moduleCodeString(pattern), { - presets: [ require('babel-preset-react') ], - plugins: [ require('babel-plugin-transform-es2015-modules-commonjs') ] - }); - + registerPartial (pattern) { // add to registry - registeredComponents.byModuleName[pattern.patternBaseName] = pattern; - - // eval() module code in this little scope that injects our - // custom wrap of require(); - ((require) => { - /* eslint-disable no-eval */ - pattern.module = eval(compiledModule.code); - })(customRequire); + registeredComponents.byPatternPartial[pattern.patternPartial] = pattern; }, + /** * Find regex matches within both pattern strings and pattern objects. * @@ -117,7 +113,7 @@ var engine_react = { * @param {object} regex A JavaScript RegExp object. * @returns {array|null} An array if a match is found, null if not. */ - patternMatcher(pattern, regex) { + patternMatcher (pattern, regex) { var matches; if (typeof pattern === 'string') { matches = pattern.match(regex); @@ -127,40 +123,60 @@ var engine_react = { return matches; }, - // find and return any {{> template-name }} within pattern - findPartials(pattern) { - return []; + // 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) { + + findPartialsWithStyleModifiers (pattern) { return []; }, - // returns any patterns that match {{> value(foo:"bar") }} or {{> - // value:mod(foo:"bar") }} within the pattern - findPartialsWithPatternParameters(pattern) { + // returns any patterns that match {{> value(foo:'bar') }} or {{> + // value:mod(foo:'bar') }} within the pattern + findPartialsWithPatternParameters (pattern) { return []; }, - findListItems(pattern) { + findListItems (pattern) { return []; }, // given a pattern, and a partial string, tease out the "pattern key" and // return it. - findPartial(partialString) { - return []; + findPartial (partialString) { + let partial = partialString.match(this.findPartialRE)[1]; + return partial; }, - rawTemplateCodeFormatter(unformattedString) { + rawTemplateCodeFormatter (unformattedString) { return beautify(unformattedString, {e4x: true, indent_size: 2}); }, - renderedCodeFormatter(unformattedString) { + renderedCodeFormatter (unformattedString) { return unformattedString; }, - markupOnlyCodeFormatter(unformattedString, pattern) { - const $ = cheerio.load(unformattedString); - return beautify.html($('.reactPatternContainer').html(), {indent_size: 2}); + markupOnlyCodeFormatter (unformattedString, pattern) { + // const $ = cheerio.load(unformattedString); + // return beautify.html($('.reactPatternContainer').html(), {indent_size: 2}); + return unformattedString; }, /** @@ -169,8 +185,7 @@ var engine_react = { * @returns {(object|object[])} - an object or array of objects, * each with two properties: path, and content */ - addOutputFiles(paths, patternlab) { - + addOutputFiles (paths, patternlab) { return []; }, @@ -183,6 +198,12 @@ var engine_react = { */ 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.'); + } } }; diff --git a/packages/patternengine-node-react/lib/outputTemplate.mustache b/packages/patternengine-node-react/lib/outputTemplate.mustache index c2b3a8f73..0153d9810 100644 --- a/packages/patternengine-node-react/lib/outputTemplate.mustache +++ b/packages/patternengine-node-react/lib/outputTemplate.mustache @@ -1,18 +1 @@ -