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');