npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@moduul/builder

v0.1.4

Published

CLI tool for building plugins for the Moduul plugin system

Readme

@moduul/builder

CLI tool for building plugins for the Moduul plugin system. Bundles TypeScript source code into optimized JavaScript with automatic manifest handling.

Features

  • 🎯 Zero Config - Works out of the box with sensible defaults
  • 📦 ESBuild Powered - Fast bundling with automatic tree-shaking
  • 🔍 Manifest Validation - Ensures plugin.manifest.json is valid
  • 🗜️ ZIP Support - Optional compression for distribution
  • 🗺️ Source Maps - Debug support with inline source maps
  • 📝 TypeScript First - Full TypeScript support

Installation

npm install -D @moduul/builder

Quick Start

Basic Build

# Build current directory (defaults: input=./src, output=./dist, format=esm)
moduul build

# Specify input and output directories
moduul build --input src --output dist

# Build as CommonJS
moduul build --format cjs

Build with ZIP

# Create a ZIP archive for distribution
moduul build --zip

CLI Commands

build

Bundles a plugin into a distributable format.

moduul build [options]

Options:

  • -i, --input <path> - Input directory containing plugin source (default: ./src)
  • -o, --output <path> - Output directory for built plugin (default: ./dist)
  • -f, --format <format> - Output module format: esm, cjs, or iife (default: esm)
  • -m, --minify - Minify the output (default: false)
  • --zip - Create a ZIP archive of the built plugin
  • -h, --help - Display help

Examples:

# Default build (ESM output)
moduul build

# Build as CommonJS
moduul build --format cjs

# Custom input/output directories
moduul build --input src --output build

# Build and zip
moduul build --zip

# Minified CJS build with ZIP
moduul build --format cjs --minify --zip

Project Structure

A typical plugin project structure:

my-plugin/
├── src/
│   └── index.ts          # Entry point
├── plugin.manifest.json  # Plugin manifest
├── package.json
├── tsconfig.json
└── dist/                 # Generated after build
    ├── index.js
    ├── index.js.map
    └── plugin.manifest.json

Manifest File

The plugin.manifest.json is required and must contain:

{
  "name": "my-plugin",
  "version": "1.0.0",
  "entryPoint": "./dist/index.js",
  "meta": {
    "description": "My awesome plugin",
    "author": "Your Name"
  }
}

Required Fields:

  • name - Unique plugin identifier (kebab-case recommended)
  • version - Semantic version (e.g., "1.0.0")
  • entryPoint - Path to the bundled output relative to plugin root

Optional Fields:

  • meta.description - Human-readable description
  • meta.author - Author name
  • meta.* - Any additional metadata

Build Process

The builder performs these steps:

  1. Validate Manifest - Reads and validates plugin.manifest.json
  2. Bundle Code - Uses esbuild to bundle TypeScript/JavaScript
  3. Generate Source Maps - Creates .map files for debugging
  4. Copy Manifest - Copies manifest to output directory
  5. Create ZIP (optional) - Compresses output into plugin.zip

Build Configuration

The builder uses esbuild with these settings:

  • Format: ESM by default; override with --format cjs or --format iife
  • Target: Node 20
  • Bundle: true (all dependencies bundled)
  • Minify: false by default; enable with --minify
  • Source Maps: true
  • Tree Shaking: Automatic

TypeScript Support

The builder automatically handles TypeScript:

src/index.ts:

export default {
  name: 'my-plugin',
  version: '1.0.0',
  
  async execute(): Promise<string> {
    return 'Hello from TypeScript!';
  }
};

Output (dist/index.js, ESM):

// Compiled and bundled ES module
export default {
  name: 'my-plugin',
  version: '1.0.0',
  
  async execute() {
    return 'Hello from TypeScript!';
  }
};

Output (dist/index.js, CJS) — built with --format cjs:

// Compiled and bundled CommonJS module
module.exports = {
  name: 'my-plugin',
  version: '1.0.0',
  
  async execute() {
    return 'Hello from TypeScript!';
  }
};

Plugin Examples

Basic Plugin

src/index.ts:

export default {
  name: 'basic-plugin',
  
  execute() {
    console.log('Plugin executed!');
  }
};

Async Plugin with Dependencies

src/index.ts:

import fetch from 'node-fetch';

export default {
  name: 'fetch-plugin',
  
  async execute(url: string) {
    const response = await fetch(url);
    return await response.json();
  }
};

Plugin with Initialization

src/index.ts:

let initialized = false;

export default {
  name: 'stateful-plugin',
  
  async init() {
    console.log('Initializing plugin...');
    initialized = true;
  },
  
  execute() {
    if (!initialized) {
      throw new Error('Plugin not initialized');
    }
    return 'Working!';
  }
};

Development Workflow

1. Create Plugin Structure

mkdir my-plugin
cd my-plugin
npm init -y

2. Install Dependencies

npm install -D @moduul/builder typescript

3. Create Files

package.json:

{
  "name": "my-plugin",
  "version": "1.0.0",
  "type": "module",
  "scripts": {
    "build": "moduul build",
    "build:cjs": "moduul build --format cjs"
  },
  "devDependencies": {
    "@moduul/builder": "*",
    "typescript": "^5.0.0"
  }
}

tsconfig.json:

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ES2022",
    "moduleResolution": "node",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "declaration": true
  },
  "include": ["src/**/*"]
}

plugin.manifest.json:

{
  "name": "my-plugin",
  "version": "1.0.0",
  "entryPoint": "./dist/index.js",
  "meta": {
    "description": "My plugin"
  }
}

src/index.ts:

export default {
  name: 'my-plugin',
  execute() {
    return 'Hello!';
  }
};

4. Build

npm run build

5. Test with PluginHost

import { PluginHost } from '@moduul/core';

const host = new PluginHost({ folder: './my-plugin' });
await host.reload();

const plugin = host.find('my-plugin');
console.log(await plugin.plugin.execute());

Distribution

Local Testing

Copy the dist/ folder or plugin.zip to your plugins directory:

cp -r dist/ /path/to/plugins/my-plugin/
# or
cp plugin.zip /path/to/plugins/

NPM Publishing

You can publish plugins to npm:

npm publish dist/

Users can then install and use:

npm install your-plugin

Advanced Usage

Choosing a Module Format

# ESM output (default)
moduul build --format esm

# CommonJS output (required by some host environments)
moduul build --format cjs

# IIFE output (browser-compatible self-executing bundle)
moduul build --format iife

Custom Directories

# Custom input/output paths
moduul build --input src --output build

Multiple Outputs

Build multiple variants:

# Development ESM build
moduul build --output dist-dev

# Production CJS build with zip
moduul build --format cjs --output dist-prod --zip

Integration with NPM Scripts

package.json:

{
  "scripts": {
    "build": "moduul build",
    "build:cjs": "moduul build --format cjs",
    "build:prod": "moduul build --minify --zip",
    "clean": "rm -rf dist",
    "prepublishOnly": "npm run build:prod"
  }
}

Troubleshooting

Build Fails with Module Error

Ensure your package.json has "type": "module":

{
  "type": "module"
}

Manifest Validation Error

Check that your plugin.manifest.json has all required fields:

  • name (string)
  • version (string)
  • entryPoint (string, relative path)

Entry Point Not Found

Verify the path specified in --entry exists:

ls src/index.ts  # Should exist

API Usage (Programmatic)

You can also use the builder programmatically:

import { buildPlugin, watchPlugin } from '@moduul/builder';

// Build once
await buildPlugin({
  input: './src',
  output: './dist',
  format: 'cjs',   // 'esm' | 'cjs' | 'iife' — defaults to 'esm'
  minify: false,
  zip: false,
});

// Watch mode
await watchPlugin({
  input: './src',
  output: './dist',
  format: 'esm',
});

Related Packages

Requirements

  • Node.js 20 or higher
  • TypeScript 5.0+ (for TypeScript projects)

License

MIT