diff --git a/cmd/tools/vwatch.v b/cmd/tools/vwatch.v index d315767546..19c9e12c6b 100644 --- a/cmd/tools/vwatch.v +++ b/cmd/tools/vwatch.v @@ -309,11 +309,18 @@ fn main() { context.pid = os.getpid() context.vexe = os.getenv('VEXE') - mut fp := flag.new_flag_parser(os.args[1..]) + watch_pos := os.args.index('watch') + all_args_before_watch_cmd := os.args#[1..watch_pos] + all_args_after_watch_cmd := os.args#[watch_pos + 1..] + // dump(os.getpid()) + // dump(all_args_before_watch_cmd) + // dump(all_args_after_watch_cmd) + + // Options after `run` should be ignored, since they are intended for the user program, not for the watcher. + // For example, `v watch run x.v -a -b -k', should pass all of -a -b -k to the compiled and run program. + only_watch_options, has_run := all_before('run', all_args_after_watch_cmd) + mut fp := flag.new_flag_parser(only_watch_options) fp.application('v watch') - if os.args[1] == 'watch' { - fp.skip_executable() - } fp.version('0.0.2') fp.description('Collect all .v files needed for a compilation, then re-run the compilation when any of the source changes.') fp.arguments_description('[--silent] [--clear] [--ignore .db] [--add /path/to/a/file.v] [run] program.v') @@ -336,7 +343,12 @@ fn main() { eprintln('Error: ${err}') exit(1) } - context.opts = remaining_options + context.opts = [] + context.opts << all_args_before_watch_cmd + context.opts << remaining_options + if has_run { + context.opts << all_after('run', all_args_after_watch_cmd) + } context.elog('>>> context.pid: ${context.pid}') context.elog('>>> context.vexe: ${context.vexe}') context.elog('>>> context.opts: ${context.opts}') @@ -347,14 +359,15 @@ fn main() { if context.is_worker { context.worker_main() } else { - context.manager_main() + context.manager_main(all_args_before_watch_cmd, all_args_after_watch_cmd) } } -fn (mut context Context) manager_main() { +fn (mut context Context) manager_main(all_args_before_watch_cmd []string, all_args_after_watch_cmd []string) { myexecutable := os.executable() - mut worker_opts := ['--vwatchworker'] - worker_opts << os.args[2..] + mut worker_opts := all_args_before_watch_cmd.clone() + worker_opts << ['watch', '--vwatchworker'] + worker_opts << all_args_after_watch_cmd for { mut worker_process := os.new_process(myexecutable) worker_process.set_args(worker_opts) @@ -384,3 +397,19 @@ fn (mut context Context) worker_main() { spawn context.compilation_runner_loop() change_detection_loop(context) } + +fn all_before(needle string, all []string) ([]string, bool) { + needle_pos := all.index(needle) + if needle_pos == -1 { + return all, false + } + return all#[..needle_pos + 1], true +} + +fn all_after(needle string, all []string) []string { + needle_pos := all.index(needle) + if needle_pos == -1 { + return all + } + return all#[needle_pos + 1..] +} diff --git a/examples/vweb/footer.html b/examples/vweb/footer.html new file mode 100644 index 0000000000..be5d660933 --- /dev/null +++ b/examples/vweb/footer.html @@ -0,0 +1,4 @@ +

+footer + + diff --git a/examples/vweb/header.html b/examples/vweb/header.html index c91231097f..0a5c6ae15b 100644 --- a/examples/vweb/header.html +++ b/examples/vweb/header.html @@ -1 +1,8 @@ + + + + +vweb example page + + header

diff --git a/examples/vweb/index.html b/examples/vweb/index.html index 736f8d643a..c8eb24669d 100644 --- a/examples/vweb/index.html +++ b/examples/vweb/index.html @@ -21,3 +21,4 @@ For loop demo:

End. +@include 'footer.html' diff --git a/vlib/v/markused/markused.v b/vlib/v/markused/markused.v index 8b3efb58f6..12de6e552c 100644 --- a/vlib/v/markused/markused.v +++ b/vlib/v/markused/markused.v @@ -195,6 +195,11 @@ pub fn mark_used(mut table ast.Table, pref_ &pref.Preferences, ast_files []&ast. } } + if k.ends_with('before_request') { + // TODO: add a more specific check for the .before_request() method in vweb apps + all_fn_root_names << k + continue + } if method_receiver_typename == '&sync.Channel' { all_fn_root_names << k continue diff --git a/vlib/v/pref/pref.v b/vlib/v/pref/pref.v index 87a7a24f0e..93d6ff2474 100644 --- a/vlib/v/pref/pref.v +++ b/vlib/v/pref/pref.v @@ -807,7 +807,7 @@ pub fn parse_args_and_show_errors(known_external_commands []string, args []strin if command == '' { command = arg command_pos = i - if res.is_eval_argument || command in ['run', 'crun'] { + if res.is_eval_argument || command in ['run', 'crun', 'watch'] { break } } else if is_source_file(command) && is_source_file(arg) diff --git a/vlib/vweb/vweb.v b/vlib/vweb/vweb.v index ebeca9b3b3..59c99201dc 100644 --- a/vlib/vweb/vweb.v +++ b/vlib/vweb/vweb.v @@ -167,7 +167,8 @@ pub mut: header http.Header // response headers // ? It doesn't seem to be used anywhere - form_error string + form_error string + livereload_poll_interval_ms int = 250 } struct FileData { @@ -190,8 +191,8 @@ pub fn (ctx Context) init_server() { } // Defining this method is optional. -// This method called before every request (aka middleware). -// Probably you can use it for check user session cookie or add header. +// This method is called before every request (aka middleware). +// You can use it for checking user session cookies or to add headers. pub fn (ctx Context) before_request() {} // TODO - test @@ -202,17 +203,22 @@ pub fn (mut ctx Context) send_response_to_client(mimetype string, res string) bo return false } ctx.done = true - - // build header - header := http.new_header_from_map({ - http.CommonHeader.content_type: mimetype - http.CommonHeader.content_length: res.len.str() - }).join(ctx.header) - + // mut resp := http.Response{ - header: header.join(vweb.headers_close) body: res } + $if vweb_livereload ? { + if mimetype == 'text/html' { + resp.body = res.replace('', '\n') + } + } + // build the header after the potential modification of resp.body from above + header := http.new_header_from_map({ + http.CommonHeader.content_type: mimetype + http.CommonHeader.content_length: resp.body.len.str() + }).join(ctx.header) + resp.header = header.join(vweb.headers_close) + // resp.set_version(.v1_1) resp.set_status(http.status_from_int(ctx.status.int())) send_string(mut ctx.conn, resp.bytestr()) or { return false } @@ -504,6 +510,19 @@ fn handle_conn[T](mut conn net.TcpConn, mut app T, routes map[string]Route) { // Calling middleware... app.before_request() + $if vweb_livereload ? { + if url.path.starts_with('/vweb_livereload/') { + if url.path.ends_with('current') { + app.handle_vweb_livereload_current() + return + } + if url.path.ends_with('script.js') { + app.handle_vweb_livereload_script() + return + } + } + } + // Static handling if serve_if_static[T](mut app, url) { // successfully served a static file diff --git a/vlib/vweb/vweb_livereload.v b/vlib/vweb/vweb_livereload.v new file mode 100644 index 0000000000..4d09c1950e --- /dev/null +++ b/vlib/vweb/vweb_livereload.v @@ -0,0 +1,48 @@ +module vweb + +import time + +// Note: to use live reloading while developing, the suggested workflow is doing: +// `v -d vweb_livereload watch --keep run your_vweb_server_project.v` +// in one shell, then open the start page of your vweb app in a browser. +// +// While developing, just open your files and edit them, then just save your +// changes. Once you save, the watch command from above, will restart your server, +// and your HTML pages will detect that shortly, then they will refresh themselves +// automatically. + +// vweb_livereload_server_start records, when the vweb server process started. +// That is later used by the /script.js and /current endpoints, which are active, +// if you have compiled your vweb project with `-d vweb_livereload`, to detect +// whether the web server has been restarted. +const vweb_livereload_server_start = time.ticks().str() + +// handle_vweb_livereload_current serves a small text file, containing the +// timestamp/ticks corresponding to when the vweb server process was started +[if vweb_livereload ?] +fn (mut ctx Context) handle_vweb_livereload_current() { + ctx.send_response_to_client('text/plain', vweb.vweb_livereload_server_start) +} + +// handle_vweb_livereload_script serves a small dynamically generated .js file, +// that contains code for polling the vweb server, and reloading the page, if it +// detects that the vweb server is newer than the vweb server, that served the +// .js file originally. +[if vweb_livereload ?] +fn (mut ctx Context) handle_vweb_livereload_script() { + res := '"use strict"; +function vweb_livereload_checker_fn(started_at) { + fetch("/vweb_livereload/" + started_at + "/current", { cache: "no-cache" }) + .then(response=>response.text()) + .then(function(current_at) { + // console.log(started_at); console.log(current_at); + if(started_at !== current_at){ + // the app was restarted on the server: + window.location.reload(); + } + }); +} +const vweb_livereload_checker = setInterval(vweb_livereload_checker_fn, ${ctx.livereload_poll_interval_ms}, "${vweb.vweb_livereload_server_start}"); +' + ctx.send_response_to_client('text/javascript', res) +}