Skip to content

Commit

Permalink
Merge pull request #104 from unyt-org/new-decorators
Browse files Browse the repository at this point in the history
Migrate to new ES6 decorators
  • Loading branch information
benStre authored Mar 3, 2024
2 parents 9a853ce + d65b22f commit 3260054
Show file tree
Hide file tree
Showing 29 changed files with 488 additions and 386 deletions.
3 changes: 1 addition & 2 deletions docs/manual/01 Getting Started.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,7 @@ This is why UIX ships with integrated features such as:
To install uix, you need to install [Deno](https://docs.deno.com/runtime/manual/getting_started/installation) first.

> [!WARNING]
> UIX is currently only supported for Deno versions <= 1.39.4. We are actively working on a
> release that is compatible with newer Deno versions.
> UIX is only supported for Deno versions > 1.40.0
#### Linux / MacOS

Expand Down
24 changes: 6 additions & 18 deletions run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

import { Datex, datex } from "datex-core-legacy/no_init.ts"; // required by getAppConfig
import type { Datex as _Datex } from "datex-core-legacy"; // required by getAppConfig

import { getAppOptions } from "./src/app/config-files.ts";
import { getExistingFile } from "./src/utils/file-utils.ts";
import { clear, command_line_options, enableTLS, login, init, rootPath, stage, watch, watch_backend, live } from "./src/app/args.ts";
Expand All @@ -22,10 +21,13 @@ import { getDXConfigData } from "./src/app/dx-config-parser.ts";
import { Path } from "./src/utils/path.ts";
import { handleAutoUpdate, updateCache } from "./auto-update.ts";

import "./src/base/uix-datex-module.ts"

import { enableErrorReporting } from "datex-core-legacy/utils/error-reporting.ts";
import { getErrorReportingPreference, saveErrorReportingPreference, shouldAskForErrorReportingPreference } from "./src/utils/error-reporting-preference.ts";
import { isCIRunner } from "./src/utils/check-ci.ts";
import { logger, runParams } from "./src/runners/runner.ts";
import { applyPlugins } from "./src/app/config-files.ts";

// login flow
if (login) await triggerLogin();
Expand Down Expand Up @@ -144,30 +146,16 @@ async function loadPlugins() {
return plugins;
}

/**
* Mock #public.uix
*/
async function mockUIX() {

await datex`
#public.uix = {
stage: function (options) (
options.${stage} default @@local
)
}
`
}
await mockUIX();
// find importmap (from app.dx or deno.json) to start the actual deno process with valid imports
const plugins = await loadPlugins();
const runners = [new LocalDockerRunner()];
const [options, new_base_url] = await normalizeAppOptions(await getAppOptions(rootPath, plugins), rootPath);
const [options, new_base_url] = await normalizeAppOptions(await getAppOptions(rootPath), rootPath);
if (!options.import_map) throw new Error("Could not find importmap");

options.import_map = await createProxyImports(options, new_base_url, params.deno_config_path!);

// make sure UIX mock is not overridden
await mockUIX();
await applyPlugins(plugins, rootPath, options)


await runBackends(options);

Expand Down
5 changes: 4 additions & 1 deletion src/app/app-plugin.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { Path } from "datex-core-legacy/utils/path.ts";
import type { normalizedAppOptions } from "./options.ts";

export interface AppPlugin<Data = unknown> {
name: string
apply(data:Data): Promise<void>|void
apply(data:Data, rootPath:Path.File, appOptions:normalizedAppOptions): Promise<void>|void
}
35 changes: 20 additions & 15 deletions src/app/config-files.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
// no explicit imports, should also work without import maps...
import {getExistingFile} from "../utils/file-utils.ts";
import { command_line_options } from "../app/args.ts";
import { Path } from "../utils/path.ts";
import { Path } from "datex-core-legacy/utils/path.ts";
import { Datex } from "datex-core-legacy/mod.ts";
import type { AppPlugin } from "./app-plugin.ts";
import { logger } from "../utils/global-values.ts";
import { normalizedAppOptions } from "./options.ts";

const default_importmap = "https://dev.cdn.unyt.org/importmap.json";
const arg_import_map_string = command_line_options.option("import-map", {type:"string", description: "Import map path"});
Expand All @@ -15,10 +16,27 @@ const arg_import_map = (arg_import_map_string?.startsWith("http://")||arg_import
undefined
)

export async function applyPlugins(plugins: AppPlugin[], rootPath:Path, appOptions: normalizedAppOptions) {
const config_path = getExistingFile(rootPath, './app.dx', './app.json');

if (!config_path) throw "Could not find an app.dx or app.json config file in " + new Path(rootPath).normal_pathname

// handle plugins (only if in dev environment, not on host, TODO: better solution)
if (plugins?.length && !Deno.env.get("UIX_HOST_ENDPOINT")) {
const pluginData = await datex.get<Record<string,any>>(config_path, undefined, undefined, plugins.map(p=>p.name));
for (const plugin of plugins) {
if (pluginData[plugin.name]) {
logger.debug(`using plugin "${plugin.name}"`);
await plugin.apply(pluginData[plugin.name], rootPath, appOptions)
}
}
}
}

/**
* get combined config of app.dx and deno.json and command line args
*/
export async function getAppOptions(root_path:URL, plugins?: AppPlugin[]) {
export async function getAppOptions(root_path:URL) {
const config_path = getExistingFile(root_path, './app.dx', './app.json');
let config:Record<string,unknown> = {}

Expand All @@ -29,18 +47,6 @@ export async function getAppOptions(root_path:URL, plugins?: AppPlugin[]) {
throw "Invalid config file"
}
config = Object.fromEntries(Datex.DatexObject.entries(<Record<string, unknown>>raw_config));

// handle plugins (only if in dev environment, not on host, TODO: better solution)
if (plugins?.length && !Deno.env.has("UIX_HOST_ENDPOINT")) {
const pluginData = await datex.get<Record<string,any>>(config_path, undefined, undefined, plugins.map(p=>p.name));
for (const plugin of plugins) {
if (pluginData[plugin.name]) {
logger.debug(`using plugin "${plugin.name}"`);
await plugin.apply(pluginData[plugin.name])
}
}
}

}
else throw "Could not find an app.dx or app.json config file in " + new Path(root_path).normal_pathname

Expand Down Expand Up @@ -70,7 +76,6 @@ export async function getAppOptions(root_path:URL, plugins?: AppPlugin[]) {
} catch {}
}


if (!config.import_map && !config.import_map_path) config.import_map_path = default_importmap;
// if (config.import_map) throw "embeded import maps are not yet supported for uix apps";

Expand Down
26 changes: 17 additions & 9 deletions src/app/frontend-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { TypescriptImportResolver } from "../server/ts-import-resolver.ts";
import { $$, Datex } from "datex-core-legacy";
import { Server, requestMetadata } from "../server/server.ts";
import { ALLOWED_ENTRYPOINT_FILE_NAMES, app } from "./app.ts";
import { Path } from "../utils/path.ts";
import { Path } from "datex-core-legacy/utils/path.ts";
import { BackendManager } from "./backend-manager.ts";
import { getExistingFile, getExistingFileExclusive } from "../utils/file-utils.ts";
import { logger } from "../utils/global-values.ts";
Expand Down Expand Up @@ -87,11 +87,12 @@ export class FrontendManager extends HTMLProvider {

initFrontendDir(){
this.transpiler = new Transpiler(this.scope, {
sourceMap: app.stage == "dev",
sourceMaps: this.app_options.source_maps ?? app.stage == "dev",
watch: this.#watch,
minifyJS: this.app_options.minify_js,
import_resolver: this.import_resolver,
on_file_update: this.#watch ? ()=>this.handleFrontendReload() : undefined
on_file_update: this.#watch ? ()=>this.handleFrontendReload() : undefined,
basePath: this.#base_path
});
}

Expand All @@ -104,7 +105,7 @@ export class FrontendManager extends HTMLProvider {
intCommonDirs() {
for (const common_dir of this.app_options.common) {
const transpiler = new Transpiler(new Path(common_dir), {
sourceMap: app.stage == "dev",
sourceMaps: this.app_options.source_maps ?? app.stage == "dev",
dist_parent_dir: this.transpiler.tmp_dir,
watch: this.#watch,
minifyJS: this.app_options.minify_js,
Expand All @@ -118,7 +119,8 @@ export class FrontendManager extends HTMLProvider {
this.#backend?.handleUpdate("common");
}
this.handleFrontendReload();
} : undefined
} : undefined,
basePath: this.#base_path
})
this.#common_transpilers.set(common_dir.toString(), [transpiler, this.srcPrefix + new Path(common_dir).name + '/'])

Expand Down Expand Up @@ -190,7 +192,7 @@ export class FrontendManager extends HTMLProvider {


#backend?: BackendManager
#base_path!:Path
#base_path!:Path.File
#web_path!: Path
#entrypoint?: Path

Expand Down Expand Up @@ -827,7 +829,13 @@ if (!window.location.origin.endsWith(".unyt.app")) {
if (content instanceof Blob || content instanceof Response) return [content, RenderMethod.RAW_CONTENT, status_code, openGraphData, headers];

// Markdown
if (content instanceof Datex.Markdown) return [getOuterHTML(<Element> content.getHTML(false), {includeShadowRoots:true, injectStandaloneJS:render_method!=RenderMethod.STATIC&&render_method!=RenderMethod.HYBRID, injectStandaloneComponents:render_method!=RenderMethod.STATIC&&render_method!=RenderMethod.HYBRID/*TODO: should also work with HYBRID, but cannot create standalone component class and new class later*/, allowIgnoreDatexFunctions:(render_method==RenderMethod.HYBRID||render_method==RenderMethod.PREVIEW), lang}), render_method, status_code, openGraphData, headers];
if (content instanceof Datex.Markdown) return [getOuterHTML(<Element> content.getHTML(false), {
includeShadowRoots:true,
injectStandaloneJS:render_method!=RenderMethod.STATIC,
injectStandaloneComponents:render_method!=RenderMethod.STATIC,
allowIgnoreDatexFunctions:(render_method==RenderMethod.HYBRID||render_method==RenderMethod.PREVIEW),
lang
}), render_method, status_code, openGraphData, headers];

// convert content to valid HTML string
if (content instanceof Element || content instanceof DocumentFragment) {
Expand All @@ -836,8 +844,8 @@ if (!window.location.origin.endsWith(".unyt.app")) {
content as Element,
{
includeShadowRoots:true,
injectStandaloneJS:render_method!=RenderMethod.STATIC&&render_method!=RenderMethod.HYBRID,
injectStandaloneComponents:render_method!=RenderMethod.STATIC&&render_method!=RenderMethod.HYBRID,
injectStandaloneJS:render_method!=RenderMethod.STATIC,
injectStandaloneComponents:render_method!=RenderMethod.STATIC,
allowIgnoreDatexFunctions:(render_method==RenderMethod.HYBRID||render_method==RenderMethod.PREVIEW),
lang,
requiredPointers
Expand Down
55 changes: 37 additions & 18 deletions src/app/options.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { Tuple } from "datex-core-legacy/types/tuple.ts";
import { ImportMap } from "../utils/importmap.ts";
import { Path } from "../utils/path.ts";
import { Path } from "datex-core-legacy/utils/path.ts";

declare const Datex: any; // cannot import Datex here, circular dependency problems

Expand Down Expand Up @@ -28,6 +28,7 @@ export type appOptions = {
debug_mode?: boolean // enable debug interfaces available on /@debug/...
minify_js?: boolean // minify transpiled javascript modules, default: true
preload_dependencies?: boolean // automatically preload all ts module dependencies, default: true
source_maps?: boolean // generate source maps for transpiled javascript modules, default: false, true for dev stage
}

export interface normalizedAppOptions extends appOptions {
Expand All @@ -43,14 +44,14 @@ export interface normalizedAppOptions extends appOptions {

experimentalFeatures: string[]
}
export async function normalizeAppOptions(options:appOptions = {}, base_url?:string|URL): Promise<[normalizedAppOptions, URL]> {
export async function normalizeAppOptions(options:appOptions = {}, baseURL?:string|URL): Promise<[normalizedAppOptions, Path.File]> {
const n_options = <normalizedAppOptions> {};

// determine base url
if (typeof base_url == "string" && !base_url.startsWith("file://")) base_url = 'file://' + base_url;
base_url ??= new Error().stack?.trim()?.match(/((?:https?|file)\:\/\/.*?)(?::\d+)*(?:$|\nevaluate@)/)?.[1];
if (!base_url) throw new Error("Could not determine the app base url (this should not happen)");
base_url = new URL(base_url.toString());
if (typeof baseURL == "string" && !baseURL.startsWith("file://")) baseURL = 'file://' + baseURL;
baseURL ??= new Error().stack?.trim()?.match(/((?:https?|file)\:\/\/.*?)(?::\d+)*(?:$|\nevaluate@)/)?.[1];
if (!baseURL) throw new Error("Could not determine the app base url (this should not happen)");
const basePath = Path.File(baseURL.toString());

n_options.name = options.name;
n_options.description = options.description;
Expand All @@ -67,6 +68,7 @@ export async function normalizeAppOptions(options:appOptions = {}, base_url?:str
n_options.debug_mode = options.debug_mode ?? false;
n_options.minify_js = options.minify_js ?? true;
n_options.preload_dependencies = options.preload_dependencies ?? true;
n_options.source_maps = options.source_maps;

// import map or import map path
if (options.import_map) n_options.import_map = new ImportMap(options.import_map);
Expand All @@ -76,24 +78,24 @@ export async function normalizeAppOptions(options:appOptions = {}, base_url?:str
else throw new Error("No importmap found or set in the app configuration") // should not happen

// default frontend, backend, common
if (options.frontend==undefined && new Path('./frontend/', base_url).fs_exists) options.frontend = [new Path('./frontend/', base_url)]
if (options.backend==undefined && new Path('./backend/', base_url).fs_exists) options.backend = [new Path('./backend/', base_url)]
if (options.common==undefined && new Path('./common/', base_url).fs_exists) options.common = [new Path('./common/', base_url)]
if (options.frontend==undefined && new Path('./frontend/', basePath).fs_exists) options.frontend = [new Path('./frontend/', basePath)]
if (options.backend==undefined && new Path('./backend/', basePath).fs_exists) options.backend = [new Path('./backend/', basePath)]
if (options.common==undefined && new Path('./common/', basePath).fs_exists) options.common = [new Path('./common/', basePath)]

// convert to arrays
const frontends = options.frontend instanceof Array ? options.frontend : options.frontend instanceof Datex.Tuple ? (options.frontend as unknown as Tuple<string>).toArray() : [options.frontend]
const backends = options.backend instanceof Array ? options.backend : options.backend instanceof Datex.Tuple ? (options.backend as unknown as Tuple<string>).toArray() : [options.backend]
const commons = options.common instanceof Array ? options.common : options.common instanceof Datex.Tuple ? (options.common as unknown as Tuple<string>).toArray() : [options.common]

// convert to absolute paths
n_options.frontend = frontends.filter(p=>!!p).map(p=>new Path<Path.Protocol.File>(p,base_url).asDir().fsCreateIfNotExists());
n_options.backend = backends.filter(p=>!!p).map(p=>new Path<Path.Protocol.File>(p,base_url).asDir().fsCreateIfNotExists());
n_options.common = commons.filter(p=>!!p).map(p=>new Path<Path.Protocol.File>(p,base_url).asDir().fsCreateIfNotExists());
n_options.frontend = frontends.filter(p=>!!p).map(p=>new Path<Path.Protocol.File>(p,basePath).asDir().fsCreateIfNotExists());
n_options.backend = backends.filter(p=>!!p).map(p=>new Path<Path.Protocol.File>(p,basePath).asDir().fsCreateIfNotExists());
n_options.common = commons.filter(p=>!!p).map(p=>new Path<Path.Protocol.File>(p,basePath).asDir().fsCreateIfNotExists());

// pages dir or default pages dir
if (options.pages) n_options.pages = new Path<Path.Protocol.File>(options.pages,base_url).asDir()
if (options.pages) n_options.pages = new Path<Path.Protocol.File>(options.pages,basePath).asDir()
else {
const defaultPagesDir = new Path<Path.Protocol.File>('./pages/', base_url);
const defaultPagesDir = new Path<Path.Protocol.File>('./pages/', basePath);
if (defaultPagesDir.fs_exists) n_options.pages = defaultPagesDir;
}

Expand All @@ -104,7 +106,7 @@ export async function normalizeAppOptions(options:appOptions = {}, base_url?:str

if (!n_options.frontend.length) {
// try to find the frontend dir
const frontend_dir = new Path<Path.Protocol.File>("./frontend/",base_url);
const frontend_dir = new Path<Path.Protocol.File>("./frontend/",basePath);
try {
if (!Deno.statSync(frontend_dir).isFile) n_options.frontend.push(frontend_dir)
}
Expand All @@ -113,7 +115,7 @@ export async function normalizeAppOptions(options:appOptions = {}, base_url?:str

if (!n_options.backend.length) {
// try to find the backend dir
const backend_dir = new Path<Path.Protocol.File>("./backend/",base_url);
const backend_dir = new Path<Path.Protocol.File>("./backend/",basePath);
try {
if (!Deno.statSync(backend_dir).isFile) n_options.backend.push(backend_dir)
}
Expand All @@ -122,12 +124,29 @@ export async function normalizeAppOptions(options:appOptions = {}, base_url?:str

if (!n_options.common.length) {
// try to find the common dir
const common_dir = new Path<Path.Protocol.File>("./common/",base_url);
const common_dir = new Path<Path.Protocol.File>("./common/",basePath);
try {
if (!Deno.statSync(common_dir).isFile) n_options.common.push(common_dir)
}
catch {}
}

return [n_options, base_url]
return [n_options, basePath]
}


export function getInferredRunPaths(importMap: ImportMap, rootPath: Path.File): {importMapPath: string|null, uixRunPath: string|null} {
const importMapPath = importMap.originalPath ? importMap.originalPath.getAsRelativeFrom(rootPath) : null;
const inferredAbsoluteRunPath = importMap.imports["uix"]?.replace(/\/uix\.ts$/, '/run.ts') ?? null as string|null;
const uixRunPath = inferredAbsoluteRunPath ?
(
Path.pathIsURL(inferredAbsoluteRunPath) ?
inferredAbsoluteRunPath :
new Path(inferredAbsoluteRunPath, importMap.path).getAsRelativeFrom(rootPath)) :
null;

return {
importMapPath,
uixRunPath
}
}
9 changes: 8 additions & 1 deletion src/app/start-app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import { getDirType } from "./utils.ts";
import { WebSocketServerInterface } from "datex-core-legacy/network/communication-interfaces/websocket-server-interface.ts"
import { HTTPServerInterface } from "datex-core-legacy/network/communication-interfaces/http-server-interface.ts"
import { communicationHub } from "datex-core-legacy/network/communication-hub.ts";
import { resolveDependencies } from "../html/dependency-resolver.ts";
import { resolve } from "https://deno.land/std@0.172.0/path/win32.ts";

const logger = new Datex.Logger("UIX App");

Expand Down Expand Up @@ -67,7 +69,9 @@ export async function startApp(app: {domains:string[], hostDomains: string[], op
// also override endpoint default
if (backend_with_default_export) {
Datex.Runtime.endpoint_entrypoint = backend_with_default_export.entrypointProxy;
backend_with_default_export.content_provider[Datex.DX_SOURCE] = Datex.Runtime.endpoint.toString(); // use @@local::#entrypoint as dx source
const content_provider = backend_with_default_export.content_provider;
if ((content_provider && typeof content_provider == "object") || typeof content_provider == "function")
(content_provider as any)[Datex.DX_SOURCE] = Datex.Runtime.endpoint.toString(); // use @@local::#entrypoint as dx source
}

let server:Server|undefined
Expand Down Expand Up @@ -166,6 +170,9 @@ export async function startApp(app: {domains:string[], hostDomains: string[], op
const {HTTP} = await import("./http-over-datex.ts")
HTTP.setServer(server);
}

// preload dependencies
resolveDependencies(import.meta.resolve("datex-core-legacy"))

return {
defaultServer: server,
Expand Down
Loading

0 comments on commit 3260054

Please sign in to comment.