diff --git a/docs/src/index.md b/docs/src/index.md index a82ac9d..457ed0d 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -32,10 +32,10 @@ Open a Browser and go to `http://localhost:8000/` to see the content being rende ### Serve docs -A derived function from `serve` that will be convenient to Julia package developpers is `servedocs` which runs `Documenter` along with `LiveServer` to render your docs and will track and render any modifications to your docs. -This can make docs development significantly easier. +A function derived from `serve` that will be convenient to Julia package developpers is `servedocs`. It runs [Documenter.jl](https://github.com/JuliaDocs/Documenter.jl) along with `LiveServer` to render your docs and will track and render any modifications to your docs. +This instantaneous feedback makes writing docs significantly easier and faster. -Assuming you are in `directory/to/YourPackage.jl` and that you have a `docs/` folder as prescribed by [Documenter.jl](https://github.com/JuliaDocs/Documenter.jl), just run: +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 @@ -66,8 +66,35 @@ Let's assume you have a directory similar to that generated by [`LiveServer.exam └── blah.html ``` -After launching `serve()` in that directory, you can visit the rendered website by navigating to the page `http://localhost:8000/`. -This initiates a GET request from the browser which is caught by a listener loop in LiveServer. -The requested file (here `index.html`) is then sent to the client after injection of a small script which can trigger a reload of the page upon receiving a signal. - -If you now modify the page `index.html`, LiveServer will detect that the file has been modified and send a signal to the client which will cause the small script to trigger a page reload. +Calling `serve()` from within this directory starts a file server. It serves +the contents of the directory as a static site, with the folder structure +defining the paths of the URLs. That is, the file `blah.html` can be viewed +at `/pages/blah.html`. When a directory is specified instead of a file, +the server checks whether there is a file `index.html` in this directory and +serves it if available. + +Visiting `http://localhost:8000/` makes your browser send a standard HTTP `GET` +request to the server. The server, running a listener loop, receives this +request, looks for `index.html` in the root +folder, and serves it. After serving it, `LiveServer` adds this file to the +list of watched files. That is, whenever this file changes, a callback is +fired (see below). The HTML page may also contain references to style sheets or +pictures, which are then requested by your browser as well. The server +sends them to the browser, and also adds them to the list of watched +files. + +**But what about the live reloading?** Triggering a page refresh in the browser +is achieved by a WebSocket connection. The WebSocket API, according to +[MDN](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API), is +> an advanced technology that makes it possible to open a two-way interactive +> communication session between the user's browser and a server. +`LiveServer` injects a small piece of JavaScript code into every HTML file +before serving it. This snippet is executed by the browser and opens a WebSocket +connection to the server, which in turn adds it to a list of viewers of this page. + +The server can now send the message "update" to all viewers of a page +whenever the page is changed. The code snippet reacts to this message by +triggering a page reload (same as hitting `F5`). The update is triggered by the +callback mentioned above. When the file is not an HTML file, the viewers of _any_ +HTML file are updated, since `LiveServer` currently does not keep track of which +HTML files reference what other files. diff --git a/docs/src/lib/internals.md b/docs/src/lib/internals.md index ca77959..bc0b68a 100644 --- a/docs/src/lib/internals.md +++ b/docs/src/lib/internals.md @@ -47,7 +47,7 @@ LiveServer.is_watched ## Live serving -The exported [`serve`](@ref) and [`setverbose`](@ref) functions are not stated again. +The exported [`serve`](@ref) and [`servedocs`](@ref) functions are not stated again. The `serve` method instantiates a listener (`HTTP.listen`) in an asynchronous task. The callback upon an incoming HTTP stream decides whether it is a standard HTTP request or a request for an upgrade to a websocket connection. The former case is handled by [`LiveServer.serve_file`](@ref), the latter by @@ -56,6 +56,7 @@ Finally, [`LiveServer.file_changed_callback`](@ref) is the function passed to th ```@docs LiveServer.serve_file +LiveServer.ws_upgrade LiveServer.ws_tracker LiveServer.file_changed_callback ``` diff --git a/docs/src/man/extending_ls.md b/docs/src/man/extending_ls.md index 7b91590..8f59e54 100644 --- a/docs/src/man/extending_ls.md +++ b/docs/src/man/extending_ls.md @@ -8,8 +8,8 @@ This page explains how to extend `SimpleWatcher <: FileWatcher` and, more genera In most circumstances, using an instance of the `SimpleWatcher` type with your own _custom callback function_ is what you will want to do. -The `SimpleWatcher` does what you expect in terms of watching files it is told to watch and triggering a function (the _callback_) when a change is detected. -The callback function takes as argument the path of the file that was modified and returns nothing. +The `SimpleWatcher` does what you expect: it watches files for changes and triggers a function (the _callback_) when a change is detected. +The callback function takes as argument the path of the file that was modified and returns `nothing`. The base callback function ([`LiveServer.file_changed_callback`](@ref)) does only one thing: it sends a signal to the relevant viewers to trigger page reloads. You will typically want to re-use `file_changed_callback` or copy its code. @@ -20,13 +20,13 @@ As an example of a custom callback, here is a simple modified callback mechanism custom_callback(fp::AbstractString) = (println("Hello!"); file_changed_callback(fp)) ``` -A more sophisticated customised callback is the one that is used in [`servedocs`](@ref) (see [`servedocs_callback`](@ref)). +A more sophisticated customised callback is the one that is used in [`servedocs`](@ref) (see [`LiveServer.servedocs_callback`](@ref)). The callback has a different behaviour depending on which file is modified and does a few extra steps before signalling the viewers to reload appropriate pages. ## Writing your own `FileWatcher` If you decide to write your own `FileWatcher` type, you will need to meet the API. -The easier is probably that you look at the code for `SimpleWatcher` and adapt it to your need. +The easier is probably that you look at the code for [`LiveServer.SimpleWatcher`](@ref) and adapt it to your need. Let's assume for now that you want to define a `CustomWatcher <: FileWatcher`. ### Fields @@ -35,18 +35,19 @@ The only field that is _required_ by the rest of the code is * `status`: a symbol that must be set to `:interrupted` upon errors in the file watching task -Likely you will want to have some (likely most) of the fields of a `SimpleWatcher` i.e.: +Likely you will want to have some (probably most) of the fields of a `SimpleWatcher` i.e.: * `callback`: the callback function to be triggered upon an event, * `task`: the asynchronous file watching task, -* `watchedfiles`: the vector of [`LiveServer.WatchedFile`](@ref) i.e. the paths to the file being watched as long as their time of last modification, -* `sleeptime`: the time to wait before going over the list of `watchedfiles` to check for events, you won't want this to be too small and it's lower bounded to `0.05` in `SimpleWatcher`. +* `watchedfiles`: the vector of [`LiveServer.WatchedFile`](@ref) i.e. the paths to the file being watched as well as their time of last modification, +* `sleeptime`: the time to wait before going over the list of `watchedfiles` to check for changes, you won't want this to be too small and it's lower bounded to `0.05` in `SimpleWatcher`. Of course you can add any extra field you may want. ### Methods -Subsequently, your `CustomWatcher` may redefine some or all of the following methods (those that aren't will use the default method defined for all `FileWatcher`). +Subsequently, your `CustomWatcher` may redefine some or all of the following methods (those that aren't will use the default method defined for `FileWatcher` and thus +all of its sub-types). The methods that are _required_ by the rest of the code are @@ -55,7 +56,7 @@ The methods that are _required_ by the rest of the code are You may also want to re-define existing methods such as -* `file_watcher_task!(::FileWatcher)`: the loop that goes over the watched files, it checks for an event (modifications) and trigger the callback function; this will be the `CustomWatcher.task`. If errors happen in this asynchronous task, the `CustomWatcher.status` should be set to `:interrupted` so that all running tasks can be stopped properly. +* `file_watcher_task!(::FileWatcher)`: the loop that goes over the watched files, checking for modifications and triggering the callback function. This task will be referenced by the field `CustomWatcher.task`. If errors happen in this asynchronous task, the `CustomWatcher.status` should be set to `:interrupted` so that all running tasks can be stopped properly. * `set_callback!(::FileWatcher, ::Function)`: a helper function to bind a watcher with a callback function. * `is_running(::FileWatcher)`: a helper function to check whether `CustomWatcher.task` is done. * `is_watched(::FileWatcher, ::AbstractString)`: check if a file is watched by the watcher. diff --git a/docs/src/man/functionalities.md b/docs/src/man/functionalities.md index 8810c05..abd7c8d 100644 --- a/docs/src/man/functionalities.md +++ b/docs/src/man/functionalities.md @@ -20,11 +20,12 @@ julia> serve() which will make the content of the folder available to be viewed in a browser. -You can specify the port that the server should listen to (default is `8000`) as well as the directory to serve if not the current one. -There is also a `verbose` argument if you want to see messages being displayed on file changes and +You can specify the port that the server should listen to (default is `8000`) as well as the directory to serve (if not the current one) as keyword arguments. +There is also a `verbose` keyword-argument if you want to see messages being displayed on file changes and connections. -More interestingly, you can specify the `filewatcher` which allows to define what will trigger the messages to the client and ultimately cause the active browser tabs to reload. +More interestingly, you can optionally specify the `filewatcher` (the only +regular argument) which allows to define what will trigger the messages to the client and ultimately cause the active browser tabs to reload. By default, it is file modifications that will trigger page reloads but you may want to write your own file watcher to perform additional actions upon file changes or trigger browser reload differently. See the section on [Extending LiveServer](@ref) for more informations. @@ -55,9 +56,9 @@ Let's assume the structure of your package looks like ``` -The standard way of running `Documenter.jl` is to run `make.jl`, wait for completion and then maybe use a third party tool to see the output. +The standard way of running `Documenter.jl` is to run `make.jl`, wait for completion and then use a standard browser or maybe some third party tool to see the output. -With `servedocs` however, you can edit the `.md` files in your `docs/src` and see the changes being applied directly in your browser which makes writing documentation easier. +With `servedocs` however, you can edit the `.md` files in your `docs/src` and see the changes being applied directly in your browser which makes writing documentation faster and easier. To launch it, navigate to `YourPackage.jl/` and simply ```julia-repl @@ -70,4 +71,8 @@ Upon modifying a `.md` file (e.g. updating `docs/src/index.md`), the `make.jl` w !!! note - the first pass of Documenter.jl takes a few seconds but subsequent passes are quite fast so that the workflow with `Documenter.jl`+`LiveServer.jl` is pretty quick. + The first pass of `Documenter.jl` takes a few seconds to complete, but subsequent passes are quite fast so that the workflow with `Documenter.jl`+`LiveServer.jl` is pretty quick. + + The first pass collects all information in the code (i.e. docstrings), while + subsequent passes only consider changes in the markdown (`.md`) files. This + restriction is necessary to achieve a fast update behavior. diff --git a/src/file_watching.jl b/src/file_watching.jl index 753679a..7ec70fe 100644 --- a/src/file_watching.jl +++ b/src/file_watching.jl @@ -48,7 +48,7 @@ abstract type FileWatcher end SimpleWatcher([callback]; sleeptime::Float64=0.1) <: FileWatcher A simple file watcher. You can specify a callback function, receiving the path of each file that -has changed as an `AbstractString`, at construction or later by the API function described below. +has changed as an `AbstractString`, at construction or later by the API function [`set_callback!`](@ref). The `sleeptime` is the time waited between two runs of the loop looking for changed files, it is constrained to be at least 0.05s. """ @@ -60,13 +60,6 @@ mutable struct SimpleWatcher <: FileWatcher status::Symbol # set to :interrupted as appropriate (caught by server) end -""" - SimpleWatcher([callback]; sleeptime) - -Instantiate a new `SimpleWatcher` with an optional callback triggered upon file change. -The `sleeptime` argument can be used to determine how often to check for file change (default is -every 0.1 second and minimum is 0.05). -""" SimpleWatcher(callback::Union{Nothing,Function}=nothing; sleeptime::Float64=0.1) = SimpleWatcher(callback, nothing, max(0.05, sleeptime), Vector{WatchedFile}(), :runnable) @@ -120,7 +113,7 @@ end """ set_callback!(fw::FileWatcher, callback::Function) -Mandatory API function to set or change the callback function being executed upon a file change. +Set or change the callback function being executed upon a file change. Can be "hot-swapped", i.e. while the file watcher is running. """ function set_callback!(fw::FileWatcher, callback::Function) @@ -135,7 +128,7 @@ end """ is_running(fw::FileWatcher) -Optional API function to check whether the file watcher is running. +Checks whether the file watcher is running. """ is_running(fw::FileWatcher) = (fw.task !== nothing) && !istaskdone(fw.task) @@ -182,7 +175,7 @@ end """ is_watched(fw::FileWatcher, f_path::AbstractString) -Check whether a file `f_path` is being watched by file watcher `fw`. +Checks whether the file specified by `f_path` is being watched. """ is_watched(fw::FileWatcher, f_path::AbstractString) = any(wf -> wf.path == f_path, fw.watchedfiles) diff --git a/src/server.jl b/src/server.jl index 796ee71..66cc97b 100644 --- a/src/server.jl +++ b/src/server.jl @@ -22,7 +22,7 @@ end """ file_changed_callback(f_path::AbstractString) -Function reacting to the change of a file `f_path`. Is set as callback for the file watcher. +Function reacting to the change of the file at `f_path`. Is set as callback for the file watcher. """ function file_changed_callback(f_path::AbstractString) VERBOSE[] && println("ℹ [LiveUpdater]: Reacting to change in file '$f_path'...") @@ -63,10 +63,10 @@ end Handler function for serving files. This takes a file watcher, to which files to be watched can be added, and a request (e.g. a path entered in a tab of the browser), and converts it to the appropriate file system path. If the path corresponds to a HTML file, it will inject the reloading -script (see file `client.js`) at the end of its body, i.e. directly before the tag. +`