@awacloud/md is a pure-JS, browser-only Markdown parsing/serialization library. No dependency beyond @awacloud/fw (workspace). This guide takes you from installation to a first parse and render, and to the pre-built bundles.
Prerequisites: an ES-module environment (a browser with an import map, Bun, or Node) with @awacloud/fw and @awacloud/md resolvable; no Node-only API is used by the package.
Install
npm install @awacloud/md
In an HTML page, import map:
<script type="importmap">
{ "imports": {
"@awacloud/fw": "/node_modules/@awacloud/fw/src/main.js",
"@awacloud/fw/": "/node_modules/@awacloud/fw/src/",
"@awacloud/md": "/node_modules/@awacloud/md/src/main.js",
"@awacloud/md/": "/node_modules/@awacloud/md/src/"
}}
</script>
<script type="module" src="./app.js"></script>
Factory pattern
Every module follows the @awacloud/fw contract (see module-pattern):
const descriptor = {
name: 'md',
dependencies: [ /* … */ ],
factory(...deps) { /* … */ return api; }
};
src/main.js (the package root, @awacloud/md) is strict factory-only: it exports the four descriptor arrays (fw_require, modules, extras, bundle) plus every module descriptor by binding name — never a materialized/resolved instance. There is no top-level md singleton or createMd helper to import directly; instances are produced by resolving a descriptor's factory() through an @awacloud/fw ModuleRuntime, or via the pre-built dist/ bundles (see below).
First parse / render
Register the core modules on an @awacloud/fw runtime, then resolve 'md':
import { runtime } from '@awacloud/fw';
import { fw_require, modules } from '@awacloud/md';
runtime.registerAll(fw_require);
runtime.registerAll(modules);
const md = runtime.resolve('md');
const ast = md.parse('# Hello\n\n*world*');
const html = md.render(ast); // an AST goes through `render`
// '<h1>Hello</h1>\n<p><em>world</em></p>\n'
md.renderHtml(text) takes a string (not an AST): it parses then renders in one step:
md.renderHtml('# Hello'); // '<h1>Hello</h1>\n'
modules is topologically ordered — registering in that order guarantees every dependency is satisfied before its consumer. runtime.register(m) (singular, in a loop) works too; registerAll is just the batch form.
Or, with the bootstrap helper
bootstrapMd does the registration ceremony above in one call and returns lazy accessors:
import { bootstrapMd } from '@awacloud/md/bootstrap.js';
const { md } = bootstrapMd();
md.renderHtml('# Hello'); // '<h1>Hello</h1>\n'
Isolated instances (md.createMd(opts))
The resolved md instance exposes .createMd(opts), which produces a fresh, isolated instance (its own .use() extension list) — useful for workers or divergent configurations:
const isolated = md.createMd({ sourcepos: true });
isolated.parse('# Title\n\nbody');
The .use(...) hook
Every instance exposes .use(ext) to plug in an extra. The contract is:
const myExtra = { name: 'myExtra', install(md) { /* patch md.parse / md.render / md.renderHtml */ } };
Idempotent: the same name is installed only once.
Extras are themselves factory descriptors — resolve one through the runtime (alongside modules) to get the installable { name, install } object:
import { runtime } from '@awacloud/fw';
import { fw_require, modules, extras } from '@awacloud/md';
runtime.registerAll(fw_require);
runtime.registerAll(modules);
runtime.registerAll(extras);
const md = runtime.resolve('md');
const mdFrontmatter = runtime.resolve('mdFrontmatter');
md.use(mdFrontmatter);
const ast = md.parse('---\ntitle: x\n---\n\nbody');
ast.data.frontmatter; // { lang: 'yaml', content: 'title: x' }
Full bundle (all extras)
There is no separate createMdFull helper — resolve the mdFullBundle descriptor (which .use()-installs all 10 extras in a deterministic order) after registering modules, extras and bundle:
import { runtime } from '@awacloud/fw';
import { fw_require, modules, extras, bundle } from '@awacloud/md';
runtime.registerAll(fw_require);
runtime.registerAll(modules);
runtime.registerAll(extras);
runtime.registerAll(bundle);
const mdFull = runtime.resolve('mdFullBundle');
mdFull.renderHtml('---\ntitle: x\n---\n\n:rocket: $e=mc^2$ ==go==');
mdFullBundle chains the 10 .use(...) calls in a deterministic order (frontmatter first, mermaid last — see bundles/md-full.md).
Pre-built bundles (Worker / zero-setup)
For Worker contexts where factory.toString() must produce a fully serializable closure — or simply to skip the ModuleRuntime registration ceremony entirely — each of the 2 assembly roots (md, md-full) is available pre-built under dist/:
import { mdBundled } from '@awacloud/md/standalone/md.js';
const md = mdBundled.factory(); // deps: [] — zero setup, ready instance
md.renderHtml('# Hello');
| Pre-built bundle | Dependencies | Notes |
|---|---|---|
dist/standalone/md.js (mdBundled) |
[] |
Core, everything inlined. Ideal for a Worker or a zero-setup script. |
dist/build/md.js (mdPackage) |
['secPolicy','sanitize','htmlEntities','url'] |
Core, delegated to @awacloud/fw. |
dist/standalone/md-full.js (mdFullBundled) |
[] |
Core + 10 extras, everything inlined. |
dist/build/md-full.js (mdFullPackage) |
['secPolicy','sanitize','htmlEntities','url'] |
Core + 10 extras, delegated to @awacloud/fw. |
Regeneration: bun run gen:bundles (idempotent).
Concrete input → parsed AST
Source:
# Title
Body with [link](https://example.com).
After md.parse(text):
ast
// → Node('document') with firstChild:
// Node('heading', level=1) -> Node('text', literal='Title')
// Node('paragraph') -> [
// Node('text', literal='Body with '),
// Node('link', destination='https://example.com') ->
// Node('text', literal='link'),
// Node('text', literal='.')
// ]
ast.data.refmap exposes the map of link reference definitions.
Next steps
- Read+write — parse + multi-target render + roundtrip
- Extending — write an extra
- Coverage — CommonMark + GFM + extras
- API index