UNPKG

5.48 kBJavaScriptView Raw
1/* eslint-disable no-console */
2
3const nodePath = require("path");
4const fs = require("fs");
5const nodeRequire = require("./node-require");
6
7var compiler;
8var marko;
9var runtime;
10
11var modifiedId = 1;
12var HOT_RELOAD_KEY = Symbol("HOT_RELOAD");
13
14function cleaResolvePathCache() {
15 var modulePathCache = require("module").Module._pathCache;
16 if (!modulePathCache) {
17 console.log(
18 '[marko/hot-reload] WARNING: Missing: require("module").Module._pathCache [' +
19 __filename +
20 "]"
21 );
22 return;
23 }
24
25 var keys = Object.keys(modulePathCache);
26 keys.forEach(function(key) {
27 delete modulePathCache[key];
28 });
29}
30
31function tryReloadTemplate(path) {
32 path = path.replace(/\.js$/, "");
33
34 try {
35 if (require.extensions[".marko"]) {
36 return require(path);
37 }
38
39 return marko.load(path);
40 } catch (e) {
41 return undefined;
42 }
43}
44
45exports.enable = function(options) {
46 if (runtime.__hotReloadEnabled) {
47 // Marko has already been monkey-patched. Nothing to do!
48 return;
49 }
50
51 options = options || {};
52 var silent = options.silent || false;
53
54 runtime.__hotReloadEnabled = true;
55
56 // We set an environment variable so that _all_ marko modules
57 // installed in the project will have hot reload enabled.
58 process.env.MARKO_HOT_RELOAD = "true";
59
60 function createHotReloadProxy(func, template, methodName) {
61 var hotReloadData = template[HOT_RELOAD_KEY];
62 if (!hotReloadData) {
63 hotReloadData = template[HOT_RELOAD_KEY] = {
64 modifiedId: modifiedId,
65 latest: template,
66 originals: {}
67 };
68 }
69
70 hotReloadData.originals[methodName] = func;
71
72 var templatePath = template.path;
73
74 function hotReloadProxy() {
75 if (hotReloadData.modifiedId !== modifiedId) {
76 hotReloadData.modifiedId = modifiedId;
77 hotReloadData.latest = tryReloadTemplate(templatePath) || template;
78
79 if (hotReloadData.latest !== template) {
80 template.meta = hotReloadData.latest.meta;
81 if (!silent) {
82 console.log(
83 "[marko/hot-reload] Template successfully reloaded: " +
84 templatePath
85 );
86 }
87 }
88 }
89
90 var latest = hotReloadData.latest;
91 var originals =
92 latest[HOT_RELOAD_KEY] && latest[HOT_RELOAD_KEY].originals;
93 if (!originals) {
94 originals = latest;
95 }
96
97 var targetFunc = originals._;
98 return targetFunc.apply(latest, arguments);
99 }
100
101 return hotReloadProxy;
102 }
103
104 var oldCreateTemplate = runtime.t;
105
106 runtime.t = function hotReloadCreateTemplate() {
107 var originalTemplate = oldCreateTemplate.apply(runtime, arguments);
108 var actualRenderFunc;
109
110 Object.defineProperty(originalTemplate, "_", {
111 get: function() {
112 return actualRenderFunc;
113 },
114
115 set: function(renderFunc) {
116 actualRenderFunc = createHotReloadProxy(
117 renderFunc,
118 originalTemplate,
119 "_"
120 );
121 }
122 });
123
124 return originalTemplate;
125 };
126};
127
128/**
129 * Checks whether a path ends with a custom Marko extension
130 */
131function _endsWithMarkoExtension(path, requireExtensions) {
132 for (var i = 0; i < requireExtensions.length; i++) {
133 if (path.endsWith(requireExtensions[i])) {
134 return true;
135 }
136 }
137 return false;
138}
139
140function normalizeExtension(extension) {
141 if (extension.charAt(0) !== ".") {
142 extension = "." + extension;
143 }
144 return extension;
145}
146
147exports.handleFileModified = function(path, options) {
148 if (!fs.existsSync(path)) {
149 console.log(
150 "[marko/hot-reload] WARNING cannot resolve template path: ",
151 path
152 );
153 return;
154 }
155
156 options = options || {};
157 var silent = options.silent || false;
158
159 // Default hot-reloaded extensions
160 var requireExtensions = [".marko", ".marko.html", ".marko.xml"];
161
162 if (options.extension) {
163 requireExtensions.push(options.extension);
164 }
165
166 if (options.extensions) {
167 requireExtensions = requireExtensions.concat(options.extensions);
168 }
169
170 var nodeRequireExtensions = nodeRequire.getExtensions();
171 if (nodeRequireExtensions) {
172 requireExtensions = requireExtensions.concat(nodeRequireExtensions);
173 }
174
175 for (var i = 0; i < requireExtensions.length; i++) {
176 requireExtensions[i] = normalizeExtension(requireExtensions[i]);
177 }
178
179 var basename = nodePath.basename(path);
180
181 function handleFileModified() {
182 if (!silent) {
183 console.log("[marko/hot-reload] File modified: " + path);
184 }
185 runtime.cache = {};
186 compiler.clearCaches();
187 cleaResolvePathCache();
188 modifiedId++;
189 }
190
191 if (basename === "marko-tag.json" || basename === "marko.json") {
192 handleFileModified();
193 // If we taglib was modified then uncache *all* templates so that they will
194 // all be reloaded
195 Object.keys(require.cache).forEach(filename => {
196 if (filename.endsWith(".marko") || filename.endsWith(".marko.js")) {
197 delete require.cache[filename];
198 }
199 });
200 } else if (_endsWithMarkoExtension(path, requireExtensions)) {
201 handleFileModified();
202 delete require.cache[path];
203 delete require.cache[path + ".js"];
204 } else if (basename === "component.js") {
205 handleFileModified();
206 var dir = nodePath.dirname(path);
207 var templatePath = nodePath.join(dir, "index.marko");
208 delete require.cache[path];
209 delete require.cache[templatePath];
210 delete require.cache[templatePath + ".js"];
211 }
212};
213
214compiler = require("./compiler");
215marko = require("./");
216runtime = require("./runtime/html");