Skip to content

Commit 041704a

Browse files
committed
fix(docs-infra): Cannot find module message
Fixed the import error message in the editor section. The build would complete successfully, but an error message would be incorrectly reported.
1 parent 25e8bcb commit 041704a

3 files changed

Lines changed: 95 additions & 9 deletions

File tree

adev/src/app/editor/code-editor/code-mirror-editor.service.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -218,8 +218,17 @@ export class CodeMirrorEditor {
218218
return;
219219
}
220220

221-
// Send message to tsVfsWorker only when current file is TypeScript file.
222-
if (!this.currentFile()?.filename.endsWith('.ts')) return;
221+
// Always allow infrastructure/setup requests to go through, regardless of current file type.
222+
const infraActions = new Set<unknown>([
223+
TsVfsWorkerActions.CREATE_VFS_ENV_REQUEST,
224+
TsVfsWorkerActions.UPDATE_VFS_ENV_REQUEST,
225+
TsVfsWorkerActions.DEFINE_TYPES_REQUEST,
226+
]);
227+
228+
if (!infraActions.has(request.action)) {
229+
// For language-service operations, ensure the current file is a TypeScript file.
230+
if (!this.currentFile()?.filename.endsWith('.ts')) return;
231+
}
223232

224233
this.tsVfsWorker.postMessage(request);
225234
};

adev/src/app/editor/typings-loader.service.spec.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,11 +64,13 @@ describe('TypingsLoader', () => {
6464
).toBeTrue();
6565
});
6666

67-
it('should only contain type definitions files', async () => {
67+
it('should only contain type definitions files or package metadata', async () => {
6868
await service.retrieveTypeDefinitions(fakeWebContainer);
6969

7070
for (const {path} of service.typings()) {
71-
expect(path.endsWith('.d.ts')).toBeTrue();
71+
const isDts = path.endsWith('.d.ts');
72+
const isPackageJson = path.endsWith('/package.json');
73+
expect(isDts || isPackageJson).toBeTrue();
7274
}
7375
});
7476

adev/src/app/editor/typings-loader.service.ts

Lines changed: 80 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,9 @@ export class TypingsLoader {
7676

7777
for (const library of this.librariesToGetTypesFrom) {
7878
// The library's package.json is where the type definitions are defined
79+
const packageJsonFsPath = `./node_modules/${library}/package.json`;
7980
const packageJsonContent = await this.webContainer.fs
80-
.readFile(`./node_modules/${library}/package.json`, 'utf-8')
81+
.readFile(packageJsonFsPath, 'utf-8')
8182
.catch((error) => {
8283
// Note: "ENOENT" errors occurs:
8384
// - While resetting the NodeRuntimeSandbox.
@@ -94,15 +95,31 @@ export class TypingsLoader {
9495
// if the package.json content is empty, skip this library
9596
if (!packageJsonContent) continue;
9697

98+
// Ensure the worker VFS also receives the package.json file so NodeNext resolution
99+
// can read "exports"/"types" information when resolving imports like '@angular/core'.
100+
filesToRead.push(`/node_modules/${library}/package.json`);
101+
97102
const packageJson = JSON.parse(packageJsonContent);
98103

99-
// If the package.json doesn't have `exports`, skip this library
104+
// If the package exposes a top-level types entry, include that directory as a fallback
105+
const topLevelTypes: string | undefined = packageJson.types ?? packageJson.typings;
106+
if (!packageJson?.exports && topLevelTypes) {
107+
const path = `/node_modules/${library}/${this.normalizePath(topLevelTypes)}`;
108+
const directory = path.substring(0, path.lastIndexOf('/'));
109+
directoriesToRead.push(directory);
110+
continue;
111+
}
112+
100113
if (!packageJson?.exports) continue;
101114

102115
// Based on `exports` we can identify paths to the types definition files
103116
for (const exportKey of Object.keys(packageJson.exports)) {
104117
const exportEntry = packageJson.exports[exportKey];
105-
const types: string | undefined = exportEntry.typings ?? exportEntry.types;
118+
// Handle both object and string entries; for strings we can't infer types, so skip
119+
const types: string | undefined =
120+
exportEntry && typeof exportEntry === 'object'
121+
? (exportEntry.typings ?? exportEntry.types)
122+
: undefined;
106123

107124
if (types) {
108125
const path = `/node_modules/${library}/${this.normalizePath(types)}`;
@@ -131,15 +148,73 @@ export class TypingsLoader {
131148
private async getTypeDefinitionFilesFromDirectory(directory: string): Promise<string[]> {
132149
if (!this.webContainer) throw new Error('this.webContainer is not defined');
133150

134-
const files = await this.webContainer.fs.readdir(directory);
151+
// Use a `visited` set to avoid loops/duplicates between recurses.
152+
return this.getTypeDefinitionFilesRecursively(directory, new Set());
153+
}
154+
155+
private async getTypeDefinitionFilesRecursively(
156+
directory: string,
157+
visited: Set<string>,
158+
): Promise<string[]> {
159+
if (!this.webContainer) throw new Error('this.webContainer is not defined');
160+
161+
// Normalize and deduplicate the current directory
162+
const dir = directory.replace(/\/+$/, '');
163+
if (visited.has(dir)) return [];
164+
visited.add(dir);
165+
166+
const results: string[] = [];
167+
168+
// Read entries; if directory doesn't exist, ignore (optional exports)
169+
const entries = await this.webContainer.fs.readdir(dir).catch((error) => {
170+
if (error?.message?.startsWith('ENOENT')) return [] as string[];
171+
throw error;
172+
});
173+
174+
// Deterministic sort: sort alfab.
175+
entries.sort();
176+
177+
for (const entry of entries) {
178+
// Some FS (or test fakes) already return full paths.
179+
// Normalize to avoid `dir/dir/file`.
180+
const fullPath = entry.startsWith(dir + '/') ? entry : `${dir}/${entry}`;
135181

136-
return files.filter(this.isTypeDefinitionFile).map((file) => `${directory}/${file}`);
182+
// If it's a `.d.ts`, add it and move on (don't try `readdir` on file)
183+
if (this.isTypeDefinitionFile(fullPath)) {
184+
results.push(fullPath);
185+
continue;
186+
}
187+
188+
// Avoid recursively going down paths that are likely files (e.g. .js/.mjs)
189+
if (this.isProbablyAFile(fullPath)) {
190+
continue;
191+
}
192+
193+
// Try reading it as a directory.
194+
const children = await this.webContainer.fs.readdir(fullPath).catch(() => null);
195+
196+
// Only if `children` is a non-empty array do we consider it a directory and descend.
197+
if (Array.isArray(children) && children.length > 0) {
198+
const nested = await this.getTypeDefinitionFilesRecursively(fullPath, visited);
199+
results.push(...nested);
200+
}
201+
}
202+
203+
// Dedup and deterministic order
204+
const uniqueSorted = Array.from(new Set(results)).sort();
205+
return uniqueSorted;
137206
}
138207

139208
private isTypeDefinitionFile(path: string): boolean {
140209
return path.endsWith('.d.ts');
141210
}
142211

212+
private isProbablyAFile(path: string): boolean {
213+
// Consider any path whose last segment contains a period to be a "file" (e.g., index.js, index.mjs)
214+
// Example regex: '/something/index.js' -> true; '/something/nested' -> false
215+
return /\/[^\/]+\.[^\/]+$/.test(path);
216+
}
217+
143218
private normalizePath(path: string): string {
144219
if (path.startsWith('./')) {
145220
return path.substring(2);

0 commit comments

Comments
 (0)