From a32bae930e860ab550a80f997a79724e33112db2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 1 Jan 2026 04:31:43 +0000 Subject: [PATCH 1/2] Initial plan From 8d3494b398d96f388c38ef1fbc0d80cf654dc826 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 1 Jan 2026 04:37:38 +0000 Subject: [PATCH 2/2] refactor: Remove any types and top-level await from compiler-adapter Co-authored-by: jaypatrick <1800595+jaypatrick@users.noreply.github.com> --- .../COMPILER_INTEGRATION.md | 24 +-- .../src/lib/compiler-adapter.ts | 146 ++++++++++++++---- 2 files changed, 131 insertions(+), 39 deletions(-) diff --git a/src/rules-compiler-typescript/COMPILER_INTEGRATION.md b/src/rules-compiler-typescript/COMPILER_INTEGRATION.md index 67a94d4..29141d5 100644 --- a/src/rules-compiler-typescript/COMPILER_INTEGRATION.md +++ b/src/rules-compiler-typescript/COMPILER_INTEGRATION.md @@ -8,20 +8,23 @@ The TypeScript rules compiler now uses the modern JSR-based `@jk-com/adblock-com ### Compiler Adapter (`src/lib/compiler-adapter.ts`) -The `compiler-adapter.ts` module provides automatic fallback logic: +The `compiler-adapter.ts` module provides automatic fallback logic with lazy initialization: -1. **Primary**: Attempts to load `@jk-com/adblock-compiler` from JSR -2. **Fallback**: Falls back to `@adguard/hostlist-compiler` from npm if JSR fails -3. **Logging**: Logs which compiler is being used at startup +1. **Lazy Loading**: Compiler is loaded on first use, not at module import time +2. **Primary**: Attempts to load `@jk-com/adblock-compiler` from JSR +3. **Fallback**: Falls back to `@adguard/hostlist-compiler` from npm if JSR fails +4. **Logging**: Logs which compiler is being used when initialized ```typescript -import { compile, FilterCompiler, getCompilerInfo } from './lib/compiler-adapter.ts'; +import { compile, getFilterCompiler, getCompilerInfo } from './lib/compiler-adapter.ts'; -// Check which compiler is active -const info = getCompilerInfo(); +// Check which compiler is active (triggers lazy initialization if not yet loaded) +const info = await getCompilerInfo(); console.log(`Using ${info.source} package: ${info.package}`); ``` +**Key Design Decision**: The adapter uses lazy initialization instead of top-level await to prevent blocking module imports. This ensures fast startup and deferred loading until the compiler is actually needed. + ## Benefits of JSR Package The `@jk-com/adblock-compiler@^0.6.0` package includes: @@ -67,8 +70,10 @@ console.log(`Compiled ${rules.length} rules`); ### Using FilterCompiler Class ```typescript -import { FilterCompiler } from './lib/compiler-adapter.ts'; +import { getFilterCompiler } from './lib/compiler-adapter.ts'; +// Get the FilterCompiler class (lazy loads on first call) +const FilterCompiler = await getFilterCompiler(); const compiler = new FilterCompiler(logger); const result = await compiler.compile(config); ``` @@ -78,7 +83,8 @@ const result = await compiler.compile(config); ```typescript import { getCompilerInfo } from './lib/compiler-adapter.ts'; -const { source, package: pkg } = getCompilerInfo(); +// This will trigger lazy initialization if compiler not yet loaded +const { source, package: pkg } = await getCompilerInfo(); console.log(`Active compiler: ${pkg} (${source})`); ``` diff --git a/src/rules-compiler-typescript/src/lib/compiler-adapter.ts b/src/rules-compiler-typescript/src/lib/compiler-adapter.ts index 8c25c34..c18834c 100644 --- a/src/rules-compiler-typescript/src/lib/compiler-adapter.ts +++ b/src/rules-compiler-typescript/src/lib/compiler-adapter.ts @@ -1,55 +1,141 @@ /** * Compiler adapter that tries the new JSR package first, with fallback to AdGuard npm package + * + * This adapter uses lazy initialization to avoid blocking module imports with top-level await. + * The compiler is loaded on first use, not at module load time. */ -import type { IConfiguration, ILogger } from '@jk-com/adblock-compiler'; +/** + * Type definitions for compiler source + */ +type CompilerSource = 'jsr' | 'npm'; + +/** + * Compiler function signature (compatible with both packages) + */ +type CompilerFunction = (config: unknown) => Promise; + +/** + * FilterCompiler class constructor signature (compatible with both packages) + */ +type FilterCompilerConstructor = new (...args: unknown[]) => unknown; + +/** + * Compiler module state + */ +interface CompilerModule { + FilterCompiler: FilterCompilerConstructor; + compile: CompilerFunction; + source: CompilerSource; +} + +/** + * Cached compiler module instance + */ +let compilerModule: CompilerModule | null = null; + +/** + * Promise for ongoing initialization (prevents multiple simultaneous loads) + */ +let initPromise: Promise | null = null; -// Try to import from JSR first -let FilterCompiler: any; -let compile: any; -let compilerSource: 'jsr' | 'npm' = 'jsr'; +/** + * Initialize the compiler by attempting to load JSR package first, then npm fallback + * This function is called lazily on first use, not at module load time + */ +async function initializeCompiler(): Promise { + // Return cached module if already initialized + if (compilerModule) { + return compilerModule; + } -try { - const jsrModule = await import('@jk-com/adblock-compiler'); - FilterCompiler = jsrModule.FilterCompiler; - compile = jsrModule.compile; - compilerSource = 'jsr'; - console.log('[Compiler] Using JSR package: @jk-com/adblock-compiler@^0.6.0'); -} catch (jsrError) { - console.warn('[Compiler] JSR package failed, falling back to npm:', jsrError); - try { - const npmModule = await import('@adguard/hostlist-compiler'); - FilterCompiler = npmModule.FilterCompiler; - compile = npmModule.compile; - compilerSource = 'npm'; - console.log('[Compiler] Using npm package: @adguard/hostlist-compiler'); - } catch (npmError) { - throw new Error( - `Failed to load compiler from both sources:\nJSR: ${jsrError}\nnpm: ${npmError}`, - ); + // If initialization is in progress, wait for it + if (initPromise) { + return initPromise; } + + // Start initialization + initPromise = (async () => { + try { + // Try JSR package first + const jsrModule = await import('@jk-com/adblock-compiler'); + const module: CompilerModule = { + FilterCompiler: jsrModule.FilterCompiler as FilterCompilerConstructor, + compile: jsrModule.compile as CompilerFunction, + source: 'jsr', + }; + console.log('[Compiler] Using JSR package: @jk-com/adblock-compiler@^0.6.0'); + compilerModule = module; + return module; + } catch (jsrError) { + console.warn('[Compiler] JSR package failed, falling back to npm:', jsrError); + + try { + // Fallback to npm package + const npmModule = await import('@adguard/hostlist-compiler'); + const module: CompilerModule = { + FilterCompiler: npmModule.FilterCompiler as FilterCompilerConstructor, + compile: npmModule.compile as CompilerFunction, + source: 'npm', + }; + console.log('[Compiler] Using npm package: @adguard/hostlist-compiler'); + compilerModule = module; + return module; + } catch (npmError) { + throw new Error( + `Failed to load compiler from both sources:\nJSR: ${jsrError}\nnpm: ${npmError}`, + ); + } + } finally { + // Clear the promise once initialization completes + initPromise = null; + } + })(); + + return initPromise; } /** * Get information about which compiler is being used + * If the compiler hasn't been initialized yet, this will trigger initialization */ -export function getCompilerInfo(): { source: 'jsr' | 'npm'; package: string } { +export async function getCompilerInfo(): Promise<{ source: CompilerSource; package: string }> { + const module = await initializeCompiler(); return { - source: compilerSource, - package: compilerSource === 'jsr' + source: module.source, + package: module.source === 'jsr' ? '@jk-com/adblock-compiler@^0.6.0' : '@adguard/hostlist-compiler', }; } /** - * Export the compiler classes and functions - * These will be from whichever package successfully loaded + * Compile function with lazy initialization + * This ensures the compiler is loaded only when first used */ -export { compile, FilterCompiler }; +export async function compile(config: unknown): Promise { + const module = await initializeCompiler(); + return module.compile(config); +} + +/** + * Get FilterCompiler class with lazy initialization + * This ensures the compiler is loaded only when first used + */ +export async function getFilterCompiler(): Promise { + const module = await initializeCompiler(); + return module.FilterCompiler; +} /** - * Export types from JSR package (they're compatible) + * Re-export types conditionally based on which package is loaded + * + * NOTE: We export types from JSR package as the canonical source since it has + * the most complete type definitions. The npm package (@adguard/hostlist-compiler) + * is fully compatible with these types as it shares the same API surface. + * + * If you need to ensure type compatibility at runtime, use the getCompilerInfo() + * function to determine which package is loaded and handle accordingly. */ export type { IBasicLogger,