├── cjs
├── package.json
└── index.js
├── test
├── package.json
├── benchmark.js
└── index.js
├── .npmrc
├── .gitignore
├── types
└── index.d.ts
├── .npmignore
├── rollup
├── index.config.js
└── es.config.js
├── es.js
├── tsconfig.json
├── README.md
├── .github
└── workflows
│ └── node.js.yml
├── LICENSE
├── esm
└── index.js
├── index.js
└── package.json
/cjs/package.json:
--------------------------------------------------------------------------------
1 | {"type":"commonjs"}
--------------------------------------------------------------------------------
/test/package.json:
--------------------------------------------------------------------------------
1 | {"type":"commonjs"}
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | package-lock=false
2 | package-lock=true
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .nyc_output
3 | coverage/
4 | node_modules/
5 |
--------------------------------------------------------------------------------
/types/index.d.ts:
--------------------------------------------------------------------------------
1 | declare function _default(template: string[], prefix: string, xml: boolean): string;
2 | export default _default;
3 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .nyc_output
3 | .eslintrc.json
4 | .travis.yml
5 | coverage/
6 | node_modules/
7 | rollup/
8 | test/
9 | tsconfig.json
10 |
--------------------------------------------------------------------------------
/rollup/index.config.js:
--------------------------------------------------------------------------------
1 | import {nodeResolve} from '@rollup/plugin-node-resolve';
2 |
3 | export default {
4 | input: './esm/index.js',
5 | plugins: [
6 | nodeResolve(),
7 | ],
8 | output: {
9 | esModule: true,
10 | file: './index.js',
11 | }
12 | };
13 |
--------------------------------------------------------------------------------
/rollup/es.config.js:
--------------------------------------------------------------------------------
1 | import {nodeResolve} from '@rollup/plugin-node-resolve';
2 | import terser from '@rollup/plugin-terser';
3 |
4 | export default {
5 | input: './esm/index.js',
6 | plugins: [
7 | nodeResolve(),
8 | terser()
9 | ],
10 | output: {
11 | esModule: true,
12 | file: './es.js',
13 | }
14 | };
15 |
--------------------------------------------------------------------------------
/es.js:
--------------------------------------------------------------------------------
1 | const e=/^(?:area|base|br|col|embed|hr|img|input|keygen|link|menuitem|meta|param|source|track|wbr)$/i,r=/<([a-zA-Z0-9]+[a-zA-Z0-9:._-]*)([^>]*?)(\/?)>/g,a=/([^\s\\>"'=]+)\s*=\s*(['"]?)\x01/g,t=/[\x01\x02]/g;var c=(c,i,m)=>{let n=0;return c.join("").trim().replace(r,((r,t,c,i)=>`<${t}${c.replace(a,"=$2$1").trimEnd()}${i?m||e.test(t)?" /":`>${t}`:""}>`)).replace(t,(e=>""===e?`\x3c!--${i+n++}--\x3e`:i+n++))};export{c as default};
2 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "NodeNext",
4 | "target": "esnext",
5 | "moduleResolution": "nodenext",
6 | "allowJs": true,
7 | "declaration": true,
8 | "emitDeclarationOnly": true,
9 | "declarationDir": "types"
10 | },
11 | "include": [
12 | "esm/index.js",
13 | "esm/exports.js",
14 | "esm/xworker.js"
15 | ]
16 | }
17 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # µparser
2 |
3 | [](https://travis-ci.com/WebReflection/uparser) [](https://coveralls.io/github/WebReflection/uparser?branch=master)
4 |
5 | The _[µhtml](https://github.com/WebReflection/uhtml#readme)_ and _[µcontent](https://github.com/WebReflection/ucontent#readme)_ template parser.
6 |
7 | ```js
8 | import parse from '@webreflection/uparser';
9 |
10 | const html = template => parse(template, 'secret', false);
11 | const svg = template => parse(template, 'secret', true);
12 |
13 | html`
`; //
14 | svg``; //
15 | ```
16 |
--------------------------------------------------------------------------------
/test/benchmark.js:
--------------------------------------------------------------------------------
1 | const parse = require('../cjs');
2 | const tag = template => template;
3 |
4 | console.log('\x1b[1m');
5 | const [result] = test(1, 'cold');
6 | test(5, 'hot');
7 | console.log('\x1b[0m\x1b[2m');
8 | console.log(result.trim());
9 | console.log('\x1b[0m');
10 |
11 | function test(times, name) {
12 | const results = [];
13 | console.time(name);
14 | for (let i = 0; i < times; i++) results.push(parse(tag`
15 |
16 |
17 | ${'title'}
18 |
19 |
20 |
21 |
22 |
23 | ${Math.random()}
24 |
25 |
26 |
27 | `, 'prefix'));
28 | console.timeEnd(name);
29 | return results;
30 | }
--------------------------------------------------------------------------------
/.github/workflows/node.js.yml:
--------------------------------------------------------------------------------
1 | # This workflow will do a clean install of node dependencies, cache/restore them, build the source code and run tests across different versions of node
2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
3 |
4 | name: build
5 |
6 | on: [push, pull_request]
7 |
8 | jobs:
9 | build:
10 |
11 | runs-on: ubuntu-latest
12 |
13 | strategy:
14 | matrix:
15 | node-version: [16]
16 |
17 | steps:
18 | - uses: actions/checkout@v2
19 | - name: Use Node.js ${{ matrix.node-version }}
20 | uses: actions/setup-node@v2
21 | with:
22 | node-version: ${{ matrix.node-version }}
23 | cache: 'npm'
24 | - run: npm ci
25 | - run: npm run build --if-present
26 | - run: npm test
27 | - run: npm run coverage --if-present
28 | - name: Coveralls
29 | uses: coverallsapp/github-action@master
30 | with:
31 | github-token: ${{ secrets.GITHUB_TOKEN }}
32 |
--------------------------------------------------------------------------------
/test/index.js:
--------------------------------------------------------------------------------
1 | const parse = require('../cjs');
2 |
3 | const expect = (comp, result) => {
4 | console.assert(comp === result, `expected "${result}" but got "${comp}"`);
5 | };
6 |
7 | expect(parse([`
8 |
9 | `], '!'), '');
10 | expect(parse([''], '!'), '');
11 | expect(parse([''], '!', true), '');
12 | expect(parse([''], '!'), '');
13 | expect(parse([''], '!'), '');
14 | expect(parse([''], '!'), '');
15 | expect(parse([''], '!'), '');
16 | expect(parse(['', '
'], '!'), '');
17 | expect(parse(['', '=
'], '!'), '=
');
18 | expect(parse([' test="', '"
'], '!'), ' test=""
');
19 | expect(parse([' a="', '" b="', '"
'], '!'), ' a="" b=""
');
20 | expect(parse(['test="', '"
'], '!'), 'test=""
');
21 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright © 2020-today, Andrea Giammarchi, @WebReflection
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the “Software”), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software
10 | is furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included
13 | in all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21 | IN THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/esm/index.js:
--------------------------------------------------------------------------------
1 | import { VOID_ELEMENTS } from 'domconstants/re';
2 |
3 | const elements = /<([a-zA-Z0-9]+[a-zA-Z0-9:._-]*)([^>]*?)(\/?)>/g;
4 | const attributes = /([^\s\\>"'=]+)\s*=\s*(['"]?)\x01/g;
5 | const holes = /[\x01\x02]/g;
6 |
7 | // \x01 Node.ELEMENT_NODE
8 | // \x02 Node.ATTRIBUTE_NODE
9 |
10 | /**
11 | * Given a template, find holes as both nodes and attributes and
12 | * return a string with holes as either comment nodes or named attributes.
13 | * @param {string[]} template a template literal tag array
14 | * @param {string} prefix prefix to use per each comment/attribute
15 | * @param {boolean} xml enforces self-closing tags
16 | * @returns {string} X/HTML with prefixed comments or attributes
17 | */
18 | export default (template, prefix, xml) => {
19 | let i = 0;
20 | return template
21 | .join('\x01')
22 | .trim()
23 | .replace(
24 | elements,
25 | (_, name, attrs, selfClosing) => `<${
26 | name
27 | }${
28 | attrs.replace(attributes, '\x02=$2$1').trimEnd()
29 | }${
30 | selfClosing ? (
31 | (xml || VOID_ELEMENTS.test(name)) ? ' /' : `>${name}`
32 | ) : ''
33 | }>`
34 | )
35 | .replace(
36 | holes,
37 | hole => hole === '\x01' ? `` : (prefix + i++)
38 | )
39 | ;
40 | };
41 |
--------------------------------------------------------------------------------
/cjs/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const { VOID_ELEMENTS } = require('domconstants/re');
3 |
4 | const elements = /<([a-zA-Z0-9]+[a-zA-Z0-9:._-]*)([^>]*?)(\/?)>/g;
5 | const attributes = /([^\s\\>"'=]+)\s*=\s*(['"]?)\x01/g;
6 | const holes = /[\x01\x02]/g;
7 |
8 | // \x01 Node.ELEMENT_NODE
9 | // \x02 Node.ATTRIBUTE_NODE
10 |
11 | /**
12 | * Given a template, find holes as both nodes and attributes and
13 | * return a string with holes as either comment nodes or named attributes.
14 | * @param {string[]} template a template literal tag array
15 | * @param {string} prefix prefix to use per each comment/attribute
16 | * @param {boolean} xml enforces self-closing tags
17 | * @returns {string} X/HTML with prefixed comments or attributes
18 | */
19 | module.exports = (template, prefix, xml) => {
20 | let i = 0;
21 | return template
22 | .join('\x01')
23 | .trim()
24 | .replace(
25 | elements,
26 | (_, name, attrs, selfClosing) => `<${
27 | name
28 | }${
29 | attrs.replace(attributes, '\x02=$2$1').trimEnd()
30 | }${
31 | selfClosing ? (
32 | (xml || VOID_ELEMENTS.test(name)) ? ' /' : `>${name}`
33 | ) : ''
34 | }>`
35 | )
36 | .replace(
37 | holes,
38 | hole => hole === '\x01' ? `` : (prefix + i++)
39 | )
40 | ;
41 | };
42 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | const VOID_ELEMENTS = /^(?:area|base|br|col|embed|hr|img|input|keygen|link|menuitem|meta|param|source|track|wbr)$/i;
2 |
3 | const elements = /<([a-zA-Z0-9]+[a-zA-Z0-9:._-]*)([^>]*?)(\/?)>/g;
4 | const attributes = /([^\s\\>"'=]+)\s*=\s*(['"]?)\x01/g;
5 | const holes = /[\x01\x02]/g;
6 |
7 | // \x01 Node.ELEMENT_NODE
8 | // \x02 Node.ATTRIBUTE_NODE
9 |
10 | /**
11 | * Given a template, find holes as both nodes and attributes and
12 | * return a string with holes as either comment nodes or named attributes.
13 | * @param {string[]} template a template literal tag array
14 | * @param {string} prefix prefix to use per each comment/attribute
15 | * @param {boolean} xml enforces self-closing tags
16 | * @returns {string} X/HTML with prefixed comments or attributes
17 | */
18 | var index = (template, prefix, xml) => {
19 | let i = 0;
20 | return template
21 | .join('\x01')
22 | .trim()
23 | .replace(
24 | elements,
25 | (_, name, attrs, selfClosing) => `<${
26 | name
27 | }${
28 | attrs.replace(attributes, '\x02=$2$1').trimEnd()
29 | }${
30 | selfClosing ? (
31 | (xml || VOID_ELEMENTS.test(name)) ? ' /' : `>${name}`
32 | ) : ''
33 | }>`
34 | )
35 | .replace(
36 | holes,
37 | hole => hole === '\x01' ? `` : (prefix + i++)
38 | )
39 | ;
40 | };
41 |
42 | export { index as default };
43 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@webreflection/uparser",
3 | "version": "0.4.0",
4 | "description": "The uhtml template parser",
5 | "main": "./cjs/index.js",
6 | "scripts": {
7 | "build": "npm run cjs && npm run rollup:es && npm run rollup:index && npm run ts && npm run test && npm run size",
8 | "cjs": "ascjs --no-default esm cjs",
9 | "rollup:es": "rollup --config rollup/es.config.js",
10 | "rollup:index": "rollup --config rollup/index.config.js",
11 | "size": "cat es.js | brotli | wc -c",
12 | "test": "c8 node test/index.js",
13 | "ts": "tsc -p .",
14 | "coverage": "mkdir -p ./coverage; c8 report --reporter=text-lcov > ./coverage/lcov.info"
15 | },
16 | "keywords": [
17 | "uhtml",
18 | "template",
19 | "parser"
20 | ],
21 | "author": "Andrea Giammarchi",
22 | "license": "MIT",
23 | "devDependencies": {
24 | "@rollup/plugin-node-resolve": "^15.3.0",
25 | "@rollup/plugin-terser": "^0.4.4",
26 | "ascjs": "^6.0.3",
27 | "c8": "^10.1.2",
28 | "rollup": "^4.24.0",
29 | "typescript": "^5.6.3"
30 | },
31 | "module": "./esm/index.js",
32 | "type": "module",
33 | "exports": {
34 | ".": {
35 | "types": "./types/index.d.ts",
36 | "import": "./esm/index.js",
37 | "default": "./cjs/index.js"
38 | },
39 | "./package.json": "./package.json"
40 | },
41 | "unpkg": "es.js",
42 | "repository": {
43 | "type": "git",
44 | "url": "git+https://github.com/WebReflection/uparser.git"
45 | },
46 | "bugs": {
47 | "url": "https://github.com/WebReflection/uparser/issues"
48 | },
49 | "homepage": "https://github.com/WebReflection/uparser#readme",
50 | "dependencies": {
51 | "domconstants": "^1.1.6"
52 | }
53 | }
54 |
--------------------------------------------------------------------------------