├── 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)?" /":`>`)).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 | [![Build Status](https://travis-ci.com/WebReflection/uparser.svg?branch=master)](https://travis-ci.com/WebReflection/uparser) [![Coverage Status](https://coveralls.io/repos/github/WebReflection/uparser/badge.svg?branch=master)](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)) ? ' /' : `>` 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)) ? ' /' : `>` 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)) ? ' /' : `>` 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 | --------------------------------------------------------------------------------