Initial release
This commit is contained in:
commit
0a9301037e
9
.editorconfig
Normal file
9
.editorconfig
Normal 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
5
.gitignore
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
deno.d.ts
|
||||||
|
.vscode
|
||||||
|
.local
|
||||||
|
.output
|
||||||
|
web/*.js
|
10
README.md
Normal file
10
README.md
Normal 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.
|
6
cli.ts
Executable file
6
cli.ts
Executable file
|
@ -0,0 +1,6 @@
|
||||||
|
#!./run
|
||||||
|
import { runDevServer } from "./src/server.ts";
|
||||||
|
|
||||||
|
if (import.meta.main) {
|
||||||
|
await runDevServer();
|
||||||
|
}
|
1
config/run.flags
Normal file
1
config/run.flags
Normal file
|
@ -0,0 +1 @@
|
||||||
|
--allow-net=0.0.0.0 --allow-read=. --allow-run=deno
|
1
config/test.flags
Normal file
1
config/test.flags
Normal file
|
@ -0,0 +1 @@
|
||||||
|
--allow-read=.
|
5
deps.testing.ts
Normal file
5
deps.testing.ts
Normal 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
9
deps.ts
Normal 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
6
doc/about.md
Normal 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.
|
19
run
Executable file
19
run
Executable 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
8
src/server.test.ts
Normal 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
133
src/server.ts
Normal 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
10
tsconfig.json
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"module": "esnext",
|
||||||
|
"target": "ESNext",
|
||||||
|
"strict": true,
|
||||||
|
"noImplicitReturns": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"preserveConstEnums": true
|
||||||
|
}
|
||||||
|
}
|
3
web-check.ts
Normal file
3
web-check.ts
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
export function check() {
|
||||||
|
alert("All good!");
|
||||||
|
}
|
16
web/index.html
Normal file
16
web/index.html
Normal 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>
|
Loading…
Reference in a new issue