Skip to content

Commit 3904446

Browse files
authored
chore: Compile ts programs given an entry point rather than all sources upfront (#1214)
chore: Compile ts programs given an entry point rather than all sources upfront.
1 parent 243edf7 commit 3904446

File tree

27 files changed

+370
-310
lines changed

27 files changed

+370
-310
lines changed

packages/js-runtime/cli/cli.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { populateArtifact, relativeToAbsolute } from "./utils.ts";
21
import {
32
Args,
43
type Command,
@@ -7,6 +6,7 @@ import {
76
} from "./interface.ts";
87
import { Processor } from "./processor.ts";
98
import { parseArgs } from "@std/cli/parse-args";
9+
import { join } from "@std/path";
1010

1111
export class RuntimeCLI {
1212
private cwd: string;
@@ -16,15 +16,15 @@ export class RuntimeCLI {
1616
this.processor = new Processor();
1717
}
1818

19-
async parse(input: string[]): Promise<Command> {
19+
parse(input: string[]): Command {
2020
const args = parseCLIArgs(this.cwd, input);
2121
if (args.help) {
2222
return { type: CommandType.Help };
2323
}
2424

2525
const runCommand: RunCommand = {
2626
type: CommandType.Run,
27-
source: await populateArtifact(args.files),
27+
entry: args.entry,
2828
};
2929
if (args.noCheck) runCommand.noCheck = args.noCheck;
3030
if (args.noRun) runCommand.noRun = args.noRun;
@@ -62,14 +62,20 @@ export function parseCLIArgs(cwd: string, input: string[]): Args {
6262
],
6363
});
6464

65+
const entry = (parsed["_"] ?? []).shift();
66+
if (!entry) {
67+
throw new Error("Missing entry.");
68+
}
6569
return {
66-
files: (parsed["_"] ?? []).map((filepath) =>
67-
relativeToAbsolute(cwd, String(filepath))
68-
),
70+
entry: relativeToAbsolute(cwd, String(entry)),
6971
help: !!parsed.help,
7072
verbose: !!parsed.verbose,
7173
noCheck: !!parsed["no-check"],
7274
noRun: !!parsed["no-run"],
7375
out: parsed.out ? relativeToAbsolute(cwd, parsed.out) : undefined,
7476
};
7577
}
78+
79+
function relativeToAbsolute(rootDir: string, filepath: string): string {
80+
return join(rootDir, filepath);
81+
}

packages/js-runtime/cli/interface.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
import { type TsArtifact } from "../interface.ts";
1+
import { type Program } from "../interface.ts";
22

33
export enum CommandType {
44
Run = "run",
55
Help = "help",
66
}
77

88
export interface Args {
9-
files: string[];
9+
entry: string;
1010
help?: boolean;
1111
verbose?: boolean;
1212
noRun?: boolean;
@@ -21,7 +21,7 @@ export interface Command {
2121

2222
export interface RunCommand extends Command {
2323
type: CommandType.Run;
24-
source: TsArtifact;
24+
entry: string;
2525
verbose?: boolean;
2626
noRun?: boolean;
2727
noCheck?: boolean;

packages/js-runtime/cli/processor.ts

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,50 @@
1-
import * as path from "@std/path";
2-
import { Command, CommandType, RunCommand } from "./interface.ts";
3-
import { getTypeLibs, TypeScriptCompiler, UnsafeEvalRuntime } from "../mod.ts";
1+
import { RunCommand } from "./interface.ts";
2+
import {
3+
getTypeLibs,
4+
ProgramGraph,
5+
Source,
6+
TypeScriptCompiler,
7+
UnsafeEvalRuntime,
8+
} from "../mod.ts";
9+
import { basename, dirname, join } from "@std/path";
10+
11+
class CliProgram implements ProgramGraph {
12+
private fsRoot: string;
13+
private _entry: Source;
14+
constructor(entryPath: string) {
15+
this.fsRoot = dirname(entryPath);
16+
this._entry = {
17+
name: entryPath.substring(this.fsRoot.length),
18+
contents: Deno.readTextFileSync(entryPath),
19+
};
20+
}
21+
22+
entry(): Source {
23+
return this._entry;
24+
}
25+
26+
resolveSource(specifier: string): Source | undefined {
27+
if (specifier && specifier[0] === "/") {
28+
const absPath = join(
29+
this.fsRoot,
30+
specifier.substring(1, specifier.length),
31+
);
32+
return {
33+
name: specifier,
34+
contents: Deno.readTextFileSync(absPath),
35+
};
36+
}
37+
return undefined;
38+
}
39+
}
440

541
export class Processor {
642
async run(command: RunCommand): Promise<any> {
43+
const program = new CliProgram(command.entry);
744
const compiler = new TypeScriptCompiler(await getTypeLibs());
8-
const compiled = compiler.compile(command.source, {
45+
const compiled = compiler.compile(program, {
946
noCheck: !!command.noCheck,
10-
filename: command.out ? path.basename(command.out) : undefined,
47+
filename: command.out ? basename(command.out) : undefined,
1148
});
1249

1350
if (command.noRun) {

packages/js-runtime/cli/utils.ts

Lines changed: 0 additions & 41 deletions
This file was deleted.

packages/js-runtime/deno.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "@commontools/js-runtime",
33
"tasks": {
44
"run": "deno run --allow-read --allow-env=\"TSC_*\",NODE_INSPECTOR_IPC,VSCODE_INSPECTOR_OPTIONS,NODE_ENV ./cli/mod.ts",
5-
"test": "deno test --allow-read --allow-run --allow-env=\"TSC_*\",NODE_INSPECTOR_IPC,VSCODE_INSPECTOR_OPTIONS,NODE_ENV"
5+
"test": "deno test --allow-read --allow-run --allow-env=\"TSC_*\",NODE_INSPECTOR_IPC,VSCODE_INSPECTOR_OPTIONS,NODE_ENV test/*.test.ts"
66
},
77
"imports": {
88
"source-map-js": "npm:source-map-js"

packages/js-runtime/eval-runtime.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ExecutableJs, JsIsolate, JsRuntime, SourceMap } from "./interface.ts";
1+
import { JsIsolate, JsRuntime, JsScript, SourceMap } from "./interface.ts";
22
import { SourceMapParser } from "./source-map.ts";
33

44
export class UnsafeEvalJsValue {
@@ -53,7 +53,7 @@ class IsolateInternals {
5353
export class UnsafeEvalIsolate implements JsIsolate {
5454
private internals = new IsolateInternals();
5555
execute(
56-
input: string | ExecutableJs,
56+
input: string | JsScript,
5757
): UnsafeEvalJsValue {
5858
const { js, filename, sourceMap } = typeof input === "string"
5959
? { js: input, filename: "NO-NAME.js" }

packages/js-runtime/interface.ts

Lines changed: 18 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export interface JsValue {
1818
// A JS runtime context.
1919
export interface JsIsolate {
2020
// Execute `js` within this `JsIsolate`, returning the value.
21-
execute(js: string | ExecutableJs): JsValue;
21+
execute(js: string | JsScript): JsValue;
2222
}
2323

2424
// A `JsRuntime` can host several `JsIsolate`s, capable
@@ -48,57 +48,35 @@ export interface TypeDefSource extends Source {
4848
contents: string;
4949
}
5050

51-
// A map of filename to typescript source.
52-
export interface TsArtifact {
53-
entry: string;
54-
files: Source[];
51+
export interface Compiler<T> {
52+
compile(input: Program | ProgramResolver, options: T): JsScript;
5553
}
5654

57-
// A transformed (TypeScript) module.
58-
export interface JsModule {
59-
// The generated JS from the source TS.
60-
contents: string;
61-
// Input source filename.
62-
originalFilename: string;
63-
// The generated source map definition.
64-
sourceMap: SourceMap;
65-
// The generated .d.ts source.
66-
// Not currently generated, but typechecked
67-
typesSrc?: string;
55+
// A program's entry point with a resolver to
56+
// resolve other sources used in the program.
57+
export interface ProgramResolver {
58+
entry(): Source;
59+
resolveSource(identifier: string): Source | undefined;
6860
}
6961

70-
export const isJsModule = (value: unknown): value is JsModule =>
71-
!!(typeof value === "object" && value &&
72-
"originalFilename" in value &&
73-
typeof value.originalFilename === "string" &&
74-
"contents" in value && typeof value.contents === "string" &&
75-
"sourceMap" in value && typeof value.sourceMap === "object" &&
76-
value.sourceMap &&
77-
"typesSrc" in value
78-
? typeof value.typesSrc === "string"
79-
: true);
80-
81-
// A collection of JS modules with an entry point.
82-
export interface JsArtifact {
62+
// An entry point and its sources for a program.
63+
export interface Program {
8364
entry: string;
84-
modules: Record<string, JsModule>;
65+
files: Source[];
66+
}
67+
68+
export function isProgram(value: unknown): value is Program {
69+
return !!value && typeof value === "object" && "entry" in value &&
70+
typeof value.entry === "string" && "files" in value &&
71+
Array.isArray(value.files);
8572
}
8673

8774
// A ready-to-execute string of JavaScript,
8875
// with optional metadata.
89-
export interface ExecutableJs {
76+
export interface JsScript {
9077
js: string;
9178
sourceMap?: SourceMap;
9279
filename?: string;
9380
}
9481

9582
export interface SourceMap extends RawSourceMap {}
96-
97-
export const isSourceMap = (value: unknown): value is SourceMap =>
98-
!!(value && typeof value === "object" &&
99-
"version" in value && value.version === "3" &&
100-
"file" in value && typeof value.file === "string" &&
101-
"sourceRoot" in value && typeof value.sourceRoot === "string" &&
102-
"sources" in value && Array.isArray(value.sources) &&
103-
"names" in value && Array.isArray(value.names) &&
104-
"mappings" in value && typeof value.mappings === "string");

packages/js-runtime/mod.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
export type {
2+
Compiler,
23
CompilerError,
3-
ExecutableJs,
44
JsIsolate,
55
JsRuntime,
6-
TsArtifact,
6+
JsScript,
7+
Program,
8+
ProgramResolver as ProgramGraph,
9+
Source,
710
} from "./interface.ts";
811
export {
912
TypeScriptCompiler,
@@ -14,4 +17,4 @@ export {
1417
UnsafeEvalJsValue,
1518
UnsafeEvalRuntime,
1619
} from "./eval-runtime.ts";
17-
export { getTypeLibs } from "./utils.ts";
20+
export { getTypeLibs } from "./typescript/utils.ts";

packages/js-runtime/source-map.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,3 +77,29 @@ function mapIsEmpty(position: MappedPosition): boolean {
7777
return position.source === null && position.name === null &&
7878
position.line === null && position.column === null;
7979
}
80+
81+
export const isSourceMap = (value: unknown): value is SourceMap =>
82+
!!(value && typeof value === "object" &&
83+
"version" in value && value.version === "3" &&
84+
"file" in value && typeof value.file === "string" &&
85+
"sourceRoot" in value && typeof value.sourceRoot === "string" &&
86+
"sources" in value && Array.isArray(value.sources) &&
87+
"names" in value && Array.isArray(value.names) &&
88+
"mappings" in value && typeof value.mappings === "string");
89+
90+
// Parses string as a `SourceMap`, or throws if unable.
91+
export function parseSourceMap(stringMap: string): SourceMap {
92+
const sourceMap = JSON.parse(stringMap);
93+
if (sourceMap && "version" in sourceMap) {
94+
// TypeScript correctly generates `version` as an integer,
95+
// but the `source-map-js` library's `RawSourceMap` we use
96+
// elsewhere expects `version` to be a string.
97+
sourceMap.version = `${sourceMap.version}`;
98+
}
99+
if (!isSourceMap(sourceMap)) {
100+
throw new Error(
101+
`Could not parse source map: ${JSON.stringify(sourceMap, null, 2)}`,
102+
);
103+
}
104+
return sourceMap;
105+
}

packages/js-runtime/test/cli-fixtures/index.tsx

Lines changed: 0 additions & 3 deletions
This file was deleted.

0 commit comments

Comments
 (0)