Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 15 additions & 9 deletions src/rules-compiler-typescript/COMPILER_INTEGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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);
```
Expand All @@ -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})`);
```

Expand Down
146 changes: 116 additions & 30 deletions src/rules-compiler-typescript/src/lib/compiler-adapter.ts
Original file line number Diff line number Diff line change
@@ -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<string[]>;

/**
* 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<CompilerModule> | 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<CompilerModule> {
// 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<string[]> {
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<FilterCompilerConstructor> {
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,
Expand Down