feat(editor): improve TypeScript IntelliSense and module resolution for Monaco editor
This commit is contained in:
BIN
.playwright-mcp/module-resolution-fixed.png
Normal file
BIN
.playwright-mcp/module-resolution-fixed.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 43 KiB |
10
changelog.md
10
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
|
||||
|
||||
|
||||
@@ -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.'
|
||||
}
|
||||
|
||||
@@ -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<string, unknown>): void;
|
||||
setDiagnosticsOptions(options: Record<string, unknown>): 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<string, string> = new Map();
|
||||
|
||||
// Track extra libs added for cleanup
|
||||
private addedExtraLibs: Map<string, IExtraLibDisposable> = 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 {
|
||||
|
||||
Reference in New Issue
Block a user