node-html-parser 1.4.5 → 1.4.9
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/README.md +15 -1
- package/dist/esm/matcher.js +5 -15
- package/dist/esm/nodes/html.js +49 -7
- package/dist/main.js +63 -22
- package/dist/matcher.js +5 -15
- package/dist/nodes/html.d.ts +6 -0
- package/dist/nodes/html.js +58 -7
- package/package.json +6 -3
package/README.md
CHANGED
|
@@ -62,7 +62,7 @@ var HTMLParser = require('node-html-parser');
|
|
|
62
62
|
var root = HTMLParser.parse('<ul id="list"><li>Hello World</li></ul>');
|
|
63
63
|
```
|
|
64
64
|
|
|
65
|
-
##
|
|
65
|
+
## Global Methods
|
|
66
66
|
|
|
67
67
|
### parse(data[, options])
|
|
68
68
|
|
|
@@ -84,6 +84,8 @@ Parse given data, and return root of the generated DOM.
|
|
|
84
84
|
}
|
|
85
85
|
```
|
|
86
86
|
|
|
87
|
+
## HTMLElement Methods
|
|
88
|
+
|
|
87
89
|
### HTMLElement#trimRight()
|
|
88
90
|
|
|
89
91
|
Trim element from right (in block) after seeing pattern in a TextNode.
|
|
@@ -140,6 +142,10 @@ Same as [outerHTML](#htmlelementouterhtml)
|
|
|
140
142
|
|
|
141
143
|
Set content. **Notice**: Do not set content of the **root** node.
|
|
142
144
|
|
|
145
|
+
### HTMLElement#remove()
|
|
146
|
+
|
|
147
|
+
Remove current element.
|
|
148
|
+
|
|
143
149
|
## HTMLElement Properties
|
|
144
150
|
|
|
145
151
|
### HTMLElement#text
|
|
@@ -175,3 +181,11 @@ Get innerHTML.
|
|
|
175
181
|
### HTMLElement#outerHTML
|
|
176
182
|
|
|
177
183
|
Get outerHTML.
|
|
184
|
+
|
|
185
|
+
### HTMLElement#nextSibling
|
|
186
|
+
|
|
187
|
+
Returns a reference to the next child node of the current element's parent.
|
|
188
|
+
|
|
189
|
+
### HTMLElement#nextElementSibling
|
|
190
|
+
|
|
191
|
+
Returns a reference to the next child element of the current element's parent.
|
package/dist/esm/matcher.js
CHANGED
|
@@ -161,31 +161,21 @@ export default class Matcher {
|
|
|
161
161
|
let attr_key = '';
|
|
162
162
|
let value = '';
|
|
163
163
|
if (tagName && tagName !== '*') {
|
|
164
|
-
let reg;
|
|
165
164
|
if (tagName.startsWith('#')) {
|
|
166
165
|
// source += 'if (el.id != ' + JSON.stringify(tagName.substr(1)) + ') return false;';// 1
|
|
167
166
|
function_name += '1';
|
|
168
167
|
}
|
|
169
168
|
else {
|
|
170
|
-
|
|
169
|
+
// https://github.com/taoqf/node-html-parser/issues/86
|
|
170
|
+
// const reg = /\[\s*([\w-]+)(\s*=\s*(((?<quote>'|")\s*(.*)(\k<quote>))|(\S*)))?\s*\]/.exec(tagName);
|
|
171
|
+
// `[a-b]`,`[ a-b ]`,`[a-b=c]`, `[a-b=c'd]`,`[a-b='c\' d"e ']`,`[ a-b = 'c\' d"e ' ]`,`[a-b="c' d\"e " ]`,`[ a-b = "c' d\"e " ]`
|
|
172
|
+
const reg = /\[\s*([\w-]+)(\s*=\s*(('\s*(.*)'|"\s*(.*)")|(\S*)))?\s*\]/.exec(tagName);
|
|
171
173
|
if (reg) {
|
|
172
174
|
attr_key = reg[1];
|
|
173
|
-
|
|
174
|
-
if (method !== '=' && method !== '!=') {
|
|
175
|
-
// eslint-disable-next-line no-template-curly-in-string
|
|
176
|
-
throw new Error('Selector not supported, Expect [key${op}value].op must be =,!=');
|
|
177
|
-
}
|
|
178
|
-
if (method === '=') {
|
|
179
|
-
method = '==';
|
|
180
|
-
}
|
|
181
|
-
value = reg[7] || reg[8];
|
|
175
|
+
value = reg[5] || reg[6] || reg[7];
|
|
182
176
|
// source += `let attrs = el.attributes;for (let key in attrs){const val = attrs[key]; if (key == "${attr_key}" && val == "${value}"){return true;}} return false;`;// 2
|
|
183
177
|
function_name += '2';
|
|
184
178
|
}
|
|
185
|
-
else if ((reg = /^\[(.*?)\]/.exec(tagName))) {
|
|
186
|
-
attr_key = reg[1];
|
|
187
|
-
function_name += '5';
|
|
188
|
-
}
|
|
189
179
|
else {
|
|
190
180
|
// source += 'if (el.tagName != ' + JSON.stringify(tagName) + ') return false;';// 3
|
|
191
181
|
function_name += '3';
|
package/dist/esm/nodes/html.js
CHANGED
|
@@ -71,6 +71,17 @@ export default class HTMLElement extends Node {
|
|
|
71
71
|
}
|
|
72
72
|
}
|
|
73
73
|
}
|
|
74
|
+
/**
|
|
75
|
+
* Remove current element
|
|
76
|
+
*/
|
|
77
|
+
remove() {
|
|
78
|
+
if (this.parentNode) {
|
|
79
|
+
const children = this.parentNode.childNodes;
|
|
80
|
+
this.parentNode.childNodes = children.filter((child) => {
|
|
81
|
+
return this !== child;
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
}
|
|
74
85
|
/**
|
|
75
86
|
* Remove Child element from childNodes array
|
|
76
87
|
* @param {HTMLElement} node node to remove
|
|
@@ -86,14 +97,13 @@ export default class HTMLElement extends Node {
|
|
|
86
97
|
* @param {HTMLElement} newNode new node
|
|
87
98
|
*/
|
|
88
99
|
exchangeChild(oldNode, newNode) {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
if (
|
|
92
|
-
|
|
93
|
-
break;
|
|
100
|
+
const children = this.childNodes;
|
|
101
|
+
this.childNodes = children.map((child) => {
|
|
102
|
+
if (child === oldNode) {
|
|
103
|
+
return newNode;
|
|
94
104
|
}
|
|
95
|
-
|
|
96
|
-
|
|
105
|
+
return child;
|
|
106
|
+
});
|
|
97
107
|
}
|
|
98
108
|
get tagName() {
|
|
99
109
|
return this.rawTagName ? this.rawTagName.toUpperCase() : this.rawTagName;
|
|
@@ -545,6 +555,38 @@ export default class HTMLElement extends Node {
|
|
|
545
555
|
// return;
|
|
546
556
|
// }
|
|
547
557
|
}
|
|
558
|
+
get nextSibling() {
|
|
559
|
+
if (this.parentNode) {
|
|
560
|
+
const children = this.parentNode.childNodes;
|
|
561
|
+
let i = 0;
|
|
562
|
+
while (i < children.length) {
|
|
563
|
+
const child = children[i++];
|
|
564
|
+
if (this === child) {
|
|
565
|
+
return children[i] || null;
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
return null;
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
get nextElementSibling() {
|
|
572
|
+
if (this.parentNode) {
|
|
573
|
+
const children = this.parentNode.childNodes;
|
|
574
|
+
let i = 0;
|
|
575
|
+
let find = false;
|
|
576
|
+
while (i < children.length) {
|
|
577
|
+
const child = children[i++];
|
|
578
|
+
if (find) {
|
|
579
|
+
if (child instanceof HTMLElement) {
|
|
580
|
+
return child || null;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
else if (this === child) {
|
|
584
|
+
find = true;
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
return null;
|
|
588
|
+
}
|
|
589
|
+
}
|
|
548
590
|
}
|
|
549
591
|
// https://html.spec.whatwg.org/multipage/custom-elements.html#valid-custom-element-name
|
|
550
592
|
const kMarkupPattern = /<!--[^]*?(?=-->)-->|<(\/?)([a-z][-.:0-9_a-z]*)\s*([^>]*?)(\/?)>/ig;
|
package/dist/main.js
CHANGED
|
@@ -312,31 +312,21 @@ define("matcher", ["require", "exports"], function (require, exports) {
|
|
|
312
312
|
var attr_key = '';
|
|
313
313
|
var value = '';
|
|
314
314
|
if (tagName && tagName !== '*') {
|
|
315
|
-
var reg = void 0;
|
|
316
315
|
if (tagName.startsWith('#')) {
|
|
317
316
|
// source += 'if (el.id != ' + JSON.stringify(tagName.substr(1)) + ') return false;';// 1
|
|
318
317
|
function_name += '1';
|
|
319
318
|
}
|
|
320
319
|
else {
|
|
321
|
-
|
|
320
|
+
// https://github.com/taoqf/node-html-parser/issues/86
|
|
321
|
+
// const reg = /\[\s*([\w-]+)(\s*=\s*(((?<quote>'|")\s*(.*)(\k<quote>))|(\S*)))?\s*\]/.exec(tagName);
|
|
322
|
+
// `[a-b]`,`[ a-b ]`,`[a-b=c]`, `[a-b=c'd]`,`[a-b='c\' d"e ']`,`[ a-b = 'c\' d"e ' ]`,`[a-b="c' d\"e " ]`,`[ a-b = "c' d\"e " ]`
|
|
323
|
+
var reg = /\[\s*([\w-]+)(\s*=\s*(('\s*(.*)'|"\s*(.*)")|(\S*)))?\s*\]/.exec(tagName);
|
|
322
324
|
if (reg) {
|
|
323
325
|
attr_key = reg[1];
|
|
324
|
-
|
|
325
|
-
if (method !== '=' && method !== '!=') {
|
|
326
|
-
// eslint-disable-next-line no-template-curly-in-string
|
|
327
|
-
throw new Error('Selector not supported, Expect [key${op}value].op must be =,!=');
|
|
328
|
-
}
|
|
329
|
-
if (method === '=') {
|
|
330
|
-
method = '==';
|
|
331
|
-
}
|
|
332
|
-
value = reg[7] || reg[8];
|
|
326
|
+
value = reg[5] || reg[6] || reg[7];
|
|
333
327
|
// source += `let attrs = el.attributes;for (let key in attrs){const val = attrs[key]; if (key == "${attr_key}" && val == "${value}"){return true;}} return false;`;// 2
|
|
334
328
|
function_name += '2';
|
|
335
329
|
}
|
|
336
|
-
else if ((reg = /^\[(.*?)\]/.exec(tagName))) {
|
|
337
|
-
attr_key = reg[1];
|
|
338
|
-
function_name += '5';
|
|
339
|
-
}
|
|
340
330
|
else {
|
|
341
331
|
// source += 'if (el.tagName != ' + JSON.stringify(tagName) + ') return false;';// 3
|
|
342
332
|
function_name += '3';
|
|
@@ -487,6 +477,18 @@ define("nodes/html", ["require", "exports", "he", "nodes/node", "nodes/type", "n
|
|
|
487
477
|
}
|
|
488
478
|
return _this;
|
|
489
479
|
}
|
|
480
|
+
/**
|
|
481
|
+
* Remove current element
|
|
482
|
+
*/
|
|
483
|
+
HTMLElement.prototype.remove = function () {
|
|
484
|
+
var _this = this;
|
|
485
|
+
if (this.parentNode) {
|
|
486
|
+
var children = this.parentNode.childNodes;
|
|
487
|
+
this.parentNode.childNodes = children.filter(function (child) {
|
|
488
|
+
return _this !== child;
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
};
|
|
490
492
|
/**
|
|
491
493
|
* Remove Child element from childNodes array
|
|
492
494
|
* @param {HTMLElement} node node to remove
|
|
@@ -502,14 +504,13 @@ define("nodes/html", ["require", "exports", "he", "nodes/node", "nodes/type", "n
|
|
|
502
504
|
* @param {HTMLElement} newNode new node
|
|
503
505
|
*/
|
|
504
506
|
HTMLElement.prototype.exchangeChild = function (oldNode, newNode) {
|
|
505
|
-
var
|
|
506
|
-
|
|
507
|
-
if (
|
|
508
|
-
|
|
509
|
-
break;
|
|
507
|
+
var children = this.childNodes;
|
|
508
|
+
this.childNodes = children.map(function (child) {
|
|
509
|
+
if (child === oldNode) {
|
|
510
|
+
return newNode;
|
|
510
511
|
}
|
|
511
|
-
|
|
512
|
-
|
|
512
|
+
return child;
|
|
513
|
+
});
|
|
513
514
|
};
|
|
514
515
|
Object.defineProperty(HTMLElement.prototype, "tagName", {
|
|
515
516
|
get: function () {
|
|
@@ -1011,6 +1012,46 @@ define("nodes/html", ["require", "exports", "he", "nodes/node", "nodes/type", "n
|
|
|
1011
1012
|
// return;
|
|
1012
1013
|
// }
|
|
1013
1014
|
};
|
|
1015
|
+
Object.defineProperty(HTMLElement.prototype, "nextSibling", {
|
|
1016
|
+
get: function () {
|
|
1017
|
+
if (this.parentNode) {
|
|
1018
|
+
var children = this.parentNode.childNodes;
|
|
1019
|
+
var i = 0;
|
|
1020
|
+
while (i < children.length) {
|
|
1021
|
+
var child = children[i++];
|
|
1022
|
+
if (this === child) {
|
|
1023
|
+
return children[i] || null;
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
return null;
|
|
1027
|
+
}
|
|
1028
|
+
},
|
|
1029
|
+
enumerable: false,
|
|
1030
|
+
configurable: true
|
|
1031
|
+
});
|
|
1032
|
+
Object.defineProperty(HTMLElement.prototype, "nextElementSibling", {
|
|
1033
|
+
get: function () {
|
|
1034
|
+
if (this.parentNode) {
|
|
1035
|
+
var children = this.parentNode.childNodes;
|
|
1036
|
+
var i = 0;
|
|
1037
|
+
var find = false;
|
|
1038
|
+
while (i < children.length) {
|
|
1039
|
+
var child = children[i++];
|
|
1040
|
+
if (find) {
|
|
1041
|
+
if (child instanceof HTMLElement) {
|
|
1042
|
+
return child || null;
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
else if (this === child) {
|
|
1046
|
+
find = true;
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
return null;
|
|
1050
|
+
}
|
|
1051
|
+
},
|
|
1052
|
+
enumerable: false,
|
|
1053
|
+
configurable: true
|
|
1054
|
+
});
|
|
1014
1055
|
return HTMLElement;
|
|
1015
1056
|
}(node_3.default));
|
|
1016
1057
|
exports.default = HTMLElement;
|
package/dist/matcher.js
CHANGED
|
@@ -163,31 +163,21 @@ var Matcher = /** @class */ (function () {
|
|
|
163
163
|
var attr_key = '';
|
|
164
164
|
var value = '';
|
|
165
165
|
if (tagName && tagName !== '*') {
|
|
166
|
-
var reg = void 0;
|
|
167
166
|
if (tagName.startsWith('#')) {
|
|
168
167
|
// source += 'if (el.id != ' + JSON.stringify(tagName.substr(1)) + ') return false;';// 1
|
|
169
168
|
function_name += '1';
|
|
170
169
|
}
|
|
171
170
|
else {
|
|
172
|
-
|
|
171
|
+
// https://github.com/taoqf/node-html-parser/issues/86
|
|
172
|
+
// const reg = /\[\s*([\w-]+)(\s*=\s*(((?<quote>'|")\s*(.*)(\k<quote>))|(\S*)))?\s*\]/.exec(tagName);
|
|
173
|
+
// `[a-b]`,`[ a-b ]`,`[a-b=c]`, `[a-b=c'd]`,`[a-b='c\' d"e ']`,`[ a-b = 'c\' d"e ' ]`,`[a-b="c' d\"e " ]`,`[ a-b = "c' d\"e " ]`
|
|
174
|
+
var reg = /\[\s*([\w-]+)(\s*=\s*(('\s*(.*)'|"\s*(.*)")|(\S*)))?\s*\]/.exec(tagName);
|
|
173
175
|
if (reg) {
|
|
174
176
|
attr_key = reg[1];
|
|
175
|
-
|
|
176
|
-
if (method !== '=' && method !== '!=') {
|
|
177
|
-
// eslint-disable-next-line no-template-curly-in-string
|
|
178
|
-
throw new Error('Selector not supported, Expect [key${op}value].op must be =,!=');
|
|
179
|
-
}
|
|
180
|
-
if (method === '=') {
|
|
181
|
-
method = '==';
|
|
182
|
-
}
|
|
183
|
-
value = reg[7] || reg[8];
|
|
177
|
+
value = reg[5] || reg[6] || reg[7];
|
|
184
178
|
// source += `let attrs = el.attributes;for (let key in attrs){const val = attrs[key]; if (key == "${attr_key}" && val == "${value}"){return true;}} return false;`;// 2
|
|
185
179
|
function_name += '2';
|
|
186
180
|
}
|
|
187
|
-
else if ((reg = /^\[(.*?)\]/.exec(tagName))) {
|
|
188
|
-
attr_key = reg[1];
|
|
189
|
-
function_name += '5';
|
|
190
|
-
}
|
|
191
181
|
else {
|
|
192
182
|
// source += 'if (el.tagName != ' + JSON.stringify(tagName) + ') return false;';// 3
|
|
193
183
|
function_name += '3';
|
package/dist/nodes/html.d.ts
CHANGED
|
@@ -42,6 +42,10 @@ export default class HTMLElement extends Node {
|
|
|
42
42
|
* @memberof HTMLElement
|
|
43
43
|
*/
|
|
44
44
|
constructor(tagName: string, keyAttrs: KeyAttributes, rawAttrs?: string, parentNode?: Node);
|
|
45
|
+
/**
|
|
46
|
+
* Remove current element
|
|
47
|
+
*/
|
|
48
|
+
remove(): void;
|
|
45
49
|
/**
|
|
46
50
|
* Remove Child element from childNodes array
|
|
47
51
|
* @param {HTMLElement} node node to remove
|
|
@@ -148,6 +152,8 @@ export default class HTMLElement extends Node {
|
|
|
148
152
|
*/
|
|
149
153
|
setAttributes(attributes: Attributes): void;
|
|
150
154
|
insertAdjacentHTML(where: InsertPosition, html: string): void;
|
|
155
|
+
get nextSibling(): Node;
|
|
156
|
+
get nextElementSibling(): HTMLElement;
|
|
151
157
|
}
|
|
152
158
|
export interface Options {
|
|
153
159
|
lowerCaseTagName: boolean;
|
package/dist/nodes/html.js
CHANGED
|
@@ -101,6 +101,18 @@ var HTMLElement = /** @class */ (function (_super) {
|
|
|
101
101
|
}
|
|
102
102
|
return _this;
|
|
103
103
|
}
|
|
104
|
+
/**
|
|
105
|
+
* Remove current element
|
|
106
|
+
*/
|
|
107
|
+
HTMLElement.prototype.remove = function () {
|
|
108
|
+
var _this = this;
|
|
109
|
+
if (this.parentNode) {
|
|
110
|
+
var children = this.parentNode.childNodes;
|
|
111
|
+
this.parentNode.childNodes = children.filter(function (child) {
|
|
112
|
+
return _this !== child;
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
};
|
|
104
116
|
/**
|
|
105
117
|
* Remove Child element from childNodes array
|
|
106
118
|
* @param {HTMLElement} node node to remove
|
|
@@ -116,14 +128,13 @@ var HTMLElement = /** @class */ (function (_super) {
|
|
|
116
128
|
* @param {HTMLElement} newNode new node
|
|
117
129
|
*/
|
|
118
130
|
HTMLElement.prototype.exchangeChild = function (oldNode, newNode) {
|
|
119
|
-
var
|
|
120
|
-
|
|
121
|
-
if (
|
|
122
|
-
|
|
123
|
-
break;
|
|
131
|
+
var children = this.childNodes;
|
|
132
|
+
this.childNodes = children.map(function (child) {
|
|
133
|
+
if (child === oldNode) {
|
|
134
|
+
return newNode;
|
|
124
135
|
}
|
|
125
|
-
|
|
126
|
-
|
|
136
|
+
return child;
|
|
137
|
+
});
|
|
127
138
|
};
|
|
128
139
|
Object.defineProperty(HTMLElement.prototype, "tagName", {
|
|
129
140
|
get: function () {
|
|
@@ -625,6 +636,46 @@ var HTMLElement = /** @class */ (function (_super) {
|
|
|
625
636
|
// return;
|
|
626
637
|
// }
|
|
627
638
|
};
|
|
639
|
+
Object.defineProperty(HTMLElement.prototype, "nextSibling", {
|
|
640
|
+
get: function () {
|
|
641
|
+
if (this.parentNode) {
|
|
642
|
+
var children = this.parentNode.childNodes;
|
|
643
|
+
var i = 0;
|
|
644
|
+
while (i < children.length) {
|
|
645
|
+
var child = children[i++];
|
|
646
|
+
if (this === child) {
|
|
647
|
+
return children[i] || null;
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
return null;
|
|
651
|
+
}
|
|
652
|
+
},
|
|
653
|
+
enumerable: false,
|
|
654
|
+
configurable: true
|
|
655
|
+
});
|
|
656
|
+
Object.defineProperty(HTMLElement.prototype, "nextElementSibling", {
|
|
657
|
+
get: function () {
|
|
658
|
+
if (this.parentNode) {
|
|
659
|
+
var children = this.parentNode.childNodes;
|
|
660
|
+
var i = 0;
|
|
661
|
+
var find = false;
|
|
662
|
+
while (i < children.length) {
|
|
663
|
+
var child = children[i++];
|
|
664
|
+
if (find) {
|
|
665
|
+
if (child instanceof HTMLElement) {
|
|
666
|
+
return child || null;
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
else if (this === child) {
|
|
670
|
+
find = true;
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
return null;
|
|
674
|
+
}
|
|
675
|
+
},
|
|
676
|
+
enumerable: false,
|
|
677
|
+
configurable: true
|
|
678
|
+
});
|
|
628
679
|
return HTMLElement;
|
|
629
680
|
}(node_1.default));
|
|
630
681
|
exports.default = HTMLElement;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "node-html-parser",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.9",
|
|
4
4
|
"description": "A very fast HTML parser, generating a simplified DOM, with basic element query support.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/esm/index.js",
|
|
@@ -13,11 +13,14 @@
|
|
|
13
13
|
"ts:amd": "tsc -t es5 -m amd -d false --outFile ./dist/main.js",
|
|
14
14
|
"ts:esm": "tsc -t esnext -m esnext -d false --outDir ./dist/esm/",
|
|
15
15
|
"build": "npm run lint && npm run clean && npm run ts:cjs && npm run ts:amd && npm run ts:esm",
|
|
16
|
-
"dev": "tsc -w",
|
|
16
|
+
"dev": "tsc -w & mocha -w ./test/*.js",
|
|
17
17
|
"pretest": "tsc -m commonjs"
|
|
18
18
|
},
|
|
19
19
|
"keywords": [
|
|
20
|
-
"
|
|
20
|
+
"parser",
|
|
21
|
+
"html",
|
|
22
|
+
"nodejs",
|
|
23
|
+
"typescript"
|
|
21
24
|
],
|
|
22
25
|
"author": "Xiaoyi Shi <ashi009@gmail.com>",
|
|
23
26
|
"contributors": [
|