diff --git a/core/lib/patternlab.js b/core/lib/patternlab.js index 4143f6dc0..3240ba908 100644 --- a/core/lib/patternlab.js +++ b/core/lib/patternlab.js @@ -50,6 +50,205 @@ plutils.log.on('info', msg => console.log(msg)); const patternEngines = require('./pattern_engines'); const EventEmitter = require('events').EventEmitter; +class PatternLab { + constructor(config) { + // Either use the config we were passed, or load one up from the config file ourselves + this.config = config || fs.readJSONSync(path.resolve(__dirname, '../../patternlab-config.json')); + // Load up engines please + this.engines = patternEngines; + this.engines.loadAllEngines(config); + + // Cache the package.json in RAM + this.package = fs.readJSONSync(path.resolve(__dirname, '../../package.json')); + + // Make ye olde event emitter + this.events = new PatternLabEventEmitter(); + + // Make a place for the pattern graph to sit + this.graph = null; + + // Make a place to attach known watchers so we can manage them better during serve and watch + this.watchers = {}; + + // Verify correctness of configuration (?) + checkConfiguration(this); + + // TODO: determine if this is the best place to wire up plugins + initializePlugins(this); + } + + buildGlobalData() { + const paths = this.config.paths; + + // + // COLLECT GLOBAL LIBRARY DATA + // + + // data.json + try { + this.data = buildPatternData(paths.source.data, fs); + } catch (ex) { + plutils.error('missing or malformed' + paths.source.data + 'data.json Pattern Lab may not work without this file.'); + this.data = {}; + } + // listitems.json + try { + this.listitems = fs.readJSONSync(path.resolve(paths.source.data, 'listitems.json')); + } catch (ex) { + plutils.warning('WARNING: missing or malformed ' + paths.source.data + 'listitems.json file. Pattern Lab may not work without this file.'); + this.listitems = {}; + } + // load up all the necessary files from pattern lab that apply to every template + try { + this.header = fs.readFileSync(path.resolve(paths.source.patternlabFiles['general-header']), 'utf8'); + this.footer = fs.readFileSync(path.resolve(paths.source.patternlabFiles['general-footer']), 'utf8'); + this.patternSection = fs.readFileSync(path.resolve(paths.source.patternlabFiles.patternSection), 'utf8'); + this.patternSectionSubType = fs.readFileSync(path.resolve(paths.source.patternlabFiles.patternSectionSubtype), 'utf8'); + this.viewAll = fs.readFileSync(path.resolve(paths.source.patternlabFiles.viewall), 'utf8'); + } catch (ex) { + console.log(ex); + plutils.error('\nERROR: missing an essential file from ' + paths.source.patternlabFiles + '. Pattern Lab won\'t work without this file.\n'); + + // GTP: it seems increasingly naughty as we refactor to just unilaterally do this here, + // but whatever. For now. + process.exit(1); + } + + + // + // INITIALIZE EMPTY GLOBAL DATA STRUCTURES + // + this.patterns = []; + this.subtypePatterns = {}; + this.partials = {}; + this.data.link = {}; + + this.setCacheBust(); + + pattern_assembler.combine_listItems(this); + + this.events.emit('patternlab-build-global-data-end', this); + } + + setCacheBust() { + if (this.config.cacheBust) { + if (this.config.debug) { + console.log('setting cacheBuster value for frontend assets.'); + } + this.cacheBuster = new Date().getTime(); + } else { + this.cacheBuster = 0; + } + } + + + // Starter Kit loading methods + + listStarterkits() { + const starterkit_manager = new sm(this.config); + return starterkit_manager.list_starterkits(); + } + + loadStarterKit(starterkitName, clean) { + const starterkit_manager = new sm(this.config); + starterkit_manager.load_starterkit(starterkitName, clean); + } + + + // Pattern processing methods + + /** + * Process the user-defined pattern head and prepare it for rendering + */ + processHeadPattern() { + try { + const headPath = path.resolve(this.config.paths.source.meta, '_00-head.mustache'); + const headPattern = new Pattern(headPath, null, this); + headPattern.template = fs.readFileSync(headPath, 'utf8'); + headPattern.isPattern = false; + headPattern.isMetaPattern = true; + pattern_assembler.decomposePattern(headPattern, this, true); + this.userHead = headPattern.extendedTemplate; + } + catch (ex) { + plutils.error('\nWARNING: Could not find the user-editable header template, currently configured to be at ' + path.join(this.config.paths.source.meta, '_00-head.mustache') + '. Your configured path may be incorrect (check this.config.paths.source.meta in your config file), the file may have been deleted, or it may have been left in the wrong place during a migration or update.\n'); + if (this.config.debug) { console.log(ex); } + + // GTP: it seems increasingly naughty as we refactor to just unilaterally do this here, + // but whatever. For now. + process.exit(1); + } + } + + /** + * Process the user-defined pattern footer and prepare it for rendering + */ + processFootPattern() { + try { + const footPath = path.resolve(this.config.paths.source.meta, '_01-foot.mustache'); + const footPattern = new Pattern(footPath, null, this); + footPattern.template = fs.readFileSync(footPath, 'utf8'); + footPattern.isPattern = false; + footPattern.isMetaPattern = true; + pattern_assembler.decomposePattern(footPattern, this, true); + this.userFoot = footPattern.extendedTemplate; + } + catch (ex) { + plutils.error('\nWARNING: Could not find the user-editable footer template, currently configured to be at ' + path.join(this.config.paths.source.meta, '_01-foot.mustache') + '. Your configured path may be incorrect (check this.config.paths.source.meta in your config file), the file may have been deleted, or it may have been left in the wrong place during a migration or update.\n'); + if (this.config.debug) { console.log(ex); } + + // GTP: it seems increasingly naughty as we refactor to just unilaterally do this here, + // but whatever. For now. + process.exit(1); + } + } + + + // info methods + + getVersion() { + return this.package.version; + } + logVersion() { + console.log(this.package.version); + } + getSupportedTemplateExtensions() { + return this.engines.getSupportedFileExtensions(); + } + + + writePatternFiles(headHTML, pattern, footerHTML) { + const nullFormatter = str => str; + const defaultFormatter = codeString => cleanHtml(codeString, {indent_size: 2}); + const makePath = type => path.join(this.config.paths.public.patterns, pattern.getPatternLink(this, type)); + const patternPage = headHTML + pattern.patternPartialCode + footerHTML; + const eng = pattern.engine; + + //beautify the output if configured to do so + const formatters = this.config.cleanOutputHtml ? { + rendered: eng.renderedCodeFormatter || defaultFormatter, + rawTemplate: eng.rawTemplateCodeFormatter || defaultFormatter, + markupOnly: eng.markupOnlyCodeFormatter || defaultFormatter + } : { + rendered: nullFormatter, + rawTemplate: nullFormatter, + markupOnly: nullFormatter + }; + + //prepare the path and contents of each output file + const outputFiles = [ + { path: makePath('rendered'), content: formatters.rendered(patternPage, pattern) }, + { path: makePath('rawTemplate'), content: formatters.rawTemplate(pattern.template, pattern) }, + { path: makePath('markupOnly'), content: formatters.markupOnly(pattern.patternPartialCode, pattern) } + ].concat( + eng.addOutputFiles ? eng.addOutputFiles(this.config.paths, this) : [] + ); + + //write the compiled template to the public patterns directory + outputFiles.forEach(outFile => fs.outputFileSync(outFile.path, outFile.content)); + } +} + //bootstrap update notifier updateNotifier({ pkg: packageInfo, @@ -192,38 +391,9 @@ function PatternLabEventEmitter() { inherits(PatternLabEventEmitter, EventEmitter); const patternlab_engine = function (config) { - const patternlab = {}; - - patternlab.engines = patternEngines; - patternlab.engines.loadAllEngines(config); - - patternlab.package = fs.readJSONSync(path.resolve(__dirname, '../../package.json')); - patternlab.config = config || fs.readJSONSync(path.resolve(__dirname, '../../patternlab-config.json')); - patternlab.events = new PatternLabEventEmitter(); - patternlab.watchers = {}; - - // Initialized when building - patternlab.graph = null; - - checkConfiguration(patternlab); - - //todo: determine if this is the best place to wire up plugins - initializePlugins(patternlab); - + const patternlab = new PatternLab(config); const paths = patternlab.config.paths; - function getVersion() { - return patternlab.package.version; - } - - function logVersion() { - console.log(patternlab.package.version); - } - - function getSupportedTemplateExtensions() { - return patternlab.engines.getSupportedFileExtensions(); - } - function help() { console.log(''); @@ -290,109 +460,21 @@ const patternlab_engine = function (config) { return value; } + // GTP: this commented out now on the advice of Brian, because we think nobody looks at it, + // and it causes problems. //debug file can be written by setting flag on patternlab-config.json - if (patternlab.config.debug) { - console.log('writing patternlab debug file to ./patternlab.json'); - fs.outputFileSync('./patternlab.json', JSON.stringify(patternlab, propertyStringReplacer, 3)); - } - } - - function setCacheBust() { - if (patternlab.config.cacheBust) { - if (patternlab.config.debug) { - console.log('setting cacheBuster value for frontend assets.'); - } - patternlab.cacheBuster = new Date().getTime(); - } else { - patternlab.cacheBuster = 0; - } - } - - function listStarterkits() { - const starterkit_manager = new sm(patternlab.config); - return starterkit_manager.list_starterkits(); - } - - function loadStarterKit(starterkitName, clean) { - const starterkit_manager = new sm(patternlab.config); - starterkit_manager.load_starterkit(starterkitName, clean); - } - - /** - * Process the user-defined pattern head and prepare it for rendering - */ - function processHeadPattern() { - try { - const headPath = path.resolve(paths.source.meta, '_00-head.mustache'); - const headPattern = new Pattern(headPath, null, patternlab); - headPattern.template = fs.readFileSync(headPath, 'utf8'); - headPattern.isPattern = false; - headPattern.isMetaPattern = true; - pattern_assembler.decomposePattern(headPattern, patternlab, true); - patternlab.userHead = headPattern.extendedTemplate; - } - catch (ex) { - plutils.error('\nWARNING: Could not find the user-editable header template, currently configured to be at ' + path.join(config.paths.source.meta, '_00-head.mustache') + '. Your configured path may be incorrect (check paths.source.meta in your config file), the file may have been deleted, or it may have been left in the wrong place during a migration or update.\n'); - if (patternlab.config.debug) { console.log(ex); } - process.exit(1); - } + // if (patternlab.config.debug) { + // console.log('writing patternlab debug file to ./patternlab.json'); + // fs.outputFileSync('./patternlab.json', JSON.stringify(patternlab, propertyStringReplacer, 3)); + // } } - /** - * Process the user-defined pattern footer and prepare it for rendering - */ - function processFootPattern() { - try { - const footPath = path.resolve(paths.source.meta, '_01-foot.mustache'); - const footPattern = new Pattern(footPath, null, patternlab); - footPattern.template = fs.readFileSync(footPath, 'utf8'); - footPattern.isPattern = false; - footPattern.isMetaPattern = true; - pattern_assembler.decomposePattern(footPattern, patternlab, true); - patternlab.userFoot = footPattern.extendedTemplate; - } - catch (ex) { - plutils.error('\nWARNING: Could not find the user-editable footer template, currently configured to be at ' + path.join(config.paths.source.meta, '_01-foot.mustache') + '. Your configured path may be incorrect (check paths.source.meta in your config file), the file may have been deleted, or it may have been left in the wrong place during a migration or update.\n'); - if (patternlab.config.debug) { console.log(ex); } - process.exit(1); - } - } - function writePatternFiles(headHTML, pattern, footerHTML) { - const nullFormatter = str => str; - const defaultFormatter = codeString => cleanHtml(codeString, {indent_size: 2}); - const makePath = type => path.join(paths.public.patterns, pattern.getPatternLink(patternlab, type)); - const patternPage = headHTML + pattern.patternPartialCode + footerHTML; - const eng = pattern.engine; - - //beautify the output if configured to do so - const formatters = config.cleanOutputHtml ? { - rendered: eng.renderedCodeFormatter || defaultFormatter, - rawTemplate: eng.rawTemplateCodeFormatter || defaultFormatter, - markupOnly: eng.markupOnlyCodeFormatter || defaultFormatter - } : { - rendered: nullFormatter, - rawTemplate: nullFormatter, - markupOnly: nullFormatter - }; - - //prepare the path and contents of each output file - const outputFiles = [ - { path: makePath('rendered'), content: formatters.rendered(patternPage, pattern) }, - { path: makePath('rawTemplate'), content: formatters.rawTemplate(pattern.template, pattern) }, - { path: makePath('markupOnly'), content: formatters.markupOnly(pattern.patternPartialCode, pattern) } - ].concat( - eng.addOutputFiles ? eng.addOutputFiles(paths, patternlab) : [] - ); - - //write the compiled template to the public patterns directory - outputFiles.forEach(outFile => fs.outputFileSync(outFile.path, outFile.content)); - } function renderSinglePattern(pattern, head) { // Pattern does not need to be built and recompiled more than once if (!pattern.isPattern || pattern.compileState === CompileState.CLEAN) { - return false; + return Promise.resolve(false); } // Allows serializing the compile state @@ -481,14 +563,15 @@ const patternlab_engine = function (config) { patternlab.events.emit('patternlab-pattern-write-begin', patternlab, pattern); //write the compiled template to the public patterns directory - writePatternFiles(headHTML, pattern, footerHTML); + patternlab.writePatternFiles(headHTML, pattern, footerHTML); patternlab.events.emit('patternlab-pattern-write-end', patternlab, pattern); // Allows serializing the compile state patternlab.graph.node(pattern).compileState = pattern.compileState = CompileState.CLEAN; plutils.log.info("Built pattern: " + pattern.patternPartial); - return true; + + return Promise.resolve(true); } /** @@ -509,7 +592,19 @@ const patternlab_engine = function (config) { return PatternGraph.loadFromFile(patternlab); } + + function cleanBuildDirectory(incrementalBuildsEnabled) { + if (incrementalBuildsEnabled) { + plutils.log.info("Incremental builds enabled."); + } else { + // needs to be done BEFORE processing patterns + fs.removeSync(paths.public.patterns); + fs.emptyDirSync(paths.public.patterns); + } + } + function buildPatterns(deletePatternDir) { + patternlab.events.emit('patternlab-build-pattern-start', patternlab); if (patternlab.config.debug) { console.log( @@ -519,12 +614,11 @@ const patternlab_engine = function (config) { ); } - patternlab.events.emit('patternlab-build-pattern-start', patternlab); - + // + // CHECK INCREMENTAL BUILD GRAPH + // const graph = patternlab.graph = loadPatternGraph(deletePatternDir); - const graphNeedsUpgrade = !PatternGraph.checkVersion(graph); - if (graphNeedsUpgrade) { plutils.log.info("Due to an upgrade, a complete rebuild is required and the public/patterns directory was deleted. " + "Incremental build is available again on the next successful run."); @@ -532,50 +626,15 @@ const patternlab_engine = function (config) { // Ensure that the freshly built graph has the latest version again. patternlab.graph.upgradeVersion(); } - // Flags - const incrementalBuildsEnabled = !(deletePatternDir || graphNeedsUpgrade); - - if (incrementalBuildsEnabled) { - plutils.log.info("Incremental builds enabled."); - } else { - // needs to be done BEFORE processing patterns - fs.emptyDirSync(paths.public.patterns); - } - - try { - patternlab.data = buildPatternData(paths.source.data, fs); - } catch (ex) { - plutils.error('missing or malformed' + paths.source.data + 'data.json Pattern Lab may not work without this file.'); - patternlab.data = {}; - } - try { - patternlab.listitems = dataLoader.loadDataFromFile(path.resolve(paths.source.data, 'listitems'), fs); - } catch (ex) { - plutils.warning('WARNING: missing or malformed ' + paths.source.data + 'listitems file. Pattern Lab may not work without this file.'); - patternlab.listitems = {}; - } - try { - patternlab.header = fs.readFileSync(path.resolve(paths.source.patternlabFiles['general-header']), 'utf8'); - patternlab.footer = fs.readFileSync(path.resolve(paths.source.patternlabFiles['general-footer']), 'utf8'); - patternlab.patternSection = fs.readFileSync(path.resolve(paths.source.patternlabFiles.patternSection), 'utf8'); - patternlab.patternSectionSubType = fs.readFileSync(path.resolve(paths.source.patternlabFiles.patternSectionSubtype), 'utf8'); - patternlab.viewAll = fs.readFileSync(path.resolve(paths.source.patternlabFiles.viewall), 'utf8'); - } catch (ex) { - console.log(ex); - plutils.error('\nERROR: missing an essential file from ' + paths.source.patternlabFiles + '. Pattern Lab won\'t work without this file.\n'); - process.exit(1); - } - patternlab.patterns = []; - patternlab.subtypePatterns = {}; - patternlab.partials = {}; - patternlab.data.link = {}; + patternlab.incrementalBuildsEnabled = !(deletePatternDir || graphNeedsUpgrade); - setCacheBust(); + // + // CLEAN BUILD DIRECTORY, maybe + // + cleanBuildDirectory(patternlab.incrementalBuildsEnabled); - pattern_assembler.combine_listItems(patternlab); - - patternlab.events.emit('patternlab-build-global-data-end', patternlab); + patternlab.buildGlobalData(); // diveSync once to perform iterative populating of patternlab object return processAllPatternsIterative(paths.source.patterns, patternlab).then(() => { @@ -592,8 +651,9 @@ const patternlab_engine = function (config) { processAllPatternsRecursive(paths.source.patterns, patternlab); //take the user defined head and foot and process any data and patterns that apply - processHeadPattern(); - processFootPattern(); + // GTP: should these really be invoked from outside? + patternlab.processHeadPattern(); + patternlab.processFootPattern(); //cascade any patternStates lineage_hunter.cascade_pattern_states(patternlab); @@ -619,7 +679,7 @@ const patternlab_engine = function (config) { // rebuild all patterns patternsToBuild = null; - if (incrementalBuildsEnabled) { + if (patternlab.incrementalBuildsEnabled) { // When the graph was loaded from file, some patterns might have been moved/deleted between runs // so the graph data become out of sync patternlab.graph.sync().forEach(n => { @@ -639,17 +699,21 @@ const patternlab_engine = function (config) { } //render all patterns last, so lineageR works - patternsToBuild.forEach(pattern => renderSinglePattern(pattern, head)); - - // Saves the pattern graph when all files have been compiled - PatternGraph.storeToFile(patternlab); - if (patternlab.config.exportToGraphViz) { - PatternGraph.exportToDot(patternlab, "dependencyGraph.dot"); - plutils.log.info(`Exported pattern graph to ${path.join(config.paths.public.root, "dependencyGraph.dot")}`); - } + return patternsToBuild + .reduce((previousPromise, pattern) => { + return previousPromise.then(() => renderSinglePattern(pattern, head)); + }, Promise.resolve()) + .then(() => { + // Saves the pattern graph when all files have been compiled + PatternGraph.storeToFile(patternlab); + if (patternlab.config.exportToGraphViz) { + PatternGraph.exportToDot(patternlab, "dependencyGraph.dot"); + plutils.log.info(`Exported pattern graph to ${path.join(config.paths.public.root, "dependencyGraph.dot")}`); + } - //export patterns if necessary - pattern_exporter.export_patterns(patternlab); + //export patterns if necessary + pattern_exporter.export_patterns(patternlab); + }); }).catch((err) => { console.log('Error in buildPatterns():', err); }); @@ -662,7 +726,7 @@ const patternlab_engine = function (config) { * @returns {void} current patternlab-node version as defined in package.json, as console output */ version: function () { - return logVersion(); + return patternlab.logVersion(); }, /** @@ -671,7 +735,7 @@ const patternlab_engine = function (config) { * @returns {string} current patternlab-node version as defined in package.json, as string */ v: function () { - return getVersion(); + return patternlab.getVersion(); }, /** @@ -749,7 +813,7 @@ const patternlab_engine = function (config) { * @returns {Promise} Returns an Array<{name,url}> for the starterkit repos */ liststarterkits: function () { - return listStarterkits(); + return patternlab.listStarterkits(); }, /** @@ -760,7 +824,7 @@ const patternlab_engine = function (config) { * @returns {void} */ loadstarterkit: function (starterkitName, clean) { - loadStarterKit(starterkitName, clean); + patternlab.loadStarterKit(starterkitName, clean); }, @@ -780,7 +844,7 @@ const patternlab_engine = function (config) { * @returns {Array} all supported file extensions */ getSupportedTemplateExtensions: function () { - return getSupportedTemplateExtensions(); + return patternlab.getSupportedTemplateExtensions(); }, /** diff --git a/package-lock.json b/package-lock.json index 8db5e864b..80de22468 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@pattern-lab/patternlab-node", - "version": "3.0.0-alpha.1", + "version": "3.0.0-alpha.4", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -1899,7 +1899,7 @@ } }, "live-server": { - "version": "github:pattern-lab/live-server#d46ec211455efb0ae2ad140b09e151bc2649c496", + "version": "github:pattern-lab/live-server#5db065902f95266af29b7e80873c3a4dbdd4620d", "requires": { "chokidar": "1.7.0", "colors": "1.1.2",