@travetto/compiler 4.0.8 → 4.1.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@travetto/compiler",
3
- "version": "4.0.8",
3
+ "version": "4.1.0",
4
4
  "description": "The compiler infrastructure for the Travetto framework",
5
5
  "keywords": [
6
6
  "compiler",
@@ -31,12 +31,12 @@
31
31
  },
32
32
  "dependencies": {
33
33
  "@parcel/watcher": "^2.4.1",
34
- "@travetto/manifest": "^4.0.2",
35
- "@travetto/transformer": "^4.0.3",
36
- "@types/node": "^20.11.25"
34
+ "@travetto/manifest": "^4.1.0",
35
+ "@travetto/transformer": "^4.1.0",
36
+ "@types/node": "^20.12.12"
37
37
  },
38
38
  "peerDependencies": {
39
- "@travetto/cli": "^4.0.6"
39
+ "@travetto/cli": "^4.1.0"
40
40
  },
41
41
  "peerDependenciesMeta": {
42
42
  "@travetto/cli": {
package/src/compiler.ts CHANGED
@@ -67,6 +67,7 @@ export class Compiler {
67
67
  case 'error': {
68
68
  process.exitCode = 1;
69
69
  if (err) {
70
+ EventUtil.sendEvent('log', { level: 'error', message: err.toString(), time: Date.now() });
70
71
  log.error('Shutting down due to failure', err.message);
71
72
  }
72
73
  break;
@@ -127,7 +128,7 @@ export class Compiler {
127
128
  EventUtil.sendEvent('state', { state: 'init', extra: { pid: process.pid } });
128
129
 
129
130
  const emitter = await this.getCompiler();
130
- let failed = false;
131
+ let failure: Error | undefined;
131
132
 
132
133
  log.debug('Compiler loaded');
133
134
 
@@ -136,17 +137,16 @@ export class Compiler {
136
137
  if (this.#dirtyFiles.length) {
137
138
  for await (const ev of this.emit(this.#dirtyFiles, emitter)) {
138
139
  if (ev.err) {
139
- failed = true;
140
140
  const compileError = CompilerUtil.buildTranspileError(ev.file, ev.err);
141
+ failure ??= compileError;
141
142
  EventUtil.sendEvent('log', { level: 'error', message: compileError.toString(), time: Date.now() });
142
143
  }
143
144
  }
144
145
  if (this.#signal.aborted) {
145
146
  log.debug('Compilation aborted');
146
- } else if (failed) {
147
+ } else if (failure) {
147
148
  log.debug('Compilation failed');
148
- process.exitCode = 1;
149
- return;
149
+ return this.#shutdown('error', failure);
150
150
  } else {
151
151
  log.debug('Compilation succeeded');
152
152
  }
package/src/state.ts CHANGED
@@ -1,5 +1,4 @@
1
1
  import ts from 'typescript';
2
- import timers from 'node:timers/promises';
3
2
 
4
3
  import { path, ManifestModuleUtil, ManifestModule, ManifestRoot, ManifestIndex } from '@travetto/manifest';
5
4
  import { TransformerManager } from '@travetto/transformer';
@@ -8,6 +7,7 @@ import { TypescriptUtil } from '../support/ts-util';
8
7
 
9
8
  import { CompilerUtil } from './util';
10
9
  import { CompileEmitError, CompileStateEntry } from './types';
10
+ import { CommonUtil } from '../support/util';
11
11
 
12
12
  function folderMapper(root: string, prefix: string): { dir: string, translate: (val: string) => string } {
13
13
  let matched: string = '~~';
@@ -63,14 +63,15 @@ export class CompilerState implements ts.CompilerHost {
63
63
 
64
64
  // Register all inputs
65
65
  for (const x of this.#modules) {
66
+ const base = x?.files ?? {};
66
67
  const files = [
67
- ...x.files.bin ?? [],
68
- ...x.files.src ?? [],
69
- ...x.files.support ?? [],
70
- ...x.files.doc ?? [],
71
- ...x.files.test ?? [],
72
- ...x.files.$index ?? [],
73
- ...x.files.$package ?? []
68
+ ...base.bin ?? [],
69
+ ...base.src ?? [],
70
+ ...base.support ?? [],
71
+ ...base.doc ?? [],
72
+ ...base.test ?? [],
73
+ ...base.$index ?? [],
74
+ ...base.$package ?? []
74
75
  ];
75
76
  for (const [file, type] of files) {
76
77
  if (CompilerUtil.validFile(type) || type === 'typings') {
@@ -103,14 +104,18 @@ export class CompilerState implements ts.CompilerHost {
103
104
  }
104
105
 
105
106
  getArbitraryInputFile(): string {
106
- return this.getBySource(this.#manifestIndex.getModule('@travetto/manifest')!.files.src[0].sourceFile)!.inputFile;
107
+ const randomSource = this.#manifestIndex.getWorkspaceModules()
108
+ .filter(x => x.files.src?.length)[0]
109
+ .files.src[0].sourceFile;
110
+
111
+ return this.getBySource(randomSource)!.inputFile;
107
112
  }
108
113
 
109
114
  async createProgram(force = false): Promise<ts.Program> {
110
115
  if (force || !this.#program) {
111
116
  this.#program = ts.createProgram({ rootNames: this.getAllFiles(), host: this, options: this.#compilerOptions, oldProgram: this.#program });
112
117
  this.#transformerManager.init(this.#program.getTypeChecker());
113
- await timers.setImmediate();
118
+ await CommonUtil.queueMacroTask();
114
119
  }
115
120
  return this.#program;
116
121
  }
@@ -179,7 +184,7 @@ export class CompilerState implements ts.CompilerHost {
179
184
  if (!contents || (contents.length === 0 && prevHash)) {
180
185
  return false; // Ignore empty file
181
186
  }
182
- const currentHash = CompilerUtil.naiveHash(contents);
187
+ const currentHash = CommonUtil.naiveHash(contents);
183
188
  const changed = prevHash !== currentHash;
184
189
  if (changed) {
185
190
  this.#sourceHashes.set(inputFile, currentHash);
package/src/util.ts CHANGED
@@ -105,7 +105,6 @@ export class CompilerUtil {
105
105
  }
106
106
 
107
107
  const errors: string[] = diagnostics.slice(0, 5).map(diag => {
108
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
109
108
  const message = ts.flattenDiagnosticMessageText(diag.messageText, '\n');
110
109
  if (diag.file) {
111
110
  const { line, character } = diag.file.getLineAndCharacterOfPosition(diag.start!);
@@ -120,18 +119,4 @@ export class CompilerUtil {
120
119
  }
121
120
  return new Error(`Transpiling ${filename.replace(nativeCwd, '.')} failed: \n${errors.join('\n')}`);
122
121
  }
123
-
124
- /**
125
- * Naive hashing
126
- */
127
- static naiveHash(text: string): number {
128
- let hash = 5381;
129
-
130
- for (let i = 0; i < text.length; i++) {
131
- // eslint-disable-next-line no-bitwise
132
- hash = (hash * 33) ^ text.charCodeAt(i);
133
- }
134
-
135
- return Math.abs(hash);
136
- }
137
122
  }
package/src/watch.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { ManifestContext, ManifestModuleUtil, ManifestUtil, PackageUtil, RuntimeIndex, path } from '@travetto/manifest';
1
+ import { ManifestModuleFileType, ManifestModuleFolderType, ManifestModuleUtil, ManifestUtil, PackageUtil, RuntimeIndex, path } from '@travetto/manifest';
2
2
 
3
3
  import type { CompileStateEntry } from './types';
4
4
  import { CompilerState } from './state';
@@ -9,6 +9,14 @@ import { AsyncQueue } from '../support/queue';
9
9
  type WatchAction = 'create' | 'update' | 'delete';
10
10
  type WatchEvent = { action: WatchAction, file: string };
11
11
  type CompilerWatchEvent = WatchEvent & { entry: CompileStateEntry };
12
+ type FileShape = {
13
+ mod: string;
14
+ folderKey: ManifestModuleFolderType;
15
+ fileType: ManifestModuleFileType;
16
+ moduleFile: string;
17
+ action: WatchAction;
18
+ };
19
+
12
20
 
13
21
  /**
14
22
  * Watch support, based on compiler state and manifest details
@@ -22,6 +30,10 @@ export class CompilerWatcher {
22
30
  this.#signal = signal;
23
31
  }
24
32
 
33
+ #reset(ev: WatchEvent): never {
34
+ throw new Error('RESET', { cause: `${ev.action}:${ev.file}` });
35
+ }
36
+
25
37
  #getIgnores(): string[] {
26
38
  // TODO: Read .gitignore?
27
39
  let ignores = PackageUtil.readPackage(this.#state.manifest.workspace.path)?.travetto?.build?.watchIgnores;
@@ -40,8 +52,8 @@ export class CompilerWatcher {
40
52
  }
41
53
 
42
54
  /** Watch files */
43
- async * #watchFolder(rootPath: string): AsyncIterable<WatchEvent> {
44
- const q = new AsyncQueue<WatchEvent>(this.#signal);
55
+ async * #watchFolder(rootPath: string): AsyncIterable<WatchEvent[]> {
56
+ const q = new AsyncQueue<WatchEvent[]>(this.#signal);
45
57
  const lib = await import('@parcel/watcher');
46
58
  const ignore = this.#getIgnores();
47
59
 
@@ -51,9 +63,7 @@ export class CompilerWatcher {
51
63
  q.throw(err instanceof Error ? err : new Error((err as Error).message));
52
64
  return;
53
65
  }
54
- for (const ev of events) {
55
- q.add({ action: ev.type, file: path.toPosix(ev.path) });
56
- }
66
+ q.add(events.map(ev => ({ action: ev.type, file: path.toPosix(ev.path) })));
57
67
  }, { ignore });
58
68
 
59
69
  if (this.#signal.aborted) { // If already aborted, can happen async
@@ -66,30 +76,53 @@ export class CompilerWatcher {
66
76
  yield* q;
67
77
  }
68
78
 
69
- async #rebuildManifestsIfNeeded(event: CompilerWatchEvent, moduleFile: string): Promise<void> {
70
- if (!event.entry.outputFile || event.action === 'update') {
79
+ async #rebuildManifestsIfNeeded(events: CompilerWatchEvent[]): Promise<void> {
80
+ events = events.filter(x => x.entry.outputFile && x.action !== 'update');
81
+
82
+ if (!events.length) {
71
83
  return;
72
84
  }
73
85
 
74
- const toUpdate: ManifestContext[] = RuntimeIndex.getDependentModules(event.entry.module.name, 'parents')
75
- .map(el => ManifestUtil.getModuleContext(this.#state.manifest, el.sourceFolder));
76
-
77
- toUpdate.push(this.#state.manifest);
78
-
79
- const mod = event.entry.module;
80
- const folderKey = ManifestModuleUtil.getFolderKey(moduleFile);
81
- const fileType = ManifestModuleUtil.getFileType(moduleFile);
86
+ const mods = [...new Set(events.map(v => v.entry.module.name))];
87
+
88
+ const moduleToFiles = new Map(mods.map(m => [m, {
89
+ context: ManifestUtil.getModuleContext(this.#state.manifest, RuntimeIndex.getManifestModule(m)!.sourceFolder),
90
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
91
+ files: [] as FileShape[]
92
+ }] as const));
93
+
94
+ const parents = new Map<string, string[]>(
95
+ mods.map(m => [m, RuntimeIndex.getDependentModules(m, 'parents').map(x => x.name)])
96
+ );
97
+
98
+ const allFiles = events.map(ev => {
99
+ const modRoot = ev.entry.module.sourceFolder || this.#state.manifest.workspace.path;
100
+ const moduleFile = ev.file.includes(modRoot) ? ev.file.split(`${modRoot}/`)[1] : ev.file;
101
+ const folderKey = ManifestModuleUtil.getFolderKey(moduleFile);
102
+ const fileType = ManifestModuleUtil.getFileType(moduleFile);
103
+ return { mod: ev.entry.module.name, action: ev.action, moduleFile, folderKey, fileType };
104
+ });
105
+
106
+ for (const file of allFiles) {
107
+ for (const parent of parents.get(file.mod)!) {
108
+ const mod = moduleToFiles.get(parent);
109
+ if (!mod || !mod.files) {
110
+ this.#reset({ action: file.action, file: file.moduleFile });
111
+ }
112
+ mod.files.push(file);
113
+ }
114
+ }
82
115
 
83
- for (const ctx of toUpdate) {
84
- const newManifest = await ManifestUtil.buildManifest(ctx);
85
- if (mod.name in newManifest.modules) {
86
- const modFiles = newManifest.modules[mod.name].files[folderKey] ??= [];
116
+ for (const { context, files } of moduleToFiles.values()) {
117
+ const newManifest = await ManifestUtil.buildManifest(context);
118
+ for (const { action, mod, fileType, moduleFile, folderKey } of files) {
119
+ const modFiles = newManifest.modules[mod].files[folderKey] ??= [];
87
120
  const idx = modFiles.findIndex(x => x[0] === moduleFile);
88
121
 
89
- if (event.action === 'create' && idx < 0) {
122
+ if (action === 'create' && idx < 0) {
90
123
  modFiles.push([moduleFile, fileType, Date.now()]);
91
124
  } else if (idx >= 0) {
92
- if (event.action === 'delete') {
125
+ if (action === 'delete') {
93
126
  modFiles.splice(idx, 1);
94
127
  } else {
95
128
  modFiles[idx] = [moduleFile, fileType, Date.now()];
@@ -120,45 +153,51 @@ export class CompilerWatcher {
120
153
  const OUTPUT_PATH = path.resolve(manifest.workspace.path, manifest.build.outputFolder);
121
154
  const COMPILER_PATH = path.resolve(manifest.workspace.path, manifest.build.compilerFolder);
122
155
 
123
- for await (const ev of this.#watchFolder(this.#state.manifest.workspace.path)) {
124
- const { action, file: sourceFile } = ev;
156
+ for await (const events of this.#watchFolder(this.#state.manifest.workspace.path)) {
125
157
 
126
- if (
127
- sourceFile === ROOT_LOCK ||
128
- sourceFile === ROOT_PKG ||
129
- (action === 'delete' && (sourceFile === OUTPUT_PATH || sourceFile === COMPILER_PATH))
130
- ) {
131
- throw new Error('RESET', { cause: `${action}:${sourceFile}` });
132
- }
158
+ const outEvents: CompilerWatchEvent[] = [];
133
159
 
134
- const fileType = ManifestModuleUtil.getFileType(sourceFile);
135
- if (!CompilerUtil.validFile(fileType)) {
136
- continue;
137
- }
160
+ for (const ev of events) {
161
+ const { action, file: sourceFile } = ev;
162
+
163
+ if (
164
+ sourceFile === ROOT_LOCK ||
165
+ sourceFile === ROOT_PKG ||
166
+ (action === 'delete' && (sourceFile === OUTPUT_PATH || sourceFile === COMPILER_PATH))
167
+ ) {
168
+ this.#reset(ev);
169
+ }
170
+
171
+ const fileType = ManifestModuleUtil.getFileType(sourceFile);
172
+ if (!CompilerUtil.validFile(fileType)) {
173
+ continue;
174
+ }
138
175
 
139
- let entry = this.#state.getBySource(sourceFile);
176
+ let entry = this.#state.getBySource(sourceFile);
140
177
 
141
- const mod = entry?.module ?? this.#state.manifestIndex.findModuleForArbitraryFile(sourceFile);
142
- if (!mod) { // Unknown module
143
- continue;
144
- }
178
+ const mod = entry?.module ?? this.#state.manifestIndex.findModuleForArbitraryFile(sourceFile);
179
+ if (!mod) { // Unknown module
180
+ continue;
181
+ }
182
+
183
+ const modRoot = mod.sourceFolder || this.#state.manifest.workspace.path;
184
+ const moduleFile = sourceFile.includes(modRoot) ? sourceFile.split(`${modRoot}/`)[1] : sourceFile;
185
+
186
+ if (action === 'create') {
187
+ entry = this.#state.registerInput(mod, moduleFile);
188
+ } else if (!entry) {
189
+ continue;
190
+ } else if (action === 'update' && !this.#state.checkIfSourceChanged(entry.inputFile)) {
191
+ continue;
192
+ } else if (action === 'delete') {
193
+ this.#state.removeInput(entry.inputFile);
194
+ }
145
195
 
146
- const modRoot = mod.sourceFolder || this.#state.manifest.workspace.path;
147
- const moduleFile = sourceFile.includes(modRoot) ? sourceFile.split(`${modRoot}/`)[1] : sourceFile;
148
-
149
- if (action === 'create') {
150
- entry = this.#state.registerInput(mod, moduleFile);
151
- } else if (!entry) {
152
- continue;
153
- } else if (action === 'update' && !this.#state.checkIfSourceChanged(entry.inputFile)) {
154
- continue;
155
- } else if (action === 'delete') {
156
- this.#state.removeInput(entry.inputFile);
196
+ outEvents.push({ action, file: entry.sourceFile, entry });
157
197
  }
158
198
 
159
- const result: CompilerWatchEvent = { action, file: entry.sourceFile, entry };
160
- await this.#rebuildManifestsIfNeeded(result, moduleFile);
161
- yield result;
199
+ await this.#rebuildManifestsIfNeeded(outEvents);
200
+ yield* outEvents;
162
201
  }
163
202
  }
164
203
  }
@@ -207,7 +207,7 @@ export class CompilerServer {
207
207
  CommonUtil.nonBlockingTimeout(2000).then(reject); // Wait 2s max
208
208
  this.#server.close(resolve);
209
209
  this.#emitEvent({ type: 'state', payload: { state: 'closed' } });
210
- setImmediate(() => {
210
+ CommonUtil.queueMacroTask().then(() => {
211
211
  this.#server.closeAllConnections();
212
212
  this.#shutdown.abort();
213
213
  });
package/support/util.ts CHANGED
@@ -58,6 +58,20 @@ export class CommonUtil {
58
58
  }
59
59
  }
60
60
 
61
+ /**
62
+ * Naive hashing
63
+ */
64
+ static naiveHash(text: string): number {
65
+ let hash = 5381;
66
+
67
+ for (let i = 0; i < text.length; i++) {
68
+ // eslint-disable-next-line no-bitwise
69
+ hash = (hash * 33) ^ text.charCodeAt(i);
70
+ }
71
+
72
+ return Math.abs(hash);
73
+ }
74
+
61
75
  /**
62
76
  * Non-blocking timeout
63
77
  */