live-server-maintained 0.0.0-stage → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.editorconfig +16 -0
- package/.github/CONTRIBUTING.md +19 -0
- package/.github/ISSUE_TEMPLATE.md +11 -0
- package/.travis.yml +5 -0
- package/README.md +12 -2
- package/index.js +403 -0
- package/injected.html +31 -0
- package/live-server.js +174 -0
- package/middleware/example.js +5 -0
- package/middleware/spa-ignore-assets.js +14 -0
- package/middleware/spa.js +13 -0
- package/package.json +68 -4
- package/test/acceptance.js +67 -0
- package/test/cli.js +65 -0
- package/test/conf/https.conf.js +7 -0
- package/test/conf/server.cert +21 -0
- package/test/conf/server.key +30 -0
- package/test/cors.js +42 -0
- package/test/data/fragment.html +3 -0
- package/test/data/htpasswd-test +1 -0
- package/test/data/index-caps.htm +13 -0
- package/test/data/index-head.html +8 -0
- package/test/data/index.html +12 -0
- package/test/data/middleware.js +5 -0
- package/test/data/style.css +1 -0
- package/test/data/sub/sub.html +10 -0
- package/test/data/test.svg +14 -0
- package/test/htpasswd.js +28 -0
- package/test/https.js +48 -0
- package/test/middleware.js +43 -0
- package/test/mount.js +30 -0
- package/test/proxy.js +28 -0
- package/test/spa.js +40 -0
package/.editorconfig
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
root = true
|
|
2
|
+
|
|
3
|
+
[*]
|
|
4
|
+
indent_style = tab
|
|
5
|
+
indent_size = 4
|
|
6
|
+
end_of_line = lf
|
|
7
|
+
charset = utf-8
|
|
8
|
+
trim_trailing_whitespace = true
|
|
9
|
+
insert_final_newline = true
|
|
10
|
+
|
|
11
|
+
[*.md]
|
|
12
|
+
trim_trailing_whitespace = false
|
|
13
|
+
|
|
14
|
+
[{package,bower}.json]
|
|
15
|
+
indent_style = space
|
|
16
|
+
indent_size = 2
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Contributing Guidelines
|
|
2
|
+
=======================
|
|
3
|
+
|
|
4
|
+
When implementing a new feature or a bugfix, consider these points:
|
|
5
|
+
|
|
6
|
+
* Is this thing useful to other people, or is it just catering for an obscure corner case resulting from e.g. weirdness in personal dev environment?
|
|
7
|
+
* Should the thing be live-server's responsibility at all, or is it better served by other tools?
|
|
8
|
+
* Am I introducing unnecessary dependencies when the same thing could easily be done using normal JavaScript / node.js features?
|
|
9
|
+
* Does the naming (e.g. command line parameters) make sense and uses same style as other similar ones?
|
|
10
|
+
* Does my code adhere to the project's coding style (observable from surrounding code)?
|
|
11
|
+
* Can backwards compatibility be preserved?
|
|
12
|
+
|
|
13
|
+
A few guiding principles: keep the app simple and small, focusing on what it's meant to provide: live reloading development web server. Avoid extra dependencies and the need to do configuration when possible and it makes sense. Minimize bloat.
|
|
14
|
+
|
|
15
|
+
If you are adding a feature, think about if it could be an extenral middleware instead, possible bundled with `live-server` in its `middleware` folder.
|
|
16
|
+
|
|
17
|
+
**New features should come with test cases!**
|
|
18
|
+
|
|
19
|
+
**Run `npm test` to check that you are not introducing new bugs or style issues!**
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
###### Before submitting an issue, please, see https://github.com/tapio/live-server#troubleshooting
|
|
2
|
+
|
|
3
|
+
## Issue description
|
|
4
|
+
|
|
5
|
+
## Software details
|
|
6
|
+
|
|
7
|
+
* Command line used for launching `live-server`:
|
|
8
|
+
* OS:
|
|
9
|
+
* Browser (if browser related):
|
|
10
|
+
* Node.js version:
|
|
11
|
+
* `live-server` version:
|
package/.travis.yml
ADDED
package/README.md
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
-
#
|
|
1
|
+
# live-server-maintained
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
An updated version of [live-server](https://github.com/tapio/live-server) with refreshed dependencies.
|
|
4
|
+
|
|
5
|
+
The command is shortened from `live-server` to `lvm`:
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
lvm
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Thats all you need to know.
|
|
12
|
+
|
|
13
|
+
Original project created by [Tapio Vierros](https://github.com/tapio).
|
package/index.js
ADDED
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
var fs = require('fs'),
|
|
3
|
+
connect = require('connect'),
|
|
4
|
+
serveIndex = require('serve-index'),
|
|
5
|
+
logger = require('morgan'),
|
|
6
|
+
WebSocket = require('faye-websocket'),
|
|
7
|
+
path = require('path'),
|
|
8
|
+
url = require('url'),
|
|
9
|
+
http = require('http'),
|
|
10
|
+
send = require('send'),
|
|
11
|
+
open = require('opn'),
|
|
12
|
+
es = require("event-stream"),
|
|
13
|
+
os = require('os'),
|
|
14
|
+
chokidar = require('chokidar');
|
|
15
|
+
require('colors');
|
|
16
|
+
|
|
17
|
+
var INJECTED_CODE = fs.readFileSync(path.join(__dirname, "injected.html"), "utf8");
|
|
18
|
+
|
|
19
|
+
var LiveServer = {
|
|
20
|
+
server: null,
|
|
21
|
+
watcher: null,
|
|
22
|
+
logLevel: 2
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
function escape(html){
|
|
26
|
+
return String(html)
|
|
27
|
+
.replace(/&(?!\w+;)/g, '&')
|
|
28
|
+
.replace(/</g, '<')
|
|
29
|
+
.replace(/>/g, '>')
|
|
30
|
+
.replace(/"/g, '"');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Based on connect.static(), but streamlined and with added code injecter
|
|
34
|
+
function staticServer(root) {
|
|
35
|
+
var isFile = false;
|
|
36
|
+
try { // For supporting mounting files instead of just directories
|
|
37
|
+
isFile = fs.statSync(root).isFile();
|
|
38
|
+
} catch (e) {
|
|
39
|
+
if (e.code !== "ENOENT") throw e;
|
|
40
|
+
}
|
|
41
|
+
return function(req, res, next) {
|
|
42
|
+
if (req.method !== "GET" && req.method !== "HEAD") return next();
|
|
43
|
+
var reqpath = isFile ? "" : url.parse(req.url).pathname;
|
|
44
|
+
var hasNoOrigin = !req.headers.origin;
|
|
45
|
+
var injectCandidates = [ new RegExp("</body>", "i"), new RegExp("</svg>"), new RegExp("</head>", "i")];
|
|
46
|
+
var injectTag = null;
|
|
47
|
+
|
|
48
|
+
function directory() {
|
|
49
|
+
var pathname = url.parse(req.originalUrl).pathname;
|
|
50
|
+
res.statusCode = 301;
|
|
51
|
+
res.setHeader('Location', pathname + '/');
|
|
52
|
+
res.end('Redirecting to ' + escape(pathname) + '/');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function file(filepath /*, stat*/) {
|
|
56
|
+
var x = path.extname(filepath).toLocaleLowerCase(), match,
|
|
57
|
+
possibleExtensions = [ "", ".html", ".htm", ".xhtml", ".php", ".svg" ];
|
|
58
|
+
if (hasNoOrigin && (possibleExtensions.indexOf(x) > -1)) {
|
|
59
|
+
// TODO: Sync file read here is not nice, but we need to determine if the html should be injected or not
|
|
60
|
+
var contents = fs.readFileSync(filepath, "utf8");
|
|
61
|
+
for (var i = 0; i < injectCandidates.length; ++i) {
|
|
62
|
+
match = injectCandidates[i].exec(contents);
|
|
63
|
+
if (match) {
|
|
64
|
+
injectTag = match[0];
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if (injectTag === null && LiveServer.logLevel >= 3) {
|
|
69
|
+
console.warn("Failed to inject refresh script!".yellow,
|
|
70
|
+
"Couldn't find any of the tags ", injectCandidates, "from", filepath);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function error(err) {
|
|
76
|
+
if (err.status === 404) return next();
|
|
77
|
+
next(err);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function inject(stream) {
|
|
81
|
+
if (injectTag) {
|
|
82
|
+
// We need to modify the length given to browser
|
|
83
|
+
var len = INJECTED_CODE.length + res.getHeader('Content-Length');
|
|
84
|
+
res.setHeader('Content-Length', len);
|
|
85
|
+
var originalPipe = stream.pipe;
|
|
86
|
+
stream.pipe = function(resp) {
|
|
87
|
+
originalPipe.call(stream, es.replace(new RegExp(injectTag, "i"), INJECTED_CODE + injectTag)).pipe(resp);
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
send(req, reqpath, { root: root })
|
|
93
|
+
.on('error', error)
|
|
94
|
+
.on('directory', directory)
|
|
95
|
+
.on('file', file)
|
|
96
|
+
.on('stream', inject)
|
|
97
|
+
.pipe(res);
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Rewrite request URL and pass it back to the static handler.
|
|
103
|
+
* @param staticHandler {function} Next handler
|
|
104
|
+
* @param file {string} Path to the entry point file
|
|
105
|
+
*/
|
|
106
|
+
function entryPoint(staticHandler, file) {
|
|
107
|
+
if (!file) return function(req, res, next) { next(); };
|
|
108
|
+
|
|
109
|
+
return function(req, res, next) {
|
|
110
|
+
req.url = "/" + file;
|
|
111
|
+
staticHandler(req, res, next);
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Start a live server with parameters given as an object
|
|
117
|
+
* @param host {string} Address to bind to (default: 0.0.0.0)
|
|
118
|
+
* @param port {number} Port number (default: 8080)
|
|
119
|
+
* @param root {string} Path to root directory (default: cwd)
|
|
120
|
+
* @param watch {array} Paths to exclusively watch for changes
|
|
121
|
+
* @param ignore {array} Paths to ignore when watching files for changes
|
|
122
|
+
* @param ignorePattern {regexp} Ignore files by RegExp
|
|
123
|
+
* @param noCssInject Don't inject CSS changes, just reload as with any other file change
|
|
124
|
+
* @param open {(string|string[])} Subpath(s) to open in browser, use false to suppress launch (default: server root)
|
|
125
|
+
* @param mount {array} Mount directories onto a route, e.g. [['/components', './node_modules']].
|
|
126
|
+
* @param logLevel {number} 0 = errors only, 1 = some, 2 = lots
|
|
127
|
+
* @param file {string} Path to the entry point file
|
|
128
|
+
* @param wait {number} Server will wait for all changes, before reloading
|
|
129
|
+
* @param htpasswd {string} Path to htpasswd file to enable HTTP Basic authentication
|
|
130
|
+
* @param middleware {array} Append middleware to stack, e.g. [function(req, res, next) { next(); }].
|
|
131
|
+
*/
|
|
132
|
+
LiveServer.start = function(options) {
|
|
133
|
+
options = options || {};
|
|
134
|
+
var host = options.host || '0.0.0.0';
|
|
135
|
+
var port = options.port !== undefined ? options.port : 8080; // 0 means random
|
|
136
|
+
var root = options.root || process.cwd();
|
|
137
|
+
var mount = options.mount || [];
|
|
138
|
+
var watchPaths = options.watch || [root];
|
|
139
|
+
LiveServer.logLevel = options.logLevel === undefined ? 2 : options.logLevel;
|
|
140
|
+
var openPath = (options.open === undefined || options.open === true) ?
|
|
141
|
+
"" : ((options.open === null || options.open === false) ? null : options.open);
|
|
142
|
+
if (options.noBrowser) openPath = null; // Backwards compatibility with 0.7.0
|
|
143
|
+
var file = options.file;
|
|
144
|
+
var staticServerHandler = staticServer(root);
|
|
145
|
+
var wait = options.wait === undefined ? 100 : options.wait;
|
|
146
|
+
var browser = options.browser || null;
|
|
147
|
+
var htpasswd = options.htpasswd || null;
|
|
148
|
+
var cors = options.cors || false;
|
|
149
|
+
var https = options.https || null;
|
|
150
|
+
var proxy = options.proxy || [];
|
|
151
|
+
var middleware = options.middleware || [];
|
|
152
|
+
var noCssInject = options.noCssInject;
|
|
153
|
+
var httpsModule = options.httpsModule;
|
|
154
|
+
|
|
155
|
+
if (httpsModule) {
|
|
156
|
+
try {
|
|
157
|
+
require.resolve(httpsModule);
|
|
158
|
+
} catch (e) {
|
|
159
|
+
console.error(("HTTPS module \"" + httpsModule + "\" you've provided was not found.").red);
|
|
160
|
+
console.error("Did you do", "\"npm install " + httpsModule + "\"?");
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
} else {
|
|
164
|
+
httpsModule = "https";
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Setup a web server
|
|
168
|
+
var app = connect();
|
|
169
|
+
|
|
170
|
+
// Add logger. Level 2 logs only errors
|
|
171
|
+
if (LiveServer.logLevel === 2) {
|
|
172
|
+
app.use(logger('dev', {
|
|
173
|
+
skip: function (req, res) { return res.statusCode < 400; }
|
|
174
|
+
}));
|
|
175
|
+
// Level 2 or above logs all requests
|
|
176
|
+
} else if (LiveServer.logLevel > 2) {
|
|
177
|
+
app.use(logger('dev'));
|
|
178
|
+
}
|
|
179
|
+
if (options.spa) {
|
|
180
|
+
middleware.push("spa");
|
|
181
|
+
}
|
|
182
|
+
// Add middleware
|
|
183
|
+
middleware.map(function(mw) {
|
|
184
|
+
if (typeof mw === "string") {
|
|
185
|
+
var ext = path.extname(mw).toLocaleLowerCase();
|
|
186
|
+
if (ext !== ".js") {
|
|
187
|
+
mw = require(path.join(__dirname, "middleware", mw + ".js"));
|
|
188
|
+
} else {
|
|
189
|
+
mw = require(mw);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
app.use(mw);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
// Use http-auth if configured
|
|
196
|
+
if (htpasswd !== null) {
|
|
197
|
+
var auth = require('http-auth');
|
|
198
|
+
var basic = auth.basic({
|
|
199
|
+
realm: "Please authorize",
|
|
200
|
+
file: htpasswd
|
|
201
|
+
});
|
|
202
|
+
app.use(function(req, res, next) {
|
|
203
|
+
basic.check(function() {
|
|
204
|
+
next();
|
|
205
|
+
})(req, res);
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
if (cors) {
|
|
209
|
+
app.use(require("cors")({
|
|
210
|
+
origin: true, // reflecting request origin
|
|
211
|
+
credentials: true // allowing requests with credentials
|
|
212
|
+
}));
|
|
213
|
+
}
|
|
214
|
+
mount.forEach(function(mountRule) {
|
|
215
|
+
var mountPath = path.resolve(process.cwd(), mountRule[1]);
|
|
216
|
+
if (!options.watch) // Auto add mount paths to wathing but only if exclusive path option is not given
|
|
217
|
+
watchPaths.push(mountPath);
|
|
218
|
+
app.use(mountRule[0], staticServer(mountPath));
|
|
219
|
+
if (LiveServer.logLevel >= 1)
|
|
220
|
+
console.log('Mapping %s to "%s"', mountRule[0], mountPath);
|
|
221
|
+
});
|
|
222
|
+
proxy.forEach(function(proxyRule) {
|
|
223
|
+
var proxyOpts = url.parse(proxyRule[1]);
|
|
224
|
+
proxyOpts.via = true;
|
|
225
|
+
proxyOpts.preserveHost = true;
|
|
226
|
+
app.use(proxyRule[0], require('proxy-middleware')(proxyOpts));
|
|
227
|
+
if (LiveServer.logLevel >= 1)
|
|
228
|
+
console.log('Mapping %s to "%s"', proxyRule[0], proxyRule[1]);
|
|
229
|
+
});
|
|
230
|
+
app.use(staticServerHandler) // Custom static server
|
|
231
|
+
.use(entryPoint(staticServerHandler, file))
|
|
232
|
+
.use(serveIndex(root, { icons: true }));
|
|
233
|
+
|
|
234
|
+
var server, protocol;
|
|
235
|
+
if (https !== null) {
|
|
236
|
+
var httpsConfig = https;
|
|
237
|
+
if (typeof https === "string") {
|
|
238
|
+
httpsConfig = require(path.resolve(process.cwd(), https));
|
|
239
|
+
}
|
|
240
|
+
server = require(httpsModule).createServer(httpsConfig, app);
|
|
241
|
+
protocol = "https";
|
|
242
|
+
} else {
|
|
243
|
+
server = http.createServer(app);
|
|
244
|
+
protocol = "http";
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Handle server startup errors
|
|
248
|
+
server.addListener('error', function(e) {
|
|
249
|
+
if (e.code === 'EADDRINUSE') {
|
|
250
|
+
var serveURL = protocol + '://' + host + ':' + port;
|
|
251
|
+
console.log('%s is already in use. Trying another port.'.yellow, serveURL);
|
|
252
|
+
setTimeout(function() {
|
|
253
|
+
server.listen(0, host);
|
|
254
|
+
}, 1000);
|
|
255
|
+
} else {
|
|
256
|
+
console.error(e.toString().red);
|
|
257
|
+
LiveServer.shutdown();
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
// Handle successful server
|
|
262
|
+
server.addListener('listening', function(/*e*/) {
|
|
263
|
+
LiveServer.server = server;
|
|
264
|
+
|
|
265
|
+
var address = server.address();
|
|
266
|
+
var serveHost = address.address === "0.0.0.0" ? "127.0.0.1" : address.address;
|
|
267
|
+
var openHost = host === "0.0.0.0" ? "127.0.0.1" : host;
|
|
268
|
+
|
|
269
|
+
var serveURL = protocol + '://' + serveHost + ':' + address.port;
|
|
270
|
+
var openURL = protocol + '://' + openHost + ':' + address.port;
|
|
271
|
+
|
|
272
|
+
var serveURLs = [ serveURL ];
|
|
273
|
+
if (LiveServer.logLevel > 2 && address.address === "0.0.0.0") {
|
|
274
|
+
var ifaces = os.networkInterfaces();
|
|
275
|
+
serveURLs = Object.keys(ifaces)
|
|
276
|
+
.map(function(iface) {
|
|
277
|
+
return ifaces[iface];
|
|
278
|
+
})
|
|
279
|
+
// flatten address data, use only IPv4
|
|
280
|
+
.reduce(function(data, addresses) {
|
|
281
|
+
addresses.filter(function(addr) {
|
|
282
|
+
return addr.family === "IPv4";
|
|
283
|
+
}).forEach(function(addr) {
|
|
284
|
+
data.push(addr);
|
|
285
|
+
});
|
|
286
|
+
return data;
|
|
287
|
+
}, [])
|
|
288
|
+
.map(function(addr) {
|
|
289
|
+
return protocol + "://" + addr.address + ":" + address.port;
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Output
|
|
294
|
+
if (LiveServer.logLevel >= 1) {
|
|
295
|
+
if (serveURL === openURL)
|
|
296
|
+
if (serveURLs.length === 1) {
|
|
297
|
+
console.log(("Serving \"%s\" at %s").green, root, serveURLs[0]);
|
|
298
|
+
} else {
|
|
299
|
+
console.log(("Serving \"%s\" at\n\t%s").green, root, serveURLs.join("\n\t"));
|
|
300
|
+
}
|
|
301
|
+
else
|
|
302
|
+
console.log(("Serving \"%s\" at %s (%s)").green, root, openURL, serveURL);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Launch browser
|
|
306
|
+
if (openPath !== null)
|
|
307
|
+
if (typeof openPath === "object") {
|
|
308
|
+
openPath.forEach(function(p) {
|
|
309
|
+
open(openURL + p, {app: browser});
|
|
310
|
+
});
|
|
311
|
+
} else {
|
|
312
|
+
open(openURL + openPath, {app: browser});
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
// Setup server to listen at port
|
|
317
|
+
server.listen(port, host);
|
|
318
|
+
|
|
319
|
+
// WebSocket
|
|
320
|
+
var clients = [];
|
|
321
|
+
server.addListener('upgrade', function(request, socket, head) {
|
|
322
|
+
var ws = new WebSocket(request, socket, head);
|
|
323
|
+
ws.onopen = function() { ws.send('connected'); };
|
|
324
|
+
|
|
325
|
+
if (wait > 0) {
|
|
326
|
+
(function() {
|
|
327
|
+
var wssend = ws.send;
|
|
328
|
+
var waitTimeout;
|
|
329
|
+
ws.send = function() {
|
|
330
|
+
var args = arguments;
|
|
331
|
+
if (waitTimeout) clearTimeout(waitTimeout);
|
|
332
|
+
waitTimeout = setTimeout(function(){
|
|
333
|
+
wssend.apply(ws, args);
|
|
334
|
+
}, wait);
|
|
335
|
+
};
|
|
336
|
+
})();
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
ws.onclose = function() {
|
|
340
|
+
clients = clients.filter(function (x) {
|
|
341
|
+
return x !== ws;
|
|
342
|
+
});
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
clients.push(ws);
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
var ignored = [
|
|
349
|
+
function(testPath) { // Always ignore dotfiles (important e.g. because editor hidden temp files)
|
|
350
|
+
return testPath !== "." && /(^[.#]|(?:__|~)$)/.test(path.basename(testPath));
|
|
351
|
+
}
|
|
352
|
+
];
|
|
353
|
+
if (options.ignore) {
|
|
354
|
+
ignored = ignored.concat(options.ignore);
|
|
355
|
+
}
|
|
356
|
+
if (options.ignorePattern) {
|
|
357
|
+
ignored.push(options.ignorePattern);
|
|
358
|
+
}
|
|
359
|
+
// Setup file watcher
|
|
360
|
+
LiveServer.watcher = chokidar.watch(watchPaths, {
|
|
361
|
+
ignored: ignored,
|
|
362
|
+
ignoreInitial: true
|
|
363
|
+
});
|
|
364
|
+
function handleChange(changePath) {
|
|
365
|
+
var cssChange = path.extname(changePath) === ".css" && !noCssInject;
|
|
366
|
+
if (LiveServer.logLevel >= 1) {
|
|
367
|
+
if (cssChange)
|
|
368
|
+
console.log("CSS change detected".magenta, changePath);
|
|
369
|
+
else console.log("Change detected".cyan, changePath);
|
|
370
|
+
}
|
|
371
|
+
clients.forEach(function(ws) {
|
|
372
|
+
if (ws)
|
|
373
|
+
ws.send(cssChange ? 'refreshcss' : 'reload');
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
LiveServer.watcher
|
|
377
|
+
.on("change", handleChange)
|
|
378
|
+
.on("add", handleChange)
|
|
379
|
+
.on("unlink", handleChange)
|
|
380
|
+
.on("addDir", handleChange)
|
|
381
|
+
.on("unlinkDir", handleChange)
|
|
382
|
+
.on("ready", function () {
|
|
383
|
+
if (LiveServer.logLevel >= 1)
|
|
384
|
+
console.log("Ready for changes".cyan);
|
|
385
|
+
})
|
|
386
|
+
.on("error", function (err) {
|
|
387
|
+
console.log("ERROR:".red, err);
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
return server;
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
LiveServer.shutdown = function() {
|
|
394
|
+
var watcher = LiveServer.watcher;
|
|
395
|
+
if (watcher) {
|
|
396
|
+
watcher.close();
|
|
397
|
+
}
|
|
398
|
+
var server = LiveServer.server;
|
|
399
|
+
if (server)
|
|
400
|
+
server.close();
|
|
401
|
+
};
|
|
402
|
+
|
|
403
|
+
module.exports = LiveServer;
|
package/injected.html
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
<!-- Code injected by live-server -->
|
|
2
|
+
<script type="text/javascript">
|
|
3
|
+
// <![CDATA[ <-- For SVG support
|
|
4
|
+
if ('WebSocket' in window) {
|
|
5
|
+
(function() {
|
|
6
|
+
function refreshCSS() {
|
|
7
|
+
var sheets = [].slice.call(document.getElementsByTagName("link"));
|
|
8
|
+
var head = document.getElementsByTagName("head")[0];
|
|
9
|
+
for (var i = 0; i < sheets.length; ++i) {
|
|
10
|
+
var elem = sheets[i];
|
|
11
|
+
head.removeChild(elem);
|
|
12
|
+
var rel = elem.rel;
|
|
13
|
+
if (elem.href && typeof rel != "string" || rel.length == 0 || rel.toLowerCase() == "stylesheet") {
|
|
14
|
+
var url = elem.href.replace(/(&|\?)_cacheOverride=\d+/, '');
|
|
15
|
+
elem.href = url + (url.indexOf('?') >= 0 ? '&' : '?') + '_cacheOverride=' + (new Date().valueOf());
|
|
16
|
+
}
|
|
17
|
+
head.appendChild(elem);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
var protocol = window.location.protocol === 'http:' ? 'ws://' : 'wss://';
|
|
21
|
+
var address = protocol + window.location.host + window.location.pathname + '/ws';
|
|
22
|
+
var socket = new WebSocket(address);
|
|
23
|
+
socket.onmessage = function(msg) {
|
|
24
|
+
if (msg.data == 'reload') window.location.reload();
|
|
25
|
+
else if (msg.data == 'refreshcss') refreshCSS();
|
|
26
|
+
};
|
|
27
|
+
console.log('Live reload enabled.');
|
|
28
|
+
})();
|
|
29
|
+
}
|
|
30
|
+
// ]]>
|
|
31
|
+
</script>
|
package/live-server.js
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
var path = require('path');
|
|
3
|
+
var fs = require('fs');
|
|
4
|
+
var assign = require('object-assign');
|
|
5
|
+
var liveServer = require("./index");
|
|
6
|
+
|
|
7
|
+
var opts = {
|
|
8
|
+
host: process.env.IP,
|
|
9
|
+
port: process.env.PORT,
|
|
10
|
+
open: true,
|
|
11
|
+
mount: [],
|
|
12
|
+
proxy: [],
|
|
13
|
+
middleware: [],
|
|
14
|
+
logLevel: 2,
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
var homeDir = process.env[(process.platform === 'win32') ? 'USERPROFILE' : 'HOME'];
|
|
18
|
+
var configPath = path.join(homeDir, '.live-server.json');
|
|
19
|
+
if (fs.existsSync(configPath)) {
|
|
20
|
+
var userConfig = fs.readFileSync(configPath, 'utf8');
|
|
21
|
+
assign(opts, JSON.parse(userConfig));
|
|
22
|
+
if (opts.ignorePattern) opts.ignorePattern = new RegExp(opts.ignorePattern);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
for (var i = process.argv.length - 1; i >= 2; --i) {
|
|
26
|
+
var arg = process.argv[i];
|
|
27
|
+
if (arg.indexOf("--port=") > -1) {
|
|
28
|
+
var portString = arg.substring(7);
|
|
29
|
+
var portNumber = parseInt(portString, 10);
|
|
30
|
+
if (portNumber === +portString) {
|
|
31
|
+
opts.port = portNumber;
|
|
32
|
+
process.argv.splice(i, 1);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
else if (arg.indexOf("--host=") > -1) {
|
|
36
|
+
opts.host = arg.substring(7);
|
|
37
|
+
process.argv.splice(i, 1);
|
|
38
|
+
}
|
|
39
|
+
else if (arg.indexOf("--open=") > -1) {
|
|
40
|
+
var open = arg.substring(7);
|
|
41
|
+
if (open.indexOf('/') !== 0) {
|
|
42
|
+
open = '/' + open;
|
|
43
|
+
}
|
|
44
|
+
switch (typeof opts.open) {
|
|
45
|
+
case "boolean":
|
|
46
|
+
opts.open = open;
|
|
47
|
+
break;
|
|
48
|
+
case "string":
|
|
49
|
+
opts.open = [opts.open, open];
|
|
50
|
+
break;
|
|
51
|
+
case "object":
|
|
52
|
+
opts.open.push(open);
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
process.argv.splice(i, 1);
|
|
56
|
+
}
|
|
57
|
+
else if (arg.indexOf("--watch=") > -1) {
|
|
58
|
+
// Will be modified later when cwd is known
|
|
59
|
+
opts.watch = arg.substring(8).split(",");
|
|
60
|
+
process.argv.splice(i, 1);
|
|
61
|
+
}
|
|
62
|
+
else if (arg.indexOf("--ignore=") > -1) {
|
|
63
|
+
// Will be modified later when cwd is known
|
|
64
|
+
opts.ignore = arg.substring(9).split(",");
|
|
65
|
+
process.argv.splice(i, 1);
|
|
66
|
+
}
|
|
67
|
+
else if (arg.indexOf("--ignorePattern=") > -1) {
|
|
68
|
+
opts.ignorePattern = new RegExp(arg.substring(16));
|
|
69
|
+
process.argv.splice(i, 1);
|
|
70
|
+
}
|
|
71
|
+
else if (arg === "--no-css-inject") {
|
|
72
|
+
opts.noCssInject = true;
|
|
73
|
+
process.argv.splice(i, 1);
|
|
74
|
+
}
|
|
75
|
+
else if (arg === "--no-browser") {
|
|
76
|
+
opts.open = false;
|
|
77
|
+
process.argv.splice(i, 1);
|
|
78
|
+
}
|
|
79
|
+
else if (arg.indexOf("--browser=") > -1) {
|
|
80
|
+
opts.browser = arg.substring(10).split(",");
|
|
81
|
+
process.argv.splice(i, 1);
|
|
82
|
+
}
|
|
83
|
+
else if (arg.indexOf("--entry-file=") > -1) {
|
|
84
|
+
var file = arg.substring(13);
|
|
85
|
+
if (file.length) {
|
|
86
|
+
opts.file = file;
|
|
87
|
+
process.argv.splice(i, 1);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
else if (arg === "--spa") {
|
|
91
|
+
opts.middleware.push("spa");
|
|
92
|
+
process.argv.splice(i, 1);
|
|
93
|
+
}
|
|
94
|
+
else if (arg === "--quiet" || arg === "-q") {
|
|
95
|
+
opts.logLevel = 0;
|
|
96
|
+
process.argv.splice(i, 1);
|
|
97
|
+
}
|
|
98
|
+
else if (arg === "--verbose" || arg === "-V") {
|
|
99
|
+
opts.logLevel = 3;
|
|
100
|
+
process.argv.splice(i, 1);
|
|
101
|
+
}
|
|
102
|
+
else if (arg.indexOf("--mount=") > -1) {
|
|
103
|
+
// e.g. "--mount=/components:./node_modules" will be ['/components', '<process.cwd()>/node_modules']
|
|
104
|
+
// split only on the first ":", as the path may contain ":" as well (e.g. C:\file.txt)
|
|
105
|
+
var match = arg.substring(8).match(/([^:]+):(.+)$/);
|
|
106
|
+
match[2] = path.resolve(process.cwd(), match[2]);
|
|
107
|
+
opts.mount.push([ match[1], match[2] ]);
|
|
108
|
+
process.argv.splice(i, 1);
|
|
109
|
+
}
|
|
110
|
+
else if (arg.indexOf("--wait=") > -1) {
|
|
111
|
+
var waitString = arg.substring(7);
|
|
112
|
+
var waitNumber = parseInt(waitString, 10);
|
|
113
|
+
if (waitNumber === +waitString) {
|
|
114
|
+
opts.wait = waitNumber;
|
|
115
|
+
process.argv.splice(i, 1);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
else if (arg === "--version" || arg === "-v") {
|
|
119
|
+
var packageJson = require('./package.json');
|
|
120
|
+
console.log(packageJson.name, packageJson.version);
|
|
121
|
+
process.exit();
|
|
122
|
+
}
|
|
123
|
+
else if (arg.indexOf("--htpasswd=") > -1) {
|
|
124
|
+
opts.htpasswd = arg.substring(11);
|
|
125
|
+
process.argv.splice(i, 1);
|
|
126
|
+
}
|
|
127
|
+
else if (arg === "--cors") {
|
|
128
|
+
opts.cors = true;
|
|
129
|
+
process.argv.splice(i, 1);
|
|
130
|
+
}
|
|
131
|
+
else if (arg.indexOf("--https=") > -1) {
|
|
132
|
+
opts.https = arg.substring(8);
|
|
133
|
+
process.argv.splice(i, 1);
|
|
134
|
+
}
|
|
135
|
+
else if (arg.indexOf("--https-module=") > -1) {
|
|
136
|
+
opts.httpsModule = arg.substring(15);
|
|
137
|
+
process.argv.splice(i, 1);
|
|
138
|
+
}
|
|
139
|
+
else if (arg.indexOf("--proxy=") > -1) {
|
|
140
|
+
// split only on the first ":", as the URL will contain ":" as well
|
|
141
|
+
var match = arg.substring(8).match(/([^:]+):(.+)$/);
|
|
142
|
+
opts.proxy.push([ match[1], match[2] ]);
|
|
143
|
+
process.argv.splice(i, 1);
|
|
144
|
+
}
|
|
145
|
+
else if (arg.indexOf("--middleware=") > -1) {
|
|
146
|
+
opts.middleware.push(arg.substring(13));
|
|
147
|
+
process.argv.splice(i, 1);
|
|
148
|
+
}
|
|
149
|
+
else if (arg === "--help" || arg === "-h") {
|
|
150
|
+
console.log('Usage: live-server [-v|--version] [-h|--help] [-q|--quiet] [--port=PORT] [--host=HOST] [--open=PATH] [--no-browser] [--browser=BROWSER] [--ignore=PATH] [--ignorePattern=RGXP] [--no-css-inject] [--entry-file=PATH] [--spa] [--mount=ROUTE:PATH] [--wait=MILLISECONDS] [--htpasswd=PATH] [--cors] [--https=PATH] [--https-module=MODULE_NAME] [--proxy=PATH] [PATH]');
|
|
151
|
+
process.exit();
|
|
152
|
+
}
|
|
153
|
+
else if (arg === "--test") {
|
|
154
|
+
// Hidden param for tests to exit automatically
|
|
155
|
+
setTimeout(liveServer.shutdown, 500);
|
|
156
|
+
process.argv.splice(i, 1);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Patch paths
|
|
161
|
+
var dir = opts.root = process.argv[2] || "";
|
|
162
|
+
|
|
163
|
+
if (opts.watch) {
|
|
164
|
+
opts.watch = opts.watch.map(function(relativePath) {
|
|
165
|
+
return path.join(dir, relativePath);
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
if (opts.ignore) {
|
|
169
|
+
opts.ignore = opts.ignore.map(function(relativePath) {
|
|
170
|
+
return path.join(dir, relativePath);
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
liveServer.start(opts);
|