we typescript now

This commit is contained in:
Hamcha 2023-11-30 10:14:32 +01:00
parent a8d2ed380e
commit d7d814015f
14 changed files with 5147 additions and 106 deletions

4
.gitignore vendored
View File

@ -2,4 +2,6 @@
/dist
.env
.vscode
/result
/result
/static/scripts/**/*.js*
/static/components/**/*.js*

12
.swcrc Normal file
View File

@ -0,0 +1,12 @@
{
"sourceMaps": true,
"jsc": {
"parser": {
"syntax": "typescript",
"tsx": false,
"decorators": false,
"dynamicImport": false
},
"target": "esnext"
}
}

1846
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -25,3 +25,8 @@ time = { version = "0.3", features = ["serde"] }
tokio = { version = "1", features = ["full"] }
tracing = "0.1"
tracing-subscriber = "0.3"
[build-dependencies]
swc = "0.269"
swc_common = { version = "0.33", features = ["tty-emitter"] }
glob = "0.3"

61
build.rs Normal file
View File

@ -0,0 +1,61 @@
use std::{
fs,
path::{Path, PathBuf},
};
use swc::config::{Options, SourceMapsConfig};
use swc_common::{
self,
errors::{ColorConfig, Handler},
sync::Lrc,
SourceMap, GLOBALS,
};
fn main() {
// Find all Typescript files (that are not type declaration)
let ts_files: Vec<_> = glob::glob(&format!("{}/**/*.ts", "static/source"))
.expect("Failed to read glob pattern")
.filter_map(|entry| entry.ok())
.filter(|entry| !entry.to_string_lossy().ends_with(".d.ts"))
.collect();
for ts_file in ts_files {
compile_file(ts_file);
}
}
fn compile_file(input: PathBuf) {
let cm: Lrc<SourceMap> = Default::default();
let handler = Handler::with_tty_emitter(ColorConfig::Auto, true, false, Some(cm.clone()));
let c = swc::Compiler::new(cm.clone());
let fm = cm
.load_file(Path::new(&input))
.expect("failed to load input typescript file");
GLOBALS.set(&Default::default(), || {
let output_path = input.with_extension("js");
let sourcemap_path = input.with_extension("js.map");
let res = c
.process_js_file(
fm,
&handler,
&Options {
swcrc: true,
filename: input.to_string_lossy().into_owned(),
output_path: Some(output_path.clone()),
source_maps: Some(SourceMapsConfig::Bool(true)),
..Default::default()
},
)
.expect("parse error");
fs::create_dir_all(output_path.parent().unwrap()).expect("failed to create parent dir");
fs::write(output_path, res.code).expect("failed to write output file");
if let Some(map) = res.map {
fs::write(sourcemap_path, map).expect("failed to write output file");
}
});
}

View File

@ -1,47 +0,0 @@
/**
* Checks if a specificied source is a valid arion-compose.nix file
* @param {string} source Source to check
* @returns {Promise<CheckResult>} Result of the check
*/
export async function check_compose(source) {
const response = await fetch("/stack/_/check", {
method: "POST",
headers: {
"Accept": "application/json",
"Content-Type": "text/plain",
},
body: source
});
if (response.ok) {
return { ok: true };
} else {
return { ok: false, error: await response.json() };
}
}
/**
* @typedef {Object} APIError
* @property {string} code - Error code
*/
/**
* @typedef {Object} CheckSuccess
* @property { true } ok - Indicates that the check was successful
* /
/**
* @typedef {Object} APIError
* @property {string} code - Unique error code
* @property {string} message - Human readable error details
*/
/**
* @typedef {Object} CheckFailure
* @property {false} ok - Indicates that the check has failed
* @property {APIError} error - Info about the encountered error
*/
/**
* @typedef {CheckSuccess | CheckFailure} CheckResult
*/

View File

@ -1,17 +0,0 @@
/**
* Find nearest parent that satisfies a requirement
* @param {Node|Element} node
* @param {(el: Element) => boolean} findFn
* @returns {HTMLElement | null} Found element, or null if no parent matches
*/
export function findNearestParent(node, findFn) {
let parent = node.parentElement;
while (parent) {
if (findFn(parent)) {
return parent;
}
parent = parent.parentElement;
}
return null;
}

View File

@ -1,24 +1,21 @@
import "/static/vendor/ace/ace.js";
import { $el } from "../../vendor/domutil/domutil.js";
import { $el } from "/static/vendor/domutil/domutil.js";
class Editor extends HTMLElement {
editor;
/** @type {HTMLDivElement} Tab container */
tabEl;
/** @type {Map<string, AceSession>} File name to file session */
files;
private editor: AceAjax.Editor;
private tabEl: HTMLDivElement;
private files: Record<string, AceAjax.IEditSession>;
private root: ShadowRoot;
constructor() {
super();
this.files = {};
this.attachShadow({ mode: "open" });
this.root = this.attachShadow({ mode: "open" });
}
connectedCallback() {
this.tabEl = $el("div", { className: "tab-container" });
this.shadowRoot.append(this.tabEl);
this.root.append(this.tabEl);
const style = $el("style");
style.textContent = `
@ -40,7 +37,7 @@ class Editor extends HTMLElement {
}
`;
const slot = $el("slot", { name: "body" });
this.shadowRoot.append(style, slot);
this.root.append(style, slot);
const editorEl = $el("div", { slot: "body" });
this.append(editorEl);
@ -61,10 +58,10 @@ class Editor extends HTMLElement {
/**
* Add a file editing session
* @param {string} name File name
* @param {string} content File contents
* @param name File name
* @param content File contents
*/
addFile(name, content) {
addFile(name: string, content: string) {
// Add to session list
this.files[name] = ace.createEditSession(content);
@ -78,10 +75,10 @@ class Editor extends HTMLElement {
/**
* Create a new tab
* @param {string} name Tab name
* @param name Tab name
* @returns Tab element
*/
#addTab(name) {
#addTab(name: string) {
const tab = $el("div", {
className: "tab",
"@click": () => {

36
static/scripts/compose.ts Normal file
View File

@ -0,0 +1,36 @@
/**
* Checks if a specificied source is a valid arion-compose.nix file
* @param source Source to check
* @returns Result of the check
*/
export async function check_compose(source: string): Promise<CheckResult> {
const response = await fetch("/stack/_/check", {
method: "POST",
headers: {
"Accept": "application/json",
"Content-Type": "text/plain",
},
body: source
});
if (response.ok) {
return { ok: true };
} else {
return { ok: false, error: await response.json() };
}
}
export interface APIError {
/** Error code */
code: string;
/** Human readable error details */
message: string;
}
export type CheckResult = {
ok: true;
} | {
ok: false;
error: APIError;
}

View File

@ -1,12 +1,11 @@
import Editor from "../ace.mjs";
import { check_compose } from "/static/js/compose.mjs";
import { check_compose } from "/static/scripts/compose.js";
/**
* Makes the form require the stack to be checked before submitting
* @param {HTMLFormElement} form
* @param {Editor} editor
* @param form
* @param editor
*/
export function add_check(form, editor) {
export function add_check(form: HTMLFormElement, editor: AceAjax.Editor) {
form.addEventListener("submit", (ev) => {
ev.preventDefault();
check_stack(editor).then((result) => {
@ -20,15 +19,15 @@ export function add_check(form, editor) {
/**
* Runs the check function and updates some DOM elements
* @param {Editor} editor Editor instance
* @param editor Editor instance
* @returns true if the stack is valid, false otherwise
*/
export async function check_stack(editor) {
const source = editor.editor.getValue();
export async function check_stack(editor: AceAjax.Editor) {
const source = editor.getValue();
const check_result = await check_compose(source);
const editorEl = document.querySelector(".ace_editor");
const editorErrorEl = document.querySelector("#editor-form .error");
const editorEl = document.querySelector(".ace_editor")!;
const editorErrorEl = document.querySelector<HTMLDivElement>("#editor-form div.error")!;
editorEl.classList.remove("err", "checked");
editorErrorEl.style.display = "block";
editorErrorEl.classList.add("pending");

View File

@ -0,0 +1,16 @@
/**
* Find nearest parent that satisfies a requirement
* @param node Node to start from
* @param findFn Matching function
* @returns Found element, or null if no parent matches
*/
export function findNearestParent(node: Node | Element, findFn: (el: Element) => boolean): HTMLElement | null {
let parent = node.parentElement;
while (parent) {
if (findFn(parent)) {
return parent;
}
parent = parent.parentElement;
}
return null;
}

3139
static/vendor/ace/ace.d.ts vendored Normal file

File diff suppressed because it is too large Load Diff

22
tsconfig.json Normal file
View File

@ -0,0 +1,22 @@
{
"compilerOptions": {
"target": "esnext",
"paths": {
"/static/*": [
"./static/*"
],
},
"lib": [
"ESNext",
"DOM"
],
"rootDir": "./",
"baseUrl": "./",
"strictNullChecks": true,
"declaration": true,
},
"include": [
"./static/**/*.ts",
"./static/**/*.d.ts"
]
}