diff --git a/.playwright-mcp/module-resolution-fixed.png b/.playwright-mcp/module-resolution-fixed.png new file mode 100644 index 0000000..6d18287 Binary files /dev/null and b/.playwright-mcp/module-resolution-fixed.png differ diff --git a/changelog.md b/changelog.md index 2b1c335..c9ce991 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,15 @@ # Changelog +## 2025-12-30 - 3.16.0 - feat(editor) +improve TypeScript IntelliSense and module resolution for Monaco editor + +- Add file cache (fileCache) and getFileContent() for synchronous access to project files +- Track and dispose Monaco extra libs (addedExtraLibs) and register project files via addExtraLib to enable TypeScript module resolution +- Add addFileAsExtraLib logic to register .ts/.tsx files also under .js/.jsx paths so ESM imports resolve to TypeScript sources +- Use ModuleResolutionKind.Bundler fallback to NodeJs and set compilerOptions (baseUrl '/', allowImportingTsExtensions, resolveJsonModule) to improve resolution +- Adapt executionEnvironment API usage to readDir/readFile and check entry.type ('directory'|'file') instead of isDirectory/isFile +- Add a debugging/screenshot asset: .playwright-mcp/module-resolution-fixed.png + ## 2025-12-30 - 3.15.0 - feat(editor) enable file-backed Monaco models and add Problems panel; lazy-init project TypeScript IntelliSense diff --git a/ts_web/00_commitinfo_data.ts b/ts_web/00_commitinfo_data.ts index 78e4cac..03ed3d9 100644 --- a/ts_web/00_commitinfo_data.ts +++ b/ts_web/00_commitinfo_data.ts @@ -3,6 +3,6 @@ */ export const commitinfo = { name: '@design.estate/dees-catalog', - version: '3.15.0', + version: '3.16.0', description: 'A comprehensive library that provides dynamic web components for building sophisticated and modern web applications using JavaScript and TypeScript.' } diff --git a/ts_web/elements/00group-editor/dees-editor-workspace/typescript-intellisense.ts b/ts_web/elements/00group-editor/dees-editor-workspace/typescript-intellisense.ts index 721acef..90a2e18 100644 --- a/ts_web/elements/00group-editor/dees-editor-workspace/typescript-intellisense.ts +++ b/ts_web/elements/00group-editor/dees-editor-workspace/typescript-intellisense.ts @@ -2,16 +2,20 @@ import type * as monaco from 'monaco-editor'; import type { IExecutionEnvironment } from '../../00group-runtime/index.js'; // Monaco TypeScript API types (runtime API still exists, types deprecated in 0.55+) +interface IExtraLibDisposable { + dispose(): void; +} + interface IMonacoTypeScriptAPI { typescriptDefaults: { setCompilerOptions(options: Record): void; setDiagnosticsOptions(options: Record): void; - addExtraLib(content: string, filePath?: string): void; + addExtraLib(content: string, filePath?: string): IExtraLibDisposable; setEagerModelSync(value: boolean): void; }; ScriptTarget: { ES2020: number }; ModuleKind: { ESNext: number }; - ModuleResolutionKind: { NodeJs: number }; + ModuleResolutionKind: { NodeJs: number; Bundler?: number }; } /** @@ -23,6 +27,12 @@ export class TypeScriptIntelliSenseManager { private monacoInstance: typeof monaco | null = null; private executionEnvironment: IExecutionEnvironment | null = null; + // Cache of file contents for synchronous access and module resolution + private fileCache: Map = new Map(); + + // Track extra libs added for cleanup + private addedExtraLibs: Map = new Map(); + /** * Get TypeScript API with proper typing for Monaco 0.55+ */ @@ -60,7 +70,7 @@ export class TypeScriptIntelliSenseManager { if (!this.executionEnvironment) return; try { - const entries = await this.executionEnvironment.readdir(dirPath); + const entries = await this.executionEnvironment.readDir(dirPath); for (const entry of entries) { const fullPath = dirPath === '/' ? `/${entry.name}` : `${dirPath}/${entry.name}`; @@ -68,9 +78,9 @@ export class TypeScriptIntelliSenseManager { // Skip node_modules - too large and handled separately via addExtraLib if (entry.name === 'node_modules') continue; - if (entry.isDirectory()) { + if (entry.type === 'directory') { await this.loadFilesFromDirectory(fullPath); - } else if (entry.isFile()) { + } else if (entry.type === 'file') { const ext = entry.name.split('.').pop()?.toLowerCase(); if (ext === 'ts' || ext === 'tsx' || ext === 'js' || ext === 'jsx') { try { @@ -94,7 +104,8 @@ export class TypeScriptIntelliSenseManager { ts.typescriptDefaults.setCompilerOptions({ target: ts.ScriptTarget.ES2020, module: ts.ModuleKind.ESNext, - moduleResolution: ts.ModuleResolutionKind.NodeJs, + // Use Bundler resolution if available (Monaco 0.45+), fallback to NodeJs + moduleResolution: ts.ModuleResolutionKind.Bundler ?? ts.ModuleResolutionKind.NodeJs, allowSyntheticDefaultImports: true, esModuleInterop: true, strict: true, @@ -103,6 +114,12 @@ export class TypeScriptIntelliSenseManager { checkJs: false, allowNonTsExtensions: true, lib: ['es2020', 'dom', 'dom.iterable'], + // Set baseUrl to root for resolving absolute imports + baseUrl: '/', + // Allow importing .ts extensions directly (useful for some setups) + allowImportingTsExtensions: true, + // Resolve JSON modules + resolveJsonModule: true, }); ts.typescriptDefaults.setDiagnosticsOptions({ @@ -260,10 +277,15 @@ export class TypeScriptIntelliSenseManager { /** * Add a file model to Monaco for cross-file IntelliSense + * Also registers the file with TypeScript via addExtraLib for module resolution */ public addFileModel(path: string, content: string): void { if (!this.monacoInstance) return; + // Cache the content for sync access + this.fileCache.set(path, content); + + // Create/update the editor model const uri = this.monacoInstance.Uri.parse(`file://${path}`); const existingModel = this.monacoInstance.editor.getModel(uri); @@ -273,6 +295,53 @@ export class TypeScriptIntelliSenseManager { const language = this.getLanguageFromPath(path); this.monacoInstance.editor.createModel(content, language, uri); } + + // Also add as extra lib for TypeScript module resolution + // This is critical - TypeScript's resolver uses extra libs, not editor models + this.addFileAsExtraLib(path, content); + } + + /** + * Add a file as an extra lib for TypeScript module resolution. + * This enables TypeScript to resolve imports to project files. + */ + private addFileAsExtraLib(path: string, content: string): void { + const ts = this.tsApi; + if (!ts) return; + + // Dispose existing lib if present (for updates) + const existing = this.addedExtraLibs.get(path); + if (existing) { + existing.dispose(); + } + + // Add the file with its actual path + const filePath = `file://${path}`; + const disposable = ts.typescriptDefaults.addExtraLib(content, filePath); + this.addedExtraLibs.set(path, disposable); + + // For .ts files, also add with .js extension to handle ESM imports + // (e.g., import from './utils.js' should resolve to ./utils.ts) + if (path.endsWith('.ts') && !path.endsWith('.d.ts')) { + const jsPath = path.replace(/\.ts$/, '.js'); + const jsFilePath = `file://${jsPath}`; + const jsDisposable = ts.typescriptDefaults.addExtraLib(content, jsFilePath); + this.addedExtraLibs.set(jsPath, jsDisposable); + this.fileCache.set(jsPath, content); + } else if (path.endsWith('.tsx')) { + const jsxPath = path.replace(/\.tsx$/, '.jsx'); + const jsxFilePath = `file://${jsxPath}`; + const jsxDisposable = ts.typescriptDefaults.addExtraLib(content, jsxFilePath); + this.addedExtraLibs.set(jsxPath, jsxDisposable); + this.fileCache.set(jsxPath, content); + } + } + + /** + * Get cached file content for synchronous access + */ + public getFileContent(path: string): string | undefined { + return this.fileCache.get(path); } private getLanguageFromPath(path: string): string {