Skip to content

Commit e19a6db

Browse files
author
Tobias Brennecke
committed
Issue #540 Incremental Builds
1 parent 53f877f commit e19a6db

16 files changed

+1351
-146
lines changed

core/lib/lineage_hunter.js

Lines changed: 40 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
"use strict";
22

3+
var extend = require("util")._extend;
4+
35
var lineage_hunter = function () {
46

57
var pa = require('./pattern_assembler');
@@ -28,6 +30,10 @@ var lineage_hunter = function () {
2830
l.lineageState = ancestorPattern.patternState;
2931
}
3032

33+
patternlab.graph.add(ancestorPattern);
34+
// Confusing: pattern includes "ancestorPattern", not the other way round
35+
patternlab.graph.link(pattern, ancestorPattern);
36+
3137
pattern.lineage.push(l);
3238

3339
//also, add the lineageR entry if it doesn't exist
@@ -44,62 +50,70 @@ var lineage_hunter = function () {
4450
}
4551

4652
ancestorPattern.lineageR.push(lr);
53+
extend(patternlab.graph.node(ancestorPattern), lr);
4754
}
4855
}
4956
});
5057
}
5158
}
5259

53-
function setPatternState(direction, pattern, targetPattern) {
54-
// if the request came from the past, apply target pattern state to current pattern lineage
60+
/**
61+
* Apply the target pattern state either to any predecessors or successors of the given
62+
* pattern in the pattern graph.
63+
* @param direction Either 'fromPast' or 'fromFuture'
64+
* @param pattern {Pattern}
65+
* @param targetPattern {Pattern}
66+
* @param graph {PatternGraph}
67+
*/
68+
function setPatternState(direction, pattern, targetPattern, graph) {
69+
var index = null;
5570
if (direction === 'fromPast') {
56-
for (var i = 0; i < pattern.lineageIndex.length; i++) {
57-
if (pattern.lineageIndex[i] === targetPattern.patternPartial) {
58-
pattern.lineage[i].lineageState = targetPattern.patternState;
59-
}
60-
}
71+
index = graph.lineage(pattern);
6172
} else {
62-
//the request came from the future, apply target pattern state to current pattern reverse lineage
63-
for (var i = 0; i < pattern.lineageRIndex.length; i++) {
64-
if (pattern.lineageRIndex[i] === targetPattern.patternPartial) {
65-
pattern.lineageR[i].lineageState = targetPattern.patternState;
66-
}
73+
index = graph.lineageR(pattern);
74+
}
75+
// if the request came from the past, apply target pattern state to current pattern lineage
76+
for (var i = 0; i < index.length; i++) {
77+
if (index[i].patternPartial === targetPattern.patternPartial) {
78+
index[i].lineageState = targetPattern.patternState;
6779
}
6880
}
6981
}
7082

7183

7284
function cascadePatternStates(patternlab) {
7385

74-
var pattern_assembler = new pa();
75-
7686
for (var i = 0; i < patternlab.patterns.length; i++) {
7787
var pattern = patternlab.patterns[i];
7888

7989
//for each pattern with a defined state
8090
if (pattern.patternState) {
91+
var lineage = patternlab.graph.lineage(pattern);
8192

82-
if (pattern.lineageIndex && pattern.lineageIndex.length > 0) {
93+
if (lineage && lineage.length > 0) {
8394

8495
//find all lineage - patterns being consumed by this one
85-
for (var h = 0; h < pattern.lineageIndex.length; h++) {
86-
var lineagePattern = pattern_assembler.getPartial(pattern.lineageIndex[h], patternlab);
87-
setPatternState('fromFuture', lineagePattern, pattern);
96+
for (var h = 0; h < lineage.length; h++) {
97+
// Not needed, the graph already knows the concrete pattern
98+
// var lineagePattern = pattern_assembler.getPartial(lineageIndex[h], patternlab);
99+
setPatternState('fromFuture', lineage[h], pattern, patternlab.graph);
88100
}
89101
}
90-
91-
if (pattern.lineageRIndex && pattern.lineageRIndex.length > 0) {
102+
var lineageR = patternlab.graph.lineageR(pattern);
103+
if (lineageR && lineageR.length > 0) {
92104

93105
//find all reverse lineage - that is, patterns consuming this one
94-
for (var j = 0; j < pattern.lineageRIndex.length; j++) {
106+
for (var j = 0; j < lineageR.length; j++) {
95107

96-
var lineageRPattern = pattern_assembler.getPartial(pattern.lineageRIndex[j], patternlab);
108+
var lineageRPattern = lineageR[j];
97109

98110
//only set patternState if pattern.patternState "is less than" the lineageRPattern.patternstate
99111
//or if lineageRPattern.patternstate (the consuming pattern) does not have a state
100112
//this makes patternlab apply the lowest common ancestor denominator
101-
if (lineageRPattern.patternState === '' || (patternlab.config.patternStateCascade.indexOf(pattern.patternState)
102-
< patternlab.config.patternStateCascade.indexOf(lineageRPattern.patternState))) {
113+
let patternStateCascade = patternlab.config.patternStateCascade;
114+
let patternStateIndex = patternStateCascade.indexOf(pattern.patternState);
115+
let patternReverseStateIndex = patternStateCascade.indexOf(lineageRPattern.patternState);
116+
if (lineageRPattern.patternState === '' || (patternStateIndex < patternReverseStateIndex)) {
103117

104118
if (patternlab.config.debug) {
105119
console.log('Found a lower common denominator pattern state: ' + pattern.patternState + ' on ' + pattern.patternPartial + '. Setting reverse lineage pattern ' + lineageRPattern.patternPartial + ' from ' + (lineageRPattern.patternState === '' ? '<<blank>>' : lineageRPattern.patternState));
@@ -108,9 +122,9 @@ var lineage_hunter = function () {
108122
lineageRPattern.patternState = pattern.patternState;
109123

110124
//take this opportunity to overwrite the lineageRPattern's lineage state too
111-
setPatternState('fromPast', lineageRPattern, pattern);
125+
setPatternState('fromPast', lineageRPattern, pattern, patternlab.graph);
112126
} else {
113-
setPatternState('fromPast', pattern, lineageRPattern);
127+
setPatternState('fromPast', pattern, lineageRPattern, patternlab.graph);
114128
}
115129
}
116130
}

core/lib/object_factory.js

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,11 @@ var Pattern = function (relPath, data, patternlab) {
7373
this.isPseudoPattern = false;
7474
this.order = Number.MAX_SAFE_INTEGER;
7575
this.engine = patternEngines.getEngineForPattern(this);
76+
// For completeness
77+
this.compileState = null;
78+
// The unix timestamp when the pattern was last modified
79+
this.lastModified = null;
80+
7681
};
7782

7883
// Pattern methods
@@ -153,6 +158,13 @@ Pattern.create = function (relPath, data, customProps, patternlab) {
153158
return extend(newPattern, customProps);
154159
};
155160

161+
var CompileState = {
162+
NEEDS_REBUILD: "needs rebuild",
163+
BUILDING: "building",
164+
CLEAN: "clean"
165+
};
166+
156167
module.exports = {
157-
Pattern: Pattern
168+
Pattern: Pattern,
169+
CompileState: CompileState
158170
};

core/lib/pattern_assembler.js

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
var path = require('path'),
44
fs = require('fs-extra'),
55
Pattern = require('./object_factory').Pattern,
6+
CompileState = require('./object_factory').CompileState,
67
pph = require('./pseudopattern_hunter'),
78
mp = require('./markdown_parser'),
89
plutils = require('./utilities'),
@@ -123,7 +124,7 @@ var pattern_assembler = function () {
123124
} else {
124125
patternlab.partials[pattern.patternPartial] = pattern.patternDesc;
125126
}
126-
127+
patternlab.graph.add(pattern);
127128
patternlab.patterns.push(pattern);
128129

129130
}
@@ -240,6 +241,30 @@ var pattern_assembler = function () {
240241
addPattern(pattern, patternlab);
241242
}
242243

244+
function checkBuildState (pattern, patternlab) {
245+
//write the compiled template to the public patterns directory
246+
var renderedTemplatePath =
247+
patternlab.config.paths.public.patterns + pattern.getPatternLink(patternlab, 'rendered');
248+
249+
if (!pattern.compileState) {
250+
pattern.compileState = CompileState.NEEDS_REBUILD;
251+
}
252+
253+
try {
254+
// Prevent error message if file does not exist
255+
fs.accessSync(renderedTemplatePath, fs.F_OK);
256+
var outputLastModified = fs.statSync(renderedTemplatePath).mtime.getTime();
257+
258+
if (pattern.lastModified && outputLastModified > pattern.lastModified) {
259+
pattern.compileState = CompileState.CLEAN;
260+
}
261+
} catch (e) {
262+
// Output does not exist yet, needs recompile
263+
}
264+
// Make the pattern known to the PatternGraph and remember its compileState
265+
patternlab.graph.add(pattern);
266+
}
267+
243268
function processPatternIterative(relPath, patternlab) {
244269

245270
var relativeDepth = (relPath.match(/\w(?=\\)|\w(?=\/)/g) || []).length;
@@ -309,7 +334,7 @@ var pattern_assembler = function () {
309334

310335
//see if this file has a state
311336
setState(currentPattern, patternlab, true);
312-
337+
var jsonFileLastModified = null;
313338
//look for a json file for this template
314339
try {
315340
var jsonFilename = path.resolve(patternsPath, currentPattern.subdir, currentPattern.fileName + ".json");
@@ -320,6 +345,7 @@ var pattern_assembler = function () {
320345
}
321346
if (jsonFilenameStats && jsonFilenameStats.isFile()) {
322347
currentPattern.jsonFileData = fs.readJSONSync(jsonFilename);
348+
jsonFileLastModified = jsonFilenameStats.mtime.getTime();
323349
if (patternlab.config.debug) {
324350
console.log('processPatternIterative: found pattern-specific data.json for ' + currentPattern.patternPartial);
325351
}
@@ -355,7 +381,20 @@ var pattern_assembler = function () {
355381
parsePatternMarkdown(currentPattern, patternlab);
356382

357383
//add the raw template to memory
358-
currentPattern.template = fs.readFileSync(path.resolve(patternsPath, relPath), 'utf8');
384+
var templatePath = path.resolve(patternsPath, currentPattern.relPath);
385+
if (templatePath) {
386+
try {
387+
var stat = fs.statSync(templatePath);
388+
// Needs recompile whenever either the JSON or the source file has been changed
389+
currentPattern.lastModified = Math.max(stat.mtime.getTime(), jsonFileLastModified);
390+
} catch (e) {
391+
// Ignore, not a regular file
392+
}
393+
}
394+
395+
checkBuildState(currentPattern, patternlab);
396+
397+
currentPattern.template = fs.readFileSync(templatePath, 'utf8');
359398

360399
//find any stylemodifiers that may be in the current pattern
361400
currentPattern.stylePartials = currentPattern.findPartialsWithStyleModifiers();
@@ -393,6 +432,15 @@ var pattern_assembler = function () {
393432
decomposePattern(currentPattern, patternlab);
394433
}
395434

435+
function findModifiedPatterns(lastModified, patternlab) {
436+
return patternlab.patterns.filter( p => {
437+
if (p.compileState !== CompileState.CLEAN || ! p.lastModified) {
438+
return true;
439+
}
440+
return p.lastModified >= lastModified;
441+
});
442+
}
443+
396444
function expandPartials(foundPatternPartials, list_item_hunter, patternlab, currentPattern) {
397445

398446
var style_modifier_hunter = new smh(),
@@ -506,6 +554,9 @@ var pattern_assembler = function () {
506554
}
507555

508556
return {
557+
find_modified_patterns: function (lastModified, patternlab) {
558+
return findModifiedPatterns(lastModified, patternlab)
559+
},
509560
find_pattern_partials: function (pattern) {
510561
return pattern.findPartials();
511562
},
@@ -533,6 +584,9 @@ var pattern_assembler = function () {
533584
renderPattern: function (template, data, partials) {
534585
return renderPattern(template, data, partials);
535586
},
587+
check_build_state: function (pattern, patternlab) {
588+
return checkBuildState(pattern, patternlab);
589+
},
536590
process_pattern_iterative: function (file, patternlab) {
537591
return processPatternIterative(file, patternlab);
538592
},

0 commit comments

Comments
 (0)