From 8449a3fd67d7494ab157e5357e22f8b79f46d030 Mon Sep 17 00:00:00 2001 From: AndrewLeedham Date: Tue, 16 Jun 2020 17:20:36 +0100 Subject: [PATCH] Convert to TypeScript --- .editorconfig | 2 + .eslintrc | 57 -- package.json | 111 ++-- rollup.config.js | 26 - src/{empty-tags.js => empty-tags.ts} | 3 +- src/jsx.d.ts | 823 +++++++++++++++++++++++++++ src/vhtml.js | 60 -- src/vhtml.ts | 124 ++++ test/{vhtml.js => vhtml.tsx} | 30 +- tsconfig.json | 14 + 10 files changed, 1048 insertions(+), 202 deletions(-) delete mode 100644 .eslintrc delete mode 100644 rollup.config.js rename src/{empty-tags.js => empty-tags.ts} (91%) create mode 100644 src/jsx.d.ts delete mode 100644 src/vhtml.js create mode 100644 src/vhtml.ts rename test/{vhtml.js => vhtml.tsx} (84%) create mode 100644 tsconfig.json diff --git a/.editorconfig b/.editorconfig index 9c378bd..3a2697d 100644 --- a/.editorconfig +++ b/.editorconfig @@ -13,3 +13,5 @@ indent_size = 2 [*.md] trim_trailing_whitespace = false +indent_style = space +indent_size = 2 \ No newline at end of file diff --git a/.eslintrc b/.eslintrc deleted file mode 100644 index 06a5fd3..0000000 --- a/.eslintrc +++ /dev/null @@ -1,57 +0,0 @@ -{ - "parser": "babel-eslint", - "extends": "eslint:recommended", - "env": { - "browser": true, - "mocha": true, - "es6": true - }, - "parserOptions": { - "ecmaFeatures": { - "modules": true, - "jsx": true - } - }, - "globals": { - "require": true - }, - "rules": { - "no-empty": 0, - "no-console": 0, - "no-unused-vars": [0, { "varsIgnorePattern": "^h$" }], - "no-cond-assign": 1, - "semi": 2, - "camelcase": 0, - "comma-style": 2, - "comma-dangle": [2, "never"], - "indent": [2, "tab", {"SwitchCase": 1}], - "no-mixed-spaces-and-tabs": [2, "smart-tabs"], - "no-trailing-spaces": [2, { "skipBlankLines": true }], - "max-nested-callbacks": [2, 3], - "no-eval": 2, - "no-implied-eval": 2, - "no-new-func": 2, - "guard-for-in": 2, - "eqeqeq": 0, - "no-else-return": 2, - "no-redeclare": 2, - "no-dupe-keys": 2, - "radix": 2, - "strict": [2, "never"], - "no-shadow": 0, - "no-delete-var": 2, - "no-undef-init": 2, - "no-shadow-restricted-names": 2, - "handle-callback-err": 0, - "no-lonely-if": 2, - "constructor-super": 2, - "no-this-before-super": 2, - "no-dupe-class-members": 2, - "no-const-assign": 2, - "prefer-spread": 2, - "no-useless-concat": 2, - "no-var": 2, - "object-shorthand": 2, - "prefer-arrow-callback": 2 - } -} diff --git a/package.json b/package.json index 6e3d2ff..9f4f313 100644 --- a/package.json +++ b/package.json @@ -1,34 +1,24 @@ { "name": "vhtml", "amdName": "vhtml", - "version": "2.2.0", + "version": "3.0.0", "description": "Hyperscript reviver that constructs a sanitized HTML string.", + "jsnext:main": "dist/vhtml.es.js", + "source": "src/vhtml.ts", + "module": "dist/vhtml.es.js", + "esmodules": "dist/vhtml.modern.js", "main": "dist/vhtml.js", - "minified:main": "dist/vhtml.min.js", - "jsnext:main": "src/vhtml.js", + "umd:main": "dist/vhtml.umd.js", + "typings": "dist/vhtml.d.ts", "scripts": { - "build": "npm-run-all transpile minify size", - "transpile": "rollup -c rollup.config.js", - "minify": "uglifyjs $npm_package_main -cm -o $npm_package_minified_main -p relative --in-source-map ${npm_package_main}.map --source-map ${npm_package_minified_main}.map", - "size": "echo \"gzip size: $(gzip-size $npm_package_minified_main | pretty-bytes)\"", - "test": "eslint {src,test} && mocha --compilers js:babel-register test/**/*.js", - "prepublish": "npm-run-all build test", - "release": "npm run -s build && git commit -am $npm_package_version && git tag $npm_package_version && git push && git push --tags && npm publish" - }, - "babel": { - "presets": [ - "es2015", - "stage-0" - ], - "plugins": [ - "transform-object-rest-spread", - [ - "transform-react-jsx", - { - "pragma": "h" - } - ] - ] + "test": "npm-run-all --silent typecheck lint testonly", + "testonly": "mocha --require ts-node/register --require tsconfig-paths/register test/**/*.tsx", + "lint": "eslint src test --ext tsx --ext ts --ext jsx --ext js", + "typecheck": "tsc --noEmit", + "bundle": "microbundle && cp src/jsx.d.ts dist/jsx.d.ts && rimraf dist/empty-tags.d.ts", + "build": "npm run clean && npm run bundle", + "clean": "rimraf dist", + "release": "npm run build && npm test && git commit -am $npm_package_version && git tag $npm_package_version && git push && git push --tags && npm publish" }, "files": [ "src", @@ -50,24 +40,63 @@ "url": "https://github.com/developit/vhtml/issues" }, "homepage": "https://github.com/developit/vhtml", + "eslintConfig": { + "extends": [ + "developit", + "plugin:@typescript-eslint/eslint-recommended", + "plugin:@typescript-eslint/recommended" + ], + "parser": "@typescript-eslint/parser", + "parserOptions": { + "sourceType": "module" + }, + "env": { + "browser": true, + "mocha": true, + "jest": false, + "es6": true + }, + "globals": { + "expect": true + }, + "settings": { + "react": { + "pragma": "h", + "version": "16.13" + } + }, + "rules": { + "semi": [ + 2, + "always" + ], + "jest/valid-expect": 0, + "@typescript-eslint/no-explicit-any": 0, + "@typescript-eslint/explicit-function-return-type": 0, + "@typescript-eslint/explicit-module-boundary-types": 0, + "@typescript-eslint/no-empty-function": 0, + "@typescript-eslint/no-empty-interface": 0, + "react/no-danger": 0, + "react/self-closing-comp": 0 + } + }, + "eslintIgnore": [ + "dist" + ], "devDependencies": { - "babel-core": "^6.6.4", - "babel-eslint": "^7.0.0", - "babel-plugin-transform-object-rest-spread": "^6.6.4", - "babel-plugin-transform-react-jsx": "^6.6.5", - "babel-preset-es2015": "^6.9.0", - "babel-preset-stage-0": "^6.5.0", - "babel-register": "^6.7.2", + "@types/chai": "^4.2.11", + "@types/mocha": "^7.0.2", + "@typescript-eslint/eslint-plugin": "^3.0.1", + "@typescript-eslint/parser": "^3.0.1", "chai": "^3.5.0", - "eslint": "^3.1.0", - "gzip-size-cli": "^1.0.0", - "mkdirp": "^0.5.1", - "mocha": "^3.1.2", + "eslint": "^7.2.0", + "eslint-config-developit": "^1.2.0", + "microbundle": "^0.12.0", + "mocha": "^8.0.1", "npm-run-all": "^2.3.0", - "pretty-bytes-cli": "^1.0.0", - "rollup": "^0.36.3", - "rollup-plugin-babel": "^2.4.0", - "rollup-plugin-es3": "^1.0.3", - "uglify-js": "^2.6.2" + "rimraf": "^3.0.2", + "ts-node": "^8.10.2", + "tsconfig-paths": "^3.9.0", + "typescript": "^3.9.3" } } diff --git a/rollup.config.js b/rollup.config.js deleted file mode 100644 index 3a7799d..0000000 --- a/rollup.config.js +++ /dev/null @@ -1,26 +0,0 @@ -import path from 'path'; -import fs from 'fs'; -import babel from 'rollup-plugin-babel'; -import es3 from 'rollup-plugin-es3'; - -let pkg = JSON.parse(fs.readFileSync('./package.json')); - -export default { - entry: pkg['jsnext:main'], - dest: pkg.main, - sourceMap: path.resolve(pkg.main), - moduleName: pkg.amdName, - exports: 'default', - format: 'umd', - plugins: [ - babel({ - babelrc: false, - comments: false, - presets: [ - ['es2015', { loose:true, modules:false }] - ].concat(pkg.babel.presets.slice(1)), - plugins: pkg.babel.plugins - }), - es3() - ] -}; diff --git a/src/empty-tags.js b/src/empty-tags.ts similarity index 91% rename from src/empty-tags.js rename to src/empty-tags.ts index 6f91e68..2322d4a 100644 --- a/src/empty-tags.js +++ b/src/empty-tags.ts @@ -3,7 +3,6 @@ export default [ 'base', 'br', 'col', - 'command', 'embed', 'hr', 'img', @@ -15,4 +14,4 @@ export default [ 'source', 'track', 'wbr' -]; \ No newline at end of file +]; diff --git a/src/jsx.d.ts b/src/jsx.d.ts new file mode 100644 index 0000000..1563e95 --- /dev/null +++ b/src/jsx.d.ts @@ -0,0 +1,823 @@ +type Defaultize = + // Distribute over unions + Props extends any // Make any properties included in Default optional + ? Partial>> & + // Include the remaining properties from Props + Pick> + : never; + +import h from './vhtml'; + +declare namespace JSXInternal { + type LibraryManagedAttributes = Component extends { + defaultProps: infer Defaults; + } + ? Defaultize + : Props; + + interface IntrinsicAttributes { + key?: any; + } + + type Element = string + + interface ElementAttributesProperty { + props: any; + } + + interface ElementChildrenAttribute { + children: any; + } + + interface SVGAttributes + extends HTMLAttributes { + accentHeight?: number | string; + accumulate?: 'none' | 'sum'; + additive?: 'replace' | 'sum'; + alignmentBaseline?: + | 'auto' + | 'baseline' + | 'before-edge' + | 'text-before-edge' + | 'middle' + | 'central' + | 'after-edge' + | 'text-after-edge' + | 'ideographic' + | 'alphabetic' + | 'hanging' + | 'mathematical' + | 'inherit'; + allowReorder?: 'no' | 'yes'; + alphabetic?: number | string; + amplitude?: number | string; + arabicForm?: 'initial' | 'medial' | 'terminal' | 'isolated'; + ascent?: number | string; + attributeName?: string; + attributeType?: string; + autoReverse?: number | string; + azimuth?: number | string; + baseFrequency?: number | string; + baselineShift?: number | string; + baseProfile?: number | string; + bbox?: number | string; + begin?: number | string; + bias?: number | string; + by?: number | string; + calcMode?: number | string; + capHeight?: number | string; + clip?: number | string; + clipPath?: string; + clipPathUnits?: number | string; + clipRule?: number | string; + colorInterpolation?: number | string; + colorInterpolationFilters?: 'auto' | 'sRGB' | 'linearRGB' | 'inherit'; + colorProfile?: number | string; + colorRendering?: number | string; + contentScriptType?: number | string; + contentStyleType?: number | string; + cursor?: number | string; + cx?: number | string; + cy?: number | string; + d?: string; + decelerate?: number | string; + descent?: number | string; + diffuseConstant?: number | string; + direction?: number | string; + display?: number | string; + divisor?: number | string; + dominantBaseline?: number | string; + dur?: number | string; + dx?: number | string; + dy?: number | string; + edgeMode?: number | string; + elevation?: number | string; + enableBackground?: number | string; + end?: number | string; + exponent?: number | string; + externalResourcesRequired?: number | string; + fill?: string; + fillOpacity?: number | string; + fillRule?: 'nonzero' | 'evenodd' | 'inherit'; + filter?: string; + filterRes?: number | string; + filterUnits?: number | string; + floodColor?: number | string; + floodOpacity?: number | string; + focusable?: number | string; + fontFamily?: string; + fontSize?: number | string; + fontSizeAdjust?: number | string; + fontStretch?: number | string; + fontStyle?: number | string; + fontVariant?: number | string; + fontWeight?: number | string; + format?: number | string; + from?: number | string; + fx?: number | string; + fy?: number | string; + g1?: number | string; + g2?: number | string; + glyphName?: number | string; + glyphOrientationHorizontal?: number | string; + glyphOrientationVertical?: number | string; + glyphRef?: number | string; + gradientTransform?: string; + gradientUnits?: string; + hanging?: number | string; + horizAdvX?: number | string; + horizOriginX?: number | string; + ideographic?: number | string; + imageRendering?: number | string; + in2?: number | string; + in?: string; + intercept?: number | string; + k1?: number | string; + k2?: number | string; + k3?: number | string; + k4?: number | string; + k?: number | string; + kernelMatrix?: number | string; + kernelUnitLength?: number | string; + kerning?: number | string; + keyPoints?: number | string; + keySplines?: number | string; + keyTimes?: number | string; + lengthAdjust?: number | string; + letterSpacing?: number | string; + lightingColor?: number | string; + limitingConeAngle?: number | string; + local?: number | string; + markerEnd?: string; + markerHeight?: number | string; + markerMid?: string; + markerStart?: string; + markerUnits?: number | string; + markerWidth?: number | string; + mask?: string; + maskContentUnits?: number | string; + maskUnits?: number | string; + mathematical?: number | string; + mode?: number | string; + numOctaves?: number | string; + offset?: number | string; + opacity?: number | string; + operator?: number | string; + order?: number | string; + orient?: number | string; + orientation?: number | string; + origin?: number | string; + overflow?: number | string; + overlinePosition?: number | string; + overlineThickness?: number | string; + paintOrder?: number | string; + panose1?: number | string; + pathLength?: number | string; + patternContentUnits?: string; + patternTransform?: number | string; + patternUnits?: string; + pointerEvents?: number | string; + points?: string; + pointsAtX?: number | string; + pointsAtY?: number | string; + pointsAtZ?: number | string; + preserveAlpha?: number | string; + preserveAspectRatio?: string; + primitiveUnits?: number | string; + r?: number | string; + radius?: number | string; + refX?: number | string; + refY?: number | string; + renderingIntent?: number | string; + repeatCount?: number | string; + repeatDur?: number | string; + requiredExtensions?: number | string; + requiredFeatures?: number | string; + restart?: number | string; + result?: string; + rotate?: number | string; + rx?: number | string; + ry?: number | string; + scale?: number | string; + seed?: number | string; + shapeRendering?: number | string; + slope?: number | string; + spacing?: number | string; + specularConstant?: number | string; + specularExponent?: number | string; + speed?: number | string; + spreadMethod?: string; + startOffset?: number | string; + stdDeviation?: number | string; + stemh?: number | string; + stemv?: number | string; + stitchTiles?: number | string; + stopColor?: string; + stopOpacity?: number | string; + strikethroughPosition?: number | string; + strikethroughThickness?: number | string; + string?: number | string; + stroke?: string; + strokeDasharray?: string | number; + strokeDashoffset?: string | number; + strokeLinecap?: 'butt' | 'round' | 'square' | 'inherit'; + strokeLinejoin?: 'miter' | 'round' | 'bevel' | 'inherit'; + strokeMiterlimit?: string; + strokeOpacity?: number | string; + strokeWidth?: number | string; + surfaceScale?: number | string; + systemLanguage?: number | string; + tableValues?: number | string; + targetX?: number | string; + targetY?: number | string; + textAnchor?: string; + textDecoration?: number | string; + textLength?: number | string; + textRendering?: number | string; + to?: number | string; + transform?: string; + u1?: number | string; + u2?: number | string; + underlinePosition?: number | string; + underlineThickness?: number | string; + unicode?: number | string; + unicodeBidi?: number | string; + unicodeRange?: number | string; + unitsPerEm?: number | string; + vAlphabetic?: number | string; + values?: string; + vectorEffect?: number | string; + version?: string; + vertAdvY?: number | string; + vertOriginX?: number | string; + vertOriginY?: number | string; + vHanging?: number | string; + vIdeographic?: number | string; + viewBox?: string; + viewTarget?: number | string; + visibility?: number | string; + vMathematical?: number | string; + widths?: number | string; + wordSpacing?: number | string; + writingMode?: number | string; + x1?: number | string; + x2?: number | string; + x?: number | string; + xChannelSelector?: string; + xHeight?: number | string; + xlinkActuate?: string; + xlinkArcrole?: string; + xlinkHref?: string; + xlinkRole?: string; + xlinkShow?: string; + xlinkTitle?: string; + xlinkType?: string; + xmlBase?: string; + xmlLang?: string; + xmlns?: string; + xmlnsXlink?: string; + xmlSpace?: string; + y1?: number | string; + y2?: number | string; + y?: number | string; + yChannelSelector?: string; + z?: number | string; + zoomAndPan?: string; + } + + interface PathAttributes { + d: string; + } + + interface DOMAttributes + extends h.VHTMLDOMAttributes { + // Image Events + onload?: string; + onloadcapture?: string; + onerror?: string; + onerrorcapture?: string; + + // Clipboard Events + oncopy?: string; + oncopycapture?: string; + oncut?: string; + oncutcapture?: string; + onpaste?: string; + onpastecapture?: string; + + // Composition Events + oncompositionend?: string; + oncompositionendcapture?: string; + oncompositionstart?: string; + oncompositionstartcapture?: string; + oncompositionupdate?: string; + oncompositionupdatecapture?: string; + + // Details Events + ontoggle?: string; + + // Focus Events + onfocus?: string; + onfocuscapture?: string; + onblur?: string; + onblurcapture?: string; + + // Form Events + onchange?: string; + onchangecapture?: string; + oninput?: string; + oninputcapture?: string; + onsearch?: string; + onsearchcapture?: string; + onsubmit?: string; + onsubmitcapture?: string; + oninvalid?: string; + oninvalidcapture?: string; + onreset?: string; + onresetcapture?: string; + onformdata?: string; + onformdatacapture?: string; + + + // Keyboard Events + onkeydown?: string; + onkeydowncapture?: string; + onkeypress?: string; + onkeypresscapture?: string; + onkeyup?: string; + onkeyupcapture?: string; + + // Media Events + onabort?: string; + onabortcapture?: string; + oncanplay?: string; + oncanplaycapture?: string; + oncanplaythrough?: string; + oncanplaythroughcapture?: string; + ondurationchange?: string; + ondurationchangecapture?: string; + onemptied?: string; + onemptiedcapture?: string; + onencrypted?: string; + onencryptedcapture?: string; + onended?: string; + onendedcapture?: string; + onloadeddata?: string; + onloadeddatacapture?: string; + onloadedmetadata?: string; + onloadedmetadatacapture?: string; + onloadstart?: string; + onloadstartcapture?: string; + onpause?: string; + onpausecapture?: string; + onplay?: string; + onplaycapture?: string; + onplaying?: string; + onplayingcapture?: string; + onprogress?: string; + onprogresscapture?: string; + onratechange?: string; + onratechangecapture?: string; + onseeked?: string; + onseekedcapture?: string; + onseeking?: string; + onseekingcapture?: string; + onstalled?: string; + onstalledcapture?: string; + onsuspend?: string; + onsuspendcapture?: string; + ontimeupdate?: string; + ontimeupdatecapture?: string; + onvolumechange?: string; + onvolumechangecapture?: string; + onwaiting?: string; + onwaitingcapture?: string; + + // MouseEvents + onclick?: string; + onclickcapture?: string; + oncontextmenu?: string; + oncontextmenucapture?: string; + ondblclick?: string; + ondblclickcapture?: string; + ondrag?: string; + ondragcapture?: string; + ondragend?: string; + ondragendcapture?: string; + ondragenter?: string; + ondragentercapture?: string; + ondragexit?: string; + ondragexitcapture?: string; + ondragleave?: string; + ondragleavecapture?: string; + ondragover?: string; + ondragovercapture?: string; + ondragstart?: string; + ondragstartcapture?: string; + ondrop?: string; + ondropcapture?: string; + onmousedown?: string; + onmousedowncapture?: string; + onmouseenter?: string; + onmouseentercapture?: string; + onmouseleave?: string; + onmouseleavecapture?: string; + onmousemove?: string; + onmousemovecapture?: string; + onmouseout?: string; + onmouseoutcapture?: string; + onmouseover?: string; + onmouseovercapture?: string; + onmouseup?: string; + onmouseupcapture?: string; + + // Selection Events + onselect?: string; + onselectcapture?: string; + + // Touch Events + ontouchcancel?: string; + ontouchcancelcapture?: string; + ontouchend?: string; + ontouchendcapture?: string; + ontouchmove?: string; + ontouchmovecapture?: string; + ontouchstart?: string; + ontouchstartcapture?: string; + + // Pointer Events + onpointerover?: string; + onpointerovercapture?: string; + onpointerenter?: string; + onpointerentercapture?: string; + onpointerdown?: string; + onpointerdowncapture?: string; + onpointermove?: string; + onpointermovecapture?: string; + onpointerup?: string; + onpointerupcapture?: string; + onpointercancel?: string; + onpointercancelcapture?: string; + onpointerout?: string; + onpointeroutcapture?: string; + onpointerleave?: string; + onpointerleavecapture?: string; + ongotpointercapture?: string; + ongotpointercapturecapture?: string; + onlostpointercapture?: string; + onlostpointercapturecapture?: string; + + // UI Events + onscroll?: string; + onscrollcapture?: string; + + // Wheel Events + onwheel?: string; + onwheelcapture?: string; + + // Animation Events + onanimationstart?: string; + onanimationstartcapture?: string; + onanimationend?: string; + onanimationendcapture?: string; + onanimationiteration?: string; + onanimationiterationcapture?: string; + + // Transition Events + ontransitionend?: string; + ontransitionendcapture?: string; + } + + interface HTMLAttributes + extends DOMAttributes { + // Standard HTML Attributes + accept?: string; + acceptCharset?: string; + accessKey?: string; + action?: string; + allowFullScreen?: boolean; + allowTransparency?: boolean; + alt?: string; + as?: string; + async?: boolean; + autocomplete?: string; + autoComplete?: string; + autocorrect?: string; + autoCorrect?: string; + autofocus?: boolean; + autoFocus?: boolean; + autoPlay?: boolean; + capture?: boolean; + cellPadding?: number | string; + cellSpacing?: number | string; + charSet?: string; + challenge?: string; + checked?: boolean; + class?: string; + className?: string; + cols?: number; + colSpan?: number; + content?: string; + contentEditable?: boolean; + contextMenu?: string; + controls?: boolean; + controlsList?: string; + coords?: string; + crossOrigin?: string; + data?: string; + dateTime?: string; + default?: boolean; + defer?: boolean; + dir?: 'auto' | 'rtl' | 'ltr'; + disabled?: boolean; + disableRemotePlayback?: boolean; + download?: any; + draggable?: boolean; + encType?: string; + form?: string; + formAction?: string; + formEncType?: string; + formMethod?: string; + formNoValidate?: boolean; + formTarget?: string; + frameBorder?: number | string; + headers?: string; + height?: number | string; + hidden?: boolean; + high?: number; + href?: string; + hrefLang?: string; + for?: string; + htmlFor?: string; + httpEquiv?: string; + icon?: string; + id?: string; + inputMode?: string; + integrity?: string; + is?: string; + keyParams?: string; + keyType?: string; + kind?: string; + label?: string; + lang?: string; + list?: string; + loading?: 'eager' | 'lazy'; + loop?: boolean; + low?: number; + manifest?: string; + marginHeight?: number; + marginWidth?: number; + max?: number | string; + maxLength?: number; + media?: string; + mediaGroup?: string; + method?: string; + min?: number | string; + minLength?: number; + multiple?: boolean; + muted?: boolean; + name?: string; + nonce?: string; + noValidate?: boolean; + open?: boolean; + optimum?: number; + pattern?: string; + placeholder?: string; + playsInline?: boolean; + poster?: string; + preload?: string; + radioGroup?: string; + readOnly?: boolean; + rel?: string; + required?: boolean; + role?: string; + rows?: number; + rowSpan?: number; + sandbox?: string; + scope?: string; + scoped?: boolean; + scrolling?: string; + seamless?: boolean; + selected?: boolean; + shape?: string; + size?: number; + sizes?: string; + slot?: string; + span?: number; + spellcheck?: boolean; + src?: string; + srcset?: string; + srcDoc?: string; + srcLang?: string; + srcSet?: string; + start?: number; + step?: number | string; + style?: string | { [key: string]: string | number }; + summary?: string; + tabIndex?: number; + target?: string; + title?: string; + type?: string; + useMap?: string; + value?: string | string[] | number; + volume?: string | number; + width?: number | string; + wmode?: string; + wrap?: string; + + // RDFa Attributes + about?: string; + datatype?: string; + inlist?: any; + prefix?: string; + property?: string; + resource?: string; + typeof?: string; + vocab?: string; + + // Microdata Attributes + itemProp?: string; + itemScope?: boolean; + itemType?: string; + itemID?: string; + itemRef?: string; + } + + interface HTMLMarqueeElement extends HTMLElement { + behavior?: 'scroll' | 'slide' | 'alternate'; + bgColor?: string; + direction?: 'left' | 'right' | 'up' | 'down'; + height?: number | string; + hspace?: number | string; + loop?: number | string; + scrollAmount?: number | string; + scrollDelay?: number | string; + trueSpeed?: boolean; + vspace?: number | string; + width?: number | string; + } + + interface IntrinsicElements { + // HTML + a: HTMLAttributes; + abbr: HTMLAttributes; + address: HTMLAttributes; + area: HTMLAttributes; + article: HTMLAttributes; + aside: HTMLAttributes; + audio: HTMLAttributes; + b: HTMLAttributes; + base: HTMLAttributes; + bdi: HTMLAttributes; + bdo: HTMLAttributes; + big: HTMLAttributes; + blockquote: HTMLAttributes; + body: HTMLAttributes; + br: HTMLAttributes; + button: HTMLAttributes; + canvas: HTMLAttributes; + caption: HTMLAttributes; + cite: HTMLAttributes; + code: HTMLAttributes; + col: HTMLAttributes; + colgroup: HTMLAttributes; + data: HTMLAttributes; + datalist: HTMLAttributes; + dd: HTMLAttributes; + del: HTMLAttributes; + details: HTMLAttributes; + dfn: HTMLAttributes; + dialog: HTMLAttributes; + div: HTMLAttributes; + dl: HTMLAttributes; + dt: HTMLAttributes; + em: HTMLAttributes; + embed: HTMLAttributes; + fieldset: HTMLAttributes; + figcaption: HTMLAttributes; + figure: HTMLAttributes; + footer: HTMLAttributes; + form: HTMLAttributes; + h1: HTMLAttributes; + h2: HTMLAttributes; + h3: HTMLAttributes; + h4: HTMLAttributes; + h5: HTMLAttributes; + h6: HTMLAttributes; + head: HTMLAttributes; + header: HTMLAttributes; + hgroup: HTMLAttributes; + hr: HTMLAttributes; + html: HTMLAttributes; + i: HTMLAttributes; + iframe: HTMLAttributes; + img: HTMLAttributes; + input: HTMLAttributes; + ins: HTMLAttributes; + kbd: HTMLAttributes; + keygen: HTMLAttributes; + label: HTMLAttributes; + legend: HTMLAttributes; + li: HTMLAttributes; + link: HTMLAttributes; + main: HTMLAttributes; + map: HTMLAttributes; + mark: HTMLAttributes; + marquee: HTMLAttributes; + menu: HTMLAttributes; + menuitem: HTMLAttributes; + meta: HTMLAttributes; + meter: HTMLAttributes; + nav: HTMLAttributes; + noscript: HTMLAttributes; + object: HTMLAttributes; + ol: HTMLAttributes; + optgroup: HTMLAttributes; + option: HTMLAttributes; + output: HTMLAttributes; + p: HTMLAttributes; + param: HTMLAttributes; + picture: HTMLAttributes; + pre: HTMLAttributes; + progress: HTMLAttributes; + q: HTMLAttributes; + rp: HTMLAttributes; + rt: HTMLAttributes; + ruby: HTMLAttributes; + s: HTMLAttributes; + samp: HTMLAttributes; + script: HTMLAttributes; + section: HTMLAttributes; + select: HTMLAttributes; + slot: HTMLAttributes; + small: HTMLAttributes; + source: HTMLAttributes; + span: HTMLAttributes; + strong: HTMLAttributes; + style: HTMLAttributes; + sub: HTMLAttributes; + summary: HTMLAttributes; + sup: HTMLAttributes; + table: HTMLAttributes; + tbody: HTMLAttributes; + td: HTMLAttributes; + textarea: HTMLAttributes; + tfoot: HTMLAttributes; + th: HTMLAttributes; + thead: HTMLAttributes; + time: HTMLAttributes; + title: HTMLAttributes; + tr: HTMLAttributes; + track: HTMLAttributes; + u: HTMLAttributes; + ul: HTMLAttributes; + var: HTMLAttributes; + video: HTMLAttributes; + wbr: HTMLAttributes; + + //SVG + svg: SVGAttributes; + animate: SVGAttributes; + circle: SVGAttributes; + clipPath: SVGAttributes; + defs: SVGAttributes; + desc: SVGAttributes; + ellipse: SVGAttributes; + feBlend: SVGAttributes; + feColorMatrix: SVGAttributes; + feComponentTransfer: SVGAttributes; + feComposite: SVGAttributes; + feConvolveMatrix: SVGAttributes; + feDiffuseLighting: SVGAttributes; + feDisplacementMap: SVGAttributes; + feFlood: SVGAttributes; + feGaussianBlur: SVGAttributes; + feImage: SVGAttributes; + feMerge: SVGAttributes; + feMergeNode: SVGAttributes; + feMorphology: SVGAttributes; + feOffset: SVGAttributes; + feSpecularLighting: SVGAttributes; + feTile: SVGAttributes; + feTurbulence: SVGAttributes; + filter: SVGAttributes; + foreignObject: SVGAttributes; + g: SVGAttributes; + image: SVGAttributes; + line: SVGAttributes; + linearGradient: SVGAttributes; + marker: SVGAttributes; + mask: SVGAttributes; + path: SVGAttributes; + pattern: SVGAttributes; + polygon: SVGAttributes; + polyline: SVGAttributes; + radialGradient: SVGAttributes; + rect: SVGAttributes; + stop: SVGAttributes; + symbol: SVGAttributes; + text: SVGAttributes; + tspan: SVGAttributes; + use: SVGAttributes; + } +} diff --git a/src/vhtml.js b/src/vhtml.js deleted file mode 100644 index 0e03f69..0000000 --- a/src/vhtml.js +++ /dev/null @@ -1,60 +0,0 @@ -import emptyTags from './empty-tags'; - -// escape an attribute -let esc = str => String(str).replace(/[&<>"']/g, s=>`&${map[s]};`); -let map = {'&':'amp','<':'lt','>':'gt','"':'quot',"'":'apos'}; -let setInnerHTMLAttr = 'dangerouslySetInnerHTML'; -let DOMAttributeNames = { - className: 'class', - htmlFor: 'for' -}; - -let sanitized = {}; - -/** Hyperscript reviver that constructs a sanitized HTML string. */ -export default function h(name, attrs) { - let stack=[], s = ''; - attrs = attrs || {}; - for (let i=arguments.length; i-- > 2; ) { - stack.push(arguments[i]); - } - - // Sortof component support! - if (typeof name==='function') { - attrs.children = stack.reverse(); - return name(attrs); - // return name(attrs, stack.reverse()); - } - - if (name) { - s += '<' + name; - if (attrs) for (let i in attrs) { - if (attrs[i]!==false && attrs[i]!=null && i !== setInnerHTMLAttr) { - s += ` ${DOMAttributeNames[i] ? DOMAttributeNames[i] : esc(i)}="${esc(attrs[i])}"`; - } - } - s += '>'; - } - - if (emptyTags.indexOf(name) === -1) { - if (attrs[setInnerHTMLAttr]) { - s += attrs[setInnerHTMLAttr].__html; - } - else while (stack.length) { - let child = stack.pop(); - if (child) { - if (child.pop) { - for (let i=child.length; i--; ) stack.push(child[i]); - } - else { - s += sanitized[child]===true ? child : esc(child); - } - } - } - - s += name ? `` : ''; - } - - sanitized[s] = true; - return s; -} diff --git a/src/vhtml.ts b/src/vhtml.ts new file mode 100644 index 0000000..835c70e --- /dev/null +++ b/src/vhtml.ts @@ -0,0 +1,124 @@ +import emptyTags from './empty-tags'; +import { JSXInternal } from './jsx'; + +// escape an attribute +const esc = (str: string): string => + String(str).replace(/[&<>"']/g, (s) => `&${map[s]};`); +const map = { '&': 'amp', '<': 'lt', '>': 'gt', '"': 'quot', "'": 'apos' }; +const setInnerHTMLAttr = 'dangerouslySetInnerHTML'; +const DOMAttributeNames = { + className: 'class', + htmlFor: 'for' +}; + +const sanitized: {[escaped: string]: boolean} = {}; + +/* eslint-disable-next-line @typescript-eslint/no-namespace */ +declare namespace h { + export import JSX = JSXInternal; + + type Key = string | number | any; + + type ComponentChild = Record | string | number | boolean | null | undefined; + + type ComponentChildren = ComponentChild[] | ComponentChild; + + interface Attributes { + key?: Key; + jsx?: boolean; + } + + interface VHTMLDOMAttributes { + children?: ComponentChildren; + dangerouslySetInnerHTML?: { + __html: string; + }; + } + + type RenderableProps

= P & + Readonly; + + type ComponentType

> = FunctionComponent

; + + interface FunctionComponent

> { + (props: RenderableProps

, context?: any): string | null; + displayName?: string; + defaultProps?: Partial

; + } + interface FunctionalComponent

> + extends FunctionComponent

{} +} + +function h( + type: string, + props: + | (JSXInternal.HTMLAttributes & + JSXInternal.SVGAttributes & + Record) + | null, + ...children: h.ComponentChildren[] +): string; +function h

( + type: h.ComponentType

, + props: (h.Attributes & P) | null, + ...children: h.ComponentChildren[] +): string; + +/** Hyperscript reviver that constructs a sanitized HTML string. */ +function h

(name: string | h.ComponentType

| null, attrs: | (JSXInternal.HTMLAttributes & + JSXInternal.SVGAttributes & + Record) | (h.Attributes & P) +| null, ...children: h.ComponentChildren[]): string { + const stack = children.reverse(); + let s = ''; + attrs = attrs || {}; + + // Sortof component support! + if (typeof name === 'function') { + const props = { + ...attrs, + children: stack.reverse() + } as h.RenderableProps

; + return name(props); + } + + if (name) { + s += '<' + name; + if (attrs) + for (const i in attrs) { + if (attrs[i] !== false && attrs[i] != null && i !== setInnerHTMLAttr) { + s += ` ${DOMAttributeNames[i] ? DOMAttributeNames[i] : esc(i)}="${esc( + attrs[i] + )}"`; + } + } + s += '>'; + } + + if (emptyTags.indexOf(name) === -1) { + if (attrs[setInnerHTMLAttr]) { + s += attrs[setInnerHTMLAttr].__html; + } + else { + while (stack.length) { + const child = stack.pop(); + if (child) { + if ((child as []).pop) { + for (let i = (child as []).length; i--; ) stack.push(child[i]); + } + else { + const resolved = String(child); + s += sanitized[resolved] === true ? resolved : esc(resolved); + } + } + } + } + + s += name ? `` : ''; + } + + sanitized[s] = true; + return s; +} + +export default h; diff --git a/test/vhtml.js b/test/vhtml.tsx similarity index 84% rename from test/vhtml.js rename to test/vhtml.tsx index f88ddf2..b248246 100644 --- a/test/vhtml.js +++ b/test/vhtml.tsx @@ -1,11 +1,9 @@ -import h from '../src/vhtml'; +import h from 'vhtml'; import { expect } from 'chai'; -/** @jsx h */ -/*global describe,it*/ describe('vhtml', () => { it('should stringify html', () => { - let items = ['one', 'two', 'three']; + const items = ['one', 'two', 'three']; expect(

Hi!

@@ -42,7 +40,7 @@ describe('vhtml', () => { it('should not sanitize the "dangerouslySetInnerHTML" attribute, and directly set its `__html` property as innerHTML', () => { expect( -
Injected HTML" }} /> +
Injected HTML' }} /> ).to.equal( `
Injected HTML
` ); @@ -52,16 +50,16 @@ describe('vhtml', () => { expect(
{[['a','b']]} - d + d {['e',['f'],[['g']]]}
).to.equal( - `
abdefg
` + `
abdefg
` ); }); it('should support sortof components', () => { - let items = ['one', 'two']; + const items = ['one', 'two']; const Item = ({ item, index, children }) => (
  • @@ -87,9 +85,10 @@ describe('vhtml', () => { }); it('should support sortof components without args', () => { - let items = ['one', 'two']; + const items = ['one', 'two']; - const Item = () => ( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const Item = ({ children }) => (
  • @@ -99,7 +98,7 @@ describe('vhtml', () => {

    Hi!

      - { items.map( (item, index) => ( + { items.map( (item) => ( This is item {item}! @@ -112,7 +111,7 @@ describe('vhtml', () => { }); it('should support sortof components without args but with children', () => { - let items = ['one', 'two']; + const items = ['one', 'two']; const Item = ({ children }) => (
    • @@ -125,7 +124,7 @@ describe('vhtml', () => {

      Hi!

        - { items.map( (item, index) => ( + { items.map( (item) => ( This is item {item}! @@ -144,7 +143,6 @@ describe('vhtml', () => {
        -
        @@ -162,7 +160,7 @@ describe('vhtml', () => {

      ).to.equal( - `


      ` + `


      ` ); }); @@ -176,7 +174,7 @@ describe('vhtml', () => { it('should support string fragments', () => { expect( - h(null, null, "foo", "bar", "baz") + h(null, null, 'foo', 'bar', 'baz') ).to.equal( 'foobarbaz' ); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..a857c3e --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compileOnSave": false, + "compilerOptions": { + "noEmit": true, + "declaration": true, + "moduleResolution": "node", + "jsx": "react", + "jsxFactory": "h", + "baseUrl": ".", + "paths": { + "vhtml": ["src/vhtml"] + } + } +}