directive-to-hof
A build-time transformer that converts JavaScript/TypeScript directives into higher-order function calls. Transform your code's behavior declaratively using simple string directives.
Overview
directive-to-hof enables you to use declarative directives in your functions that get transformed into higher-order function calls at build time. This approach provides compile-time optimization without runtime overhead.
Key Features
- Zero Runtime Overhead: Directives are transformed at build time, not runtime
- Multiple Directive Support: Use multiple directives on the same function
- Collision-Safe Imports: Automatic import name collision resolution
- TypeScript Support: Full TypeScript compatibility with type safety
- Build Tool Integration: Works with Vite, Rollup, and esbuild
- Flexible Configuration: Support for async-only directives and custom import paths
Installation
npm install directive-to-hof
yarn add directive-to-hof
pnpm add directive-to-hof
Usage
Basic Example
// Before transformation
function expensiveCalculation(x, y) {
'use once';
'use memo';
return x * y * Math.random();
}
// After transformation
import { useOnce } from './use-once-function.js';
import { useMemo } from './memo-function.js';
const expensiveCalculation = useOnce(
useMemo((x, y) => {
return x * y * Math.random();
})
);
Vite Plugin
import { defineConfig } from 'vite';
import { vite } from 'directive-to-hof';
export default defineConfig({
plugins: [
vite({
directive: 'use once',
importPath: './use-once-function.js',
importName: 'useOnce',
asyncOnly: false,
}),
],
});
Rollup Plugin
import { rollup } from 'directive-to-hof';
export default {
plugins: [
rollup({
directive: 'use once',
importPath: './use-once-function.js',
importName: 'useOnce',
asyncOnly: false,
}),
],
};
esbuild Plugin
import { esbuild } from 'directive-to-hof';
const plugins = [
esbuild({
directive: 'use once',
importPath: './use-once-function.js',
importName: 'useOnce',
asyncOnly: false,
}),
];
Configuration Options
DirectiveTransformerOptions
interface DirectiveTransformerOptions {
/**
* The directive string to look for in function bodies
*/
directive: string;
/**
* Path to the module containing the higher-order function
*/
importPath: string;
/**
* Name of the higher-order function to import
*/
importName: string;
/**
* Whether the directive can only be used in async functions
* @default true
*/
asyncOnly?: boolean;
}
Multiple Directives
You can configure multiple directives by passing an array of options:
const transformer = createDirectiveTransformer([
{
directive: 'use once',
importPath: './use-once-function.js',
importName: 'useOnce',
asyncOnly: false,
},
{
directive: 'use memo',
importPath: './memo-function.js',
importName: 'useMemo',
asyncOnly: false,
},
{
directive: 'use cache',
importPath: './cache-function.js',
importName: 'useCache',
asyncOnly: true,
},
]);
Programmatic API
createDirectiveTransformer
import { createDirectiveTransformer } from 'directive-to-hof';
const transformer = createDirectiveTransformer({
directive: 'use once',
importPath: './use-once-function.js',
importName: 'useOnce',
asyncOnly: false,
});
const code = `
let count = 0;
function increment() {
'use once';
return ++count;
}
`;
const { contents } = await transformer(code, { path: './example.js' });
console.log(contents);
Higher-Order Function Examples
useOnce Implementation
// use-once-function.js
export function useOnce(fn) {
let result;
let called = false;
return (...args) => {
if (called) return result;
called = true;
return (result = fn(...args));
};
}
useMemo Implementation
// memo-function.js
export function useMemo(fn) {
const cache = new Map();
return (...args) => {
const key = JSON.stringify(args);
if (cache.has(key)) {
return cache.get(key);
}
const result = fn(...args);
cache.set(key, result);
return result;
};
}
useCache Implementation
// cache-function.js
export function useCache(fn) {
const cache = new Map();
return async (...args) => {
const key = JSON.stringify(args);
if (cache.has(key)) {
return cache.get(key);
}
const result = await fn(...args);
cache.set(key, result);
return result;
};
}
Advanced Usage
Function Types Supported
- Function declarations
- Function expressions
- Arrow functions
- Object methods
- Class methods
Multiple Directives on Same Function
// Input
function complexFunction(x, y) {
'use once';
'use memo';
return x * y * Math.random();
}
// Output
const complexFunction = useOnce(
useMemo((x, y) => {
return x * y * Math.random();
})
);
Import Collision Resolution
The transformer automatically handles import name collisions by generating unique aliases:
// If 'useOnce' already exists in scope
const useOnce = 'existing variable';
// The transformer will generate a unique import alias
import { useOnce as useOnce_ABC123 } from './use-once-function.js';
Contributing
Contributions are welcome! Please read our Contributing Guide for details on our code of conduct and the process for submitting pull requests.
License
This project is licensed under the MIT License - see the LICENSE file for details.
Changelog
See CHANGELOG.md for a list of changes and version history.