Fixes #266 - Hot reloading fails if original template is deleted

This commit is contained in:
Patrick Steele-Idem 2016-04-15 10:35:17 -06:00
parent 3203d630ec
commit 36a3a02f9f
3 changed files with 56 additions and 19 deletions

View File

@ -20,7 +20,8 @@ var extend = require('raptor-util/extend');
var compiler = require('../compiler');
var nodePath = require('path');
var modifiedFlag = 1;
var modifiedId = 1;
var nextTemplateId = 0;
var runtime;
/**
@ -32,6 +33,14 @@ function _getMarkoRuntime() {
return runtime || (runtime = require('../runtime'));
}
function tryReload(path, runtime) {
try {
return runtime.load(path);
} catch(e) {
return undefined;
}
}
exports.enable = function() {
var runtime = _getMarkoRuntime();
@ -60,13 +69,16 @@ exports.enable = function() {
if (typeof v === 'function') {
var oldMethod = v;
Template.prototype[k] = function() {
if (this.__hotReloadModifiedFlag !== modifiedFlag) {
if (this.__hotReloadModifiedId !== modifiedId) {
var path = this.__hotReloadPath;
if (path) {
// Reload the template
var template = runtime.load(path);
extend(this, template);
var template = tryReload(path, runtime);
if (template && template.__hotReloadTemplateId !== this.__hotReloadTemplateId) {
extend(this, template);
}
}
this.__hotReloadModifiedId = modifiedId;
}
return oldMethod.apply(this, arguments);
@ -95,9 +107,16 @@ exports.enable = function() {
var template = oldCreateTemplate.apply(runtime, arguments);
// Store the current last modified with the template
template.__hotReloadModifiedFlag = modifiedFlag;
template.__hotReloadModifiedId = modifiedId;
// Store the path of the loaded template so that we can reload it if
// necessary
template.__hotReloadPath = templatePath;
// Assign a unique ID to the loaded template so that we can know if
// a new version of the template is loaded
template.__hotReloadTemplateId = nextTemplateId++;
return template;
};
};
@ -110,18 +129,26 @@ exports.handleFileModified = function(path) {
path.endsWith('.marko.html') ||
path.endsWith('.marko.xml') ||
basename === 'marko-tag.json' ||
basename === 'marko-taglib.json') {
basename === 'marko.json') {
console.log('[marko/hot-reload] File modified: ' + path);
if (path.endsWith('.marko') || path.endsWith('.marko.html')) {
// Uncache just the modified template
delete require.cache[path];
delete require.cache[path + '.js'];
} else {
// If we taglib was modified then uncache *all* templates so that they will
// all be reloaded
Object.keys(require.cache).forEach((filename) => {
if (filename.endsWith('.marko') || filename.endsWith('.marko.js')) {
delete require.cache[filename];
}
});
}
runtime.cache = {};
compiler.clearCaches();
modifiedFlag++;
modifiedId++;
}
};

View File

@ -68,6 +68,11 @@ function compile(templatePath, markoCompiler, compilerOptions) {
return compiledSrc;
}
function getLoadedTemplate(path) {
var cached = require.cache[path];
return cached && cached.exports.render ? cached.exports : undefined;
}
exports.install = function(options) {
options = options || {};
@ -85,10 +90,10 @@ exports.install = function(options) {
require.extensions[extension] = function markoExtension(module, filename) {
var targetFile = filename + '.js';
var loaded = require.cache[targetFile];
if (loaded) {
var cachedTemplate = getLoadedTemplate(targetFile) || getLoadedTemplate(filename);
if (cachedTemplate) {
// The template has already been loaded so use the exports of the already loaded template
module.exports = loaded.exports;
module.exports = cachedTemplate;
return;
}

View File

@ -73,24 +73,28 @@ function loadSource(templatePath, compiledSrc) {
return templateModule.exports;
}
function getLoadedTemplate(path) {
var cached = require.cache[path];
return cached && cached.exports.render ? cached.exports : undefined;
}
function loadFile(templatePath, options) {
var targetFile = templatePath + '.js';
// Short-circuit loading if the template has already been cached in the Node.js require cache
var cached = require.cache[targetFile];
if (cached) {
return cached.exports;
var cachedTemplate = getLoadedTemplate(targetFile) || getLoadedTemplate(templatePath);
if (cachedTemplate) {
return cachedTemplate;
}
// Just in case the the path wasn't a fully resolved file system path...
templatePath = nodePath.resolve(cwd, templatePath);
var targetDir = nodePath.dirname(templatePath);
targetFile = templatePath + '.js';
// Check the require cache again after fully resolving the path
cached = require.cache[targetFile];
if (cached) {
return cached.exports;
cachedTemplate = getLoadedTemplate(targetFile) || getLoadedTemplate(templatePath);
if (cachedTemplate) {
return cachedTemplate;
}
options = extend(extend({}, markoCompiler.defaultOptions), options);
@ -114,6 +118,7 @@ function loadFile(templatePath, options) {
// console.log('Compiled code for "' + templatePath + '":\n' + compiledSrc);
var filename = nodePath.basename(targetFile);
var targetDir = nodePath.dirname(targetFile);
var tempFile = nodePath.join(targetDir, '.' + process.pid + '.' + Date.now() + '.' + filename);
fs.writeFileSync(tempFile, compiledSrc, fsReadOptions);
fs.renameSync(tempFile, targetFile);