Skip to content

neplextech/directive-to-hof

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

8 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

npm version Build Status TypeScript License

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.

About

Transform directives into a higher order function

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published