sommark 5.0.5 → 5.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/async-hooks.js +19 -0
- package/core/evaluator.js +212 -22
- package/core/helpers/config-loader.js +29 -9
- package/core/helpers/lib.js +1 -1
- package/core/helpers/preprocessor.js +19 -0
- package/core/modules.js +59 -21
- package/core/parser.js +6 -3
- package/core/pathe-bundle.js +1 -0
- package/core/transpiler.js +130 -15
- package/core/validator.js +17 -4
- package/dist/sommark.browser.js +669 -214
- package/dist/sommark.browser.lite.js +458 -191
- package/dist/sommark.parser.js +6 -3
- package/esbuild.js +64 -0
- package/index.browser.js +4 -1
- package/index.js +102 -1
- package/index.shared.js +13 -2
- package/mappers/languages/markdown.js +99 -81
- package/mappers/languages/xml.js +76 -61
- package/mappers/shared/index.js +10 -1
- package/package.json +11 -3
- package/rollup.js +79 -0
- package/vite.js +20 -0
package/mappers/languages/xml.js
CHANGED
|
@@ -4,100 +4,115 @@ import { registerSharedOutputs } from "../shared/index.js";
|
|
|
4
4
|
/**
|
|
5
5
|
* Renders a standard XML tag based on the provided identifier and arguments.
|
|
6
6
|
* Ensures strict attribute quoting and handles self-closing tags for empty bodies.
|
|
7
|
-
*
|
|
7
|
+
*
|
|
8
8
|
* @param {string} id - The XML tag identifier (case-sensitive).
|
|
9
9
|
* @param {Object} props - Key-value pairs to be rendered as XML attributes.
|
|
10
10
|
* @param {string} content - The rendered inner content of the tag.
|
|
11
11
|
* @returns {string} The fully rendered XML tag string.
|
|
12
12
|
*/
|
|
13
13
|
const renderXmlTag = function (id, props, content, isSelfClosing) {
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
// XML is case-sensitive, so we use the exact id provided
|
|
15
|
+
const element = this.tag(id);
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
17
|
+
// Filter out positional indices (numeric keys) for XML attributes
|
|
18
|
+
const namedArgs = {};
|
|
19
|
+
Object.keys(props).forEach(key => {
|
|
20
|
+
if (isNaN(parseInt(key))) {
|
|
21
|
+
namedArgs[key] = props[key];
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
// In XML, attributes must always have values (strict = true)
|
|
26
|
+
element.attributes(namedArgs, true);
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
const hasBody = typeof content === "string" && content.trim().length > 0;
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
if (isSelfClosing || !hasBody) {
|
|
31
|
+
return element.selfClose();
|
|
32
|
+
}
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
return element.body(content);
|
|
35
35
|
};
|
|
36
36
|
|
|
37
37
|
/**
|
|
38
38
|
* The XML Mapper used for creating XML pages.
|
|
39
39
|
*/
|
|
40
40
|
const XML = Mapper.define({
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
41
|
+
/**
|
|
42
|
+
* Renders a comment in XML format.
|
|
43
|
+
* @param {string} text - The comment content.
|
|
44
|
+
* @returns {string}
|
|
45
|
+
*/
|
|
46
|
+
comment(text) {
|
|
47
|
+
return `<!-- ${text} -->`;
|
|
48
|
+
},
|
|
49
49
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
50
|
+
/**
|
|
51
|
+
* Resolves unknown tags by preserving their original case and applying XML rules.
|
|
52
|
+
* @param {Object} node - The AST node representing the unknown tag.
|
|
53
|
+
* @returns {Object} Renderer definition for the tag.
|
|
54
|
+
*/
|
|
55
|
+
getUnknownTag(node) {
|
|
56
|
+
const id = node.id;
|
|
57
|
+
return {
|
|
58
|
+
render: ({ props, content, isSelfClosing }) => renderXmlTag.call(this, id, props, content, isSelfClosing),
|
|
59
|
+
options: {}
|
|
60
|
+
};
|
|
61
|
+
},
|
|
62
|
+
options: {
|
|
63
|
+
trimAndWrapBlocks: true
|
|
64
|
+
}
|
|
62
65
|
});
|
|
63
66
|
|
|
64
67
|
/**
|
|
65
68
|
* Registers the XML declaration as a self-closing block.
|
|
66
69
|
* Usage: [xml = version: "1.0", encoding: "UTF-8"]
|
|
67
70
|
*/
|
|
68
|
-
XML.register(
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
71
|
+
XML.register(
|
|
72
|
+
"xml",
|
|
73
|
+
({ props }) => {
|
|
74
|
+
const version = props.version || "1.0";
|
|
75
|
+
const encoding = props.encoding || "UTF-8";
|
|
76
|
+
return `<?xml version="${version}" encoding="${encoding}"?>`;
|
|
77
|
+
},
|
|
78
|
+
{ rules: { is_empty_body: true } }
|
|
79
|
+
);
|
|
73
80
|
|
|
74
81
|
/**
|
|
75
82
|
* Registers the DOCTYPE declaration.
|
|
76
83
|
* Usage: [doctype = root: "note", system: "note.dtd"]
|
|
77
84
|
*/
|
|
78
|
-
XML.register(
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
85
|
+
XML.register(
|
|
86
|
+
["DOCTYPE", "doctype"],
|
|
87
|
+
({ props }) => {
|
|
88
|
+
const root = props.root || "root";
|
|
89
|
+
const system = props.system;
|
|
90
|
+
const pub = props.public || props.fpi;
|
|
82
91
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
},
|
|
92
|
+
if (pub && system) {
|
|
93
|
+
return `<!DOCTYPE ${root} PUBLIC "${pub}" "${system}">`;
|
|
94
|
+
} else if (system) {
|
|
95
|
+
return `<!DOCTYPE ${root} SYSTEM "${system}">`;
|
|
96
|
+
}
|
|
97
|
+
return `<!DOCTYPE ${root}>`;
|
|
98
|
+
},
|
|
99
|
+
{ rules: { is_empty_body: true } }
|
|
100
|
+
);
|
|
90
101
|
|
|
91
102
|
/**
|
|
92
103
|
* Registers the XML stylesheet processing instruction.
|
|
93
104
|
* Usage: [xml-stylesheet = href: "style.xsl"]
|
|
94
105
|
*/
|
|
95
|
-
XML.register(
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
106
|
+
XML.register(
|
|
107
|
+
"xml-stylesheet",
|
|
108
|
+
({ props }) => {
|
|
109
|
+
const type = props.type || "text/xsl";
|
|
110
|
+
const href = props.href;
|
|
111
|
+
if (!href) return "";
|
|
112
|
+
return `<?xml-stylesheet type="${type}" href="${href}"?>`;
|
|
113
|
+
},
|
|
114
|
+
{ rules: { is_empty_body: true } }
|
|
115
|
+
);
|
|
101
116
|
|
|
102
117
|
/**
|
|
103
118
|
* Registers CDATA sections.
|
|
@@ -105,8 +120,8 @@ XML.register("xml-stylesheet", ({ props }) => {
|
|
|
105
120
|
* Self-closing: [cdata = "raw content" !] or [cdata = text: "raw content" !]
|
|
106
121
|
*/
|
|
107
122
|
XML.register("cdata", ({ props, content, isSelfClosing }) => {
|
|
108
|
-
|
|
109
|
-
|
|
123
|
+
const text = isSelfClosing ? (props[0] ?? props.text ?? "") : content;
|
|
124
|
+
return `<![CDATA[${text}]]>`;
|
|
110
125
|
});
|
|
111
126
|
|
|
112
127
|
registerSharedOutputs(XML);
|
package/mappers/shared/index.js
CHANGED
|
@@ -1,8 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Registers universal utility blocks shared across all SomMark mappers.
|
|
3
|
-
* These blocks are considered "Format Agnostic."
|
|
4
3
|
*
|
|
5
4
|
* @param {Mapper} mapper - The mapper instance to register tags on.
|
|
6
5
|
*/
|
|
7
6
|
export function registerSharedOutputs(mapper) {
|
|
7
|
+
mapper.register(
|
|
8
|
+
["raw", "Raw"],
|
|
9
|
+
({ content }) => {
|
|
10
|
+
return content;
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
escape: false, rules: {
|
|
14
|
+
required_directives: ["raw"]
|
|
15
|
+
} }
|
|
16
|
+
);
|
|
8
17
|
}
|
package/package.json
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sommark",
|
|
3
|
-
"version": "5.0
|
|
3
|
+
"version": "5.2.0",
|
|
4
4
|
"description": "SomMark is a template language that compiles to multiple output formats — HTML, JSON, YAML, TOML, CSV, Markdown, XML, and more.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"files": [
|
|
7
7
|
"index.js",
|
|
8
8
|
"index.browser.js",
|
|
9
9
|
"index.shared.js",
|
|
10
|
+
"async-hooks.js",
|
|
11
|
+
"vite.js",
|
|
12
|
+
"rollup.js",
|
|
13
|
+
"esbuild.js",
|
|
10
14
|
"core/",
|
|
11
15
|
"mappers/",
|
|
12
16
|
"helpers/",
|
|
@@ -25,18 +29,22 @@
|
|
|
25
29
|
"type": "module",
|
|
26
30
|
"exports": {
|
|
27
31
|
".": "./index.js",
|
|
28
|
-
"./browser": "./index.browser.js"
|
|
32
|
+
"./browser": "./index.browser.js",
|
|
33
|
+
"./vite": "./vite.js",
|
|
34
|
+
"./rollup": "./rollup.js",
|
|
35
|
+
"./esbuild": "./esbuild.js"
|
|
29
36
|
},
|
|
30
37
|
"scripts": {
|
|
31
38
|
"test": "vitest",
|
|
32
39
|
"test:run": "vitest run --pool=forks --maxWorkers=1",
|
|
33
40
|
"test:watch": "vitest watch",
|
|
34
41
|
"test:html": "vitest run tests/html",
|
|
42
|
+
"build:pathe": "node scripts/build-pathe.js",
|
|
35
43
|
"build:browser": "rollup -c rollup.browser.config.js",
|
|
36
44
|
"build:lite": "rollup -c rollup.browser.lite.config.js",
|
|
37
45
|
"build:lexer": "rollup -c rollup.browser.lexer.config.js",
|
|
38
46
|
"build:parser": "rollup -c rollup.browser.parser.config.js",
|
|
39
|
-
"build:all": "npm run build:browser && npm run build:lite && npm run build:lexer && npm run build:parser",
|
|
47
|
+
"build:all": "npm run build:pathe && npm run build:browser && npm run build:lite && npm run build:lexer && npm run build:parser",
|
|
40
48
|
"prepublishOnly": "npm run build:all"
|
|
41
49
|
},
|
|
42
50
|
"bin": {
|
package/rollup.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
2
|
+
import nodePath from "node:path";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Rollup plugin for SomMark.
|
|
6
|
+
*
|
|
7
|
+
* Fixes two issues:
|
|
8
|
+
* 1. Prevents Rollup from tree-shaking QuickJS modules (they have side effects
|
|
9
|
+
* that Rollup incorrectly marks as removable).
|
|
10
|
+
* 2. Handles QuickJS WASM assets referenced via new URL("*.wasm", import.meta.url).
|
|
11
|
+
* Rollup does not copy or rewrite these automatically, so this plugin emits
|
|
12
|
+
* them as assets and rewrites the URLs to point to the output paths.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* // rollup.config.js
|
|
16
|
+
* import { sommarkRollup } from "sommark/rollup";
|
|
17
|
+
* export default { plugins: [sommarkRollup(), commonjs(), nodeResolve({ browser: true })] };
|
|
18
|
+
*/
|
|
19
|
+
export function sommarkRollup() {
|
|
20
|
+
let emitted = new Map();
|
|
21
|
+
|
|
22
|
+
return {
|
|
23
|
+
name: "sommark",
|
|
24
|
+
|
|
25
|
+
options(opts) {
|
|
26
|
+
const prev = opts.treeshake;
|
|
27
|
+
return {
|
|
28
|
+
...opts,
|
|
29
|
+
treeshake: {
|
|
30
|
+
...(prev && typeof prev === "object" ? prev : {}),
|
|
31
|
+
moduleSideEffects(id, external) {
|
|
32
|
+
// quickjs-emscripten uses side-effectful env/importObject setup that
|
|
33
|
+
// tree-shaking removes incorrectly without this guard
|
|
34
|
+
if (id.includes("quickjs") || id.includes("@jitl/")) return true;
|
|
35
|
+
const fn = prev?.moduleSideEffects;
|
|
36
|
+
return typeof fn === "function" ? fn(id, external) : (fn ?? true);
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
buildStart() {
|
|
43
|
+
emitted = new Map();
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
transform(code, id) {
|
|
47
|
+
if (!code.includes(".wasm")) return null;
|
|
48
|
+
|
|
49
|
+
const dir = nodePath.dirname(id);
|
|
50
|
+
let changed = false;
|
|
51
|
+
let result = code;
|
|
52
|
+
|
|
53
|
+
const pattern = /new URL\(['"]([^'"]+\.wasm)['"]\s*,\s*import\.meta\.url\)/g;
|
|
54
|
+
let match;
|
|
55
|
+
while ((match = pattern.exec(code)) !== null) {
|
|
56
|
+
const [full, wasmFile] = match;
|
|
57
|
+
const wasmPath = nodePath.resolve(dir, wasmFile);
|
|
58
|
+
if (!existsSync(wasmPath)) continue;
|
|
59
|
+
|
|
60
|
+
let refId;
|
|
61
|
+
if (emitted.has(wasmPath)) {
|
|
62
|
+
refId = emitted.get(wasmPath);
|
|
63
|
+
} else {
|
|
64
|
+
const source = readFileSync(wasmPath);
|
|
65
|
+
refId = this.emitFile({ type: "asset", name: nodePath.basename(wasmPath), source });
|
|
66
|
+
emitted.set(wasmPath, refId);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
result = result.replace(
|
|
70
|
+
full,
|
|
71
|
+
`new URL(import.meta.ROLLUP_FILE_URL_${refId}, import.meta.url)`
|
|
72
|
+
);
|
|
73
|
+
changed = true;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return changed ? { code: result, map: null } : null;
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
}
|
package/vite.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vite plugin for SomMark.
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* // vite.config.js
|
|
6
|
+
* import { sommarkVite } from "sommark/vite";
|
|
7
|
+
* export default defineConfig({ plugins: [sommarkVite()] });
|
|
8
|
+
*/
|
|
9
|
+
export function sommarkVite() {
|
|
10
|
+
return {
|
|
11
|
+
name: "sommark",
|
|
12
|
+
config() {
|
|
13
|
+
return {
|
|
14
|
+
optimizeDeps: {
|
|
15
|
+
exclude: ["quickjs-emscripten"],
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
}
|