# \[ANN\] LiveServer: A simple development server with live-reload capability

**URL:** <https://discourse.julialang.org/t/ann-liveserver-a-simple-development-server-with-live-reload-capability/23380>\
**Category:** Package Announcements\
**Tags:** package, announcement\
**Created:** [April 21, 2019, 8:07pm UTC](https://discourse.julialang.org/t/ann-liveserver-a-simple-development-server-with-live-reload-capability/23380 "2019-04-21T20:07:03Z")\
**Posts on this page:** 9\
**Page:** 1

<div class="post-metadata">

**Author:** ![asprionj](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/asprionj/32/6856_2.png) [@asprionj](https://discourse.julialang.org/u/asprionj)\
**Post date:** [April 21, 2019, 8:07pm UTC](https://discourse.julialang.org/t/ann-liveserver-a-simple-development-server-with-live-reload-capability/23380/1 "2019-04-21T20:07:03Z")

</div>

Hi everyone, we (@tlienart and myself) have developed [a simple and lightweight pure-Julia development web-server](https://github.com/asprionj/LiveServer.jl), inspired from Python’s [`http.server`](https://docs.python.org/3/library/http.server.html) and Node’s [`browsersync`](https://www.browsersync.io/). It is based on [HTTP.jl](https://github.com/JuliaWeb/HTTP.jl) and has live-reload capability, i.e. when changing files, every browser (tab) currently displaying a corresponding page is automatically refreshed.

It is tested for Linux/Mac and Windows and Julia 1.0, 1.1, and nightly. It is in the General registry, i.e. you can install it by

```julia-repl
pkg> add LiveServer

```

## Functionality

The main function `LiveServer` exports is `serve` which starts observing the current (or specified) folder and serves its contents. The following code creates an example directory and serves it:

```julia
julia> using LiveServer
julia> LiveServer.example() # creates an "example/" folder with some files
julia> cd("example")
julia> serve() # starts the local server & the file watching
✓ LiveServer listening on http://localhost:8000/ ...
  (use CTRL+C to shut down)

```

Open a Browser and go to `http://localhost:8000/` to see the content being rendered; try modifying files (e.g. `index.html`) and watch the changes being rendered immediately in the browser.

**Note** : This package in no way is, nor tries to be or become a production server. It is meant for developing static sites, let you efficiently work on your docs (see below), or anything else that requires a mock file-server with auto-reload of the browser.

### Serve docs

Derived from `serve`, `servedocs` runs [Documenter.jl](https://github.com/JuliaDocs/Documenter.jl) along with `LiveServer` to render your docs and will track and live-reload any modifications. Assuming you are in `directory/to/YourPackage.jl` and that you have a `docs/` folder as prescribed by `Documenter`, just run:

```julia
julia> using LiveServer
julia> servedocs()

```

Go to `http://localhost:8000/` to see your docs; try modifying files (e.g. `docs/index.md`) and watch the changes being rendered in the browser.

## Status

The main functionality works flawlessly and has been tested thoroughly (… but only by two people so far). There’s also a complete set of tests; the coverage is currently at 81% since we have to monkey-patch `HTTP.jl` to fix [#405](https://github.com/JuliaWeb/HTTP.jl/issues/405) by PR [#406](https://github.com/JuliaWeb/HTTP.jl/pull/406) – the guys are working on a “clean” solution, and we will remove our “hacky” patch as soon as it is cleanly fixed.

We put quite some effort in having good [documentation](https://asprionj.github.io/LiveServer.jl/dev/).

**We welcome comments and suggestions for improvements!**

---

<div class="post-metadata">

**Author:** ![tlienart](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tlienart/32/7640_2.png) [@tlienart](https://discourse.julialang.org/u/tlienart)\
**Post date:** [April 22, 2019, 4:02am UTC](https://discourse.julialang.org/t/ann-liveserver-a-simple-development-server-with-live-reload-capability/23380/2 "2019-04-22T04:02:23Z")

</div>

Side note: for `servedocs`, the module whose docs you would be working on should be activated so if you were in a new julia session, it would be:

```julia
julia> using YourPackage, LiveServer
julia> servedocs()

```

otherwise the cross-references from Documenter.jl will not render (the rest will). I’ll update the docs to clarify this.

---

<div class="post-metadata">

**Author:** ![tim.holy](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tim.holy/32/52_2.png) [@tim.holy](https://discourse.julialang.org/u/tim.holy)\
**Post date:** [April 22, 2019, 8:35am UTC](https://discourse.julialang.org/t/ann-liveserver-a-simple-development-server-with-live-reload-capability/23380/3 "2019-04-22T08:35:04Z")

</div>

Seems useful. In conjunction with [`Revise.entr`](https://timholy.github.io/Revise.jl/stable/#Other-Revise-workflows-1) you might even have something that handles non-static sites?

---

<div class="post-metadata">

**Author:** ![asprionj](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/asprionj/32/6856_2.png) [@asprionj](https://discourse.julialang.org/u/asprionj)\
**Post date:** [April 22, 2019, 12:09pm UTC](https://discourse.julialang.org/t/ann-liveserver-a-simple-development-server-with-live-reload-capability/23380/4 "2019-04-22T12:09:12Z")

</div>

The file-watching system of `LiveServer` can be flexibly modified or extended, cf. [this section](https://asprionj.github.io/LiveServer.jl/dev/man/extending_ls/) in the docs. The `serve()` function also allows to pass in a `coreloopfun`, i.e. a function that is called from the main loop. [This can also be used to extend the serving](https://asprionj.github.io/LiveServer.jl/dev/man/extending_ls/#Using-a-custom-coreloopfun-1) to “dynamic” files, e.g. by triggering a re-generation of HTML files when markdown or code files change.

BTW, from a quick glance, `entr` seems to use `FileWatching` and an `@async` task for each file to be watched. FYI, we started out using`FileWatching` as well, but [abandoned it for a simpler and “more sluggish” watching mechanism](https://asprionj.github.io/LiveServer.jl/dev/man/extending_ls/#Why-not-use-FileWatching?-1).

---

<div class="post-metadata">

**Author:** ![tim.holy](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tim.holy/32/52_2.png) [@tim.holy](https://discourse.julialang.org/u/tim.holy)\
**Post date:** [April 22, 2019, 1:49pm UTC](https://discourse.julialang.org/t/ann-liveserver-a-simple-development-server-with-live-reload-capability/23380/5 "2019-04-22T13:49:37Z")

</div>

The advantage of Revise is that you don’t have to recompile everything when code changes: it just changes the specific methods that get modified. If your content doesn’t rely on that much Julia code, you may not care, but if you have a big stack you will notice recompiles.

WRT FileWatching, yes, `Revise.entr` was a quick hack and its use of `FileWatching` is naive; the rest of the Revise is more sophisticated, in that it (1) watches directories rather than files on platforms that support it (and then checks `mtime`s to see which files need attention), thus reducing the number of inodes allocated by the system, and (2) aggregates all triggers until the next REPL command, thus avoiding “double triggers.” If LiveServer can handle all the use cases of `entr`, I’d be happy to modify Revise’s docs to recommend LiveServer instead of `entr` (and maybe even deprecate `entr`). Conversely, you may wish to consider how to integrate Revise into the LiveServer workflow.

It’s worth pointing out that `watch_file` doesn’t trigger when a file is opened, only when files are modified. (You can verify this for yourself with two REPL sessions, one with `status = watch_file(filename)` and another with `readlines(filename)`.) But editors do all sorts of weird things with the files they are editing, which may be the source of the impression that simply opening the file can trigger it.

You appear to be using a strategy known as polling. `FileWatching` also supports polling, and indeed on some filesystems (notably NFS) polling is your only option. (Revise makes this configurable.) The downside of polling is that it doesn’t scale very well to large numbers of files. That may not be relevant to your use case, of course.

---

<div class="post-metadata">

**Author:** ![asprionj](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/asprionj/32/6856_2.png) [@asprionj](https://discourse.julialang.org/u/asprionj)\
**Post date:** [April 22, 2019, 2:11pm UTC](https://discourse.julialang.org/t/ann-liveserver-a-simple-development-server-with-live-reload-capability/23380/6 "2019-04-22T14:11:37Z")

</div>

Yes, we do simple (short) polling and trigger reloads upon changes of any individual file (i.e. don’t aggregate all changes that happened during the sleep time and then fire all relevant reloads only once). But the intended usage is for someone to _manually_ work on the files being watched (or the one-to-one corresponding source file such as markdown). Resolution of the minimal set of reloads would also involve keeping track of inner-file references (e.g. a HTML file linking to CSS, JS or picture files), which we have not implemented yet. We just wanted to keep everything as simple as possible, at least for the first implementation.

I’d have to get deeper into `Revise` and `entr` specifically to see whether replacing it (or parts of `LiveServer` by it) could be an option.

One thing that’s not solved right now is that with `servedocs`, the docstrings etc. are only parsed during the first (slow) run of `Documenter`. In update-passes, only the markdown is re-evaluated to have a “real-time” update. But when changing docstrings, one has to stop and restart the server, and then wait for the full pass of `Documenter` again to see those changes in the docs. Could `Revise` help here?

---

<div class="post-metadata">

**Author:** ![ExpandingMan](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/expandingman/32/866_2.png) [@ExpandingMan](https://discourse.julialang.org/u/ExpandingMan)\
**Post date:** [April 22, 2019, 2:27pm UTC](https://discourse.julialang.org/t/ann-liveserver-a-simple-development-server-with-live-reload-capability/23380/7 "2019-04-22T14:27:20Z")

</div>

I use `python -m http.server` a lot (not as often as I used to because the network topologies are really screwed up lately where I work). It’s nice to have a Julia alternative!

---

<div class="post-metadata">

**Author:** ![tim.holy](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tim.holy/32/52_2.png) [@tim.holy](https://discourse.julialang.org/u/tim.holy)\
**Post date:** [April 22, 2019, 3:18pm UTC](https://discourse.julialang.org/t/ann-liveserver-a-simple-development-server-with-live-reload-capability/23380/8 "2019-04-22T15:18:15Z")

</div>

> But when changing docstrings, one has to stop and restart the server, and then wait for the full pass of `Documenter` again to see those changes in the docs. Could `Revise` help here?

With Revise alone, Documenter needs a full pass to generate the doc pages and so any changes require you to do a second full pass. However, that full pass is faster because you don’t have to reload the package. For example, the first time I build the JuliaImages documentation, it’s ~80s; the second time (in the same REPL session) it’s ~36s. Obviously the time required will depend a lot on how many `doctest`s etc. the package has; IIUC `doctest`s spin up a new Julia session, so you don’t get the benefit of Revise there.

---

<div class="post-metadata">

**Author:** ![HugoTrentesaux](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/hugotrentesaux/32/12583_2.png) [@HugoTrentesaux](https://discourse.julialang.org/u/HugoTrentesaux)\
**Post date:** [March 19, 2022, 8:59pm UTC](https://discourse.julialang.org/t/ann-liveserver-a-simple-development-server-with-live-reload-capability/23380/9 "2022-03-19T20:59:46Z")

</div>

> [@ExpandingMan](#):
>
> `python -m http.server`

So the equivalent would be `julia -e "using LiveServer; serve()"` ?
