From 6337bf5bb9cad44a6399cb6ab1b45aa47525e73a Mon Sep 17 00:00:00 2001 From: Brian Kim Date: Thu, 2 Jul 2020 19:54:34 -0400 Subject: [PATCH 01/26] update dependencies --- website/package.json | 42 ++-- website/webpack.tsx | 5 +- website/yarn.lock | 489 +++++++++++++++++++++++++------------------ 3 files changed, 306 insertions(+), 230 deletions(-) diff --git a/website/package.json b/website/package.json index 73929069d..3f81c6a41 100644 --- a/website/package.json +++ b/website/package.json @@ -6,17 +6,17 @@ "deploy": "gh-pages -d dist" }, "dependencies": { - "@bikeshaving/crank": "*", + "@bikeshaving/crank": "0.2.1", "@repeaterjs/repeater": "^3.0.1", - "@types/fs-extra": "^8.0.1", - "@types/webpack": "^4.41.6", + "@types/fs-extra": "^9.0.1", + "@types/webpack": "^4.41.18", "core-js": "^3.6.4", - "css-loader": "^3.4.2", - "file-loader": "^5.1.0", - "front-matter": "^3.1.0", - "fs-extra": "^8.1.0", - "html-loader": "^0.5.5", - "marked": "^0.8.0", + "css-loader": "^3.6.0", + "file-loader": "^6.0.0", + "front-matter": "^4.0.2", + "fs-extra": "^9.0.1", + "html-loader": "^1.1.0", + "marked": "^1.1.0", "mini-css-extract-plugin": "^0.9.0", "normalize.css": "^8.0.1", "postcss-loader": "^3.0.0", @@ -24,24 +24,24 @@ "postcss-preset-env": "^6.7.0", "prismjs": "^1.19.0", "regenerator-runtime": "^0.13.3", - "style-loader": "^1.1.3", + "style-loader": "^1.2.1", "truncate-html": "^1.0.3", - "ts-loader": "^6.2.1", - "url-loader": "^3.0.0", - "webpack": "^4.41.6" + "ts-loader": "^7.0.5", + "url-loader": "^4.1.0", + "webpack": "^4.43.0" }, "devDependencies": { - "@babel/core": "^7.8.4", + "@babel/core": "^7.10.4", "@types/babel__standalone": "^7.1.0", - "@types/codemirror": "^0.0.84", - "@types/marked": "^0.7.2", + "@types/codemirror": "^0.0.96", + "@types/marked": "^1.1.0", "@types/mini-css-extract-plugin": "^0.9.1", - "@types/prismjs": "^1.16.0", - "gh-pages": "^2.2.0", + "@types/prismjs": "^1.16.1", + "gh-pages": "^3.1.0", "gh-pages-deploy": "^0.5.1", - "nodemon": "^2.0.2", - "ts-node": "^8.6.2", - "typescript": "^3.7.5" + "nodemon": "^2.0.4", + "ts-node": "^8.10.2", + "typescript": "^3.9.6" }, "postcss": {} } diff --git a/website/webpack.tsx b/website/webpack.tsx index 3422ebe08..b92d7c547 100644 --- a/website/webpack.tsx +++ b/website/webpack.tsx @@ -12,7 +12,8 @@ import postcssNested from "postcss-nested"; const config: webpack.Configuration = { mode: "development", - plugins: [new MiniCssExtractPlugin({filename: "[name].css"})], + // WHY TYPESCRIPT + plugins: [new MiniCssExtractPlugin({filename: "[name].css"}) as any], module: { rules: [ { @@ -190,7 +191,7 @@ export class Storage { } } -const StorageKey = Symbol("webpack.StorageKey"); +const StorageKey = Symbol.for("CrankWebpackStorageKey"); export interface PageProps { storage: Storage; diff --git a/website/yarn.lock b/website/yarn.lock index 512cec9b4..ce6d33a7a 100644 --- a/website/yarn.lock +++ b/website/yarn.lock @@ -9,7 +9,7 @@ dependencies: "@babel/highlight" "^7.10.4" -"@babel/core@^7.1.0", "@babel/core@^7.8.4": +"@babel/core@^7.1.0", "@babel/core@^7.10.4": version "7.10.5" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.10.5.tgz#1f15e2cca8ad9a1d78a38ddba612f5e7cdbbd330" integrity sha512-O34LQooYVDXPl7QWCdW9p4NR+QlzOr7xShPPJz8GsuCU3/8ua/wqTr7gmnxXv+WBESiGU/G5s16i6tUvHkNb+w== @@ -176,7 +176,7 @@ lodash "^4.17.19" to-fast-properties "^2.0.0" -"@bikeshaving/crank@*": +"@bikeshaving/crank@0.2.1": version "0.2.1" resolved "https://registry.yarnpkg.com/@bikeshaving/crank/-/crank-0.2.1.tgz#b7de2eede6f295208f941c371f118dfc801360ec" integrity sha512-yvlrHku3oN189Wd2a8Wmau8306ENlJ9QdOcHbvXo0vkUBI/g8Q8G8OT0QgaeSLvpDeKErgdrYU9+KSUpqWsBOw== @@ -222,10 +222,10 @@ dependencies: "@types/node" "*" -"@types/codemirror@^0.0.84": - version "0.0.84" - resolved "https://registry.yarnpkg.com/@types/codemirror/-/codemirror-0.0.84.tgz#b0cfca79ccdfd45ffe1f737668276a31b3149ebd" - integrity sha512-W78ZhfHPGYoYGCpAcEa268QUU3CVMA8BwcybxUMhEs2v5Rj58/7lGeRh3P7tauWRnSg3Pyn5ymw2lt65AyrVUw== +"@types/codemirror@^0.0.96": + version "0.0.96" + resolved "https://registry.yarnpkg.com/@types/codemirror/-/codemirror-0.0.96.tgz#73b52e784a246cebef31d544fef45ea764de5bad" + integrity sha512-GTswEV26Bl1byRxpD3sKd1rT2AISr0rK9ImlJgEzfvqhcVWeu4xQKFQI6UgSC95NT5swNG4st/oRMeGVZgPj9w== dependencies: "@types/tern" "*" @@ -239,10 +239,10 @@ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.45.tgz#e9387572998e5ecdac221950dab3e8c3b16af884" integrity sha512-jnqIUKDUqJbDIUxm0Uj7bnlMnRm1T/eZ9N+AVMqhPgzrba2GhGG5o/jCTwmdPK709nEZsGoMzXEDUjcXHa3W0g== -"@types/fs-extra@^8.0.1": - version "8.1.1" - resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-8.1.1.tgz#1e49f22d09aa46e19b51c0b013cb63d0d923a068" - integrity sha512-TcUlBem321DFQzBNuz8p0CLLKp0VvF/XH9E4KHNmgwyp4E3AfgI5cjiIVZWlbfThBop2qxFIh4+LeY6hVWWZ2w== +"@types/fs-extra@^9.0.1": + version "9.0.1" + resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-9.0.1.tgz#91c8fc4c51f6d5dbe44c2ca9ab09310bd00c7918" + integrity sha512-B42Sxuaz09MhC3DDeW5kubRcQ5by4iuVQ0cRRWM2lggLzAa/KVom0Aft/208NgMvNQQZ86s5rVcqDdn/SH0/mg== dependencies: "@types/node" "*" @@ -251,10 +251,10 @@ resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.5.tgz#dcce4430e64b443ba8945f0290fb564ad5bac6dd" integrity sha512-7+2BITlgjgDhH0vvwZU/HZJVyk+2XUlvxXe8dFMedNX/aMkaOq++rMAFXc0tM7ij15QaWlbdQASBR9dihi+bDQ== -"@types/marked@^0.7.2": - version "0.7.4" - resolved "https://registry.yarnpkg.com/@types/marked/-/marked-0.7.4.tgz#607685669bb1bbde2300bc58ba43486cbbee1f0a" - integrity sha512-fdg0NO4qpuHWtZk6dASgsrBggY+8N4dWthl1bAQG9ceKUNKFjqpHaDKCAhRUI6y8vavG7hLSJ4YBwJtZyZEXqw== +"@types/marked@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@types/marked/-/marked-1.1.0.tgz#53509b5f127e0c05c19176fcf1d743a41e00ff19" + integrity sha512-j8XXj6/l9kFvCwMyVqozznqpd/nk80krrW+QiIJN60Uu9gX5Pvn4/qPJ2YngQrR3QREPwmrE1f9/EWKVTFzoEw== "@types/mini-css-extract-plugin@^0.9.1": version "0.9.1" @@ -264,11 +264,11 @@ "@types/webpack" "*" "@types/node@*": - version "14.0.24" - resolved "https://registry.yarnpkg.com/@types/node/-/node-14.0.24.tgz#b0f86f58564fa02a28b68f8b55d4cdec42e3b9d6" - integrity sha512-btt/oNOiDWcSuI721MdL8VQGnjsKjlTMdrKyTcLCKeQp/n4AAMFJ961wMbp+09y8WuGPClDEv07RIItdXKIXAA== + version "14.0.26" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.0.26.tgz#22a3b8a46510da8944b67bfc27df02c34a35331c" + integrity sha512-W+fpe5s91FBGE0pEa0lnqGLL4USgpLgs4nokw16SrBBco/gQxuua7KnArSEOd5iaMqbbSHV10vUDkJYJJqpXKA== -"@types/prismjs@^1.16.0": +"@types/prismjs@^1.16.1": version "1.16.1" resolved "https://registry.yarnpkg.com/@types/prismjs/-/prismjs-1.16.1.tgz#50b82947207847db6abcbcd14caa89e3b897c259" integrity sha512-RNgcK3FEc1GpeOkamGDq42EYkb6yZW5OWQwTS56NJIB8WL0QGISQglA7En7NUx9RGP8AC52DOe+squqbAckXlA== @@ -298,15 +298,15 @@ source-map "^0.6.1" "@types/webpack-sources@*": - version "1.4.0" - resolved "https://registry.yarnpkg.com/@types/webpack-sources/-/webpack-sources-1.4.0.tgz#e58f1f05f87d39a5c64cf85705bdbdbb94d4d57e" - integrity sha512-c88dKrpSle9BtTqR6ifdaxu1Lvjsl3C5OsfvuUbUwdXymshv1TkufUAXBajCCUM/f/TmnkZC/Esb03MinzSiXQ== + version "1.4.1" + resolved "https://registry.yarnpkg.com/@types/webpack-sources/-/webpack-sources-1.4.1.tgz#3bed49013ec7935680d781e83cf4b5ce13ddf917" + integrity sha512-B/RJcbpMp1/od7KADJlW/jeXTEau6NYmhWo+hB29cEfRriYK9SRlH8sY4hI9Au7nrP95Z5MumGvIEiEBHMxoWA== dependencies: "@types/node" "*" "@types/source-list-map" "*" source-map "^0.7.3" -"@types/webpack@*", "@types/webpack@^4.41.6": +"@types/webpack@*", "@types/webpack@^4.41.18": version "4.41.21" resolved "https://registry.yarnpkg.com/@types/webpack/-/webpack-4.41.21.tgz#cc685b332c33f153bb2f5fc1fa3ac8adeb592dee" integrity sha512-2j9WVnNrr/8PLAB5csW44xzQSJwS26aOnICsP3pSGCEdsu6KYtfQ6QJsVUKHWRnm1bL7HziJsfh5fHqth87yKA== @@ -489,9 +489,9 @@ ajv-errors@^1.0.0: integrity sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ== ajv-keywords@^3.1.0, ajv-keywords@^3.4.1: - version "3.5.1" - resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.1.tgz#b83ca89c5d42d69031f424cad49aada0236c6957" - integrity sha512-KWcq3xN8fDjSB+IMoh2VaXVhRI0BBGxoYp3rx7Pkb6z0cFjYR9Q9l4yZqqals0/zsioCmocC5H6UvsGD4MoIBA== + version "3.5.2" + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" + integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== ajv@^6.1.0, ajv@^6.10.2, ajv@^6.12.2: version "6.12.3" @@ -632,11 +632,6 @@ assign-symbols@^1.0.0: resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c= -ast-types@0.9.6: - version "0.9.6" - resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.9.6.tgz#102c9e9e9005d3e7e3829bf0c4fa24ee862ee9b9" - integrity sha1-ECyenpAF0+fjgpvwxPok7oYu6bk= - async-each@^1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf" @@ -659,6 +654,11 @@ async@~1.0.0: resolved "https://registry.yarnpkg.com/async/-/async-1.0.0.tgz#f8fc04ca3a13784ade9e1641af98578cfbd647a9" integrity sha1-+PwEyjoTeErenhZBr5hXjPvWR6k= +at-least-node@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2" + integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg== + atob@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" @@ -955,13 +955,13 @@ callsites@^2.0.0: resolved "https://registry.yarnpkg.com/callsites/-/callsites-2.0.0.tgz#06eb84f00eea413da86affefacbffb36093b3c50" integrity sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA= -camel-case@3.0.x: - version "3.0.0" - resolved "https://registry.yarnpkg.com/camel-case/-/camel-case-3.0.0.tgz#ca3c3688a4e9cf3a4cda777dc4dcbc713249cf73" - integrity sha1-yjw2iKTpzzpM2nd9xNy8cTJJz3M= +camel-case@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/camel-case/-/camel-case-4.1.1.tgz#1fc41c854f00e2f7d0139dfeba1542d6896fe547" + integrity sha512-7fa2WcG4fYFkclIvEmxBbTvmibwF2/agfEBc6q3lOpVu0A13ltLsA+Hr/8Hp6kp5f+G7hKi6t8lys6XxP+1K6Q== dependencies: - no-case "^2.2.0" - upper-case "^1.1.1" + pascal-case "^3.1.1" + tslib "^1.10.0" camelcase@^5.3.1: version "5.3.1" @@ -969,9 +969,9 @@ camelcase@^5.3.1: integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== caniuse-lite@^1.0.30000981, caniuse-lite@^1.0.30001093, caniuse-lite@^1.0.30001097: - version "1.0.30001105" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001105.tgz#d2cb0b31e5cf2f3ce845033b61c5c01566549abf" - integrity sha512-JupOe6+dGMr7E20siZHIZQwYqrllxotAhiaej96y6x00b/48rPt42o+SzOSCPbrpsDWvRja40Hwrj0g0q6LZJg== + version "1.0.30001107" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001107.tgz#809360df7a5b3458f627aa46b0f6ed6d5239da9a" + integrity sha512-86rCH+G8onCmdN4VZzJet5uPELII59cUzDphko3thQFgAQG1RNa+sVLDoALIhRYmflo5iSIzWY3vu1XTWtNMQQ== chalk@^1.1.1: version "1.1.3" @@ -1042,7 +1042,7 @@ chokidar@^2.1.8: optionalDependencies: fsevents "^1.2.7" -chokidar@^3.2.2, chokidar@^3.4.0: +chokidar@^3.2.2, chokidar@^3.4.1: version "3.4.1" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.4.1.tgz#e905bdecf10eaa0a0b1db0c664481cc4cbc22ba1" integrity sha512-TQTJyr2stihpC4Sya9hs2Xh+O2wf+igjL36Y75xx2WdHuiICcn/XJza46Jwt0eT5hVpQOzo3FpY3cj3RVYLX0g== @@ -1092,7 +1092,7 @@ class-utils@^0.3.5: isobject "^3.0.0" static-extend "^0.1.1" -clean-css@4.2.x: +clean-css@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-4.2.3.tgz#507b5de7d97b48ee53d84adb0160ff6216380f78" integrity sha512-VcMWDN54ZN/DS+g58HYL5/n4Zrqe8vHJpGA8KdgUXFU4fuP/aHNw8eld9SyEIyabIMJX/0RaY/fplOo5hYLSFA== @@ -1167,20 +1167,15 @@ colors@^1.1.2: resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA== -commander@2.17.x: - version "2.17.1" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.17.1.tgz#bd77ab7de6de94205ceacc72f1716d29f20a77bf" - integrity sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg== - commander@^2.18.0, commander@^2.20.0: version "2.20.3" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== -commander@~2.19.0: - version "2.19.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.19.0.tgz#f6198aa84e5b83c46054b94ddedbfed5ee9ff12a" - integrity sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg== +commander@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" + integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== commondir@^1.0.1: version "1.0.1" @@ -1341,7 +1336,7 @@ css-has-pseudo@^0.10.0: postcss "^7.0.6" postcss-selector-parser "^5.0.0-rc.4" -css-loader@^3.4.2: +css-loader@^3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-3.6.0.tgz#2e4b2c7e6e2d27f8c8f28f61bffcd2e6c91ef645" integrity sha512-M5lSukoWi1If8dhQAUCvj4H8vUt3vOnwbQBH9DdTm/s4Ym2B/3dPMtYZeJmq7Q3S3Pa+I94DcZ7pc9bP14cWIQ== @@ -1520,7 +1515,7 @@ diffie-hellman@^5.0.0: miller-rabin "^4.0.0" randombytes "^2.0.0" -dom-serializer@0: +dom-serializer@0, dom-serializer@^0.2.1: version "0.2.2" resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.2.tgz#1afb81f533717175d478655debc5e332d9f9bb51" integrity sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g== @@ -1558,6 +1553,13 @@ domhandler@^2.3.0: dependencies: domelementtype "1" +domhandler@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-3.0.0.tgz#51cd13efca31da95bbb0c5bee3a48300e333b3e9" + integrity sha512-eKLdI5v9m67kbXQbJSNn1zjh0SDzvzWVWtX+qEI3eMjZw8daH9k8rlj1FZY9memPwjiskQFbe7vHVVJIAqoEhw== + dependencies: + domelementtype "^2.0.1" + domutils@1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.5.1.tgz#dcd8488a26f563d61079e48c9f7b7e32373682cf" @@ -1574,6 +1576,23 @@ domutils@^1.5.1: dom-serializer "0" domelementtype "1" +domutils@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.1.0.tgz#7ade3201af43703fde154952e3a868eb4b635f16" + integrity sha512-CD9M0Dm1iaHfQ1R/TI+z3/JWp/pgub0j4jIQKH89ARR4ATAV2nbaOQS5XxU9maJP5jHaPdDDQSEHuE2UmpUTKg== + dependencies: + dom-serializer "^0.2.1" + domelementtype "^2.0.1" + domhandler "^3.0.0" + +dot-case@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/dot-case/-/dot-case-3.0.3.tgz#21d3b52efaaba2ea5fda875bb1aa8124521cf4aa" + integrity sha512-7hwEmg6RiSQfm/GwPL4AAWXKy3YNNZA3oFv2Pdiey0mwkRCPZ9x6SZbkLcn8Ma5PYeVokzoD4Twv2n7LKp5WeA== + dependencies: + no-case "^3.0.3" + tslib "^1.10.0" + dot-prop@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.2.0.tgz#c34ecc29556dc45f1f4c22697b6f4904e0cc4fcb" @@ -1597,9 +1616,9 @@ duplexify@^3.2.0, duplexify@^3.4.2, duplexify@^3.6.0: stream-shift "^1.0.0" electron-to-chromium@^1.3.488: - version "1.3.504" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.504.tgz#54d6288202f8453053c006eb862e2e3b7bc867a5" - integrity sha512-yOXnuPaaLAIZUVuXHYDCo3EeaiEfbFgYWCPH1tBMp+jznCq/zQYKnf6HmkKBmLJ0VES81avl18JZO1lx/XAHOw== + version "1.3.510" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.510.tgz#dee781ff8b595c0deb60172b75d50b6889757eda" + integrity sha512-sLtGB0znXdmo6lM8hy5wTVo+fLqvIuO8hEpgc0DvPmFZqvBu/WB7AarEwhxVKjf3rVbws/rC8Xf+AlsOb36lJQ== elliptic@^6.0.0, elliptic@^6.5.2: version "6.5.3" @@ -1641,7 +1660,7 @@ end-of-stream@^1.0.0, end-of-stream@^1.1.0: dependencies: once "^1.4.0" -enhanced-resolve@^4.0.0, enhanced-resolve@^4.1.0: +enhanced-resolve@^4.0.0, enhanced-resolve@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.3.0.tgz#3b806f3bfafc1ec7de69551ef93cca46c1704126" integrity sha512-3e87LvavsdxyoCfGusJnrZ5G8SLPOFeHSNpZI/ATL9a5leXo2k0w6MKnbqhdBad9qTobSfB20Ld7UmgoNbAZkQ== @@ -1674,14 +1693,6 @@ error-ex@^1.3.1: dependencies: is-arrayish "^0.2.1" -es6-templates@^0.2.3: - version "0.2.3" - resolved "https://registry.yarnpkg.com/es6-templates/-/es6-templates-0.2.3.tgz#5cb9ac9fb1ded6eb1239342b81d792bbb4078ee4" - integrity sha1-XLmsn7He1usSOTQrgdeSu7QHjuQ= - dependencies: - recast "~0.11.12" - through "~2.3.6" - escalade@^3.0.1: version "3.0.2" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.0.2.tgz#6a580d70edb87880f22b4c91d0d56078df6962c4" @@ -1710,11 +1721,6 @@ esprima@^4.0.0: resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== -esprima@~3.1.0: - version "3.1.3" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633" - integrity sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM= - esrecurse@^4.1.0: version "4.2.1" resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.2.1.tgz#007a3b9fdbc2b3bb87e4879ea19c92fdbd3942cf" @@ -1804,23 +1810,18 @@ fast-json-stable-stringify@^2.0.0: resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== -fastparse@^1.1.1: - version "1.1.2" - resolved "https://registry.yarnpkg.com/fastparse/-/fastparse-1.1.2.tgz#91728c5a5942eced8531283c79441ee4122c35a9" - integrity sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ== - figgy-pudding@^3.5.1: version "3.5.2" resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.2.tgz#b4eee8148abb01dcf1d1ac34367d59e12fa61d6e" integrity sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw== -file-loader@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-5.1.0.tgz#cb56c070efc0e40666424309bd0d9e45ac6f2bb8" - integrity sha512-u/VkLGskw3Ue59nyOwUwXI/6nuBCo7KBkniB/l7ICwr/7cPNGsL1WCXUp3GB0qgOOKU1TiP49bv4DZF/LJqprg== +file-loader@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-6.0.0.tgz#97bbfaab7a2460c07bcbd72d3a6922407f67649f" + integrity sha512-/aMOAYEFXDdjG0wytpTL5YQLfZnnTmLNjn+AIrJ/6HVnTfDqLsVKUUwkDf4I4kgex36BvjuXEn/TX9B/1ESyqQ== dependencies: - loader-utils "^1.4.0" - schema-utils "^2.5.0" + loader-utils "^2.0.0" + schema-utils "^2.6.5" file-uri-to-path@1.0.0: version "1.0.0" @@ -1875,6 +1876,15 @@ find-cache-dir@^2.1.0: make-dir "^2.0.0" pkg-dir "^3.0.0" +find-cache-dir@^3.3.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-3.3.1.tgz#89b33fad4a4670daa94f855f7fbe31d6d84fe880" + integrity sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ== + dependencies: + commondir "^1.0.1" + make-dir "^3.0.2" + pkg-dir "^4.1.0" + find-up@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" @@ -1882,6 +1892,14 @@ find-up@^3.0.0: dependencies: locate-path "^3.0.0" +find-up@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" + integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== + dependencies: + locate-path "^5.0.0" + path-exists "^4.0.0" + flatten@^1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.3.tgz#c1283ac9f27b368abc1e36d1ff7b04501a30356b" @@ -1915,10 +1933,10 @@ from2@^2.1.0: inherits "^2.0.1" readable-stream "^2.0.0" -front-matter@^3.1.0: - version "3.2.1" - resolved "https://registry.yarnpkg.com/front-matter/-/front-matter-3.2.1.tgz#88be839638f397bbbcb0d61ac03bd08abb4f0a40" - integrity sha512-YUhgEhbL6tG+Ok3vTGIoSDKqcr47aSDvyhEqIv8B+YuBJFsPnOiArNXTPp2yO07NL+a0L4+2jXlKlKqyVcsRRA== +front-matter@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/front-matter/-/front-matter-4.0.2.tgz#b14e54dc745cfd7293484f3210d15ea4edd7f4d5" + integrity sha512-I8ZuJ/qG92NWX8i5x1Y8qyj3vizhXS31OxjKDu3LKP+7/qBgfIKValiZIEwoVoJKUHlhWtYrktkxV1XsX+pPlg== dependencies: js-yaml "^3.13.1" @@ -1931,6 +1949,16 @@ fs-extra@^8.1.0: jsonfile "^4.0.0" universalify "^0.1.0" +fs-extra@^9.0.1: + version "9.0.1" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.0.1.tgz#910da0062437ba4c39fedd863f1675ccfefcb9fc" + integrity sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ== + dependencies: + at-least-node "^1.0.0" + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^1.0.0" + fs-write-stream-atomic@^1.0.8: version "1.0.10" resolved "https://registry.yarnpkg.com/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz#b47df53493ef911df75731e70a9ded0189db40c9" @@ -2008,15 +2036,16 @@ gh-pages-deploy@^0.5.1: prompt "1.0.0" require-module "^0.1.0" -gh-pages@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/gh-pages/-/gh-pages-2.2.0.tgz#74ebeaca8d2b9a11279dcbd4a39ddfff3e6caa24" - integrity sha512-c+yPkNOPMFGNisYg9r4qvsMIjVYikJv7ImFOhPIVPt0+AcRUamZ7zkGRLHz7FKB0xrlZ+ddSOJsZv9XAFVXLmA== +gh-pages@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/gh-pages/-/gh-pages-3.1.0.tgz#ec3ed0f6a6e3fc3d888758fa018f08191c96bd55" + integrity sha512-3b1rly9kuf3/dXsT8+ZxP0UhNLOo1CItj+3e31yUVcaph/yDsJ9RzD7JOw5o5zpBTJVQLlJAASNkUfepi9fe2w== dependencies: async "^2.6.1" commander "^2.18.0" email-addresses "^3.0.1" filenamify-url "^1.0.0" + find-cache-dir "^3.3.1" fs-extra "^8.1.0" globby "^6.1.0" @@ -2169,7 +2198,7 @@ hash.js@^1.0.0, hash.js@^1.0.3: inherits "^2.0.3" minimalistic-assert "^1.0.1" -he@1.2.x: +he@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== @@ -2183,29 +2212,29 @@ hmac-drbg@^1.0.0: minimalistic-assert "^1.0.0" minimalistic-crypto-utils "^1.0.1" -html-loader@^0.5.5: - version "0.5.5" - resolved "https://registry.yarnpkg.com/html-loader/-/html-loader-0.5.5.tgz#6356dbeb0c49756d8ebd5ca327f16ff06ab5faea" - integrity sha512-7hIW7YinOYUpo//kSYcPB6dCKoceKLmOwjEMmhIobHuWGDVl0Nwe4l68mdG/Ru0wcUxQjVMEoZpkalZ/SE7zog== +html-loader@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/html-loader/-/html-loader-1.1.0.tgz#91915f4d274caa9d46d1c3dc847cd82bfc037dbd" + integrity sha512-zwLbEgy+i7sgIYTlxI9M7jwkn29IvdsV6f1y7a2aLv/w8l1RigVk0PFijBZLLFsdi2gvL8sf2VJhTjLlfnK8sA== dependencies: - es6-templates "^0.2.3" - fastparse "^1.1.1" - html-minifier "^3.5.8" - loader-utils "^1.1.0" - object-assign "^4.1.1" + html-minifier-terser "^5.0.5" + htmlparser2 "^4.1.0" + loader-utils "^2.0.0" + parse-srcset "^1.0.2" + schema-utils "^2.6.5" -html-minifier@^3.5.8: - version "3.5.21" - resolved "https://registry.yarnpkg.com/html-minifier/-/html-minifier-3.5.21.tgz#d0040e054730e354db008463593194015212d20c" - integrity sha512-LKUKwuJDhxNa3uf/LPR/KVjm/l3rBqtYeCOAekvG8F1vItxMUpueGd94i/asDDr8/1u7InxzFA5EeGjhhG5mMA== +html-minifier-terser@^5.0.5: + version "5.1.1" + resolved "https://registry.yarnpkg.com/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz#922e96f1f3bb60832c2634b79884096389b1f054" + integrity sha512-ZPr5MNObqnV/T9akshPKbVgyOqLmy+Bxo7juKCfTfnjNniTAMdy4hz21YQqoofMBJD2kdREaqPPdThoR78Tgxg== dependencies: - camel-case "3.0.x" - clean-css "4.2.x" - commander "2.17.x" - he "1.2.x" - param-case "2.1.x" - relateurl "0.2.x" - uglify-js "3.4.x" + camel-case "^4.1.1" + clean-css "^4.2.3" + commander "^4.1.1" + he "^1.2.0" + param-case "^3.0.3" + relateurl "^0.2.7" + terser "^4.6.3" htmlparser2@^3.9.1: version "3.10.1" @@ -2219,6 +2248,16 @@ htmlparser2@^3.9.1: inherits "^2.0.1" readable-stream "^3.1.1" +htmlparser2@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-4.1.0.tgz#9a4ef161f2e4625ebf7dfbe6c0a2f52d18a59e78" + integrity sha512-4zDq1a1zhE4gQso/c5LP1OtrhYTncXNSpvJYtWJBtXAETPlMfi3IFNjGuQbYLuVY4ZR0QMqRVvo4Pdy9KLyP8Q== + dependencies: + domelementtype "^2.0.1" + domhandler "^3.0.0" + domutils "^2.0.0" + entities "^2.0.0" + http-cache-semantics@^4.0.0: version "4.1.0" resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390" @@ -2605,6 +2644,15 @@ jsonfile@^4.0.0: optionalDependencies: graceful-fs "^4.1.6" +jsonfile@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.0.1.tgz#98966cba214378c8c84b82e085907b40bf614179" + integrity sha512-jR2b5v7d2vIOust+w3wtFKZIfpC2pnRmFAhAC/BuweZFQR8qZzxH1OyrQ10HmdVYiXWkYUqPVsz91cG7EL2FBg== + dependencies: + universalify "^1.0.0" + optionalDependencies: + graceful-fs "^4.1.6" + keyv@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/keyv/-/keyv-3.1.0.tgz#ecc228486f69991e49e9476485a5be1e8fc5c4d9" @@ -2648,7 +2696,7 @@ loader-runner@^2.4.0: resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.4.0.tgz#ed47066bfe534d7e84c4c7b9998c2a75607d9357" integrity sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw== -loader-utils@^1.0.2, loader-utils@^1.1.0, loader-utils@^1.2.3, loader-utils@^1.4.0: +loader-utils@^1.0.2, loader-utils@^1.1.0, loader-utils@^1.2.3: version "1.4.0" resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.0.tgz#c579b5e34cb34b1a74edc6c1fb36bfa371d5a613" integrity sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA== @@ -2674,6 +2722,13 @@ locate-path@^3.0.0: p-locate "^3.0.0" path-exists "^3.0.0" +locate-path@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" + integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== + dependencies: + p-locate "^4.1.0" + lodash._reinterpolate@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d" @@ -2759,10 +2814,12 @@ lodash@^4.17.14, lodash@^4.17.19: resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b" integrity sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ== -lower-case@^1.1.1: - version "1.1.4" - resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-1.1.4.tgz#9a2cabd1b9e8e0ae993a4bf7d5875c39c42e8eac" - integrity sha1-miyr0bno4K6ZOkv31YdcOcQujqw= +lower-case@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-2.0.1.tgz#39eeb36e396115cc05e29422eaea9e692c9408c7" + integrity sha512-LiWgfDLLb1dwbFQZsSglpRj+1ctGnayXz3Uv0/WO8n558JycT5fg6zkNcnW0G68Nn0aEldTFeEfmjCfmqry/rQ== + dependencies: + tslib "^1.10.0" lowercase-keys@^1.0.0, lowercase-keys@^1.0.1: version "1.0.1" @@ -2789,7 +2846,7 @@ make-dir@^2.0.0: pify "^4.0.1" semver "^5.6.0" -make-dir@^3.0.0: +make-dir@^3.0.0, make-dir@^3.0.2: version "3.1.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== @@ -2813,10 +2870,10 @@ map-visit@^1.0.0: dependencies: object-visit "^1.0.0" -marked@^0.8.0: - version "0.8.2" - resolved "https://registry.yarnpkg.com/marked/-/marked-0.8.2.tgz#4faad28d26ede351a7a1aaa5fec67915c869e355" - integrity sha512-EGwzEeCcLniFX51DhTpmTom+dSA/MG/OBUDjnWtHbEnjAH180VzUeAw+oE4+Zv+CoYBWyRlYOTR0N8SO9R1PVw== +marked@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/marked/-/marked-1.1.1.tgz#e5d61b69842210d5df57b05856e0c91572703e6a" + integrity sha512-mJzT8D2yPxoPh7h0UXkB+dBj4FykPJ2OIfxAWeIHrvoHDkFxukV/29QxoFQoPM6RLEwhIFdJpmKBlqVM3s2ZIw== md5.js@^1.3.4: version "1.3.5" @@ -2878,10 +2935,17 @@ miller-rabin@^4.0.0: bn.js "^4.0.0" brorand "^1.0.1" -mime@^2.4.4: - version "2.4.6" - resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.6.tgz#e5b407c90db442f2beb5b162373d07b69affa4d1" - integrity sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA== +mime-db@1.44.0: + version "1.44.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.44.0.tgz#fa11c5eb0aca1334b4233cb4d52f10c5a6272f92" + integrity sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg== + +mime-types@^2.1.26: + version "2.1.27" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.27.tgz#47949f98e279ea53119f5722e0f34e529bec009f" + integrity sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w== + dependencies: + mime-db "1.44.0" mimic-response@^1.0.0, mimic-response@^1.0.1: version "1.0.1" @@ -3032,12 +3096,13 @@ neo-async@^2.5.0, neo-async@^2.6.1: resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== -no-case@^2.2.0: - version "2.3.2" - resolved "https://registry.yarnpkg.com/no-case/-/no-case-2.3.2.tgz#60b813396be39b3f1288a4c1ed5d1e7d28b464ac" - integrity sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ== +no-case@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/no-case/-/no-case-3.0.3.tgz#c21b434c1ffe48b39087e86cfb4d2582e9df18f8" + integrity sha512-ehY/mVQCf9BL0gKfsJBvFJen+1V//U+0HQMPrWct40ixE4jnv0bfvxDbWtAHL9EcaPEOJHVVYKoQn1TlZUB8Tw== dependencies: - lower-case "^1.1.1" + lower-case "^2.0.1" + tslib "^1.10.0" node-libs-browser@^2.2.1: version "2.2.1" @@ -3069,11 +3134,11 @@ node-libs-browser@^2.2.1: vm-browserify "^1.0.1" node-releases@^1.1.58: - version "1.1.59" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.59.tgz#4d648330641cec704bff10f8e4fe28e453ab8e8e" - integrity sha512-H3JrdUczbdiwxN5FuJPyCHnGHIFqQ0wWxo+9j1kAXAzqNMAHlo+4I/sYYxpyK0irQ73HgdiyzD32oqQDcU2Osw== + version "1.1.60" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.60.tgz#6948bdfce8286f0b5d0e5a88e8384e954dfe7084" + integrity sha512-gsO4vjEdQaTusZAEebUWp2a5d7dF5DYoIpDG7WySnk7BuZDW+GPpHXoXXuYawRBr/9t5q54tirPz79kFIWg4dA== -nodemon@^2.0.2: +nodemon@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-2.0.4.tgz#55b09319eb488d6394aa9818148c0c2d1c04c416" integrity sha512-Ltced+hIfTmaS28Zjv1BM552oQ3dbwPqI4+zI0SLgq+wpJhSyqgYude/aZa/3i31VCQWMfXJVxvu86abcam3uQ== @@ -3200,7 +3265,7 @@ p-cancelable@^1.0.0: resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-1.1.0.tgz#d078d15a3af409220c886f1d9a0ca2e441ab26cc" integrity sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw== -p-limit@^2.0.0: +p-limit@^2.0.0, p-limit@^2.2.0: version "2.3.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== @@ -3214,6 +3279,13 @@ p-locate@^3.0.0: dependencies: p-limit "^2.0.0" +p-locate@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" + integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== + dependencies: + p-limit "^2.2.0" + p-try@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" @@ -3243,12 +3315,13 @@ parallel-transform@^1.1.0: inherits "^2.0.3" readable-stream "^2.1.5" -param-case@2.1.x: - version "2.1.1" - resolved "https://registry.yarnpkg.com/param-case/-/param-case-2.1.1.tgz#df94fd8cf6531ecf75e6bef9a0858fbc72be2247" - integrity sha1-35T9jPZTHs915r75oIWPvHK+Ikc= +param-case@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/param-case/-/param-case-3.0.3.tgz#4be41f8399eff621c56eebb829a5e451d9801238" + integrity sha512-VWBVyimc1+QrzappRs7waeN2YmoZFCGXWASRYX1/rGHtXqEcrGEIDm+jqIwFa2fRXNgQEwrxaYuIrX0WcAguTA== dependencies: - no-case "^2.2.0" + dot-case "^3.0.3" + tslib "^1.10.0" parse-asn1@^5.0.0, parse-asn1@^5.1.5: version "5.1.5" @@ -3270,6 +3343,19 @@ parse-json@^4.0.0: error-ex "^1.3.1" json-parse-better-errors "^1.0.1" +parse-srcset@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/parse-srcset/-/parse-srcset-1.0.2.tgz#f2bd221f6cc970a938d88556abc589caaaa2bde1" + integrity sha1-8r0iH2zJcKk42IVWq8WJyqqiveE= + +pascal-case@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/pascal-case/-/pascal-case-3.1.1.tgz#5ac1975133ed619281e88920973d2cd1f279de5f" + integrity sha512-XIeHKqIrsquVTQL2crjq3NfJUxmdLasn3TYOU0VBM+UX2a6ztAWBlJQBePLGY7VHW8+2dRadeIPK5+KImwTxQA== + dependencies: + no-case "^3.0.3" + tslib "^1.10.0" + pascalcase@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" @@ -3290,6 +3376,11 @@ path-exists@^3.0.0: resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU= +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + path-is-absolute@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" @@ -3345,6 +3436,13 @@ pkg-dir@^3.0.0: dependencies: find-up "^3.0.0" +pkg-dir@^4.1.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" + integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== + dependencies: + find-up "^4.0.0" + pkginfo@0.3.x: version "0.3.1" resolved "https://registry.yarnpkg.com/pkginfo/-/pkginfo-0.3.1.tgz#5b29f6a81f70717142e09e765bbeab97b4f81e21" @@ -3550,14 +3648,14 @@ postcss-modules-extract-imports@^2.0.0: postcss "^7.0.5" postcss-modules-local-by-default@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-3.0.2.tgz#e8a6561be914aaf3c052876377524ca90dbb7915" - integrity sha512-jM/V8eqM4oJ/22j0gx4jrp63GSvDH6v86OqyTHHUvk4/k1vceipZsaymiZ5PvocqZOl5SFHiFJqjs3la0wnfIQ== + version "3.0.3" + resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-3.0.3.tgz#bb14e0cc78279d504dbdcbfd7e0ca28993ffbbb0" + integrity sha512-e3xDq+LotiGesympRlKNgaJ0PCzoUIdpH0dj47iWAui/kyTgh3CiAr1qP54uodmJhl6p9rN6BoNcdEDVJx9RDw== dependencies: icss-utils "^4.1.1" - postcss "^7.0.16" + postcss "^7.0.32" postcss-selector-parser "^6.0.2" - postcss-value-parser "^4.0.0" + postcss-value-parser "^4.1.0" postcss-modules-scope@^2.2.0: version "2.2.0" @@ -3704,7 +3802,7 @@ postcss-selector-parser@^6.0.0, postcss-selector-parser@^6.0.2: indexes-of "^1.0.1" uniq "^1.0.1" -postcss-value-parser@^4.0.0, postcss-value-parser@^4.1.0: +postcss-value-parser@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz#443f6a20ced6481a2bda4fa8532a6e55d789a2cb" integrity sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ== @@ -3718,7 +3816,7 @@ postcss-values-parser@^2.0.0, postcss-values-parser@^2.0.1: indexes-of "^1.0.1" uniq "^1.0.1" -postcss@^7.0.0, postcss@^7.0.14, postcss@^7.0.16, postcss@^7.0.17, postcss@^7.0.2, postcss@^7.0.32, postcss@^7.0.5, postcss@^7.0.6: +postcss@^7.0.0, postcss@^7.0.14, postcss@^7.0.17, postcss@^7.0.2, postcss@^7.0.32, postcss@^7.0.5, postcss@^7.0.6: version "7.0.32" resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.32.tgz#4310d6ee347053da3433db2be492883d62cec59d" integrity sha512-03eXong5NLnNCD05xscnGKGDZ98CyzoqPSMjOe6SuoQY7Z2hIj0Ld1g/O/UQRuOle2aRtiIRDg9tDcTGAkLfKw== @@ -3744,11 +3842,6 @@ prismjs@^1.19.0: optionalDependencies: clipboard "^2.0.0" -private@~0.1.5: - version "0.1.8" - resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff" - integrity sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg== - process-nextick-args@~2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" @@ -3933,20 +4026,10 @@ readdirp@~3.4.0: dependencies: picomatch "^2.2.1" -recast@~0.11.12: - version "0.11.23" - resolved "https://registry.yarnpkg.com/recast/-/recast-0.11.23.tgz#451fd3004ab1e4df9b4e4b66376b2a21912462d3" - integrity sha1-RR/TAEqx5N+bTktmN2sqIZEkYtM= - dependencies: - ast-types "0.9.6" - esprima "~3.1.0" - private "~0.1.5" - source-map "~0.5.0" - regenerator-runtime@^0.13.3: - version "0.13.6" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.6.tgz#d236043c46ffab2968c1ef651803d8acdea8ed65" - integrity sha512-GmwlGiazQEbOwQWDdbbaP10i15pGtScYWLbMZuu+RKRz0cZ+g8IUONazBnaZqe7j1670IV1HgE4/8iy7CQPf4Q== + version "0.13.7" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz#cac2dacc8a1ea675feaabaeb8ae833898ae46f55" + integrity sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew== regex-not@^1.0.0, regex-not@^1.0.2: version "1.0.2" @@ -3970,7 +4053,7 @@ registry-url@^5.0.0: dependencies: rc "^1.2.8" -relateurl@0.2.x: +relateurl@^0.2.7: version "0.2.7" resolved "https://registry.yarnpkg.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9" integrity sha1-VNvzd+UUQKypCkzSdGANP/LYiKk= @@ -4089,7 +4172,7 @@ schema-utils@^1.0.0: ajv-errors "^1.0.0" ajv-keywords "^3.1.0" -schema-utils@^2.5.0, schema-utils@^2.6.6, schema-utils@^2.7.0: +schema-utils@^2.6.5, schema-utils@^2.6.6, schema-utils@^2.7.0: version "2.7.0" resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.7.0.tgz#17151f76d8eae67fbbf77960c33c676ad9f4efc7" integrity sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A== @@ -4226,7 +4309,7 @@ source-map-url@^0.4.0: resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3" integrity sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM= -source-map@^0.5.0, source-map@^0.5.6, source-map@~0.5.0: +source-map@^0.5.0, source-map@^0.5.6: version "0.5.7" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= @@ -4394,7 +4477,7 @@ strip-url-auth@^1.0.0: resolved "https://registry.yarnpkg.com/strip-url-auth/-/strip-url-auth-1.0.1.tgz#22b0fa3a41385b33be3f331551bbb837fa0cd7ae" integrity sha1-IrD6OkE4WzO+PzMVUbu4N/oM164= -style-loader@^1.1.3: +style-loader@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-1.2.1.tgz#c5cbbfbf1170d076cfdd86e0109c5bba114baa1a" integrity sha512-ByHSTQvHLkWE9Ir5+lGbVOXhxX10fbprhLvdg96wedFZb4NDekDPxVKv5Fwmio+QcMlkkNfuK+5W1peQ5CUhZg== @@ -4460,7 +4543,7 @@ terser-webpack-plugin@^1.4.3: webpack-sources "^1.4.0" worker-farm "^1.7.0" -terser@^4.1.2: +terser@^4.1.2, terser@^4.6.3: version "4.8.0" resolved "https://registry.yarnpkg.com/terser/-/terser-4.8.0.tgz#63056343d7c70bb29f3af665865a46fe03a0df17" integrity sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw== @@ -4477,7 +4560,7 @@ through2@^2.0.0, through2@^2.0.2, through2@^2.0.3: readable-stream "~2.3.6" xtend "~4.0.1" -through@2, through@~2.3.6: +through@2: version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= @@ -4563,10 +4646,10 @@ truncate-html@^1.0.3: "@types/cheerio" "^0.22.8" cheerio "0.22.0" -ts-loader@^6.2.1: - version "6.2.2" - resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-6.2.2.tgz#dffa3879b01a1a1e0a4b85e2b8421dc0dfff1c58" - integrity sha512-HDo5kXZCBml3EUPcc7RlZOV/JGlLHwppTLEHb3SHnr5V7NXD4klMEkrhJe5wgRbaWsSXi+Y1SIBN/K9B6zWGWQ== +ts-loader@^7.0.5: + version "7.0.5" + resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-7.0.5.tgz#789338fb01cb5dc0a33c54e50558b34a73c9c4c5" + integrity sha512-zXypEIT6k3oTc+OZNx/cqElrsbBtYqDknf48OZos0NQ3RTt045fBIU8RRSu+suObBzYB355aIPGOe/3kj9h7Ig== dependencies: chalk "^2.3.0" enhanced-resolve "^4.0.0" @@ -4574,7 +4657,7 @@ ts-loader@^6.2.1: micromatch "^4.0.0" semver "^6.0.0" -ts-node@^8.6.2: +ts-node@^8.10.2: version "8.10.2" resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-8.10.2.tgz#eee03764633b1234ddd37f8db9ec10b75ec7fb8d" integrity sha512-ISJJGgkIpDdBhWVu3jufsWpK3Rzo7bdiIXJjQc0ynKxVOVcg2oIrf2H2cejminGrptVc6q6/uynAHNCuWGbpVA== @@ -4585,7 +4668,7 @@ ts-node@^8.6.2: source-map-support "^0.5.17" yn "3.1.1" -tslib@^1.9.0: +tslib@^1.10.0, tslib@^1.9.0: version "1.13.0" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.13.0.tgz#c881e13cc7015894ed914862d276436fa9a47043" integrity sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q== @@ -4612,19 +4695,11 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= -typescript@^3.7.5: +typescript@^3.9.6: version "3.9.7" resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.7.tgz#98d600a5ebdc38f40cb277522f12dc800e9e25fa" integrity sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw== -uglify-js@3.4.x: - version "3.4.10" - resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.4.10.tgz#9ad9563d8eb3acdfb8d38597d2af1d815f6a755f" - integrity sha512-Y2VsbPVs0FIshJztycsO2SfPk7/KAF/T72qzv9u5EpQ4kB2hQoHlhNQTsNyy6ul7lQtqJN/AoWeS23OzEiEFxw== - dependencies: - commander "~2.19.0" - source-map "~0.6.1" - undefsafe@^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.3.tgz#6b166e7094ad46313b2202da7ecc2cd7cc6e7aae" @@ -4673,6 +4748,11 @@ universalify@^0.1.0: resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== +universalify@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-1.0.0.tgz#b61a1da173e8435b2fe3c67d29b9adf8594bd16d" + integrity sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug== + unset-value@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unset-value/-/unset-value-1.0.0.tgz#8376873f7d2335179ffb1e6fc3a8ed0dfc8ab559" @@ -4705,11 +4785,6 @@ update-notifier@^4.0.0: semver-diff "^3.1.1" xdg-basedir "^4.0.0" -upper-case@^1.1.1: - version "1.1.3" - resolved "https://registry.yarnpkg.com/upper-case/-/upper-case-1.1.3.tgz#f6b4501c2ec4cdd26ba78be7222961de77621598" - integrity sha1-9rRQHC7EzdJrp4vnIilh3ndiFZg= - uri-js@^4.2.2: version "4.2.2" resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0" @@ -4722,14 +4797,14 @@ urix@^0.1.0: resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI= -url-loader@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/url-loader/-/url-loader-3.0.0.tgz#9f1f11b371acf6e51ed15a50db635e02eec18368" - integrity sha512-a84JJbIA5xTFTWyjjcPdnsu+41o/SNE8SpXMdUvXs6Q+LuhCD9E2+0VCiuDWqgo3GGXVlFHzArDmBpj9PgWn4A== +url-loader@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/url-loader/-/url-loader-4.1.0.tgz#c7d6b0d6b0fccd51ab3ffc58a78d32b8d89a7be2" + integrity sha512-IzgAAIC8wRrg6NYkFIJY09vtktQcsvU8V6HhtQj9PTefbYImzLB1hufqo4m+RyM5N3mLx5BqJKccgxJS+W3kqw== dependencies: - loader-utils "^1.2.3" - mime "^2.4.4" - schema-utils "^2.5.0" + loader-utils "^2.0.0" + mime-types "^2.1.26" + schema-utils "^2.6.5" url-parse-lax@^3.0.0: version "3.0.0" @@ -4799,15 +4874,15 @@ watchpack-chokidar2@^2.0.0: dependencies: chokidar "^2.1.8" -watchpack@^1.6.1: - version "1.7.2" - resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.7.2.tgz#c02e4d4d49913c3e7e122c3325365af9d331e9aa" - integrity sha512-ymVbbQP40MFTp+cNMvpyBpBtygHnPzPkHqoIwRRj/0B8KhqQwV8LaKjtbaxF2lK4vl8zN9wCxS46IFCU5K4W0g== +watchpack@^1.7.4: + version "1.7.4" + resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.7.4.tgz#6e9da53b3c80bb2d6508188f5b200410866cd30b" + integrity sha512-aWAgTW4MoSJzZPAicljkO1hsi1oKj/RRq/OJQh2PKI2UKL04c2Bs+MBOB+BBABHTXJpf9mCwHN7ANCvYsvY2sg== dependencies: graceful-fs "^4.1.2" neo-async "^2.5.0" optionalDependencies: - chokidar "^3.4.0" + chokidar "^3.4.1" watchpack-chokidar2 "^2.0.0" webpack-sources@^1.1.0, webpack-sources@^1.4.0, webpack-sources@^1.4.1: @@ -4818,10 +4893,10 @@ webpack-sources@^1.1.0, webpack-sources@^1.4.0, webpack-sources@^1.4.1: source-list-map "^2.0.0" source-map "~0.6.1" -webpack@^4.41.6: - version "4.43.0" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.43.0.tgz#c48547b11d563224c561dad1172c8aa0b8a678e6" - integrity sha512-GW1LjnPipFW2Y78OOab8NJlCflB7EFskMih2AHdvjbpKMeDJqEgSx24cXXXiPS65+WSwVyxtDsJH6jGX2czy+g== +webpack@^4.43.0: + version "4.44.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.44.0.tgz#3b08f88a89470175f036f4a9496b8a0428668802" + integrity sha512-wAuJxK123sqAw31SpkPiPW3iKHgFUiKvO7E7UZjtdExcsRe3fgav4mvoMM7vvpjLHVoJ6a0Mtp2fzkoA13e0Zw== dependencies: "@webassemblyjs/ast" "1.9.0" "@webassemblyjs/helper-module-context" "1.9.0" @@ -4831,7 +4906,7 @@ webpack@^4.41.6: ajv "^6.10.2" ajv-keywords "^3.4.1" chrome-trace-event "^1.0.2" - enhanced-resolve "^4.1.0" + enhanced-resolve "^4.3.0" eslint-scope "^4.0.3" json-parse-better-errors "^1.0.2" loader-runner "^2.4.0" @@ -4844,7 +4919,7 @@ webpack@^4.41.6: schema-utils "^1.0.0" tapable "^1.1.3" terser-webpack-plugin "^1.4.3" - watchpack "^1.6.1" + watchpack "^1.7.4" webpack-sources "^1.4.1" widest-line@^3.1.0: From 34bd74d12d287f5aa56cad5b304c52f2a19db364 Mon Sep 17 00:00:00 2001 From: Brian Kim Date: Fri, 10 Jul 2020 13:38:31 -0400 Subject: [PATCH 02/26] planning out/writing docs stuff --- website/blog/2020-04-15-introducing-crank.md | 14 +- website/guides/02-elements.md | 2 +- website/guides/05-special-tags-and-props.md | 17 +- website/guides/06-lifecycles.md | 6 +- website/guides/07-working-with-typescript.md | 6 +- website/guides/08-api-reference.md | 19 -- website/guides/08-reusable-logic.md | 19 ++ website/guides/09-custom-renderers.md | 142 ++++++++++ ...-react.md => 10-differences-from-react.md} | 1 - website/guides/11-api-reference.md | 244 ++++++++++++++++++ 10 files changed, 439 insertions(+), 31 deletions(-) delete mode 100644 website/guides/08-api-reference.md create mode 100644 website/guides/08-reusable-logic.md create mode 100644 website/guides/09-custom-renderers.md rename website/guides/{999999999-differences-from-react.md => 10-differences-from-react.md} (99%) create mode 100644 website/guides/11-api-reference.md diff --git a/website/blog/2020-04-15-introducing-crank.md b/website/blog/2020-04-15-introducing-crank.md index 9c9fb61e3..f8649928d 100644 --- a/website/blog/2020-04-15-introducing-crank.md +++ b/website/blog/2020-04-15-introducing-crank.md @@ -6,15 +6,15 @@ publishDate: 2020-04-15T19:18:41.371Z After months of development, I’m happy to introduce Crank.js, a new framework for creating JSX-driven components with functions, promises and generators. And I know what you’re thinking: *oh no, not another web framework.* There are already so many of them out there and each carries a non-negligible cost in terms of learning it and building an ecosystem to surround it, so it makes sense that you would reject newcomers if only to avoid the deep sense of exhaustion which has come to be known amongst front-end developers as “JavaScript fatigue.” Therefore, this post is both an introduction to Crank as well as an apology: I’m sorry for creating yet another framework, and I hope that by explaining the circumstances which led me to do so, you will forgive me. -I will be honest. Before embarking on this project, I never considered myself capable of making a “web framework.” I don’t maintain any popular open-source libraries, and most of the early commits to this project had messages like “why on Earth are you doing this?” Before working on Crank, my framework of choice was React, and I had used it dutifully for almost every project within my control since the `React.createClass` days. And as React evolved, I must admit, I was intrigued and excited with the announcement of each new code-named feature like “Fibers,” “Hooks” and “Suspense.” I’d spend hours attempting to decipher tweets from Sebastian Markbage, one of the principal architects behind React, and I sincerely felt like React would continue to be relevant well into the 2020s. +I will be honest. Before embarking on this project, I never considered myself capable of making a “web framework.” I don’t maintain any popular open-source libraries, and most of the early commits to this project had messages like “I can’t even believe I’m actually considering making my own web framework.” Before working on Crank, my framework of choice was React, and I had used it dutifully for almost every project within my control since the `React.createClass` days. And as React evolved, I must admit, I was intrigued and excited with the announcement of each new code-named feature like “Fibers,” “Hooks” and “Suspense.” I sincerely felt that React would continue to be relevant well into the 2020s. ![The first commit messages](../static/commits.png) -However, over time, I grew increasingly alienated by what I perceived to be the more general direction of React, which was to reframe it as a “UI runtime.” Each new API felt exciting, but I disliked how opaque and error-prone the concrete code written with these APIs seemed. I was unhappy, for instance, with the strangeness and pitfalls of the new Hooks API, and I worried about the constant warnings the React team gave about how code which worked today would break once something called “Concurrent Mode” landed. *I already have a UI runtime*, I began to think, whenever I read the latest on React, *it’s called JavaScript.* +However, over time, I grew increasingly alienated by what I perceived to be the more general direction of React, which was to reframe it as a “UI runtime.” Each new API felt exciting, but I disliked how opaque and error-prone the concrete code written with these APIs seemed. I was unhappy, for instance, with the strangeness and pitfalls of the new Hooks API, and I worried about the constant warnings the React team gave about how code which worked today would break once something called “Concurrent Mode” landed. *I already have a UI runtime*, I began to grumble whenever I read the latest on React, *it’s called JavaScript.* -Towards the end, I felt marooned, because on the one hand I didn’t feel comfortable using React anymore, but on the other, I didn’t want to use any of the alternative frameworks either. I agreed with the criticisms which Vue and Svelte advocates lobbed in the direction of React, but I was unwilling to convert to these frameworks because they prominently featured HTML template languages as the main way to use them. +Towards the end, I felt marooned, because on the one hand I didn’t feel comfortable using React anymore, but on the other, I didn’t want to use any of the alternatives either. I agreed with the criticisms which Vue and Svelte advocates lobbed in the direction of React, but I was unwilling to convert to these frameworks because they prominently featured HTML template languages as the main way to use them. -I like JSX. I like the small surface area it provides compared to template languages, which provide their own syntax to do basic things like iterating over an array or conditionally rendering something. At the same time, the other frameworks which used JSX like Preact and Inferno seemed to follow React blindly in its heroic evolution from “a view layer” into “a UI runtime.” Rather than thinking critically about each new feature, these libraries seemed eager to mimic them for purposes of compatibility, opting to distinguish themselves instead in terms of library metrics like bundle size (Preact) or runtime performance (Inferno). My problems with React weren’t related to bundle size or runtime performance. It was the API itself that needed fixing. I felt like React, which had up to this point been the standard-bearer of JSX, was no longer up to the task of defending its colors. +I like JSX. I like the small surface area it provides compared to template languages, which provide their own syntax to do basic things like iterating over an array or conditionally rendering something. Meanwhile, the other frameworks which used JSX like Preact and Inferno seemed to follow React blindly in its heroic evolution from “a view layer” into “a UI runtime.” Rather than thinking critically about each new feature, these libraries seemed eager to mimic them for purposes of compatibility, opting to distinguish themselves instead in terms of library metrics like bundle size (Preact) or runtime performance (Inferno). My problems with React weren’t related to bundle size or runtime performance. It was the API itself that needed fixing. I felt like React, which had up to this point been the standard-bearer of JSX, was no longer up to the task of defending its colors. ## Tired of the suspense @@ -46,7 +46,7 @@ Correspondingly, a lot of the pain points of React began to make sense. All of t Freed of this dogmatic assertion, I pondered for a week or so on the kind of JSX-based library you could create if components didn’t have to be sync functions. After all, JavaScript has at present four separate function syntaxes (`function`, `async function`, `function *`, and `async function *`); wouldn’t it be nice if we could use this entire palette to write components? Could there be a use-case for generator functions as well? Again the [React team dismissed generators by definition](https://github.com/facebook/react/issues/7942#issuecomment-254987818), because generator functions returned generator objects, which are stateful and therefore “impure.” ## JavaScript is already a UI runtime -At this point, I was intrigued by this idea but I also didn’t want to write a React alternative. I wanted to write applications, not build and maintain a framework. And so I was about to move on to something else, when my previous work with async iterators and generators gave me a flash of insight. The entire React lifecycle, all the “componentDidWhat” methods, everything which React was trying to do with classes and hooks and state and refs, all of it could be expressed within a single async generator function. +At this point, I was intrigued by this idea but I also didn’t want to write a React alternative. I wanted to write applications, not build and maintain a framework. And so I was about to move on to something else, when my previous work with async iterators and generators gave me a flash of insight. The entire React lifecycle, all of the `componentDidWhat` methods, everything which React was trying to do with classes and hooks and state and refs, all of it could be expressed within a single async generator function. ```js async function *MyComponent(props) { @@ -71,7 +71,7 @@ async function *MyComponent(props) { } ``` -This is some pseudo-code I sketched out, where the calls to `componentDidWhat` functions are just there to demonstrate where code goes compared to the React lifecycle. The actual Crank API turned out to be different, but in the moment I felt like I had captured lightning in a bottle. By yielding JSX elements rather than returning them, you could have code which ran before or after the component rendered, emulating the `componentWillUpdate` or `componentDidUpdate` lifecycle methods. New props could be passed in by stepping through a framework-provided async iterator, which resumed with fresh props whenever the component was rerendered. And the concept of local state, which in React requires calls to `this.setState` or the `useState` hook, could simply be expressed with local variables, because yielding is not final and the generator’s local scope could be preserved between renders. +This is some pseudo-code I sketched out, where the calls to `componentDidWhat` functions merely demonstrate where code goes compared to the React lifecycle. While the actual Crank API turned out to be slightly different, in the moment I felt like I had captured lightning in a bottle. By yielding JSX elements rather than returning them, you could have code which ran before or after the component rendered, emulating the `componentWillUpdate` or `componentDidUpdate` lifecycle methods. New props could be passed in by stepping through a framework-provided async iterator, which resumed with fresh props whenever the component was rerendered. And the concept of local state, which in React requires calls to `this.setState` or the `useState` hook, could simply be expressed with local variables, because yielding is not final and the generator’s local scope could be preserved between renders. Furthermore, you could implement something like the `componentDidCatch` and `componentWillUnmount` lifecycle methods directly within the async generator by wrapping the `yield` operator in a `try`/`catch`/`finally` block. And the framework could, upon producing DOM nodes, pass these nodes back into the generator, so you could do direct DOM manipulations without React’s notion of “refs.” All these things which React required separate methods or hooks to accomplish could be done within async generator functions with just the control-flow operators that JavaScript provides, and all within the same scope. @@ -86,4 +86,4 @@ By combining these relatively old, almost boring technologies with JSX syntax, I And again, I sincerely apologize for creating yet another framework in an already crowded space, but I hope, if you’ve read this far, you understand why I did so, namely, because I thought React was dropping the ball in terms of its newest APIs, because I still wanted to use JSX, and because of the sudden realization that we could be doing so much more with the different function syntaxes available to us in JavaScript. -If any of this interests you, if you want to continue to use JSX over template languages, if you’re tired of debugging hooks, if you want to use promises in your components *today*, if you’re looking for a framework which has, arguably, the most “just JavaScript” story for reactivity, I encourage you to check out Crank. You can read [the documentation](/guides/getting-started) or check out the [TodoMVC example](https://codesandbox.io/s/crank-todomvc-k6s0x) that made me tear up a little. Crank is still in its early days, and there’s a lot of work to be done before it can be considered a full-fledged framework, but I think the ideas behind it are sound and I’ve thoroughly enjoyed designing it. I can’t wait to see what people build with Crank. +If any of this interests you, if you want to continue to use JSX over template languages, if you’re tired of debugging hooks, if you want to use promises in your components *today*, if you’re looking for a framework which has, arguably, the most “just JavaScript” story for reactivity, I encourage you to check out Crank. You can read [the documentation](/guides/getting-started) or check out the [TodoMVC example](https://codesandbox.io/s/crank-todomvc-k6s0x) that made me cry a little haha. Crank is still in its early days, and there’s a lot of work to be done before it can be considered a full-fledged framework, but I think the ideas behind it are sound and I’ve thoroughly enjoyed designing it. I can’t wait to see what people build with Crank. diff --git a/website/guides/02-elements.md b/website/guides/02-elements.md index 485a00d0d..6791772cb 100644 --- a/website/guides/02-elements.md +++ b/website/guides/02-elements.md @@ -110,7 +110,7 @@ console.log(document.body.innerHTML); // "
123 abc
" ``` ## Element diffing -Crank uses the same “virtual DOM diffing” algorithm made popular by React, where elements of the tree are compared by tag and position between renders, and subtrees whose root tags don’t match are thrown away. This allows you to write declarative code which focuses on producing the right element tree, while Crank does the dirty work of managing state and mutating the DOM. +Crank uses the same “virtual DOM diffing” algorithm made popular by React, where DOM operations and component state is determined by the element’s tag and position in the tree. This approach allows you to write declarative code which focuses on producing the right elements, while Crank does the dirty work of managing stateful components and mutating the DOM. ```jsx renderer.render( diff --git a/website/guides/05-special-tags-and-props.md b/website/guides/05-special-tags-and-props.md index b5dd6fdc1..96f356d31 100644 --- a/website/guides/05-special-tags-and-props.md +++ b/website/guides/05-special-tags-and-props.md @@ -4,7 +4,9 @@ title: Special Props and Tags The element diffing algorithm used by Crank is both declarative and efficient, but there are times when you might want to tweak the way it works. Crank provides special props and tags which produce different rendering behaviors. -## Special Props +## Special Crank Props +### children + ### crank-key By default, Crank will use an element’s tag and position to determine if it represents an update or a change to the tree. Because elements often represent stateful DOM nodes or components, it can be useful to *key* the children of an element to hint to renderers that an element has been added, moved or removed. In Crank, we do this with the special prop `crank-key`: @@ -71,6 +73,19 @@ console.log(document.body.innerHTML); console.log(document.firstChild.firstChild === span); // true ``` +### crank-ref +TKTKTKTKTKTKTKTKTKTKTKTK + +## Special DOM Props +### style + +### innerHTML + +### onevent + +### class/className + + ## Special Tags Crank provides several element tags which have special meaning when rendering. In actuality, these tags are symbols and behave similarly to string tags, except they affect the diffing algorithm and output. diff --git a/website/guides/06-lifecycles.md b/website/guides/06-lifecycles.md index 52c2021e8..975912b8a 100644 --- a/website/guides/06-lifecycles.md +++ b/website/guides/06-lifecycles.md @@ -2,7 +2,11 @@ title: Lifecycles --- -Crank uses the full power and expressiveness of generator functions to encapsulate the notion of lifecycles within the same variable scope. Internally, Crank achieves this by calling the `next`, `return` and `throw` methods of the generator object as components are inserted, updated and removed from the element tree. As a developer, you can use the `yield`, `return`, `try`, `catch`, and `finally` keywords within your generator components to take full advantage of the generator’s natural lifecycle. +Crank uses generator functions rather than hooks or classes to define component lifecycles. Internally, this is achieved by calling the `next`, `return` and `throw` methods of the generator object as components are inserted, updated and removed from the element tree. As a developer, you can use the `yield`, `return`, `try`, `catch`, and `finally` keywords within your generator components to take full advantage of the generator’s natural lifecycle. + +## A review of generator functions + +TODO ## Returning from a generator diff --git a/website/guides/07-working-with-typescript.md b/website/guides/07-working-with-typescript.md index 15d6e79b7..77dd43e6f 100644 --- a/website/guides/07-working-with-typescript.md +++ b/website/guides/07-working-with-typescript.md @@ -96,7 +96,7 @@ function Greeting ({name, children}: {name: string, children: Children}) { ``` ## Typing event listeners -If you dispatch custom events, you’re going to want parent event listeners to be typed with the event you bubbled automatically. To do so, you can extend a global `EventMap` type provided by Crank. +If you dispatch custom events, you may want parent event listeners to be typed with the event you bubbled automatically. To do so, you can use module augmentation to extend the `EventMap` interface provided by Crank. ```tsx declare global { @@ -121,3 +121,7 @@ function MyButton (props) { ); } ``` + +## Typing provisions +By default, calls to `Context.prototype.get` and `Context.prototype.set` will be loosely typed. If you want stricter typings of these methods, you can use module augmentation to extend the `ProvisionMap` interface provided by Crank. + diff --git a/website/guides/08-api-reference.md b/website/guides/08-api-reference.md deleted file mode 100644 index a0da10166..000000000 --- a/website/guides/08-api-reference.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -title: API Reference -publish: false ---- -## API Reference -TODO - -## `@bikeshaving/crank` -### `createElement` -### `Element` -### `Child` -### `Context` -### `Renderer` -## `@bikeshaving/crank/dom` -### `DOMRenderer` -### `renderer` -## `@bikeshaving/crank/html` -### `HTMLRenderer` -### `renderer` diff --git a/website/guides/08-reusable-logic.md b/website/guides/08-reusable-logic.md new file mode 100644 index 000000000..4d1b9985b --- /dev/null +++ b/website/guides/08-reusable-logic.md @@ -0,0 +1,19 @@ +--- +title: Reusable Logic +--- + +## Additional Context methods + +### Provisions + +### Scheduling and cleanup + +## Strategies for writing Crank libraries + +### Global Context extensions + +### Context helper factories + +### Higher-order components + +### Async iterators diff --git a/website/guides/09-custom-renderers.md b/website/guides/09-custom-renderers.md new file mode 100644 index 000000000..561e7b546 --- /dev/null +++ b/website/guides/09-custom-renderers.md @@ -0,0 +1,142 @@ +--- +title: Custom Renderers +--- + +The core Crank module provides an abstract `Renderer` class which can be extended to produce more than just DOM nodes or HTML strings, allowing you to target alternative environments such as WebGL-based canvas libraries, terminals, smartphones or smart TVs. This guide provides an overview of the concepts and internal methods which you will need to know when implementing a custom renderer yourself. Alternatively, you can read through the [DOM](https://github.com/bikeshaving/crank/blob/master/src/dom.ts?ts=2) and [HTML](https://github.com/bikeshaving/crank/blob/master/src/html.ts?ts=2) renderer implementations to learn by example. + +**Warning:** The custom renderer API is currently unstable both because of its performance-sensitive nature and because the exact complications of rendering to a wide variety of environments are not yet fully known. If you maintain a Crank renderer, you *will* have to deal with breaking changes as Crank is optimized and as new renderer requirements are discovered. + +## The lifecycle of an element + +Crank does not provide lifecycle methods or hooks as part of its public interface, instead opting to rely on the natural lifecycle of generator functions. However, we use common lifecycle terms like *mounting*, *updating*, *unmounting* and *committing* internally to conceptualize the process of rendering. + +Rendering is essentially a depth-first walk of an element tree, where we recursively compare new elements to what was previously rendered. When elements are new to the tree, we “mount” the element, when elements have been seen before, we “update” the element, and when elements no longer exist in the tree, they are “unmounted.” + +“Committing” is the part of rendering process where we actually perform the operations which create, mutate and dispose of nodes. Elements are committed in a *post-order traversal* of the tree, meaning that by the time a specific element is committed, all of its children will have already committed as well. This is done so that rendering side-effects happen all at once, even if there are async components in the tree, which leads to a more consistent and performant user experience. By contrast, components can be thought of as executing in a *pre-order traversal* of the tree, because the only way to get the children of a component element is to execute its component. + +## Types associated with the Renderer class + +**Note:** Renderer development is considerably more abstract than application development, and using the TypeScript types provided by Crank can make this process much easier to understand. Therefore, this guide both assumes familiarity with TypeScript and uses TypeScript syntax in its examples. + +The renderer class takes four type parameters which describe the types of values as they flow in and out of the custom renderer methods. + +```ts +class Renderer< + TNode extends object, + TScope = unknown, + TRoot extends object = TNode, + TResult = ElementValue +> +``` + +- `TNode` is the most important type: it is the type of the node associated with each host element. For instance, for the basic DOM renderer, TNode is the [DOM Node interface](https://developer.mozilla.org/en-US/docs/Web/API/Node). +- `TScope` is the type of the *scope*, a renderer-specific concept for arbitrary data which can passed down the tree between host elements. Scopes are useful for passing contextual information down the tree to be used when nodes are created; for instance, the DOM renderer uses the scope to pass down information about whether we’re currently rendering in an SVG element. +- `TRoot` is the type of the root node. This is the type of the second parameter passed to the `Renderer.render` method, and the `root` prop passed to `Portal` elements. It is usually the same type as `TNode` but can vary according to renderer requirements. +- `TResult` describes the type of values made visible to renderer consumers. Any time Crank exposes an internal node, for instance, via the `crank-ref` callback, or as the result of yield expressions in generator components, the renderer can intercept this access and provide something other than the internal nodes, allowing renderers to hide implementation details and provide results which make more sense for a specific environment. + For example, the HTML string renderer has an internal node representation, but converts these nodes to strings before they’re exposed to consumers. This is done because the internal nodes must be referentially unique and mutated during rendering, while JavaScript strings are referentially transparent and immutable. Therefore, the `TResult` of the `HTMLRenderer` subclass is `string`. + +## Methods +The following is a description of the signatures of internal renderer methods and when they’re executed. + +### Renderer.prototype.create + +```ts +create( + tag: string | symbol, props: Record, scope: TScope +): TNode; +``` + +The `create` method is called for each host element the first time the element is committed. The tag and props parameters are the tag and props of the host element which initiated this call, and the scope is the current scope of the element. The return value is the node which will be associated with the host element. + +By default, this method will throw a `Not Implemented` error, so custom renderers should always implement this method. + +By default, escape returns the same string that was passed in. + +### Renderer.prototype.patch +```ts +patch( + tag: string | symbol, props: Record, node: TNode, scope: TScope, +): unknown; +``` + +The `patch` method is called for each host element whenever it is committed. The tag and props are the tag and props of the associated host element, the node is the value produced by the `create` method when the value was mounted, and the scope is the current scope of the element. + +This method is useful for mutating nodes whenever the host element is committedk. It is optional and its return value is ignored. + +### Renderer.prototype.arrange +```ts +arrange( + tag: string | symbol, props: Record, parent: TNode | TRoot, children: Array +): unknown; +``` + +The `arrange` method is called for each host element whenever it is committed. The tag and props are the tag and props of the associated host element, the parent is the value created by the create method for a host node. The `arrange` is also called for every root/portal element, so parent can be the second parameter passed to `Renderer.render`, or the `root` prop passed to `Portal` elements. The `children` of `arrange` is always an array of nodes and strings, which are determined by the related parent element’s children. + +In addition to being called when a host or portal element is committed, the `arrange` method can also be called as the last step of a component `refresh`. Because the component’s children may have changed, the nearest ancestor host or portal element has to be rearranged so that the parent node can handle the new children. + +This method is where the magic happens, and is useful for connecting the nodes of your target environment in tree form. If your target environment has a separate It is optional and the return value is ignored. + +### Renderer.prototype.scope +```ts +scope( + tag: string | symbol, props: Record, scope: TScope | undefined +): TScope; +``` + +The `scope` method is called for each host or portal element as elements are mounted or updated. Unlike the other custom renderer methods, the `scope` method is called during the pre-order traversal of the tree, much like components are. The `scope` method is passed the tag and props of the relevant host element, as well as the current scope, and the return value becomes the scope which is passed to the `create` and `scope` methods which are called for child elements. + +### Renderer.prototype.escape +```ts +escape(text: string, scope: TScope): string; +``` + +The `escape` method is called whenever a string is encountered in the element tree. It is mainly useful when creating string-based renderers like HTML or XML string renderers, because most rendering targets like the DOM provide text node interfaces which sanitize inputs by default. One important detail is that `escape` should not return text nodes or anything besides a string. We defer this step to the `arrange` method because this allows us to normalize a host element’s children by concatenating adjacent strings before it is passed to `arrange`. + +By default, the `escape` method returns the string which was passed in. + +### Renderer.prototype.parse +```ts +parse(text: string, scope: TScope): TNode | string; +``` + +When a `Raw` element is committed, if its `value` prop is a string, we call the `parse` method with that string and the current scope. The return value is the parsed node, or it can be a string as well, in which case parse will be handled like a string child by parents. The `escape` method will not be called on the return value. + +By default, the `parse` method returns the string which was passed in. + +### Renderer.prototype.dispose +```ts +dispose( + tag: string | symbol, props: Record, node: TNode +): unknown +``` + +When a host element is unmounted, we call the `dispose` method with the related host element’s tag, props and node. This method is useful if you need to manually release a node or remove event listeners from it for garbage collection purposes. + +This method is optional and its return value is ignored. + +### Renderer.prototype.complete +```ts +complete(root: TRoot): unknown; +``` + +The `complete` method is called at the end of every render execution, when all elements have been committed and all other renderer methods have been called. It is useful, if your rendering target needs to be manually rerendered before any node mutations or rearrangements take effect. + +This method is optional and its return value is ignored. + +### Renderer.prototype.read +```ts +read(value: Array | TNode | string | undefined): TResult; +``` + +The renderer will expose rendered values in the following places: + +- As the return value of `Renderer.prototype.render` +- As the return value of `Context.prototype.refresh` +- As the argument passed to `crank-ref` props +- As the argument passed to `Context.prototype.schedule` and `Context.prototype.cleanup` +- Via the `Context.prototype.value` getter +- As the yield value of generator components + +When an element or elements are read in this way, we call the `read` method to give renderers a chance to manipulate what is exposed so as to hide internal implementation details and return something which makes sense for the target environment. The parameter passed to read can be a node, a string, undefined, or an array of nodes and strings. The return value is what is actually exposed. + +This method is optional. By default, read is an identity function which returns the value passed in. diff --git a/website/guides/999999999-differences-from-react.md b/website/guides/10-differences-from-react.md similarity index 99% rename from website/guides/999999999-differences-from-react.md rename to website/guides/10-differences-from-react.md index 60fd8d7e2..f454b266c 100644 --- a/website/guides/999999999-differences-from-react.md +++ b/website/guides/10-differences-from-react.md @@ -1,6 +1,5 @@ --- title: Differences from React -publish: false --- Though Crank is very much inspired by and similar to React, exact compatibility is a non-goal, and we’ve used this as opportunity to “fix” a lot of pain points with React which bothered us over the years. The following is a list of differences with React, as well as justifications for why we chose to implement features differently. diff --git a/website/guides/11-api-reference.md b/website/guides/11-api-reference.md new file mode 100644 index 000000000..e80ca36a8 --- /dev/null +++ b/website/guides/11-api-reference.md @@ -0,0 +1,244 @@ +--- +title: API Reference +--- + +## Types +### `Tag` +A type which represents all valid values which can be used for the tag of an element. + +**Example:** + +```ts +let tag: Tag; + +// VALID ASSIGNMENTS +tag = "div"; +tag = Symbol("div"); +function MyComponent() { +} +tag = MyComponent; + +// INVALID ASSIGNMENTS +// @ts-expect-error +tag = 1; +// @ts-expect-error +tag = {}; +``` + +### `TagProps` +A helper type to map the tag of an element to its expected props. + +**Type Parameters:** +- `TTag` - The tag of the associated element. + +**Example:** +```ts +function Greeting({name}: {name: string}) { + return
Hello {name}
+} + +let props: TagProps; + +// VALID ASSIGNMENTS +props = {name: "Alice"}; + +// INVALID ASSIGNMENTS +// @ts-expect-error +props = {name: 1000}; +``` + +### `Child` +A type which describes all valid singular values of an element tree. + +**Example:** +```ts +let child: Child; + +// VALID ASSIGNMENTS +child = "hello"; +child = 1; +child = true; +child = false; +child = null; +child = undefined; +child =
Hello
; + +// INVALID ASSIGNMENTS +// @ts-expect-error +child = [
Hello
,
World
]; +// @ts-expect-error +child = {}; +// @ts-expect-error +child = new Promise(() => {}); +// @ts-expect-error +child = new RegExp("Hello"); +// @ts-expect-error +child = Symbol("Hello"); +``` + +### `Children` +A type which describes all valid values of an element tree, including arbitrarily nested iterables of such values. + +**Example:** +```ts +let children: Children; + +// VALID ASSIGNMENTS +children = "hello"; +children = 1; +children = true; +children = false; +children = null; +children = undefined; +children =
Hello
; +children = [
Hello
,
World
]; +children = new Set([
Hello
,
World
]); + +// INVALID ASSIGNMENTS +// @ts-expect-error +children = {}; +// @ts-expect-error +children = new RegExp("Hello"); +// @ts-expect-error +children = Symbol("Hello"); +``` + +### `Component` +A type which represents all functions which can be used as a component. + +**Type Parameters:** +- `TProps` - The expected props of the component + +### `ElementValue` +A helper type which repesents all the possible rendered values of an element. + +**Type Parameters:** +- `TNode` - The node type for the element as created by the renderer. + +### `EventMap` +An interface which maps Event type strings to event subclasses. Can be extended via TypeScript module augmentation for strongly typed event listeners. + +**Example:** +```ts +declare global { + module "@bikeshaving/crank" { + interface EventMap { + click: MouseEvent; + } + } +} +``` + +### `ProvisionMap` +An interface which can be extended to provide strongly typed provisions. See `Context.prototype.get` and `Context.prototype.set`. + +**Example:** +```ts +declare global { + module "@bikeshaving/crank" { + interface ProvisionMap { + greeting: string; + } + } +} +``` + +## Special Tags +### `Fragment` +A special element tag for grouping multiple children within a parent. + +### `Portal` +A special element tag for creating a new element subtree with a different root, passed via the root prop. + +**Props:** +- root - TODO + +### `Copy` +A special element tag which copies whatever child appeared previously in the element’s position. + +### `Raw` +A special element tag for injecting raw nodes into an element tree via its value prop. + +**Props:** +- value - TODO + +## Functions +### `createElement` +**Parameters:** +- `tag: string | symbol | Component` - TODO +- `props: Record` - TODO +- `...children: Children` - TODO + +Creates an element with the specified tag, props and children. This function is typically used as a target for JSX transpilers, rather than called directly. + +### `isElement` +**Parameters:** +- `value: unknown` - The value to be tested. + +Returns true if the value passed in is a Crank Element. + +### `cloneElement` +**Parameters:** +- `element: Element` - The Element to be cloned. + +Clones the passed in element. + +**Remarks:** +Throws a `TypeError` if the value passed in is not an element. + +## Classes +### `Element` +Elements are the basic building blocks of Crank applications. They are JavaScript objects which are interpreted by renderers to produce and manage stateful nodes. + +#### Properties +- `tag` +- `props` +- `key` +- `ref` + +#### Methods +- [`constructor`](#Element.constructor) + +### `Renderer` +An abstract class which is subclassed to render to different target environments. This class is responsible for kicking off the rendering process, caching previous trees by root, and creating/mutating/disposing the nodes of the target environment. + +**Type Parameters:** +- `TNode` +- `TScope` +- `TRoot` +- `TResult` + +#### Methods +- [`constructor`](#Renderer.constructor) + Creates a new Renderer. The base `Renderer` constructor accepts no parameters. + +- [`render`](#Renderer.render) + **Parameters:** + - `children: Children` + - `root?: TRoot` + - `ctx?: Context` + +**NOTE:** The internal Crank renderer methods documented in the [guide on custom renderers.](./custom-renderers) + +### `Context` +A class which is instantiated and passed to every component function as its `this` value. + +**Type Parameters:** +- `TProps` - The expected props of the related component. +- `TResult` - The expected result from rendering the component. + +#### Properties +- `props` - **Readonly** The current props of the component. +- `value` - **Readonly** The current rendered value of the component. + +#### Methods +- [`[Symbol.iterator]`](#Context[Symbol.iterator]) +- [`[Symbol.asyncIterator]`](#Context[Symbol.asyncIterator]) +- [`get`](#Context.get) +- [`set`](#Context.set) +- [`refresh`](#Context.refresh) +- [`schedule`](#Context.schedule) +- [`cleanup`](#Context.cleanup) +- [`addEventListener`](#Context.addEventListener) +- [`removeEventListener`](#Context.removeEventListener) +- [`dispatchEvent`](#Context.dispatchEvent) From bfd909c1a66fff1496413467ed2370c42ffd7143 Mon Sep 17 00:00:00 2001 From: Brian Kim Date: Sat, 11 Jul 2020 21:52:19 -0400 Subject: [PATCH 03/26] drafting some stuff --- README.md | 20 +- website/build.tsx | 2 +- website/guides/07-reusable-logic.md | 256 ++++++++++++++++++ website/guides/08-reusable-logic.md | 19 -- ...cript.md => 08-working-with-typescript.md} | 0 website/webpack.tsx | 2 +- 6 files changed, 272 insertions(+), 27 deletions(-) create mode 100644 website/guides/07-reusable-logic.md delete mode 100644 website/guides/08-reusable-logic.md rename website/guides/{07-working-with-typescript.md => 08-working-with-typescript.md} (100%) diff --git a/README.md b/README.md index 1b4be75d6..041718b7f 100644 --- a/README.md +++ b/README.md @@ -4,15 +4,23 @@ Write JSX-driven components with functions, promises and generators. Documentation is available at [crank.js.org](https://crank.js.org). Crank.js is in a beta phase, and some APIs may change. To read more about the motivations for this library, you can read the [introductory blog post](https://crank.js.org/blog/introducing-crank). ## Features -### Declarative components -Crank uses the same JSX syntax and diffing algorithm popularized by React, allowing you to write HTML-like code directly in your JavaScript. +### Declarative +Crank uses the same JSX syntax and diffing algorithm popularized by React, allowing you to write HTML-like syntax directly in your JavaScript. - -### Just JavaScript™ +### Just Functions All components in Crank are just functions or generator functions. No classes, hooks, proxies or template languages are needed. -### Promises today -Crank provides first-class support for promises. You can use async/await directly in components, and race components to display fallback UIs. +### Promise-fluent +Crank provides first-class support for promises. You can use async/await directly in components, and race async components to display fallback UIs. + +### Lightweight +Crank has no dependencies, and its core is a single file. It currently measures at [4.5KB minified and gzipped](https://bundlephobia.com/result?p=@bikeshaving/crank). + +### Performant +[According to synthetic benchmarks](https://krausest.github.io/js-framework-benchmark/current.html), Crank beats React in terms of execution time and memory usage. It‘s current performance is comparable to Preact and Vue. + +### Extensible +TKTKTK WRITE ABOUT LIBRARY PATTERNS AND CUSTOM RENDERERS. ## Installation Crank is available on [NPM](https://npmjs.org/@bikeshaving/crank) in the ESModule and CommonJS formats. diff --git a/website/build.tsx b/website/build.tsx index 378ef5e8e..b2a01baaa 100644 --- a/website/build.tsx +++ b/website/build.tsx @@ -205,7 +205,7 @@ function Home(): Element {

-

Just JavaScript™

+

Just Functions

All components in Crank are just functions or generator functions. No classes, hooks, proxies or template languages are needed. diff --git a/website/guides/07-reusable-logic.md b/website/guides/07-reusable-logic.md new file mode 100644 index 000000000..a64a42b7c --- /dev/null +++ b/website/guides/07-reusable-logic.md @@ -0,0 +1,256 @@ +--- +title: Reusable Logic +--- + +## Additional Context properties and methods +Crank provides additional utilities via Contexts to help you write utilities and share logic between components via plugins and extensions. Most of the APIs here are mainly for library authors and should not be used in the course of typical component development. + +### Provisions +Crank allows you to provide data to all of a component’s descendants via the methods `Context.prototype.get` and `Context.prototype.set`. The `set` method sets a “provision” under a specific key, and the `get` method. + +```ts +function GreetingProvider({greeting, children}) { + this.set("greeting", greeting); + return children; +} + +function Greeting({name}) { + const greeting = this.get("greeting"); + return

{greeting}, {name}

; +} + +function* App() { + return ( +
+ +
+ ); +} + +renderer.render( + + + , + document.body, +); + +console.log(document.body); // "

Hello, Brian

" +``` + +Provisions are useful for when you want to coordinate components based on their relationships within the element tree. Provisions allow libraries to define components which interact with their descendants without rigidly defined component hierarchies or requiring the library user to pass data manually between components as props. Anything can be a key for the `get` and `set` methods, so you can use a symbol to ensure that the provision you pass between components are private and do not collide with contexts set by others. Crank does not link “providers” and “consumers” in any way, and doesn’t automatically refresh components when `set` is called, so it’s up to you to make sure consumers update when providers update. + +### Callback-based methods +Contexts provides two utility methods which take callbacks: + +#### `Context.prototype.schedule` +You can pass a callback to the `schedule` method to listen for when the component commits. This can be deferred if the component runs asynchronously or has async children. + +```ts +EXAMPLE TKKTKTKTK +``` + +Callbacks passed to `schedule` fire synchronously after the component commits, with the rendered value of the component as its only parameter. They only fire once per call and callback function (think `requestAnimationFrame`, not `setInterval`). This means you have to continuously call the `schedule` method for each update if you want to execute some code every time your component commits. + +#### `Context.prototype.cleanup` +Similarly, you can pass a callback to the `cleanup` method to listen for when the component unmounts. + +```ts +EXAMPLE TKTKTKTKTK +``` + +All `cleanup` callbacks fire synchronously when the component is removed, and only once per registered callback function. + +### Helpful context getters + +#### `Context.prototype.props` +The current props of a component can be accessed as via the readonly context property `props`. We recommended that you access props within components via component parameters or context iterators, but the `props` property can be useful when you need to access a component’s current props from within a plugin or extension. + +#### `Context.prototype.value` +Similarly, the most recently rendered value of a component is accessible via the readonly context property `value`. Again, we recommended that you access rendered values via the many methods described in [accessing rendered values](#KTKTKTKTK), but it can be useful to access the current value directly when writing helper context methods. + +## Strategies for writing Crank libraries + +The following are various patterns you can use to write and reuse logic between components, as well as a description of their tradeoffs. We will be wrapping [`window.setInterval`](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setInterval) in examples to demonstrate each design pattern. + +### Global Context Extensions +You can import and extend the Context class’s prototype to globally extend all contexts in your application. + +```ts +import {Context} from "@bikeshaving/crank"; + +const ContextIntervalSymbol = Symbol.for("ContextIntervalSymbol"); + +Context.prototype.setInterval = function(callback, delay, ...args) { + const interval = window.setInterval(callback, delay, ...args); + if (typeof this[ContextIntervalSymbol] === "undefined") { + this[ContextIntervalSymbol] = new Set(); + this.cleanup(() => { + for (const interval of this[ContextIntervalSymbol]) { + window.clearInterval(interval); + } + }); + } + + this[ContextIntervalSymbol].add(interval); +}; + +Context.prototype.clearInterval = function(interval) { + if (typeof this[ContextIntervalSymbol] !== "undefined") { + this[ContextIntervalSymbol].delete(interval); + } + + window.clearInterval(interval); +} + +function *Counter() { + let seconds = 0; + this.setInterval(() => { + seconds++; + this.refresh(); + }, 1000); + + while (true) { + yield
Seconds: {seconds}
; + } +} +``` + +In this example, we define the methods `setInterval` and `clearInterval` directly the `Context` prototype. The example also demonstrates caching intervals on a set which is hidden using an unexported symbol. You can use symbols to hide the internal state of your global context extensions from users. + +**Pros:** +- Methods are available to every component automatically. + +**Cons:** +- Globally scoped. +- No way to write setup logic. +- No way to respond to props updates. + +**Use cases:** +Global context extensions are useful for creating Crank-specific wrappers around already global, well-known APIs like `setInterval`, `requestAnimationFrame` or `fetch`. + +### Context helper factories +As an alternative to global context extensions, you can write factory functions which are passed contexts to scope your logic per component. + +```ts +function createSetInterval(ctx, callback, delay, ...args) { + const interval = window.setInterval(callback, delay, ...args); + ctx.cleanup(() => window.clearInterval(interval)); + return interval; +} + +function *Counter() { + let seconds = 0; + const setInterval = createSetInterval(this); + setInterval(() => { + seconds++; + this.refresh(); + }, 1000); + + while (true) { + yield
Seconds: {seconds}
; + } + +} +``` + +Instead of defining the `setInterval` method globally, we define it locally by passing the context of the component into the `createSetInterval` function. + +**Pros:** +- Locally scoped. +- Explicitly imported and referenced. +- Setup logic can be written directly in the factory function. + +**Cons:** +- Naming factory functions can be difficult. +- No way to respond to props updates. + +**Use cases:** +Context helper factories are useful when you want to write locally-scoped factories, especially if they require setup logic. Good use-cases include context-aware state management utilities or wrappers around stateful APIs like [mutation observers](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver) or [HTML drag and drop](https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API). + +### Higher-order components +Because Crank components are just functions, you can write functions which both take components as parameters and return wrapped component functions. + +```ts +function interval(Component) { + return function *WrappedComponent() { + let seconds = 0; + const interval = window.setInterval(() => { + seconds++; + this.refresh(); + }, 1000) + try { + for (const props of this) { + yield ; + } + } finally { + window.clearInterval(interval); + } + }; +} + +const Counter = interval((props) =>
Seconds: {props.seconds}
); +``` + +The interval function takes a component function and returns a component which passes the number of seconds as a prop, as well as refreshing the component whenever the interval is fired. + +**Pros:** +- Locally scoped. +- Explicitly imported and referenced. +- Able to respond to new props within the returned component. + +**Cons:** +- Naming higher-order functions can be difficult. +- JavaScript doesn’t provide an easy syntax for decorating functions. +- Props that the higher-order component pass in may clash with the component’s own expected props. + +**Use cases:** +The main advantage of higher-order components is that you can respond to props in your utilities just like you would with a component. Higher-order components are most useful when you need reusable logic which refreshes a component, like animation utilities, or modify only well-known props, like styled component libraries. + +### Async iterators +Because components can be written as async generator functions, you can integrate utility functions which return async iterators seamlessly with Crank. + +```ts +async function *createInterval(delay) { + let available = true; + let resolve; + const interval = window.setInterval(() => { + if (resolve) { + resolve(Date.now()); + resolve = undefined; + } else { + available = true; + } + }, delay); + + try { + while (true) { + if (available) { + available = false; + yield Date.now(); + } else { + yield new Promise((resolve1) => (resolve = resolve1)); + } + } + } finally { + window.clearInterval(interval); + } +} + +async function *Counter() { + let seconds = 0; + for await (const _ of createInterval(1000)) { + yield
Seconds: {seconds}
; + seconds++; + } +} +``` + +**Pros:** +- The utilities you write are framework-agnostic. +- Uniform logic to dispose of resources. + +**Cons:** +- Promises and async iterators can be prone to race conditions and deadlocks. + +**Use cases:** +If you use async iterators/generators already, Crank is the perfect fit for your application. diff --git a/website/guides/08-reusable-logic.md b/website/guides/08-reusable-logic.md deleted file mode 100644 index 4d1b9985b..000000000 --- a/website/guides/08-reusable-logic.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -title: Reusable Logic ---- - -## Additional Context methods - -### Provisions - -### Scheduling and cleanup - -## Strategies for writing Crank libraries - -### Global Context extensions - -### Context helper factories - -### Higher-order components - -### Async iterators diff --git a/website/guides/07-working-with-typescript.md b/website/guides/08-working-with-typescript.md similarity index 100% rename from website/guides/07-working-with-typescript.md rename to website/guides/08-working-with-typescript.md diff --git a/website/webpack.tsx b/website/webpack.tsx index b92d7c547..b71a386f9 100644 --- a/website/webpack.tsx +++ b/website/webpack.tsx @@ -181,7 +181,7 @@ export class Storage { .resolve(this.dir, name) .replace(new RegExp("^" + this.dir + "/"), ""); this.files = {...this.files, [name]: "./" + name}; - const stats = await this.run(); + const stats = await this.run1(); let assets = stats.assetsByChunkName![name]; if (!Array.isArray(assets)) { assets = [assets]; From 7c4f43e9591122bce38230425a60cca544d78f8c Mon Sep 17 00:00:00 2001 From: Brian Kim Date: Mon, 13 Jul 2020 14:20:51 -0400 Subject: [PATCH 04/26] tweaks --- README.md | 2 +- website/build.tsx | 10 +- ...-props.md => 05-special-props-and-tags.md} | 0 website/guides/06-lifecycles.md | 4 - website/guides/07-reusable-logic.md | 4 +- website/guides/08-working-with-typescript.md | 1 - website/guides/10-differences-from-react.md | 83 --------- website/guides/10-mapping-react-to-crank.md | 157 ++++++++++++++++++ website/src/index.css | 5 +- 9 files changed, 168 insertions(+), 98 deletions(-) rename website/guides/{05-special-tags-and-props.md => 05-special-props-and-tags.md} (100%) delete mode 100644 website/guides/10-differences-from-react.md create mode 100644 website/guides/10-mapping-react-to-crank.md diff --git a/README.md b/README.md index 041718b7f..f91808bf1 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ Crank uses the same JSX syntax and diffing algorithm popularized by React, allow ### Just Functions All components in Crank are just functions or generator functions. No classes, hooks, proxies or template languages are needed. -### Promise-fluent +### Native Promise Support Crank provides first-class support for promises. You can use async/await directly in components, and race async components to display fallback UIs. ### Lightweight diff --git a/website/build.tsx b/website/build.tsx index b2a01baaa..c5bef1acd 100644 --- a/website/build.tsx +++ b/website/build.tsx @@ -197,7 +197,7 @@ function Home(): Element {
-

Declarative components

+

Declarative Components

Crank uses the same JSX syntax and diffing algorithm popularized by React, allowing you to write HTML-like code directly in your @@ -212,11 +212,11 @@ function Home(): Element {

-

Promises today

+

Native Promise Support

- Crank provides first-class support for promises. You can use - async/await directly in components, and race components to display - fallback UIs. + Crank provides first-class support for promises. You can use async + functions as components, and race components to display fallback + UIs.

diff --git a/website/guides/05-special-tags-and-props.md b/website/guides/05-special-props-and-tags.md similarity index 100% rename from website/guides/05-special-tags-and-props.md rename to website/guides/05-special-props-and-tags.md diff --git a/website/guides/06-lifecycles.md b/website/guides/06-lifecycles.md index 975912b8a..7fab4d083 100644 --- a/website/guides/06-lifecycles.md +++ b/website/guides/06-lifecycles.md @@ -4,10 +4,6 @@ title: Lifecycles Crank uses generator functions rather than hooks or classes to define component lifecycles. Internally, this is achieved by calling the `next`, `return` and `throw` methods of the generator object as components are inserted, updated and removed from the element tree. As a developer, you can use the `yield`, `return`, `try`, `catch`, and `finally` keywords within your generator components to take full advantage of the generator’s natural lifecycle. -## A review of generator functions - -TODO - ## Returning from a generator Usually, you’ll yield in generator components so that they can continue to respond to updates, but you may want to also `return` a final state. Unlike function components, which are called and returned once for each update, once a generator component returns, it will never update again. diff --git a/website/guides/07-reusable-logic.md b/website/guides/07-reusable-logic.md index a64a42b7c..c8323cb19 100644 --- a/website/guides/07-reusable-logic.md +++ b/website/guides/07-reusable-logic.md @@ -250,7 +250,7 @@ async function *Counter() { - Uniform logic to dispose of resources. **Cons:** -- Promises and async iterators can be prone to race conditions and deadlocks. +- Promises and async iterators can cause race conditions and deadlocks, without any language-level features to help you debug them. **Use cases:** -If you use async iterators/generators already, Crank is the perfect fit for your application. +If you use async iterators/generators already, Crank is the perfect framework for your application. diff --git a/website/guides/08-working-with-typescript.md b/website/guides/08-working-with-typescript.md index 77dd43e6f..7cb340271 100644 --- a/website/guides/08-working-with-typescript.md +++ b/website/guides/08-working-with-typescript.md @@ -124,4 +124,3 @@ function MyButton (props) { ## Typing provisions By default, calls to `Context.prototype.get` and `Context.prototype.set` will be loosely typed. If you want stricter typings of these methods, you can use module augmentation to extend the `ProvisionMap` interface provided by Crank. - diff --git a/website/guides/10-differences-from-react.md b/website/guides/10-differences-from-react.md deleted file mode 100644 index f454b266c..000000000 --- a/website/guides/10-differences-from-react.md +++ /dev/null @@ -1,83 +0,0 @@ ---- -title: Differences from React ---- - -Though Crank is very much inspired by and similar to React, exact compatibility is a non-goal, and we’ve used this as opportunity to “fix” a lot of pain points with React which bothered us over the years. The following is a list of differences with React, as well as justifications for why we chose to implement features differently. - -## No classes -Crank uses functions, generator functions and async functions to implement all of what React implements with classes. Here for instance, is the entirety of the React class-based API implemented with a single async generator function: - -```jsx -async function *ReactComponent(props) { - let state = componentWillMount(props); - let ref = yield render(props, state); - state = componentDidMount(props, state, ref); - try { - for await (const newProps of this) { - if (shouldComponentUpdate(props, newProps, state, ref)) { - state = componentWillUpdate(props, newProps, state, ref); - ref = yield render(props, state); - state = componentDidUpdate(props, newProps, state, ref); - props = newProps; - } - } - } catch (err) { - componentDidCatch(err); - } finally { - componentWillUnmount(ref); - } -} -``` - -## No hooks -Crank does not implement hooks. Hooks bad. - -## No `setState` or `useState` -React has always tightly coupled component updates with local state. Because Crank uses generator functions, state is just local variables, and you can call `this.refresh()` to update the UI to match state. Decoupling these two concerns allows for more nuanced updates to local state without `shouldComponentUpdate` hacks, and is easier to reason about than relying on the framework to provide local state. - -## No `Suspense` -The project known as React `Suspense` is both sub-optimal and likely to be vaporware. It relies on the unusual mechanism of throwing promises, has the hard requirement of a caching mechanism, and is generally difficult to reason about. By leveraging async functions and async generators, Crank allows you to implement the `Suspense` element in user space. No argument from the React team about the necessity of `Suspense` will ever justify it over the convenience provided by being able to use the `await` operator directly in components. - -## Props match HTML attributes rather than JS APIs -### `for` not `htmlFor`, `class` not `className` - -Crank does not place any restrictions on the names of JSX props. This means that you can write JSX like ``. -## style can be a `cssText` string, style object uses snake-case, and `px` is not magically added to numbers. -```jsx -
Hello
-``` -## No “controlled”/“uncontrolled”, “value”/“defaultValue” components. -If you don’t want your component to be updated, don’t update it. -## No `dangerouslySetInnerHTML={{__html}}` props. -Just use the `innerHTML` prop. React doesn’t do anything to make setting `innerHTML` safe; they just make you type more and search for the exact spelling of `dangerouslySetInnerHTML` every month or so. -## Fragments can have `innerHTML`. -TKTKTK update for Raw -Fragment behavior can be overridden by renderers, and both the DOM and HTML renderers allow fragments to accept an innerHTML prop, allowing arbitrarily HTML to be written without a parent. -## Portals are just a special element tag. -Their behavior is defined by renderers, and all element trees are wrapped implicitly or explicitly in a root portal. -### No `componentDidUpdate` or `React.memo`. -Crank uses the special `Copy` element to prevent child updates. -## No `React.cloneElement` -Elements are just plain-old JavaScript objects, and there is no need to use a special method to copy them. You can re-use the same elements within generators, mutate them, or use spread syntax to shallowly copy them. Crank will handle reused elements gracefully. -## No event props -Event props in React are terrible for the following reasons: - The EventTarget API takes more than just a function, it also takes options which allow you to register event listeners in the `capture` phase, register `passive` listeners, or register event listeners `once`. React has attempted to allow event handlers to be registered in the capture phase by adding props like `onClickCapture`, but embedding all these options in the prop name would be madness (`onClickCaptureOncePassive`). By emulating the event target API, Crank provides the full power of the `EventTarget` API. -## Stop doxxing event listeners. -Event listeners are difficult to name and make the most sense as anonymous functions which are made meaningful by the `type` it’s associated with. React developers often adopt a naming scheme to cache event listeners on component instances like `this.handleClick` to avoid `PureComponent`/`componentDidUpdate` de-optimizations, but if they only had to be defined once, we wouldn’t have to do this. Generator functions naturally allow components to define anonymous event listeners once when the component mounts, and the event target API provided by Crank automatically unregisters these listeners when components are thrown away. This means you never have to reason about when these functions are redefined or what they are capturing in their closures. -## Custom events are events, and they can be prevented or bubbled like regular DOM events. -When attempting to define custom event handler props in React, React developers will typically mimic the component props API and allow callbacks to be passed into the component, which the component author will then call directly when they want to trigger the event. This is a painful to do, because you often have to make sure the callback is defined on props, because they are usually optional, and then React developers will also arbitrarily pass data to the callback which is not an event, making custom `onWhatever` props disanalogous with DOM event props, because DOM event props call callbacks with an event. There is no standard for what event-like callback props are called with in React, and there is no way for components to allow parents to prevent default behavior by calling `ev.preventDefault` as you would with a regular DOM event. Worst of all, these props must be passed directly from parent to child, so if a component wants to listen to an event in a deeply nested component, event handlers must either be passed using React contexts, or passed explicitly through each component layer, at each layer renamed to make sense for each nested component API. - -Crank avoids this sitution by mimicking the DOM EventTarget API, and allowing developers to create and bubble real `Event` or `CustomEvent` objects with `this.dispatchEvent`. These events can be namespaced, typed, and components can allow parents to cancel events. -## No refs - React’s `ref` API has undergone multiple revisions over the years and it’s only gotten more confusing/difficult to use. Crank passes rendered DOM nodes and strings back into generator functions, so you can access them by reading the result of `yield` expressions in generators. You can assign these “refs” to local variables and treat them as you would any local variable without worry. -## Children can contain any kind of iterable, not just arrays. - There’s no reason to restrict children in JSX elements to just arrays. You can interpolate ES6 Maps, Sets or any other user-defined iterable into your Crank elements, and Crank will simply render them in an implicit `Fragment`. -## Keys -### key has been named to `crank-key`. -In React, the `key` prop is special and erased from the props visible to components. Insofar as `key` is a common word, Crank namespaces `key` as `crank-key`. -### The elements of iterables don’t require unique keys. -Pestering the user to add unique keys to every element of an array is not something Crank will ever do, insofar as most of the time, developers just set the `key` to an `index` into the array. If the developer needs state to be preserved, they will eventually discover that it isn’t preserved in the course of normal development and add a key. -## No render props -Crank strongly discourages the React idiom of passing a function as children (or any other prop for that matter. “Render props” produce ugly and non-sensical syntax, and were a direct response to the lack of composability of React class components. Most if not all “render prop” patterns are easier to implement in Crank with the use of higher-order components which are passed component functions and props and produce elements of their own accord. -## Contexts -A feature equivalent to React Contexts is planned but not yet implemented. diff --git a/website/guides/10-mapping-react-to-crank.md b/website/guides/10-mapping-react-to-crank.md new file mode 100644 index 000000000..a3c81a241 --- /dev/null +++ b/website/guides/10-mapping-react-to-crank.md @@ -0,0 +1,157 @@ +--- +title: Mapping React to Crank +unpublished: true +--- + +Though Crank is inspired by React, compatibility is a non-goal, and certain concepts may be implemented using different, non-compatible APIs. The following is a reference for React developers to help them map React concepts APIs to their equivalents in Crank. + +## Class Components +Crank uses functions exclusively for components; it does not provide a class-based component API. You can emulate most of React’s Component class API with the natural lifecycle of an async generator function: + +```jsx +async function *ReactComponent(props) { + let state = componentWillMount(props); + let ref = yield render(props, state); + state = componentDidMount(props, state, ref); + try { + for await (const newProps of this) { + if (shouldComponentUpdate(props, newProps, state, ref)) { + state = componentWillUpdate(props, newProps, state, ref); + ref = yield render(props, state); + state = componentDidUpdate(props, newProps, state, ref); + props = newProps; + } else { + yield ; + } + } + } catch (err) { + componentDidCatch(err); + } finally { + componentWillUnmount(ref); + } +} +``` + +This is pseudocode which demonstrates where the methods of React would be called relative to an async generator component. Refer to the [guide on lifecycles](./lifecycles) for more information on using generator functions. + +### `setState` +Crank uses generator functions and local variables for local state. Refer to [the section on stateful components](#TTKTKTKTKTKTKTK). + +### `forceUpdate` +Crank is not “reactive” in the same sense as React, in that it does not actually know when your component’s local state is updated. You can either use `Context.prototype.refresh` to manually refresh the component, much like `forceUpdate`, or you can use async generator components, which refresh automatically whenever the async generator object fulfills. + +### `defaultProps` +Crank doesn’t have a `defaultProps` implementation. Instead, you can provide default values when destructuring props. + +### `shouldComponentUpdate` +Components themselves do not provide a way to prevent updates to themselves. Instead, you can use `Copy` elements to prevent the rerendering of a specific subtree. [Refer to the description of `Copy` elements](#TTKTKTK) for more information. + +### `componentWillMount` and `componentDidMount` +Setup code can simply be written at the top of a generator component. + +### getDerivedStateFromProps`, `componentWillUpdate` and `getSnapshotBeforeUpdate` +Code which compares old and new props or state and performs side-effects can be written directly in your components. See the section on [`prop updates`](#TK) for examples of comparing old and new props. Additionally, check out the [`Context.prototoype.schedule`](#TKTKTK), which takes a callback which is called whenever the component commits. + +### `componentWillUnmount` +You can use `try`/`finally` to run code when the component is unmounted. You can also use the [`Context.prototype.cleanup`] method if you’re writing extensions which don’t run in the main execution of the component. + +### `componentDidUpdate` +Crank uses the special `Copy` element to prevent child updates. See the guide on the `Copy` element to see how you might reimplement `React.memo` directly in user space. + +### Error Boundaries/`componentDidCatch` +To catch errors which occur in child components, you can use generator components and wrap `yield` operations in a `try`/`catch` block. Refer to [the relevant section in guides](#TK). + +## Hooks +Crank does not implement any APIs similar to React Hooks. The following are alternatives to specific hooks. + +### `useState` and `useReducer` +Crank uses generator functions and local variables for local state. Refer to [the section on stateful components](#TTKTKTKTKTKTKTK). + +### `useEffect` and `useLayoutEffect` +Crank does not have any requirements that rendering should be “pure.” In other words, you can trigger side-effects directly while rendering because Crank does not execute components more times than you might expect. + +### `useMemo`/`useCallback` +TTKTK + +### `useImperativeHandle` +Crank does not yet have a way to access component instances, and parent components should not access child contexts directly. An imperative wrapper which uses web components is planned. + +### Custom Hooks +The main appeal of hooks for library authors is that you can encapsulate shared logic in hooks. Refer to the [guide on reusable logic](./reusable-logic) for some patterns for reusing logic between components. + +## Suspense and Concurrent Mode +Crank does not implement any sort of Suspense-like API. As an alternative, you can use async functions and async generator functions directly. See the [guide on async components](./async-components) for an introduction to async components, as well as a demonstration of how you can implement the `Suspense` component directly in user space. + +## `PropTypes` +Crank is written in TypeScript, and you can add type checking to component elements by typing the props parameter of the component function. + +## Fragments and array children + There’s no reason to restrict children in JSX elements to just arrays. You can interpolate ES6 Maps, Sets or any other user-defined iterable into your Crank elements, and Crank will simply render them in an implicit `Fragment`. + +## `React.cloneElement` +You can clone elements using the `cloneElement` function. + +## `ReactDOM.createPortal` +The `createPortal` function is replaced by a special `Portal` element, whose behavior and expected props varies according to the target rendering environment. Refer to [the guide on the `Portal` element](#KTKTKTKT) for more information. + +## `React.memo` +You can use `Copy` elements to implement `React.memo` in user space: +```jsx +function equals(props, newProps) { + for (const name in {...props, ...newProps}) { + if (props[name] !== newProps[name]) { + return false; + } + } + + return true; +} + +function memo(Component) { + return function *Wrapped({props}) { + yield ; + for (const newProps of this) { + if (equals(props, newProps)) { + yield ; + } else { + yield ; + } + + props = newProps; + } + }; +} +``` + +See [the guide on component elements](#TKTKTKTK) for more information. + +## DOM props +### `htmlFor` and `className` +Crank does not place any restrictions on the names of props. This means that you can write JSX like ``. + +### The `style` prop. +```jsx +
Hello
+``` + +The `style` prop value can be a `cssText` string, or an object, similar to React. Unlike React, the CSS property names match the case of their CSS equivalents, and we do not add units to numbers. + +### `onevent` props +Crank provides `onevent` props, but they work using the DOM `onevent` props. Crank also provides an `EventTarget` API for components which adds and removes event listeners from the top-level nodes of each component. In both cases, Crank does not use a synthetic event API or shim events. See [the guide on events for more information](#TKTKTKTKTK). + +### Controlled and Uncontrolled Props +Crank does not have a concept of controlled or uncontrolled props, and does not have `defaultValue` props. + +### `dangerouslySetInnerHTML` +Crank elements accept an `innerHTML` prop. Alternatively, you can use the special `Raw` tag to insert arbitrary HTML strings or even nodes in the tree. [See the guide on the `Raw` element](#TKTK) for more information. + +## Keys +Crank provides keyed rendering via the `crank-key` prop. The prop was renamed because “key” is a common word and because the prop is not passed directly into child objects. + +Keys work similarly to the way they do in React. The main difference is that Crank does not require elements which appear in arrays or iterables to be keyed. + +## Refs +Crank provides the callback ref API from React via the `crank-ref` prop. Crank does not TKTKTKT + +## React Contexts +TKTKTKTKT diff --git a/website/src/index.css b/website/src/index.css index bd404827b..9aafe0598 100644 --- a/website/src/index.css +++ b/website/src/index.css @@ -247,19 +247,20 @@ li { pre { padding: .8em; - background: #e1e4ea; margin: 1.5em 0; font-size: .9rem; + background: #e1e4ea; color: #0b2f5b; overflow: auto; tab-size: 2; } :not(pre) > code { - background: #2b303c; padding: 0 .1em; margin: 0; display: inline; + background: #e1e4ea; + color: #0b2f5b; } .token.comment, From 272d332a383e17bc045d94354160e9a6f8926be2 Mon Sep 17 00:00:00 2001 From: Brian Kim Date: Mon, 13 Jul 2020 19:36:07 -0400 Subject: [PATCH 05/26] some tweaks, moving events to its own file --- website/blog/2020-04-15-introducing-crank.md | 4 +- website/guides/02-elements.md | 24 +-- website/guides/03-components.md | 156 +++--------------- website/guides/04-handling-events.md | 130 +++++++++++++++ ...c-components.md => 05-async-components.md} | 0 ...d-tags.md => 06-special-props-and-tags.md} | 0 .../{06-lifecycles.md => 07-lifecycles.md} | 0 ...reusable-logic.md => 08-reusable-logic.md} | 0 ...cript.md => 09-working-with-typescript.md} | 0 ...om-renderers.md => 10-custom-renderers.md} | 0 ...-crank.md => 11-mapping-react-to-crank.md} | 12 +- ...1-api-reference.md => 12-api-reference.md} | 0 12 files changed, 170 insertions(+), 156 deletions(-) create mode 100644 website/guides/04-handling-events.md rename website/guides/{04-async-components.md => 05-async-components.md} (100%) rename website/guides/{05-special-props-and-tags.md => 06-special-props-and-tags.md} (100%) rename website/guides/{06-lifecycles.md => 07-lifecycles.md} (100%) rename website/guides/{07-reusable-logic.md => 08-reusable-logic.md} (100%) rename website/guides/{08-working-with-typescript.md => 09-working-with-typescript.md} (100%) rename website/guides/{09-custom-renderers.md => 10-custom-renderers.md} (100%) rename website/guides/{10-mapping-react-to-crank.md => 11-mapping-react-to-crank.md} (95%) rename website/guides/{11-api-reference.md => 12-api-reference.md} (100%) diff --git a/website/blog/2020-04-15-introducing-crank.md b/website/blog/2020-04-15-introducing-crank.md index f8649928d..22b1cfebd 100644 --- a/website/blog/2020-04-15-introducing-crank.md +++ b/website/blog/2020-04-15-introducing-crank.md @@ -18,9 +18,9 @@ I like JSX. I like the small surface area it provides compared to template langu ## Tired of the suspense -The tipping point for me was React’s perennially unready `Suspense` API, React’s solution for async rendering. For the most part, I ignored talks and articles describing Suspense, partially because the React team kept signaling that the API was in flux, but mainly because most discussions of Suspense just seemed to go over my head. I assumed they were working it out, and we’d eventually have something like async/await for React components, so I continued to incorporate React into my projects without thinking too hard about the future of React and promises. +The tipping point for me was React’s perennially unready Suspense API, React’s solution for async rendering. For the most part, I ignored talks and articles describing Suspense, partially because the React team kept signaling that the API was in flux, but mainly because most discussions of Suspense just seemed to go over my head. I assumed they would work it out, and we’d eventually have something like async/await for React components, so I continued to incorporate React into my projects without thinking too hard about the future of React and promises. -This was until I decided to explore the Suspense API for myself, when I was trying to create a React hook for usage with async iterators. I had created an async iterator library that I was proud of ([Repeater.js](https://github.com/repeaterjs/repeater)), and I wanted to figure out a way to increase adoption, not just of the library, but also of async iterators in general. The answer seemed logical: create a React hook! At the time, it seemed like every API in existence was being transformed into a React hook, and I thought it would be nice for there to be hooks which allowed developers to use async iterators within React components as well. +This was until I decided to explore the Suspense API for myself, when I was trying to create a React hook for usage with async iterators. I had created an async iterator library that I was proud of ([Repeater.js](https://github.com/repeaterjs/repeater)), and I wanted to figure out a way to increase adoption, not just of the library, but also of async iterators in general. The answer seemed logical: create a React hook! At the time, it seemed like every API in existence was being transformed into a React hook somehow, and I thought it would be nice for there to be hooks which allowed developers to use async iterators within React components as well. The result of this effort is [available on GitHub](https://github.com/repeaterjs/react-hooks), and the library is usable, but I mostly abandoned the effort and any sort of greenfield React development when I came to understand what Suspense was and how unwieldy it would have been to incorporate Suspense into the hooks I had written. As of April 2020, the mechanism behind Suspense is for components which make async calls to throw a promise while rendering to indicate that the component is doing something asynchronously. “Throw” as in the way you would “throw” an error in JavaScript with the `throw` operator. In short, React will attempt to render your components, and if a thenable is thrown in the course of rendering, React will catch it in a special parent component called `Suspense`, render a fallback if sufficient time has elapsed, and when the promise has fulfilled, attempt to render the component again. I say “as of April 2020,” because the React team has consistently said the exact details of the Suspense API might change and has used this declaration to preempt any possible criticisms of this mechanism. However, as far as I can tell, that’s how it will work, and how everyone who has written libraries featuring Suspense assumes it will work. diff --git a/website/guides/02-elements.md b/website/guides/02-elements.md index 6791772cb..ef3744321 100644 --- a/website/guides/02-elements.md +++ b/website/guides/02-elements.md @@ -5,7 +5,7 @@ title: JSX, Elements and Renderers **Note for React developers:** If you’re familiar with how JSX and elements work in React, you may want to skip ahead to the section on components. Elements in Crank work almost exactly as they do in React. -Crank is best used with [JSX](https://facebook.github.io/jsx/), an XML-like syntax extension to JavaScript. Crank is designed to work with transpilers like Babel and TypeScript out-of-box; all you need to do is enable JSX parsing, import the `createElement` function, and include a `@jsx` comment directive (`/** @jsx createElement */`). The parser will then transpile JSX into `createElement` calls. For example, in the code below, the JSX expression assigned to `el` transpiles to the `createElement` call assigned to `el1`. +Crank is best used with [JSX](https://facebook.github.io/jsx/), an XML-like syntax extension to JavaScript. It is designed to work with transpilers like Babel and TypeScript out-of-box; all you need to do is enable JSX parsing, import the `createElement` function, and include a `@jsx` comment directive (`/** @jsx createElement */`). The parser will then transpile JSX into `createElement` calls. For example, in the code below, the JSX expression assigned to `el` transpiles to the `createElement` call assigned to `el1`. ```jsx /** @jsx createElement */ @@ -16,7 +16,7 @@ const el =
An element
; const el1 = createElement("div", {id: "element"}, "An element"); ``` -The `createElement` function returns an *element*, a plain old JavaScript object. Elements on their own don’t do anything special; rather, Crank provides special classes called *renderers* which interpret elements to produce and manage DOM nodes, HTML strings, canvas scene graphs, or whatever else you can think of. Crank ships with two renderers for web development: one for managing DOM nodes, available through the module `@bikeshaving/crank/dom`, and one for creating HTML strings, available through the module `@bikeshaving/crank/html`. You can use these modules to render interactive user interfaces on the client and HTML responses on the server. +The `createElement` function returns an *element*, a JavaScript object. Elements on their own don’t do anything special; rather, Crank provides special classes called *renderers* which interpret elements to produce and manage DOM nodes, HTML strings, canvas scene graphs, or whatever else you can think of. Crank ships with two renderers for web development: one for managing DOM nodes, available through the module `@bikeshaving/crank/dom`, and one for creating HTML strings, available through the module `@bikeshaving/crank/html`. You can use these modules to render interactive user interfaces in the browser and HTML responses on the server. ```jsx /** @jsx createElement */ @@ -35,24 +35,24 @@ console.log(HTMLRenderer.render(el)); //
Hello world
![Image of a JSX element](../static/parts-of-jsx.svg) -An element can be thought of as having three main parts: a *tag*, *props* and *children*. These roughly correspond to tags, attributes and content in HTML, and for the most part, you can copy-paste HTML into JavaScript and have things work as you would expect. The main difference is that JSX has to be well-balanced like XML, so void tags must have a closing slash (`` not ``). Also, if you forget to close an element or mismatch opening and closing tags, the parser will throw an error, whereas HTML can contain unbalanced or malformed contents and mostly still work. The advantage of using JSX is that it allows arbitrary JavaScript expressions to be interpolated into elements as its tag, props or children, meaning you can use syntax which looks like HTML seamlessly within JavaScript. +An element can be thought of as having three main parts: a *tag*, *props* and *children*. These roughly correspond to tags, attributes and content in HTML, and for the most part, you can copy-paste HTML into JavaScript and have things work as you would expect. The main difference is that JSX has to be well-balanced like XML, so void tags must have a closing slash (`` not ``). Also, if you forget to close an element or mismatch opening and closing tags, the parser will throw an error, whereas HTML can contain unbalanced or malformed contents and mostly still work. The advantage of using JSX is that it allows you to interpolate JavaScript expressions as its tag, props or children. ### Tags ```jsx const intrinsicEl =
; // transpiles to: -const intrinsicEl1 = createElement("div"); +const intrinsicEl1 = createElement("div", null); const componentEl = ; // transpiles to: -const componentEl1 = createElement(Component); +const componentEl1 = createElement(Component, null); ``` -Tags are the first part of an element expression, surrounded by angle brackets, and can be thought of as the name or type of the element. By convention, JSX parsers treat lower-cased tags as strings and capitalized tags as variables. When a tag is a string, this signifies that the element will be handled by the renderer. In Crank, we call elements with string tags *intrinsic* elements, and for both of the web renderers, these elements correspond to actual HTML elements like `div` or `input`. As we’ll see later, elements can also have function tags, in which case the behavior of the element is defined by the execution of the referenced function and not the renderer. Elements with functions for tags are called *component elements*. +Tags are the first part of an element expression, and can be thought of as the “name” or “type” of the element. It is transpiled to the first argument of a `createElement` call. By convention, JSX parsers treat lower-cased tags as strings and capitalized tags as variables. When a tag is a string, this signifies that the element will be handled by the renderer. In Crank, we call elements with string tags *intrinsic* elements, and for both of the web renderers, these elements correspond to actual HTML elements like `div` or `input`. As we’ll see later, elements can also have function tags, in which case the behavior of the element is defined by the execution of the referenced function and not the renderer. Elements with functions for tags are called *component elements*. ### Props -The attribute-like `key="value"` syntax in JSX is transpiled to a single object for each element. We call this object the *props* object, short for “properties.” The value of each prop is a string if the string-like syntax is used (`key="value"`), or it can be an interpolated JavaScript value if the value is placed within curly brackets (`key={value}`). Props are used by both intrinsic and component elements to pass values into them and can be thought of as the named arguments to the tag. +The attribute-like `key="value"` syntax in JSX is transpiled to a single object for each element, and is passed as the second argument to the resulting `createElement` call. We call this object the *props* object, short for “properties.” The value of each prop is a string if the string-like syntax is used (`key="value"`), or it can be an interpolated JavaScript value if the value is placed within curly brackets (`key={value}`). Props are used by both intrinsic and component elements to “pass” values into them, similar to how you might pass values into functions as arguments when invoking them. ```jsx const myClass = "my-class"; @@ -63,7 +63,7 @@ const el1 = createElement("div", {id: "my-id", "class": myClass}); console.log(el.props); // {id: "my-id", "class": "my-class"} ``` -If you already have an object that you want to use as props, you can use the special JSX `...` syntax to “spread” it into an element. This works similarly to [ES6 spread syntax](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax) and can be useful when writing components which modify just one or two props. +If you already have an object that you want to use as props, you can use the special JSX `...` syntax to “spread” it into an element. This works similarly to [ES6 spread syntax](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax). ```jsx const props = {id: "1", src: "https://example.com/image", alt: "An image"}; @@ -73,7 +73,7 @@ const el1 = createElement("img", {...props, id: "2"}); ``` ### Children -As with HTML, Crank elements can have contents, placed between its opening and closing tags. These contents are referred to as *children*. Because elements can have children which are also elements, elements can form a nested tree of nodes. +As with HTML, Crank elements can have contents, placed between its opening and closing tags. These contents are referred to as *children*. Because elements can have children which are also elements, they form a nested tree of nodes which we call the *element tree*. ```jsx const list = ( @@ -91,7 +91,7 @@ const list1 = createElement("ul", null, console.log(list.props.children.length); // 2 ``` -By default, the contents of JSX are interpreted as strings, but you can use curly brackets just as we did with props to interpolate arbitrary JavaScript expressions into an element’s children. Besides elements, almost every value in JavaScript can participate in the element tree. Strings and numbers are rendered as text, while the values `null`, `undefined`, `true` and `false` are erased, allowing you to render things conditionally with boolean expressions. +As you can see in the example, the contents of elements which are not themselves part of element expressions are interpreted as strings. However, just as with props, you can use curly brackets to interpolate JavaScript expressions into an element’s children. Besides elements and strings, almost every value in JavaScript can participate in an element tree. Numbers are rendered as strings, and the values `null`, `undefined`, `true` and `false` are erased, allowing you to render things conditionally with boolean expressions. ```jsx const el =
{"a"}{1 + 1}{true}{false}{null}{undefined}
; @@ -100,7 +100,7 @@ renderer.render(el, document.body); console.log(document.body.innerHTML); //
a2
``` -Crank also allows iterables of values to be inserted, so, for instance, you can interpolate an array or a set of elements into an element tree. +Crank also allows arbitrarily nested iterables of values to be inserted, so, for instance, you can interpolate an array or a set of values into an element tree. ```jsx const arr = [1, 2, 3]; @@ -110,7 +110,7 @@ console.log(document.body.innerHTML); // "
123 abc
" ``` ## Element diffing -Crank uses the same “virtual DOM diffing” algorithm made popular by React, where DOM operations and component state is determined by the element’s tag and position in the tree. This approach allows you to write declarative code which focuses on producing the right elements, while Crank does the dirty work of managing stateful components and mutating the DOM. +Crank uses the same “virtual DOM diffing” algorithm made popular by React, where we compare elements by tag and position to reduce and reuse DOM mutations. This approach allows you to write declarative code which focuses on producing the right element tree, while Crank does the dirty work of managing state and mutating the DOM. ```jsx renderer.render( diff --git a/website/guides/03-components.md b/website/guides/03-components.md index c7f503bdc..69a196400 100644 --- a/website/guides/03-components.md +++ b/website/guides/03-components.md @@ -2,9 +2,11 @@ title: Components --- -So far, we’ve only seen and used intrinsic elements, but eventually, we’ll want to group parts of the element tree into reusable *components.* In Crank, all components are just functions; there is no class-based interface. +So far, we’ve only seen and used host elements, but eventually, we’ll want to group parts of the element tree into reusable *components.* In Crank, all components are functions; there is no class-based component API. ## Basic Components +The simplest kind of component you can write is a *sync function component*. When rendered, the function is invoked with the props object of the element as its first argument, and the return value of the function is recursively rendered as the element’s children. + ```js function Greeting({name}) { return
Hello, {name}
; @@ -14,19 +16,7 @@ renderer.render(, document.body); console.log(document.body.innerHTML); // "
Hello World
" ``` -The simplest kind of component you can write is a *sync function component*. When rendered, the function is invoked with the props object of the element as its first parameter, and the return value, usually an element, is recursively rendered by the renderer as the element’s children. - -As seen in the example above, you can use [object destructuring](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Object_destructuring) on the props parameter for convenience. Additionally, you can assign default values to each prop using JavaScript’s default value syntax: - -```js -function Greeting({name="World"}) { - return
Hello, {name}
; -} - -renderer.render(, document.body); // "
Hello World
" -``` - -Component elements can take children just as intrinsic elements can. The `createElement` function will add children to the props object under the name `children`, and it is up to the component to place the children somewhere in the returned element tree. If you don’t use the `children` prop, the `children` passed in will not be rendered. +Component elements can be passed children just as host elements can. The `createElement` function will add children to the props object under the name `children`, and it is up to the component to place the children somewhere in the returned element tree. If you don’t use the `children` prop, the `children` passed in will be ignored. ```js function Greeting({name, children}) { @@ -47,6 +37,16 @@ renderer.render( console.log(document.body.innerHTML); // "
Message for Nemo: Howdy
" ``` +As seen in the examples above, you can use [object destructuring](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Object_destructuring) on the props parameter for convenience. This means you can assign default values to each prop using JavaScript’s default value syntax: + +```js +function Greeting({name="World"}) { + return
Hello, {name}
; +} + +renderer.render(, document.body); // "
Hello World
" +``` + ## Stateful Components Eventually, you’re going to want to write components with local state. In Crank, we use [generator functions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function*) to do so. @@ -82,10 +82,10 @@ console.log(document.body.innerHTML); // "
You have updated this component 1 time
" ``` -Because we’re now yielding elements rather than returning them, we can make components stateful using variables in the local scope. Every time the component is updated, Crank resumes the generator, pausing at the next `yield`. The yielded expressions, usually elements, are then recursively rendered by the renderer, just as if it were returned in a sync function component. Furthermore, Crank uses the same diffing algorithm which reuses DOM nodes to reuse generator objects, so that the execution of the generator is preserved between renders. This allows local state to be encapsulated within the generator’s scope. +Because we’re now yielding elements rather than returning them, we can make components stateful using variables in the local scope. Every time the component is updated, Crank resumes the generator, pausing at the next `yield`. The yielded expressions, usually elements, are then recursively rendered, just as if it were returned in a sync function component. Furthermore, Crank uses the same diffing algorithm which reuses DOM nodes to reuse generator objects, so that the execution of the generator is preserved between renders. This allows local state to be encapsulated within the generator’s scope. -### Contexts -The `Counter` component’s local state only changes when it is rerendered, but we may want to write components which update themselves based on timers or events. Crank allows components to control themselves by passing in a custom object called a *context* as the `this` value of each component. These contexts provide several utility methods, most important of which is `this.refresh`, which tells Crank to update the component in place. For generator components, this means that the generator associated with the context will resume so it can yield another value. +### Contexts +In the above example, the `Counter` component’s local state only changes when it is rerendered, but we may want to write components which update themselves based on timers or events. Crank allows components to control themselves by passing in a custom object called a *context* as the `this` value of each component. These contexts provide several utility methods, most important of which is `this.refresh`, which tells Crank to update the related component in place. For generator components, Crank resumes the generator component so it can yield another value. ```jsx function *Timer() { @@ -112,7 +112,7 @@ This `Timer` component is similar to the `Counter` one, except now the state (th One important detail about the `Timer` example is that it cleans up after itself with `clearInterval`. Crank will call the `return` method on generator components when the element is removed from the tree, so that the finally block executes and `clearInterval` is called. In this way, you can use the natural lifecycle of a generator to write setup and teardown logic for components, all within the same scope. ### Prop updates -The generator components we’ve seen so far haven’t used the props object. Generator components can accept props as its first parameter just like regular function components. +The generator components we’ve seen so far haven’t used props. Generator components can accept props as its first parameter just like regular function components. ```jsx function *LabeledCounter({message}) { @@ -132,7 +132,7 @@ renderer.render(, docu console.log(document.body.innerHTML); // "
The count is now: 3
" ``` -This mostly works, except we have a bug where the component kept yielding the initial message even though a new message was passed in via props. To fix this, we can make sure props are kept up to date by iterating over the context: +This mostly works, except now we have a bug where the component kept yielding the initial message even though a new message was passed in via props. To fix this, we can make sure props are kept up to date by iterating over the context: ```jsx function *Counter({message}) { @@ -153,7 +153,7 @@ console.log(document.body.innerHTML); // "
What if I update the message: 2Hello again Bob
" ``` The fact that state is just local variables allows us to blur the lines between props and state, in a way that is easy to understand and without lifecycle methods like `componentWillUpdate` from React. With generators and `for` loops, comparing old and new props is as easy as comparing adjacent elements of an array. - -## Interactive components -Components produce elements, which can be rendered as DOM nodes. Most applications require event listeners to be attached to these nodes so that application state can be updated according to user input. To facilitate this, Crank provides two APIs for listening to events on rendered children: - -### The EventTarget interface -Crank contexts implement the same `EventTarget` interface used by the DOM, and automatically registers and tears down these listeners as DOM nodes are added and removed. You can write interactive components by combining event listeners with local variables and the `this.refresh` method. - -```jsx -function *Clicker() { - let count = 0; - this.addEventListener("click", () => { - count++; - this.refresh(); - }); - - while (true) { - yield ( - - ); - } -} -``` - -The local state `count` is now updated in the event listener, which triggers when the rendered button is actually clicked. The `this.addEventListener` method only attaches to the top-level node which each component renders, so if you want to listen to events on a nested node, you must use event delegation: - -```jsx -function *Clicker() { - let count = 0; - this.addEventListener("click", (ev) => { - if (ev.target.tagName === "BUTTON") { - count++; - this.refresh(); - } - }); - - while (true) { - yield ( -
- The button has been clicked {count} {count === 1 ? "time" : "times"}. - -
- ); - } -} -``` - -Because the event listener is attached to the outer `div`, we have to filter events by `ev.target.tagName` in the listener to make sure we’re not incrementing `count` based on clicks which don’t target the `button`. - -### DOM onevent props -As an alternative to event targets, Crank also allows you to attach event callbacks directly on rendered children using event props. These props start with `on`, are all lowercase, and correspond exactly to the properties specified by the DOM’s [GlobalEventHandlers mixin](https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers). - -```jsx -function *Clicker() { - let count = 0; - const handleClick = () => { - count++; - this.refresh(); - }; - - while (true) { - yield ( -
- The button has been clicked {count} {count === 1 ? "time" : "times"}. - -
- ); - } -} -``` - -The props-based onevent API and the context-based EventTarget API both have their advantages. On the one hand, using onevent props means you don’t have to filter events by target, and can register them on the exact children you’d like to listen to. On the other hand, using `this.addEventListener` allows you to register `passive` listeners, or listeners which trigger during the capturing phase. Crank supports both API styles for convenience and flexibility. - -### Dispatching events -Crank contexts implement the full EventTarget interface, meaning you can use `this.dispatchEvent` and the [`CustomEvent`](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent) constructor to bubble events to parent components: - -```jsx -function MyButton(props) { - this.addEventListener("click", () => { - this.dispatchEvent(new CustomEvent("mybutton.click", { - bubbles: true, - detail: {id: props.id}, - })); - }); - - return ( - +
+ ); + } +} +``` + +### The EventTarget interface +As an alternative to the `onevent` API, Crank contexts also implement the same `EventTarget` interface used by the DOM. The `addEventListener` method attaches a listener to a component’s rendered DOM elements. + +```jsx +function *Clicker() { + let count = 0; + this.addEventListener("click", () => { + count++; + this.refresh(); + }); + + while (true) { + yield ( + + ); + } +} +``` + +The local state `count` is now updated in the event listener, which triggers when the rendered button is actually clicked. + +**NOTE:** When using the `addEventListener` method, you do not have to call the `removeEventListener` method if you merely need to remove event listeners when the component is unmounted. This is done automatically. + +## Event delegation + +The Context’s `addEventListener` method only attaches to the top-level node or nodes which each component renders, so if you want to listen to events on a nested node, you must use event delegation: + +```jsx +function *Clicker() { + let count = 0; + this.addEventListener("click", (ev) => { + if (ev.target.tagName === "BUTTON") { + count++; + this.refresh(); + } + }); + + while (true) { + yield ( +
+ The button has been clicked {count} {count === 1 ? "time" : "times"}. + +
+ ); + } +} +``` + +Because the event listener is attached to the outer `div`, we have to filter events by `ev.target.tagName` in the listener to make sure we’re not incrementing `count` based on clicks which don’t target the `button`. + +## When to use onevent props and the EventTarget API +The props-based onevent API and the context-based EventTarget API both have their advantages. On the one hand, using onevent props means you don’t have to filter events by target, and can register them on the exact element you’d like to listen to. + +On the other hand, using `this.addEventListener` allows you to take full advantage of the EventTarget API, including registering `passive` events or events which are dispatched during the `capture` phase. Additionally, the EventTarget API can be used without referencing or accessing the child elements which a component renders, meaning you can use it to listen to components whose children are passed in, or in utility functions which don’t have access to the rendered elements at all. + +Crank supports both API styles for convenience and flexibility. + +### Dispatching events +Crank contexts implement the full EventTarget interface, meaning you can use the `dispatchEvent` method and the [`CustomEvent`](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent) constructor to bubble events to parent components: + +```jsx +function MyButton(props) { + this.addEventListener("click", () => { + this.dispatchEvent(new CustomEvent("mybuttonclick", { + bubbles: true, + detail: {id: props.id}, + })); + }); + + return ( +
+

Features

Declarative Components

Crank uses the same JSX syntax and diffing algorithm popularized - by React, allowing you to write HTML-like code directly in your + by React, allowing you to write HTML-like code directly in JavaScript.

@@ -212,11 +213,40 @@ function Home(): Element {

-

Native Promise Support

+

Promise-friendly

- Crank provides first-class support for promises. You can use async - functions as components, and race components to display fallback - UIs. + Crank provides first-class support for promises. You can use + async/await directly in components, and race components to display + fallback UIs. +

+
+
+

Lightweight

+

+ Crank has no dependencies, and its core is a single file. It + currently measures at{" "} + + 4.5KB minified and gzipped + + . +

+
+
+

Performant

+

+ + According to benchmarks + + , Crank beats React in terms of speed and memory usage, and is + comparable to that of Preact or Vue. +

+
+
+

Extensible

+

+ The core renderer can be extended to target alternative + environments such as WebGL libraries, terminals, smartphones or + smart TVs.

diff --git a/website/guides/02-elements.md b/website/guides/02-elements.md index ef3744321..23b6b3e08 100644 --- a/website/guides/02-elements.md +++ b/website/guides/02-elements.md @@ -5,7 +5,7 @@ title: JSX, Elements and Renderers **Note for React developers:** If you’re familiar with how JSX and elements work in React, you may want to skip ahead to the section on components. Elements in Crank work almost exactly as they do in React. -Crank is best used with [JSX](https://facebook.github.io/jsx/), an XML-like syntax extension to JavaScript. It is designed to work with transpilers like Babel and TypeScript out-of-box; all you need to do is enable JSX parsing, import the `createElement` function, and include a `@jsx` comment directive (`/** @jsx createElement */`). The parser will then transpile JSX into `createElement` calls. For example, in the code below, the JSX expression assigned to `el` transpiles to the `createElement` call assigned to `el1`. +Crank is best used with [JSX](https://facebook.github.io/jsx/), an XML-like syntax extension to JavaScript. It is designed to work with transpilers like Babel and TypeScript out-of-box; all you need to do is enable JSX parsing, import the `createElement` function, and include a `@jsx` comment directive (`/** @jsx createElement */`). The parser will then transpile JSX into `createElement` calls. For example, in the following code, the JSX expression assigned to `el` transpiles to the `createElement` call assigned to `el1`. ```jsx /** @jsx createElement */ diff --git a/website/guides/03-components.md b/website/guides/03-components.md index 69a196400..3558fb90f 100644 --- a/website/guides/03-components.md +++ b/website/guides/03-components.md @@ -37,7 +37,7 @@ renderer.render( console.log(document.body.innerHTML); // "
Message for Nemo: Howdy
" ``` -As seen in the examples above, you can use [object destructuring](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Object_destructuring) on the props parameter for convenience. This means you can assign default values to each prop using JavaScript’s default value syntax: +You may have noticed in the preceding examples that we used [object destructuring](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Object_destructuring) on the props parameter for convenience. You can further assign default values to each prop using JavaScript’s default value syntax. ```js function Greeting({name="World"}) { @@ -85,7 +85,7 @@ console.log(document.body.innerHTML); Because we’re now yielding elements rather than returning them, we can make components stateful using variables in the local scope. Every time the component is updated, Crank resumes the generator, pausing at the next `yield`. The yielded expressions, usually elements, are then recursively rendered, just as if it were returned in a sync function component. Furthermore, Crank uses the same diffing algorithm which reuses DOM nodes to reuse generator objects, so that the execution of the generator is preserved between renders. This allows local state to be encapsulated within the generator’s scope. ### Contexts -In the above example, the `Counter` component’s local state only changes when it is rerendered, but we may want to write components which update themselves based on timers or events. Crank allows components to control themselves by passing in a custom object called a *context* as the `this` value of each component. These contexts provide several utility methods, most important of which is `this.refresh`, which tells Crank to update the related component in place. For generator components, Crank resumes the generator component so it can yield another value. +In the preceding example, the `Counter` component’s local state only changes when it is rerendered, but we may want to write components which update themselves based on timers or events. Crank allows components to control themselves by passing in a custom object called a *context* as the `this` value of each component. These contexts provide several utility methods, most important of which is `this.refresh`, which tells Crank to update the related component in place. For generator components, Crank resumes the generator component so it can yield another value. ```jsx function *Timer() { @@ -153,7 +153,7 @@ console.log(document.body.innerHTML); // "
What if I update the message: 2, document.body); In this example, the `RandomDogLoader` component is an async generator component which races the `LoadingIndicator` component with the `RandomDog` component. Because the async generator component resumes continuously, both components are executed, and according to the second rule, only the second component shows if it fulfills faster than the first component, which fulfills at a fixed interval of one second. -The above example hints at how we could abstract this pattern to implement a `Suspense` component, a proposed custom API in React which allows async components with fallback states: +The preceding example hints at how we could abstract this pattern to implement a `Suspense` component, a proposed custom API in React which allows async components with fallback states: ```jsx async function Fallback({timeout = 1000, children}) { diff --git a/website/guides/06-special-props-and-tags.md b/website/guides/06-special-props-and-tags.md index 96f356d31..bb833f41c 100644 --- a/website/guides/06-special-props-and-tags.md +++ b/website/guides/06-special-props-and-tags.md @@ -166,7 +166,7 @@ function memo(Component) { } ``` -In the example above, `memo` is a higher-order component, a function which takes a component and returns a component which compares new and old props and yields a `Copy` element if old and new props are shallowly equal. A `Copy` element can appear anywhere in an element tree to prevent rerenderings, and the only prop `Copy` elements take is the `crank-key` prop, allowing you to copy elements by key rather than position. +In this example, `memo` is a higher-order component, a function which takes a component and returns a component which compares new and old props and yields a `Copy` element if old and new props are shallowly equal. A `Copy` element can appear anywhere in an element tree to prevent rerenderings, and the only prop `Copy` elements take is the `crank-key` prop, allowing you to copy elements by key rather than position. ### Raw Sometimes, you may want to insert raw HTML or actual DOM nodes directly into the element tree. Crank allows you to do this with the `Raw` element. The `Raw` element takes a `value` prop which is interpreted by the renderer. For the DOM renderer, if `value` is an HTML string, the renderer will parse and insert the resulting DOM nodes, and if it’s already a DOM node Crank will insert them in place. Be careful when using `Raw` elements, as passing unsanitized text inputs can lead to security vulnerabilities. diff --git a/website/guides/07-lifecycles.md b/website/guides/07-lifecycles.md index 7fab4d083..ffba48de4 100644 --- a/website/guides/07-lifecycles.md +++ b/website/guides/07-lifecycles.md @@ -137,6 +137,6 @@ async function *MyInput(props) { } ``` -The above component focuses every time it is rerendered. You might notice that we use an async generator component here. That’s because async generators continuously resume, and rely on the `for await` loop to await new updates. +The `MyInput` component focuses every time it is rerendered. You might notice that we use an async generator component here. That’s because async generators continuously resume, and rely on the `for await` loop to await new updates. **TODO: Design APIs/Document them for working with yield expressions in sync generators.** diff --git a/website/src/index.css b/website/src/index.css index 9aafe0598..a1c975e37 100644 --- a/website/src/index.css +++ b/website/src/index.css @@ -191,7 +191,7 @@ li { } .hero { - min-height: 500px; + padding: 7em 0; display: flex; flex-direction: column; justify-content: center; @@ -234,13 +234,12 @@ li { @media screen and (min-width: 1100px) { display: flex; flex-direction: row; + flex-wrap: wrap; justify-content: space-between; + .feature { + flex-basis: 32%; margin: 0; - max-width: 500px; - + .feature { - margin-left: 1rem; - } } } } From bfc3b24c97e6ee34aac369cf0b67d275537ae83b Mon Sep 17 00:00:00 2001 From: Brian Kim Date: Tue, 14 Jul 2020 19:15:49 -0400 Subject: [PATCH 07/26] filling more docs in --- website/build.tsx | 8 +-- website/guides/02-elements.md | 16 +++-- website/guides/03-components.md | 13 ++-- website/guides/04-handling-events.md | 12 ++-- website/guides/05-async-components.md | 18 ++--- website/guides/06-special-props-and-tags.md | 79 ++++++++++++++++++--- 6 files changed, 107 insertions(+), 39 deletions(-) diff --git a/website/build.tsx b/website/build.tsx index 30e16a2bf..e37586123 100644 --- a/website/build.tsx +++ b/website/build.tsx @@ -198,7 +198,7 @@ function Home(): Element {

Features

-

Declarative Components

+

Declarative

Crank uses the same JSX syntax and diffing algorithm popularized by React, allowing you to write HTML-like code directly in @@ -215,8 +215,8 @@ function Home(): Element {

Promise-friendly

- Crank provides first-class support for promises. You can use - async/await directly in components, and race components to display + Crank provides first-class support for promises. You can define + components as async functions and race renderings to display fallback UIs.

@@ -238,7 +238,7 @@ function Home(): Element { According to benchmarks , Crank beats React in terms of speed and memory usage, and is - comparable to that of Preact or Vue. + currently comparable to Preact or Vue.

diff --git a/website/guides/02-elements.md b/website/guides/02-elements.md index 23b6b3e08..72552226f 100644 --- a/website/guides/02-elements.md +++ b/website/guides/02-elements.md @@ -16,7 +16,7 @@ const el =
An element
; const el1 = createElement("div", {id: "element"}, "An element"); ``` -The `createElement` function returns an *element*, a JavaScript object. Elements on their own don’t do anything special; rather, Crank provides special classes called *renderers* which interpret elements to produce and manage DOM nodes, HTML strings, canvas scene graphs, or whatever else you can think of. Crank ships with two renderers for web development: one for managing DOM nodes, available through the module `@bikeshaving/crank/dom`, and one for creating HTML strings, available through the module `@bikeshaving/crank/html`. You can use these modules to render interactive user interfaces in the browser and HTML responses on the server. +The `createElement` function returns an *element*, a JavaScript object. Elements on their own don’t do anything special; instead, Crank provides special classes called *renderers* which interpret elements to produce and manage DOM nodes, HTML strings, canvas scene graphs, or whatever else you can think of. Crank ships with two renderers for web development: one for managing DOM nodes, available through the module `@bikeshaving/crank/dom`, and one for creating HTML strings, available through the module `@bikeshaving/crank/html`. You can use these modules to render interactive user interfaces in the browser and HTML responses on the server. ```jsx /** @jsx createElement */ @@ -35,9 +35,11 @@ console.log(HTMLRenderer.render(el)); //
Hello world
![Image of a JSX element](../static/parts-of-jsx.svg) -An element can be thought of as having three main parts: a *tag*, *props* and *children*. These roughly correspond to tags, attributes and content in HTML, and for the most part, you can copy-paste HTML into JavaScript and have things work as you would expect. The main difference is that JSX has to be well-balanced like XML, so void tags must have a closing slash (`` not ``). Also, if you forget to close an element or mismatch opening and closing tags, the parser will throw an error, whereas HTML can contain unbalanced or malformed contents and mostly still work. The advantage of using JSX is that it allows you to interpolate JavaScript expressions as its tag, props or children. +An element can be thought of as having three main parts: a *tag*, *props* and *children*. These roughly correspond to tags, attributes and content in HTML, and for the most part, you can copy-paste HTML into JavaScript and have things work as you would expect. The main difference is that JSX has to be well-balanced like XML, so void tags must have a closing slash (`
` not `
`). Also, if you forget to close an element or mismatch opening and closing tags, the parser will throw an error, whereas HTML can contain unbalanced or malformed contents and mostly still work. The advantage of using JSX is that it allows you to interpolate arbitrary JavaScript expressions as an element’s tag, props or children. ### Tags +Tags are the first part of an element expression, and can be thought of as the “name” or “type” of the element. JSX parsers will transpile the tag name as the first argument of a `createElement` call. + ```jsx const intrinsicEl =
; // transpiles to: @@ -48,11 +50,11 @@ const componentEl = ; const componentEl1 = createElement(Component, null); ``` -Tags are the first part of an element expression, and can be thought of as the “name” or “type” of the element. It is transpiled to the first argument of a `createElement` call. By convention, JSX parsers treat lower-cased tags as strings and capitalized tags as variables. When a tag is a string, this signifies that the element will be handled by the renderer. In Crank, we call elements with string tags *intrinsic* elements, and for both of the web renderers, these elements correspond to actual HTML elements like `div` or `input`. As we’ll see later, elements can also have function tags, in which case the behavior of the element is defined by the execution of the referenced function and not the renderer. Elements with functions for tags are called *component elements*. +By convention, JSX parsers treat lower-cased tags as strings and capitalized tags as variables. When a tag is a string, this signifies that the element will be handled by the renderer. We call elements with string tags *host* or *intrinsic* elements, and for both of the web renderers, these elements correspond to actual HTML elements like `div` or `input`. As we’ll see later, elements can also have function tags, in which case the behavior of the element is defined by the execution of the referenced function and not the renderer. Elements with function tags are called *component elements*. ### Props -The attribute-like `key="value"` syntax in JSX is transpiled to a single object for each element, and is passed as the second argument to the resulting `createElement` call. We call this object the *props* object, short for “properties.” The value of each prop is a string if the string-like syntax is used (`key="value"`), or it can be an interpolated JavaScript value if the value is placed within curly brackets (`key={value}`). Props are used by both intrinsic and component elements to “pass” values into them, similar to how you might pass values into functions as arguments when invoking them. +JSX parsers coalesce the attribute-like `key="value"` syntax to a single object for each element, and pass this object as the second argument to the resulting `createElement` call. ```jsx const myClass = "my-class"; @@ -63,6 +65,8 @@ const el1 = createElement("div", {id: "my-id", "class": myClass}); console.log(el.props); // {id: "my-id", "class": "my-class"} ``` +We call this object the *props* object, short for “properties.” The value of each prop is a string if the string-like syntax is used (`key="value"`), or it can be an interpolated JavaScript value if the value is placed within curly brackets (`key={value}`). You can use props to “pass” values into host and component elements, similar to how you might pass arguments into functions when invoking them. + If you already have an object that you want to use as props, you can use the special JSX `...` syntax to “spread” it into an element. This works similarly to [ES6 spread syntax](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax). ```jsx @@ -91,7 +95,7 @@ const list1 = createElement("ul", null, console.log(list.props.children.length); // 2 ``` -As you can see in the example, the contents of elements which are not themselves part of element expressions are interpreted as strings. However, just as with props, you can use curly brackets to interpolate JavaScript expressions into an element’s children. Besides elements and strings, almost every value in JavaScript can participate in an element tree. Numbers are rendered as strings, and the values `null`, `undefined`, `true` and `false` are erased, allowing you to render things conditionally with boolean expressions. +As you can see in the example, JSX parsers interpret the contents of an element which fall outside elements as strings. However, just as with props, you can use curly brackets to interpolate JavaScript expressions into an element’s children. Besides elements and strings, almost every value in JavaScript can participate in an element tree. Numbers are rendered as strings, and the values `null`, `undefined`, `true` and `false` are erased, allowing you to render things conditionally with boolean expressions. ```jsx const el =
{"a"}{1 + 1}{true}{false}{null}{undefined}
; @@ -133,3 +137,5 @@ renderer.render( console.log(document.body.firstChild === div); // true console.log(document.body.firstChild.firstChild === span); // true ``` + +**NOTE:** We usually avoid using the term “virtual DOM” in Crank, insofar as the Crank renderer can render to multiple environments; instead, we use the term “element diffing” to mean mostly the same thing. diff --git a/website/guides/03-components.md b/website/guides/03-components.md index 3558fb90f..3fec69046 100644 --- a/website/guides/03-components.md +++ b/website/guides/03-components.md @@ -16,7 +16,7 @@ renderer.render(, document.body); console.log(document.body.innerHTML); // "
Hello World
" ``` -Component elements can be passed children just as host elements can. The `createElement` function will add children to the props object under the name `children`, and it is up to the component to place the children somewhere in the returned element tree. If you don’t use the `children` prop, the `children` passed in will be ignored. +Component elements can be passed children just as host elements can. The `createElement` function will add children to the props object under the name `children`, and it is up to the component to place the children somewhere in the returned element tree. If you don’t use the `children` prop, it will be ignored. ```js function Greeting({name, children}) { @@ -37,7 +37,7 @@ renderer.render( console.log(document.body.innerHTML); // "
Message for Nemo: Howdy
" ``` -You may have noticed in the preceding examples that we used [object destructuring](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Object_destructuring) on the props parameter for convenience. You can further assign default values to each prop using JavaScript’s default value syntax. +You may have noticed in the preceding examples that we used [object destructuring](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Object_destructuring) on the props parameter for convenience. You can further assign default values to a specific prop by using JavaScript’s default value syntax. ```js function Greeting({name="World"}) { @@ -48,7 +48,7 @@ renderer.render(, document.body); // "
Hello World
" ``` ## Stateful Components -Eventually, you’re going to want to write components with local state. In Crank, we use [generator functions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function*) to do so. +Eventually, you’re going to want to write components with local state. In Crank, we use [generator functions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function*) to do so. These types of components are referred to as *sync generator components*. ```jsx function *Counter() { @@ -85,7 +85,7 @@ console.log(document.body.innerHTML); Because we’re now yielding elements rather than returning them, we can make components stateful using variables in the local scope. Every time the component is updated, Crank resumes the generator, pausing at the next `yield`. The yielded expressions, usually elements, are then recursively rendered, just as if it were returned in a sync function component. Furthermore, Crank uses the same diffing algorithm which reuses DOM nodes to reuse generator objects, so that the execution of the generator is preserved between renders. This allows local state to be encapsulated within the generator’s scope. ### Contexts -In the preceding example, the `Counter` component’s local state only changes when it is rerendered, but we may want to write components which update themselves based on timers or events. Crank allows components to control themselves by passing in a custom object called a *context* as the `this` value of each component. These contexts provide several utility methods, most important of which is `this.refresh`, which tells Crank to update the related component in place. For generator components, Crank resumes the generator component so it can yield another value. +In the preceding example, the `Counter` component’s local state only changes when it is rerendered, but we may want to write components which update themselves based on timers or events. Crank allows components to control themselves by passing in a custom object called a *context* as the `this` keyword of each component. Contexts provide several utility methods, most important of which is the `refresh` method, which tells Crank to update the related component in place. ```jsx function *Timer() { @@ -107,7 +107,7 @@ function *Timer() { } ``` -This `Timer` component is similar to the `Counter` one, except now the state (the local variable `seconds`) is updated in the callback passed to `setInterval`, rather than when the component is rerendered. The `this.refresh` method is called to ensure that the generator is stepped through each interval, so that the rendered DOM actually reflects the updated `seconds` variable. +This `Timer` component is similar to the `Counter` one, except now the state (the local variable `seconds`) is updated in the callback passed to `setInterval`, rather than when the component is rerendered. Additionally, `refresh` method is called to ensure that the generator is stepped through each interval, so that the rendered DOM actually reflects the updated `seconds` variable. One important detail about the `Timer` example is that it cleans up after itself with `clearInterval`. Crank will call the `return` method on generator components when the element is removed from the tree, so that the finally block executes and `clearInterval` is called. In this way, you can use the natural lifecycle of a generator to write setup and teardown logic for components, all within the same scope. @@ -127,6 +127,7 @@ renderer.render(, document.body); console.log(document.body.innerHTML); // "
The count is now: 1
" renderer.render(, document.body); console.log(document.body.innerHTML); // "
The count is now: 2
" + renderer.render(, document.body); // WOOPS! console.log(document.body.innerHTML); // "
The count is now: 3
" @@ -153,7 +154,7 @@ console.log(document.body.innerHTML); // "
What if I update the message: 2 ## Async generator components -Just as you can write stateful components with sync generator functions, you can also write stateful async components with async generator functions. +Just as you can write stateful components with sync generator functions, you can also write stateful *async* components with *async generator functions*. ```jsx async function *AsyncLabeledCounter ({message}) { @@ -129,7 +129,7 @@ async function *AsyncLabeledCounter ({message}) { })(); ``` -`AsyncLabeledCounter` is an async version of the `LabeledCounter` example introduced in the section on sync generator components, and demonstrates several key differences between sync and async generator components. First, rather than using `while` or `for…of` loops as with sync generator components, we now use a `for await…of` loop. This is possible because Crank contexts are not just an *iterable* of props, but also an *async iterable* of props as well. Second, you’ll notice that the async generator yields multiple times per iteration over `this`, once to show a loading message and once to show the actual count. While it is possible for sync generators to yield multiple times per iteration over `this`, it wouldn’t necessarily make sense to do so because generators suspend at each yield, and upon resuming a second time within the same loop, the props would be stale. In contrast, async generator components are continuously resumed; Rather than suspending at each yield, we rely on the `for await…of` loop, which pauses at the bottom for the next rendering. +`AsyncLabeledCounter` is an async version of the `LabeledCounter` example introduced in [the section on sync generator components](#TK). This example demonstrates several key differences between sync and async generator components. Firstly, rather than using `while` or `for…of` loops as with sync generator components, we now use a `for await…of` loop. This is possible because Crank contexts are not just an *iterable* of props, but also an *async iterable* of props as well. Secondly, you’ll notice that the async generator yields multiple times per iteration over `this`, once to show a loading message and once to show the actual count. While it is possible for sync generators to yield multiple times per iteration over `this`, it wouldn’t necessarily make sense to do so because generators suspend at each yield, and upon resuming a second time within the same loop, the props would be stale. In contrast, async generator components are continuously resumed; Rather than suspending at each yield, we rely on the `for await…of` loop, which suspends at the bottom of its block until the next update. ## Responsive Loading Indicators The async components we’ve seen so far have been all or nothing, in the sense that Crank can’t show anything until all components in the tree have fulfilled. This can be a problem when you have an async call which takes longer than expected. It would be nice if parts of the element tree could be shown without waiting, to create responsive user experiences. However, because loading indicators which show immediately can paradoxically make your app seem less responsive, we can use the async rules described previously along with async generator functions to show loading indicators which appear only when certain promises take too long to settle. @@ -185,9 +185,9 @@ function *RandomDogApp() { renderer.render(, document.body); ``` -In this example, the `RandomDogLoader` component is an async generator component which races the `LoadingIndicator` component with the `RandomDog` component. Because the async generator component resumes continuously, both components are executed, and according to the second rule, only the second component shows if it fulfills faster than the first component, which fulfills at a fixed interval of one second. +In this example, the `RandomDogLoader` component is an async generator component which races the `LoadingIndicator` component with the `RandomDog` component. Because the async generator component resumes continuously, both components are rendered, and according to the second rule of async components, the loading indicator only shows if `RandomDog` component takes longer than a second to fulfill. -The preceding example hints at how we could abstract this pattern to implement a `Suspense` component, a proposed custom API in React which allows async components with fallback states: +The preceding example hints at how we could abstract this pattern to implement a `Suspense` component, an API in React which allows for async components with fallback states: ```jsx async function Fallback({timeout = 1000, children}) { @@ -212,4 +212,4 @@ async function *Suspense({timeout, fallback, children}) { })(); ``` -As you can see, with Crank, no special tags are needed for async loading states, and the functionality to write this complex logic is implemented using the same element diffing algorithm that governs synchronous components. This approach is also more flexible in the sense that you can extend it, for instance, to include a second fallback state which fulfills after ten seconds, which might inform the user that something went wrong or that servers are slow to respond. Best of all, you can use async/await directly in your components! +As you can see, no special tags are needed for async loading states, and the functionality to write this complex logic is implemented using the same element diffing algorithm that governs synchronous components. Additionally, this approach is more flexible in the sense that you can extend it, for instance, to include a second fallback state which fulfills after ten seconds, which might inform the user that something went wrong or that servers are slow to respond. diff --git a/website/guides/06-special-props-and-tags.md b/website/guides/06-special-props-and-tags.md index bb833f41c..112e6a5c9 100644 --- a/website/guides/06-special-props-and-tags.md +++ b/website/guides/06-special-props-and-tags.md @@ -2,12 +2,11 @@ title: Special Props and Tags --- -The element diffing algorithm used by Crank is both declarative and efficient, but there are times when you might want to tweak the way it works. Crank provides special props and tags which produce different rendering behaviors. +## Special Props -## Special Crank Props -### children +The following are props which can be used with all elements, regardless of tag or renderer. -### crank-key +### `crank-key` By default, Crank will use an element’s tag and position to determine if it represents an update or a change to the tree. Because elements often represent stateful DOM nodes or components, it can be useful to *key* the children of an element to hint to renderers that an element has been added, moved or removed. In Crank, we do this with the special prop `crank-key`: ```jsx @@ -74,20 +73,65 @@ console.log(document.firstChild.firstChild === span); // true ``` ### crank-ref -TKTKTKTKTKTKTKTKTKTKTKTK +Sometimes, you may want to access the rendered value of a specific element in the element tree. To do this, you can use the `crank-ref` prop, which takes a callback which is fired when the element is actually rendered. + +```tsx +function *MyPlayer() { + let audio; + while (true) { + yield ( +
+ +
+ ); + } +} +``` + +Refs can be attached to any element in the element tree, and the value passed to the callback will vary according the type of the element and the renderer. + +### `children` +The `children` prop passed to components is special because it is not usually set with JSX’s `key="value"` prop syntax, but by the contents between the opening and closing tags. Crank places no limitations on the types of values that can be passed into components as children, but patterns like [render props](https://reactjs.org/docs/render-props.html), where a callback is passed as the child of a component should be avoided. + +The actual type of the `children` prop will vary according to the number of children passed in. If a component element has no children (``), the `children` prop will be undefined, if it has one child (``), the `children` prop will be set to that child, and if it has multiple children (``), the `children` prop will be set to an array of those children. The reason we do this is to avoid an array allocation. All props have to be retained between renders, and avoiding allocating an extra array for every element in the tree can reduce the runtime memory costs of the framework dramatically. + +Therefore, the `children` prop should be treated as a black box, only to be rendered somewhere within a component’s returned or yielded children. Attempting to iterate over or manipulate the passed in children of a component is an anti-pattern, and you should use [event bubbling](#TKTKTKT) or [provisions](#TTKTKTK) to coordinate ancestor/descendant components. ## Special DOM Props + +The following props are specific to host elements for the HTML and DOM renderers. + ### style +The style prop can be used to add inline styles to an element. It can either be a CSS string, in which case it works exactly as it does in HTML, or it can also be an object, in which case CSS declarations can be set individually. + +```jsx +
Hello
+``` + +Unlike other JSX frameworks, Crank does not camel-case style properties or add pixel units to numbers. ### innerHTML +The innerHTML prop can be used to set the `innerHTML` of an element. -### onevent +Be careful when using the `innerHTML` prop, as passing unsanitized text inputs can lead to security vulnerabilities. As an alternative, you can also use [the special `Raw` element tag](#TKTKTK), which allows to inject raw HTML or even actual DOM nodes into the element tree, without requiring a parent host element. -### class/className +### `class` vs `className`, `for` vs `htmlFor` +Crank strives to make copying and pasting HTML into your components as easy as possible, and to this extent it allows you to use `class` and `for` as props in your elements. +```jsx + +``` + +You can still use `className` and `htmlFor` as well, but using the former prop names is much more convenient. ## Special Tags -Crank provides several element tags which have special meaning when rendering. In actuality, these tags are symbols and behave similarly to string tags, except they affect the diffing algorithm and output. + +Crank provides four element tags which have special meaning to the renderer, and affect element diffing and rendering output in various ways. ### Fragment Crank provides a `Fragment` tag, which allows you to render multiple children into a parent without wrapping them in another DOM node. Under the hood, iterables which appear in the element tree are also implicitly wrapped in a `Fragment` element by the renderer. @@ -109,8 +153,23 @@ console.log(document.body.innerHTML); // "
Sibling 1
Sibling 2
" ``` +Internally, the `Fragment` is the empty string, and you can use the empty string directly when calling `createElement` yourself without having to reference the `Crank` namespace. + +```jsx +function Siblings() { + return createElement("", null, [ +
Sibling 1
, +
Sibling 2
, + ]); +} + +renderer.render(, document.body); +console.log(document.body.innerHTML); +// "
Sibling 1
Sibling 2
" +``` + ### Portal -Sometimes you may want to render into multiple DOM nodes from the same element tree. You can do this with the `Portal` tag, passing in a DOM node as its `root` prop. The Portal’s children will be rendered into the specified root. This is useful when writing modals or working with pages where you need to render into multiple entry-points. Events dispatched from a `Portal` element‘s child contexts via `this.dispatchEvent` will still bubble into parent component contexts. +Sometimes you may want to render into a DOM node which isn’t the current parent element, or even a part of the currently rendered DOM tree. You can do this with the `Portal` tag, passing in a DOM node as its `root` prop. The Portal’s children will be rendered into the specified root element, just as if Renderer.render was called with the root value as its second argument. ```jsx /** @jsx createElement */ @@ -136,6 +195,8 @@ console.log(root2.innerHTML); // "
This div is rendered into root2
" ``` +This tag is useful for instance when creating modals or tooltips, which usually need to be rendered into separate DOM elements at the bottom of the page for visibility reasons. Events dispatched from a `Portal` element‘s child contexts via the `dispatchEvent` method will still bubble into parent components. + ### Copy It‘s often fine to rerender Crank components, because elements are diffed, persistent between renders, and unnecessary mutations usually avoided. However, you might want to prevent a child from updating when the parent rerenders, perhaps because a certain prop hasn’t changed, because you want to batch updates from the parent, or as a performance optimization. To do this, you can use the `Copy` tag to indicate to Crank that you don’t want to update a previously rendered element in its position. From 1684a67c5af87d18a9196ffeebe4e47394c70584 Mon Sep 17 00:00:00 2001 From: Brian Kim Date: Wed, 15 Jul 2020 19:28:50 -0400 Subject: [PATCH 08/26] anchor headers --- website/build.tsx | 14 ++++++++++++++ website/src/index.css | 25 ++++++++++++++++++++----- 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/website/build.tsx b/website/build.tsx index e37586123..fdf672588 100644 --- a/website/build.tsx +++ b/website/build.tsx @@ -41,6 +41,20 @@ interface DocInfo { publishDate?: Date; } +const markedRenderer: Partial = { + heading(text, level, raw, slugger) { + const slug = slugger.slug(raw); + if (level <= 3) { + return ` + ${text} + `; + } + return `${text}`; + }, +}; + +marked.use({renderer: markedRenderer as marked.Renderer}); + async function parseDocs( name: string, root: string = name, diff --git a/website/src/index.css b/website/src/index.css index a1c975e37..2c34cea31 100644 --- a/website/src/index.css +++ b/website/src/index.css @@ -25,12 +25,17 @@ img { h1, h2, h3, h4, h5, h6 { padding: 0; margin: 1.4em 0; + a { + color: inherit; + text-decoration: none; + } } h1, h2, h3 { color: #dbb368; } + blockquote { border-left: 2px solid #dbb368; margin: 1.5em 0; @@ -43,12 +48,22 @@ blockquote { a { color: inherit; -} + &.current { + color: #dbb368; + } -a.current { - color: #dbb368; + &.anchor { + outline: none; + &::before { + content: ""; + display: inline-block; + margin-top: -5em; + padding-top: 5em; + } + } } + li { margin: 0 0 1em; } @@ -172,11 +187,11 @@ li { .content { margin: 0 auto; padding: 2rem 1rem; - :first-child { + > :first-child { margin-top: 0; } - :last-child { + > :last-child { margin-bottom: 0; } From be2ffcf949d093a657527432c7328b6ad599415a Mon Sep 17 00:00:00 2001 From: Brian Kim Date: Wed, 15 Jul 2020 23:39:05 -0400 Subject: [PATCH 09/26] more docs --- website/guides/01-getting-started.md | 2 +- website/guides/02-elements.md | 33 +++--- website/guides/03-components.md | 43 +++++--- website/guides/04-handling-events.md | 113 ++++++++++++++++++-- website/guides/06-special-props-and-tags.md | 16 +-- website/guides/12-api-reference.md | 24 ++--- 6 files changed, 172 insertions(+), 59 deletions(-) diff --git a/website/guides/01-getting-started.md b/website/guides/01-getting-started.md index 6d8f30c54..583e8c464 100644 --- a/website/guides/01-getting-started.md +++ b/website/guides/01-getting-started.md @@ -17,7 +17,7 @@ import {renderer} from "@bikeshaving/crank/dom"; renderer.render(
Hello world
, document.body); ``` -If your environment does not support ESModules (you’ll probably see a message like `SyntaxError: Unexpected token export`), you can import the CommonJS versions of the library under the `cjs` directory. +If your environment does not support ESModules (you may see a message like `SyntaxError: Unexpected token export`), you can import the CommonJS versions of the library under the `cjs` directory. ```jsx /** @jsx createElement */ diff --git a/website/guides/02-elements.md b/website/guides/02-elements.md index 72552226f..20956fbb2 100644 --- a/website/guides/02-elements.md +++ b/website/guides/02-elements.md @@ -3,9 +3,11 @@ id: elements title: JSX, Elements and Renderers --- -**Note for React developers:** If you’re familiar with how JSX and elements work in React, you may want to skip ahead to the section on components. Elements in Crank work almost exactly as they do in React. +**Note:** If you’re familiar with how JSX and elements work in React, you may want to skip ahead to [the guide for components](./components). Elements in Crank work almost exactly as they do in React. -Crank is best used with [JSX](https://facebook.github.io/jsx/), an XML-like syntax extension to JavaScript. It is designed to work with transpilers like Babel and TypeScript out-of-box; all you need to do is enable JSX parsing, import the `createElement` function, and include a `@jsx` comment directive (`/** @jsx createElement */`). The parser will then transpile JSX into `createElement` calls. For example, in the following code, the JSX expression assigned to `el` transpiles to the `createElement` call assigned to `el1`. +## Elements + +Crank is best used with [JSX](https://facebook.github.io/jsx/), an XML-like syntax extension to JavaScript. It is designed to work with transpilers like Babel and TypeScript out-of-box. JSX transpilers work by transforming JSX expressions into `createElement` factory function calls. For example, in the following code, the JSX expression assigned to `el` transpiles to the `createElement` call assigned to `el1`. ```jsx /** @jsx createElement */ @@ -16,7 +18,9 @@ const el =
An element
; const el1 = createElement("div", {id: "element"}, "An element"); ``` -The `createElement` function returns an *element*, a JavaScript object. Elements on their own don’t do anything special; instead, Crank provides special classes called *renderers* which interpret elements to produce and manage DOM nodes, HTML strings, canvas scene graphs, or whatever else you can think of. Crank ships with two renderers for web development: one for managing DOM nodes, available through the module `@bikeshaving/crank/dom`, and one for creating HTML strings, available through the module `@bikeshaving/crank/html`. You can use these modules to render interactive user interfaces in the browser and HTML responses on the server. +The `createElement` function returns an *element*, a JavaScript object. Elements on their own don’t do anything special; instead, Crank provides special classes called *renderers* which interpret elements to produce DOM nodes, HTML strings, or whatever else you can think of. + +Crank ships with two renderer subclasses for web development: one for managing DOM nodes, available through the module `@bikeshaving/crank/dom`, and one for creating HTML strings, available through the module `@bikeshaving/crank/html`. You can use these modules to render interactive user interfaces in the browser and HTML responses on the server. ```jsx /** @jsx createElement */ @@ -31,14 +35,14 @@ console.log(node.innerHTML); //
Hello world
console.log(HTMLRenderer.render(el)); //
Hello world
``` -## The parts of an element +## The Parts of an Element ![Image of a JSX element](../static/parts-of-jsx.svg) -An element can be thought of as having three main parts: a *tag*, *props* and *children*. These roughly correspond to tags, attributes and content in HTML, and for the most part, you can copy-paste HTML into JavaScript and have things work as you would expect. The main difference is that JSX has to be well-balanced like XML, so void tags must have a closing slash (`
` not `
`). Also, if you forget to close an element or mismatch opening and closing tags, the parser will throw an error, whereas HTML can contain unbalanced or malformed contents and mostly still work. The advantage of using JSX is that it allows you to interpolate arbitrary JavaScript expressions as an element’s tag, props or children. +An element can be thought of as having three main parts: a *tag*, *props* and *children*. These roughly correspond to the syntax for tags, attributes and content in HTML, and for the most part, you can copy-paste HTML into JSX-flavored JavaScript and have things work as you would expect. The main difference is that JSX has to be well-balanced like XML, so void tags must have a closing slash (`
` not `
`). Also, if you forget to close an element or mismatch opening and closing tags, the parser will throw an error, whereas HTML can contain unbalanced or malformed and mostly still work. The advantage of using JSX is that it allows you to interpolate arbitrary JavaScript expressions as an element’s tag, props or children. ### Tags -Tags are the first part of an element expression, and can be thought of as the “name” or “type” of the element. JSX parsers will transpile the tag name as the first argument of a `createElement` call. +Tags are the first part of a JSX element expression, and can be thought of as the “name” or “type” of the element. JSX parsers will transpile the tag name as the first argument of a `createElement` call. ```jsx const intrinsicEl =
; @@ -50,11 +54,10 @@ const componentEl = ; const componentEl1 = createElement(Component, null); ``` -By convention, JSX parsers treat lower-cased tags as strings and capitalized tags as variables. When a tag is a string, this signifies that the element will be handled by the renderer. We call elements with string tags *host* or *intrinsic* elements, and for both of the web renderers, these elements correspond to actual HTML elements like `div` or `input`. As we’ll see later, elements can also have function tags, in which case the behavior of the element is defined by the execution of the referenced function and not the renderer. Elements with function tags are called *component elements*. - +By convention, JSX parsers treat lowercased tags as strings and capitalized tags as variables. When a tag is a string, this signifies that the element will be handled by the renderer. We call elements with string tags *host* or *intrinsic* elements, and for both of the web renderers, these elements correspond to actual HTML elements like `div` or `input`. As we’ll see later, elements can also have function tags, in which case the behavior of the element is defined not by the renderer but by the execution of the referenced function. Elements with function tags are called *component elements*. ### Props -JSX parsers coalesce the attribute-like `key="value"` syntax to a single object for each element, and pass this object as the second argument to the resulting `createElement` call. +JSX parsers coalesce the attribute-like `key="value"` syntax to a single object for each element, and pass this object to the resulting `createElement` call as its second argument. ```jsx const myClass = "my-class"; @@ -65,7 +68,7 @@ const el1 = createElement("div", {id: "my-id", "class": myClass}); console.log(el.props); // {id: "my-id", "class": "my-class"} ``` -We call this object the *props* object, short for “properties.” The value of each prop is a string if the string-like syntax is used (`key="value"`), or it can be an interpolated JavaScript value if the value is placed within curly brackets (`key={value}`). You can use props to “pass” values into host and component elements, similar to how you might pass arguments into functions when invoking them. +We call this object the *props* object, short for “properties.” The value of each prop is a string if the string-like syntax is used (`key="value"`), or it can be an interpolated JavaScript expression by placing the value in curly brackets (`key={value}`). You can use props to “pass” values into host and component elements, similar to how you might pass arguments into functions when invoking them. If you already have an object that you want to use as props, you can use the special JSX `...` syntax to “spread” it into an element. This works similarly to [ES6 spread syntax](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax). @@ -77,7 +80,7 @@ const el1 = createElement("img", {...props, id: "2"}); ``` ### Children -As with HTML, Crank elements can have contents, placed between its opening and closing tags. These contents are referred to as *children*. Because elements can have children which are also elements, they form a nested tree of nodes which we call the *element tree*. +As with HTML, Crank elements can have contents, placed between its opening and closing tags. These contents are referred to as the element’s *children.* Because elements can have children which are also elements, they form a tree of nodes which we call the *element tree*. ```jsx const list = ( @@ -95,7 +98,7 @@ const list1 = createElement("ul", null, console.log(list.props.children.length); // 2 ``` -As you can see in the example, JSX parsers interpret the contents of an element which fall outside elements as strings. However, just as with props, you can use curly brackets to interpolate JavaScript expressions into an element’s children. Besides elements and strings, almost every value in JavaScript can participate in an element tree. Numbers are rendered as strings, and the values `null`, `undefined`, `true` and `false` are erased, allowing you to render things conditionally with boolean expressions. +As seen in the example, JSX parsers interpret the contents of elements which are not themselves elements as strings. However, just as with props, you can use curly brackets to interpolate JavaScript expressions into an element’s children. Besides elements and strings, almost every value in JavaScript can participate in an element tree. Numbers are rendered as strings, and the values `null`, `undefined`, `true` and `false` are erased, allowing you to render things conditionally with boolean expressions. ```jsx const el =
{"a"}{1 + 1}{true}{false}{null}{undefined}
; @@ -113,8 +116,8 @@ renderer.render(
{arr} {set}
, document.body); console.log(document.body.innerHTML); // "
123 abc
" ``` -## Element diffing -Crank uses the same “virtual DOM diffing” algorithm made popular by React, where we compare elements by tag and position to reduce and reuse DOM mutations. This approach allows you to write declarative code which focuses on producing the right element tree, while Crank does the dirty work of managing state and mutating the DOM. +## Element Diffing +Crank uses the same “virtual DOM” diffing algorithm made popular by React, where we compare elements by tag and position to reduce the number of DOM mutations and reuse DOM nodes. This approach allows you to write declarative code which focuses on producing the right tree, while Crank does the dirty work of managing state and mutating the DOM. ```jsx renderer.render( @@ -138,4 +141,4 @@ console.log(document.body.firstChild === div); // true console.log(document.body.firstChild.firstChild === span); // true ``` -**NOTE:** We usually avoid using the term “virtual DOM” in Crank, insofar as the Crank renderer can render to multiple environments; instead, we use the term “element diffing” to mean mostly the same thing. +**Note:** We usually avoid using the term “virtual DOM” in Crank, insofar as the core renderer can be extended to target multiple environments; instead, we use the term “element diffing” to mean mostly the same thing. diff --git a/website/guides/03-components.md b/website/guides/03-components.md index 3fec69046..6a79c0823 100644 --- a/website/guides/03-components.md +++ b/website/guides/03-components.md @@ -5,7 +5,7 @@ title: Components So far, we’ve only seen and used host elements, but eventually, we’ll want to group parts of the element tree into reusable *components.* In Crank, all components are functions; there is no class-based component API. ## Basic Components -The simplest kind of component you can write is a *sync function component*. When rendered, the function is invoked with the props object of the element as its first argument, and the return value of the function is recursively rendered as the element’s children. +The simplest kind of component is a *function component*. When rendered, the function is invoked with the props of the element as its first argument, and the return value of the function is recursively rendered as the element’s children. ```js function Greeting({name}) { @@ -37,18 +37,8 @@ renderer.render( console.log(document.body.innerHTML); // "
Message for Nemo: Howdy
" ``` -You may have noticed in the preceding examples that we used [object destructuring](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Object_destructuring) on the props parameter for convenience. You can further assign default values to a specific prop by using JavaScript’s default value syntax. - -```js -function Greeting({name="World"}) { - return
Hello, {name}
; -} - -renderer.render(, document.body); // "
Hello World
" -``` - ## Stateful Components -Eventually, you’re going to want to write components with local state. In Crank, we use [generator functions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function*) to do so. These types of components are referred to as *sync generator components*. +Eventually, youre going to want to write components with local state. In Crank, we use [generator functions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function*) to do so. These types of components are referred to as *generator components*. ```jsx function *Counter() { @@ -82,10 +72,10 @@ console.log(document.body.innerHTML); // "
You have updated this component 1 time
" ``` -Because we’re now yielding elements rather than returning them, we can make components stateful using variables in the local scope. Every time the component is updated, Crank resumes the generator, pausing at the next `yield`. The yielded expressions, usually elements, are then recursively rendered, just as if it were returned in a sync function component. Furthermore, Crank uses the same diffing algorithm which reuses DOM nodes to reuse generator objects, so that the execution of the generator is preserved between renders. This allows local state to be encapsulated within the generator’s scope. +By yielding elements rather than returning them, we can make components stateful using variables in the generator’s local scope. Every time the component is updated, Crank resumes the generator, pausing at the next `yield`. The yielded expressions, usually elements, are then recursively rendered, just as if they were returned from a function component. Furthermore, Crank uses the same diffing algorithm which reuses DOM nodes to reuse generator objects, so that the execution of generator components are preserved between renders. ### Contexts -In the preceding example, the `Counter` component’s local state only changes when it is rerendered, but we may want to write components which update themselves based on timers or events. Crank allows components to control themselves by passing in a custom object called a *context* as the `this` keyword of each component. Contexts provide several utility methods, most important of which is the `refresh` method, which tells Crank to update the related component in place. +In the preceding example, the `Counter` component’s local state changes when it is rerendered, but we may want to write components which update themselves instead according to timers or events. Crank allows components to control themselves by passing in an object called a *context* as the `this` keyword of each component. Contexts provide several utility methods, most important of which is the `refresh` method, which tells Crank to update the related component instance in place. ```jsx function *Timer() { @@ -111,7 +101,7 @@ This `Timer` component is similar to the `Counter` one, except now the state (th One important detail about the `Timer` example is that it cleans up after itself with `clearInterval`. Crank will call the `return` method on generator components when the element is removed from the tree, so that the finally block executes and `clearInterval` is called. In this way, you can use the natural lifecycle of a generator to write setup and teardown logic for components, all within the same scope. -### Prop updates +### Props Updates The generator components we’ve seen so far haven’t used props. Generator components can accept props as its first parameter just like regular function components. ```jsx @@ -183,3 +173,26 @@ console.log(document.body.innerHTML); // "
Hello again Bob
" ``` The fact that state is just local variables allows us to blur the lines between props and state, in a way that is easy to understand and without lifecycle methods like `componentWillUpdate` from React. With generators and `for` loops, comparing old and new props is as easy as comparing adjacent elements of an array. + +## Default Props +You may have noticed in the preceding examples that we used [object destructuring](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Object_destructuring) on the props parameter for convenience. You can further assign default values to a specific prop by using JavaScript’s default value syntax. + +```jsx +function Greeting({name="World"}) { + return
Hello, {name}
; +} + +renderer.render(, document.body); // "
Hello World
" +``` + +This works well for function components, but for generator components, you should make sure that you use the same default in both the parameter list and the `for` statement. + +```jsx +function *Greeting({name="World"}) { + for ({name="World"} of this) { + yield
Hello, {name}
; + } +} +``` + +Components which mismatch default props between these two positions can cause surprising behavior. diff --git a/website/guides/04-handling-events.md b/website/guides/04-handling-events.md index b6bd0876f..f74d5b43c 100644 --- a/website/guides/04-handling-events.md +++ b/website/guides/04-handling-events.md @@ -2,10 +2,10 @@ title: Handling Events --- -Most DOM applications require some measure of interactivity, where the user interface updates according to user input. To facilitate this, Crank provides two APIs for listening to events on rendered DOM nodes. +Most web applications require some measure of interactivity, where the user interface updates according to user input. To facilitate this, Crank provides two APIs for listening to events on rendered DOM nodes. -## onevent props -You can attach event callbacks to host element directly using onevent props. These props start with `on`, are all lowercase, and correspond exactly to the properties as specified according to the DOM’s [GlobalEventHandlers mixin API](https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers). By combining event props, local variables and `this.refresh`, you can write interactive components. +## DOM `onevent` Props +You can attach event callbacks to host element directly using `onevent` props. These props start with `on`, are all lowercase, and correspond to the properties as specified according to the DOM’s [GlobalEventHandlers mixin API](https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers). By combining event props, local variables and `this.refresh`, you can write interactive components. ```jsx function *Clicker() { @@ -26,7 +26,7 @@ function *Clicker() { } ``` -## The EventTarget interface +## The EventTarget Interface As an alternative to the `onevent` API, Crank contexts also implement the same `EventTarget` interface used by the DOM. The `addEventListener` method attaches a listener to a component’s rendered DOM. ```jsx @@ -49,7 +49,7 @@ The local state `count` is now updated in the event listener, which triggers whe **NOTE:** When using the `addEventListener` method, you do not have to call the `removeEventListener` method if you merely need to remove event listeners when the component is unmounted. This is done automatically. -## Event delegation +## Event Delegation The Context’s `addEventListener` method only attaches to the top-level node or nodes which each component renders, so if you want to listen to events on a nested node, you must use event delegation: @@ -76,10 +76,10 @@ function *Clicker() { Because the event listener is attached to the outer `div`, we have to filter events by `ev.target.tagName` in the listener to make sure we’re not incrementing `count` based on clicks which don’t target the `button` element. -## When to use onevent props and the EventTarget API -The props-based onevent API and the context-based EventTarget API both have their advantages. On the one hand, using onevent props means you don’t have to filter events by target, and you can register them on the exact element you’d like to listen to. +## `onevent` vs `EventTarget` +The props-based `onevent` API and the context-based `EventTarget` API both have their advantages. On the one hand, using `onevent` props means you don’t have to filter events by target, and you can register them on the exact element you’d like to listen to. -On the other hand, using `this.addEventListener` allows you to take full advantage of the EventTarget API, including registering passive event listeners or events which are dispatched during the capture phase. Additionally, the EventTarget API can be used without referencing or accessing the child elements which a component renders, meaning you can use it to listen to components whose children are passed in, or in utility functions which don’t have access to the produced elements. +On the other, using the `addEventListener` method allows you to take full advantage of the EventTarget API, including registering passive event listeners or events which are dispatched during the capture phase. Additionally, the EventTarget API can be used without referencing or accessing the child elements which a component renders, meaning you can use it to listen to components whose children are passed in, or in utility functions which don’t have access to produced elements. Crank supports both API styles for convenience and flexibility. @@ -127,3 +127,100 @@ function *MyApp() { ``` `MyButton` is a function component which wraps a `button` element. It dispatches a `CustomEvent` whose type is `"mybuttonclick"` when it is pressed, and whose `detail` property contains data about the id of the clicked button. This event is not triggered or bubbled on the underlying DOM nodes; instead, it can be listened for by parent component contexts using event bubbling, and in the example, the event propagates and is handled by the `MyApp` component. Using custom events and event bubbling allows you to encapsulate state transitions within component hierarchies without the need for complex state management solutions used in other frameworks like Redux or VueX. + +## Controlled and Uncontrolled Props +Form elements like ``, `