Appearance
JavaScript Guide
Build Noorle plugins with JavaScript.
Why JavaScript?
Strengths
- Familiar Syntax - Use the world's most popular programming language
- Rapid Development - Fast iteration with dynamic typing
- Native JSON Handling - Built-in JSON parsing and serialization
Best For
- Quick prototypes and simple plugins
- Developers comfortable with dynamic typing
- Projects that prioritize development speed
Trade-offs
- No compile-time type checking
- Runtime errors instead of build-time errors
Quick Start
Prerequisites
bash
# Install Noorle CLI
curl -L cli.noorle.dev | sh
Create Plugin
bash
# Initialize from template
noorle plugin init my-plugin --template javascript
cd my-plugin
# Install dependencies (JavaScript toolchain, wasm tools)
noorle plugin prepare
# Build and deploy
noorle plugin publish
Minimal Example
javascript
/**
* Process input data
* @param {string} input - The data to process
* @returns {string} Processed result
*/
export function process(input) {
if (!input) {
throw new Error("Input cannot be empty");
}
const result = {
original: input,
processed: true,
timestamp: new Date().toISOString()
};
return JSON.stringify(result, null, 2);
}
Project Structure
tree
my-plugin/
├── app.js # Main implementation
├── wit/world.wit # API definition
├── package.json # Language dependencies
├── build.sh # Build script
├── prepare.sh # Dependency installer
├── noorle.yaml # Plugin config (optional)
└── dist/ # Build output
├── plugin.wasm # Compiled component
└── *.npack # Deployment archive
File Descriptions
File | Purpose |
---|---|
app.js | Plugin implementation |
wit/world.wit | WebAssembly interface definition |
package.json | Dependencies and project config |
build.sh | Compilation and component creation |
prepare.sh | Installs JavaScript toolchain and wasm tools |
Implementation Guide
Basic Plugin Structure
javascript
// ES module configuration required
const CONFIG = {
mode: 'development',
verbose: true,
version: '1.0.0'
};
function validateInput(input) {
if (!input || typeof input !== 'string') {
throw new Error("Input must be a non-empty string");
}
if (!input.trim()) {
throw new Error("Input cannot be empty");
}
return true;
}
export function process(input) {
validateInput(input);
try {
const data = JSON.parse(input);
const result = {
...data,
processed: true,
timestamp: new Date().toISOString(),
config: CONFIG
};
return JSON.stringify(result, null, 2);
} catch (error) {
throw new Error(`Processing failed: ${error.message}`);
}
}
Working with WIT
Define your API in wit/world.wit
:
wit
package example:my-plugin;
world my-plugin-component {
/// Function documentation becomes tool description
export process: func(input: string) -> result<string, string>;
}
JavaScript-specific binding:
javascript
// Export functions that match your WIT interface exactly
export function process(input) {
// Implementation must match WIT signature
return processData(input);
}
Error Handling
javascript
function safeJsonParse(input, fallback = null) {
try {
return JSON.parse(input);
} catch (error) {
if (fallback !== null) {
return fallback;
}
throw new Error(`Invalid JSON: ${error.message}`);
}
}
export function processData(input) {
// Validate input type
if (typeof input !== 'string') {
throw new Error(`Expected string, got ${typeof input}`);
}
// Parse with error handling
const data = safeJsonParse(input);
if (!data || typeof data !== 'object') {
throw new Error("Input must be a valid JSON object");
}
// Process with validation
const result = {
...data,
processed: true,
validatedAt: new Date().toISOString()
};
return JSON.stringify(result);
}
Environment Variables
javascript
import { getEnvironment } from "wasi:cli/[email protected]";
function loadConfig() {
const env = getEnvironment();
const config = {};
// Parse environment variables
for (const [key, value] of env) {
if (key === "API_KEY") {
config.apiKey = value;
} else if (key === "DEBUG") {
config.debug = value === "true";
} else if (key === "MAX_ITEMS") {
config.maxItems = parseInt(value, 10) || 100;
}
}
return config;
}
export function getConfiguration() {
const config = loadConfig();
return JSON.stringify({
...config,
timestamp: new Date().toISOString(),
loaded: true
});
}
JSON Processing
javascript
function validateUserData(data) {
const required = ['id', 'name', 'email'];
const missing = required.filter(field => !(field in data));
if (missing.length > 0) {
throw new Error(`Missing required fields: ${missing.join(', ')}`);
}
if (typeof data.id !== 'number') {
throw new Error("Field 'id' must be a number");
}
if (typeof data.name !== 'string') {
throw new Error("Field 'name' must be a string");
}
return true;
}
export function processUser(input) {
const userData = safeJsonParse(input);
validateUserData(userData);
const processedUser = {
...userData,
processed: true,
processedAt: new Date().toISOString(),
valid: true
};
return JSON.stringify(processedUser, null, 2);
}
HTTP Requests (Optional)
Required WIT imports:
wit
import wasi:http/[email protected];
import wasi:http/[email protected];
Implementation:
javascript
// HTTP client example (simplified)
async function fetchData(url) {
try {
// Note: Actual WASI HTTP implementation required
const response = await makeHttpRequest(url);
return {
data: response.data,
status: response.status,
success: true
};
} catch (error) {
return {
data: null,
status: 500,
success: false,
error: error.message
};
}
}
export async function processWithHttp(input) {
const { url } = JSON.parse(input);
const result = await fetchData(url);
return JSON.stringify(result);
}
Required permissions:
yaml
permissions:
network:
allow:
- host: "api.example.com"
Testing
Local Testing with Wasmtime
bash
# Build the plugin
noorle plugin build
# Test with WAVE format
wasmtime run --invoke 'process("test input")' dist/plugin.wasm
wasmtime run --invoke 'add(1, 2)' dist/plugin.wasm
wasmtime run --invoke 'greet("World")' dist/plugin.wasm
# With environment variables
wasmtime run --env KEY=value --invoke 'process("test")' dist/plugin.wasm
Language-Specific Features
Class-Based Architecture
javascript
class DataProcessor {
constructor(options = {}) {
this.mode = options.mode || 'default';
this.verbose = options.verbose || false;
this.config = this.loadConfig();
}
loadConfig() {
return {
timeout: 5000,
retries: 3,
format: 'json'
};
}
validate(input) {
if (!input) {
throw new Error("Input is required");
}
return true;
}
transform(data) {
return {
...data,
transformed: true,
mode: this.mode,
timestamp: new Date().toISOString()
};
}
process(input) {
this.validate(input);
if (this.verbose) {
console.log(`Processing in ${this.mode} mode`);
}
const parsed = JSON.parse(input);
return this.transform(parsed);
}
}
export function processWithClass(input) {
const processor = new DataProcessor({
mode: 'advanced',
verbose: true
});
const result = processor.process(input);
return JSON.stringify(result);
}
Functional Programming Patterns
javascript
// Higher-order functions for data processing
function createProcessor(transformFn) {
return function(input) {
const data = JSON.parse(input);
const transformed = transformFn(data);
return JSON.stringify(transformed);
};
}
// Composition helpers
const pipe = (...fns) => (value) => fns.reduce((acc, fn) => fn(acc), value);
const addTimestamp = (data) => ({
...data,
timestamp: new Date().toISOString()
});
const markProcessed = (data) => ({
...data,
processed: true
});
const addMetadata = (data) => ({
...data,
metadata: {
version: '1.0.0',
processor: 'javascript'
}
});
export function processWithComposition(input) {
const data = JSON.parse(input);
const processData = pipe(
addTimestamp,
markProcessed,
addMetadata
);
const result = processData(data);
return JSON.stringify(result, null, 2);
}
Build Configuration
Dependencies
package.json:
json
{
"type": "module",
"dependencies": {
"@bytecodealliance/componentize-js": "0.19.1",
"@bytecodealliance/jco": "1.15.0"
},
"devDependencies": {
"@types/node": "^18.0.0"
}
}
Build Optimization
Optimize package.json for WebAssembly:
json
{
"type": "module",
"main": "app.js",
"scripts": {
"test": "node --test",
"build": "./build.sh"
}
}
Build Process
bash
# Build your plugin (handles compilation, optimization, packaging)
noorle plugin build
The build process:
- Prepares JavaScript runtime environment
wkg
fetches WIT dependencies from imports- Creates component with JavaScript toolchain
- Optimizes output
- Packages into .npack archive
Troubleshooting
Common Issues
Build Fails
Problem: Node.js version compatibility
Solution:
bash
# Update to Node.js 18+
nvm install 18
nvm use 18
# Clear dependencies and reinstall
rm -rf node_modules package-lock.json
npm install
Runtime Error
Problem: JSON parsing failures
Solution:
javascript
function safeJsonProcess(input) {
try {
const data = JSON.parse(input);
return processData(data);
} catch (error) {
throw new Error(`Invalid JSON input: ${error.message}`);
}
}
JavaScript-Specific Issue
Problem: Module import errors
Solution:
bash
# Ensure package.json has ES module configuration
echo '{"type": "module"}' > package.json
# Check imports use .js extensions where needed
# import { helper } from './utils.js';
Debugging
Enable debug output:
bash
DEBUG=1 noorle plugin build
View WASM exports:
bash
wasm-tools component wit dist/plugin.wasm
Performance
Optimization tips:
- Keep dependencies minimal for WASM compatibility
- Use built-in JSON methods for best performance
- Avoid large utility libraries
Best Practices
Do's
- ✅ Use ES module syntax consistently
- ✅ Validate all inputs thoroughly
- ✅ Implement clear error messages
- ✅ Leverage JavaScript's JSON strengths
Don'ts
- ❌ Use Node.js-specific APIs
- ❌ Import CommonJS modules
- ❌ Assume input types without validation
Security
- Validate all inputs
- Use minimal permissions
- Never log sensitive data
- Sanitize error messages
Advanced Topics
Custom WASI Interfaces
wit
world my-plugin-component {
import my-custom:interface/types@1.0.0;
export process: func(input: string) -> result<string, string>;
}
javascript
// Import and use custom interfaces
import { customFunction } from "my-custom:interface/[email protected]";
export function useCustomInterface(input) {
const data = JSON.parse(input);
const result = customFunction(data);
return JSON.stringify({ result });
}
Performance Monitoring
javascript
class PerformanceTracker {
constructor() {
this.metrics = new Map();
}
time(name, operation) {
const startTime = Date.now();
const result = operation();
const duration = Date.now() - startTime;
this.metrics.set(name, {
duration,
timestamp: new Date().toISOString()
});
console.log(`Operation ${name} took ${duration}ms`);
return result;
}
getMetrics() {
return Object.fromEntries(this.metrics);
}
}
const tracker = new PerformanceTracker();
export function processWithMetrics(input) {
return tracker.time('processData', () => {
return processData(input);
});
}
Working with Async Operations
javascript
// Handle async operations with proper error handling
export async function processAsync(input) {
try {
// Simulate async work
await new Promise(resolve => setTimeout(resolve, 100));
const data = JSON.parse(input);
const result = {
...data,
processed: true,
processingTime: 100
};
return JSON.stringify(result);
} catch (error) {
throw new Error(`Async processing failed: ${error.message}`);
}
}
Resources
Documentation
Tools
- Node.js - JavaScript runtime environment
- ComponentizeJS - JavaScript to WASM compiler
Examples
Next Steps
Continue Learning
- Other Language Guides - Explore Python, TypeScript, JavaScript, Go, Rust
- Configuration - Advanced plugin settings
- Publishing - Deploy your plugin
Build Your Plugin
- Quick Start - Step-by-step guide
- Project Structure - Understanding plugin anatomy
- Platform Overview - How plugins work in Noorle