@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.jsonis 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/builderQuick 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 cjsBuild with ZIP
# Create a ZIP archive for distribution
moduul build --zipCLI 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, oriife(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 --zipProject 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.jsonManifest 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 descriptionmeta.author- Author namemeta.*- Any additional metadata
Build Process
The builder performs these steps:
- Validate Manifest - Reads and validates
plugin.manifest.json - Bundle Code - Uses esbuild to bundle TypeScript/JavaScript
- Generate Source Maps - Creates
.mapfiles for debugging - Copy Manifest - Copies manifest to output directory
- Create ZIP (optional) - Compresses output into
plugin.zip
Build Configuration
The builder uses esbuild with these settings:
- Format: ESM by default; override with
--format cjsor--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 -y2. Install Dependencies
npm install -D @moduul/builder typescript3. 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 build5. 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-pluginAdvanced 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 iifeCustom Directories
# Custom input/output paths
moduul build --input src --output buildMultiple Outputs
Build multiple variants:
# Development ESM build
moduul build --output dist-dev
# Production CJS build with zip
moduul build --format cjs --output dist-prod --zipIntegration 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 existAPI 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
- @moduul/core - Plugin host system
- @moduul/boilerplate - Official plugin template
Requirements
- Node.js 20 or higher
- TypeScript 5.0+ (for TypeScript projects)
License
MIT
