nsp-server-pages 0.1.1 → 0.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/cjs/index.js +6 -1
- package/cjs/package.json +1 -0
- package/cjs/src/app.js +20 -20
- package/cjs/src/catch.js +10 -9
- package/cjs/src/loaders.js +1 -1
- package/cjs/src/mount.js +6 -6
- package/cjs/src/parser/attr.js +92 -0
- package/{src/parse-el.js → cjs/src/parser/el.js} +15 -28
- package/cjs/src/parser/jsp.js +88 -0
- package/cjs/src/parser/scriptlet.js +47 -0
- package/cjs/src/parser/tag.js +130 -0
- package/{src/parse-text.js → cjs/src/parser/text.js} +13 -25
- package/cjs/src/stack-store.js +31 -0
- package/cjs/src/taglib.js +7 -7
- package/esm/index.js +2 -0
- package/esm/package.json +4 -0
- package/{src → esm/src}/app.js +19 -19
- package/{src → esm/src}/catch.js +10 -9
- package/{src → esm/src}/mount.js +6 -6
- package/esm/src/parser/attr.js +88 -0
- package/{cjs/src/parse-el.js → esm/src/parser/el.js} +11 -32
- package/esm/src/parser/jsp.js +83 -0
- package/esm/src/parser/scriptlet.js +43 -0
- package/esm/src/parser/tag.js +126 -0
- package/{cjs/src/parse-text.js → esm/src/parser/text.js} +9 -29
- package/esm/src/stack-store.js +27 -0
- package/{src → esm/src}/taglib.js +7 -7
- package/package.json +14 -13
- package/types/hooks.d.ts +103 -0
- package/{index.d.ts → types/index.d.ts} +62 -40
- package/cjs/src/parse-attr.js +0 -91
- package/cjs/src/parse-jsp.js +0 -206
- package/cjs/src/parse-scriptlet.js +0 -71
- package/index.js +0 -1
- package/src/parse-attr.js +0 -87
- package/src/parse-jsp.js +0 -201
- package/src/parse-scriptlet.js +0 -67
- /package/{src → esm/src}/bundle.js +0 -0
- /package/{src → esm/src}/concat.js +0 -0
- /package/{src → esm/src}/loaders.js +0 -0
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { Text } from "./text.js";
|
|
2
|
+
import { Attr } from "./attr.js";
|
|
3
|
+
const emptyText = {
|
|
4
|
+
'""': true,
|
|
5
|
+
"''": true,
|
|
6
|
+
"``": true,
|
|
7
|
+
"null": true,
|
|
8
|
+
"undefined": true,
|
|
9
|
+
"": true,
|
|
10
|
+
};
|
|
11
|
+
const isTranspiler = (v) => ("function" === typeof v?.toJS);
|
|
12
|
+
/**
|
|
13
|
+
* Root node or an taglib node
|
|
14
|
+
*/
|
|
15
|
+
export class Tag {
|
|
16
|
+
constructor(app, src) {
|
|
17
|
+
this.app = app;
|
|
18
|
+
this.src = src;
|
|
19
|
+
this.children = [];
|
|
20
|
+
this.tagName = src?.match(/^<\/?([^\s=/>]+)/)?.[1];
|
|
21
|
+
}
|
|
22
|
+
append(node) {
|
|
23
|
+
this.children.push(node);
|
|
24
|
+
}
|
|
25
|
+
isOpen() {
|
|
26
|
+
return !/\/\s*>$/.test(this.src);
|
|
27
|
+
}
|
|
28
|
+
isClose() {
|
|
29
|
+
return /^<\//.test(this.src);
|
|
30
|
+
}
|
|
31
|
+
getBodyJS(option) {
|
|
32
|
+
const { app } = this;
|
|
33
|
+
const { indent, trimSpaces, vName } = app.options;
|
|
34
|
+
const spaces = +indent ? " ".repeat(+indent) : (indent ?? "");
|
|
35
|
+
const currentLF = option?.LF ?? "\n";
|
|
36
|
+
const nextLF = currentLF + spaces;
|
|
37
|
+
const { children } = this;
|
|
38
|
+
const args = children.map(item => {
|
|
39
|
+
if (isTranspiler(item)) {
|
|
40
|
+
return item.toJS({ LF: nextLF });
|
|
41
|
+
}
|
|
42
|
+
else if (!/\S/.test(item)) {
|
|
43
|
+
// item with only whitespace
|
|
44
|
+
return (trimSpaces !== false) ? '""' : JSON.stringify(item);
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
if (trimSpaces !== false) {
|
|
48
|
+
item = item.replace(/^\s*[\r\n]/s, "\n");
|
|
49
|
+
item = item.replace(/\s*[\r\n]\s*$/s, "\n");
|
|
50
|
+
item = item.replace(/^[ \t]+/s, " ");
|
|
51
|
+
item = item.replace(/[ \t]+$/s, " ");
|
|
52
|
+
}
|
|
53
|
+
let js = new Text(app, item).toJS({ LF: nextLF });
|
|
54
|
+
if (/\(.+?\)|\$\{.+?}/s.test(js)) {
|
|
55
|
+
js = `${vName} => ${js}`; // array function
|
|
56
|
+
}
|
|
57
|
+
return js;
|
|
58
|
+
}
|
|
59
|
+
}).filter(v => !emptyText[v]);
|
|
60
|
+
// empty body
|
|
61
|
+
if (!children.length) {
|
|
62
|
+
return "";
|
|
63
|
+
}
|
|
64
|
+
// keep a single empty string at least if all arguments are trimmed
|
|
65
|
+
if (!args.length) {
|
|
66
|
+
args.push('""');
|
|
67
|
+
}
|
|
68
|
+
const last = args.length - 1;
|
|
69
|
+
args.forEach((v, idx) => {
|
|
70
|
+
const isComment = /^\/\/[^\n]*$/s.test(v);
|
|
71
|
+
if (idx !== last && !isComment) {
|
|
72
|
+
args[idx] += ",";
|
|
73
|
+
}
|
|
74
|
+
else if (idx === last && isComment) {
|
|
75
|
+
args[idx] += currentLF;
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
const bodyL = /^`\n/s.test(args.at(0)) ? "" : nextLF;
|
|
79
|
+
const bodyR = /(\n`|[)\s])$/s.test(args.at(-1)) ? "" : currentLF;
|
|
80
|
+
return bodyL + args.join(nextLF) + bodyR;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Transpile JSP document to JavaScript source code
|
|
84
|
+
*/
|
|
85
|
+
toJS(option) {
|
|
86
|
+
const { app, tagName } = this;
|
|
87
|
+
const { indent, nspName } = app.options;
|
|
88
|
+
// root element
|
|
89
|
+
if (!tagName) {
|
|
90
|
+
const bodyJS = this.getBodyJS(option);
|
|
91
|
+
return `${nspName}.bundle(${bodyJS})`; // root element
|
|
92
|
+
}
|
|
93
|
+
if (this.isClose())
|
|
94
|
+
return; // invalid
|
|
95
|
+
const commentJS = this.getCommentJS(option);
|
|
96
|
+
const attr = new Attr(app, this.src);
|
|
97
|
+
const body = this.getBodyJS(option);
|
|
98
|
+
const spaces = +indent ? " ".repeat(+indent) : (indent ?? "");
|
|
99
|
+
const currentLF = option?.LF ?? "\n";
|
|
100
|
+
const nextLF = currentLF + spaces;
|
|
101
|
+
const tagOption = { LF: (body ? nextLF : currentLF) };
|
|
102
|
+
const type = `parse.tag.${tagName}`;
|
|
103
|
+
const def = { app, name: tagName, attr, body, LF: currentLF, nextLF };
|
|
104
|
+
const tagJS = app.process(type, def) ?? this.getTagJS(def, tagOption);
|
|
105
|
+
return commentJS ? commentJS + tagJS : tagJS;
|
|
106
|
+
}
|
|
107
|
+
getCommentJS(option) {
|
|
108
|
+
const { app, src } = this;
|
|
109
|
+
if (!app.options.comment)
|
|
110
|
+
return;
|
|
111
|
+
const currentLF = option?.LF ?? "\n";
|
|
112
|
+
return `// ${src?.replace(/\s*[\r\n]\s*/g, " ") ?? ""}${currentLF}`;
|
|
113
|
+
}
|
|
114
|
+
getTagJS(def, option) {
|
|
115
|
+
const { app, tagName } = this;
|
|
116
|
+
const { nspName, vName } = app.options;
|
|
117
|
+
const attrRaw = def.attr.toJS(option);
|
|
118
|
+
// transpile attributes to array function if they include variables
|
|
119
|
+
const hasVars = /\(.+?\)|\$\{.+?}/s.test(attrRaw);
|
|
120
|
+
const attrJS = hasVars ? `${vName} => (${attrRaw})` : attrRaw;
|
|
121
|
+
const nameJS = JSON.stringify(tagName);
|
|
122
|
+
const hasAttr = /:/.test(attrRaw);
|
|
123
|
+
const restJS = def.body ? (`, ${attrJS}, ${def.body}`) : (hasAttr ? `, ${attrJS}` : "");
|
|
124
|
+
return `${nspName}.tag(${nameJS}${restJS})`;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
@@ -1,8 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
exports.parseText = void 0;
|
|
4
|
-
const parse_el_js_1 = require("./parse-el.js");
|
|
5
|
-
const parse_scriptlet_js_1 = require("./parse-scriptlet.js");
|
|
1
|
+
import { EL } from "./el.js";
|
|
2
|
+
import { Scriptlet } from "./scriptlet.js";
|
|
6
3
|
/**
|
|
7
4
|
* escape special characters in Template Literal
|
|
8
5
|
*/
|
|
@@ -19,35 +16,18 @@ const bodyRegExp = new RegExp(bodyRE, "s");
|
|
|
19
16
|
/**
|
|
20
17
|
* Parser for: text content
|
|
21
18
|
*/
|
|
22
|
-
|
|
23
|
-
exports.parseText = parseText;
|
|
24
|
-
class TextParser {
|
|
19
|
+
export class Text {
|
|
25
20
|
constructor(app, src) {
|
|
26
21
|
this.app = app;
|
|
27
|
-
this.src = src;
|
|
28
|
-
//
|
|
22
|
+
this.src = app.process("before.parse.text", src) ?? src;
|
|
29
23
|
}
|
|
30
24
|
/**
|
|
31
25
|
* Transpile ${expression} and <% scriptlet %> to JavaScript source code
|
|
32
26
|
*/
|
|
33
27
|
toJS(option) {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
* Compile ${expression} and <% scriptlet %> to JavaScript function instance
|
|
38
|
-
*/
|
|
39
|
-
toFn() {
|
|
40
|
-
const { app } = this;
|
|
41
|
-
const { nspName, vName } = app.options;
|
|
42
|
-
const js = this.toJS();
|
|
43
|
-
try {
|
|
44
|
-
const fn = Function(nspName, vName, `return ${js}`);
|
|
45
|
-
return (context) => fn(app, context);
|
|
46
|
-
}
|
|
47
|
-
catch (e) {
|
|
48
|
-
app.log("TextParser: " + js?.substring(0, 1000));
|
|
49
|
-
throw e;
|
|
50
|
-
}
|
|
28
|
+
const { app, src } = this;
|
|
29
|
+
const js = app.process("parse.text", src) ?? textToJS(app, src, option);
|
|
30
|
+
return app.process("after.parse.text", js) ?? js;
|
|
51
31
|
}
|
|
52
32
|
}
|
|
53
33
|
/**
|
|
@@ -66,7 +46,7 @@ const textToJS = (app, src, option) => {
|
|
|
66
46
|
const isAsync = /^#/s.test(value);
|
|
67
47
|
value = value.replace(/^[$#]\{\s*/s, "");
|
|
68
48
|
value = value.replace(/\s*}$/s, "");
|
|
69
|
-
const item =
|
|
49
|
+
const item = new EL(app, value);
|
|
70
50
|
if (isAsync) {
|
|
71
51
|
items.push({ toJS: (option) => `await ${item.toJS(option)}` });
|
|
72
52
|
}
|
|
@@ -76,7 +56,7 @@ const textToJS = (app, src, option) => {
|
|
|
76
56
|
}
|
|
77
57
|
else if (i3 === 2) {
|
|
78
58
|
// <% scriptlet %>
|
|
79
|
-
const item =
|
|
59
|
+
const item = new Scriptlet(app, value);
|
|
80
60
|
items.push(item);
|
|
81
61
|
}
|
|
82
62
|
else {
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export class StackStore {
|
|
2
|
+
constructor(value) {
|
|
3
|
+
this.stack = [];
|
|
4
|
+
if (arguments.length) {
|
|
5
|
+
this.set(value);
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
open(value) {
|
|
9
|
+
this.stack.unshift(value);
|
|
10
|
+
}
|
|
11
|
+
close() {
|
|
12
|
+
return this.stack.shift();
|
|
13
|
+
}
|
|
14
|
+
get() {
|
|
15
|
+
return this.stack[0];
|
|
16
|
+
}
|
|
17
|
+
set(value) {
|
|
18
|
+
this.stack[0] = value;
|
|
19
|
+
}
|
|
20
|
+
find(test) {
|
|
21
|
+
for (const data of this.stack) {
|
|
22
|
+
if (test(data)) {
|
|
23
|
+
return data;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { toXML } from "to-xml";
|
|
2
|
-
export
|
|
3
|
-
const { fnMap, tagMap } =
|
|
2
|
+
export function addTagLib(tagLibDef) {
|
|
3
|
+
const { fnMap, tagMap } = this;
|
|
4
4
|
const { ns, fn, tag } = tagLibDef;
|
|
5
5
|
if (fn) {
|
|
6
6
|
for (const name in fn) {
|
|
@@ -12,14 +12,14 @@ export const addTagLib = (app, tagLibDef) => {
|
|
|
12
12
|
tagMap.set(`${ns}:${name}`, tag[name]);
|
|
13
13
|
}
|
|
14
14
|
}
|
|
15
|
-
}
|
|
16
|
-
export
|
|
17
|
-
const { tagMap } =
|
|
15
|
+
}
|
|
16
|
+
export function prepareTag(name, attr, body) {
|
|
17
|
+
const { tagMap } = this;
|
|
18
18
|
const tagFn = tagMap.get(name) || defaultTagFn;
|
|
19
19
|
const attrFn = !attr ? () => ({}) : (typeof attr !== "function") ? () => attr : attr;
|
|
20
|
-
const tagDef = { name, app, attr: attrFn, body };
|
|
20
|
+
const tagDef = { name, app: this, attr: attrFn, body };
|
|
21
21
|
return tagFn(tagDef);
|
|
22
|
-
}
|
|
22
|
+
}
|
|
23
23
|
const defaultTagFn = (tagDef) => {
|
|
24
24
|
const { name } = tagDef;
|
|
25
25
|
// tagDef.app.log(`Unknown tag: ${name}`);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nsp-server-pages",
|
|
3
3
|
"description": "NSP JavaScript Server Pages for Node.js",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.2.0",
|
|
5
5
|
"author": "@kawanet",
|
|
6
6
|
"bugs": {
|
|
7
7
|
"url": "https://github.com/kawanet/nsp-server-pages/issues"
|
|
@@ -13,25 +13,26 @@
|
|
|
13
13
|
"devDependencies": {
|
|
14
14
|
"@rollup/plugin-node-resolve": "^15.2.1",
|
|
15
15
|
"@types/mocha": "^10.0.1",
|
|
16
|
-
"@types/node": "^20.5.
|
|
16
|
+
"@types/node": "^20.5.6",
|
|
17
17
|
"mocha": "^10.2.0",
|
|
18
|
-
"typescript": "^5.
|
|
18
|
+
"typescript": "^5.2.2"
|
|
19
19
|
},
|
|
20
20
|
"exports": {
|
|
21
21
|
"import": {
|
|
22
|
-
"types": "./index.d.ts",
|
|
23
|
-
"default": "./index.js"
|
|
22
|
+
"types": "./types/index.d.ts",
|
|
23
|
+
"default": "./esm/index.js"
|
|
24
24
|
},
|
|
25
25
|
"require": "./cjs/index.js"
|
|
26
26
|
},
|
|
27
27
|
"files": [
|
|
28
|
-
"cjs
|
|
28
|
+
"cjs/*.js",
|
|
29
29
|
"cjs/package.json",
|
|
30
|
-
"cjs/src
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
"
|
|
30
|
+
"cjs/src/**/*.js",
|
|
31
|
+
"esm/*.js",
|
|
32
|
+
"esm/package.json",
|
|
33
|
+
"esm/src/**/*.js",
|
|
34
|
+
"types/*.d.ts",
|
|
35
|
+
"package.json"
|
|
35
36
|
],
|
|
36
37
|
"homepage": "https://github.com/kawanet/nsp-server-pages#readme",
|
|
37
38
|
"keywords": [
|
|
@@ -41,7 +42,7 @@
|
|
|
41
42
|
"taglib"
|
|
42
43
|
],
|
|
43
44
|
"license": "Apache-2.0",
|
|
44
|
-
"main": "index.js",
|
|
45
|
+
"main": "./esm/index.js",
|
|
45
46
|
"repository": {
|
|
46
47
|
"type": "git",
|
|
47
48
|
"url": "git+https://github.com/kawanet/nsp-server-pages.git"
|
|
@@ -54,5 +55,5 @@
|
|
|
54
55
|
},
|
|
55
56
|
"sideEffects": false,
|
|
56
57
|
"type": "module",
|
|
57
|
-
"types": "./index.d.ts"
|
|
58
|
+
"types": "./types/index.d.ts"
|
|
58
59
|
}
|
package/types/hooks.d.ts
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import type {NSP} from "./index";
|
|
2
|
+
|
|
3
|
+
type RuntimeErrorHookType = "error";
|
|
4
|
+
|
|
5
|
+
type RuntimeScriptletHookTypes =
|
|
6
|
+
"directive"
|
|
7
|
+
| "declaration"
|
|
8
|
+
| "expression"
|
|
9
|
+
| "scriptlet";
|
|
10
|
+
|
|
11
|
+
type BeforeParseHookTypes =
|
|
12
|
+
"before.parse.attr"
|
|
13
|
+
| "before.parse.comment"
|
|
14
|
+
| "before.parse.declaration"
|
|
15
|
+
| "before.parse.directive"
|
|
16
|
+
| "before.parse.el"
|
|
17
|
+
| "before.parse.expression"
|
|
18
|
+
| "before.parse.jsp"
|
|
19
|
+
| "before.parse.text";
|
|
20
|
+
|
|
21
|
+
type ParseHookTypes =
|
|
22
|
+
"parse.attr"
|
|
23
|
+
| "parse.comment"
|
|
24
|
+
| "parse.declaration"
|
|
25
|
+
| "parse.directive"
|
|
26
|
+
| "parse.el"
|
|
27
|
+
| "parse.expression"
|
|
28
|
+
| "parse.jsp"
|
|
29
|
+
| "parse.text";
|
|
30
|
+
|
|
31
|
+
type AfterParseHookTypes =
|
|
32
|
+
"after.parse.attr"
|
|
33
|
+
| "after.parse.comment"
|
|
34
|
+
| "after.parse.declaration"
|
|
35
|
+
| "after.parse.directive"
|
|
36
|
+
| "after.parse.el"
|
|
37
|
+
| "after.parse.expression"
|
|
38
|
+
| "after.parse.jsp"
|
|
39
|
+
| "after.parse.text";
|
|
40
|
+
|
|
41
|
+
type ParseTagHookTypes = `parse.tag.${string}`;
|
|
42
|
+
|
|
43
|
+
type KnownHookTypes =
|
|
44
|
+
RuntimeErrorHookType
|
|
45
|
+
| RuntimeScriptletHookTypes
|
|
46
|
+
| BeforeParseHookTypes
|
|
47
|
+
| ParseHookTypes
|
|
48
|
+
| AfterParseHookTypes
|
|
49
|
+
| ParseTagHookTypes;
|
|
50
|
+
|
|
51
|
+
export interface Hooks {
|
|
52
|
+
/**
|
|
53
|
+
* ==== RUNTIME HOOKS ====
|
|
54
|
+
*
|
|
55
|
+
* hook called when an Error thrown.
|
|
56
|
+
* return a string to output the error message and cancel the exception.
|
|
57
|
+
* return undefined to stop the page with the exception.
|
|
58
|
+
*/
|
|
59
|
+
hook(type: RuntimeErrorHookType, fn: (e: Error, context: any) => string | void): void;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* hooks called with JSP directive, declaration, scriptlet on runtime.
|
|
63
|
+
*/
|
|
64
|
+
hook(type: RuntimeScriptletHookTypes, fn: (src: string, context: any) => string | void): void;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* ==== TRANSPILER HOOKS ====
|
|
68
|
+
*
|
|
69
|
+
* hooks called with input JSP document before transpiling started.
|
|
70
|
+
* return a string to modify the input.
|
|
71
|
+
* return undefined not to modify the input.
|
|
72
|
+
*/
|
|
73
|
+
hook(type: BeforeParseHookTypes, fn: (src: string) => string | void): void;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* hooks called with JSP document to replace our default transpiler.
|
|
77
|
+
* return a string if you have own transpiler for the type.
|
|
78
|
+
* return undefined for our default transpiler to work.
|
|
79
|
+
*/
|
|
80
|
+
hook(type: ParseHookTypes, fn: (src: string) => string | void): void;
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* hooks called with output JavaScript code after transpiling done.
|
|
84
|
+
* return a string to modify the output.
|
|
85
|
+
* return undefined not to modify the output.
|
|
86
|
+
*/
|
|
87
|
+
hook(type: AfterParseHookTypes, fn: (src: string) => string | void): void;
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* hooks called with TagParserDef to transpile tag implementation inline.
|
|
91
|
+
*
|
|
92
|
+
* @example
|
|
93
|
+
* nsp.hook("parse.tag.c:set", tag => {
|
|
94
|
+
* return `v => { v[${tag.attr.get("var")}] = ${tag.attr.get("value")} }`;
|
|
95
|
+
* });
|
|
96
|
+
*/
|
|
97
|
+
hook<A>(type: ParseTagHookTypes, fn: (tag: NSP.TagParserDef<A>) => string | void): void;
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* ==== OTHER HOOKS ====
|
|
101
|
+
*/
|
|
102
|
+
hook(type: Exclude<string, KnownHookTypes>, fn: (...args: any[]) => any): void;
|
|
103
|
+
}
|
|
@@ -4,9 +4,11 @@
|
|
|
4
4
|
* @see https://github.com/kawanet/nsp-server-pages
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
import type {Hooks} from "./hooks.js";
|
|
8
|
+
|
|
7
9
|
export const createNSP: (options?: NSP.Options) => NSP.App;
|
|
8
10
|
|
|
9
|
-
declare namespace NSP {
|
|
11
|
+
export declare namespace NSP {
|
|
10
12
|
type NodeFn<T> = (context: T) => string | Promise<string>;
|
|
11
13
|
|
|
12
14
|
type Node<T> = string | NodeFn<T>;
|
|
@@ -52,10 +54,10 @@ declare namespace NSP {
|
|
|
52
54
|
storeKey?: string;
|
|
53
55
|
|
|
54
56
|
/**
|
|
55
|
-
* indent
|
|
56
|
-
* @default 0
|
|
57
|
+
* indent spaces for JavaScript source generated
|
|
58
|
+
* @default 0 (no indent)
|
|
57
59
|
*/
|
|
58
|
-
indent?: number;
|
|
60
|
+
indent?: number | string;
|
|
59
61
|
|
|
60
62
|
/**
|
|
61
63
|
* add comments at toJS() result
|
|
@@ -74,23 +76,10 @@ declare namespace NSP {
|
|
|
74
76
|
* @default false
|
|
75
77
|
*/
|
|
76
78
|
nullish?: boolean;
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* expression filter before transpile starts
|
|
80
|
-
*/
|
|
81
|
-
prefilter?: (src: string) => string;
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* expression filter after transpile done
|
|
85
|
-
*/
|
|
86
|
-
postfilter?: (src: string) => string;
|
|
87
79
|
}
|
|
88
80
|
|
|
89
|
-
interface App {
|
|
90
|
-
fnMap: Map<string, (...args: any[]) => any>;
|
|
91
|
-
loaders: LoaderFn[];
|
|
81
|
+
interface App extends Hooks {
|
|
92
82
|
options: Options;
|
|
93
|
-
tagMap: Map<string, TagFn<any>>;
|
|
94
83
|
|
|
95
84
|
/**
|
|
96
85
|
* register a tag library
|
|
@@ -110,13 +99,7 @@ declare namespace NSP {
|
|
|
110
99
|
/**
|
|
111
100
|
* retrieve a result from hook function
|
|
112
101
|
*/
|
|
113
|
-
process<
|
|
114
|
-
|
|
115
|
-
process<T>(type: "directive", src: string, context: T): string;
|
|
116
|
-
|
|
117
|
-
process<T>(type: "declaration", src: string, context: T): string;
|
|
118
|
-
|
|
119
|
-
process<T>(type: "scriptlet", src: string, context: T): string;
|
|
102
|
+
process<R>(type: string, ...args: any[]): R;
|
|
120
103
|
|
|
121
104
|
/**
|
|
122
105
|
* pickup the taglib function
|
|
@@ -148,25 +131,20 @@ declare namespace NSP {
|
|
|
148
131
|
mount(path: RegExp | string, fn: LoaderFn): void;
|
|
149
132
|
|
|
150
133
|
/**
|
|
151
|
-
* register a hook function
|
|
134
|
+
* register a hook function. see hooks.d.ts for detail
|
|
152
135
|
*/
|
|
153
|
-
hook(type: "error", fn: <T>(e: Error, context?: T) => string | void): void;
|
|
154
|
-
|
|
155
|
-
hook(type: "directive", fn: <T>(src: string, context?: T) => string | void): void;
|
|
156
136
|
|
|
157
|
-
hook(type:
|
|
158
|
-
|
|
159
|
-
hook(type: "scriptlet", fn: <T>(src: string, context?: T) => string | void): void;
|
|
137
|
+
// hook(type: string, fn: (...args: any[]) => any): void;
|
|
160
138
|
|
|
161
139
|
/**
|
|
162
140
|
* parse a JSP document
|
|
163
141
|
*/
|
|
164
|
-
parse(src: string):
|
|
142
|
+
parse(src: string): JspParser;
|
|
165
143
|
|
|
166
144
|
/**
|
|
167
145
|
* get a private data store in context
|
|
168
146
|
*/
|
|
169
|
-
store<
|
|
147
|
+
store<P>(context: any, key: string): StackStore<P>;
|
|
170
148
|
|
|
171
149
|
/**
|
|
172
150
|
* generates a NodeFn for the tag
|
|
@@ -191,22 +169,66 @@ declare namespace NSP {
|
|
|
191
169
|
tag?: { [name: string]: TagFn<any> };
|
|
192
170
|
}
|
|
193
171
|
|
|
172
|
+
interface StackStore<P> {
|
|
173
|
+
/**
|
|
174
|
+
* set value to the store
|
|
175
|
+
*/
|
|
176
|
+
set(value: P): void;
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* get value from the store
|
|
180
|
+
*/
|
|
181
|
+
get(): P;
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* open a new layer
|
|
185
|
+
*/
|
|
186
|
+
open(value?: P): void;
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* close the current layer
|
|
190
|
+
*/
|
|
191
|
+
close(): P;
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* find a value from layers with the test function
|
|
195
|
+
*/
|
|
196
|
+
find(test: (data: P) => boolean): P;
|
|
197
|
+
}
|
|
198
|
+
|
|
194
199
|
interface ToJSOption {
|
|
195
|
-
|
|
200
|
+
LF?: string;
|
|
196
201
|
}
|
|
197
202
|
|
|
198
|
-
|
|
199
|
-
* Parser for JSP document
|
|
200
|
-
*/
|
|
201
|
-
interface Parser {
|
|
203
|
+
interface Transpiler {
|
|
202
204
|
/**
|
|
203
|
-
* transpile
|
|
205
|
+
* transpile to JavaScript source code
|
|
204
206
|
*/
|
|
205
207
|
toJS(option?: ToJSOption): string;
|
|
208
|
+
}
|
|
206
209
|
|
|
210
|
+
/**
|
|
211
|
+
* Parser for JSP document
|
|
212
|
+
*/
|
|
213
|
+
interface JspParser extends Transpiler {
|
|
207
214
|
/**
|
|
208
215
|
* compile the JSP document as a NodeFn
|
|
209
216
|
*/
|
|
210
217
|
toFn<T>(): NodeFn<T>;
|
|
211
218
|
}
|
|
219
|
+
|
|
220
|
+
interface TagParserDef<A> {
|
|
221
|
+
app: App;
|
|
222
|
+
name: string;
|
|
223
|
+
attr: AttrParser<A>;
|
|
224
|
+
body: string;
|
|
225
|
+
LF: string;
|
|
226
|
+
nextLF: string;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
interface AttrParser<A> extends Transpiler {
|
|
230
|
+
keys(): (keyof A)[];
|
|
231
|
+
|
|
232
|
+
get(key: keyof A): string; // JavaScript
|
|
233
|
+
}
|
|
212
234
|
}
|
package/cjs/src/parse-attr.js
DELETED
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.parseAttr = void 0;
|
|
4
|
-
const parse_text_js_1 = require("./parse-text.js");
|
|
5
|
-
/**
|
|
6
|
-
* Parser for HTML tag attributes <tagName attr="value"/>
|
|
7
|
-
*/
|
|
8
|
-
const parseAttr = (app, src) => new AttrParser(app, src);
|
|
9
|
-
exports.parseAttr = parseAttr;
|
|
10
|
-
class AttrParser {
|
|
11
|
-
constructor(app, src) {
|
|
12
|
-
this.app = app;
|
|
13
|
-
this.src = src;
|
|
14
|
-
//
|
|
15
|
-
}
|
|
16
|
-
/**
|
|
17
|
-
* Transpile HTML tag attributes to JavaScript source code
|
|
18
|
-
*/
|
|
19
|
-
toJS(option) {
|
|
20
|
-
return attrToJS(this.app, this.src, option);
|
|
21
|
-
}
|
|
22
|
-
/**
|
|
23
|
-
* Compile HTML tag attributes to JavaScript function instance
|
|
24
|
-
*/
|
|
25
|
-
toFn() {
|
|
26
|
-
const { app } = this;
|
|
27
|
-
const { nspName, vName } = app.options;
|
|
28
|
-
const js = this.toJS();
|
|
29
|
-
try {
|
|
30
|
-
const fn = Function(nspName, vName, `return ${js}`);
|
|
31
|
-
return (context) => fn(app, context);
|
|
32
|
-
}
|
|
33
|
-
catch (e) {
|
|
34
|
-
app.log("AttrParser: " + js.substring(0, 1000));
|
|
35
|
-
throw e;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
/**
|
|
40
|
-
* Transpile HTML tag attributes to JavaScript source code
|
|
41
|
-
*/
|
|
42
|
-
const attrToJS = (app, tag, option) => {
|
|
43
|
-
tag = tag?.replace(/^\s*<\S+\s*/s, "");
|
|
44
|
-
tag = tag?.replace(/\s*\/?>\s*$/s, "");
|
|
45
|
-
const indent = +app.options.indent || 0;
|
|
46
|
-
const currentIndent = +option?.indent || 0;
|
|
47
|
-
const nextIndent = currentIndent + indent;
|
|
48
|
-
const currentLF = currentIndent ? "\n" + " ".repeat(currentIndent) : "\n";
|
|
49
|
-
const nextLF = nextIndent ? "\n" + " ".repeat(nextIndent) : "\n";
|
|
50
|
-
const keys = [];
|
|
51
|
-
const index = {};
|
|
52
|
-
tag?.replace(/([^\s='"]+)(\s*=(?:\s*"([^"]*)"|\s*'([^']*)'|([^\s='"]*)))?/g, (_, key, eq, v1, v2, v3) => {
|
|
53
|
-
if (!index[key])
|
|
54
|
-
keys.push(key);
|
|
55
|
-
index[key] = (eq ? unescapeXML(v1 || v2 || v3 || "") : true);
|
|
56
|
-
return "";
|
|
57
|
-
});
|
|
58
|
-
const items = keys.map(key => {
|
|
59
|
-
let value = index[key];
|
|
60
|
-
if (!/^[A-Za-z_]\w+$/.test(key)) {
|
|
61
|
-
key = JSON.stringify(key);
|
|
62
|
-
}
|
|
63
|
-
if ("string" === typeof value) {
|
|
64
|
-
value = (0, parse_text_js_1.parseText)(app, value).toJS({ indent: nextIndent });
|
|
65
|
-
}
|
|
66
|
-
return `${key}: ${value}`;
|
|
67
|
-
});
|
|
68
|
-
// no arguments
|
|
69
|
-
if (!keys.length)
|
|
70
|
-
return 'null';
|
|
71
|
-
const js = items.join(`,${nextLF}`);
|
|
72
|
-
const trailingComma = (keys.length > 1) ? "," : "";
|
|
73
|
-
return `{${nextLF}${js}${trailingComma}${currentLF}}`;
|
|
74
|
-
};
|
|
75
|
-
const UNESCAPE = {
|
|
76
|
-
"&": "&",
|
|
77
|
-
"<": "<",
|
|
78
|
-
">": ">",
|
|
79
|
-
"'": "'",
|
|
80
|
-
""": '"'
|
|
81
|
-
};
|
|
82
|
-
const unescapeXML = (str) => {
|
|
83
|
-
return str?.replace(/(&(?:lt|gt|amp|apos|quot|#(?:\d{1,6}|x[0-9a-fA-F]{1,5}));)/g, (str) => {
|
|
84
|
-
if (str[1] === "#") {
|
|
85
|
-
const code = (str[2] === "x") ? parseInt(str.substring(3), 16) : parseInt(str.substr(2), 10);
|
|
86
|
-
if (code > -1)
|
|
87
|
-
return String.fromCharCode(code);
|
|
88
|
-
}
|
|
89
|
-
return UNESCAPE[str] || str;
|
|
90
|
-
});
|
|
91
|
-
};
|