markserv 1.17.4 → 1.19.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/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
  <img alt="Markserv Logo" src="https://markserv.github.io/markserv/media/markserv-readme-banner.svg">
3
3
  </p>
4
4
 
5
- > :checkered_flag: serve markdown as html (GitHub style), index directories, and live-reload as you edit
5
+ > :checkered_flag: serve markdown as html (GitHub style), index directories, and hot-reload as you edit
6
6
 
7
7
  [![Gitter](https://img.shields.io/badge/gitter-join%20chat%20%E2%86%92-FF69B4.svg?style=flat-square&logo=gitter-white&link=https://gitter.im/markserv)](https://gitter.im/markserv)
8
8
  [![CHANGELOG](https://img.shields.io/badge/changelog-&nbsp;✔️-orange.svg?style=flat-square)](CHANGELOG.md)
@@ -20,7 +20,7 @@
20
20
  - Markdown content rendered as HTML
21
21
  - GitHub flavor CSS and Syntax Highlighting
22
22
  - [Just in Time Templating](#stopwatch-just-in-time-templating): Markdown, HTML &amp; LESS
23
- - LiveReload as you edit
23
+ - Hot-reload as you edit (no browser plugin required)
24
24
  - Directory indexes
25
25
  - MIME Type file support
26
26
 
@@ -75,20 +75,37 @@ Start Markserv and open the closest README.md file in the browser:
75
75
  $ readme
76
76
  ```
77
77
 
78
- ## :zap: Live Reload
78
+ ## :zap: Hot Reload
79
79
 
80
- To see real-time updates as you save your markdown files, you will need to install the LiveReload plugin for your browser:
80
+ Markserv includes built-in hot-reload via WebSocket. When you save a Markdown file, the page content updates instantly without a full page reload and without any browser plugin.
81
81
 
82
- - [Chrome](https://chrome.google.com/webstore/detail/livereload/jnihajbhpnppcggbcgedagnkighmdlei?hl=en)
83
- - [Firefox](https://addons.mozilla.org/addon/livereload-web-extension/)
84
- - [Internet Explorer](https://github.com/dvdotsenko/livereload_ie_extension)
82
+ To disable hot-reload, use `--no-hotreload`:
85
83
 
86
- With the Live Reload plugin installed and turned on, you should see the page reloading as you save your Markdown file.
84
+ ```shell
85
+ markserv --no-hotreload README.md
86
+ ```
87
87
 
88
88
  <p align="center">
89
89
  <img alt="Markserv Live Reload" src="media/markserv-live-reload.gif" width="100%">
90
90
  </p>
91
91
 
92
+ ## :art: Themes
93
+
94
+ Markserv ships with four themes: **dark** (default), **light**, **synthwave**, and **solarized**. You can set the theme from the CLI:
95
+
96
+ ```shell
97
+ # Use light theme
98
+ markserv --light README.md
99
+
100
+ # Use synthwave theme
101
+ markserv --synthwave README.md
102
+
103
+ # Or use the --theme flag for any theme
104
+ markserv --theme solarized README.md
105
+ ```
106
+
107
+ You can also toggle between themes in the browser using the theme button in the bottom-right corner. Your choice is saved in `localStorage` and persists across hot-reloads.
108
+
92
109
  ## :link: Markdown Links
93
110
 
94
111
  You can link to an external Markdown file in the same way that you use GitHub Wiki links. You can use the example code here to see how external links work.
@@ -105,7 +122,13 @@ Example link:
105
122
 
106
123
  ## :stopwatch: Just in Time Templating
107
124
 
108
- Markserv allows you to include nested content. Templates are fetched and rendered when you request them in your browser. The `maxDepth` of includes is set to `10`.
125
+ Markserv allows you to include nested content using `--templates`. Templates are fetched and rendered when you request them in your browser. The `maxDepth` of includes is set to `10`.
126
+
127
+ **Note:** Templating is disabled by default. Enable it with `--templates`:
128
+
129
+ ```shell
130
+ markserv --templates README.md
131
+ ```
109
132
 
110
133
  If you would like to look at an example, you can look in the [tests/templates](tests/templates/) directory of this repo.
111
134
 
@@ -114,7 +137,7 @@ To see the server output of this templating example:
114
137
  ```shell
115
138
  $ git clone@github.com/f1lt3r/markserv.git
116
139
  $ cd markserv
117
- $ markserv tests/templates/index.html
140
+ $ markserv --templates tests/templates/index.html
118
141
  ```
119
142
 
120
143
  ### Include Markdown
package/lib/cli-defs.js CHANGED
@@ -5,9 +5,10 @@ module.exports = {
5
5
  default: '8642'
6
6
  },
7
7
 
8
- livereloadport: {
9
- alias: 'b',
10
- default: 35729
8
+ hotreload: {
9
+ alias: 'l',
10
+ type: 'boolean',
11
+ default: true
11
12
  },
12
13
 
13
14
  address: {
@@ -23,6 +24,25 @@ module.exports = {
23
24
  verbose: {
24
25
  alias: 'v',
25
26
  default: false
27
+ },
28
+
29
+ theme: {
30
+ default: 'dark'
31
+ },
32
+
33
+ light: {
34
+ type: 'boolean',
35
+ default: false
36
+ },
37
+
38
+ synthwave: {
39
+ type: 'boolean',
40
+ default: false
41
+ },
42
+
43
+ templates: {
44
+ type: 'boolean',
45
+ default: false
26
46
  }
27
47
  }
28
48
  }
package/lib/cli-help.txt CHANGED
@@ -3,9 +3,12 @@ $ markserv <file/dir>
3
3
  $ readme (serve closest README.md file)
4
4
 
5
5
  Options
6
- --port, -p, HTTP port [port] (80)
7
- --livereloadport, -l LiveReload port [port/false] (35729)
6
+ --port, -p, HTTP port [port] (8642)
7
+ --hotreload, -l Hot-reload via WebSocket (true), disable with --no-hotreload
8
8
  --browser, -b Launch browser (true)
9
9
  --silent, -i Silent (false)
10
10
  --address, -a Serve on ip/address [address] (localhost)
11
- --verbose, -v Verbose output (false)
11
+ --verbose, -v Verbose output (false)
12
+ --light Use light theme (default: dark)
13
+ --synthwave Use synthwave/retro-neon theme (default: dark)
14
+ --templates Enable {file:}, {markdown:}, {html:}, {less:} templating (false)
package/lib/cli.js CHANGED
@@ -14,21 +14,7 @@ const cliDefs = require('./cli-defs')
14
14
  const cliOpts = meow(cliHelp, cliDefs)
15
15
 
16
16
  const validateServerPath = (serverPath, cwd) => {
17
- if (serverPath === cwd) {
18
- return cwd
19
- }
20
-
21
- let validatedPath
22
-
23
- if (serverPath[0]) {
24
- validatedPath = path.normalize(path.join(cwd, serverPath))
25
- }
26
-
27
- if (serverPath[0] === '/' || serverPath[0] === '.') {
28
- validatedPath = path.normalize(path.join(cwd, serverPath))
29
- }
30
-
31
- return validatedPath
17
+ return path.resolve(cwd, serverPath)
32
18
  }
33
19
 
34
20
  const run = opts => {
package/lib/server.js CHANGED
@@ -10,8 +10,8 @@ const Promise = require('bluebird')
10
10
  const connect = require('connect')
11
11
  const less = require('less')
12
12
  const send = require('send')
13
- const liveReload = require('livereload')
14
- const connectLiveReload = require('connect-livereload')
13
+ const WebSocket = require('ws')
14
+ const getPort = require('get-port')
15
15
  const implant = require('implant')
16
16
  const deepmerge = require('deepmerge')
17
17
  const handlebars = require('handlebars')
@@ -166,12 +166,12 @@ const getFile = path => new Promise((resolve, reject) => {
166
166
 
167
167
  // Get Custom Less CSS to use in all Markdown files
168
168
  const buildLessStyleSheet = cssPath =>
169
- new Promise(resolve =>
169
+ new Promise((resolve, reject) =>
170
170
  getFile(cssPath).then(data =>
171
171
  less.render(data).then(data =>
172
172
  resolve(data.css)
173
173
  )
174
- )
174
+ ).catch(reject)
175
175
  )
176
176
 
177
177
  const baseTemplate = (templateUrl, handlebarData) => new Promise((resolve, reject) => {
@@ -296,6 +296,22 @@ const createBreadcrumbs = path => {
296
296
  }
297
297
 
298
298
  // Http_request_handler: handles all the browser requests
299
+ const resolveTheme = flags => {
300
+ if (flags.light) {
301
+ return 'light'
302
+ }
303
+
304
+ if (flags.synthwave) {
305
+ return 'synthwave'
306
+ }
307
+
308
+ if (flags.theme && flags.theme !== 'dark') {
309
+ return flags.theme
310
+ }
311
+
312
+ return 'dark'
313
+ }
314
+
299
315
  const createRequestHandler = flags => {
300
316
  let {dir} = flags
301
317
  const isDir = fs.statSync(dir).isDirectory()
@@ -304,6 +320,13 @@ const createRequestHandler = flags => {
304
320
  }
305
321
 
306
322
  flags.$openLocation = path.relative(dir, flags.dir)
323
+ const theme = resolveTheme(flags)
324
+ const themeFlags = {
325
+ themeDark: theme === 'dark',
326
+ themeLight: theme === 'light',
327
+ themeSynthwave: theme === 'synthwave',
328
+ themeSolarized: theme === 'solarized'
329
+ }
307
330
 
308
331
  const implantOpts = {
309
332
  maxDepth: 10
@@ -320,6 +343,10 @@ const createRequestHandler = flags => {
320
343
  }),
321
344
 
322
345
  file: (url, opts) => new Promise(resolve => {
346
+ if (typeof url !== 'string' || typeof (opts && opts.baseDir) !== 'string') {
347
+ return resolve(false)
348
+ }
349
+
323
350
  const absUrl = path.join(opts.baseDir, url)
324
351
  getFile(absUrl)
325
352
  .then(data => {
@@ -333,6 +360,10 @@ const createRequestHandler = flags => {
333
360
  }),
334
361
 
335
362
  less: (url, opts) => new Promise(resolve => {
363
+ if (typeof url !== 'string' || typeof (opts && opts.baseDir) !== 'string') {
364
+ return resolve(false)
365
+ }
366
+
336
367
  const absUrl = path.join(opts.baseDir, url)
337
368
  buildLessStyleSheet(absUrl)
338
369
  .then(data => {
@@ -346,6 +377,10 @@ const createRequestHandler = flags => {
346
377
  }),
347
378
 
348
379
  markdown: (url, opts) => new Promise(resolve => {
380
+ if (typeof url !== 'string' || typeof (opts && opts.baseDir) !== 'string') {
381
+ return resolve(false)
382
+ }
383
+
349
384
  const absUrl = path.join(opts.baseDir, url)
350
385
  getFile(absUrl).then(markdownToHTML)
351
386
  .then(data => {
@@ -359,6 +394,10 @@ const createRequestHandler = flags => {
359
394
  }),
360
395
 
361
396
  html: (url, opts) => new Promise(resolve => {
397
+ if (typeof url !== 'string' || typeof (opts && opts.baseDir) !== 'string') {
398
+ return resolve(false)
399
+ }
400
+
362
401
  const absUrl = path.join(opts.baseDir, url)
363
402
  getFile(absUrl)
364
403
  .then(data => {
@@ -396,7 +435,12 @@ const createRequestHandler = flags => {
396
435
  filePath,
397
436
  errorMsg,
398
437
  errorStack,
399
- referer
438
+ referer,
439
+ theme,
440
+ ...themeFlags,
441
+ hotreload: flags.$hotreload,
442
+ wsPort: flags.$wsPort,
443
+ rootDir: flags.dir
400
444
  }
401
445
 
402
446
  return baseTemplate(templateUrl, handlebarData).then(final => {
@@ -455,26 +499,42 @@ const createRequestHandler = flags => {
455
499
  if (isMarkdown) {
456
500
  msg('markdown', style.link(prettyPath), flags)
457
501
  getFile(filePath).then(markdownToHTML).then(filePath).then(html => {
458
- return implant(html, implantHandlers, implantOpts).then(output => {
502
+ const contentPromise = flags.templates ?
503
+ implant(html, implantHandlers, implantOpts) :
504
+ Promise.resolve(html)
505
+
506
+ return contentPromise.then(output => {
459
507
  const templateUrl = path.join(__dirname, 'templates/markdown.html')
460
508
 
461
509
  const handlebarData = {
462
510
  title: path.parse(filePath).base,
463
511
  content: output,
464
- pid: process.pid | 'N/A'
512
+ pid: process.pid | 'N/A',
513
+ theme,
514
+ ...themeFlags,
515
+ hotreload: flags.$hotreload,
516
+ wsPort: flags.$wsPort,
517
+ rootDir: flags.dir
465
518
  }
466
519
 
467
520
  return baseTemplate(templateUrl, handlebarData).then(final => {
468
- const lvl2Dir = path.parse(templateUrl).dir
469
- const lvl2Opts = deepmerge(implantOpts, {baseDir: lvl2Dir})
470
-
471
- return implant(final, implantHandlers, lvl2Opts)
472
- .then(output => {
473
- res.writeHead(200, {
474
- 'content-type': 'text/html'
521
+ if (flags.templates) {
522
+ const lvl2Dir = path.parse(templateUrl).dir
523
+ const lvl2Opts = deepmerge(implantOpts, {baseDir: lvl2Dir})
524
+
525
+ return implant(final, implantHandlers, lvl2Opts)
526
+ .then(output => {
527
+ res.writeHead(200, {
528
+ 'content-type': 'text/html'
529
+ })
530
+ res.end(output)
475
531
  })
476
- res.end(output)
477
- })
532
+ }
533
+
534
+ res.writeHead(200, {
535
+ 'content-type': 'text/html'
536
+ })
537
+ res.end(final)
478
538
  })
479
539
  })
480
540
  }).catch(error => {
@@ -483,7 +543,11 @@ const createRequestHandler = flags => {
483
543
  } else if (isHtml) {
484
544
  msg('html', style.link(prettyPath), flags)
485
545
  getFile(filePath).then(html => {
486
- return implant(html, implantHandlers, implantOpts).then(output => {
546
+ const contentPromise = flags.templates ?
547
+ implant(html, implantHandlers, implantOpts) :
548
+ Promise.resolve(html)
549
+
550
+ return contentPromise.then(output => {
487
551
  res.writeHead(200, {
488
552
  'content-type': 'text/html'
489
553
  })
@@ -504,20 +568,32 @@ const createRequestHandler = flags => {
504
568
  content: dirToHtml(filePath),
505
569
  title: path.parse(filePath).base,
506
570
  pid: process.pid | 'N/A',
507
- breadcrumbs: createBreadcrumbs(path.relative(dir, filePath))
571
+ breadcrumbs: createBreadcrumbs(path.relative(dir, filePath)),
572
+ theme,
573
+ ...themeFlags,
574
+ hotreload: flags.$hotreload,
575
+ wsPort: flags.$wsPort,
576
+ rootDir: flags.dir
508
577
  }
509
578
 
510
579
  return baseTemplate(templateUrl, handlebarData).then(final => {
511
- const lvl2Dir = path.parse(templateUrl).dir
512
- const lvl2Opts = deepmerge(implantOpts, {baseDir: lvl2Dir})
513
- return implant(final, implantHandlers, lvl2Opts).then(output => {
514
- res.writeHead(200, {
515
- 'content-type': 'text/html'
580
+ if (flags.templates) {
581
+ const lvl2Dir = path.parse(templateUrl).dir
582
+ const lvl2Opts = deepmerge(implantOpts, {baseDir: lvl2Dir})
583
+ return implant(final, implantHandlers, lvl2Opts).then(output => {
584
+ res.writeHead(200, {
585
+ 'content-type': 'text/html'
586
+ })
587
+ res.end(output)
588
+ }).catch(error => {
589
+ console.error(error)
516
590
  })
517
- res.end(output)
518
- }).catch(error => {
519
- console.error(error)
591
+ }
592
+
593
+ res.writeHead(200, {
594
+ 'content-type': 'text/html'
520
595
  })
596
+ res.end(final)
521
597
  })
522
598
  } catch (error) {
523
599
  errorPage(500, filePath, error)
@@ -530,11 +606,8 @@ const createRequestHandler = flags => {
530
606
  }
531
607
  }
532
608
 
533
- const startConnectApp = (liveReloadPort, httpRequestHandler) => {
609
+ const startConnectApp = httpRequestHandler => {
534
610
  return connect()
535
- .use(connectLiveReload({
536
- port: liveReloadPort
537
- }))
538
611
  .use('/', httpRequestHandler)
539
612
  }
540
613
 
@@ -551,33 +624,178 @@ const startHTTPServer = (connectApp, port, flags) => {
551
624
  return httpServer
552
625
  }
553
626
 
554
- const startLiveReloadServer = (liveReloadPort, flags) => {
627
+ const startHotReload = (wsPort, flags) => {
555
628
  let {dir} = flags
556
629
  const isDir = fs.statSync(dir).isDirectory()
557
630
  if (!isDir) {
558
631
  dir = path.parse(flags.dir).dir
559
632
  }
560
633
 
561
- const exts = fileTypes.watch.map(type => type.slice(1))
562
- const exclusions = fileTypes.exclusions.map(exPath => {
563
- return path.join(dir, exPath)
634
+ const wss = new WebSocket.Server({port: wsPort})
635
+ const clients = new Map()
636
+
637
+ wss.on('connection', ws => {
638
+ ws.on('message', data => {
639
+ try {
640
+ const msg_ = JSON.parse(data)
641
+ if (msg_.path) {
642
+ clients.set(ws, msg_.path)
643
+ }
644
+ } catch (_) {}
645
+ })
646
+
647
+ ws.on('close', () => {
648
+ clients.delete(ws)
649
+ })
564
650
  })
565
651
 
566
- return liveReload.createServer({
567
- exts,
568
- exclusions,
569
- port: liveReloadPort
570
- }).watch(path.resolve(dir))
652
+ const sendToClients = (sockets, content) => {
653
+ for (const ws of sockets) {
654
+ if (ws.readyState === WebSocket.OPEN) {
655
+ ws.send(content)
656
+ }
657
+ }
658
+ }
659
+
660
+ // Debounced file watcher
661
+ let debounceTimer
662
+ const watchDir = path.resolve(dir)
663
+
664
+ const handleChange = () => {
665
+ // Group clients by path
666
+ const pathClients = new Map()
667
+ for (const [ws, clientPath] of clients) {
668
+ if (ws.readyState !== WebSocket.OPEN) {
669
+ continue
670
+ }
671
+
672
+ if (!pathClients.has(clientPath)) {
673
+ pathClients.set(clientPath, [])
674
+ }
675
+
676
+ pathClients.get(clientPath).push(ws)
677
+ }
678
+
679
+ const implantOpts = {maxDepth: 10}
680
+
681
+ const implantHandlers = {
682
+ markserv: () => new Promise(resolve => {
683
+ const value = path.relative(dir, __dirname)
684
+ resolve(value)
685
+ }),
686
+
687
+ file: (url, opts) => new Promise(resolve => {
688
+ const absUrl = path.join(opts.baseDir, url)
689
+ getFile(absUrl).then(data => resolve(data)).catch(() => resolve(false))
690
+ }),
691
+
692
+ less: (url, opts) => new Promise(resolve => {
693
+ const absUrl = path.join(opts.baseDir, url)
694
+ buildLessStyleSheet(absUrl).then(data => resolve(data)).catch(() => resolve(false))
695
+ }),
696
+
697
+ markdown: (url, opts) => new Promise(resolve => {
698
+ const absUrl = path.join(opts.baseDir, url)
699
+ getFile(absUrl).then(markdownToHTML).then(data => resolve(data)).catch(() => resolve(false))
700
+ }),
701
+
702
+ html: (url, opts) => new Promise(resolve => {
703
+ const absUrl = path.join(opts.baseDir, url)
704
+ getFile(absUrl).then(data => resolve(data)).catch(() => resolve(false))
705
+ })
706
+ }
707
+
708
+ for (const [clientPath, sockets] of pathClients) {
709
+ const decodedUrl = getPathFromUrl(decodeURIComponent(clientPath))
710
+ const filePath = path.normalize(unescape(dir) + unescape(decodedUrl))
711
+ const baseDir = path.parse(filePath).dir
712
+ implantOpts.baseDir = baseDir
713
+
714
+ let stat
715
+ let isDir_
716
+ let isMarkdown
717
+
718
+ try {
719
+ stat = fs.statSync(filePath)
720
+ isDir_ = stat.isDirectory()
721
+ if (!isDir_) {
722
+ isMarkdown = isType(fileTypes.markdown, filePath)
723
+ }
724
+ } catch (_) {
725
+ continue
726
+ }
727
+
728
+ if (isMarkdown) {
729
+ getFile(filePath)
730
+ .then(markdownToHTML)
731
+ .then(html => implant(html, implantHandlers, Object.assign({}, implantOpts, {baseDir})))
732
+ .then(output => {
733
+ sendToClients(sockets, output)
734
+ })
735
+ .catch(error => {
736
+ errormsg('hotreload', filePath, flags, error)
737
+ })
738
+ } else if (isDir_) {
739
+ try {
740
+ const content = dirToHtml(filePath)
741
+ const breadcrumbHtml = createBreadcrumbs(path.relative(dir, filePath))
742
+ let headerHtml = '<h1 class="icon folder isfolder">'
743
+ for (const crumb of breadcrumbHtml) {
744
+ headerHtml += `<a href="${crumb.href}">${crumb.text}</a>`
745
+ }
746
+
747
+ headerHtml += '</h1>\n'
748
+ const fullContent = headerHtml + content +
749
+ '<footer><sup><hr> Served by <a href="https://www.npmjs.com/package/markserv">MarkServ</a> | PID: ' + (process.pid || 'N/A') + '</sup></footer>'
750
+
751
+ sendToClients(sockets, fullContent)
752
+ } catch (error) {
753
+ errormsg('hotreload', filePath, flags, error)
754
+ }
755
+ }
756
+ }
757
+ }
758
+
759
+ try {
760
+ fs.watch(watchDir, {recursive: true}, (eventType, filename) => {
761
+ if (!filename) {
762
+ return
763
+ }
764
+
765
+ // Check exclusions
766
+ for (const exclusion of fileTypes.exclusions) {
767
+ if (filename.includes(exclusion.replace(/\/$/, ''))) {
768
+ return
769
+ }
770
+ }
771
+
772
+ // Check if file extension is watched
773
+ const ext = path.extname(filename)
774
+ if (ext && !fileTypes.watch.includes(ext)) {
775
+ return
776
+ }
777
+
778
+ clearTimeout(debounceTimer)
779
+ debounceTimer = setTimeout(handleChange, 150)
780
+ })
781
+ } catch (error) {
782
+ errormsg('watch', watchDir, flags, error)
783
+ }
784
+
785
+ return wss
571
786
  }
572
787
 
573
- const logActiveServerInfo = async (serveURL, httpPort, liveReloadPort, flags) => {
788
+ const logActiveServerInfo = async (serveURL, httpPort, wsPort, flags) => {
574
789
  const dir = path.resolve(flags.dir)
575
790
 
576
791
  const githubLink = 'github.com/markserv'
577
792
 
578
793
  msg('address', style.address(serveURL), flags)
579
794
  msg('path', chalk`{grey ${style.address(dir)}}`, flags)
580
- msg('livereload', chalk`{grey communicating on port: ${style.port(liveReloadPort)}}`, flags)
795
+
796
+ if (wsPort) {
797
+ msg('hotreload', chalk`{grey ws://localhost:${style.port(wsPort)}}`, flags)
798
+ }
581
799
 
582
800
  if (process.pid) {
583
801
  msg('process', chalk`{grey your pid is: ${style.pid(process.pid)}}`, flags)
@@ -666,22 +884,31 @@ const optionalUpgrade = async flags => {
666
884
  }
667
885
 
668
886
  const init = async flags => {
669
- const liveReloadPort = flags.livereloadport
670
- const httpPort = flags.port
887
+ const preferredPort = Number(flags.port) || 8642
888
+ const httpPort = flags.port ? preferredPort : await getPort({port: preferredPort})
889
+
890
+ let wsPort = null
891
+ const hotreloadEnabled = flags.hotreload !== false && flags.hotreload !== 'false'
892
+ if (hotreloadEnabled) {
893
+ wsPort = await getPort({port: httpPort + 1})
894
+ }
895
+
896
+ flags.$wsPort = wsPort
897
+ flags.$hotreload = hotreloadEnabled
671
898
 
672
899
  const httpRequestHandler = createRequestHandler(flags)
673
- const connectApp = startConnectApp(liveReloadPort, httpRequestHandler)
900
+ const connectApp = startConnectApp(httpRequestHandler)
674
901
  const httpServer = await startHTTPServer(connectApp, httpPort, flags)
675
902
 
676
- let liveReloadServer
677
- if (liveReloadPort && liveReloadPort !== 'false') {
678
- liveReloadServer = await startLiveReloadServer(liveReloadPort, flags)
903
+ let hotReloadServer
904
+ if (hotreloadEnabled) {
905
+ hotReloadServer = startHotReload(wsPort, flags)
679
906
  }
680
907
 
681
908
  const serveURL = 'http://' + flags.address + ':' + httpPort
682
909
 
683
910
  // Log server info to CLI
684
- logActiveServerInfo(serveURL, httpPort, liveReloadPort, flags)
911
+ logActiveServerInfo(serveURL, httpPort, wsPort, flags)
685
912
 
686
913
  let launchUrl = false
687
914
  if (flags.$openLocation || flags.$pathProvided) {
@@ -691,7 +918,7 @@ const init = async flags => {
691
918
  const service = {
692
919
  pid: process.pid,
693
920
  httpServer,
694
- liveReloadServer,
921
+ hotReloadServer,
695
922
  connectApp,
696
923
  launchUrl
697
924
  }