Initial release

This commit is contained in:
Michaël Lemaire 2021-09-07 00:16:35 +02:00
commit 0a9301037e
17 changed files with 246 additions and 0 deletions

9
.editorconfig Normal file
View file

@ -0,0 +1,9 @@
root = true
[*.{ts,json}]
charset = utf-8
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 2
trim_trailing_whitespace = true

5
.gitignore vendored Normal file
View file

@ -0,0 +1,5 @@
deno.d.ts
.vscode
.local
.output
web/*.js

10
README.md Normal file
View file

@ -0,0 +1,10 @@
# typescript/devserver
[![Build Status](https://thunderk.visualstudio.com/typescript/_apis/build/status/devserver?branchName=master)](https://dev.azure.com/thunderk/typescript/_build?pipelineNameFilter=devserver)
## About
Web development server for other typescript projects.
This utility serves the local `web` directory, bundling requested js files on
the fly from sources.

4
TODO.md Normal file
View file

@ -0,0 +1,4 @@
# TODO
- Cache
- Autoreload

6
cli.ts Executable file
View file

@ -0,0 +1,6 @@
#!./run
import { runDevServer } from "./src/server.ts";
if (import.meta.main) {
await runDevServer();
}

1
config/run.flags Normal file
View file

@ -0,0 +1 @@
--allow-net=0.0.0.0 --allow-read=. --allow-run=deno

1
config/test.flags Normal file
View file

@ -0,0 +1 @@
--allow-read=.

5
deps.testing.ts Normal file
View file

@ -0,0 +1,5 @@
export {
describe,
expect,
it,
} from "https://js.thunderk.net/testing@1.0.0/mod.ts";

9
deps.ts Normal file
View file

@ -0,0 +1,9 @@
export { Sys } from "https://js.thunderk.net/system@1.0.0/mod.ts";
export {
listenAndServe,
ServerRequest,
} from "https://deno.land/std@0.106.0/http/server.ts";
export type { Response as ServerResponse } from "https://deno.land/std@0.106.0/http/server.ts";
export { serveFile } from "https://deno.land/std@0.106.0/http/file_server.ts";
export { posix } from "https://deno.land/std@0.106.0/path/mod.ts";

6
doc/about.md Normal file
View file

@ -0,0 +1,6 @@
## About
Web development server for other typescript projects.
This utility serves the local `web` directory, bundling requested js files on
the fly from sources.

1
doc/index Normal file
View file

@ -0,0 +1 @@
about

19
run Executable file
View file

@ -0,0 +1,19 @@
#!/bin/sh
# Simplified run tool for deno commands
if test $# -eq 0
then
echo "Usage: $0 [file or command]"
exit 1
elif echo $1 | grep -q '.*.ts'
then
denocmd=run
denoargs=$1
shift
else
denocmd=$1
shift
fi
denoargs="$(cat config/$denocmd.flags 2> /dev/null) $denoargs $@"
exec deno $denocmd $denoargs

8
src/server.test.ts Normal file
View file

@ -0,0 +1,8 @@
import { describe, expect, it } from "../deps.testing.ts";
import { normalizeURL } from "./server.ts";
describe("server", () => {
it("normalizes URLs", () => {
expect(normalizeURL("/dir/file.txt?t=1")).toEqual("/dir/file.txt");
});
});

133
src/server.ts Normal file
View file

@ -0,0 +1,133 @@
// Adapted from https://deno.land/std@0.106.0/http/file_server.ts
import {
listenAndServe,
posix,
serveFile,
ServerRequest,
ServerResponse,
Sys,
} from "../deps.ts";
export function runDevServer(): void {
const target = posix.resolve("./web/");
const handler = async (req: ServerRequest) => {
let response: ServerResponse | undefined;
try {
const normalizedUrl = normalizeURL(req.url);
let fsPath = posix.join(target, normalizedUrl);
if (fsPath.indexOf(target) !== 0) {
fsPath = target;
}
const srcPath = fsPath.replace(/\/web\/(.*)\.js$/, "/$1.ts");
if (srcPath != fsPath) {
// TODO check only local
await bundle(srcPath, fsPath);
}
const fileInfo = await Deno.stat(fsPath);
if (fileInfo.isDirectory) {
response = await serveFile(req, posix.join(fsPath, "index.html"));
} else {
response = await serveFile(req, fsPath);
}
} catch (e) {
console.error(e.message);
response = await serveFallback(req, e);
} finally {
serverLog(req, response!);
try {
await req.respond(response!);
} catch (e) {
console.error(e.message);
}
}
};
const port = 4507;
const host = "0.0.0.0";
const addr = `${host}:${port}`;
listenAndServe(addr, handler);
console.log(`Dev server listening on http://${addr}/`);
}
export function normalizeURL(url: string): string {
let normalizedUrl = url;
try {
normalizedUrl = decodeURI(normalizedUrl);
} catch (e) {
if (!(e instanceof URIError)) {
throw e;
}
}
try {
//allowed per https://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html
const absoluteURI = new URL(normalizedUrl);
normalizedUrl = absoluteURI.pathname;
} catch (e) { //wasn't an absoluteURI
if (!(e instanceof TypeError)) {
throw e;
}
}
if (normalizedUrl[0] !== "/") {
throw new URIError("The request URI is malformed.");
}
normalizedUrl = posix.normalize(normalizedUrl);
const startOfParams = normalizedUrl.indexOf("?");
return startOfParams > -1
? normalizedUrl.slice(0, startOfParams)
: normalizedUrl;
}
const encoder = new TextEncoder();
function serveFallback(_req: ServerRequest, e: Error): Promise<ServerResponse> {
if (e instanceof URIError) {
return Promise.resolve({
status: 400,
body: encoder.encode("Bad Request"),
});
} else if (e instanceof Deno.errors.NotFound) {
return Promise.resolve({
status: 404,
body: encoder.encode("Not Found"),
});
} else {
return Promise.resolve({
status: 500,
body: encoder.encode("Internal server error"),
});
}
}
function serverLog(req: ServerRequest, res: ServerResponse): void {
const d = new Date().toISOString();
const dateFmt = `[${d.slice(0, 10)} ${d.slice(11, 19)}]`;
const s = `${dateFmt} "${req.method} ${req.url} ${req.proto}" ${res.status}`;
console.log(s);
}
async function run(...cmd: string[]): Promise<boolean> {
const process = Sys.run({ cmd });
const result = await process.status();
return result.success;
}
async function bundle(
source: string,
destination: string,
): Promise<void> {
// TODO add config flags
if (
await run("deno", "bundle", source, destination) &&
await run("deno", "fmt", destination)
) {
return;
} else {
console.log(`Bundle failed for: ${source}`);
}
}

10
tsconfig.json Normal file
View file

@ -0,0 +1,10 @@
{
"compilerOptions": {
"module": "esnext",
"target": "ESNext",
"strict": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"preserveConstEnums": true
}
}

3
web-check.ts Normal file
View file

@ -0,0 +1,3 @@
export function check() {
alert("All good!");
}

16
web/index.html Normal file
View file

@ -0,0 +1,16 @@
<html>
<head>
<meta charset="utf-8">
<script type="module">
import { check } from "./web-check.js";
check();
</script>
</head>
<body>
<h1>Test</h1>
<p>Should get an alert...</p>
</body>
</html>