├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── dom-expressions.config.js ├── h └── package.json ├── html └── package.json ├── package-lock.json ├── package.json ├── rollup.config.js ├── src ├── h.ts ├── html.ts ├── index.ts ├── runtime.d.ts └── runtime.js └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | lib/ 3 | dist/ 4 | types/ -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | src/ 2 | types/tsconfig.tsbuildinfo 3 | *.config.js 4 | tsconfig.json -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Ryan Carniato 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 is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | 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 IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # S JSX 2 | 3 | This library is a demonstration of the raw performance of S.js when used with the DOM Expressions runtime. This is an experimental approach used mostly for benchmarking and I'd recommend you checkout the official renderer for S.js, [Surplus](https://github.com/adamhaile/surplus) or [Solid](https://github.com/ryansolid/solid) which are better tested and provide a more comprehensive set of features. 4 | 5 | It accomplishes this with using [Babel Plugin JSX DOM Expressions](https://github.com/ryansolid/babel-plugin-jsx-dom-expressions). It compiles JSX to DOM statements and wraps expressions in functions that can be called by the library of choice. In this case autorun wrap these expressions ensuring the view stays up to date. Unlike Virtual DOM only the changed nodes are affected and the whole tree is not re-rendered over and over. 6 | 7 | To use simply wrap your code in render: 8 | 9 | ```js 10 | import { render } from 's-jsx'; 11 | 12 | render(App, document.body); 13 | ``` 14 | 15 | And include 'babel-plugin-jsx-dom-expressions' in your babelrc, webpack babel loader, or rollup babel plugin. 16 | 17 | ```js 18 | "plugins": [["jsx-dom-expressions", {moduleName: 's-jsx'}]] 19 | ``` 20 | 21 | # Installation 22 | 23 | ```sh 24 | > npm install s-jsx babel-plugin-jsx-dom-expressions 25 | ``` 26 | 27 | Alternatively this library supports Tagged Template Literals or HyperScript for non-precompiled environments by installing the companion library and including variants: 28 | ```js 29 | import { html } from 's-jsx/html'; // or 30 | import { h } from 's-jsx/h'; 31 | ``` 32 | There is a small performance overhead of using these runtimes but the performance is still very impressive. Further documentation available at: [Lit DOM Expressions](https://github.com/ryansolid/lit-dom-expressions) and [Hyper DOM Expressions](https://github.com/ryansolid/hyper-dom-expressions). 33 | -------------------------------------------------------------------------------- /dom-expressions.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | output: 'src/runtime.js', 3 | includeTypes: true, 4 | variables: { 5 | imports: [ `import wrap, {value, sample as ignore} from 's-js'` ], 6 | declarations: { 7 | wrapCondition: `(fn) => { 8 | const s = value(ignore(fn)); 9 | wrap(() => s(fn())) 10 | return s; 11 | }` 12 | }, 13 | includeContext: false, 14 | wrapConditionals: true 15 | } 16 | } -------------------------------------------------------------------------------- /h/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "s-jsx/h", 3 | "main": "../lib/h.js", 4 | "module": "../dist/h.js", 5 | "types": "../types/h.d.ts", 6 | "sideEffects": false 7 | } -------------------------------------------------------------------------------- /html/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "s-jsx/html", 3 | "main": "../lib/html.js", 4 | "module": "../dist/html.js", 5 | "types": "../types/html.d.ts", 6 | "sideEffects": false 7 | } -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "s-jsx", 3 | "version": "0.2.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@babel/code-frame": { 8 | "version": "7.8.0", 9 | "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.0.tgz", 10 | "integrity": "sha512-AN2IR/wCUYsM+PdErq6Bp3RFTXl8W0p9Nmymm7zkpsCmh+r/YYcckaCGpU8Q/mEKmST19kkGRaG42A/jxOWwBA==", 11 | "dev": true, 12 | "requires": { 13 | "@babel/highlight": "^7.8.0" 14 | } 15 | }, 16 | "@babel/core": { 17 | "version": "7.8.0", 18 | "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.8.0.tgz", 19 | "integrity": "sha512-3rqPi/bv/Xfu2YzHvBz4XqMI1fKVwnhntPA1/fjoECrSjrhbOCxlTrbVu5gUtr8zkxW+RpkDOa/HCW93gzS2Dw==", 20 | "dev": true, 21 | "requires": { 22 | "@babel/code-frame": "^7.8.0", 23 | "@babel/generator": "^7.8.0", 24 | "@babel/helpers": "^7.8.0", 25 | "@babel/parser": "^7.8.0", 26 | "@babel/template": "^7.8.0", 27 | "@babel/traverse": "^7.8.0", 28 | "@babel/types": "^7.8.0", 29 | "convert-source-map": "^1.7.0", 30 | "debug": "^4.1.0", 31 | "gensync": "^1.0.0-beta.1", 32 | "json5": "^2.1.0", 33 | "lodash": "^4.17.13", 34 | "resolve": "^1.3.2", 35 | "semver": "^5.4.1", 36 | "source-map": "^0.5.0" 37 | } 38 | }, 39 | "@babel/generator": { 40 | "version": "7.8.0", 41 | "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.8.0.tgz", 42 | "integrity": "sha512-2Lp2e02CV2C7j/H4n4D9YvsvdhPVVg9GDIamr6Tu4tU35mL3mzOrzl1lZ8ZJtysfZXh+y+AGORc2rPS7yHxBUg==", 43 | "dev": true, 44 | "requires": { 45 | "@babel/types": "^7.8.0", 46 | "jsesc": "^2.5.1", 47 | "lodash": "^4.17.13", 48 | "source-map": "^0.5.0" 49 | } 50 | }, 51 | "@babel/helper-create-class-features-plugin": { 52 | "version": "7.8.0", 53 | "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.8.0.tgz", 54 | "integrity": "sha512-ctCvqYBTlwEl2uF4hCxE0cd/sSw71Zfag0jKa39y4HDLh0BQ4PVBX1384Ye8GqrEZ69xgLp9fwPbv3GgIDDF2Q==", 55 | "dev": true, 56 | "requires": { 57 | "@babel/helper-function-name": "^7.8.0", 58 | "@babel/helper-member-expression-to-functions": "^7.8.0", 59 | "@babel/helper-optimise-call-expression": "^7.8.0", 60 | "@babel/helper-plugin-utils": "^7.8.0", 61 | "@babel/helper-replace-supers": "^7.8.0", 62 | "@babel/helper-split-export-declaration": "^7.8.0" 63 | } 64 | }, 65 | "@babel/helper-function-name": { 66 | "version": "7.8.0", 67 | "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.8.0.tgz", 68 | "integrity": "sha512-x9psucuU0Xalw+0Vpr2FYJMLB7/KnPSLZhlkUyOGbYAWRDfmtZBrguYpJYiaNCRV7vGkYjO/gF6/J6yMvdWTDw==", 69 | "dev": true, 70 | "requires": { 71 | "@babel/helper-get-function-arity": "^7.8.0", 72 | "@babel/template": "^7.8.0", 73 | "@babel/types": "^7.8.0" 74 | } 75 | }, 76 | "@babel/helper-get-function-arity": { 77 | "version": "7.8.0", 78 | "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.0.tgz", 79 | "integrity": "sha512-eUP5grliToMapQiTaYS2AAO/WwaCG7cuJztR1v/a1aPzUzUeGt+AaI9OvLATc/AfFkF8SLJ10d5ugGt/AQ9d6w==", 80 | "dev": true, 81 | "requires": { 82 | "@babel/types": "^7.8.0" 83 | } 84 | }, 85 | "@babel/helper-member-expression-to-functions": { 86 | "version": "7.8.0", 87 | "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.8.0.tgz", 88 | "integrity": "sha512-0m1QabGrdXuoxX/g+KOAGndoHwskC70WweqHRQyCsaO67KOEELYh4ECcGw6ZGKjDKa5Y7SW4Qbhw6ly4Fah/jQ==", 89 | "dev": true, 90 | "requires": { 91 | "@babel/types": "^7.8.0" 92 | } 93 | }, 94 | "@babel/helper-module-imports": { 95 | "version": "7.8.0", 96 | "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.8.0.tgz", 97 | "integrity": "sha512-ylY9J6ZxEcjmJaJ4P6aVs/fZdrZVctCGnxxfYXwCnSMapqd544zT8lWK2qI/vBPjE5gS0o2jILnH+AkpsPauEQ==", 98 | "dev": true, 99 | "requires": { 100 | "@babel/types": "^7.8.0" 101 | } 102 | }, 103 | "@babel/helper-optimise-call-expression": { 104 | "version": "7.8.0", 105 | "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.8.0.tgz", 106 | "integrity": "sha512-aiJt1m+K57y0n10fTw+QXcCXzmpkG+o+NoQmAZqlZPstkTE0PZT+Z27QSd/6Gf00nuXJQO4NiJ0/YagSW5kC2A==", 107 | "dev": true, 108 | "requires": { 109 | "@babel/types": "^7.8.0" 110 | } 111 | }, 112 | "@babel/helper-plugin-utils": { 113 | "version": "7.8.0", 114 | "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.8.0.tgz", 115 | "integrity": "sha512-+hAlRGdf8fHQAyNnDBqTHQhwdLURLdrCROoWaEQYiQhk2sV9Rhs+GoFZZfMJExTq9HG8o2NX3uN2G90bFtmFdA==", 116 | "dev": true 117 | }, 118 | "@babel/helper-replace-supers": { 119 | "version": "7.8.0", 120 | "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.8.0.tgz", 121 | "integrity": "sha512-R2CyorW4tcO3YzdkClLpt6MS84G+tPkOi0MmiCn1bvYVnmDpdl9R15XOi3NQW2mhOAEeBnuQ4g1Bh7pT2sX8fg==", 122 | "dev": true, 123 | "requires": { 124 | "@babel/helper-member-expression-to-functions": "^7.8.0", 125 | "@babel/helper-optimise-call-expression": "^7.8.0", 126 | "@babel/traverse": "^7.8.0", 127 | "@babel/types": "^7.8.0" 128 | } 129 | }, 130 | "@babel/helper-split-export-declaration": { 131 | "version": "7.8.0", 132 | "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.0.tgz", 133 | "integrity": "sha512-YhYFhH4T6DlbT6CPtVgLfC1Jp2gbCawU/ml7WJvUpBg01bCrXSzTYMZZXbbIGjq/kHmK8YUATxTppcRGzj31pA==", 134 | "dev": true, 135 | "requires": { 136 | "@babel/types": "^7.8.0" 137 | } 138 | }, 139 | "@babel/helpers": { 140 | "version": "7.8.0", 141 | "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.8.0.tgz", 142 | "integrity": "sha512-srWKpjAFbiut5JoCReZJ098hLqoZ9HufOnKZPggc7j74XaPuQ+9b3RYPV1M/HfjL63lCNd8uI1O487qIWxAFNA==", 143 | "dev": true, 144 | "requires": { 145 | "@babel/template": "^7.8.0", 146 | "@babel/traverse": "^7.8.0", 147 | "@babel/types": "^7.8.0" 148 | } 149 | }, 150 | "@babel/highlight": { 151 | "version": "7.8.0", 152 | "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.8.0.tgz", 153 | "integrity": "sha512-OsdTJbHlPtIk2mmtwXItYrdmalJ8T0zpVzNAbKSkHshuywj7zb29Y09McV/jQsQunc/nEyHiPV2oy9llYMLqxw==", 154 | "dev": true, 155 | "requires": { 156 | "chalk": "^2.0.0", 157 | "esutils": "^2.0.2", 158 | "js-tokens": "^4.0.0" 159 | } 160 | }, 161 | "@babel/parser": { 162 | "version": "7.8.0", 163 | "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.0.tgz", 164 | "integrity": "sha512-VVtsnUYbd1+2A2vOVhm4P2qNXQE8L/W859GpUHfUcdhX8d3pEKThZuIr6fztocWx9HbK+00/CR0tXnhAggJ4CA==", 165 | "dev": true 166 | }, 167 | "@babel/plugin-syntax-typescript": { 168 | "version": "7.8.0", 169 | "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.8.0.tgz", 170 | "integrity": "sha512-LrvVrabb993Ve5fzXsyEkfYCuhpXBwsUFjlvgD8UmXXg3r/8/ceooSdRvjdmtPXXz+lHaqZHZooV1jMWer2qkA==", 171 | "dev": true, 172 | "requires": { 173 | "@babel/helper-plugin-utils": "^7.8.0" 174 | } 175 | }, 176 | "@babel/plugin-transform-typescript": { 177 | "version": "7.8.0", 178 | "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.8.0.tgz", 179 | "integrity": "sha512-RhMZnNWcyvX+rM6mk888MaeoVl5pGfmYP3as709n4+0d15SRedz4r+LPRg2a9s4z+t+DM+gy8uz/rmM3Cb8JBw==", 180 | "dev": true, 181 | "requires": { 182 | "@babel/helper-create-class-features-plugin": "^7.8.0", 183 | "@babel/helper-plugin-utils": "^7.8.0", 184 | "@babel/plugin-syntax-typescript": "^7.8.0" 185 | } 186 | }, 187 | "@babel/preset-typescript": { 188 | "version": "7.8.0", 189 | "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.8.0.tgz", 190 | "integrity": "sha512-mvu4OmrLK6qRPiXlOkE4yOeOszHzk9itwe6aiMN0RL9Bc5uAwAotVTy4kKl17evLMd1WsvWT1O3mZltynuqxXg==", 191 | "dev": true, 192 | "requires": { 193 | "@babel/helper-plugin-utils": "^7.8.0", 194 | "@babel/plugin-transform-typescript": "^7.8.0" 195 | } 196 | }, 197 | "@babel/template": { 198 | "version": "7.8.0", 199 | "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.0.tgz", 200 | "integrity": "sha512-0NNMDsY2t3ltAVVK1WHNiaePo3tXPUeJpCX4I3xSKFoEl852wJHG8mrgHVADf8Lz1y+8al9cF7cSSfzSnFSYiw==", 201 | "dev": true, 202 | "requires": { 203 | "@babel/code-frame": "^7.8.0", 204 | "@babel/parser": "^7.8.0", 205 | "@babel/types": "^7.8.0" 206 | } 207 | }, 208 | "@babel/traverse": { 209 | "version": "7.8.0", 210 | "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.8.0.tgz", 211 | "integrity": "sha512-d/6sPXFLGlJHZO/zWDtgFaKyalCOHLedzxpVJn6el1cw+f2TZa7xZEszeXdOw6EUemqRFBAn106BWBvtSck9Qw==", 212 | "dev": true, 213 | "requires": { 214 | "@babel/code-frame": "^7.8.0", 215 | "@babel/generator": "^7.8.0", 216 | "@babel/helper-function-name": "^7.8.0", 217 | "@babel/helper-split-export-declaration": "^7.8.0", 218 | "@babel/parser": "^7.8.0", 219 | "@babel/types": "^7.8.0", 220 | "debug": "^4.1.0", 221 | "globals": "^11.1.0", 222 | "lodash": "^4.17.13" 223 | } 224 | }, 225 | "@babel/types": { 226 | "version": "7.8.0", 227 | "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.0.tgz", 228 | "integrity": "sha512-1RF84ehyx9HH09dMMwGWl3UTWlVoCPtqqJPjGuC4JzMe1ZIVDJ2DT8mv3cPv/A7veLD6sgR7vi95lJqm+ZayIg==", 229 | "dev": true, 230 | "requires": { 231 | "esutils": "^2.0.2", 232 | "lodash": "^4.17.13", 233 | "to-fast-properties": "^2.0.0" 234 | } 235 | }, 236 | "@types/estree": { 237 | "version": "0.0.42", 238 | "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.42.tgz", 239 | "integrity": "sha512-K1DPVvnBCPxzD+G51/cxVIoc2X8uUVl1zpJeE6iKcgHMj4+tbat5Xu4TjV7v2QSDbIeAfLi2hIk+u2+s0MlpUQ==", 240 | "dev": true 241 | }, 242 | "@types/node": { 243 | "version": "13.1.6", 244 | "resolved": "https://registry.npmjs.org/@types/node/-/node-13.1.6.tgz", 245 | "integrity": "sha512-Jg1F+bmxcpENHP23sVKkNuU3uaxPnsBMW0cLjleiikFKomJQbsn0Cqk2yDvQArqzZN6ABfBkZ0To7pQ8sLdWDg==", 246 | "dev": true 247 | }, 248 | "@types/resolve": { 249 | "version": "0.0.8", 250 | "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-0.0.8.tgz", 251 | "integrity": "sha512-auApPaJf3NPfe18hSoJkp8EbZzer2ISk7o8mCC3M9he/a04+gbMF97NkpD2S8riMGvm4BMRI59/SZQSaLTKpsQ==", 252 | "dev": true, 253 | "requires": { 254 | "@types/node": "*" 255 | } 256 | }, 257 | "acorn": { 258 | "version": "7.1.0", 259 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.0.tgz", 260 | "integrity": "sha512-kL5CuoXA/dgxlBbVrflsflzQ3PAas7RYZB52NOm/6839iVYJgKMJ3cQJD+t2i5+qFa8h3MDpEOJiS64E8JLnSQ==", 261 | "dev": true 262 | }, 263 | "ansi-styles": { 264 | "version": "3.2.1", 265 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", 266 | "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", 267 | "dev": true, 268 | "requires": { 269 | "color-convert": "^1.9.0" 270 | } 271 | }, 272 | "builtin-modules": { 273 | "version": "3.1.0", 274 | "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.1.0.tgz", 275 | "integrity": "sha512-k0KL0aWZuBt2lrxrcASWDfwOLMnodeQjodT/1SxEQAXsHANgo6ZC/VEaSEHCXt7aSTZ4/4H5LKa+tBXmW7Vtvw==", 276 | "dev": true 277 | }, 278 | "chalk": { 279 | "version": "2.4.2", 280 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", 281 | "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", 282 | "dev": true, 283 | "requires": { 284 | "ansi-styles": "^3.2.1", 285 | "escape-string-regexp": "^1.0.5", 286 | "supports-color": "^5.3.0" 287 | } 288 | }, 289 | "color-convert": { 290 | "version": "1.9.3", 291 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", 292 | "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", 293 | "dev": true, 294 | "requires": { 295 | "color-name": "1.1.3" 296 | } 297 | }, 298 | "color-name": { 299 | "version": "1.1.3", 300 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", 301 | "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", 302 | "dev": true 303 | }, 304 | "convert-source-map": { 305 | "version": "1.7.0", 306 | "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", 307 | "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", 308 | "dev": true, 309 | "requires": { 310 | "safe-buffer": "~5.1.1" 311 | } 312 | }, 313 | "debug": { 314 | "version": "4.1.1", 315 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", 316 | "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", 317 | "dev": true, 318 | "requires": { 319 | "ms": "^2.1.1" 320 | } 321 | }, 322 | "dom-expressions": { 323 | "version": "0.15.0", 324 | "resolved": "https://registry.npmjs.org/dom-expressions/-/dom-expressions-0.15.0.tgz", 325 | "integrity": "sha512-cLdwLkjjsWsRB2Qh/OvKWFPKDnUiVP78QzD0DmY3+lvC0zGXhKNe2/cfCANzW3B5a2Xz9wK2r6ix29gXQOla7w==", 326 | "dev": true, 327 | "requires": { 328 | "ejs": "^3.0.1" 329 | } 330 | }, 331 | "ejs": { 332 | "version": "3.0.1", 333 | "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.0.1.tgz", 334 | "integrity": "sha512-cuIMtJwxvzumSAkqaaoGY/L6Fc/t6YvoP9/VIaK0V/CyqKLEQ8sqODmYfy/cjXEdZ9+OOL8TecbJu+1RsofGDw==", 335 | "dev": true 336 | }, 337 | "escape-string-regexp": { 338 | "version": "1.0.5", 339 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 340 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", 341 | "dev": true 342 | }, 343 | "estree-walker": { 344 | "version": "0.6.1", 345 | "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz", 346 | "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==", 347 | "dev": true 348 | }, 349 | "esutils": { 350 | "version": "2.0.3", 351 | "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", 352 | "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", 353 | "dev": true 354 | }, 355 | "gensync": { 356 | "version": "1.0.0-beta.1", 357 | "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.1.tgz", 358 | "integrity": "sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg==", 359 | "dev": true 360 | }, 361 | "globals": { 362 | "version": "11.12.0", 363 | "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", 364 | "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", 365 | "dev": true 366 | }, 367 | "has-flag": { 368 | "version": "3.0.0", 369 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 370 | "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", 371 | "dev": true 372 | }, 373 | "hyper-dom-expressions": { 374 | "version": "0.15.0", 375 | "resolved": "https://registry.npmjs.org/hyper-dom-expressions/-/hyper-dom-expressions-0.15.0.tgz", 376 | "integrity": "sha512-Ju5uNfMgx6yQLMaXcJl8qIcOaxbCFC9VZ7Y9E0D99/52QZPe+cPjthKmus9hocUp/2FYSzngE+xcw7hWgCgF/g==", 377 | "dev": true 378 | }, 379 | "is-module": { 380 | "version": "1.0.0", 381 | "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", 382 | "integrity": "sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE=", 383 | "dev": true 384 | }, 385 | "js-tokens": { 386 | "version": "4.0.0", 387 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", 388 | "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", 389 | "dev": true 390 | }, 391 | "jsesc": { 392 | "version": "2.5.2", 393 | "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", 394 | "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", 395 | "dev": true 396 | }, 397 | "json5": { 398 | "version": "2.1.1", 399 | "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.1.tgz", 400 | "integrity": "sha512-l+3HXD0GEI3huGq1njuqtzYK8OYJyXMkOLtQ53pjWh89tvWS2h6l+1zMkYWqlb57+SiQodKZyvMEFb2X+KrFhQ==", 401 | "dev": true, 402 | "requires": { 403 | "minimist": "^1.2.0" 404 | } 405 | }, 406 | "lit-dom-expressions": { 407 | "version": "0.15.0", 408 | "resolved": "https://registry.npmjs.org/lit-dom-expressions/-/lit-dom-expressions-0.15.0.tgz", 409 | "integrity": "sha512-vbu8bfr0VIkDcSUzx5I0OTsuXRDeeIpWpVGQzN2zZsbh+w+hmgyeQfLzABLICl5+1u1+mtigwPOOEMsG+EcOBQ==", 410 | "dev": true 411 | }, 412 | "lodash": { 413 | "version": "4.17.15", 414 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", 415 | "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", 416 | "dev": true 417 | }, 418 | "minimist": { 419 | "version": "1.2.0", 420 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", 421 | "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", 422 | "dev": true 423 | }, 424 | "ms": { 425 | "version": "2.1.2", 426 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 427 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", 428 | "dev": true 429 | }, 430 | "ncp": { 431 | "version": "2.0.0", 432 | "resolved": "https://registry.npmjs.org/ncp/-/ncp-2.0.0.tgz", 433 | "integrity": "sha1-GVoh1sRuNh0vsSgbo4uR6d9727M=", 434 | "dev": true 435 | }, 436 | "path-parse": { 437 | "version": "1.0.6", 438 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", 439 | "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", 440 | "dev": true 441 | }, 442 | "resolve": { 443 | "version": "1.14.2", 444 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.14.2.tgz", 445 | "integrity": "sha512-EjlOBLBO1kxsUxsKjLt7TAECyKW6fOh1VRkykQkKGzcBbjjPIxBqGh0jf7GJ3k/f5mxMqW3htMD3WdTUVtW8HQ==", 446 | "dev": true, 447 | "requires": { 448 | "path-parse": "^1.0.6" 449 | } 450 | }, 451 | "rollup": { 452 | "version": "1.29.0", 453 | "resolved": "https://registry.npmjs.org/rollup/-/rollup-1.29.0.tgz", 454 | "integrity": "sha512-V63Iz0dSdI5qPPN5HmCN6OBRzBFhMqNWcvwgq863JtSCTU6Vdvqq6S2fYle/dSCyoPrBkIP3EIr1RVs3HTRqqg==", 455 | "dev": true, 456 | "requires": { 457 | "@types/estree": "*", 458 | "@types/node": "*", 459 | "acorn": "^7.1.0" 460 | } 461 | }, 462 | "rollup-plugin-babel": { 463 | "version": "4.3.3", 464 | "resolved": "https://registry.npmjs.org/rollup-plugin-babel/-/rollup-plugin-babel-4.3.3.tgz", 465 | "integrity": "sha512-tKzWOCmIJD/6aKNz0H1GMM+lW1q9KyFubbWzGiOG540zxPPifnEAHTZwjo0g991Y+DyOZcLqBgqOdqazYE5fkw==", 466 | "dev": true, 467 | "requires": { 468 | "@babel/helper-module-imports": "^7.0.0", 469 | "rollup-pluginutils": "^2.8.1" 470 | } 471 | }, 472 | "rollup-plugin-node-resolve": { 473 | "version": "5.2.0", 474 | "resolved": "https://registry.npmjs.org/rollup-plugin-node-resolve/-/rollup-plugin-node-resolve-5.2.0.tgz", 475 | "integrity": "sha512-jUlyaDXts7TW2CqQ4GaO5VJ4PwwaV8VUGA7+km3n6k6xtOEacf61u0VXwN80phY/evMcaS+9eIeJ9MOyDxt5Zw==", 476 | "dev": true, 477 | "requires": { 478 | "@types/resolve": "0.0.8", 479 | "builtin-modules": "^3.1.0", 480 | "is-module": "^1.0.0", 481 | "resolve": "^1.11.1", 482 | "rollup-pluginutils": "^2.8.1" 483 | } 484 | }, 485 | "rollup-pluginutils": { 486 | "version": "2.8.2", 487 | "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz", 488 | "integrity": "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==", 489 | "dev": true, 490 | "requires": { 491 | "estree-walker": "^0.6.1" 492 | } 493 | }, 494 | "s-js": { 495 | "version": "0.4.9", 496 | "resolved": "https://registry.npmjs.org/s-js/-/s-js-0.4.9.tgz", 497 | "integrity": "sha512-RtpOm+cM6O0sHg6IA70wH+UC3FZcND+rccBZpBAHzlUgNO2Bm5BN+FnM8+OBxzXdwpKWFwX11JGF0MFRkhSoIQ==" 498 | }, 499 | "safe-buffer": { 500 | "version": "5.1.2", 501 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 502 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", 503 | "dev": true 504 | }, 505 | "semver": { 506 | "version": "5.7.1", 507 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", 508 | "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", 509 | "dev": true 510 | }, 511 | "source-map": { 512 | "version": "0.5.7", 513 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", 514 | "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", 515 | "dev": true 516 | }, 517 | "supports-color": { 518 | "version": "5.5.0", 519 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", 520 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", 521 | "dev": true, 522 | "requires": { 523 | "has-flag": "^3.0.0" 524 | } 525 | }, 526 | "to-fast-properties": { 527 | "version": "2.0.0", 528 | "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", 529 | "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", 530 | "dev": true 531 | }, 532 | "typescript": { 533 | "version": "3.7.4", 534 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.7.4.tgz", 535 | "integrity": "sha512-A25xv5XCtarLwXpcDNZzCGvW2D1S3/bACratYBx2sax8PefsFhlYmkQicKHvpYflFS8if4zne5zT5kpJ7pzuvw==", 536 | "dev": true 537 | } 538 | } 539 | } 540 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "s-jsx", 3 | "description": "An alternative JSX renderer for S.js", 4 | "version": "0.2.0", 5 | "author": "Ryan Carniato", 6 | "license": "MIT", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/ryansolid/s-jsx" 10 | }, 11 | "module": "dist/index.js", 12 | "main": "lib/index.js", 13 | "types": "types/index.d.ts", 14 | "scripts": { 15 | "build": "dom-expressions && rollup -c && tsc", 16 | "prepublishOnly": "npm run build" 17 | }, 18 | "devDependencies": { 19 | "@babel/core": "7.8.0", 20 | "@babel/preset-typescript": "7.8.0", 21 | "dom-expressions": "0.15.0", 22 | "hyper-dom-expressions": "~0.15.0", 23 | "lit-dom-expressions": "~0.15.0", 24 | "ncp": "2.0.0", 25 | "rollup": "^1.29.0", 26 | "rollup-plugin-babel": "4.3.3", 27 | "rollup-plugin-node-resolve": "5.2.0", 28 | "typescript": "3.7.4" 29 | }, 30 | "dependencies": { 31 | "s-js": "~0.4.9" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import babel from 'rollup-plugin-babel'; 2 | import nodeResolve from 'rollup-plugin-node-resolve'; 3 | 4 | const plugins = [ 5 | nodeResolve({ 6 | extensions: ['.js', '.ts'] 7 | }), 8 | babel({ 9 | extensions: ['.js', '.ts'], 10 | presets: ["@babel/preset-typescript"], 11 | exclude: 'node_modules/**', 12 | retainLines: true 13 | }) 14 | ]; 15 | 16 | export default [{ 17 | input: 'src/index.ts', 18 | output: [{ 19 | format: 'cjs', 20 | file: 'lib/index.js' 21 | }, { 22 | format: 'es', 23 | file: 'dist/index.js' 24 | }], 25 | external: ['s-js'], 26 | plugins 27 | }, { 28 | input: 'src/html.ts', 29 | output: [{ 30 | format: 'cjs', 31 | file: 'lib/html.js' 32 | }, { 33 | format: 'es', 34 | file: 'dist/html.js' 35 | }], 36 | external: ['./index', 'lit-dom-expressions'], 37 | plugins 38 | }, { 39 | input: 'src/h.ts', 40 | output: [{ 41 | format: 'cjs', 42 | file: 'lib/h.js' 43 | }, { 44 | format: 'es', 45 | file: 'dist/h.js' 46 | }], 47 | external: ['./index', 'hyper-dom-expressions'], 48 | plugins 49 | }]; -------------------------------------------------------------------------------- /src/h.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import { createHyperScript } from 'hyper-dom-expressions'; 3 | import * as r from './index'; 4 | 5 | export const h = createHyperScript(r); -------------------------------------------------------------------------------- /src/html.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import { createHTML } from 'lit-dom-expressions'; 3 | import * as r from './index'; 4 | 5 | export const html = createHTML(r); -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import S from 's-js'; 2 | export * from './runtime' 3 | 4 | import { insert, hydrate as hydr, renderToString as rTS } from "./runtime"; 5 | 6 | type MountableElement = Element | Document | ShadowRoot | DocumentFragment; 7 | 8 | export function render(code: () => any, mount: MountableElement): () => void { 9 | let dispose: () => void; 10 | S.root(disposer => { 11 | dispose = disposer; 12 | insert(mount, code()); 13 | }); 14 | return dispose!; 15 | } 16 | 17 | export function renderToString(code: () => any): Promise { 18 | return S.root(dispose => { 19 | const p = rTS(code); 20 | dispose(); 21 | return p; 22 | }); 23 | } 24 | 25 | export function hydrate( 26 | code: () => any, 27 | element: MountableElement 28 | ): () => void { 29 | let disposer: () => void; 30 | hydr(() => { 31 | disposer = render(code, element); 32 | }, element); 33 | return disposer!; 34 | } 35 | -------------------------------------------------------------------------------- /src/runtime.d.ts: -------------------------------------------------------------------------------- 1 | export function template(html: string, isSVG?: boolean): Element; 2 | export function wrap(fn: (prev?: T) => T): any; 3 | export function wrapCondition(fn: () => any): () => any; 4 | export function insert( 5 | parent: Element | Document | ShadowRoot | DocumentFragment, 6 | accessor: any, 7 | init?: any, 8 | marker?: Node 9 | ): any; 10 | export function createComponent( 11 | Comp: (props: any) => any, 12 | props: any, 13 | dynamicKeys?: string[] 14 | ): any; 15 | export function delegateEvents(eventNames: string[]): void; 16 | export function clearDelegatedEvents(): void; 17 | export function spread( 18 | node: Element, 19 | accessor: any, 20 | isSVG?: Boolean, 21 | skipChildren?: Boolean 22 | ): void; 23 | export function classList( 24 | node: Element, 25 | value: { [k: string]: boolean }, 26 | prev?: { [k: string]: boolean } 27 | ): void; 28 | export function currentContext(): any; 29 | export function renderToString( 30 | fn: (done?: () => void) => any, 31 | options?: { 32 | timeoutMs?: number; 33 | } 34 | ): Promise; 35 | export function hydrate( 36 | fn: () => unknown, 37 | node: Element | Document | ShadowRoot | DocumentFragment 38 | ): void; 39 | export function getNextElement( 40 | template: HTMLTemplateElement, 41 | isSSR: boolean 42 | ): Node; 43 | export function getNextMarker(start: Node): [Node, Array]; 44 | -------------------------------------------------------------------------------- /src/runtime.js: -------------------------------------------------------------------------------- 1 | import { Attributes, SVGAttributes, NonComposedEvents } from 'dom-expressions'; 2 | import wrap, {value, sample as ignore} from 's-js'; 3 | 4 | const wrapCondition = (fn) => { 5 | const s = value(ignore(fn)); 6 | wrap(() => s(fn())) 7 | return s; 8 | }; 9 | 10 | const eventRegistry = new Set(); 11 | const config = {}; 12 | 13 | export { wrap, wrapCondition }; 14 | 15 | export function template(html, isSVG) { 16 | const t = document.createElement('template'); 17 | t.innerHTML = html; 18 | if (t.innerHTML !== html) throw new Error(`Template html does not match input:\n${t.innerHTML}\n${html}`); 19 | let node = t.content.firstChild; 20 | if (isSVG) node = node.firstChild; 21 | return node; 22 | } 23 | 24 | export function createComponent(Comp, props, dynamicKeys) { 25 | if (dynamicKeys) { 26 | for (let i = 0; i < dynamicKeys.length; i++) dynamicProp(props, dynamicKeys[i]); 27 | } 28 | 29 | return ignore(() => Comp(props)); 30 | } 31 | 32 | export function delegateEvents(eventNames) { 33 | for (let i = 0, l = eventNames.length; i < l; i++) { 34 | const name = eventNames[i]; 35 | if (!eventRegistry.has(name)) { 36 | eventRegistry.add(name); 37 | document.addEventListener(name, eventHandler); 38 | } 39 | } 40 | } 41 | 42 | export function clearDelegatedEvents() { 43 | for (let name of eventRegistry.keys()) document.removeEventListener(name, eventHandler); 44 | eventRegistry.clear(); 45 | } 46 | 47 | export function classList(node, value, prev) { 48 | const classKeys = Object.keys(value); 49 | for (let i = 0, len = classKeys.length; i < len; i++) { 50 | const key = classKeys[i], 51 | classValue = value[key], 52 | classNames = key.split(/\s+/); 53 | if (prev && prev[key] === classValue) continue; 54 | for (let j = 0, nameLen = classNames.length; j < nameLen; j++) 55 | node.classList.toggle(classNames[j], classValue); 56 | } 57 | } 58 | 59 | export function spread(node, accessor, isSVG, skipChildren) { 60 | if (typeof accessor === 'function') { 61 | wrap(current => spreadExpression(node, accessor(), current, isSVG, skipChildren)); 62 | } else spreadExpression(node, accessor, undefined, isSVG, skipChildren); 63 | } 64 | 65 | export function insert(parent, accessor, marker, initial) { 66 | if (marker !== undefined && !initial) initial = []; 67 | if (typeof accessor === 'function') 68 | wrap((current = initial) => insertExpression(parent, accessor(), current, marker)); 69 | else if (Array.isArray(accessor) && checkDynamicArray(accessor)) { 70 | wrap((current = initial) => insertExpression(parent, accessor, current, marker)); 71 | } else { 72 | return insertExpression(parent, accessor, initial, marker); 73 | } 74 | } 75 | 76 | // SSR 77 | export function renderToString(code, options = {}) { 78 | options = { timeoutMs: 10000, ...options } 79 | config.hydrate = { id: '', count: 0 }; 80 | const container = document.createElement("div"); 81 | return new Promise(resolve => { 82 | setTimeout(() => resolve(container.innerHTML), options.timeoutMs); 83 | if (!code.length) { 84 | insert(container, code()); 85 | resolve(container.innerHTML); 86 | } else insert(container, code(() => resolve(container.innerHTML))); 87 | }); 88 | } 89 | 90 | export function hydrate(code, root) { 91 | config.hydrate = { id: '', count: 0, registry: new Map() }; 92 | const templates = root.querySelectorAll(`*[_hk]`); 93 | for (let i = 0; i < templates.length; i++) { 94 | const node = templates[i]; 95 | config.hydrate.registry.set(node.getAttribute('_hk'), node); 96 | } 97 | code(); 98 | delete config.hydrate; 99 | } 100 | 101 | export function getNextElement(template, isSSR) { 102 | const hydrate = config.hydrate; 103 | let node, key; 104 | if (!hydrate || !hydrate.registry || !(node = hydrate.registry.get(key = `${hydrate.id}:${hydrate.count++}`))) { 105 | const el = template.cloneNode(true); 106 | if (isSSR && hydrate) 107 | el.setAttribute('_hk', `${hydrate.id}:${hydrate.count++}`); 108 | return el; 109 | } 110 | if (window && window._$HYDRATION) window._$HYDRATION.completed.add(key); 111 | return node; 112 | } 113 | 114 | export function getNextMarker(start) { 115 | let end = start, 116 | count = 0, 117 | current = []; 118 | if (config.hydrate && config.hydrate.registry) { 119 | while (end) { 120 | if (end.nodeType === 8) { 121 | const v = end.nodeValue; 122 | if (v === "#") count++; 123 | else if (v === "/") { 124 | if (count === 0) return [end, current]; 125 | count--; 126 | } 127 | } 128 | current.push(end); 129 | end = end.nextSibling; 130 | } 131 | } 132 | return [end, current]; 133 | } 134 | 135 | export function runHydrationEvents(id) { 136 | if (window && window._$HYDRATION) { 137 | const { completed, events } = window._$HYDRATION; 138 | while (events.length) { 139 | const [id, e] = events[0]; 140 | if (!completed.has(id)) return; 141 | eventHandler(e); 142 | events.shift(); 143 | } 144 | } 145 | } 146 | 147 | // Internal Functions 148 | function dynamicProp(props, key) { 149 | const src = props[key]; 150 | Object.defineProperty(props, key, { 151 | get() { return src(); }, 152 | enumerable: true 153 | }); 154 | } 155 | 156 | function lookup(el) { 157 | return el && (el.model || lookup(el.host || el.parentNode)); 158 | } 159 | 160 | function eventHandler(e) { 161 | const key = `__${e.type}`; 162 | let node = (e.composedPath && e.composedPath()[0]) || e.target; 163 | // reverse Shadow DOM retargetting 164 | if (e.target !== node) { 165 | Object.defineProperty(e, 'target', { 166 | configurable: true, 167 | value: node 168 | }) 169 | } 170 | 171 | // simulate currentTarget 172 | Object.defineProperty(e, 'currentTarget', { 173 | configurable: true, 174 | get() { return node; } 175 | }) 176 | 177 | while (node !== null) { 178 | const handler = node[key]; 179 | if (handler) { 180 | const model = handler.length > 1 ? lookup(node): undefined; 181 | handler(e, model); 182 | if (e.cancelBubble) return; 183 | } 184 | node = (node.host && node.host instanceof Node) ? node.host : node.parentNode; 185 | } 186 | } 187 | 188 | function spreadExpression(node, props, prevProps = {}, isSVG, skipChildren) { 189 | let info; 190 | if (!skipChildren && "children" in props) { 191 | wrap(() => 192 | (prevProps.children = insertExpression( 193 | node, 194 | props.children, 195 | prevProps.children 196 | )) 197 | ); 198 | } 199 | wrap(() => { 200 | for (const prop in props) { 201 | if (prop === "children") continue; 202 | const value = props[prop]; 203 | if (value === prevProps[prop]) continue; 204 | if (prop === "style") { 205 | Object.assign(node.style, value); 206 | } else if (prop === "classList") { 207 | classList(node, value, prevProps[prop]); 208 | // really only for forwarding from Components, can't forward normal ref 209 | } else if (prop === "ref" || prop === "forwardRef") { 210 | value(node); 211 | } else if (prop.slice(0, 2) === "on") { 212 | const lc = prop.toLowerCase(); 213 | if (lc !== prop && !NonComposedEvents.has(lc.slice(2))) { 214 | const name = lc.slice(2); 215 | node[`__${name}`] = value; 216 | delegateEvents([name]); 217 | } else node[lc] = value; 218 | } else if (prop === "events") { 219 | for (const eventName in value) 220 | node.addEventListener(eventName, value[eventName]); 221 | } else if ((info = Attributes[prop])) { 222 | if (info.type === "attribute") { 223 | node.setAttribute(prop, value); 224 | } else node[info.alias] = value; 225 | } else if (isSVG) { 226 | if ((info = SVGAttributes[prop])) { 227 | if (info.alias) node.setAttribute(info.alias, value); 228 | else node.setAttribute(prop, value); 229 | } else 230 | node.setAttribute( 231 | prop.replace(/([A-Z])/g, g => `-${g[0].toLowerCase()}`), 232 | value 233 | ); 234 | } else node[prop] = value; 235 | prevProps[prop] = value; 236 | } 237 | }); 238 | return prevProps; 239 | } 240 | 241 | function normalizeIncomingArray(normalized, array) { 242 | for (let i = 0, len = array.length; i < len; i++) { 243 | let item = array[i], t; 244 | if (item instanceof Node) { 245 | normalized.push(item); 246 | } else if (item == null || item === true || item === false) { // matches null, undefined, true or false 247 | // skip 248 | } else if (Array.isArray(item)) { 249 | normalizeIncomingArray(normalized, item); 250 | } else if ((t = typeof item) === 'string') { 251 | normalized.push(document.createTextNode(item)); 252 | } else if (t === 'function') { 253 | const idx = item(); 254 | normalizeIncomingArray(normalized, Array.isArray(idx) ? idx : [idx]); 255 | } else normalized.push(document.createTextNode(item.toString())); 256 | } 257 | return normalized; 258 | } 259 | 260 | function appendNodes(parent, array, marker) { 261 | for (let i = 0, len = array.length; i < len; i++) parent.insertBefore(array[i], marker); 262 | } 263 | 264 | function cleanChildren(parent, current, marker, replacement) { 265 | if (marker === undefined) return parent.textContent = ''; 266 | const node = (replacement || document.createTextNode('')); 267 | if (current.length) { 268 | node !== current[0] && parent.replaceChild(node, current[0]); 269 | for (let i = current.length - 1; i > 0; i--) parent.removeChild(current[i]); 270 | } else parent.insertBefore(node, marker); 271 | return [node]; 272 | } 273 | 274 | function checkDynamicArray(array) { 275 | for (let i = 0, len = array.length; i < len; i++) { 276 | const item = array[i]; 277 | if (Array.isArray(item) && checkDynamicArray(item) || typeof item === 'function') return true; 278 | } 279 | return false; 280 | } 281 | 282 | function insertExpression(parent, value, current, marker) { 283 | 284 | if (value === current) return current; 285 | const t = typeof value, 286 | multi = marker !== undefined; 287 | parent = (multi && current[0] && current[0].parentNode) || parent; 288 | 289 | if (t === 'string' || t === 'number') { 290 | if (t === 'number') value = value.toString(); 291 | if (multi) { 292 | let node = current[0]; 293 | if (node && node.nodeType === 3) { 294 | node.data = value; 295 | } else node = document.createTextNode(value); 296 | current = cleanChildren(parent, current, marker, node) 297 | } else { 298 | if (current !== '' && typeof current === 'string') { 299 | current = parent.firstChild.data = value; 300 | } else current = parent.textContent = value; 301 | } 302 | } else if (value == null || t === 'boolean') { 303 | if (config.hydrate && config.hydrate.registry) return current; 304 | current = cleanChildren(parent, current, marker); 305 | } else if (t === 'function') { 306 | wrap(() => current = insertExpression(parent, value(), current, marker)); 307 | 308 | } else if (Array.isArray(value)) { 309 | const array = normalizeIncomingArray([], value); 310 | if (config.hydrate && config.hydrate.registry) return current; 311 | if (array.length === 0) { 312 | current = cleanChildren(parent, current, marker); 313 | if (multi) return current; 314 | } else { 315 | if (Array.isArray(current)) { 316 | if (current.length === 0) { 317 | appendNodes(parent, array, marker); 318 | } else reconcileArrays(parent, current, array); 319 | } else if (current == null || current === '') { 320 | appendNodes(parent, array); 321 | } else { 322 | reconcileArrays(parent, (multi && current) || [parent.firstChild], array); 323 | } 324 | } 325 | current = array; 326 | } else if (value instanceof Node) { 327 | if (Array.isArray(current)) { 328 | if (multi) return current = cleanChildren(parent, current, marker, value); 329 | cleanChildren(parent, current, null, value); 330 | } else if (current == null || current === '') { 331 | parent.appendChild(value); 332 | } else parent.replaceChild(value, parent.firstChild); 333 | current = value; 334 | } 335 | 336 | return current; 337 | } 338 | 339 | // Picked from 340 | // https://github.com/adamhaile/surplus/blob/master/src/runtime/content.ts#L368 341 | var NOMATCH = -1 342 | 343 | // reconcile the content of parent from ns to us 344 | // see ivi's excellent writeup of diffing arrays in a vdom library: 345 | // https://github.com/ivijs/ivi/blob/2c81ead934b9128e092cc2a5ef2d3cabc73cb5dd/packages/ivi/src/vdom/implementation.ts#L1187 346 | // this code isn't identical, since we're diffing real dom nodes to nodes-or-strings, 347 | // but the core methodology of trimming ends and reversals, matching nodes, then using 348 | // the longest increasing subsequence to minimize DOM ops is inspired by ivi. 349 | function reconcileArrays(parent, ns, us) { 350 | var ulen = us.length, 351 | // n = nodes, u = updates 352 | // ranges defined by min and max indices 353 | nmin = 0, 354 | nmax = ns.length - 1, 355 | umin = 0, 356 | umax = ulen - 1, 357 | // start nodes of ranges 358 | n = ns[nmin], 359 | u = us[umin], 360 | // end nodes of ranges 361 | nx = ns[nmax], 362 | ux = us[umax], 363 | // node, if any, just after ux, used for doing .insertBefore() to put nodes at end 364 | ul = nx.nextSibling, 365 | i, j, k, 366 | loop = true; 367 | 368 | // scan over common prefixes, suffixes, and simple reversals 369 | fixes: while (loop) { 370 | loop = false; 371 | 372 | // common prefix, u === n 373 | while (u === n) { 374 | umin++; 375 | nmin++; 376 | if (umin > umax || nmin > nmax) break fixes; 377 | u = us[umin]; 378 | n = ns[nmin]; 379 | } 380 | 381 | // common suffix, ux === nx 382 | while (ux === nx) { 383 | ul = nx; 384 | umax--; 385 | nmax--; 386 | if (umin > umax || nmin > nmax) break fixes; 387 | ux = us[umax]; 388 | nx = ns[nmax]; 389 | } 390 | 391 | // reversal u === nx, have to swap node forward 392 | while (u === nx) { 393 | loop = true; 394 | parent.insertBefore(nx, n); 395 | umin++; 396 | nmax--; 397 | if (umin > umax || nmin > nmax) break fixes; 398 | u = us[umin]; 399 | nx = ns[nmax]; 400 | } 401 | 402 | // reversal ux === n, have to swap node back 403 | while (ux === n) { 404 | loop = true; 405 | if (ul === null) parent.appendChild(n); 406 | else parent.insertBefore(n, ul); 407 | ul = n; 408 | umax--; 409 | nmin++; 410 | if (umin > umax || nmin > nmax) break fixes; 411 | ux = us[umax]; 412 | n = ns[nmin]; 413 | } 414 | } 415 | 416 | // if that covered all updates, just need to remove any remaining nodes and we're done 417 | if (umin > umax) { 418 | // remove any remaining nodes 419 | while (nmin <= nmax) { 420 | parent.removeChild(ns[nmax]); 421 | nmax--; 422 | } 423 | return; 424 | } 425 | 426 | // if that covered all current nodes, just need to insert any remaining updates and we're done 427 | if (nmin > nmax) { 428 | // insert any remaining nodes 429 | while (umin <= umax) { 430 | parent.insertBefore(us[umin], ul); 431 | umin++; 432 | } 433 | return; 434 | } 435 | 436 | // Positions for reusing nodes from current DOM state 437 | const P = new Array(umax - umin + 1), 438 | I = new Map(); 439 | for(let i = umin; i <= umax; i++) { 440 | P[i] = NOMATCH; 441 | I.set(us[i], i); 442 | } 443 | 444 | let reusingNodes = umin + us.length - 1 - umax, 445 | toRemove = [] 446 | 447 | for(let i = nmin; i <= nmax; i++) { 448 | if (I.has(ns[i])) { 449 | P[I.get(ns[i])] = i 450 | reusingNodes++ 451 | } else toRemove.push(i) 452 | } 453 | 454 | // Fast path for full replace 455 | if (reusingNodes === 0) { 456 | if (n !== parent.firstChild || nx !== parent.lastChild) { 457 | for (i = nmin; i <= nmax; i++) parent.removeChild(ns[i]); 458 | while (umin <= umax) { 459 | parent.insertBefore(us[umin], ul); 460 | umin++ 461 | } 462 | return; 463 | } 464 | // no nodes preserved, use fast clear and append 465 | parent.textContent = ''; 466 | while (umin <= umax) { 467 | parent.appendChild(us[umin]); 468 | umin++; 469 | } 470 | return; 471 | } 472 | 473 | // find longest common sequence between ns and us, represented as the indices 474 | // of the longest increasing subsequence in src 475 | var lcs = longestPositiveIncreasingSubsequence(P, umin), 476 | nodes = [], 477 | tmp = ns[nmin], lisIdx = lcs.length - 1, tmpB;; 478 | 479 | // Collect nodes to work with them 480 | for(let i = nmin; i <= nmax; i++) { 481 | nodes[i] = tmp; 482 | tmp = tmp.nextSibling; 483 | } 484 | 485 | for(let i = 0; i < toRemove.length; i++) parent.removeChild(nodes[toRemove[i]]) 486 | 487 | for(let i = umax; i >= umin; i--) { 488 | if(lcs[lisIdx] === i) { 489 | ul = nodes[P[lcs[lisIdx]]] 490 | lisIdx-- 491 | } else { 492 | tmpB = (P[i] === NOMATCH) ? us[i] : nodes[P[i]]; 493 | parent.insertBefore(tmpB, ul) 494 | ul = tmpB 495 | } 496 | } 497 | } 498 | 499 | // return an array of the indices of ns that comprise the longest increasing subsequence within ns 500 | function longestPositiveIncreasingSubsequence(ns, newStart) { 501 | let seq = [], 502 | is = [], 503 | l = -1, 504 | pre = new Array(ns.length); 505 | 506 | for (let i = newStart, len = ns.length; i < len; i++) { 507 | let n = ns[i]; 508 | if (n < 0) continue; 509 | let j = findGreatestIndexLEQ(seq, n); 510 | if (j !== -1) pre[i] = is[j]; 511 | if (j === l) { 512 | l++; 513 | seq[l] = n; 514 | is[l] = i; 515 | } else if (n < seq[j + 1]) { 516 | seq[j + 1] = n; 517 | is[j + 1] = i; 518 | } 519 | } 520 | 521 | for (let i = is[l]; l >= 0; i = pre[i], l--) { 522 | seq[l] = i; 523 | } 524 | 525 | return seq; 526 | } 527 | 528 | function findGreatestIndexLEQ(seq, n) { 529 | // invariant: lo is guaranteed to be index of a value <= n, hi to be > 530 | // therefore, they actually start out of range: (-1, last + 1) 531 | var lo = -1, 532 | hi = seq.length; 533 | 534 | // fast path for simple increasing sequences 535 | if (hi > 0 && seq[hi - 1] <= n) return hi - 1; 536 | 537 | while (hi - lo > 1) { 538 | var mid = Math.floor((lo + hi) / 2); 539 | if (seq[mid] > n) { 540 | hi = mid; 541 | } else { 542 | lo = mid; 543 | } 544 | } 545 | 546 | return lo; 547 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "types", 4 | "emitDeclarationOnly": true, 5 | "declaration": true, 6 | "incremental": true, 7 | "target": "esnext", 8 | "moduleResolution": "node", 9 | "strict": true 10 | } 11 | } --------------------------------------------------------------------------------