weml-monaco
v0.4.0
Published
WEML (White Estate Markup Language) language support for the Monaco editor: syntax highlighting, autocomplete, hover docs, structure diagnostics, outline and canonical formatting.
Maintainers
Readme
WEML language support for Monaco
A Monaco-editor port of the WEML (White Estate Markup Language) VS Code
extension. It provides, for models with language id weml:
- Syntax highlighting — a Monarch tokenizer (HTML-flavoured, since WEML is HTML5-compatible).
- Context-aware autocomplete — tags / attributes / attribute values that
respect the WEML nesting rules, with element-scaffolding snippets and
composite snippets (
table,w-list,figure,div + w-heading, …). - Hover tooltips — documentation generated from
docs/(tags, attributes, enum values,metafields,<a href>link types). - Structure diagnostics — unknown tags/attributes, required attributes,
enum values, child cardinality,
<a href>/idrules, duplicate<div id>, required<meta>in<head>; surfaced as editor markers. - Document outline — by
<w-heading level=…>and top-level<div>s. - Formatting — "Format Document" runs the safe V2 canonical normalizer. It validates matching/closed tags and text preservation before returning an edit; invalid documents remain unchanged.
- Toolbar & editor commands — an optional DOM toolbar (top panel) plus
keybindings that mirror the VS Code extension: wrap selection in
<w-format>/other inlines, snippet buttons, "Renumber div ids", and a HTML ↔ canonical format cycle.
This package does not modify the original VS Code extension — it is a
standalone re-implementation living entirely under monaco-editor/.
Install & build
npm install weml-monacoThe package has no runtime dependencies (node-html-parser and
vscode-html-languageservice are bundled in) and monaco-editor is a peer
dependency you already have.
| entry point | file | for |
| ---------------------- | ----------------------- | -------------------------------------- |
| weml-monaco | dist/index.mjs | ESM — bundlers, import |
| weml-monaco | dist/index.cjs | CJS — require |
| weml-monaco/global | dist/index.global.js | <script> / CDN → window.WemlMonaco |
Working on the package itself:
cd monaco-editor
npm install
npm run build # tsup → dist/ (ESM + CJS + IIFE + declarations)
npm run typecheck # tsc --noEmit
npm test # build + Node smoke test
npm run check:package # publint + arethetypeswrong on the packed tarballUsage
Monaco is passed in at registration time, so this works no matter how Monaco itself is loaded (AMD/CDN, ESM bundle, or a global):
import * as monaco from 'monaco-editor';
import { registerWeml } from 'weml-monaco';
const disposable = registerWeml(monaco);
const model = monaco.editor.createModel(source, 'weml');
monaco.editor.create(document.getElementById('container'), { model });
// disposable.dispose(); // tears down every provider + diagnostics listenerregisterWeml(monaco, options?) accepts:
| option | default | meaning |
| ---------------------- | ----------- | -------------------------------------------------- |
| extensions | ['.weml'] | file extensions associated with the language |
| diagnostics | true | wire live validation → setModelMarkers |
| diagnosticsDebounceMs| 300 | debounce for re-validating after edits |
Diagnostics are attached to every weml model automatically (on creation,
content change, and language switch) and cleared on disposal. To validate a
model on demand instead, call validateWemlModel(monaco, model) and feed the
returned IMarkerData[] to monaco.editor.setModelMarkers. To run the
formatter directly, call formatWemlText(source).
Structural validation failures are also available through
isWemlStructureValidationFailure(error) and include a code, line and column.
Toolbar & editor commands
Monaco has no webview panels, so the VS Code toolbar is ported as a DOM top panel you mount yourself, plus per-editor actions/keybindings. Button behaviour is identical to the VS Code toolbar.
import { registerWeml, createWemlToolbar } from 'weml-monaco';
registerWeml(monaco);
const editor = monaco.editor.create(container, { model });
const toolbar = createWemlToolbar(monaco, editor, {
container: document.getElementById('weml-toolbar'), // where to mount the panel
onStatus: (msg) => console.log(msg), // optional status sink
// includeSearchRow: true, // stub search row (as in VS Code); default true
// onSearch: (query, lang) => { ... },
});
// toolbar.element → the toolbar DOM node
// toolbar.dispose() → removes the panel and its actionscreateWemlToolbar also registers the editor actions (so they work without the
panel too, via keybindings and the command palette):
| action / command | keybinding | effect |
| ------------------------- | ------------- | -------------------------------------------------- |
| weml.wrapFormat.bold | Ctrl+B | wrap selection in <w-format type="bold"> |
| weml.wrapFormat.italic | Ctrl+I | wrap selection in <w-format type="italic"> |
| weml.wrapFormat.underline| Ctrl+U | wrap selection in <w-format type="underline"> |
| weml.cycleFormat | Alt+Shift+F | cycle document: HTML ↔ canonical WEML formatting |
| weml.renumberDivIds | — | renumber every <div> id to a sequential 1…n |
To register the actions without a toolbar, call
registerWemlEditorActions(monaco, editor, options). The underlying operations
(wrapSelection, insertSnippetById, renumberDivIds, cycleFormat) and the
pure helpers (planDivIdRenumber, buildWrapSnippet) are exported too.
Snippet insertion uses Monaco's snippetController2, so $1/$2 tab stops
behave like editor.insertSnippet in VS Code.
Demo
npm run demo # builds dist/ and copies the IIFE bundle → demo/weml-monaco.js
# then open demo/index.html in a browserdemo/index.html loads Monaco from a CDN (AMD loader) and the WEML bundle as a
global, registers the language and opens a sample document. Try Ctrl+Space for
completions, hover a tag, "Format Document", Ctrl+Shift+O for the outline, or
introduce an error to see diagnostics.
How it relates to the VS Code extension
The schema and rules are reused verbatim from the extension:
| reused as-is | adapted for Monaco |
| ------------------------------------ | ---------------------------------------- |
| generated/schema.ts | schema/docs.ts (plain IMarkdownString) |
| schema/structureRules.ts | wemlDocument.ts (model ↔ LSP positions) |
| schema/snippets.ts | providers/* (Monaco provider APIs) |
| wemlContentNormalizeV2.js | index.ts (registration + diagnostics) |
Parsing still uses vscode-html-languageservice (it is editor-agnostic and
runs in the browser); the only real work was swapping the vscode.* provider
APIs for monaco.languages.* and converting between Monaco's 1-based
line/column positions and the language service's 0-based LSP positions.
