Code Monkey home page Code Monkey logo

liveserver.jl's Introduction

Live Server for Julia

CI Actions Status codecov docs

This is a simple and lightweight development web-server written in Julia, based on HTTP.jl. It has live-reload capability, i.e. when modifying a file, every browser (tab) currently displaying the corresponding page is automatically refreshed.

LiveServer is inspired from Python's http.server and Node's browsersync.

Installation

To install it in Julia ≥ 1.6, use the package manager with

pkg> add LiveServer

Broken pipe message

Infrequently, you may see an error message in your console while using LiveServer that does not interrupt the server and does not otherwise affect your ability to see updates in the browser. This error message will look like

┌ LogLevel(1999): handle_connection handler error
│   exception =
│    IOError: write: broken pipe (EPIPE)

You can basically ignore this message, it's a problem with HTTP.jl.

If your application depends on LiveServer and you'd like to avoid having that kind of messages being shown to your users, you can consider using LoggingExtras.jl which allows you to filter out messages based on their provenance.

We experimented with shipping LoggingExtras in LiveServer but ended up rolling that back as it made other applications less stable.

Legacy notes

For Julia < 1.6, you can use LiveServer's version 0.9.2:

For Julia [1.0, 1.3), you can use LiveServer's version 0.7.4:

Make it a shell command

LiveServer is a small package and fast to load with one main functionality (serve), it can be convenient to make it a shell command: (I'm using the name lss here but you could use something else):

alias lss='julia -e "import LiveServer as LS; LS.serve(launch_browser=true)"'

you can then use lss in any directory to show a directory listing in your browser, and if the directory has an index.html then that will be rendered in your browser.

Usage

The main function LiveServer exports is serve which starts listening to the current folder and makes its content available to a browser. The following code creates an example directory and serves it:

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.

In the REPL:

julia> using LiveServer
julia> serve(host="0.0.0.0", port=8001, dir=".") # starts the remote server & the file watching
✓ LiveServer listening on http://0.0.0.0:8001...
  (use CTRL+C to shut down)

In the terminal:

julia -e 'using LiveServer; serve(host="0.0.0.0", port=8001, dir=".")'

Open a browser and go to https://localhost:8001/ to see the rendered content of index.html or, if it doesn't exist, the content of the directory. You can set the port to a custom number. This is similar to the http.server in Python.

Serve docs

servedocs is a convenience function that runs Documenter along with LiveServer to watch your doc files for any changes and render them in your browser when modifications are detected.

Assuming you are in directory/to/YourPackage.jl, that you have a docs/ folder as prescribed by Documenter.jl and LiveServer installed in your global environment, you can run:

$ julia

pkg> activate docs

julia> using YourPackage, LiveServer

julia> servedocs()
[ Info: SetupBuildDirectory: setting up build directory.
[ Info: ExpandTemplates: expanding markdown templates.
...
└ Deploying: ✘
✓ LiveServer listening on http://localhost:8000/ ...
  (use CTRL+C to shut down)

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

To run the server with one line of code, run:

$ julia --project=docs -ie 'using YourPackage, LiveServer; servedocs()'

Note: this works with Literate.jl as well. See the docs.

DEV/Path testing

See also issue #135 and related PRs.

  • servedocs(), navigate to literate, images should show
  • serve() navigate manually to docs/build/ should show, remove trailing slash in URL docs/build should redirect to docs/build/
  • serve(dir=...) should work + when navigating to assets etc

liveserver.jl's People

Contributors

asprionj avatar briochemc avatar csertegt3 avatar dilumaluthge avatar emstoudenmire avatar ffevotte avatar findmyway avatar fonsp avatar fredrikekre avatar jameswrigley avatar juliatagbot avatar lorenzoh avatar mortenpi avatar rikhuijzer avatar roger-luo avatar tkf avatar tlienart avatar tlnagy avatar totalverb avatar zsz00 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

liveserver.jl's Issues

Sequence of events --> DOCS

This is a possible reason behind #3 I think I need another pair of eyes on the reasoning given that this websocket and HTTP business is still a bit mysterious to me

sequence of events

(this is what I think happens, I think it would be useful to fine tune this and have it eventually somewhere in the doc as it's useful to understand / improve?)

  1. launch of liveserver
  2. initial browser event : {direct visit of server @ ipaddr:port OR refresh of a page that was already there}
  3. triggers one or several GET request(s) to server (several in the case where the page itself loads secondary stuff like CSS); for each of these GET request
  4. each request is caught by HTTP.listen and the corresponding http.message has a header that contains the "GET"
  5. HTTP.handle is called, the file requested is added to the filewatcher
  6. a HTTP.Response(202, ...) is returned to the browser with the content of the file requested AND the reloading script
  7. an upgrade message is triggered after the response for that file
  8. the upgrade message is then processed by the ws_tracker
  9. a new websocket is launched and associated with that file and appended to a list of websockets

Then upon a file event on one of those watched files (let's assume it's a html file)

  1. the file event effectively triggers a ping on all available (open/active) websockets associated with the file
  2. this is effectively a message that is caught by the browser which is looking at that websocket via the javascript sock.onmessage
  3. this triggers a browser-reload

But then, and this is the important part

  1. the browser-reload re-generates a GET request

and with it the sequence of event where basically a new websocket is added.

Anyway:

  1. would be good to check if this order of operations is right, possibly it's partially right but would be good to have it exactly right
  2. if it's confirmed then that explains #3 since every browser reload re-triggers the construction of a new (not useful) websocket

And it seems to me we should be able to detect that? Ideally we would be able to distinguish at the GET level

  • GET request coming from a new tab / new browser ==> needs a new websocket
  • GET request coming from something already active ==> no need for a new websocket unless there is none available

What do you think and would you see a way of going about this?

Reloading page after closing server still shows action

Actions:

  1. serve
  2. shut it down with CTRL+C
  3. hit refresh in the browser this shows
julia> serve()
Starting live-server on 0.0.0.0:8000...
Adding file './index.html' to watcher...
Adding file './css/master.css' to watcher...
WebSocket request from /...
WebSocket connections for file './index.html':
wsi.io = T0  ⏸    0↑🔒    0↓🔒   0s 0.0.0.0:8000:8000 ≣16

✓ server closed gracefully.

julia> Adding file './index.html' to watcher...
Adding file './css/master.css' to watcher...

So this shows that the FileWatcher is not stopped properly.

Edit: we should consider using watch_folder which can be stopped with unwatch_folder

WatchedFile synchronization failure

The test "Watcher/WatchedFile struct " does not seem to work consistently seemingly due to timing issues. I've tried to massively increase the sleep times here and there but it still fails on occasions. This is not good and requires investigation.

This may in fact be due to APFS vs older stuff... as I'm testing this home on a mac with Sierra but wrote the tests on Mojave.

servedocs improvements

  • First suggestion: #43 (comment)
  • Then, we should add tests for it to get coverage up again.
  • Should we move it to a separate file? It does not belong in the main module file LiveServer.jl, but somehow neither in server.jl...
  • A nice feature would be that it automatically detects new files. For this, we could use the coreloopfun and a marginally modified FileWatcher with one additional field holding the path to the docs folder we're serving. This could be used in the loop-function (e.g. on every 10th call or so) to check for new files against is_watched.

This is your brainchild, so I let you decide what to do and what to leave. I'll help as time allows me to. ;-)

Add package labels to help discovery

In the red circle you should see something that allows you to add tags.

Maybe things like web server julia or whatever you think (it gives suggestions as well) would be good

Screen Shot 2019-04-05 at 6 03 43 pm

Minor code cleanup

(I can do this after pushing some tests)

  • use same docstrings everywhere. Since pretty much everything is internal, I don't think it's very useful to add [INTERNAL]
  • use ! consistently, if it mutates the object, add one (e.g. watch_file)
  • use standardised spacing convention arg1, arg2, arg3
  • (ongoing)

Controlling verbosity

There needs to be a way for the user to control the verbosity of the server and file watcher. Like a print level for numerical solvers or something. Implementation could be a global const VERBOSE = Ref{Bool}(true) which may be toggled by a function.
Have to make sure that this also works with custom file watchers, not only with the SimpleWatcher defined within LiveServer.

Making the watcher an argument to `serve`

I'm thinking that the logic watcher! (using the terminology in my PR but basically the stuff where you look at whether files have changed) could be passed to serve which should just serve as a vector to generates browser refreshes.

That way JuDoc (or anything else) could implement its own logic for watching files, possibly doing more stuff along the way, and ping the websockets via serve.

It needs a bit of thinking as to how the API can be exposed properly to an external package but I think it shouldn't be too hard and for JuDoc it'd be great (and would kill the problem that you mentioned some time ago of watching the files twice).

WS connection dropped on Safari

So on Safari there seems to be issues with how websocket connections are handled.

  • On branch simpleFW the updates don't seem to trigger a reload
  • On branch wshandling-wip the updates cause an error (stream unavailable)

The same code works on Firefox.

Only watch what we need to

This is not urgent. I'm just thinking that at the moment, if we trigger lots of requests, lots of files will be watched for changes. This makes sense if all the requests come from new tabs. But otherwise doesn't (e.g. if it's the same tab asking different things we should only really track the file that is currently being watched).

Watching files is very cheap though so not a big deal and even in the case of a huge website, we'd basically have to do hundreds of thousands of requests and modify all these files for it to maybe be a bottleneck.

Suggestion: a doable intermediate step which would feel cleaner is to stop watching files for which there is no associated viewer.

isrunning -> is_running

for consistency, we have has_watched is_watched etc so should rename.

I'm in the process of adding docs so will do that in a bit.

Closing HTTP cleanly (the return)

It's funny, I was writing a MWE to post on HTTP.jl about how you can't really shut down their listener and wrote this:

using HTTP
using Sockets
server = Sockets.listen(8000)
@async HTTP.listen(ip"127.0.0.1", server=server) do http::HTTP.Stream
    if http.message.method == "GET" && http.message.target in ("/", "/index.html")
         h = HTTP.Handlers.RequestHandlerFunction() do req::HTTP.Request
                 HTTP.Response(200,read("index.html", String))
             end
         HTTP.Handlers.handle(h, http)
    end
end
try
    while true
        sleep(0.1)
    end
catch err
    isa(err, InterruptException) && close(server)
end

However this works... perfectly fine!

So I think the problem is to do with websockets still remaining active... Using close(wsi.io) may not kill the websocket completely (not sure what happens), using close(wsi) sometimes doesn't work depending on the status and may throw an error (at least when I tried).

Anyway this is not really an issue but if you have a better understanding for what's going on, it might help us improve that part of the code (which currently works but is a bit ugly)

(re)Set the dependency to the official HTTP.jl

Due to bug JuliaWeb/HTTP.jl#405 I changed the dependency of LiveServer from the official HTTP.jl branch to my own (with a patch that fixes the issue).

Eventually (when they fix the bug) we can revert this and use the official branch. In the mean time it doesn't change much, and removes bugs for us while not modifying functionality.

Edit: for those who may be reading this comment, the patched fork was suggested as a PR to HTTP.jl but the patch is considered not ideal (we don't disagree); however the ideal fix will probably take a while to be fully implemented which is why we're currently using the patched fork here.

WebSocket handler to keep connection alive

The issue with Chromium in #2 seems not to originate in insecure connection to localhost. I added an onopen method to the WebSocket object in the client, console.log-ing some text. Also, added a console.log to the onmessage method, and made ws_tracker send a welcome message to the client right at the end, just before return nothing. Both messages are shown also in Chromium. That is, the connection opens and also successfully receives the message, but then instantly closes.

I then added a sleep(10) just before the return nothing at the end of ws_tracker. In Chromium, the websocket now remains open (readyState 1) for 10 seconds, then closes. Can also be nicely observed using verbose=true in HTTP.listen. So, I guess that:

  • Once the handler function passed to HTTP.listen returns, the HTTP.Stream is closed.
  • The unintended behavior is probably with Firefox, not with Chromium. Chromium closes the connection as it should, but Firefox somehow keeps it alive, even though the server seems to close it...? (No idea how this is possible at all...!)

So, does this mean that we need to have another async task for each WS connection? I assumed this (keeping the stream open, closing it once the websocket is closed) would be handled internally by HTTP.jl's WebSocket type.

SVG are not served properly

This is almost certainly a problem with HTTP.jl but is causing issues here and in JuDoc.

MWE:

<!doctype html>
<html lang="en">
<body>
  <img src="blah.svg">
</body>
</html>

where blah.svg is let's say https://commons.wikimedia.org/wiki/File:Julia_prog_language.svg

This is the result on FF (back) and Chromium (front)

Screen Shot 2019-05-02 at 5 46 02 pm

Same thing with browser-sync

Screen Shot 2019-05-02 at 5 47 50 pm

with liveserver

  • works on FF
  • does not work on Safari, Chrome, Chromium

with browser-sync

  • works on FF, Safari, Chrome, Chromium (and I suspect all other major browsers)

Working out what's going on

I'm not entirely sure what's going on but I'm 99% sure it's got something to do with HTTP.jl not sending the right MIME type or something of the sorts.

Using servedocs & kicking the tire (?)

Suggestion: I think it's a good exercise and test for us to write the doc while using the servedocs function and reporting errors here, hopefully that will help us kick the tire and get rid of sneaky bugs.

julia> pwd()
/Users/tlienart/.julia/dev/LiveServer.jl
julia> using LiveServer
julia> servedocs()

If you modify a md file such as index.md, a pass of Documenter.jl is re-triggered and the changes appear in the browser. Great that's what we want.

A problem encountered

It seems we can still have a problem with websocket connections being dropped. I got this one once (but couldn't reproduce...)

┌ Info: Deployment criteria:-ENV["TRAVIS_REPO_SLUG"]="" occurs in repo="github.com/asprionj/LiveServer.jl.git"-ENV["TRAVIS_PULL_REQUEST"]="" is "false"-ENV["TRAVIS_TAG"]="" is (i) empty or (ii) a valid VersionNumber
│ -ENV["TRAVIS_BRANCH"]="" matches devbranch="master" (if tag is empty)
│ -ENV["DOCUMENTER_KEY"] exists
└ Deploying: ✘
┌ Error: An error happened whilst watching files; shutting down. Error was: Base.IOError("write: broken pipe (EPIPE)", -32)
└ @ LiveServer ~/.julia/dev/LiveServer.jl/src/file_watching.jl:113
ERROR: ┌ Error: Exception while generating log record in module LiveServer at /Users/tlienart/.julia/dev/LiveServer.jl/src/file_watching.jl:113
│   exception =
│    InterruptException:

Anyway I think it's great if we use that so that we can practice with it more extensively and eventually have something that works most of the time!

Add License

We should add a license file. Typically projects in Julia are MIT licensed and consequently we should add a file like this with title LICENSE.md (should be copy pasted so that GitHub can parse it and mark our projects as such).

I can do it but want to check first you're happy with going MIT license.

The LiveServer.jl package is licensed under the MIT "Expat" License:

> Copyright (c) 2019: Jonas Asprion, Thibaut Lienart.
>
> Permission is hereby granted, free of charge, to any person obtaining a copy
> of this software and associated documentation files (the "Software"), to deal
> in the Software without restriction, including without limitation the rights
> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
> copies of the Software, and to permit persons to whom the Software is
> furnished to do so, subject to the following conditions:
>
> The above copyright notice and this permission notice shall be included in all
> copies or substantial portions of the Software.
>
> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
> SOFTWARE.
>

Interplay with Documenter.jl

I think it'd be nice to have LiveServer play nicely with Documenter.jl in that when you compile the docs you typically want to have a look at them too.

One thing to do (I'm working on it) is to make serve take an argument dir which can specify a directory to watch that is not the current one. That's not too hard just requires get_fs_path to use the root.

Then we'd have to see how the two can play well together, I'm sure that would get some Julia folks interested.

Error chasing

So I'm now using LiveServer heavily with JuDoc while deploying new templates and 95% of the time it's all great.

A few times I've had error triggered when closing the server (upon interrupt). Here's one of the latest ones:

^CERROR: EOFError: read end of file
Stacktrace:
 [1] #serve#8(::Int64, ::String, ::Bool, ::getfield(JuDoc, Symbol("##92#93")){Bool,Bool,NamedTuple{(:md, :html, :other, :infra),NTuple{4,Dict{Pair{String,String},Float64}}},NamedTuple{(:head, :pg_foot, :foot),Tuple{String,String,String}}}, ::typeof(LiveServer.serve), ::LiveServer.SimpleWatcher) at /Users/tlienart/.julia/dev/LiveServer.jl/src/server.jl:234
 [2] #serve at ./none:0 [inlined] (repeats 2 times)
 [3] #serve#91(::Bool, ::Bool, ::Int64, ::Bool, ::typeof(serve)) at /Users/tlienart/.julia/dev/JuDoc/src/manager/judoc.jl:177
 [4] serve() at /Users/tlienart/.julia/dev/JuDoc/src/manager/judoc.jl:149
 [5] top-level scope at REPL[3]:1
caused by [exception 1]
EOFError: read end of file
Stacktrace:
 [1] try_yieldto(::typeof(Base.ensure_rescheduled), ::Base.RefValue{Task}) at ./event.jl:277
 [2] wait() at ./event.jl:336
 [3] wait(::Base.GenericCondition{Base.AlwaysLockedST}) at ./event.jl:99
 [4] stream_wait(::Timer, ::Base.GenericCondition{Base.AlwaysLockedST}) at ./stream.jl:47
 [5] wait at ./event.jl:456 [inlined]
 [6] sleep at ./event.jl:510 [inlined]
 [7] #serve#8(::Int64, ::String, ::Bool, ::getfield(JuDoc, Symbol("##92#93")){Bool,Bool,NamedTuple{(:md, :html, :other, :infra),NTuple{4,Dict{Pair{String,String},Float64}}},NamedTuple{(:head, :pg_foot, :foot),Tuple{String,String,String}}}, ::typeof(LiveServer.serve), ::LiveServer.SimpleWatcher) at /Users/tlienart/.julia/dev/LiveServer.jl/src/server.jl:230
 [8] #serve at ./none:0 [inlined] (repeats 2 times)
 [9] #serve#91(::Bool, ::Bool, ::Int64, ::Bool, ::typeof(serve)) at /Users/tlienart/.julia/dev/JuDoc/src/manager/judoc.jl:177
 [10] serve() at /Users/tlienart/.julia/dev/JuDoc/src/manager/judoc.jl:149
 [11] top-level scope at REPL[3]:1

I think what happens there is that the ordering of events is such that there's still potentially a stream being written to as part of the HTTP side which gets closed and doesn't know what to do.
I'm not sure about this and in a way it's irrelevant bc such errors are hard to reproduce but I've seen that one, sometimes the Broken Pipe error, sometimes something else. It's rare though but just looks unclean.

My reasoning is that we should be careful when catching errors. Possibly we should allow for some errors to be left un-propagated in LiveServer.serve() because they will have been ultimately caused by the interruption (and so we don't care about catching it).

Ideally we would check where the error comes from but I'm not sure how to do that (maybe backtrace?)

I'll try to build a short list of such errors that I've seen & I think we should ignore:

reproduced

  1. EOFError (reproduced above)

not reproduced recently

  1. IOError (I think doesn't happen anymore since putting the websocket update message in a try/catch) Edit: this does not seem to happen anymore thanks to the fix in the HTTP patch.

Exported functions

I don't think we need to export start/stop/SimpleWatcher/set_callback!/watch_file! and would think it's best we did not.

Most of the time the user will not provide a watcher. And even if they do, they should use LiveServer.... because it's clearer what they're doing to extend the functions. (Just like when one tries to extend Base.show for instance).

Number of sockets grows

So currently, the ws_tracker function

  • creates a new websocket
  • appends it to the list of websocket associated with one file

But that seems wasteful, in practice for every upgrade request, new websockets are added. In practice, if you display the number of websockets associated with one file, it just grows and grows and grows even though there is some filtering for active/open sockets.

Edit: to see this, print the number of sockets at every upgrade request and hit save a couple of time (even without changes) in index.html it increments by 1 each time.

It seems to me that we should be able to detect if the upgrade request comes from a place we've seen before, in which case there shouldn't be a new websocket but it should use the existing one (?) unless it has disappeared.

What do you think?

Stops updating after a number of events?

When running, if modifying index.html several times in a row (let's say ±10 times I don't think it's a precise number) the webpage seems to stop updating. (and it stops adding websockets as well).

I'm not too sure what may cause this; will need to dig further.

Stop server if file-watching task fails

When the file-watching task (function _file_watcher in the SimpleWatcher) fails due to an exception (not being an InterrupException), the server should stop. We could add an API function / a field to SimpleWatcher like status and check on this in the main loop in serve() that catches user interrupts. Good idea?

Good interface for file watchers

We need to define a clean and suitable interface for file watchers. (In the context of branch simpleFW).

One idea that just came to my mind is to require them to provide methods like add_file (simple to do, just pass the string to the corresponding channel) and something like set_filechange_callback. The latter needs some thought. The idea behind it is to hide the details on the communication. That is, the "user" of the watcher (here it's the serve() command) should not need to know how it has to communicate (via channels in the SimpleWatcher, but probably differently for other watchers) but just call API methods that are always the same.

Stuff to do before announcement

  • wait until HTTP merges my fix or suggests an alternative fix (open: #10) change HTTP requirement to my branch with a patch.
  • write docs (open: #17 #5 done: #20)
  • reduce Readme to minimal example and move all info to docs
  • bring coverage > 90% (this may not be trivial) (though open: #30)
    • unclear how to test ws_tracker, maybe look at docs from HTTP should be able to simulate http stream maybe or generate upgrade request
  • add appveyor tests and see if work (open: #29)
  • add license (#31)
  • add tags to help package discovery (#33)

Move JS snippet to .js file

Then read this into memory (e.g. when starting the server). Keeping it as a string in a .jl file just isn't the right way to do it.

Tests

start adding some

stop(simplewatcher) fails

sw = LiveServer.SimpleWatcher(identity)
LiveServer.watch_file(sw, file1)

@test !LiveServer.isrunning(sw)
LiveServer.start(sw)
@test LiveServer.isrunning(sw)

@test_broken LiveServer.stop(sw)
@test_broken !LiveServer.isrunning(sw)

So LiveServer.stop fails with stacktrace

 Got exception outside of a @test
  schedule: Task not runnable
  Stacktrace:
   [1] error(::String) at ./error.jl:33
   [2] enq_work at ./event.jl:169 [inlined]
   [3] #schedule#451(::Bool, ::typeof(schedule), ::Task, ::InterruptException) at ./event.jl:215
   [4] #schedule at /Users/tlienart/.julia/dev/LiveServer.jl/src/file_watching.jl:0 [inlined]
   [5] stop(::SimpleWatcher) at /Users/tlienart/.julia/dev/LiveServer.jl/src/file_watching.jl:125
   [6] top-level scope at /Users/tlienart/.julia/dev/LiveServer.jl/test/file_watching.jl:69
   [7] top-level scope at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.2/Test/src/Test.jl:1113
   [8] top-level scope at /Users/tlienart/.julia/dev/LiveServer.jl/test/file_watching.jl:51
   [9] include_string(::Module, ::String, ::String) at ./loading.jl:1012
   [10] include_string(::Module, ::String, ::String, ::Int64) at /Users/tlienart/.julia/packages/CodeTools/xGemk/src/eval.jl:30
   [11] (::getfield(Atom, Symbol("##116#121")){String,Int64,String})() at /Users/tlienart/.julia/packages/Atom/mngyX/src/eval.jl:94
   [12] withpath(::getfield(Atom, Symbol("##116#121")){String,Int64,String}, ::String) at /Users/tlienart/.julia/packages/CodeTools/xGemk/src/utils.jl:30
   [13] withpath at /Users/tlienart/.julia/packages/Atom/mngyX/src/eval.jl:46 [inlined]
   [14] #115 at /Users/tlienart/.julia/packages/Atom/mngyX/src/eval.jl:93 [inlined]
   [15] with_logstate(::getfield(Atom, Symbol("##115#120")){String,Int64,String}, ::Base.CoreLogging.LogState) at ./logging.jl:395
   [16] with_logger at ./logging.jl:491 [inlined]
   [17] #114 at /Users/tlienart/.julia/packages/Atom/mngyX/src/eval.jl:92 [inlined]
   [18] hideprompt(::getfield(Atom, Symbol("##114#119")){String,Int64,String}) at /Users/tlienart/.julia/packages/Atom/mngyX/src/repl.jl:87
   [19] macro expansion at /Users/tlienart/.julia/packages/Atom/mngyX/src/eval.jl:91 [inlined]
   [20] macro expansion at /Users/tlienart/.julia/packages/Media/ItEPc/src/dynamic.jl:24 [inlined]
   [21] (::getfield(Atom, Symbol("##113#118")))(::Dict{String,Any}) at /Users/tlienart/.julia/packages/Atom/mngyX/src/eval.jl:86
   [22] handlemsg(::Dict{String,Any}, ::Dict{String,Any}) at /Users/tlienart/.julia/packages/Atom/mngyX/src/comm.jl:164
   [23] (::getfield(Atom, Symbol("##19#21")){Array{Any,1}})() at ./task.jl:261

see also test/file_watching

servedocs(MyModule)?

Suggestion in #43 to have

servedocs(MyModule)

So a few considerations:

  • I'm not entirely sure the use case is relevant, wouldn't the person just do that in the right folder? My workflow is to open Atom in the MyModule.jl folder and then you'd have to using MyModule, LiveServer; servedocs() which doesn't seem like too much to ask especially compared to Documenter which requires basically something similar?)
  • if MyModule is active, then this can work indeed via dirname(pathof(MyModule))
  • if MyModule is not active then this will not work and then something extra needs to be done to make the module active first (using MyModule).

Maybe something that could be done is a macro

@servedocs MyModule

which would

  • check if MyModule is active, if not activate it
  • run servedocs in the appropriate location

Tag a first release

Assuming you're roughly ok with

  • state of the docs rough
  • using my branch of HTTP.jl for now

I think we can tag a release and try to register the package, test it a bit more, and then eventually announce it.

In the "test it a bit more", I intend to hook it into JuDoc which should provide a good use case (and more tire kicking should there be any more sneaky bugs to find out).

Announcement on discourse

I guess now that we have a working 0.1.0 we should announce the package on Discourse :).

Here are things that I think should be mentioned but of course you should be the one presenting it 😄

  1. copy-paste the example from the readme
  2. explain that it can be used combined with Documenter to have live-rendering docs when editing the docs
  3. mention that it's inspired from "browser-sync"
  4. that it's meant for developping sites, not for interactive apps (for that people should consider Mux and all of that stuff)
  5. that we welcome comments and suggestions for improvements

Cheers!

PS: I wouldn't mention JuDoc, I want to fix a few more things before announcing that package soon. (btw, pre-rendering is working now as you suggested for KaTeX and highlight.js!!)

[docs] structure

I saw that you added a fair bit of content to filewatching.md which is great however I must say I was a bit confused by it all (it gives the impression that you can use the filewatcher for things that are unrelated to the server, this may be true but I don't think it should be advertised because it's not really what it's meant for... even if it's true I think we should concentrate for now on the primary goal for the package).

I therefore think it would help if we established the structure of the different parts of the docs together?

My thoughts for the docs would have been something like:

. Landing page: how to quickly get it going
. Overview of how it works
  . something short that corresponds to your nice doc/drawing in #5 and links to relevant subparts of docs
. Functionalities (public API)
  . explain in more details how serve works + keywords
  . explain verbose
  . explain servedocs
  . Note: explain that there may be errors if re-starting the server in a same julia session due to a bug in HTTP
. Extending LiveServer
  . extending simplewatcher to take your own callback
  . writing your own watcher
. Libs
  . Filewatching
    . when the file changes, the time of last change is compared, trigger a callback function
    . explain that the standard callback causes the browser refresh, refer to the relevant part of the doc in `server` that explains how the websockets work
  . Server
    . a server is started at localhost:port , upon a request (url entered in browser), the relevant file is served to the client and the file is marked as being watched with a websocket corresponding to the requester
    . each watched file has a number of viewers in the form of websockets (e.g. tabs)
    . a file change will cause the server to write on all websockets associated with the file (see `update_and_close_viewers`
  . Error handling
    . explain the different errors and how they're handled/propagated

of course can modify etc, what do you think?

Documentation

We need it. I'd suggest to go the default route with Documenter.jl and DocStringExtensions.jl for auto-documenting fields of structs etc. Any other suggestions?
Where could we host the docs? (Maybe there's also a "default" for that, but I'm not aware of it...)

Get CI working

I'll work on tests eventually but it'd be good if you could go to

not urgent, just opening the issue so we don't lose that in the ocean of emails (I know I'm responsible for 90% of that).

Normal HTTP connections kept open

To enable arbitrarily long websocket connections, we set readtimeout = 0 on HTTP.listen. The downside of this is that also standard HTTP requests are kept open. When running with verbose=true, one gets several Closed (x): ... Info's per viewer of index.html some minutes after having stopped the server. It's always the ones not corresponding to a websocket.

This is invisible in non-verbose mode, and does not bind many resources, so it is not severe. But it should be addressed anyway, which needed to be done in HTTP.jl. So we'd have to think about a good way of doing this (having a websocket-specific readtimeout with default 0) and file another pull request...

Client-side loop to re-establish broken WS connection

This is not entirely reproducible but try:

  • open
  • click on the download zip link
  • get back on the page
  • modify source --> might not trigger update

It's unclear to me why but it should be sorted out.

Edit: just add this to the docs with explanation provided by Jonas

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.