├── .gitignore ├── package.json ├── license ├── yarn.lock └── readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "unified-handbook", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "repository": "unifiedjs/handbook", 6 | "author": "John Otander ", 7 | "license": "MIT", 8 | "scripts": { 9 | "start": "node build" 10 | }, 11 | "dependencies": { 12 | "remark": "^11.0.1", 13 | "remark-toc": "^6.0.0" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019 John Otander and Titus Wormer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@types/unist@^2.0.0", "@types/unist@^2.0.2": 6 | version "2.0.3" 7 | resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.3.tgz#9c088679876f374eb5983f150d4787aa6fb32d7e" 8 | integrity sha512-FvUupuM3rlRsRtCN+fDudtmytGO6iHJuuRKS1Ss0pG5z8oX0diNEw94UEL7hgDbpN94rgaK5R7sWm6RrSkZuAQ== 9 | 10 | bail@^1.0.0: 11 | version "1.0.4" 12 | resolved "https://registry.yarnpkg.com/bail/-/bail-1.0.4.tgz#7181b66d508aa3055d3f6c13f0a0c720641dde9b" 13 | integrity sha512-S8vuDB4w6YpRhICUDET3guPlQpaJl7od94tpZ0Fvnyp+MKW/HyDTcRDck+29C9g+d/qQHnddRH3+94kZdrW0Ww== 14 | 15 | ccount@^1.0.0: 16 | version "1.0.4" 17 | resolved "https://registry.yarnpkg.com/ccount/-/ccount-1.0.4.tgz#9cf2de494ca84060a2a8d2854edd6dfb0445f386" 18 | integrity sha512-fpZ81yYfzentuieinmGnphk0pLkOTMm6MZdVqwd77ROvhko6iujLNGrHH5E7utq3ygWklwfmwuG+A7P+NpqT6w== 19 | 20 | character-entities-html4@^1.0.0: 21 | version "1.1.3" 22 | resolved "https://registry.yarnpkg.com/character-entities-html4/-/character-entities-html4-1.1.3.tgz#5ce6e01618e47048ac22f34f7f39db5c6fd679ef" 23 | integrity sha512-SwnyZ7jQBCRHELk9zf2CN5AnGEc2nA+uKMZLHvcqhpPprjkYhiLn0DywMHgN5ttFZuITMATbh68M6VIVKwJbcg== 24 | 25 | character-entities-legacy@^1.0.0: 26 | version "1.1.3" 27 | resolved "https://registry.yarnpkg.com/character-entities-legacy/-/character-entities-legacy-1.1.3.tgz#3c729991d9293da0ede6dddcaf1f2ce1009ee8b4" 28 | integrity sha512-YAxUpPoPwxYFsslbdKkhrGnXAtXoHNgYjlBM3WMXkWGTl5RsY3QmOyhwAgL8Nxm9l5LBThXGawxKPn68y6/fww== 29 | 30 | character-entities@^1.0.0: 31 | version "1.2.3" 32 | resolved "https://registry.yarnpkg.com/character-entities/-/character-entities-1.2.3.tgz#bbed4a52fe7ef98cc713c6d80d9faa26916d54e6" 33 | integrity sha512-yB4oYSAa9yLcGyTbB4ItFwHw43QHdH129IJ5R+WvxOkWlyFnR5FAaBNnUq4mcxsTVZGh28bHoeTHMKXH1wZf3w== 34 | 35 | character-reference-invalid@^1.0.0: 36 | version "1.1.3" 37 | resolved "https://registry.yarnpkg.com/character-reference-invalid/-/character-reference-invalid-1.1.3.tgz#1647f4f726638d3ea4a750cf5d1975c1c7919a85" 38 | integrity sha512-VOq6PRzQBam/8Jm6XBGk2fNEnHXAdGd6go0rtd4weAGECBamHDwwCQSOT12TACIYUZegUXnV6xBXqUssijtxIg== 39 | 40 | collapse-white-space@^1.0.2: 41 | version "1.0.5" 42 | resolved "https://registry.yarnpkg.com/collapse-white-space/-/collapse-white-space-1.0.5.tgz#c2495b699ab1ed380d29a1091e01063e75dbbe3a" 43 | integrity sha512-703bOOmytCYAX9cXYqoikYIx6twmFCXsnzRQheBcTG3nzKYBR4P/+wkYeH+Mvj7qUz8zZDtdyzbxfnEi/kYzRQ== 44 | 45 | "emoji-regex@>=6.0.0 <=6.1.1": 46 | version "6.1.1" 47 | resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-6.1.1.tgz#c6cd0ec1b0642e2a3c67a1137efc5e796da4f88e" 48 | integrity sha1-xs0OwbBkLio8Z6ETfvxeeW2k+I4= 49 | 50 | extend@^3.0.0, extend@^3.0.2: 51 | version "3.0.2" 52 | resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" 53 | integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== 54 | 55 | github-slugger@^1.2.1: 56 | version "1.2.1" 57 | resolved "https://registry.yarnpkg.com/github-slugger/-/github-slugger-1.2.1.tgz#47e904e70bf2dccd0014748142d31126cfd49508" 58 | integrity sha512-SsZUjg/P03KPzQBt7OxJPasGw6NRO5uOgiZ5RGXVud5iSIZ0eNZeNp5rTwCxtavrRUa/A77j8mePVc5lEvk0KQ== 59 | dependencies: 60 | emoji-regex ">=6.0.0 <=6.1.1" 61 | 62 | inherits@^2.0.1: 63 | version "2.0.4" 64 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" 65 | integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== 66 | 67 | is-alphabetical@^1.0.0: 68 | version "1.0.3" 69 | resolved "https://registry.yarnpkg.com/is-alphabetical/-/is-alphabetical-1.0.3.tgz#eb04cc47219a8895d8450ace4715abff2258a1f8" 70 | integrity sha512-eEMa6MKpHFzw38eKm56iNNi6GJ7lf6aLLio7Kr23sJPAECscgRtZvOBYybejWDQ2bM949Y++61PY+udzj5QMLA== 71 | 72 | is-alphanumeric@^1.0.0: 73 | version "1.0.0" 74 | resolved "https://registry.yarnpkg.com/is-alphanumeric/-/is-alphanumeric-1.0.0.tgz#4a9cef71daf4c001c1d81d63d140cf53fd6889f4" 75 | integrity sha1-Spzvcdr0wAHB2B1j0UDPU/1oifQ= 76 | 77 | is-alphanumerical@^1.0.0: 78 | version "1.0.3" 79 | resolved "https://registry.yarnpkg.com/is-alphanumerical/-/is-alphanumerical-1.0.3.tgz#57ae21c374277b3defe0274c640a5704b8f6657c" 80 | integrity sha512-A1IGAPO5AW9vSh7omxIlOGwIqEvpW/TA+DksVOPM5ODuxKlZS09+TEM1E3275lJqO2oJ38vDpeAL3DCIiHE6eA== 81 | dependencies: 82 | is-alphabetical "^1.0.0" 83 | is-decimal "^1.0.0" 84 | 85 | is-buffer@^2.0.0: 86 | version "2.0.3" 87 | resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.3.tgz#4ecf3fcf749cbd1e472689e109ac66261a25e725" 88 | integrity sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw== 89 | 90 | is-decimal@^1.0.0, is-decimal@^1.0.2: 91 | version "1.0.3" 92 | resolved "https://registry.yarnpkg.com/is-decimal/-/is-decimal-1.0.3.tgz#381068759b9dc807d8c0dc0bfbae2b68e1da48b7" 93 | integrity sha512-bvLSwoDg2q6Gf+E2LEPiklHZxxiSi3XAh4Mav65mKqTfCO1HM3uBs24TjEH8iJX3bbDdLXKJXBTmGzuTUuAEjQ== 94 | 95 | is-hexadecimal@^1.0.0: 96 | version "1.0.3" 97 | resolved "https://registry.yarnpkg.com/is-hexadecimal/-/is-hexadecimal-1.0.3.tgz#e8a426a69b6d31470d3a33a47bb825cda02506ee" 98 | integrity sha512-zxQ9//Q3D/34poZf8fiy3m3XVpbQc7ren15iKqrTtLPwkPD/t3Scy9Imp63FujULGxuK0ZlCwoo5xNpktFgbOA== 99 | 100 | is-plain-obj@^2.0.0: 101 | version "2.0.0" 102 | resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.0.0.tgz#7fd1a7f1b69e160cde9181d2313f445c68aa2679" 103 | integrity sha512-EYisGhpgSCwspmIuRHGjROWTon2Xp8Z7U03Wubk/bTL5TTRC5R1rGVgyjzBrk9+ULdH6cRD06KRcw/xfqhVYKQ== 104 | 105 | is-whitespace-character@^1.0.0: 106 | version "1.0.3" 107 | resolved "https://registry.yarnpkg.com/is-whitespace-character/-/is-whitespace-character-1.0.3.tgz#b3ad9546d916d7d3ffa78204bca0c26b56257fac" 108 | integrity sha512-SNPgMLz9JzPccD3nPctcj8sZlX9DAMJSKH8bP7Z6bohCwuNgX8xbWr1eTAYXX9Vpi/aSn8Y1akL9WgM3t43YNQ== 109 | 110 | is-word-character@^1.0.0: 111 | version "1.0.3" 112 | resolved "https://registry.yarnpkg.com/is-word-character/-/is-word-character-1.0.3.tgz#264d15541cbad0ba833d3992c34e6b40873b08aa" 113 | integrity sha512-0wfcrFgOOOBdgRNT9H33xe6Zi6yhX/uoc4U8NBZGeQQB0ctU1dnlNTyL9JM2646bHDTpsDm1Brb3VPoCIMrd/A== 114 | 115 | longest-streak@^2.0.1: 116 | version "2.0.3" 117 | resolved "https://registry.yarnpkg.com/longest-streak/-/longest-streak-2.0.3.tgz#3de7a3f47ee18e9074ded8575b5c091f5d0a4105" 118 | integrity sha512-9lz5IVdpwsKLMzQi0MQ+oD9EA0mIGcWYP7jXMTZVXP8D42PwuAk+M/HBFYQoxt1G5OR8m7aSIgb1UymfWGBWEw== 119 | 120 | markdown-escapes@^1.0.0: 121 | version "1.0.3" 122 | resolved "https://registry.yarnpkg.com/markdown-escapes/-/markdown-escapes-1.0.3.tgz#6155e10416efaafab665d466ce598216375195f5" 123 | integrity sha512-XUi5HJhhV5R74k8/0H2oCbCiYf/u4cO/rX8tnGkRvrqhsr5BRNU6Mg0yt/8UIx1iIS8220BNJsDb7XnILhLepw== 124 | 125 | markdown-table@^1.1.0: 126 | version "1.1.3" 127 | resolved "https://registry.yarnpkg.com/markdown-table/-/markdown-table-1.1.3.tgz#9fcb69bcfdb8717bfd0398c6ec2d93036ef8de60" 128 | integrity sha512-1RUZVgQlpJSPWYbFSpmudq5nHY1doEIv89gBtF0s4gW1GF2XorxcA/70M5vq7rLv0a6mhOUccRsqkwhwLCIQ2Q== 129 | 130 | mdast-util-compact@^1.0.0: 131 | version "1.0.3" 132 | resolved "https://registry.yarnpkg.com/mdast-util-compact/-/mdast-util-compact-1.0.3.tgz#98a25cc8a7865761a41477b3a87d1dcef0b1e79d" 133 | integrity sha512-nRiU5GpNy62rZppDKbLwhhtw5DXoFMqw9UNZFmlPsNaQCZ//WLjGKUwWMdJrUH+Se7UvtO2gXtAMe0g/N+eI5w== 134 | dependencies: 135 | unist-util-visit "^1.1.0" 136 | 137 | mdast-util-to-string@^1.0.5: 138 | version "1.0.6" 139 | resolved "https://registry.yarnpkg.com/mdast-util-to-string/-/mdast-util-to-string-1.0.6.tgz#7d85421021343b33de1552fc71cb8e5b4ae7536d" 140 | integrity sha512-868pp48gUPmZIhfKrLbaDneuzGiw3OTDjHc5M1kAepR2CWBJ+HpEsm252K4aXdiP5coVZaJPOqGtVU6Po8xnXg== 141 | 142 | mdast-util-toc@^4.0.0: 143 | version "4.2.0" 144 | resolved "https://registry.yarnpkg.com/mdast-util-toc/-/mdast-util-toc-4.2.0.tgz#9085c4313359c981e4c1d22efe02f8ce01072da4" 145 | integrity sha512-BEbxg3GtAsvswmV9bMAV/WJA7v0CsG5tYk90xnTlIeCnnBKD6Cof3KEg/K3xQ1O2FRyJKMWkgKZB2mpnOjA0tg== 146 | dependencies: 147 | extend "^3.0.2" 148 | github-slugger "^1.2.1" 149 | mdast-util-to-string "^1.0.5" 150 | unist-util-is "^3.0.0" 151 | unist-util-visit "^1.1.0" 152 | 153 | parse-entities@^1.0.2, parse-entities@^1.1.0: 154 | version "1.2.2" 155 | resolved "https://registry.yarnpkg.com/parse-entities/-/parse-entities-1.2.2.tgz#c31bf0f653b6661354f8973559cb86dd1d5edf50" 156 | integrity sha512-NzfpbxW/NPrzZ/yYSoQxyqUZMZXIdCfE0OIN4ESsnptHJECoUk3FZktxNuzQf4tjt5UEopnxpYJbvYuxIFDdsg== 157 | dependencies: 158 | character-entities "^1.0.0" 159 | character-entities-legacy "^1.0.0" 160 | character-reference-invalid "^1.0.0" 161 | is-alphanumerical "^1.0.0" 162 | is-decimal "^1.0.0" 163 | is-hexadecimal "^1.0.0" 164 | 165 | remark-parse@^7.0.0: 166 | version "7.0.1" 167 | resolved "https://registry.yarnpkg.com/remark-parse/-/remark-parse-7.0.1.tgz#0c13d67e0d7b82c2ad2d8b6604ec5fae6c333c2b" 168 | integrity sha512-WOZLa545jYXtSy+txza6ACudKWByQac4S2DmGk+tAGO/3XnVTOxwyCIxB7nTcLlk8Aayhcuf3cV1WV6U6L7/DQ== 169 | dependencies: 170 | collapse-white-space "^1.0.2" 171 | is-alphabetical "^1.0.0" 172 | is-decimal "^1.0.0" 173 | is-whitespace-character "^1.0.0" 174 | is-word-character "^1.0.0" 175 | markdown-escapes "^1.0.0" 176 | parse-entities "^1.1.0" 177 | repeat-string "^1.5.4" 178 | state-toggle "^1.0.0" 179 | trim "0.0.1" 180 | trim-trailing-lines "^1.0.0" 181 | unherit "^1.0.4" 182 | unist-util-remove-position "^1.0.0" 183 | vfile-location "^2.0.0" 184 | xtend "^4.0.1" 185 | 186 | remark-stringify@^7.0.0: 187 | version "7.0.3" 188 | resolved "https://registry.yarnpkg.com/remark-stringify/-/remark-stringify-7.0.3.tgz#9221e9770b0b395af83a0d5881a44b6fcb9d0a2a" 189 | integrity sha512-+jgmjNjm2kR7y2Ns1BATXRlFr+iQ7sDcpSgytfU77nkw7UCd5yJNArSxB3MU3Uul7HuyYNTCjetoGfy8xLia1A== 190 | dependencies: 191 | ccount "^1.0.0" 192 | is-alphanumeric "^1.0.0" 193 | is-decimal "^1.0.0" 194 | is-whitespace-character "^1.0.0" 195 | longest-streak "^2.0.1" 196 | markdown-escapes "^1.0.0" 197 | markdown-table "^1.1.0" 198 | mdast-util-compact "^1.0.0" 199 | parse-entities "^1.0.2" 200 | repeat-string "^1.5.4" 201 | state-toggle "^1.0.0" 202 | stringify-entities "^2.0.0" 203 | unherit "^1.0.4" 204 | xtend "^4.0.1" 205 | 206 | remark-toc@^6.0.0: 207 | version "6.0.0" 208 | resolved "https://registry.yarnpkg.com/remark-toc/-/remark-toc-6.0.0.tgz#32210839ab74a8ac3696aa78a178338989c5a76f" 209 | integrity sha512-h3I79AOiza+/JzfeWPOfKE13ISVsgX7/huHvhYQG5Xe4Pe2U/CTXDSl0Dqr5O2hBub+gBS1kM1lEALk4jv7qBA== 210 | dependencies: 211 | mdast-util-toc "^4.0.0" 212 | 213 | remark@^11.0.1: 214 | version "11.0.1" 215 | resolved "https://registry.yarnpkg.com/remark/-/remark-11.0.1.tgz#3c16e1ed84c78a661299991bb8d5fa7ee5d18e3c" 216 | integrity sha512-Fl2AvN+yU6sOBAjUz3xNC5iEvLkXV8PZicLOOLifjU8uKGusNvhHfGRCfETsqyvRHZ24JXqEyDY4hRLhoUd30A== 217 | dependencies: 218 | remark-parse "^7.0.0" 219 | remark-stringify "^7.0.0" 220 | unified "^8.2.0" 221 | 222 | repeat-string@^1.5.4: 223 | version "1.6.1" 224 | resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" 225 | integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= 226 | 227 | replace-ext@1.0.0: 228 | version "1.0.0" 229 | resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-1.0.0.tgz#de63128373fcbf7c3ccfa4de5a480c45a67958eb" 230 | integrity sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs= 231 | 232 | state-toggle@^1.0.0: 233 | version "1.0.2" 234 | resolved "https://registry.yarnpkg.com/state-toggle/-/state-toggle-1.0.2.tgz#75e93a61944116b4959d665c8db2d243631d6ddc" 235 | integrity sha512-8LpelPGR0qQM4PnfLiplOQNJcIN1/r2Gy0xKB2zKnIW2YzPMt2sR4I/+gtPjhN7Svh9kw+zqEg2SFwpBO9iNiw== 236 | 237 | stringify-entities@^2.0.0: 238 | version "2.0.0" 239 | resolved "https://registry.yarnpkg.com/stringify-entities/-/stringify-entities-2.0.0.tgz#fa7ca6614b355fb6c28448140a20c4ede7462827" 240 | integrity sha512-fqqhZzXyAM6pGD9lky/GOPq6V4X0SeTAFBl0iXb/BzOegl40gpf/bV3QQP7zULNYvjr6+Dx8SCaDULjVoOru0A== 241 | dependencies: 242 | character-entities-html4 "^1.0.0" 243 | character-entities-legacy "^1.0.0" 244 | is-alphanumerical "^1.0.0" 245 | is-decimal "^1.0.2" 246 | is-hexadecimal "^1.0.0" 247 | 248 | trim-trailing-lines@^1.0.0: 249 | version "1.1.2" 250 | resolved "https://registry.yarnpkg.com/trim-trailing-lines/-/trim-trailing-lines-1.1.2.tgz#d2f1e153161152e9f02fabc670fb40bec2ea2e3a" 251 | integrity sha512-MUjYItdrqqj2zpcHFTkMa9WAv4JHTI6gnRQGPFLrt5L9a6tRMiDnIqYl8JBvu2d2Tc3lWJKQwlGCp0K8AvCM+Q== 252 | 253 | trim@0.0.1: 254 | version "0.0.1" 255 | resolved "https://registry.yarnpkg.com/trim/-/trim-0.0.1.tgz#5858547f6b290757ee95cccc666fb50084c460dd" 256 | integrity sha1-WFhUf2spB1fulczMZm+1AITEYN0= 257 | 258 | trough@^1.0.0: 259 | version "1.0.4" 260 | resolved "https://registry.yarnpkg.com/trough/-/trough-1.0.4.tgz#3b52b1f13924f460c3fbfd0df69b587dbcbc762e" 261 | integrity sha512-tdzBRDGWcI1OpPVmChbdSKhvSVurznZ8X36AYURAcl+0o2ldlCY2XPzyXNNxwJwwyIU+rIglTCG4kxtNKBQH7Q== 262 | 263 | unherit@^1.0.4: 264 | version "1.1.2" 265 | resolved "https://registry.yarnpkg.com/unherit/-/unherit-1.1.2.tgz#14f1f397253ee4ec95cec167762e77df83678449" 266 | integrity sha512-W3tMnpaMG7ZY6xe/moK04U9fBhi6wEiCYHUW5Mop/wQHf12+79EQGwxYejNdhEz2mkqkBlGwm7pxmgBKMVUj0w== 267 | dependencies: 268 | inherits "^2.0.1" 269 | xtend "^4.0.1" 270 | 271 | unified@^8.2.0: 272 | version "8.3.2" 273 | resolved "https://registry.yarnpkg.com/unified/-/unified-8.3.2.tgz#aed69d0e577d6ef27268431c63a10faef60e63ab" 274 | integrity sha512-NDtUAXcd4c+mKppCbsZHzmhkKEQuhveZNBrFYmNgMIMk2K9bc8hmG3mLEGVtRmSNodobwyMePAnvIGVWZfPdzQ== 275 | dependencies: 276 | bail "^1.0.0" 277 | extend "^3.0.0" 278 | is-plain-obj "^2.0.0" 279 | trough "^1.0.0" 280 | vfile "^4.0.0" 281 | 282 | unist-util-is@^3.0.0: 283 | version "3.0.0" 284 | resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-3.0.0.tgz#d9e84381c2468e82629e4a5be9d7d05a2dd324cd" 285 | integrity sha512-sVZZX3+kspVNmLWBPAB6r+7D9ZgAFPNWm66f7YNb420RlQSbn+n8rG8dGZSkrER7ZIXGQYNm5pqC3v3HopH24A== 286 | 287 | unist-util-remove-position@^1.0.0: 288 | version "1.1.3" 289 | resolved "https://registry.yarnpkg.com/unist-util-remove-position/-/unist-util-remove-position-1.1.3.tgz#d91aa8b89b30cb38bad2924da11072faa64fd972" 290 | integrity sha512-CtszTlOjP2sBGYc2zcKA/CvNdTdEs3ozbiJ63IPBxh8iZg42SCCb8m04f8z2+V1aSk5a7BxbZKEdoDjadmBkWA== 291 | dependencies: 292 | unist-util-visit "^1.1.0" 293 | 294 | unist-util-stringify-position@^2.0.0: 295 | version "2.0.1" 296 | resolved "https://registry.yarnpkg.com/unist-util-stringify-position/-/unist-util-stringify-position-2.0.1.tgz#de2a2bc8d3febfa606652673a91455b6a36fb9f3" 297 | integrity sha512-Zqlf6+FRI39Bah8Q6ZnNGrEHUhwJOkHde2MHVk96lLyftfJJckaPslKgzhVcviXj8KcE9UJM9F+a4JEiBUTYgA== 298 | dependencies: 299 | "@types/unist" "^2.0.2" 300 | 301 | unist-util-visit-parents@^2.0.0: 302 | version "2.1.2" 303 | resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-2.1.2.tgz#25e43e55312166f3348cae6743588781d112c1e9" 304 | integrity sha512-DyN5vD4NE3aSeB+PXYNKxzGsfocxp6asDc2XXE3b0ekO2BaRUpBicbbUygfSvYfUz1IkmjFR1YF7dPklraMZ2g== 305 | dependencies: 306 | unist-util-is "^3.0.0" 307 | 308 | unist-util-visit@^1.1.0: 309 | version "1.4.1" 310 | resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-1.4.1.tgz#4724aaa8486e6ee6e26d7ff3c8685960d560b1e3" 311 | integrity sha512-AvGNk7Bb//EmJZyhtRUnNMEpId/AZ5Ph/KUpTI09WHQuDZHKovQ1oEv3mfmKpWKtoMzyMC4GLBm1Zy5k12fjIw== 312 | dependencies: 313 | unist-util-visit-parents "^2.0.0" 314 | 315 | vfile-location@^2.0.0: 316 | version "2.0.5" 317 | resolved "https://registry.yarnpkg.com/vfile-location/-/vfile-location-2.0.5.tgz#c83eb02f8040228a8d2b3f10e485be3e3433e0a2" 318 | integrity sha512-Pa1ey0OzYBkLPxPZI3d9E+S4BmvfVwNAAXrrqGbwTVXWaX2p9kM1zZ+n35UtVM06shmWKH4RPRN8KI80qE3wNQ== 319 | 320 | vfile-message@^2.0.0: 321 | version "2.0.1" 322 | resolved "https://registry.yarnpkg.com/vfile-message/-/vfile-message-2.0.1.tgz#951881861c22fc1eb39f873c0b93e336a64e8f6d" 323 | integrity sha512-KtasSV+uVU7RWhUn4Lw+wW1Zl/nW8JWx7JCPps10Y9JRRIDeDXf8wfBLoOSsJLyo27DqMyAi54C6Jf/d6Kr2Bw== 324 | dependencies: 325 | "@types/unist" "^2.0.2" 326 | unist-util-stringify-position "^2.0.0" 327 | 328 | vfile@^4.0.0: 329 | version "4.0.1" 330 | resolved "https://registry.yarnpkg.com/vfile/-/vfile-4.0.1.tgz#fc3d43a1c71916034216bf65926d5ee3c64ed60c" 331 | integrity sha512-lRHFCuC4SQBFr7Uq91oJDJxlnftoTLQ7eKIpMdubhYcVMho4781a8MWXLy3qZrZ0/STD1kRiKc0cQOHm4OkPeA== 332 | dependencies: 333 | "@types/unist" "^2.0.0" 334 | is-buffer "^2.0.0" 335 | replace-ext "1.0.0" 336 | unist-util-stringify-position "^2.0.0" 337 | vfile-message "^2.0.0" 338 | 339 | xtend@^4.0.1: 340 | version "4.0.2" 341 | resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" 342 | integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== 343 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # unified handbook 2 | 3 | **:warning: This is a work in progress** 4 | 5 | The compiler for your content. 6 | 7 | This handbook describes the unified ecosystem. It goes in depth about the numerous 8 | syntaxes it supports, usage, and practical guides on writing plugins. Additionally, 9 | it will attempt to define murky, computer science-y concepts that unified attempts 10 | to abstract away. 11 | 12 | ## Table of contents 13 | 14 | - [Introduction](#introduction) 15 | - [How does it work?](#how-does-it-work) 16 | - [Supported syntaxes](#supported-syntaxes) 17 | - [Abstract syntax trees](#abstract-syntax-trees) 18 | - [Constructing an AST](#constructing-an-ast) 19 | - [Parse example](#parse-example) 20 | - [unist](#unist) 21 | - [Motivation](#motivation) 22 | - [Visitors](#visitors) 23 | - [unist-util-visit](#unist-util-visit) 24 | - [Visit nodes based on context](#visit-nodes-based-on-context) 25 | - [unist-util-remove](#unist-util-remove) 26 | - [Advanced operations](#advanced-operations) 27 | - [Optimizing traversal](#optimizing-traversal) 28 | - [Removing nodes based on parent context](#removing-nodes-based-on-parent-context) 29 | - [unist resources](#unist-resources) 30 | - [unified](#unified) 31 | - [Parser](#parser) 32 | - [Compiler](#compiler) 33 | - [Transpiler](#transpiler) 34 | - [Usage](#usage) 35 | - [remark](#remark) 36 | - [remark CLI](#remark-cli) 37 | - [Inspect](#inspect) 38 | - [Use a plugin](#use-a-plugin) 39 | - [Lint](#lint) 40 | - [remark guides](#remark-guides) 41 | - [Writing a plugin to modify headings](#writing-a-plugin-to-modify-headings) 42 | - [rehype](#rehype) 43 | - [retext](#retext) 44 | - [MDX](#mdx) 45 | - [MDX transpilation pipeline](#mdx-transpilation-pipeline) 46 | - [Tree traversal](#tree-traversal) 47 | - [Breadth-first traversal](#breadth-first-traversal) 48 | - [Depth-first traversal](#depth-first-traversal) 49 | - [Glossary](#glossary) 50 | - [Tree](#tree) 51 | - [Child](#child) 52 | - [Parent](#parent) 53 | - [Index](#index) 54 | - [Sibling](#sibling) 55 | - [Root](#root) 56 | - [Descendant](#descendant) 57 | - [Ancestor](#ancestor) 58 | - [Head](#head) 59 | - [Tail](#tail) 60 | - [Leaf](#leaf) 61 | - [Branch](#branch) 62 | - [Generated](#generated) 63 | - [Type](#type) 64 | - [Positional information](#positional-information) 65 | - [File](#file) 66 | - [Preorder](#preorder) 67 | - [Postorder](#postorder) 68 | - [Enter](#enter) 69 | - [Exit](#exit) 70 | - [Collective](#collective) 71 | - [Authors](#authors) 72 | - [Additional resources](#additional-resources) 73 | - [Acknowledgements](#acknowledgements) 74 | - [License](#license) 75 | - [Notes](#notes) 76 | 77 | ## Introduction 78 | 79 | **unified** enables new exciting projects like [Gatsby][] to pull in Markdown, 80 | [MDX][] to embed [JSX][], and [Prettier][] to format it. It’s used in about 300k 81 | projects on GitHub and has about 10m downloads each month on npm: you’re probably 82 | using it. 83 | 84 | It powers [remarkjs][], [rehypejs][], [mdx-js][], [retextjs][], and [redotjs][]. It's 85 | used to build other projects like [prettier][], [gatsbyjs][], and more. 86 | 87 | Some notable users are [Node.js][], [ZEIT][], [Netlify][], [GitHub][], [Mozilla][], 88 | [WordPress][], [Adobe][], [Facebook][], [Google][]. 89 | 90 | ## How does it work? 91 | 92 | unified uses [abstract syntax trees][asts], or ASTs, that plugins can operate on. It 93 | can even process between different formats. This means you can parse a markdown document, 94 | transform it to HTML, and then transpile back to markdown. 95 | 96 | unified leverages a syntax tree specification (called [unist][] or UST) so that utilities 97 | can be shared amongst different formats. In practice, you can use `unist-util-visit` to visit 98 | nodes **using the same library with the same API** on any supported AST. 99 | 100 | ```js 101 | visit(markdownAST, 'images', transformImages) 102 | visit(htmlAST, 'img', transformImgs) 103 | ``` 104 | 105 | ## Supported syntaxes 106 | 107 | unified supports a few different syntaxes. Each have their own formal specification and are 108 | compatible with all `unist` utility libraries. 109 | 110 | - **[mdast][]**/**[remarkjs][]**: Markdown 111 | - **[hast][]**/**[rehypejs][]**: HTML 112 | - **[nlcst][]**/**[retextjs][]**: Natural language 113 | - **[mdxast][]**/**[mdx-js][]**: MDX 114 | 115 | Each syntax has its own GitHub organization and subset of plugins and libraries. 116 | 117 | ## Abstract syntax trees 118 | 119 | An abstract syntax tree, or AST, is a representation of input. It's an abstraction that 120 | enables developers to analyze, transform and generate code. 121 | 122 | They're the integral data structure in the unified ecosystem. Most plugins operate solely 123 | on the AST, receiving it as an argument and then returning a new AST afterwards. 124 | 125 | Your most basic plugin looks like the following (where the tree is an AST): 126 | 127 | ```js 128 | module.exports = options => tree => { 129 | return tree 130 | } 131 | ``` 132 | 133 | It accepts the AST as an argument, and then returns it. You can make it do 134 | something slightly more interesting by counting the heading nodes. 135 | 136 | ```js 137 | const visit = require('unist-util-visit') 138 | 139 | module.exports = options => tree => { 140 | let headingsCount = 0 141 | 142 | visit(tree, 'heading', node => { 143 | headingsCount++ 144 | }) 145 | } 146 | ``` 147 | 148 | Or, turn all `h1`s in a document into `h2`s: 149 | 150 | ```js 151 | const visit = require('unist-util-visit') 152 | 153 | module.exports = options => tree => { 154 | visit(tree, 'heading', node => { 155 | if (node.depth === 1) { 156 | node.depth = 2 157 | } 158 | }) 159 | } 160 | ``` 161 | 162 | If you ran the plugin above with `# Hello, world!` and compiled it 163 | back to markdown, the output would be `## Hello, world!`. 164 | 165 | unified uses ASTs because plugins are much easier to write when operating 166 | on objects rather than the strings themselves. You could achieve the same 167 | result with a string replacement: 168 | 169 | ```js 170 | markdown.replace(/^#\s+/g, '## ') 171 | ``` 172 | 173 | But this would be brittle and doesn't handle the thousands of edge cases 174 | with complex grammars which make up the syntax of markdown, HTML, and 175 | MDX. 176 | 177 | ### Constructing an AST 178 | 179 | In order to form an AST, unified takes an input string and passes that 180 | to a tokenizer. A tokenizer breaks up the input into tokens based on the 181 | syntax. In unified the tokenizer and lexer are coupled. When syntax is 182 | found the string is "eaten" and it's given metadata like node type (this 183 | is the "lexer"). 184 | 185 | Then, the parser turns this information into an AST. All together the 186 | pipeline looks like: 187 | 188 | [INPUT] => [TOKENIZER/LEXER] => [PARSER] => [AST] 189 | 190 | #### Parse example 191 | 192 | Consider this markdown input: 193 | 194 | ```md 195 | # Hello, **world**! 196 | ``` 197 | 198 | The tokenizer will match the "#" and create a heading node. Then 199 | it will begin searching for inline syntax where it will encounter 200 | "\*\*" and create a strong node. 201 | 202 | It's important to note that the parser first looks for block-level 203 | syntax which includes headings, code blocks, lists, paragraphs, 204 | and block quotes. 205 | 206 | Once a block has been opened, inline tokenization begins which searches 207 | for syntax including bold, code, emphasis, and links. 208 | 209 | The markdown will result in the following AST: 210 | 211 | ```json 212 | { 213 | "type": "heading", 214 | "depth": 1, 215 | "children": [ 216 | { 217 | "type": "text", 218 | "value": "Hello, ", 219 | "position": {} 220 | }, 221 | { 222 | "type": "strong", 223 | "children": [ 224 | { 225 | "type": "text", 226 | "value": "world", 227 | "position": {} 228 | } 229 | ], 230 | "position": {} 231 | }, 232 | { 233 | "type": "text", 234 | "value": "!", 235 | "position": {} 236 | } 237 | ], 238 | "position": {} 239 | } 240 | ``` 241 | 242 | A compiler turns an AST into output (typically a string). It provides 243 | functions that handle each node type and compiles them to the desired 244 | end result. 245 | 246 | For example, a compiler for markdown would encounter a `link` node and 247 | transform it into `[]()` markdown syntax. 248 | 249 | [AST] => [COMPILER] => [OUTPUT] 250 | 251 | It would turn the AST example above _back_ into the source markdown when 252 | compiling to markdown. It could also be compiled to HTML and would result 253 | in: 254 | 255 | ```html 256 |

257 | Hello, world! 258 |

259 | ``` 260 | 261 | ## unist 262 | 263 | unist is a specification for syntax trees which ensures that libraries that work with 264 | unified are as interoperable as possible. **All ASTs in unified conform to this spec**. 265 | It's the bread and butter of the ecosystem. 266 | 267 | ### Motivation 268 | 269 | A standard AST allows developers to use the same visitor function on all formats, whether 270 | it's markdown, HTML, natural language, or MDX. Using the same library ensures that the core 271 | functionality is as solid as possible while cutting down on cognitive overhead when trying 272 | to perform common tasks. 273 | 274 | ### Visitors 275 | 276 | When working with ASTs it's common to need to [traverse the tree]((#tree-traversal)). 277 | This is typically referred to as "visiting". A handler for a particular type of node 278 | is called a "visitor". 279 | 280 | unified comes with visitor utilities so you don't have to reinvent the wheel every 281 | time you want to operate on particular nodes. 282 | 283 | #### unist-util-visit 284 | 285 | unist-util-visit is a library that improves the DX of tree traversal 286 | for unist trees. It's a function that takes a tree, a node type, and 287 | a callback which it invokes with any matching nodes that are found. 288 | 289 | ```js 290 | visit(tree, 'image', node => { 291 | console.log(node) 292 | }) 293 | ``` 294 | 295 | **Note**: This performs a depth-first tree traversal in preorder (NLR). 296 | 297 | ##### Visit nodes based on context 298 | 299 | Something that's useful with unist utilities is that they can be used 300 | on subtrees. A subtree would be any node in the tree that may or may 301 | not have children. 302 | 303 | For example if you only wanted to visit images within heading nodes 304 | you could first visit headings, and then visit images contained within 305 | each heading node you encounter. 306 | 307 | ```js 308 | visit(tree, 'heading', headingNode => { 309 | visit(headingNode, 'image', node => { 310 | console.log(node) 311 | }) 312 | }) 313 | ``` 314 | 315 | ### unist-util-remove 316 | 317 | ### Advanced operations 318 | 319 | Once you're familiar with some of the primary unist utilities, you can 320 | combine them together to address more complex needs. 321 | 322 | #### Optimizing traversal 323 | 324 | When you care about multiple node types and are operating on large documents 325 | it might be preferable to walk all nodes and add a check for each node type 326 | with unist-util-is. 327 | 328 | #### Removing nodes based on parent context 329 | 330 | In some cases you might want to remove nodes based on their parent context. Consider 331 | a scenario where you want to remove all images contained within a heading. 332 | 333 | You can achieve this by combining unist-util-visit with unist-util-remove. The idea 334 | is that you first visit the parent, which would be heading nodes, and then remove 335 | images from the subtree. 336 | 337 | ```js 338 | visit(tree, 'heading', headingNode => { 339 | remove(headingNode, 'image') 340 | }) 341 | ``` 342 | 343 | [Watch this lesson on egghead →](https://egghead.io/lessons/javascript-remove-markdown-nodes-from-a-document-with-unist-util-remove) 344 | 345 | ### unist resources 346 | 347 | - [Read more about unist →](https://github.com/syntax-tree/unist) 348 | - [See the list of unist utilities →](https://github.com/syntax-tree/unist/blob/master/readme.md#utilities) 349 | 350 | ## unified 351 | 352 | unified is the interface for working with syntax trees and can be used in the same way 353 | for any of the supported syntaxes. 354 | 355 | For unified to work it requires two key pieces: a parser and a compiler. 356 | 357 | ### Parser 358 | 359 | A parser takes a string and tokenizes it based on syntax. A markdown parser would 360 | turn `# Hello, world!` into a `heading` node. 361 | 362 | unified has a parser for each of its supported syntax trees. 363 | 364 | ### Compiler 365 | 366 | A compiler turns an AST into its "output". This is typically a string. In some cases 367 | folks want to parse a markdown document, transform it, and then write back out markdown 368 | (like Prettier). In other cases folks might want to turn markdown into HTML. 369 | 370 | unified already supports compilers for most common outputs including markdown, HTML, 371 | text, and MDX. It even offers compilers for less common use cases including compiling 372 | markdown to CLI manual pages. 373 | 374 | ### Transpiler 375 | 376 | unified also offers transpilers. This is how one syntax tree is converted to another format. 377 | The most common transpiler is `mdast-util-to-hast` which converts the markdown AST (mdast) 378 | to the HTML AST (hast). 379 | 380 | ### Usage 381 | 382 | unified should be invoked: 383 | 384 | ```js 385 | unified() 386 | ``` 387 | 388 | Passed plugins: 389 | 390 | ```js 391 | .use(remarkParse) 392 | ``` 393 | 394 | And then given a string to operate on: 395 | 396 | ```js 397 | .process('# Hello, world!', (err, file) => { 398 | console.log(String(file)) 399 | }) 400 | ``` 401 | 402 | A more real-world example might want to turn a markdown document into an HTML string which 403 | would look something like: 404 | 405 | ```js 406 | var unified = require('unified') 407 | var markdown = require('remark-parse') 408 | var remark2rehype = require('remark-rehype') 409 | var doc = require('rehype-document') 410 | var format = require('rehype-format') 411 | var html = require('rehype-stringify') 412 | var report = require('vfile-reporter') 413 | 414 | unified() 415 | .use(markdown) 416 | .use(remark2rehype) 417 | .use(doc, {title: '👋🌍'}) 418 | .use(format) 419 | .use(html) 420 | .process('# Hello world!', function(err, file) { 421 | console.error(report(err || file)) 422 | console.log(String(file)) 423 | }) 424 | ``` 425 | 426 | **The code is doing the following** 427 | 428 | - Receives a markdown string (`process()`) 429 | - Parses the markdown (`.use(markdown)`) 430 | - Converts the mdast to hast (`.use(remark2rehype)`) 431 | - Wraps the hast in a document (`.use(doc)`) 432 | - Formats the hast (`.use(format)`) 433 | - Converts the hast to HTML (`.use(html)`) 434 | 435 | It'll result in an HTML string: 436 | 437 | ```html 438 | 439 | 440 | 441 | 442 | 👋🌍 443 | 444 | 445 | 446 |

Hello world!

447 | 448 | 449 | ``` 450 | 451 | ## remark 452 | 453 | remark is a plugin-based markdown processor. It has the ability to parse 454 | markdown, transform it with plugins, and then write back to markdown or 455 | transpile it to another format like HTML. 456 | 457 | It's highly configurable. Even plugins can customize the parser and compiler 458 | if needed. 459 | 460 | You can use the remark library directly in your scripts: 461 | 462 | ```js 463 | remark() 464 | .processSync('# Hello, world!') 465 | ``` 466 | 467 | Though, it's really a shortcut for: 468 | 469 | ```js 470 | unified() 471 | .use(remarkParse) 472 | .use(remarkStringify) 473 | .processSync('# Hello, world!') 474 | ``` 475 | 476 | ### remark CLI 477 | 478 | remark offers a CLI that which can be used to automate tasks. 479 | 480 | #### Inspect 481 | 482 | A useful option with the remark CLI is inspecting the AST of a 483 | document. This can be useful when you're trying to remember the 484 | name of a node type or you want an overview of the overall 485 | structure. 486 | 487 | ```sh 488 | ❯ remark doc.md --inspect 489 | root[13] (1:1-67:1, 0-2740) 490 | ├─ paragraph[1] (1:1-1:64, 0-63) 491 | │ └─ text: "import TableOfContents from '../src/components/TableOfContents'" (1:1-1:64, 0-63) 492 | ├─ heading[1] (3:1-3:15, 65-79) [depth=1] 493 | │ └─ text: "Fecunda illa" (3:3-3:15, 67-79) 494 | ├─ html: "" (5:1-5:46, 81-126) 495 | ├─ heading[1] (7:1-7:18, 128-145) [depth=2] 496 | │ └─ text: "Sorore extulit" (7:4-7:18, 131-145) 497 | ├─ paragraph[1] (9:1-12:75, 147-454) 498 | │ └─ text: "Lorem markdownum sorore extulit, non suo putant tritumque amplexa silvis: in,\nlascivaque femineam ara etiam! Oppida clipeus formidine, germanae in filia\netiamnunc demisso visa misce, praedaeque protinus communis paverunt dedit, suo.\nSertaque Hyperborea eatque, sed valles novercam tellure exhortantur coegi." (9:1-12:75, 147-454) 499 | ├─ list[3] (14:1-16:58, 456-573) [ordered=true][start=1][spread=false] 500 | │ ├─ listItem[1] (14:1-14:22, 456-477) [spread=false] 501 | │ │ └─ paragraph[1] (14:4-14:22, 459-477) 502 | │ │ └─ text: "Cunctosque plusque" (14:4-14:22, 459-477) 503 | │ ├─ listItem[1] (15:1-15:38, 478-515) [spread=false] 504 | │ │ └─ paragraph[1] (15:4-15:38, 481-515) 505 | │ │ └─ text: "Cum ego vacuas fata nolet At dedit" (15:4-15:38, 481-515) 506 | │ └─ listItem[1] (16:1-16:58, 516-573) [spread=false] 507 | │ └─ paragraph[1] (16:4-16:58, 519-573) 508 | │ └─ text: "Nec legerat ostendisse ponat sulcis vincirem cinctaque" (16:4-16:58, 519-573) 509 | ``` 510 | 511 | #### Use a plugin 512 | 513 | You can use plugins with the CLI: 514 | 515 | ```sh 516 | remark doc.md --use toc 517 | ``` 518 | 519 | This will output a markdown string with a table of contents added. 520 | If you'd like, you can overwrite the document with the generated table 521 | of contents: 522 | 523 | ```sh 524 | remark doc.md -o --use toc 525 | ``` 526 | 527 | #### Lint 528 | 529 | You can use a lint preset to ensure your markdown style guide is adhered 530 | to: 531 | 532 | ```sh 533 | ❯ remark doc.md --use preset-lint-markdown-style-guide 534 | 535 | 15:1-15:38 warning Marker should be `1`, was `2` ordered-list-marker-value remark-lint 536 | 16:1-16:58 warning Marker should be `1`, was `3` ordered-list-marker-value remark-lint 537 | 34:1-60:6 warning Code blocks should be fenced code-block-style remark-lint 538 | 539 | ⚠ 4 warnings 540 | ``` 541 | 542 | If you want to exit with a failure code (`1`) when the lint doesn't pass you can use the `--frail` option: 543 | 544 | ```sh 545 | ❯ remark doc.md --frail --use preset-lint-markdown-style-guide || echo '!!!failed' 546 | 547 | 548 | 15:1-15:38 warning Marker should be `1`, was `2` ordered-list-marker-value remark-lint 549 | 16:1-16:58 warning Marker should be `1`, was `3` ordered-list-marker-value remark-lint 550 | 34:1-60:6 warning Code blocks should be fenced code-block-style remark-lint 551 | 552 | ⚠ 4 warnings 553 | !!!failed 554 | ``` 555 | 556 | [Watch a video introduction to the CLI →](https://egghead.io/lessons/javascript-introduction-to-the-remark-cli) 557 | 558 | ### remark guides 559 | 560 | #### Writing a plugin to modify headings 561 | 562 | `unist-util-visit` is useful for visiting nodes in an AST based on a particular 563 | type. To visit all headings you can use it like so: 564 | 565 | ```js 566 | module.exports = () => tree => { 567 | visit(tree, 'heading', node => { 568 | console.log(node) 569 | }) 570 | } 571 | ``` 572 | 573 | The above will log all heading nodes. Heading nodes also have a `depth` field which 574 | indicates whether it's `h1`-`h6`. You can use that to narrow down what heading 575 | nodes you want to operate on. 576 | 577 | Below is a plugin that prefixes "BREAKING" to all `h1`s in a markdown document. 578 | 579 | ```js 580 | const visit = require('unist-util-visit') 581 | 582 | module.exports = () => tree => { 583 | visit(tree, 'heading', node => { 584 | if (node.depth !== 1) { 585 | return 586 | } 587 | 588 | visit(node, 'text', textNode => { 589 | textNode.value = 'BREAKING ' + textNode.value 590 | }) 591 | }) 592 | } 593 | ``` 594 | 595 | [Watch the lesson on egghead →](https://egghead.io/lessons/javascript-create-a-remark-plugin-to-modify-markdown-headings) 596 | 597 | ## rehype 598 | 599 | rehype is an HTML processor in the same way that remark is for 600 | markdown. 601 | 602 | ```js 603 | rehype() 604 | .processSync('Hi

Hello world!') 605 | ``` 606 | 607 | ## retext 608 | 609 | ## MDX 610 | 611 | MDX is a syntax and language for embedding JSX in markdown. It allows you 612 | to embed components in your documents for writing immersive and interactive 613 | content. 614 | 615 | An example MDX document looks like: 616 | 617 | ```md 618 | import Chart from '../components/snowfall-chart' 619 | 620 | # Last year's snowfall 621 | 622 | In the winter of2018, the snowfall was above average. It was followed by 623 | a warm spring which caused flood conditions in many of the nearby rivers. 624 | 625 | 626 | ``` 627 | 628 | The MDX core library extends the remark parser with the [remark-mdx][] 629 | plugin in order to define its own JSX-enabled syntax. 630 | 631 | ### MDX transpilation pipeline 632 | 633 | MDX uses [remark][remarkjs] and [rehype][rehypejs] internally. 634 | The flow of MDX consists of the following six steps: 635 | 636 | 1. **Parse**: MDX text => MDAST 637 | 2. **Transpile**: MDAST => MDXAST (remark-mdx) 638 | 3. **Transform**: remark plugins applied to AST 639 | 4. **Transpile**: MDXAST => MDXHAST 640 | 5. **Transform**: rehype plugins applied to AST 641 | 6. **Generate**: MDXHAST => JSX text 642 | 643 | The final result is JSX that can be used in React/Preact/Vue/etc. 644 | 645 | MDX allows you to hook into this flow at step 3 and 5, where you can use remark 646 | and rehype plugins (respectively) to benefit from their ecosystems. 647 | 648 | ## Tree traversal 649 | 650 | Tree traversal is a common task when working with a [_tree_][term-tree] to 651 | search it. 652 | Tree traversal is typically either _breadth-first_ or _depth-first_. 653 | 654 | In the following examples, we’ll work with this tree: 655 | 656 | ```ascii 657 | +---+ 658 | | A | 659 | +-+-+ 660 | | 661 | +-----+-----+ 662 | | | 663 | +-+-+ +-+-+ 664 | | B | | F | 665 | +-+-+ +-+-+ 666 | | | 667 | +-----+--+--+ | 668 | | | | | 669 | +-+-+ +-+-+ +-+-+ +-+-+ 670 | | C | | D | | E | | G | 671 | +---+ +---+ +---+ +---+ 672 | ``` 673 | 674 | ### Breadth-first traversal 675 | 676 | **Breadth-first traversal** is visiting a node and all its 677 | [_siblings_][term-sibling] to broaden the search at that level, before 678 | traversing [_children_][term-child]. 679 | 680 | For the syntax tree defined in the diagram, a breadth-first traversal first 681 | searches the root of the tree (**A**), then its children (**B** and **F**), then 682 | their children (**C**, **D**, **E**, and **G**). 683 | 684 | ### Depth-first traversal 685 | 686 | Alternatively, and more commonly, **depth-first traversal** is used. 687 | The search is first deepened, by traversing [_children_][term-child], before 688 | traversing [_siblings_][term-sibling]. 689 | 690 | For the syntax tree defined in the diagram, a depth-first traversal first 691 | searches the root of the tree (**A**), then one of its children (**B** or 692 | **F**), then their children (**C**, **D**, and **E**, or **G**). 693 | 694 | For a given node _N_ with [_children_][term-child], a **depth-first traversal** 695 | performs three steps, simplified to only binary trees (every node has 696 | [_head_][term-head] and [_tail_][term-tail], but no other children): 697 | 698 | - **N**: visit _N_ itself 699 | - **L**: traverse [_head_][term-head] 700 | - **R**: traverse [_tail_][term-tail] 701 | 702 | These steps can be done in any order, but for non-binary trees, **L** and **R** 703 | occur together. 704 | If **L** is done before **R**, the traversal is called _left-to-right_ 705 | traversal, otherwise it is called _right-to-left_ traversal. 706 | In the case of non-binary trees, the other children between _head_ and _tail_ 707 | are processed in that order as well, so for _left-to-right_ traversal, first 708 | _head_ is traversed (**L**), then its _next sibling_ is traversed, etcetera, 709 | until finally _tail_ (**R**) is traversed. 710 | 711 | Because **L** and **R** occur together for non-binary trees, we can produce four 712 | types of orders: NLR, NRL, LRN, RLN. 713 | 714 | NLR and LRN (the two _left-to-right_ traversal options) are most commonly used 715 | and respectively named [_preorder_][term-preorder] and 716 | [_postorder_][term-postorder]. 717 | 718 | For the syntax tree defined in the diagram, _preorder_ and _postorder_ traversal 719 | thus first search the root of the tree (**A**), then its head (**B**), then its 720 | children from left-to-right (**C**, **D**, and then **E**). 721 | After all [_descendants_][term-descendant] of **B** are traversed, its next 722 | sibling (**F**) is traversed and then finally its only child (**G**). 723 | 724 | ## Glossary 725 | 726 | ### Tree 727 | 728 | A **tree** is a node and all of its [_descendants_][term-descendant] (if any). 729 | 730 | ### Child 731 | 732 | Node X is **child** of node Y, if Y’s `children` include X. 733 | 734 | ### Parent 735 | 736 | Node X is **parent** of node Y, if Y is a [_child_][term-child] of X. 737 | 738 | ### Index 739 | 740 | The **index** of a [_child_][term-child] is its number of preceding 741 | [_siblings_][term-sibling], or `0` if it has none. 742 | 743 | ### Sibling 744 | 745 | Node X is a **sibling** of node Y, if X and Y have the same 746 | [_parent_][term-parent] (if any). 747 | 748 | The **previous sibling** of a [_child_][term-child] is its **sibling** at its 749 | [_index_][term-index] minus 1. 750 | 751 | The **next sibling** of a [_child_][term-child] is its **sibling** at its 752 | [_index_][term-index] plus 1. 753 | 754 | ### Root 755 | 756 | The **root** of a node is itself, if without [_parent_][term-parent], or the 757 | **root** of its [_parent_][term-parent]. 758 | 759 | The **root** of a [_tree_][term-tree] is any node in that [_tree_][term-tree] 760 | without [_parent_][term-parent]. 761 | 762 | ### Descendant 763 | 764 | Node X is **descendant** of node Y, if X is a [_child_][term-child] of Y, or if 765 | X is a [_child_][term-child] of node Z that is a **descendant** of Y. 766 | 767 | An **inclusive descendant** is a node or one of its **descendants**. 768 | 769 | ### Ancestor 770 | 771 | Node X is an **ancestor** of node Y, if Y is a [_descendant_][term-descendant] 772 | of X. 773 | 774 | An **inclusive ancestor** is a node or one of its **ancestors**. 775 | 776 | ### Head 777 | 778 | The **head** of a node is its first [_child_][term-child] (if any). 779 | 780 | ### Tail 781 | 782 | The **tail** of a node is its last [_child_][term-child] (if any). 783 | 784 | ### Leaf 785 | 786 | A **leaf** is a node with no [_children_][term-child]. 787 | 788 | ### Branch 789 | 790 | A **branch** is a node with one or more [_children_][term-child]. 791 | 792 | ### Generated 793 | 794 | A node is **generated** if it does not have [_positional 795 | information_][term-positional-info]. 796 | 797 | ### Type 798 | 799 | The **type** of a node is the value of its `type` field. 800 | 801 | ### Positional information 802 | 803 | The **positional information** of a node is the value of its `position` field. 804 | 805 | ### File 806 | 807 | A **file** is a source document that represents the original file that was 808 | parsed to produce the syntax tree. 809 | [_Positional information_][term-positional-info] represents the place of a node 810 | in this file. 811 | Files are provided by the host environment and not defined by unist. 812 | 813 | For example, see projects such as [**vfile**][vfile]. 814 | 815 | ### Preorder 816 | 817 | In **preorder** (**NLR**) is [depth-first][traversal-depth] [tree 818 | traversal][traversal] that performs the following steps for each node _N_: 819 | 820 | 1. **N**: visit _N_ itself 821 | 2. **L**: traverse [_head_][term-head] (then its _next sibling_, recursively 822 | moving forward until reaching _tail_) 823 | 3. **R**: traverse [_tail_][term-tail] 824 | 825 | ### Postorder 826 | 827 | In **postorder** (**LRN**) is [depth-first][traversal-depth] [tree 828 | traversal][traversal] that performs the following steps for each node _N_: 829 | 830 | 1. **L**: traverse [_head_][term-head] (then its _next sibling_, recursively 831 | moving forward until reaching _tail_) 832 | 2. **R**: traverse [_tail_][term-tail] 833 | 3. **N**: visit _N_ itself 834 | 835 | ### Enter 836 | 837 | **Enter** is a step right before other steps performed on a given node _N_ when 838 | [**traversing**][traversal] a tree. 839 | 840 | For example, when performing _preorder_ traversal, **enter** is the first step 841 | taken, right before visiting _N_ itself. 842 | 843 | ### Exit 844 | 845 | **Exit** is a step right after other steps performed on a given node _N_ when 846 | [**traversing**][traversal] a tree. 847 | 848 | For example, when performing _preorder_ traversal, **exit** is the last step 849 | taken, right after traversing the [_tail_][term-tail] of _N_. 850 | 851 | ## Collective 852 | 853 | unified was originally created by [Titus Wormer][wooorm]. It's now governed by a collective 854 | which handles the many GitHub organizations, repositories, and packages that are part of the 855 | greater unified ecosystem. 856 | 857 | The collective and its governance won't be addressed in this handbook. If you're interested, 858 | you can [read more about the collective](https://github.com/unifiedjs/collective) 859 | on GitHub. 860 | 861 | ## Authors 862 | 863 | - [John Otander][johno] 864 | - [Titus Wormer][wooorm] 865 | 866 | ## Additional resources 867 | 868 | - [AST Explorer](https://astexplorer.net) 869 | - [MDX Playground](https://mdxjs.com/playground) 870 | - [Gatsby remark plugin tutorial](https://www.gatsbyjs.org/docs/remark-plugin-tutorial/) 871 | - [How to build a compiler](https://www.youtube.com/watch?v=ZYFOWesCm_0) 872 | 873 | ## Acknowledgements 874 | 875 | This handbook is inspired by the [babel-handbook][] written by 876 | [James Kyle][jamiebuilds]. 877 | 878 | ## License 879 | 880 | [MIT](https://github.com/unifiedjs/handbook/license) 881 | 882 | ## Notes 883 | 884 | - unist nodes are accompanied by positional information. To keep AST printouts as 885 | simple as possible, it will be an empty object (`"position": {}`) when it isn't 886 | relevant for the example. 887 | 888 | [Adobe]: https://www.adobe.com 889 | 890 | [Facebook]: https://www.facebook.com 891 | 892 | [GitHub]: https://github.com 893 | 894 | [Google]: https://www.google.com 895 | 896 | [Mozilla]: https://www.mozilla.org 897 | 898 | [Netlify]: https://www.netlify.com 899 | 900 | [Node.js]: https://nodejs.org 901 | 902 | [WordPress]: https://wordpress.com 903 | 904 | [ZEIT]: https://zeit.co 905 | 906 | [asts]: https://github.com/syntax-tree 907 | 908 | [babel-handbook]: https://github.com/jamiebuilds/babel-handbook 909 | 910 | [gatsby]: https://gatsbyjs.org 911 | 912 | [gatsbyjs]: https://gatsbyjs.org 913 | 914 | [hast]: https://github.com/syntax-tree/hast 915 | 916 | [jamiebuilds]: https://github.com/jamiebuilds 917 | 918 | [johno]: https://johno.com 919 | 920 | [jsx]: https://reactjs.org/docs/jsx-in-depth.html 921 | 922 | [mdast]: https://github.com/syntax-tree/mdast 923 | 924 | [mdx-js]: https://github.com/mdx-js 925 | 926 | [mdx]: https://mdxjs.com 927 | 928 | [mdxast]: https://github.com/syntax-tree/mdxast 929 | 930 | [nlcst]: https://github.com/syntax-tree/nlcst 931 | 932 | [prettier]: https://prettier.io 933 | 934 | [redotjs]: https://github.com/redotjs 935 | 936 | [rehypejs]: https://github.com/rehypejs 937 | 938 | [remark-mdx]: https://github.com/mdx-js/mdx/tree/master/packages/remark-mdx 939 | 940 | [remarkjs]: https://github.com/remarkjs 941 | 942 | [retextjs]: https://github.com/retextjs 943 | 944 | [term-tree]: #tree 945 | 946 | [term-preorder]: #preorder 947 | 948 | [term-postorder]: #postorder 949 | 950 | [term-child]: #child 951 | 952 | [term-parent]: #parent-1 953 | 954 | [term-index]: #index 955 | 956 | [term-sibling]: #sibling 957 | 958 | [term-descendant]: #descendant 959 | 960 | [term-head]: #head 961 | 962 | [term-tail]: #tail 963 | 964 | [term-generated]: #generated 965 | 966 | [term-type]: #type 967 | 968 | [term-positional-info]: #positional-information 969 | 970 | [term-file]: #file 971 | 972 | [unist]: https://github.com/syntax-tree/unist 973 | 974 | [vfile]: https://github.com/vfile/vfile 975 | 976 | [wooorm]: https://github.com/wooorm 977 | --------------------------------------------------------------------------------