Initial release
This commit is contained in:
commit
0a9301037e
17 changed files with 246 additions and 0 deletions
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.
|
4
TODO.md
Normal file
4
TODO.md
Normal file
|
@ -0,0 +1,4 @@
|
|||
# TODO
|
||||
|
||||
- Cache
|
||||
- Autoreload
|
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.
|
1
doc/index
Normal file
1
doc/index
Normal file
|
@ -0,0 +1 @@
|
|||
about
|
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