@icaroglauco/dsljs
v0.1.3
Published
IDSL macro system and structural expander for JavaScript
Maintainers
Readme
@icaroglauco/dsljs
Compile-time macros for JavaScript.
@icaroglauco/dsljs lets you define local DSLs inside a .dsljs file and expand them into plain JavaScript before runtime. The generated output is ordinary JavaScript with no additional runtime layer, which makes the tool useful for code generation, structural syntax experiments, and domain-specific authoring workflows.
This package is designed for teams that want custom authoring syntax while keeping deployment artifacts plain JavaScript.
Highlights
- Compile custom syntax to standard JavaScript
- Keep macro definitions close to the code that uses them
- Generate deterministic output with zero runtime overhead
- Use the package from the CLI, from Vite, or programmatically
- Add editor and ESLint support for
.dsljsfiles
Installation
For most projects this package should be installed as a development dependency:
npm install -D @icaroglauco/dsljsThe package exposes:
dsljsandidslCLI commands@icaroglauco/dsljs/vitefor Vite integration@icaroglauco/dsljs/eslint-configfor ESLint- the parser/compiler API from
@icaroglauco/dsljs
Quick Start
Create a file named hello.dsljs:
macros: {
$macro LOG $msg #(
console.log("[Macro Log]:", $msg);
)#
$macro style($text) #(
console.log($text);
)#
}
const project = "dsljs";
LOG `Build completed for ${project}`
style(/*css*/`
.banner {
padding: 16px;
border: 1px solid #d0d7de;
}
`)Compile it:
npx dsljs hello.dsljs hello.jsGenerated output:
const project = "dsljs";
console.log("[Macro Log]:", `Build completed for ${project}`);
console.log(/*css*/`
.banner {
padding: 16px;
border: 1px solid #d0d7de;
}
`);How It Works
Each .dsljs source file can contain a macros: { ... } block. That block declares rewrite rules using $macro, and the rest of the file is expanded against those rules during compilation.
At a high level:
- The compiler reads the
macros: { ... }block. - Macro definitions are parsed into rewrite rules.
- The remaining file is transformed.
- The final result is emitted as standard JavaScript.
Macro definitions do not remain in the final output.
Professional Usage Examples
1. Structural Logging and Build-Time CSS
This pattern is useful when you want a concise authoring syntax while still shipping plain JavaScript:
macros: {
$macro LOG $msg #(
console.log("[Macro Log]:", $msg);
)#
$macro style($text) #(
console.log($text);
)#
}
LOG `Starting dashboard build`
style(/*css*/`
#dashboard {
display: grid;
gap: 24px;
}
`)Expanded JavaScript:
console.log("[Macro Log]:", `Starting dashboard build`);
console.log(/*css*/`
#dashboard {
display: grid;
gap: 24px;
}
`);2. Domain-Specific Factory Generation
Macros can encode a business or application-specific construction pattern.
Source:
macros: {
$macro struct game $name[
types: $types,
events: $events
] => {
$($prop = $value;)...
}
#(
const $name = function () {
const types = { $types };
const events = [ $events ];
const listeners = Object.fromEntries(
events.map(eventName => [eventName, []])
);
return {
types: () => types,
events: () => events,
listeners: () => listeners,
on(eventName, listener) {
if (!listeners[eventName]) {
listeners[eventName] = [];
}
listeners[eventName].push(listener);
},
emit(eventName, payload) {
for (const listener of listeners[eventName] ?? []) {
listener(payload);
}
},
$($prop: $value,)...
};
}
)#
}
struct game arena[
types: {
state: "GameState",
entity: "Entity",
player: "Player"
},
events: ["boot", "tick", "gameover"]
] => {
version = "0.1.0";
initialScene = "forest";
}
const game = arena();
game.emit("boot", { scene: game.initialScene });This style is useful when you want a compact authoring format for repetitive object or module generation.
3. HTML-Like Template Authoring
Another good fit is markup-oriented generation:
macros: {
$macro tag [
$tagname
{ $($attr:$value,)... }
( $inner )
] #(
`<${$tagname}
$(
${$attr}=${$value}
)...
>
${$inner}
</${$tagname}>`
)#
}
const card = tag[
section
{ class: "profile-card", id: "user-42" }
(
`<h2>Ana</h2><p>Platform Engineer</p>`
)
];That gives you a lightweight way to create project-specific view or content syntaxes without introducing a custom runtime.
CLI
The package ships with the dsljs command.
dsljs <input.dsljs> [output.js]
dsljs dist <srcDir> <outDir>
dsljs watch <src> <out>
dsljs run <file.dsljs> [...args]
dsljs watch-run <file.dsljs> [...args]
dsljs shadow <file.dsljs> [file.generated.js]
dsljs watch-shadow <file.dsljs> [file.generated.js]Common workflows:
Compile a single file:
npx dsljs src/main.dsljs dist/main.jsPrint the compiled JavaScript to stdout:
npx dsljs src/main.dsljsCompile an entire directory:
npx dsljs dist src distWatch and rebuild files:
npx dsljs watch src distGenerate a shadow file for editor tooling:
npx dsljs shadow src/main.dsljs src/main.generated.jsRun a DSL file directly after expansion:
npx dsljs run scripts/task.dsljs --env productionVite Integration
Use the Vite plugin when your project imports .dsljs files directly.
vite.config.js
import { defineConfig } from "vite";
import dsljs from "@icaroglauco/dsljs/vite";
export default defineConfig({
plugins: [dsljs()]
});Example module:
import "./startup.dsljs";The plugin expands .dsljs sources before Vite continues with the rest of the pipeline.
Programmatic API
The root package exports the compiler helpers from src/parser.js.
Compile a source string:
import { compileDslSource } from "@icaroglauco/dsljs";
const source = `
macros: {
$macro LOG $msg #( console.log($msg); )#
}
LOG "hello"
`;
const output = compileDslSource(source);
console.log(output);Compile from a file:
import { compileDslFile } from "@icaroglauco/dsljs";
const output = compileDslFile("src/main.dsljs");
console.log(output);Low-level helpers such as stripMacrosBlock, parseMacrosFromBlock, and expandMacros are also exported for advanced workflows.
ESLint
The package includes a ready-to-use flat config for .dsljs files:
import dsljsConfig from "@icaroglauco/dsljs/eslint-config";
export default dsljsConfig;This config wires the custom processor and validates .dsljs sources through ESLint.
VS Code Support
On install, the package tries to set up local VS Code support automatically by:
- installing the bundled editor extension locally
- updating
.vscode/settings.json - updating
.vscode/extensions.json - creating an ESLint config proxy if one does not exist
- creating a minimal
jsconfig.jsonif one does not exist
If you need to skip this behavior, set the DSLJS_SKIP_VSCODE_SETUP=1 environment variable before installation.
The editor support is designed around .dsljs and .idsl files, while the Vite plugin currently targets .dsljs imports.
Repository Examples
The repository includes real examples you can use as reference:
example/example.dsljsexample/game.dsljs
Notes
- Expansion happens at compile time, not at runtime
- Generated output is standard JavaScript
- Macro design is intentionally flexible and low-level
- The best results come from keeping macros small, explicit, and domain-focused
