marko/hot-reload/index.js
2017-02-20 16:30:16 -07:00

191 lines
5.5 KiB
JavaScript

require('raptor-polyfill/string/endsWith');
const nodePath = require('path');
const fs = require('fs');
const nodeRequire = require('../node-require');
var compiler;
var marko;
var runtime;
var components;
var modifiedId = 1;
var HOT_RELOAD_KEY = Symbol('HOT_RELOAD');
function cleaResolvePathCache() {
var modulePathCache = require('module').Module._pathCache;
if (!modulePathCache) {
console.log('[marko/hot-reload] WARNING: Missing: require("module").Module._pathCache [' + __filename + ']');
return;
}
var keys = Object.keys(modulePathCache);
keys.forEach(function(key) {
delete modulePathCache[key];
});
}
function tryReloadTemplate(path) {
path = path.replace(/\.js$/, '');
try {
return marko.load(path);
} catch(e) {
return undefined;
}
}
exports.enable = function() {
if (runtime.__hotReloadEnabled) {
// Marko has already been monkey-patched. Nothing to do!
return;
}
runtime.__hotReloadEnabled = true;
// We set an environment variable so that _all_ marko modules
// installed in the project will have hot reload enabled.
process.env.MARKO_HOT_RELOAD = 'true';
function createHotReloadProxy(func, template, methodName) {
var hotReloadData = template[HOT_RELOAD_KEY];
if (!hotReloadData) {
hotReloadData = template[HOT_RELOAD_KEY] = {
modifiedId: modifiedId,
latest: template,
originals: {}
};
}
hotReloadData.originals[methodName] = func;
var templatePath = template.path;
function hotReloadProxy() {
if (hotReloadData.modifiedId !== modifiedId) {
hotReloadData.modifiedId = modifiedId;
hotReloadData.latest = tryReloadTemplate(templatePath) || template;
if (hotReloadData.latest !== template) {
template.meta = hotReloadData.latest.meta;
console.log('[marko/hot-reload] Template successfully reloaded: ' + templatePath);
}
}
var latest = hotReloadData.latest;
var originals = latest[HOT_RELOAD_KEY] && latest[HOT_RELOAD_KEY].originals;
if (!originals) {
originals = latest;
}
var targetFunc = originals._;
return targetFunc.apply(latest, arguments);
}
return hotReloadProxy;
}
var oldCreateTemplate = runtime.t;
runtime.t = function hotReloadCreateTemplate(path) {
var originalTemplate = oldCreateTemplate.apply(runtime, arguments);
var actualRenderFunc;
Object.defineProperty(originalTemplate, '_', {
get: function() {
return actualRenderFunc;
},
set: function(renderFunc) {
actualRenderFunc = createHotReloadProxy(renderFunc, originalTemplate, '_');
}
});
return originalTemplate;
};
};
/**
* Checks whether a path ends with a custom Marko extension
*/
function _endsWithMarkoExtension(path, requireExtensions) {
for (var i = 0; i < requireExtensions.length; i++) {
if (path.endsWith(requireExtensions[i])) {
return true;
}
}
return false;
}
function normalizeExtension(extension) {
if (extension.charAt(0) !== '.') {
extension = '.' + extension;
}
return extension;
}
exports.handleFileModified = function(path, options) {
if (!fs.existsSync(path)) {
console.log('[marko/hot-reload] WARNING cannot resolve template path: ', path);
return;
}
options = options || {};
// Default hot-reloaded extensions
var requireExtensions = ['.marko', '.marko.html', '.marko.xml'];
if (options.extension) {
requireExtensions.push(options.extension);
}
if (options.extensions) {
requireExtensions = requireExtensions.concat(options.extensions);
}
var nodeRequireExtensions = nodeRequire.getExtensions();
if (nodeRequireExtensions) {
requireExtensions = requireExtensions.concat(nodeRequireExtensions);
}
for (var i = 0; i < requireExtensions.length; i++) {
requireExtensions[i] = normalizeExtension(requireExtensions[i]);
}
var basename = nodePath.basename(path);
function handleFileModified() {
console.log('[marko/hot-reload] File modified: ' + path);
runtime.cache = {};
compiler.clearCaches();
cleaResolvePathCache();
modifiedId++;
}
if (basename === 'marko-tag.json' || basename === 'marko.json') {
handleFileModified();
// 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];
}
});
} else if (_endsWithMarkoExtension(path, requireExtensions)) {
handleFileModified();
delete require.cache[path];
delete require.cache[path + '.js'];
} else if (basename === 'component.js') {
handleFileModified();
var dir = nodePath.dirname(path);
var templatePath = nodePath.join(dir, 'index.marko');
delete require.cache[path];
delete require.cache[templatePath];
delete require.cache[templatePath + '.js'];
}
};
compiler = require('../compiler');
marko = require('../');
runtime = require('../runtime/html');
components = require('../components');