Skip to content

Commit

Permalink
feat(patternlab): Separate patternlab.js into two files
Browse files Browse the repository at this point in the history
* index.js will contain the trimmed down cleaner public entry point
* patternlab.js is now just the class
* no more free functions
* code is rendering correctly, with only 5 unit test failures
* still work to do to potentially move and re-org some files
  • Loading branch information
bmuenzenmeyer committed Nov 17, 2017
1 parent cd4bace commit 86233ca
Show file tree
Hide file tree
Showing 3 changed files with 449 additions and 432 deletions.
380 changes: 380 additions & 0 deletions core/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,380 @@
/*
* patternlab-node https://github.com/pattern-lab/patternlab-node
*
* Brian Muenzenmeyer, Geoff Pursell, Raphael Okon, tburny 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 packageInfo = require('../package.json');

const path = require('path');
const updateNotifier = require('update-notifier');

const logger = require('./lib/log');
const PatternGraph = require('./lib/pattern_graph').PatternGraph;
const CompileState = require('./lib/object_factory').CompileState;
const pa = require('./lib/pattern_assembler');
const pe = require('./lib/pattern_exporter');
const lh = require('./lib/lineage_hunter');

const PatternLab = require('./lib/patternlab');

let fs = require('fs-extra'); // eslint-disable-line
let ui_builder = require('./lib/ui_builder'); // eslint-disable-line
let assetCopier = require('./lib/asset_copy'); // eslint-disable-line
let pattern_exporter = new pe(); // eslint-disable-line
let serve = require('./lib/serve'); // eslint-disable-line

const pattern_assembler = new pa();
const lineage_hunter = new lh();

//bootstrap update notifier
updateNotifier({
pkg: packageInfo,
updateCheckInterval: 1000 * 60 * 60 * 24 // notify at most once a day
}).notify();

const patternlab_module = function (config) {
const patternlab = new PatternLab(config);
const paths = patternlab.config.paths;

function help() {

logger.info('');

logger.info('|=======================================|');
logger.debug(' Pattern Lab Node Help v' + patternlab.package.version);
logger.info('|=======================================|');

logger.info('');
logger.info('API - usually consumed by an edition');
logger.info('');

logger.debug(' patternlab:build');
logger.info(' > Compiles the patterns and frontend, outputting to config.paths.public');
logger.info('');

logger.debug(' patternlab:patternsonly');
logger.info(' > Compiles the patterns only, outputting to config.paths.public');
logger.info('');

logger.debug(' patternlab:version');
logger.info(' > Return the version of patternlab-node you have installed');
logger.info('');

logger.debug(' patternlab:help');
logger.info(' > Get more information about patternlab-node, pattern lab in general, and where to report issues.');
logger.info('');

logger.debug(' patternlab:liststarterkits');
logger.info(' > Returns a url with the list of available starterkits hosted on the Pattern Lab organization Github account');
logger.info('');

logger.debug(' patternlab:loadstarterkit');
logger.info(' > Load a starterkit into config.paths.source/*');
logger.info(' > NOTE: Overwrites existing content, and only cleans out existing directory if --clean=true argument is passed.');
logger.info(' > NOTE: In most cases, `npm install starterkit-name` will precede this call.');
logger.info(' > arguments:');
logger.info(' -- kit ');
logger.info(' > the name of the starter kit to load');
logger.info(' -- clean ');
logger.info(' > removes all files from config.paths.source/ prior to load');
logger.info(' > example (gulp):');
logger.info(' `gulp patternlab:loadstarterkit --kit=starterkit-mustache-demo`');
logger.info('');

logger.info('===============================');
logger.info('');
logger.info('Visit http://patternlab.io/ for more info about Pattern Lab');
logger.info('Visit https://github.com/pattern-lab/patternlab-node/issues to open an issue.');
logger.info('Visit https://github.com/pattern-lab/patternlab-node/wiki to view the changelog, roadmap, and other info.');
logger.info('');
logger.info('===============================');
}

/**
* If a graph was serialized and then {@code deletePatternDir == true}, there is a mismatch in the
* pattern metadata and not all patterns might be recompiled.
* For that reason an empty graph is returned in this case, so every pattern will be flagged as
* "needs recompile". Otherwise the pattern graph is loaded from the meta data.
*
* @param patternlab
* @param {boolean} deletePatternDir When {@code true}, an empty graph is returned
* @return {PatternGraph}
*/
function loadPatternGraph(deletePatternDir) {
// Sanity check to prevent problems when code is refactored
if (deletePatternDir) {
return PatternGraph.empty();
}
return PatternGraph.loadFromFile(patternlab);
}

function cleanBuildDirectory(incrementalBuildsEnabled) {
if (incrementalBuildsEnabled) {
logger.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);

//
// CHECK INCREMENTAL BUILD GRAPH
//
const graph = patternlab.graph = loadPatternGraph(deletePatternDir);
const graphNeedsUpgrade = !PatternGraph.checkVersion(graph);
if (graphNeedsUpgrade) {
logger.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.");

// Ensure that the freshly built graph has the latest version again.
patternlab.graph.upgradeVersion();
}

// Flags
patternlab.incrementalBuildsEnabled = !(deletePatternDir || graphNeedsUpgrade);

//
// CLEAN BUILD DIRECTORY, maybe
//
cleanBuildDirectory(patternlab.incrementalBuildsEnabled);

patternlab.buildGlobalData();

// diveSync once to perform iterative populating of patternlab object
return patternlab.processAllPatternsIterative(paths.source.patterns, patternlab).then(() => {

patternlab.events.emit('patternlab-pattern-iteration-end', patternlab);

//now that all the main patterns are known, look for any links that might be within data and expand them
//we need to do this before expanding patterns & partials into extendedTemplates, otherwise we could lose the data -> partial reference
pattern_assembler.parse_data_links(patternlab);

//diveSync again to recursively include partials, filling out the
//extendedTemplate property of the patternlab.patterns elements
// TODO we can reduce the time needed by only processing changed patterns and their partials
patternlab.processAllPatternsRecursive(paths.source.patterns, patternlab);

//take the user defined head and foot and process any data and patterns that apply
// GTP: should these really be invoked from outside?
patternlab.processHeadPattern();
patternlab.processFootPattern();

//cascade any patternStates
lineage_hunter.cascade_pattern_states(patternlab);

//set pattern-specific header if necessary
let head;
if (patternlab.userHead) {
head = patternlab.userHead;
} else {
head = patternlab.header;
}

//set the pattern-specific header by compiling the general-header with data, and then adding it to the meta header
patternlab.data.patternLabHead = pattern_assembler.renderPattern(patternlab.header, {
cacheBuster: patternlab.cacheBuster
});

// If deletePatternDir == true or graph needs to be updated
// rebuild all patterns
let patternsToBuild = null;

// If deletePatternDir == true or graph needs to be updated
// rebuild all patterns
patternsToBuild = null;

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 => {
logger.log.info("[Deleted/Moved] " + n);
});

// TODO Find created or deleted files
const now = new Date().getTime();
pattern_assembler.mark_modified_patterns(now, patternlab);
patternsToBuild = patternlab.graph.compileOrder();
} else {
// build all patterns, mark all to be rebuilt
patternsToBuild = patternlab.patterns;
for (const p of patternsToBuild) {
p.compileState = CompileState.NEEDS_REBUILD;
}
}

//render all patterns last, so lineageR works
return patternsToBuild
.reduce((previousPromise, pattern) => {
return previousPromise.then(() => patternlab.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");
logger.log.info(`Exported pattern graph to ${path.join(config.paths.public.root, "dependencyGraph.dot")}`);
}

//export patterns if necessary
pattern_exporter.export_patterns(patternlab);
});
}).catch((err) => {
logger.info('Error in buildPatterns():', err);
});
}

return {
/**
* logs current version
*
* @returns {void} current patternlab-node version as defined in package.json, as console output
*/
version: function () {
return patternlab.logVersion();
},

/**
* return current version
*
* @returns {string} current patternlab-node version as defined in package.json, as string
*/
v: function () {
return patternlab.getVersion();
},

/**
* build patterns, copy assets, and construct ui
*
* @param {function} callback a function invoked when build is complete
* @param {object} options an object used to control build behavior
* @returns {Promise} a promise fulfilled when build is complete
*/
build: function (callback, options) {
if (patternlab && patternlab.isBusy) {
logger.info('Pattern Lab is busy building a previous run - returning early.');
return Promise.resolve();
}
patternlab.isBusy = true;
return buildPatterns(options.cleanPublic).then(() => {

new ui_builder().buildFrontend(patternlab);
assetCopier().copyAssets(patternlab.config.paths, patternlab, options);

this.events.on('patternlab-pattern-change', () => {
if (!patternlab.isBusy) {
options.cleanPublic = false;
return this.build(callback, options);
}
return Promise.resolve();
});

this.events.on('patternlab-global-change', () => {
if (!patternlab.isBusy) {
options.cleanPublic = true; //rebuild everything
return this.build(callback, options);
}
return Promise.resolve();
});

patternlab.isBusy = false;
callback();
});
},

/**
* logs usage
*
* @returns {void} pattern lab API usage, as console output
*/
help: function () {
help();
},

/**
* build patterns only, leaving existing public files intact
*
* @param {function} callback a function invoked when build is complete
* @param {object} options an object used to control build behavior
* @returns {Promise} a promise fulfilled when build is complete
*/
patternsonly: function (callback, options) {
if (patternlab && patternlab.isBusy) {
logger.info('Pattern Lab is busy building a previous run - returning early.');
return Promise.resolve();
}
patternlab.isBusy = true;
return buildPatterns(options.cleanPublic).then(() => {
patternlab.isBusy = false;
callback();
});
},

/**
* fetches starterkit repos from pattern-lab github org that contain 'starterkit' in their name
*
* @returns {Promise} Returns an Array<{name,url}> for the starterkit repos
*/
liststarterkits: function () {
return patternlab.listStarterkits();
},

/**
* load starterkit already available via `node_modules/`
*
* @param {string} starterkitName name of starterkit
* @param {boolean} clean whether or not to delete contents of source/ before load
* @returns {void}
*/
loadstarterkit: function (starterkitName, clean) {
patternlab.loadStarterKit(starterkitName, clean);
},


/**
* install plugin already available via `node_modules/`
*
* @param {string} pluginName name of plugin
* @returns {void}
*/
installplugin: function (pluginName) {
patternlab.installPlugin(pluginName);
},

/**
* returns all file extensions supported by installed PatternEngines
*
* @returns {Array<string>} all supported file extensions
*/
getSupportedTemplateExtensions: function () {
return patternlab.getSupportedTemplateExtensions();
},

/**
* build patterns, copy assets, and construct ui, watch source files, and serve locally
*
* @param {object} options an object used to control build, copy, and serve behavior
* @returns {Promise} TODO: validate
*/
serve: function (options) {
options.watch = true;
return this.build(() => {}, options).then(function () {
serve(patternlab);
});
},

events: patternlab.events
};
};

module.exports = patternlab_module;
Loading

0 comments on commit 86233ca

Please sign in to comment.