diff --git a/packages/cli/package.json b/packages/cli/package.json index 6bdfac65..c0c2307a 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/cli", - "version": "0.8.61", + "version": "0.8.62", "type": "module", "main": "src/index.ts", "exports": { diff --git a/packages/dev-server/package.json b/packages/dev-server/package.json index 65c184d9..4c3a94b4 100644 --- a/packages/dev-server/package.json +++ b/packages/dev-server/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/dev-server", - "version": "0.8.53", + "version": "0.8.54", "type": "module", "main": "src/index.ts", "exports": { diff --git a/packages/dev-server/src/index.ts b/packages/dev-server/src/index.ts index 66c73de2..2b953310 100644 --- a/packages/dev-server/src/index.ts +++ b/packages/dev-server/src/index.ts @@ -367,6 +367,12 @@ export async function startServer(opts: ServeOptions): Promise { externalRoutes: pluginContributions.routes, middlewareFiles: pluginContributions.middleware, }); + // UI and plugin components are intentionally registered application-wide; + // using them without a source import is not an implicit-import diagnostic. + setCompileImportOptions(appRoot, { + ...importConfig, + globalComponents: router.components.map((component) => component.name), + }); const styleEntry = opts.styleEntry ?? null; const devToolbarConfig = resolveDevToolbarConfig(mode, opts.devToolbar); diff --git a/packages/dev-server/src/pipeline.ts b/packages/dev-server/src/pipeline.ts index b4ec5573..d5844997 100644 --- a/packages/dev-server/src/pipeline.ts +++ b/packages/dev-server/src/pipeline.ts @@ -86,6 +86,7 @@ interface CompileImportOptions { mode: ImportMode; aliases: Record; autoImport: boolean; + globalComponents: Set; } const compileImportOptions = new Map(); const warnedImportDiagnostics = new Set(); @@ -104,12 +105,18 @@ export function setDevCompilerPipeline(pipeline: DevCompilerPipeline | null): vo export function setCompileImportOptions( appRoot: string, - options: { mode?: ImportMode; aliases?: Record; autoImport?: boolean } = {}, + options: { + mode?: ImportMode; + aliases?: Record; + autoImport?: boolean; + globalComponents?: Iterable; + } = {}, ): void { compileImportOptions.set(resolve(appRoot), { mode: options.mode ?? "compatible", aliases: { "@": "./app", ...(options.aliases ?? {}) }, autoImport: options.autoImport ?? true, + globalComponents: new Set(options.globalComponents ?? []), }); } @@ -141,11 +148,18 @@ function importOptionsHash(file: string): string { mode: "compatible" as const, aliases: { "@": "./app" }, autoImport: true, + globalComponents: new Set(), }; const aliases = Object.fromEntries( Object.entries(options.aliases).sort(([left], [right]) => left.localeCompare(right)), ); - return hashPath(JSON.stringify({ ...options, aliases })); + return hashPath( + JSON.stringify({ + ...options, + aliases, + globalComponents: [...options.globalComponents].sort(), + }), + ); } /** Island bundles already built this dev session, keyed by resolved source. */ @@ -493,7 +507,10 @@ function validateConfiguredImports(_source: string, ast: PageAst, file: string): "Transition", ]); const missing = [...usedComponents].filter( - (name) => !compilerBuiltins.has(name) && !imported.has(name), + (name) => + !compilerBuiltins.has(name) && + !options.globalComponents.has(name) && + !imported.has(name), ); if (ast.layoutIsSymbol && ast.layout && !imported.has(ast.layout)) missing.push(ast.layout); if (!missing.length) return; diff --git a/packages/dev-server/test/import-modes-v060.test.ts b/packages/dev-server/test/import-modes-v060.test.ts index 21b203a0..35acd2aa 100644 --- a/packages/dev-server/test/import-modes-v060.test.ts +++ b/packages/dev-server/test/import-modes-v060.test.ts @@ -71,6 +71,13 @@ test("compiler-native reactive elements do not require application imports", () expect(() => compileWrnArtifacts(page, 5)).not.toThrow(); }); +test("globally registered plugin components do not require source imports", () => { + const { root, page } = fixture(); + writeFileSync(page, "page Home { view { } }"); + setCompileImportOptions(root, { mode: "explicit", globalComponents: ["SignIn"] }); + expect(() => compileWrnArtifacts(page, 6)).not.toThrow(); +}); + test("component examples in comments do not require imports", () => { const { root, page } = fixture(); writeFileSync(