@travetto/compiler 3.0.0-rc.3 → 3.0.0-rc.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  <!-- This file was generated by @travetto/doc and should not be modified directly -->
2
- <!-- Please modify https://github.com/travetto/travetto/tree/main/module/compiler/doc.ts and execute "npx trv doc" to rebuild -->
2
+ <!-- Please modify https://github.com/travetto/travetto/tree/main/module/compiler/DOC.ts and execute "npx trv doc" to rebuild -->
3
3
  # Compiler
4
- ## Node-integration of Typescript Compiler with advanced functionality for detecting changes in classes and methods.
4
+ ## Compiler
5
5
 
6
6
  **Install: @travetto/compiler**
7
7
  ```bash
@@ -17,9 +17,12 @@ This module expands upon [Typescript](https://typescriptlang.org), with suppleme
17
17
  * Support for detecting changes in sources files at runtime
18
18
  * Allows for hot-reloading of classes during development
19
19
  * Utilizes `es2015` `Proxy`s to allow for swapping out implementation at runtime
20
-
21
20
  Additionally, there is support for common AST transformations via [Transformation](https://github.com/travetto/travetto/tree/main/module/transformer#readme "Functionality for AST transformations, with transformer registration, and general utils")
22
-
23
21
  ## Debugging
24
-
25
22
  When dealing with transformers, logging is somewhat tricky as the compiler executes before the code is loaded. To that end, the file `compiler.log` is created in the cache directory during the compilation process. This is a location that transformers should be free to log to, for debugging, and any additional feedback.
23
+
24
+ ## CLI
25
+
26
+ The module provides the ability to clear the compilation cache to handle any inconsistencies that may arise.
27
+
28
+ TODO: Describe cli behavior
package/__index__.ts ADDED
@@ -0,0 +1,6 @@
1
+ export * from './src/compiler';
2
+ export * from './src/state';
3
+ export * from './src/util';
4
+ export * from './src/watch';
5
+ export * from './src/log';
6
+ export * from './src/types';
package/bin/trv.js ADDED
@@ -0,0 +1,71 @@
1
+ #!/usr/bin/env node
2
+
3
+ // @ts-check
4
+ import fs from 'fs/promises';
5
+ import path from 'path';
6
+ import { createRequire } from 'module';
7
+
8
+ import { getManifestContext } from '@travetto/manifest/bin/context.js';
9
+
10
+ const VALID_OPS = { watch: 'watch', build: 'build', clean: 'clean', manifest: 'manifest' };
11
+
12
+ const COMPILER_FILES = [...['launcher', 'transpile', 'lock', 'log', 'lock-pinger'].map(x => `support/${x}.js`), 'package.json'];
13
+
14
+ /**
15
+ * @param {import('@travetto/manifest').ManifestContext} ctx
16
+ * @return {Promise<import('@travetto/compiler/support/launcher').launch>}
17
+ */
18
+ const $getLauncher = async (ctx) => {
19
+ const compPkg = createRequire(path.resolve(ctx.workspacePath, 'node_modules')).resolve('@travetto/compiler/package.json');
20
+ const files = [];
21
+
22
+ for (const file of COMPILER_FILES) {
23
+ const target = path.resolve(ctx.workspacePath, ctx.compilerFolder, 'node_modules', '@travetto/compiler', file);
24
+ const src = compPkg.replace('package.json', file.replace(/[.]js$/, '.ts'));
25
+
26
+ const targetTime = await fs.stat(target).then(s => Math.max(s.mtimeMs, s.ctimeMs)).catch(() => 0);
27
+ const srcTime = await fs.stat(src).then(s => Math.max(s.mtimeMs, s.ctimeMs));
28
+ // If stale
29
+ if (srcTime > targetTime) {
30
+ const ts = (await import('typescript')).default;
31
+ const module = ctx.moduleType === 'module' ? ts.ModuleKind.ESNext : ts.ModuleKind.CommonJS;
32
+ await fs.mkdir(path.dirname(target), { recursive: true });
33
+ const text = await fs.readFile(src, 'utf8');
34
+ if (file.endsWith('.js')) {
35
+ const content = ts.transpile(
36
+ text,
37
+ { target: ts.ScriptTarget.ES2020, module, esModuleInterop: true, allowSyntheticDefaultImports: true }
38
+ )
39
+ .replace(/^((?:im|ex)port .*from '[.][^']+)(')/mg, (_, a, b) => `${a}.js${b}`)
40
+ .replace(/^(import [^\n]*from '[^.][^\n/]+[/][^\n/]+[/][^\n']+)(')/mg, (_, a, b) => `${a}.js${b}`);
41
+ await fs.writeFile(target, content, 'utf8');
42
+ } else {
43
+ const pkg = JSON.parse(text);
44
+ pkg.type = ctx.moduleType;
45
+ await fs.writeFile(target, JSON.stringify(pkg, null, 2), 'utf8');
46
+ }
47
+ // Compile
48
+ }
49
+ files.push(target);
50
+ }
51
+
52
+ try { return require(files[0]).launch; }
53
+ catch { return import(files[0]).then(x => x.launch); }
54
+ };
55
+
56
+ (async () => {
57
+ const ctx = await getManifestContext();
58
+ const [op, args] = [VALID_OPS[process.argv[2]], process.argv.slice(3)];
59
+
60
+ if (op === 'clean') {
61
+ const folders = process.argv.find(x => x === '--all' || x === '-a') ? [ctx.outputFolder, ctx.compilerFolder] : [ctx.outputFolder];
62
+ for (const f of folders) {
63
+ await fs.rm(path.resolve(ctx.workspacePath, f), { force: true, recursive: true });
64
+ }
65
+ return console.log(`Cleaned ${ctx.workspacePath}: [${folders.join(', ')}]`);
66
+ }
67
+
68
+ const rootCtx = ctx.monoRepo ? await getManifestContext(ctx.workspacePath) : ctx;
69
+
70
+ return (await $getLauncher(ctx))(ctx, rootCtx, op, args);
71
+ })();
package/package.json CHANGED
@@ -1,16 +1,11 @@
1
1
  {
2
2
  "name": "@travetto/compiler",
3
- "displayName": "Compiler",
4
- "version": "3.0.0-rc.3",
5
- "description": "Node-integration of Typescript Compiler with advanced functionality for detecting changes in classes and methods.",
3
+ "version": "3.0.0-rc.31",
4
+ "description": "Compiler",
6
5
  "keywords": [
7
- "tsc",
8
- "typescript",
9
6
  "compiler",
10
- "caching",
11
- "change-detection",
12
- "real-time",
13
- "travetto"
7
+ "travetto",
8
+ "typescript"
14
9
  ],
15
10
  "homepage": "https://travetto.io",
16
11
  "license": "MIT",
@@ -18,21 +13,43 @@
18
13
  "email": "travetto.framework@gmail.com",
19
14
  "name": "Travetto Framework"
20
15
  },
16
+ "type": "module",
21
17
  "files": [
22
- "index.ts",
18
+ "__index__.ts",
23
19
  "src",
24
- "support"
20
+ "bin",
21
+ "support",
22
+ "tsconfig.trv.json"
25
23
  ],
26
- "main": "index.ts",
24
+ "main": "__index__.ts",
25
+ "bin": {
26
+ "trv": "bin/trv.js"
27
+ },
27
28
  "repository": {
28
29
  "url": "https://github.com/travetto/travetto.git",
29
30
  "directory": "module/compiler"
30
31
  },
31
32
  "dependencies": {
32
- "@travetto/base": "^3.0.0-rc.1",
33
- "@travetto/transformer": "^3.0.0-rc.3",
34
- "@travetto/watch": "^3.0.0-rc.1"
33
+ "@parcel/watcher": "^2.1.0",
34
+ "@travetto/manifest": "^3.0.0-rc.17",
35
+ "@travetto/terminal": "^3.0.0-rc.10",
36
+ "@travetto/transformer": "^3.0.0-rc.21"
37
+ },
38
+ "peerDependencies": {
39
+ "@travetto/cli": "^3.0.0-rc.22"
40
+ },
41
+ "peerDependenciesMeta": {
42
+ "@travetto/cli": {
43
+ "optional": true
44
+ }
45
+ },
46
+ "travetto": {
47
+ "displayName": "Compiler",
48
+ "profiles": [
49
+ "compile"
50
+ ]
35
51
  },
52
+ "private": false,
36
53
  "publishConfig": {
37
54
  "access": "public"
38
55
  }
package/src/compiler.ts CHANGED
@@ -1,187 +1,148 @@
1
- import { EventEmitter } from 'events';
2
- import * as sourceMapSupport from 'source-map-support';
3
- import * as ts from 'typescript';
1
+ import { install } from 'source-map-support';
2
+ import ts from 'typescript';
3
+ import fs from 'fs/promises';
4
4
 
5
- import { PathUtil, EnvUtil, AppCache } from '@travetto/boot';
6
- import { SourceIndex } from '@travetto/boot/src/internal/source';
7
- import { ModuleManager } from '@travetto/boot/src/internal/module';
8
- import { Dynamic } from '@travetto/base/src/internal/dynamic';
9
- import { TranspileUtil } from '@travetto/boot/src/internal/transpile-util';
5
+ import { GlobalTerminal, TerminalProgressEvent } from '@travetto/terminal';
6
+ import { RootIndex } from '@travetto/manifest';
10
7
 
11
- import { SourceHost } from './host';
12
- import { TransformerManager } from './transformer';
13
-
14
- type FileListener = (name: string) => void;
15
- type EventType = 'added' | 'removed' | 'changed';
8
+ import { CompilerUtil } from './util';
9
+ import { CompilerState } from './state';
10
+ import { CompilerWatcher } from './watch';
11
+ import { Log } from './log';
12
+ import { CompileEmitError, CompileEmitEvent, CompileEmitter } from './types';
16
13
 
17
14
  /**
18
- * Compilation orchestrator, interfaces with watching, unloading, emitting and delegates appropriately
15
+ * Compilation support
19
16
  */
20
- @Dynamic('@travetto/compiler/support/dynamic.compiler')
21
- class $Compiler {
22
-
23
- #program: ts.Program | undefined;
24
- #transformerManager = new TransformerManager();
25
- #emitter = new EventEmitter();
26
- #host = new SourceHost();
27
-
28
- active = false;
17
+ export class Compiler {
29
18
 
30
19
  /**
31
- * Build typescript program
32
- *
33
- * @param forFile If this file is new, force a recompilation
20
+ * Run compiler as a main entry point
34
21
  */
35
- #getProgram(forFile?: string): ts.Program {
22
+ static async main(): Promise<void> {
23
+ const [dirty, watch] = process.argv.slice(2);
24
+ const state = await CompilerState.get(RootIndex);
25
+ const dirtyFiles = (await fs.readFile(dirty, 'utf8')).split(/\n/).filter(x => !!x);
26
+ await new Compiler(state, dirtyFiles, watch === 'true').run();
27
+ process.exit(0);
28
+ }
29
+
30
+ #state: CompilerState;
31
+ #dirtyFiles: string[];
32
+ #watch?: boolean;
36
33
 
37
- const rootFiles = this.#host.getRootFiles();
34
+ constructor(state: CompilerState, dirtyFiles: string[], watch?: boolean) {
35
+ this.#state = state;
36
+ this.#dirtyFiles = dirtyFiles[0] === '*' ?
37
+ this.#state.getAllFiles() :
38
+ dirtyFiles.map(f => this.#state.getBySource(f)!.input);
39
+ this.#watch = watch;
40
+ }
38
41
 
39
- if (!this.#program || (forFile && !rootFiles.has(forFile))) {
40
- console.debug('Loading program', { size: rootFiles.size, src: forFile });
41
- if (forFile) {
42
- rootFiles.add(forFile);
42
+ /**
43
+ * Watches local modules
44
+ */
45
+ #watchLocalModules(emit: CompileEmitter): Promise<() => Promise<void>> {
46
+ return new CompilerWatcher(this.#state).watchFiles(async file => {
47
+ const err = await emit(file, true);
48
+ if (err) {
49
+ Log.info('Compilation Error', CompilerUtil.buildTranspileError(file, err));
50
+ } else {
51
+ Log.info(`Compiled ${file.split('node_modules/')[1]}`);
43
52
  }
44
- this.#program = ts.createProgram({
45
- rootNames: [...rootFiles],
46
- options: TranspileUtil.compilerOptions,
47
- host: this.#host,
48
- oldProgram: this.#program
49
- });
50
- this.#transformerManager.build(this.#program.getTypeChecker());
51
- }
52
- return this.#program;
53
+ return err;
54
+ });
53
55
  }
54
56
 
55
57
  /**
56
- * Perform actual transpilation
58
+ * Compile in a single pass, only emitting dirty files
57
59
  */
58
- #transpile(filename: string, force = false): string {
59
- if (force || !AppCache.hasEntry(filename)) {
60
- console.debug('Emitting', { filename: filename.replace(PathUtil.cwd, '.') });
60
+ async getCompiler(): Promise<CompileEmitter> {
61
+ let program: ts.Program;
61
62
 
63
+ const emit = async (inputFile: string, needsNewProgram = program === undefined): Promise<CompileEmitError | undefined> => {
62
64
  try {
63
- const prog = this.#getProgram(filename);
64
-
65
- const result = prog.emit(
66
- prog.getSourceFile(filename),
67
- undefined,
68
- undefined,
69
- false,
70
- this.#transformerManager.getTransformers()
71
- );
72
-
73
- TranspileUtil.checkTranspileErrors(filename, result.diagnostics);
65
+ if (needsNewProgram) {
66
+ program = this.#state.createProgram(program);
67
+ }
68
+ const result = this.#state.writeInputFile(program, inputFile);
69
+ if (result?.diagnostics?.length) {
70
+ return result.diagnostics;
71
+ }
74
72
  } catch (err) {
75
- if (!(err instanceof Error)) {
73
+ if (err instanceof Error) {
74
+ return err;
75
+ } else {
76
76
  throw err;
77
77
  }
78
- const errContent = TranspileUtil.transpileError(filename, err);
79
- this.#host.contents.set(filename, errContent);
80
78
  }
81
- // Save writing for typescript program (`writeFile`)
82
- } else {
83
- this.#host.fetchFile(filename);
84
- }
79
+ };
85
80
 
86
- return this.#host.contents.get(filename)!;
81
+ return emit;
87
82
  }
88
83
 
89
84
  /**
90
- * Get program
91
- * @private
85
+ * Emit all files as a stream
92
86
  */
93
- getProgram(): ts.Program {
94
- return this.#getProgram();
87
+ async * emit(files: string[], emitter: CompileEmitter): AsyncIterable<CompileEmitEvent> {
88
+ let i = 0;
89
+ for (const file of files) {
90
+ const err = await emitter(file);
91
+ const imp = file.replace(/.*node_modules\//, '');
92
+ yield { file: imp, i: i += 1, err, total: files.length };
93
+ }
94
+ Log.debug(`Compiled ${i} files`);
95
95
  }
96
96
 
97
97
  /**
98
- * Initialize the compiler
98
+ * Run the compiler
99
99
  */
100
- async init(): Promise<void> {
101
- if (this.active) {
102
- return;
103
- }
104
-
105
- const start = Date.now();
106
- this.active = true;
100
+ async run(): Promise<void> {
101
+ await GlobalTerminal.init();
102
+ install();
107
103
 
108
- if (!EnvUtil.isReadonly()) {
109
- await this.#transformerManager.init();
110
- // Enhance transpilation, with custom transformations
111
- ModuleManager.setTranspiler(tsf => this.#transpile(tsf));
112
- }
104
+ Log.debug('Compilation started');
113
105
 
114
- ModuleManager.onUnload((f, unlink) => this.#host.unload(f, unlink)); // Remove source
106
+ process.on('disconnect', () => process.exit(0));
115
107
 
116
- // Update source map support to read from transpiler cache
117
- sourceMapSupport.install({
118
- retrieveFile: p => this.#host.contents.get(PathUtil.toUnixTs(p))!
119
- });
108
+ const emitter = await this.getCompiler();
109
+ let failed = false;
120
110
 
121
- console.debug('Initialized', { duration: (Date.now() - start) / 1000 });
122
- }
111
+ Log.debug('Compiler loaded');
123
112
 
124
- /**
125
- * Reset the compiler
126
- */
127
- reset(): void {
128
- if (!EnvUtil.isReadonly()) {
129
- this.#transformerManager.reset();
130
- this.#host.reset();
131
- this.#program = undefined;
132
- }
133
- ModuleManager.clearUnloadHandlers();
134
- SourceIndex.reset();
135
- this.active = false;
136
- }
137
-
138
- /**
139
- * Notify of an add/remove/change event
140
- */
141
- notify(type: EventType, filename: string): void {
142
- console.debug('File Event', { type, filename: filename.replace(PathUtil.cwd, '.') });
143
- this.#emitter.emit(type, filename);
144
- }
145
-
146
- /**
147
- * Listen for events
148
- */
149
- on(type: EventType, handler: FileListener): this {
150
- this.#emitter.on(type, handler);
151
- return this;
152
- }
153
-
154
- /**
155
- * Unload if file is known
156
- */
157
- added(filename: string): void {
158
- if (filename in require.cache) { // if already loaded
159
- ModuleManager.unload(filename);
113
+ const resolveEmittedFile = ({ file, total, i, err }: CompileEmitEvent): TerminalProgressEvent => {
114
+ if (err) {
115
+ failed = true;
116
+ console.error(CompilerUtil.buildTranspileError(file, err));
117
+ }
118
+ return { idx: i, total, text: `Compiling [%idx/%total] -- ${file}` };
119
+ };
120
+
121
+ if (this.#dirtyFiles.length) {
122
+ await GlobalTerminal.trackProgress(this.emit(this.#dirtyFiles, emitter), resolveEmittedFile, { position: 'bottom' });
123
+ if (failed) {
124
+ Log.debug('Compilation failed');
125
+ process.exit(1);
126
+ } else {
127
+ Log.debug('Compilation succeeded');
128
+ }
129
+ } else if (this.#watch) {
130
+ // Prime compiler before complete
131
+ const resolved = this.#state.getArbitraryInputFile();
132
+ await emitter(resolved, true);
160
133
  }
161
- // Load Synchronously
162
- require(filename);
163
- this.notify('added', filename);
164
- }
165
134
 
166
- /**
167
- * Handle when a file is removed during watch
168
- */
169
- removed(filename: string): void {
170
- ModuleManager.unload(filename, true);
171
- this.notify('removed', filename);
172
- }
135
+ process.send?.('build-complete');
173
136
 
174
- /**
175
- * When a file changes during watch
176
- */
177
- changed(filename: string): void {
178
- if (this.#host.hashChanged(filename)) {
179
- ModuleManager.unload(filename);
180
- // Load Synchronously
181
- require(filename);
182
- this.notify('changed', filename);
137
+ if (this.#watch) {
138
+ Log.info('Watch is ready');
139
+ await this.#watchLocalModules(emitter);
140
+ const output = this.#state.resolveOutputFile('.');
141
+ for await (const _ of fs.watch(output)) {
142
+ if (!await fs.stat(output).catch(() => false)) {
143
+ process.send?.('restart');
144
+ }
145
+ }
183
146
  }
184
147
  }
185
148
  }
186
-
187
- export const Compiler = new $Compiler();
package/src/log.ts ADDED
@@ -0,0 +1,18 @@
1
+ import util from 'util';
2
+
3
+ import type { CompilerLogEvent } from '../support/log';
4
+
5
+ function log(level: 'info' | 'debug', message: string, ...args: unknown[]): void {
6
+ if (process.send) {
7
+ const ev: CompilerLogEvent = [level, util.format(message, ...args)];
8
+ process.send(ev);
9
+ } else {
10
+ // eslint-disable-next-line no-console
11
+ console[level](message, ...args);
12
+ }
13
+ }
14
+
15
+ export const Log = {
16
+ debug: log.bind(null, 'debug'),
17
+ info: log.bind(null, 'info')
18
+ };