The response has been limited to 50k tokens of the smallest files in the repo. You can remove this limitation by removing the max tokens filter.
├── .github
    └── ISSUE_TEMPLATE
    │   └── what-the-fuck-is-_____-.md
├── .gitignore
├── LICENSE-code-snippets.md
├── LICENSE-posts.md
├── LICENSE-website.md
├── README.md
├── components
    ├── entry
    │   ├── text.js
    │   └── text.module.css
    ├── error
    │   ├── error.module.css
    │   └── index.js
    ├── footer
    │   ├── footer.module.css
    │   └── index.js
    ├── header
    │   ├── header.module.css
    │   └── index.js
    ├── icon.js
    ├── icons
    │   ├── github.js
    │   ├── logo.js
    │   ├── moon.js
    │   ├── sun.js
    │   └── twitter.js
    ├── link
    │   ├── index.js
    │   └── link.module.css
    ├── page
    │   ├── index.js
    │   └── page.module.css
    ├── post
    │   ├── index.js
    │   ├── navigation.js
    │   ├── navigation.module.css
    │   └── post.module.css
    ├── posts-list
    │   ├── index.js
    │   └── posts-list.module.css
    ├── preview
    │   ├── index.js
    │   └── preview.module.css
    └── seo.js
├── jsconfig.json
├── lib
    ├── get-markdown.js
    ├── get-posts.js
    ├── gtag.js
    ├── render-markdown.js
    ├── rss.js
    ├── theme.js
    └── use-mounted.js
├── package.json
├── pages
    ├── 404.js
    ├── [slug].js
    ├── _app.js
    ├── _document.js
    ├── _error.js
    └── index.js
├── posts
    ├── closure.md
    ├── composition.md
    ├── dynamic-dispatch.md
    └── memoization.md
├── public
    └── robots.txt
├── styles
    ├── global.css
    ├── inter.css
    ├── markdown.css
    ├── nprogress.css
    └── syntax.css
└── yarn.lock


/.github/ISSUE_TEMPLATE/what-the-fuck-is-_____-.md:
--------------------------------------------------------------------------------
 1 | ---
 2 | name: WTF is _____?
 3 | about: Request a definition
 4 | title: ''
 5 | labels: ''
 6 | assignees: ''
 7 | 
 8 | ---
 9 | 
10 | <!-- Don't be shy! And vote on other people's proposals. -->
11 | 


--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
 2 | 
 3 | # dependencies
 4 | /node_modules
 5 | /.pnp
 6 | .pnp.js
 7 | 
 8 | # testing
 9 | /coverage
10 | 
11 | # next.js
12 | /.next/
13 | /out/
14 | 
15 | # production
16 | /build
17 | 
18 | # misc
19 | .DS_Store
20 | *.pem
21 | 
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 | 
27 | # local env files
28 | .env.local
29 | .env.development.local
30 | .env.test.local
31 | .env.production.local
32 | 
33 | # vercel
34 | .vercel
35 | 
36 | # rss
37 | public/feed.xml
38 | 


--------------------------------------------------------------------------------
/LICENSE-code-snippets.md:
--------------------------------------------------------------------------------
 1 | Code snippets included in posts on the site are licensed under more permissive terms than general post content.
 2 | Please see LICENSE-posts for the terms regarding general post content.
 3 | 
 4 | Code snippets may be used under the terms of the MIT License:
 5 | 
 6 | MIT License
 7 | 
 8 | Copyright (c) 2020 Dan Abramov and the contributors.
 9 | 
10 | Permission is hereby granted, free of charge, to any person obtaining a copy
11 | of this software and associated documentation files (the "Software"), to deal
12 | in the Software without restriction, including without limitation the rights
13 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14 | copies of the Software, and to permit persons to whom the Software is
15 | furnished to do so, subject to the following conditions:
16 | 
17 | The above copyright notice and this permission notice shall be included in all
18 | copies or substantial portions of the Software.
19 | 
20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26 | SOFTWARE.
27 | 


--------------------------------------------------------------------------------
/LICENSE-posts.md:
--------------------------------------------------------------------------------
1 | Copyright (c) Dan Abramov and the contributors.
2 | All rights reserved.
3 | 
4 | (Please see LICENSE-code-snippets for the terms regarding use of code snippets found in posts.)
5 | 


--------------------------------------------------------------------------------
/LICENSE-website.md:
--------------------------------------------------------------------------------
 1 | The MIT License (MIT)
 2 | 
 3 | Copyright (c) 2020 Dan Abramov
 4 | 
 5 | Permission is hereby granted, free of charge, to any person obtaining a copy
 6 | of this software and associated documentation files (the "Software"), to deal
 7 | in the Software without restriction, including without limitation the rights
 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 | 
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 | 
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 | 


--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
 1 | # [whatthefuck.is](https://whatthefuck.is/)
 2 | 
 3 | An opinionated glossary of computer science terms for front-end developers. Written by Dan Abramov.
 4 | 
 5 | ## Contributing
 6 | 
 7 | Typo fixes and website improvements are welcome as pull requests.
 8 | 
 9 | We **do not** accept definitions, but you’re welcome to vote on (and request) definitions in the [issue tracker](https://github.com/gaearon/whatthefuck.is/issues). You can sort popular issues by [sorting by reactions](https://github.com/gaearon/whatthefuck.is/issues?page=1&q=is%3Aissue+is%3Aopen+sort%3Areactions-%2B1-desc).
10 | 


--------------------------------------------------------------------------------
/components/entry/text.js:
--------------------------------------------------------------------------------
 1 | import { memo, useState } from 'react'
 2 | import cn from 'classnames'
 3 | 
 4 | import Link from '@/components/link'
 5 | import styles from './text.module.css'
 6 | 
 7 | const categories = {
 8 |   'computer science': '🎓',
 9 |   language: '🔭',
10 |   tools: '🍭'
11 | }
12 | 
13 | const request = ['🙋🏻', '🙋🏼', '🙋🏽', '🙋🏾', '🙋🏿']
14 | 
15 | const TextEntry = ({ title, type, comment, href, category, as }) => {
16 |   const [diceRoll] = useState(Math.random())
17 |   const emoji = category
18 |     ? categories[category]
19 |     : request[Math.round(diceRoll * (request.length - 1))]
20 |   return (
21 |     <li className={cn(styles.item, !category && styles.request)}>
22 |       <Link
23 |         href={href}
24 |         as={as}
25 |         external={!as}
26 |         title={`${title}`}
27 |         className={styles.link}
28 |       >
29 |         {emoji && (
30 |           <span
31 |             role="img"
32 |             aria-label={category}
33 |             title={category}
34 |             className={styles.category}
35 |           >
36 |             {emoji}
37 |           </span>
38 |         )}
39 |         <span className={cn(styles.title, 'clamp', !category && styles.new)}>
40 |           {title}
41 |         </span>
42 |       </Link>
43 |     </li>
44 |   )
45 | }
46 | 
47 | export default memo(TextEntry)
48 | 


--------------------------------------------------------------------------------
/components/entry/text.module.css:
--------------------------------------------------------------------------------
 1 | .item {
 2 |   list-style-type: none;
 3 |   position: relative;
 4 | }
 5 | 
 6 | .request {
 7 |   margin-top: var(--big-gap);
 8 | }
 9 | 
10 | .link {
11 |   text-decoration: none;
12 |   color: var(--fg);
13 |   outline: none;
14 |   display: flex;
15 |   align-items: center;
16 |   padding-top: var(--gap);
17 |   display: flex;
18 |   align-items: flex-start;
19 |   padding-bottom: 3rem;
20 | }
21 | 
22 | .link:hover {
23 |   text-decoration: underline;
24 |   text-underline-position: under;
25 |   text-decoration-style: dotted;
26 | }
27 | 
28 | .link:hover .title,
29 | .link:focus .title {
30 |   color: var(--fg);
31 | }
32 | 
33 | .title,
34 | .description {
35 |   word-break: break-word;
36 |   margin: 0;
37 |   line-height: 1.3;
38 | }
39 | 
40 | .category {
41 |   position: absolute;
42 |   left: -6rem;
43 |   transform: scale(3);
44 | }
45 | 
46 | .title {
47 |   font-size: 1.5rem;
48 |   font-weight: bold;
49 |   color: var(--accent)
50 | }
51 | 
52 | .description {
53 |   color: var(--gray);
54 |   margin-top: var(--gap-half);
55 | }
56 | 
57 | .dot {
58 |   height: 10px;
59 |   width: 10px;
60 |   border-radius: 50%;
61 |   background: var(--color);
62 |   margin-left: var(--gap-double);
63 | }
64 | 
65 | @media (max-width: 600px) {
66 |   .title,
67 |   .description {
68 |     -webkit-line-clamp: 2;
69 |   }
70 | }
71 | 


--------------------------------------------------------------------------------
/components/error/error.module.css:
--------------------------------------------------------------------------------
 1 | .section {
 2 |   width: 100%;
 3 |   display: flex;
 4 |   justify-content: center;
 5 |   align-items: center;
 6 |   color: var(--light-gray);
 7 | }
 8 | 
 9 | .section > span {
10 |   font-size: 16rem;
11 |   margin: 0;
12 |   line-height: normal;
13 |   font-weight: bold;
14 | }
15 | 


--------------------------------------------------------------------------------
/components/error/index.js:
--------------------------------------------------------------------------------
 1 | import Head from 'next/head'
 2 | 
 3 | import Page from '@/components/page'
 4 | import Link from '@/components/link'
 5 | import styles from './error.module.css'
 6 | 
 7 | const Error = ({ missingTerm, status }) => {
 8 |   return (
 9 |     <Page title={status || 'Error'} showSlug={false}>
10 |       <Head>
11 |         <title>{[status]}</title>
12 |       </Head>
13 | 
14 |       {status === 404 ? (
15 |         <>
16 |           <h1>404</h1>
17 |           <p>
18 |             The term <strong>{missingTerm}</strong> doesn’t exist yet. You can{' '}
19 |             <Link
20 |               underline
21 |               href={`https://github.com/gaearon/whatthefuck.is/issues/new?template=what-the-fuck-is-_____-.md&title=WTF is ${missingTerm}?`}
22 |             >
23 |               suggest this term
24 |             </Link>
25 |             .
26 |           </p>
27 | 
28 |           <Link underline href="/">
29 |             Go Home
30 |           </Link>
31 |         </>
32 |       ) : (
33 |         <section className={styles.section}>
34 |           <span>{status || 'пиздец'}</span>
35 |           <p>For fuck’s sake.</p>
36 |         </section>
37 |       )}
38 |     </Page>
39 |   )
40 | }
41 | 
42 | export default Error
43 | 


--------------------------------------------------------------------------------
/components/footer/footer.module.css:
--------------------------------------------------------------------------------
 1 | .footer {
 2 |   display: flex;
 3 |   justify-content: space-between;
 4 |   align-items: center;
 5 | 
 6 |   padding: 0 var(--gap);
 7 |   margin: 0 auto;
 8 |   margin-top: var(--big-gap);
 9 |   max-width: var(--main-content);
10 | }
11 | 
12 | .footer a + a {
13 |   margin-left: var(--gap);
14 | }
15 | 


--------------------------------------------------------------------------------
/components/footer/index.js:
--------------------------------------------------------------------------------
 1 | import Link from '@/components/link'
 2 | import styles from './footer.module.css'
 3 | 
 4 | const Footer = ({ slug }) => {
 5 |   return (
 6 |     <>
 7 |       <footer className={styles.footer}>
 8 |         <div>
 9 |           <Link href="https://justjavascript.com" external gray>
10 |             justjavascript
11 |           </Link>
12 |           <Link href="https://overreacted.io" external gray>
13 |             overreacted
14 |           </Link>
15 |         </div>
16 |         <Link href="/feed.xml" external gray>
17 |           rss
18 |         </Link>
19 |       </footer>
20 |       <footer className={styles.footer}>
21 |         <div />
22 |         <Link href={`https://whatthefork.is/${slug ?? ''}`} external gray>
23 |           curse-free mirror
24 |         </Link>
25 |         <div />
26 |       </footer>
27 |     </>
28 |   )
29 | }
30 | 
31 | export default Footer
32 | 


--------------------------------------------------------------------------------
/components/header/header.module.css:
--------------------------------------------------------------------------------
 1 | .home {
 2 |   outline: none;
 3 |   display: inline-flex;
 4 | }
 5 | 
 6 | .command {
 7 |   display: inline-flex;
 8 |   padding: 10px;
 9 |   margin: -10px;
10 |   border-radius: var(--radius);
11 | }
12 | 
13 | .command:hover,
14 | .command:focus {
15 |   outline: none;
16 |   background: var(--lighter-gray);
17 | }
18 | 
19 | .command {
20 |   width: 50px;
21 |   height: 50px;
22 |   cursor: pointer;
23 |   background: none;
24 | }
25 | 
26 | .home,
27 | .slug {
28 |   color: var(--fg);
29 |   text-decoration: none;
30 |   font-size: 1.5rem;
31 |   font-weight: bold;
32 | }
33 | 
34 | .slug {
35 |   color: var(--fg);
36 | }
37 | 
38 | .nav {
39 |   z-index: 10;
40 |   margin: var(--small-gap) auto var(--big-gap) auto;
41 |   position: sticky;
42 |   padding: var(--gap) 0;
43 |   top: 0;
44 |   background-color: var(--header-bg);
45 | }
46 | 
47 | .header {
48 |   height: 32px;
49 |   margin: 0 auto;
50 |   padding: 0 var(--gap);
51 |   max-width: var(--main-content);
52 |   display: flex;
53 |   justify-content: space-between;
54 |   align-items: center;
55 | }
56 | 
57 | .tagline {
58 |   font-family: Georgia, serif;
59 |   font-style: italic;
60 |   font-size: 24px;
61 | }
62 | 
63 | .hint {
64 |   width: 100%;
65 |   color: var(--gray);
66 |   display: flex;
67 |   justify-content: space-between;
68 |   align-items: center;
69 | }
70 | 
71 | .hint kbd {
72 |   color: var(--fg);
73 | }
74 | 
75 | @media (max-width: 960px) {
76 |   .nav {
77 |     margin: var(--gap-double) 0;
78 |     padding: var(--gap-double) 0;
79 |   }
80 | }
81 | 


--------------------------------------------------------------------------------
/components/header/index.js:
--------------------------------------------------------------------------------
 1 | import Link from 'next/link'
 2 | 
 3 | import styles from './header.module.css'
 4 | import LogoIcon from '@/components/icons/logo'
 5 | import useTheme from '@/lib/theme'
 6 | import Moon from '@/components/icons/moon'
 7 | import Sun from '@/components/icons/sun'
 8 | import useMounted from '@/lib/use-mounted'
 9 | 
10 | const Header = ({ slug, title }) => {
11 |   const isMounted = useMounted()
12 |   const { theme, toggleTheme } = useTheme()
13 | 
14 |   return (
15 |     <nav className={styles.nav}>
16 |       <div className={styles.header}>
17 |         <span>
18 |           <Link href="/">
19 |             <a
20 |               aria-label="Navigate Home"
21 |               className={slug ? styles.home : styles.slug}
22 |             >
23 |               whatthefuck.is
24 |             </a>
25 |           </Link>
26 |           <span className={styles.tagline}>
27 |             &nbsp;&nbsp;&nbsp;·&nbsp;&nbsp;{'  '}
28 |             {slug ? (
29 |               <b>
30 |                 <span style={{ color: 'var(--accent)' }}>{title}</span>
31 |               </b>
32 |             ) : (
33 |               <>Dan’s&nbsp;JavaScript&nbsp;Glossary</>
34 |             )}
35 |           </span>
36 |         </span>
37 |         <button
38 |           className={styles.command}
39 |           onClick={toggleTheme}
40 |           aria-label="Toggle Theme"
41 |         >
42 |           {isMounted &&
43 |             (theme === 'light' ? (
44 |               <Moon color="var(--fg)" size={30} key="icon-light" />
45 |             ) : (
46 |               <Sun color="var(--fg)" size={30} key="icon-dark" />
47 |             ))}
48 |         </button>
49 |       </div>
50 |     </nav>
51 |   )
52 | }
53 | 
54 | export default Header
55 | 


--------------------------------------------------------------------------------
/components/icon.js:
--------------------------------------------------------------------------------
 1 | const withIcon = icon => {
 2 |   const Icon = ({ size = 24, color = 'currentColor' }) => {
 3 |     return (
 4 |       <svg
 5 |         viewBox="0 0 24 24"
 6 |         width={size}
 7 |         height={size}
 8 |         fill={color}
 9 |         dangerouslySetInnerHTML={{ __html: icon }}
10 |       />
11 |     )
12 |   }
13 | 
14 |   return Icon
15 | }
16 | 
17 | export default withIcon
18 | 


--------------------------------------------------------------------------------
/components/icons/github.js:
--------------------------------------------------------------------------------
1 | import Icon from '@/components/icon'
2 | 
3 | const GitHub = `<path fill-rule="evenodd" clip-rule="evenodd" d="M12 3c-4.973 0-9 4.13-9 9.228 0 4.083 2.576 7.532 6.154 8.754.45.081.618-.196.618-.438 0-.22-.01-.946-.01-1.719-2.262.427-2.847-.565-3.027-1.084-.101-.265-.54-1.084-.923-1.303-.315-.173-.764-.6-.01-.612.708-.011 1.214.67 1.383.946.81 1.396 2.104 1.004 2.621.762.079-.6.315-1.004.574-1.235-2.003-.23-4.095-1.026-4.095-4.556 0-1.003.349-1.834.922-2.48-.09-.23-.404-1.176.09-2.445 0 0 .754-.242 2.475.946a8.159 8.159 0 012.25-.312c.765 0 1.53.104 2.25.312 1.722-1.2 2.475-.946 2.475-.946.495 1.269.18 2.215.09 2.445.574.646.923 1.465.923 2.48 0 3.541-2.104 4.326-4.106 4.556.326.289.607.842.607 1.707 0 1.235-.011 2.227-.011 2.538 0 .242.169.53.619.438a9.036 9.036 0 004.439-3.366A9.402 9.402 0 0021 12.228C21 7.129 16.973 3 12 3z"/>`
4 | 
5 | export default Icon(GitHub)
6 | 


--------------------------------------------------------------------------------
/components/icons/logo.js:
--------------------------------------------------------------------------------
 1 | const Logo = () => {
 2 |   return (
 3 |     <svg height="30" width="30" viewBox="0 0 43 50" fill="var(--fg)">
 4 |       <path d="M9.41558 50C4.56115 50 2.23885 47.3933 1.14535 45.2065C0.012987 42.9417 0 40.6797 0 40.5844V9.41558C0 9.32045 0.012987 7.05823 1.14535 4.79351C2.23885 2.60671 4.56115 0 9.41558 0H25C25.1747 0 29.3275 0.0241342 33.5182 2.11937C37.4824 4.10152 42.2078 8.32814 42.2078 17.2078C42.2078 26.0874 37.4824 30.3141 33.5182 32.2961C29.3275 34.3914 25.1747 34.4156 25 34.4156H18.8312V40.5844C18.8312 40.6797 18.8182 42.9417 17.6858 45.2065C16.5923 47.3933 14.27 50 9.41558 50ZM3.24675 40.5844C3.24686 40.5936 3.27175 42.2568 4.09307 43.8403C5.10974 45.8004 6.85076 46.7532 9.41558 46.7532C12.0175 46.7532 13.7728 45.7724 14.7817 43.7545C15.5626 42.1929 15.5844 40.6001 15.5844 40.5844V34.4825C14.1437 34.5769 11.9361 34.8047 9.76115 35.3532C5.43853 36.4434 3.24675 38.2035 3.24675 40.5844ZM17.2078 31.1688H25C25.0326 31.1687 28.649 31.1319 32.1601 29.3448C36.6728 27.0478 38.961 22.9643 38.961 17.2078C38.961 11.4104 36.6412 7.31093 32.0662 5.02348C28.5535 3.2671 25.0351 3.24675 25 3.24675H9.41558C6.81364 3.24675 5.05833 4.2276 4.04946 6.24545C3.26861 7.80714 3.24675 9.39989 3.24675 9.41558V34.6601C4.59318 33.6926 6.45996 32.8318 9.02186 32.1913C13.0654 31.1804 17.0406 31.1688 17.2078 31.1688Z" />
 5 |     </svg>
 6 |   )
 7 | }
 8 | 
 9 | export default Logo
10 | 


--------------------------------------------------------------------------------
/components/icons/moon.js:
--------------------------------------------------------------------------------
1 | import Icon from '@/components/icon'
2 | 
3 | const Moon = `<path d="M15.977 14.456c-3.839 0-6.294-2.393-6.294-6.217 0-.79.192-1.92.447-2.505a.9.9 0 00.078-.332A.401.401 0 009.79 5c-.078 0-.249.021-.405.078C6.76 6.122 5 8.93 5 11.888 5 16.035 8.179 19 12.337 19c3.058 0 5.705-1.842 6.585-4.142.064-.162.078-.332.078-.395a.437.437 0 00-.419-.438.98.98 0 00-.305.064 7.979 7.979 0 01-2.299.367z"/>`
4 | 
5 | export default Icon(Moon)
6 | 


--------------------------------------------------------------------------------
/components/icons/sun.js:
--------------------------------------------------------------------------------
1 | import Icon from '@/components/icon'
2 | 
3 | const Sun = `<path d="M12.727 3.722A.736.736 0 0011.996 3a.734.734 0 00-.723.722v1.742c0 .39.332.721.723.721a.736.736 0 00.731-.721V3.722zm3.376 3.16a.735.735 0 000 1.02.726.726 0 001.031 0l1.239-1.236a.73.73 0 000-1.028.73.73 0 00-1.015 0l-1.255 1.244zm-9.245 1.02a.724.724 0 001.022 0c.283-.265.283-.746.009-1.02l-1.24-1.244a.748.748 0 00-1.022 0 .743.743 0 00-.008 1.02l1.239 1.244zm5.138-.132c-2.32 0-4.24 1.916-4.24 4.23 0 2.314 1.92 4.239 4.24 4.239 2.311 0 4.232-1.925 4.232-4.239s-1.92-4.23-4.232-4.23zm8.272 4.952c.4 0 .732-.332.732-.722a.736.736 0 00-.732-.722h-1.737a.734.734 0 00-.724.722c0 .39.333.722.724.722h1.737zM3.723 11.278A.734.734 0 003 12c0 .39.333.722.723.722h1.738c.399 0 .732-.332.732-.722a.736.736 0 00-.732-.722H3.723zm13.403 4.828a.74.74 0 00-1.023 0 .735.735 0 000 1.02l1.255 1.244a.73.73 0 001.015-.008.72.72 0 000-1.02l-1.247-1.236zM5.619 17.334a.743.743 0 00-.008 1.02.75.75 0 001.03.008l1.24-1.236a.727.727 0 00.008-1.02.75.75 0 00-1.031 0l-1.24 1.228zm7.108 1.202a.736.736 0 00-.731-.721.734.734 0 00-.723.721v1.742c0 .39.332.722.723.722a.736.736 0 00.731-.722v-1.742z"/>`
4 | 
5 | export default Icon(Sun)
6 | 


--------------------------------------------------------------------------------
/components/icons/twitter.js:
--------------------------------------------------------------------------------
1 | import Icon from '@/components/icon'
2 | 
3 | const Twitter = `<path d="M8.66 20c6.793 0 10.508-6.156 10.508-11.495 0-.175 0-.349-.01-.522A7.943 7.943 0 0021 5.892a6.883 6.883 0 01-2.121.635c.77-.504 1.348-1.298 1.624-2.234a7.03 7.03 0 01-2.346.98c-1.144-1.331-2.963-1.657-4.437-.794-1.473.862-2.234 2.699-1.856 4.48-2.97-.164-5.736-1.698-7.611-4.222-.98 1.846-.48 4.208 1.143 5.393a3.428 3.428 0 01-1.676-.506v.052c0 1.923 1.24 3.58 2.963 3.96a3.38 3.38 0 01-1.668.07c.484 1.645 1.87 2.772 3.45 2.805-1.307 1.124-2.923 1.734-4.586 1.733-.294-.001-.587-.02-.879-.059C4.689 19.371 6.654 20 8.66 19.997"/>`
4 | 
5 | export default Icon(Twitter)
6 | 


--------------------------------------------------------------------------------
/components/link/index.js:
--------------------------------------------------------------------------------
 1 | import NextLink from 'next/link'
 2 | import cn from 'classnames'
 3 | 
 4 | import styles from './link.module.css'
 5 | 
 6 | const canPrefetch = href => {
 7 |   if (!href || !href.startsWith('/')) {
 8 |     return false
 9 |   }
10 | 
11 |   return true
12 | }
13 | 
14 | const Link = ({
15 |   external,
16 |   href,
17 |   as,
18 |   passHref,
19 |   children,
20 |   className,
21 | 
22 |   // Styling
23 |   underline,
24 |   gray,
25 |   ...props
26 | }) => {
27 |   const c = cn(className, styles.reset, {
28 |     [styles.gray]: gray,
29 |     [styles.underline]: underline
30 |   })
31 | 
32 |   if (external) {
33 |     return (
34 |       <a
35 |         href={href}
36 |         target="_blank"
37 |         rel="noopener noreferrer"
38 |         className={c}
39 |         {...props}
40 |       >
41 |         {children}
42 |       </a>
43 |     )
44 |   }
45 | 
46 |   return (
47 |     <>
48 |       <NextLink
49 |         href={href}
50 |         as={as}
51 |         prefetch={canPrefetch(href) ? undefined : false}
52 |         passHref={passHref}
53 |       >
54 |         {passHref ? (
55 |           children
56 |         ) : (
57 |           <a className={c} {...props}>
58 |             {children}
59 |           </a>
60 |         )}
61 |       </NextLink>
62 |     </>
63 |   )
64 | }
65 | 
66 | export default Link
67 | 


--------------------------------------------------------------------------------
/components/link/link.module.css:
--------------------------------------------------------------------------------
 1 | .reset {
 2 |   outline: none;
 3 |   color: inherit;
 4 |   text-decoration: none;
 5 | }
 6 | 
 7 | .gray {
 8 |   color: var(--gray);
 9 | }
10 | 
11 | .gray:hover, .gray:focus {
12 |   color: var(--fg);
13 | }
14 | 
15 | .underline {
16 |   composes: reset;
17 | 
18 |   background-image: linear-gradient(to right, var(--gray), var(--gray));
19 |   background-size: 100% 1px;
20 |   background-position: left bottom;
21 |   background-repeat: no-repeat;
22 |   text-shadow: 0.1em 0 var(--bg), -0.1em 0 var(--bg), 0.05em 0 var(--bg),
23 |     -0.05em 0 var(--bg);
24 | }
25 | 
26 | .underline:hover, .underline:focus {
27 |   color: var(--gray);
28 | }
29 | 


--------------------------------------------------------------------------------
/components/page/index.js:
--------------------------------------------------------------------------------
 1 | import SEO from '@/components/seo'
 2 | import Header from '@/components/header'
 3 | import Footer from '@/components/footer'
 4 | import styles from './page.module.css'
 5 | 
 6 | const Page = ({
 7 |   header = true,
 8 |   footer = true,
 9 |   slug,
10 |   title,
11 |   description,
12 |   image,
13 |   showSlug = true,
14 |   children
15 | }) => {
16 |   return (
17 |     <div className={styles.wrapper}>
18 |       <SEO
19 |         title={`${(title
20 |           ? `What the fuck is ${title}?`
21 |           : 'What the fuck is ...?') + ' ・ Dan’s JavaScript Glossary'}`}
22 |         description={description}
23 |         image={image}
24 |       />
25 | 
26 |       {showSlug && <Header slug={showSlug && slug} title={showSlug && title} />}
27 |       <main className={styles.main}>{children}</main>
28 |       <Footer slug={slug} />
29 |     </div>
30 |   )
31 | }
32 | 
33 | export default Page
34 | 


--------------------------------------------------------------------------------
/components/page/page.module.css:
--------------------------------------------------------------------------------
 1 | .wrapper {
 2 |   height: 100%;
 3 |   padding-bottom: var(--small-gap);
 4 | }
 5 | 
 6 | .main {
 7 |   max-width: var(--main-content);
 8 |   margin: 0 auto;
 9 |   padding: 0 var(--gap);
10 | }
11 | 


--------------------------------------------------------------------------------
/components/post/index.js:
--------------------------------------------------------------------------------
 1 | import Head from 'next/head'
 2 | 
 3 | import Navigation from './navigation'
 4 | import Page from '@/components/page'
 5 | import styles from './post.module.css'
 6 | 
 7 | function escapeHtml(unsafe) {
 8 |   return unsafe
 9 |     .replace(/&/g, '&amp;')
10 |     .replace(/</g, '&lt;')
11 |     .replace(/>/g, '&gt;')
12 |     .replace(/"/g, '&quot;')
13 |     .replace(/'/g, '&#039;')
14 | }
15 | 
16 | const Post = ({
17 |   title,
18 |   slug,
19 |   html,
20 |   hidden,
21 |   og,
22 |   description,
23 |   date,
24 |   previous,
25 |   next
26 | }) => {
27 |   return (
28 |     <Page
29 |       slug={slug}
30 |       title={title}
31 |       description={description}
32 |       image={
33 |         og && og === true
34 |           ? `https://res.cloudinary.com/dsdlhtnpw/image/upload/${slug}.png`
35 |           : og
36 |       }
37 |     >
38 |       <Head>
39 |         {hidden && <meta name="robots" content="noindex" />}
40 |         {date && <meta name="date" content={date} />}
41 |       </Head>
42 | 
43 |       <article
44 |         dangerouslySetInnerHTML={{
45 |           __html: `${html}`
46 |         }}
47 |       />
48 | 
49 |       <Navigation previous={previous} next={next} />
50 |     </Page>
51 |   )
52 | }
53 | 
54 | export default Post
55 | 


--------------------------------------------------------------------------------
/components/post/navigation.js:
--------------------------------------------------------------------------------
 1 | import Link from '@/components/link'
 2 | 
 3 | import styles from './navigation.module.css'
 4 | 
 5 | const Previous = ({ previous, next }) => {
 6 |   return (
 7 |     <div className={styles.navigation}>
 8 |       <div className={styles.previous}>
 9 |         {previous && (
10 |           <Link href="/[slug]" gray as={`/${previous.slug}`}>
11 |             <div className={styles.title}>← {previous.title}</div>
12 |           </Link>
13 |         )}
14 |       </div>
15 | 
16 |       <div className={styles.next}>
17 |         {next && (
18 |           <Link href="/[slug]" gray as={`/${next.slug}`}>
19 |             <div className={styles.title}>{next.title} →</div>
20 |           </Link>
21 |         )}
22 |       </div>
23 |     </div>
24 |   )
25 | }
26 | 
27 | export default Previous
28 | 


--------------------------------------------------------------------------------
/components/post/navigation.module.css:
--------------------------------------------------------------------------------
 1 | .navigation {
 2 |   margin: var(--big-gap) 0;
 3 |   display: flex;
 4 |   justify-content: space-between;
 5 |   align-items: center;
 6 | }
 7 | 
 8 | .previous, .next {
 9 |   font-weight: 500;
10 | }
11 | 
12 | .next {
13 |   text-align: right;
14 | }
15 | 
16 | .title {
17 |   font-weight: normal;
18 |   margin-bottom: var(--gap-half);
19 | }
20 | 
21 | .previous:hover, .next:hover,
22 | .previous:focus, .next:focus {
23 |   color: var(--fg);
24 | }
25 | 


--------------------------------------------------------------------------------
/components/post/post.module.css:
--------------------------------------------------------------------------------
1 | .date {
2 |   font-weight: 500;
3 |   font-size: 1rem;
4 | }
5 | 
6 | .title {
7 |   margin-top: 1rem;
8 | }
9 | 


--------------------------------------------------------------------------------
/components/posts-list/index.js:
--------------------------------------------------------------------------------
 1 | import { useState } from 'react'
 2 | 
 3 | import TextEntry from '@/components/entry/text'
 4 | import styles from './posts-list.module.css'
 5 | 
 6 | const Posts = ({ slug, posts, paginate }) => {
 7 |   const [showMore, setShowMore] = useState(3)
 8 | 
 9 |   return (
10 |     <>
11 |       <ul className={styles.container}>
12 |         {posts.slice(0, paginate ? showMore : undefined).map(post => {
13 |           return (
14 |             <TextEntry
15 |               key={`post-item-${post.slug}`}
16 |               href="/[slug]"
17 |               as={`/${post.slug}`}
18 |               title={`${post.title}`}
19 |               category={post.category}
20 |             />
21 |           )
22 |         })}
23 |         {paginate && showMore < posts.length && (
24 |           <button
25 |             onClick={() => {
26 |               setShowMore(showMore + 3)
27 |             }}
28 |             className={styles.button}
29 |           >
30 |             Show More
31 |           </button>
32 |         )}
33 |         <TextEntry
34 |           key={`new`}
35 |           href="https://github.com/gaearon/whatthefuck.is/issues/"
36 |           title={'request a definition'}
37 |           category={null}
38 |         />
39 |       </ul>
40 |     </>
41 |   )
42 | }
43 | 
44 | export default Posts
45 | 


--------------------------------------------------------------------------------
/components/posts-list/posts-list.module.css:
--------------------------------------------------------------------------------
 1 | .container {
 2 |   max-width: var(--main-content);
 3 |   margin: 0 auto;
 4 | }
 5 | 
 6 | .button {
 7 |   margin-top: var(--small-gap);
 8 |   width: 100%;
 9 |   cursor: pointer;
10 |   border-radius: var(--radius);
11 |   color: var(--gray);
12 |   font-weight: 500;
13 |   background: var(--lighter-gray);
14 |   border: none;
15 |   padding: var(--gap-half) var(--gap);
16 |   outline: none;
17 | }
18 | 
19 | .button:hover,
20 | .button:focus {
21 |   color: var(--fg);
22 | }
23 | 
24 | .button:active {
25 |   background-color: var(--lightest-gray);
26 | }
27 | 


--------------------------------------------------------------------------------
/components/preview/index.js:
--------------------------------------------------------------------------------
 1 | import NextLink from 'next/link'
 2 | import cn from 'classnames'
 3 | 
 4 | import styles from './preview.module.css'
 5 | 
 6 | const Preview = ({ post, active }) => {
 7 |   const { title, slug, date } = post
 8 | 
 9 |   const d = new Date(date)
10 |   const displayDate = `${d.toLocaleString('default', {
11 |     month: 'long'
12 |   })} ${d.getDate()}`
13 | 
14 |   return (
15 |     <NextLink href="[slug]" as={`/${slug}`}>
16 |       <a className={cn(styles.link, { [styles.active]: active })}>
17 |         <p className="clamp">{title}</p>
18 |         <hr />
19 |         <time>{displayDate}</time>
20 |       </a>
21 |     </NextLink>
22 |   )
23 | }
24 | 
25 | export default Preview
26 | 


--------------------------------------------------------------------------------
/components/preview/preview.module.css:
--------------------------------------------------------------------------------
 1 | .link {
 2 |   margin-bottom: var(--gap-half);
 3 |   display: flex;
 4 |   align-items: center;
 5 |   justify-content: space-between;
 6 |   color: inherit;
 7 |   text-decoration: none;
 8 |   color: var(--gray);
 9 | }
10 | 
11 | .link.active {
12 |   font-weight: 600;
13 | }
14 | 
15 | .link.active,
16 | .link.active hr,
17 | .link:hover,
18 | .link:hover hr {
19 |   color: var(--fg);
20 |   border-color: var(--fg);
21 | }
22 | 
23 | .link p {
24 |   margin: 0;
25 |   flex: 0 1 auto;
26 | }
27 | 
28 | .link hr {
29 |   flex: 1 0 1rem;
30 |   margin: 0 var(--gap);
31 |   border: none;
32 |   height: 1px;
33 |   border-bottom: 1px solid var(--light-gray);
34 |   width: 100%;
35 | }
36 | 
37 | .link time {
38 |   flex-shrink: 0;
39 |   font-size: 1.125rem;
40 | }
41 | 
42 | @media (max-width: 600px) {
43 |   .link hr {
44 |     display: none;
45 |   }
46 | }
47 | 


--------------------------------------------------------------------------------
/components/seo.js:
--------------------------------------------------------------------------------
 1 | import NextHead from 'next/head'
 2 | 
 3 | const Head = ({
 4 |   title = 'whatthefuck.is',
 5 |   description = 'Dan’s JavaScript Glossary',
 6 |   image,
 7 |   children
 8 | }) => {
 9 |   return (
10 |     <NextHead>
11 |       {/* Title */}
12 |       <title>{title}</title>
13 |       <meta name="og:title" content={title} />
14 | 
15 |       {/* Description */}
16 |       <meta name="description" content={description} />
17 |       <meta name="og:description" content={description} />
18 | 
19 |       {/* General */}
20 |       <meta name="viewport" content="width=device-width, initial-scale=1.0" />
21 |       <meta httpEquiv="Content-Language" content="en" />
22 | 
23 |       <meta name="twitter:site" content="@dan_abramov" />
24 |       <meta name="apple-mobile-web-app-title" content="whatthefuck.is" />
25 |       <meta name="author" content="Dan Abramov" />
26 | 
27 |       {/* RSS feed */}
28 |       <link
29 |         rel="alternate"
30 |         type="application/rss+xml"
31 |         title="RSS Feed for whatthefuck.is"
32 |         href="/feed.xml"
33 |       />
34 | 
35 |       {/* TODO: Favicons */}
36 |       <meta name="msapplication-TileColor" content="#ffffff" />
37 |       <meta name="theme-color" content="#ffffff" />
38 | 
39 |       {/* Inter font */}
40 |       <link
41 |         rel="preload"
42 |         href="https://assets.vercel.com/raw/upload/v1587415301/fonts/2/inter-var-latin.woff2"
43 |         as="font"
44 |         type="font/woff2"
45 |         crossOrigin="anonymous"
46 |       />
47 | 
48 |       {children}
49 |     </NextHead>
50 |   )
51 | }
52 | 
53 | export default Head
54 | 


--------------------------------------------------------------------------------
/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 |   "compilerOptions": {
3 |     "baseUrl": "node_modules",
4 |     "paths": { "@/*": ["../*"] }
5 |   }
6 | }
7 | 


--------------------------------------------------------------------------------
/lib/get-markdown.js:
--------------------------------------------------------------------------------
 1 | import fs from 'fs'
 2 | import path from 'path'
 3 | import { promisify } from 'util'
 4 | import renderMarkdown from './render-markdown'
 5 | 
 6 | const readFile = promisify(fs.readFile)
 7 | 
 8 | const getMarkdown = async filename => {
 9 |   const filenamePath = path.resolve(process.cwd(), filename)
10 |   const contents = await readFile(filenamePath, 'utf-8')
11 |   const html = renderMarkdown(contents)
12 | 
13 |   return html
14 | }
15 | 
16 | export default getMarkdown
17 | 


--------------------------------------------------------------------------------
/lib/get-posts.js:
--------------------------------------------------------------------------------
 1 | import matter from 'gray-matter'
 2 | import fs from 'fs'
 3 | import path from 'path'
 4 | 
 5 | export default () => {
 6 |   let dir
 7 |   try {
 8 |     dir = fs.readdirSync('./posts/')
 9 |   } catch (err) {
10 |     // No posts yet
11 |     return []
12 |   }
13 |   const posts = dir
14 |     .filter(file => path.extname(file) === '.md')
15 |     .map(file => {
16 |       const postContent = fs.readFileSync(`./posts/${file}`, 'utf8')
17 |       const { data, content } = matter(postContent)
18 | 
19 |       if (data.published === false) {
20 |         return null
21 |       }
22 | 
23 |       return { ...data, body: content, title: data.title.replace(' ', ' ') }
24 |     })
25 |     .filter(Boolean)
26 |     .sort((a, b) => a.slug.localeCompare(b.slug))
27 | 
28 |   return posts
29 | }
30 | 


--------------------------------------------------------------------------------
/lib/gtag.js:
--------------------------------------------------------------------------------
 1 | export const GA_TRACKING_ID = 'UA-173265493-1'
 2 | 
 3 | export const pageview = (url) => {
 4 |   window.gtag('config', GA_TRACKING_ID, {
 5 |     page_path: url,
 6 |   })
 7 | }
 8 | 
 9 | export const event = ({ action, category, label, value }) => {
10 |   window.gtag('event', action, {
11 |     event_category: category,
12 |     event_label: label,
13 |     value: value,
14 |   })
15 | }
16 | 


--------------------------------------------------------------------------------
/lib/render-markdown.js:
--------------------------------------------------------------------------------
  1 | import marked from 'marked'
  2 | import Highlight, { defaultProps } from 'prism-react-renderer'
  3 | import { renderToStaticMarkup } from 'react-dom/server'
  4 | import { format } from 'prettier'
  5 | 
  6 | // Don't want any theme at all
  7 | delete defaultProps.theme
  8 | 
  9 | import linkStyles from '../components/link/link.module.css'
 10 | 
 11 | const renderer = new marked.Renderer()
 12 | 
 13 | renderer.heading = (text, level, raw, slugger) => {
 14 |   const id = slugger.slug(text)
 15 |   const Component = `h${level}`
 16 | 
 17 |   return renderToStaticMarkup(
 18 |     <Component>
 19 |       <a href={`#${id}`} id={id} className="header-link">
 20 |         {text}
 21 |       </a>
 22 |     </Component>
 23 |   )
 24 | }
 25 | 
 26 | renderer.link = (href, _, text) =>
 27 |   `<a href=${href} target="_blank" rel="noopener noreferrer" class="${linkStyles.underline}">${text}</a>`
 28 | 
 29 | renderer.checkbox = () => ''
 30 | renderer.listitem = (text, task, checked) => {
 31 |   if (task) {
 32 |     return `<li class="reset"><span class="check">&#8203;<input type="checkbox" disabled ${
 33 |       checked ? 'checked' : ''
 34 |     } /></span><span>${text}</span></li>`
 35 |   }
 36 | 
 37 |   return `<li>${text}</li>`
 38 | }
 39 | renderer.code = (code, options) => {
 40 |   const opts = options.split(' ').map(o => o.trim())
 41 |   const language = opts[0]
 42 |   const highlight = opts
 43 |     .filter(o => o.startsWith('highlight='))
 44 |     .pop()
 45 |     ?.replace('highlight=', '')
 46 |     .trim()
 47 |   const raw = options.includes('raw')
 48 | 
 49 |   // Touch it up with Prettier
 50 |   let formattedCode = code
 51 | 
 52 |   if (!raw) {
 53 |     try {
 54 |       formattedCode = format(code, {
 55 |         semi: false,
 56 |         singleQuote: true,
 57 |         parser: language === 'jsx' || language === 'jsx' ? 'babel' : language
 58 |       })
 59 |     } catch (e) {} // Don't really mind if it fails
 60 |   }
 61 | 
 62 |   return renderToStaticMarkup(
 63 |     <pre>
 64 |       <Code language={language} code={formattedCode} highlight={highlight} />
 65 |     </pre>
 66 |   )
 67 | }
 68 | 
 69 | marked.setOptions({
 70 |   gfm: true,
 71 |   breaks: true,
 72 |   headerIds: true,
 73 |   renderer
 74 | })
 75 | 
 76 | export default markdown => marked(markdown)
 77 | 
 78 | const Code = ({ code, language, highlight, ...props }) => {
 79 |   if (!language)
 80 |     return <code {...props} dangerouslySetInnerHTML={{ __html: code }} />
 81 | 
 82 |   const highlightedLines = highlight
 83 |     ? highlight.split(',').reduce((lines, h) => {
 84 |         if (h.includes('-')) {
 85 |           // Expand ranges like 3-5 into [3,4,5]
 86 |           const [start, end] = h.split('-').map(Number)
 87 |           const x = Array(end - start + 1)
 88 |             .fill()
 89 |             .map((_, i) => i + start)
 90 |           return [...lines, ...x]
 91 |         }
 92 | 
 93 |         return [...lines, Number(h)]
 94 |       }, [])
 95 |     : []
 96 | 
 97 |   // https://mdxjs.com/guides/syntax-highlighting#all-together
 98 |   return (
 99 |     <Highlight {...defaultProps} code={code.trim()} language={language}>
100 |       {({ className, style, tokens, getLineProps, getTokenProps }) => (
101 |         <code className={className} style={{ ...style }}>
102 |           {tokens.map((line, i) => (
103 |             <div
104 |               key={i}
105 |               {...getLineProps({ line, key: i })}
106 |               style={
107 |                 highlightedLines.includes(i + 1)
108 |                   ? {
109 |                       background: 'var(--highlight)',
110 |                       margin: '0 -1rem',
111 |                       padding: '0 1rem'
112 |                     }
113 |                   : null
114 |               }
115 |             >
116 |               {line.map((token, key) => (
117 |                 <span key={key} {...getTokenProps({ token, key })} />
118 |               ))}
119 |             </div>
120 |           ))}
121 |         </code>
122 |       )}
123 |     </Highlight>
124 |   )
125 | }
126 | 


--------------------------------------------------------------------------------
/lib/rss.js:
--------------------------------------------------------------------------------
 1 | const fs = require('fs')
 2 | const RSS = require('rss')
 3 | const path = require('path')
 4 | const marked = require('marked')
 5 | const matter = require('gray-matter')
 6 | 
 7 | const getPosts = () => {
 8 |   let dir
 9 |   try {
10 |     dir = fs.readdirSync(path.resolve(__dirname, '../posts/'))
11 |   } catch (err) {
12 |     // No posts.
13 |     return []
14 |   }
15 | 
16 |   return dir
17 |     .filter(file => path.extname(file) === '.md')
18 |     .map(file => {
19 |       const postContent = fs.readFileSync(`./posts/${file}`, 'utf8')
20 |       const { data, content } = matter(postContent)
21 |       return { ...data, body: content }
22 |     })
23 |     .sort((a, b) => new Date(b.date) - new Date(a.date))
24 | }
25 | 
26 | const renderer = new marked.Renderer()
27 | 
28 | renderer.link = (href, _, text) =>
29 |   `<a href="${href}" target="_blank" rel="noopener noreferrer">${text}</a>`
30 | 
31 | marked.setOptions({
32 |   gfm: true,
33 |   breaks: true,
34 |   headerIds: true,
35 |   renderer
36 | })
37 | 
38 | const renderPost = md => marked(md)
39 | 
40 | const main = () => {
41 |   const feed = new RSS({
42 |     title: 'whatthefuck.is',
43 |     site_url: 'https://whatthefuck.is',
44 |     feed_url: 'https://whatthefuck.is/feed.xml',
45 |     language: 'en'
46 |   })
47 | 
48 |   const posts = getPosts()
49 |   posts.forEach(post => {
50 |     const url = `https://whatthefuck.is/${post.slug}`
51 | 
52 |     feed.item({
53 |       title: post.title,
54 |       description: renderPost(post.body),
55 |       date: new Date(post.date),
56 |       author: 'Dan Abramov',
57 |       url,
58 |       guid: url
59 |     })
60 |   })
61 | 
62 |   const rss = feed.xml({ indent: true })
63 |   fs.writeFileSync(path.join(__dirname, '../public/feed.xml'), rss)
64 | }
65 | 
66 | main()
67 | 


--------------------------------------------------------------------------------
/lib/theme.js:
--------------------------------------------------------------------------------
 1 | import { useCallback, useEffect } from 'react'
 2 | import useSWR from 'swr'
 3 | 
 4 | export const themeStorageKey = 'theme'
 5 | 
 6 | const isServer = typeof window === 'undefined'
 7 | const getTheme = () => {
 8 |   if (isServer) return 'dark'
 9 |   return localStorage.getItem(themeStorageKey) || 'dark'
10 | }
11 | 
12 | const setLightMode = () => {
13 |   try {
14 |     localStorage.setItem(themeStorageKey, 'light')
15 |     document.documentElement.classList.add('light')
16 |   } catch (err) {
17 |     console.error(err)
18 |   }
19 | }
20 | 
21 | const setDarkMode = () => {
22 |   try {
23 |     localStorage.setItem(themeStorageKey, 'dark')
24 |     document.documentElement.classList.remove('light')
25 |   } catch (err) {
26 |     console.error(err)
27 |   }
28 | }
29 | 
30 | const disableAnimation = () => {
31 |   const css = document.createElement('style')
32 |   css.type = 'text/css'
33 |   css.appendChild(
34 |     document.createTextNode(
35 |       `* {
36 |         -webkit-transition: none !important;
37 |         -moz-transition: none !important;
38 |         -o-transition: none !important;
39 |         -ms-transition: none !important;
40 |         transition: none !important;
41 |       }`
42 |     )
43 |   )
44 |   document.head.appendChild(css)
45 | 
46 |   return () => {
47 |     // Force restyle
48 |     ;(() => window.getComputedStyle(css).opacity)()
49 |     document.head.removeChild(css)
50 |   }
51 | }
52 | 
53 | const useTheme = () => {
54 |   const { data: theme, mutate } = useSWR(themeStorageKey, getTheme, {
55 |     initialData: getTheme()
56 |   })
57 | 
58 |   const setTheme = useCallback(
59 |     newTheme => {
60 |       mutate(newTheme, false)
61 |     },
62 |     [mutate]
63 |   )
64 | 
65 |   useEffect(() => {
66 |     const enable = disableAnimation()
67 | 
68 |     if (theme === 'dark') {
69 |       setDarkMode()
70 |     } else {
71 |       setLightMode()
72 |     }
73 | 
74 |     enable()
75 |   }, [theme])
76 | 
77 |   return {
78 |     theme,
79 |     setTheme,
80 |     toggleTheme: () => setTheme(!theme || theme === 'dark' ? 'light' : 'dark')
81 |   }
82 | }
83 | 
84 | export default useTheme
85 | 


--------------------------------------------------------------------------------
/lib/use-mounted.js:
--------------------------------------------------------------------------------
 1 | import { useEffect, useState } from 'react'
 2 | 
 3 | const useMounted = () => {
 4 |   const [mounted, setMounted] = useState(false)
 5 | 
 6 |   useEffect(() => setMounted(true), [])
 7 | 
 8 |   return mounted
 9 | }
10 | 
11 | export default useMounted
12 | 


--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "name": "blog",
 3 |   "version": "1.0.0",
 4 |   "scripts": {
 5 |     "lint": "prettier --write '**/*.js'",
 6 |     "dev": "next-remote-watch posts",
 7 |     "build": "yarn rss && next build",
 8 |     "rss": "node lib/rss"
 9 |   },
10 |   "pre-commit": "lint",
11 |   "license": "MIT",
12 |   "dependencies": {
13 |     "classnames": "^2.2.6",
14 |     "intersection-observer": "^0.7.0",
15 |     "lodash.debounce": "^4.0.8",
16 |     "next": "^9.4.2",
17 |     "nprogress": "^0.2.0",
18 |     "react": "^16.13.1",
19 |     "react-dom": "^16.13.1",
20 |     "swr": "^0.2.0"
21 |   },
22 |   "devDependencies": {
23 |     "@types/classnames": "^2.2.10",
24 |     "@types/node": "^14.0.22",
25 |     "@types/react": "^16.9.23",
26 |     "@typescript-eslint/eslint-plugin": "^2.22.0",
27 |     "@typescript-eslint/parser": "^2.22.0",
28 |     "babel-eslint": "^10.0.2",
29 |     "babel-plugin-module-resolver": "^4.0.0",
30 |     "eslint": "^6.1.0",
31 |     "eslint-plugin-import": "^2.18.2",
32 |     "eslint-plugin-react": "^7.14.3",
33 |     "eslint-plugin-react-hooks": "^2.5.0",
34 |     "gray-matter": "^4.0.2",
35 |     "marked": "^1.1.0",
36 |     "next-remote-watch": "^0.1.1",
37 |     "prettier": "^1.18.2",
38 |     "prism-react-renderer": "^1.1.1",
39 |     "rss": "^1.2.2",
40 |     "typescript": "^3.8.3"
41 |   },
42 |   "prettier": {
43 |     "semi": false,
44 |     "singleQuote": true
45 |   }
46 | }
47 | 


--------------------------------------------------------------------------------
/pages/404.js:
--------------------------------------------------------------------------------
 1 | import { withRouter } from 'next/router'
 2 | 
 3 | import Error from '@/components/error'
 4 | 
 5 | // Included just to prevent Automatic Static Optimization
 6 | export async function getStaticProps(context) {
 7 |   return {
 8 |     props: {}
 9 |   }
10 | }
11 | 
12 | // Strips potential `/term?something=else` into `term`
13 | const extractTerm = path =>
14 |   path.substring(
15 |     path.indexOf('/') + 1,
16 |     Math.max(path.indexOf('?'), path.length)
17 |   )
18 | 
19 | function E({ router }) {
20 |   return <Error missingTerm={extractTerm(router.asPath)} status={404} />
21 | }
22 | 
23 | export default withRouter(E)
24 | 


--------------------------------------------------------------------------------
/pages/[slug].js:
--------------------------------------------------------------------------------
 1 | import Post from '@/components/post'
 2 | import getPosts from '@/lib/get-posts'
 3 | import renderMarkdown from '@/lib/render-markdown'
 4 | 
 5 | const PostPage = props => {
 6 |   return <Post {...props} />
 7 | }
 8 | 
 9 | export const getStaticProps = ({ params: { slug } }) => {
10 |   const posts = getPosts()
11 |   const postIndex = posts.findIndex(p => p.slug === slug)
12 |   const post = posts[postIndex]
13 |   const { body, ...rest } = post
14 | 
15 |   return {
16 |     props: {
17 |       previous: posts[postIndex - 1] || null,
18 |       next: posts[postIndex + 1] || null,
19 |       ...rest,
20 |       html: renderMarkdown(body)
21 |     }
22 |   }
23 | }
24 | 
25 | export const getStaticPaths = () => {
26 |   return {
27 |     paths: getPosts().map(p => `/${p.slug}`),
28 |     fallback: false
29 |   }
30 | }
31 | 
32 | export default PostPage
33 | 


--------------------------------------------------------------------------------
/pages/_app.js:
--------------------------------------------------------------------------------
 1 | import React from 'react'
 2 | import Router from 'next/router'
 3 | import App from 'next/app'
 4 | import nprogress from 'nprogress'
 5 | import debounce from 'lodash.debounce'
 6 | import * as gtag from '@/lib/gtag'
 7 | 
 8 | // Only show nprogress after 500ms (slow loading)
 9 | const start = debounce(nprogress.start, 500)
10 | Router.events.on('routeChangeStart', start)
11 | Router.events.on('routeChangeComplete', url => {
12 |   start.cancel()
13 |   nprogress.done()
14 |   window.scrollTo(0, 0)
15 |   gtag.pageview(url)
16 | })
17 | Router.events.on('routeChangeError', () => {
18 |   start.cancel()
19 |   nprogress.done()
20 | })
21 | 
22 | import '@/styles/global.css'
23 | 
24 | class MyApp extends App {
25 |   render() {
26 |     const { Component, pageProps } = this.props
27 |     return <Component {...pageProps} />
28 |   }
29 | }
30 | 
31 | export default MyApp
32 | 


--------------------------------------------------------------------------------
/pages/_document.js:
--------------------------------------------------------------------------------
 1 | import React from 'react'
 2 | import Document, { Html, Head, Main, NextScript } from 'next/document'
 3 | 
 4 | import { themeStorageKey } from '@/lib/theme'
 5 | import { GA_TRACKING_ID } from '@/lib/gtag'
 6 | const bgVariableName = '--bg'
 7 | 
 8 | class MyDocument extends Document {
 9 |   render() {
10 |     return (
11 |       <Html lang="en">
12 |         <Head />
13 |         <body>
14 |           <script
15 |             dangerouslySetInnerHTML={{
16 |               __html: `(function() {
17 |                 try {
18 |                   var outdatedValue = localStorage.getItem('light-mode')
19 | 
20 |                   if (outdatedValue) {
21 |                     localStorage.setItem('${themeStorageKey}', 'light')
22 |                     localStorage.removeItem('light-mode')
23 |                   }
24 | 
25 |                   var mode = localStorage.getItem('${themeStorageKey}')
26 |                   if (!mode) return
27 |                   document.documentElement.classList.add(mode)
28 |                   var bgValue = getComputedStyle(document.documentElement)
29 |                     .getPropertyValue('${bgVariableName}')
30 |                   document.documentElement.style.background = bgValue
31 |                 } catch (e) {}
32 |               })()`
33 |             }}
34 |           />
35 |           <script
36 |             async
37 |             src={`https://www.googletagmanager.com/gtag/js?id=${GA_TRACKING_ID}`}
38 |           />
39 |           <script
40 |             dangerouslySetInnerHTML={{
41 |               __html: `
42 |             window.dataLayer = window.dataLayer || [];
43 |             function gtag(){dataLayer.push(arguments);}
44 |             gtag('js', new Date());
45 |             gtag('config', '${GA_TRACKING_ID}', {
46 |               page_path: window.location.pathname,
47 |             });
48 |           `
49 |             }}
50 |           />
51 |           <Main />
52 |           <NextScript />
53 |         </body>
54 |       </Html>
55 |     )
56 |   }
57 | }
58 | 
59 | export default MyDocument
60 | 


--------------------------------------------------------------------------------
/pages/_error.js:
--------------------------------------------------------------------------------
 1 | import React from 'react'
 2 | 
 3 | import Error from '@/components/error'
 4 | 
 5 | class E extends React.Component {
 6 |   static getInitialProps({ res, err }) {
 7 |     const status = res ? res.statusCode : err ? err.statusCode : null
 8 |     return { status }
 9 |   }
10 | 
11 |   render() {
12 |     const { status } = this.props
13 |     return <Error status={status} />
14 |   }
15 | }
16 | 
17 | export default E
18 | 


--------------------------------------------------------------------------------
/pages/index.js:
--------------------------------------------------------------------------------
 1 | import React from 'react'
 2 | 
 3 | import Page from '@/components/page'
 4 | import PostsList from '@/components/posts-list'
 5 | import getPosts from '@/lib/get-posts'
 6 | 
 7 | const Blog = ({ posts }) => {
 8 |   return (
 9 |     <Page description="Dan’s JavaScript Glossary">
10 |       <article>
11 |         <PostsList posts={posts} />
12 |       </article>
13 |     </Page>
14 |   )
15 | }
16 | 
17 | export const getStaticProps = () => {
18 |   const posts = getPosts()
19 | 
20 |   return {
21 |     props: {
22 |       posts
23 |     }
24 |   }
25 | }
26 | 
27 | export default Blog
28 | 


--------------------------------------------------------------------------------
/posts/closure.md:
--------------------------------------------------------------------------------
  1 | ---
  2 | title: a closure
  3 | slug: closure
  4 | date: July 11, 2020
  5 | category: language
  6 | ---
  7 | 
  8 | Closures are confusing because they are an “invisible” concept.
  9 | 
 10 | When you use an object, a variable, or a function, you do this intentionally. You think: “I’m gonna need a variable here,” and add it to your code.
 11 | 
 12 | Closures are different. By the time most people approach closures, they have already used them unknowingly many times — and it is likely that this is true for yourself, too. So learning closures is less about understanding a *new* concept and more about recognizing something you have *already been doing* for a while.
 13 | 
 14 | ### tl;dr
 15 | 
 16 | You have a closure when **a function accesses variables defined outside of it**.
 17 | 
 18 | For example, this code snippet contains a closure:
 19 | 
 20 | ```js
 21 | let users = ['Alice', 'Dan', 'Jessica'];
 22 | let query = 'A';
 23 | let user = users.filter(user => user.startsWith(query));
 24 | ```
 25 | 
 26 | Notice how `user => user.startsWith(query)` is itself a function. It uses the `query` variable. But the `query` variable is defined *outside* of that function. That’s a closure.
 27 | 
 28 | ---
 29 | 
 30 | **You can stop reading here, if you want.** The rest of this article approaches closures in a different way. Instead of explaining what a closure is, it will walk you through the process of *discovering* closures — like the first programmers did in the 1960s.
 31 | 
 32 | ---
 33 | 
 34 | ### Step 1: Functions Can Access Outside Variables
 35 | 
 36 | To understand closures, we need to be somewhat familiar with variables and functions. In this example, we declare the `food` variable *inside* the `eat` function:
 37 | 
 38 | ```js
 39 | function eat() {
 40 |   let food = 'cheese';
 41 |   console.log(food + ' is good');
 42 | }
 43 | 
 44 | eat(); // Logs 'cheese is good'
 45 | ```
 46 | 
 47 | But what if we wanted to later change the `food` variable *outside* of the `eat` function? To do this, we can move the `food` variable itself out of our function into the top level:
 48 | 
 49 | ```js
 50 | let food = 'cheese'; // We moved it outside
 51 | 
 52 | function eat() {
 53 |   console.log(food + ' is good');
 54 | }
 55 | ```
 56 | 
 57 | This lets us change the `food` “from the outside” any time that we want to:
 58 | 
 59 | ```js
 60 | eat(); // Logs 'cheese is good'
 61 | food = 'pizza';
 62 | eat(); // Logs 'pizza is good'
 63 | food = 'sushi';
 64 | eat(); // Logs 'sushi is good'
 65 | ```
 66 | 
 67 | In other words, the `food` variable is no longer *local* to our `eat` function, but our `eat` function nevertheless has no trouble accessing it. **Functions can access variables outside of them.** Stop for a second and make sure that you have no problem with this idea. Once it has settled comfortably in your brain, move to the second step.
 68 | 
 69 | ### Step 2: Wrapping Code in a Function Call
 70 | 
 71 | Let’s say we have some code:
 72 | 
 73 | ```js
 74 | /* A snippet of code */
 75 | ```
 76 | 
 77 | It doesn’t matter what that code does. But let’s say that **we want to run it twice**.
 78 | 
 79 | One way to do it would be to copy and paste it:
 80 | 
 81 | ```js
 82 | /* A snippet of code */
 83 | /* A snippet of code */
 84 | ```
 85 | 
 86 | Another way to do it would be to use a loop:
 87 | 
 88 | ```js
 89 | for (let i = 0; i < 2; i++) {
 90 |   /* A snippet of code */
 91 | }
 92 | ```
 93 | 
 94 | The third way, which we’re particularly interested in today, is to wrap it in a function:
 95 | 
 96 | ```js
 97 | function doTheThing() {
 98 |   /* A snippet of code */
 99 | }
100 | 
101 | doTheThing();
102 | doTheThing();
103 | ```
104 | 
105 | Using a function gives us the ultimate flexibility because we can run this function any number of times, at any time — and from anywhere in our program.
106 | 
107 | In fact, **we can even call our new function only *once***, if we wanted to:
108 | 
109 | ```js
110 | function doTheThing() {
111 |   /* A snippet of code */
112 | }
113 | 
114 | doTheThing();
115 | ```
116 | 
117 | Notice how the code above is equivalent to the original code snippet:
118 | 
119 | ```js
120 | /* A snippet of code */
121 | ```
122 | 
123 | In other words, **if we take some piece of code, “wrap” that code in a function, and then call that function exactly once, we haven’t changed what that code is doing**. There are some exceptions to this rule which we will ignore, but generally speaking this should make sense. Sit on this idea until your brain feels comfortable with it.
124 | 
125 | ### Step 3: Discovering Closures
126 | 
127 | We have traced our way through two different ideas:
128 | 
129 | * **Functions can access variables defined outside of them.**
130 | * **Wrapping code in a function and calling it once doesn’t change the result.**
131 | 
132 | Now let’s see what happens if we combine them.
133 | 
134 | We’ll take our code example from the first step:
135 | 
136 | ```js
137 | let food = 'cheese';
138 | 
139 | function eat() {
140 |   console.log(food + ' is good');
141 | }
142 | 
143 | eat();
144 | ```
145 | 
146 | Then we’ll wrap *this whole example* into a function, which we’re going to call once:
147 | 
148 | ```js
149 | function liveADay() {
150 |   let food = 'cheese';
151 | 
152 |   function eat() {
153 |     console.log(food + ' is good');
154 |   }
155 | 
156 |   eat();
157 | }
158 | 
159 | liveADay();
160 | ```
161 | 
162 | Read both snippets one more time and make sure that they are equivalent.
163 | 
164 | This code works! But look closer. Notice the `eat` function is *inside* the `liveADay` function. Is that even allowed? Can we really put a function inside another function?
165 | 
166 | There are languages in which a code structured this way is *not* valid. For example, this code is not valid in the C language (which doesn’t have closures). This means that in C, our second conclusion isn’t true — we can’t just take some arbitrary piece of code and wrap it in a function. But JavaScript doesn’t suffer from that limitation.
167 | 
168 | Take another good look at this code and notice where `food` is declared and used:
169 | 
170 | ```js
171 | function liveADay() {
172 |   let food = 'cheese'; // Declare `food`
173 | 
174 |   function eat() {
175 |     console.log(food + ' is good'); // Read `food`
176 |   }
177 | 
178 |   eat();
179 | }
180 | 
181 | liveADay();
182 | ```
183 | 
184 | Let’s go through this code together — step by step. First, we declare the `liveADay` function at the top level. We immediately call it. It has a `food` local variable. It also contains an `eat` function. Then it calls that `eat` function. Because `eat` is inside of `liveADay`, it “sees” all of its variables. This is why it can read the `food` variable.
185 | 
186 | **This is called a closure.**
187 | 
188 | **We say that there is a closure when a function (such as `eat`) reads or writes a variable (such as `food`) that is declared outside of it (such as in `liveADay`).**
189 | 
190 | Take some time to re-read this, and make sure you can trace this in the code.
191 | 
192 | Here is an example we’ve introduced in the tl;dr section:
193 | 
194 | ```js
195 | let users = ['Alice', 'Dan', 'Jessica'];
196 | let query = 'A';
197 | let user = users.filter(user => user.startsWith(query));
198 | ```
199 | 
200 | It may be easier to notice the closure if we rewrite it with a function expression:
201 | 
202 | ```js
203 | let users = ['Alice', 'Dan', 'Jessica'];
204 | // 1. The query variable is declared outside
205 | let query = 'A';
206 | let user = users.filter(function(user) {
207 |   // 2. We are in a nested function
208 |   // 3. And we read the query variable (which is declared outside!)
209 |   return user.startsWith(query);
210 | });
211 | ```
212 | 
213 | Whenever a function accesses a variable that is declared outside of it, we say it is a closure. The term itself is used a bit loosely. Some people will refer to the *nested function itself* as “the closure” in this example. Others might refer to the *technique* of accessing the outside variables as the closure. Practically, it doesn’t matter.
214 | 
215 | ### A Ghost of a Function Call
216 | 
217 | Closures might seem deceptively simple now. This doesn’t mean they’re without their own pitfalls. The fact that a function may read and write variables outside has rather deep consequences if you really think about it. For example, this means that these variables will “survive” for as long as the nested function may be called:
218 | 
219 | ```js
220 | function liveADay() {
221 |   let food = 'cheese';
222 | 
223 |   function eat() {
224 |     console.log(food + ' is good');
225 |   }
226 | 
227 |   // Call eat after five seconds
228 |   setTimeout(eat, 5000);
229 | }
230 | 
231 | liveADay();
232 | ```
233 | 
234 | Here, `food` is a local variable inside the `liveADay()` function call. It’s tempting to think it “disappears” after we exit `liveADay`, and it won’t come back to haunt us.
235 | 
236 | However, inside of `liveADay` we tell the browser to call `eat` in five seconds. And `eat` reads the `food` variable. **So the JavaScript engine needs to keep the `food` variable from that particular `liveADay()` call available until `eat` has been called.**
237 | 
238 | In that sense, we can think of closures as of “ghosts” or “memories” of the past function calls. Even though our `liveADay()` function call has long finished, its variables must continue to exist for as long as the nested `eat` function may still be called. Luckily, JavaScript does that for us, so we don’t need to think about it.
239 | 
240 | ### Why “Closures”?
241 | 
242 | Finally, you might be wondering why closures are called that way. The reason is mostly historical. A person familiar with the computer science jargon might say that an expression like `user => user.startsWith(query)` has an “open binding”. In other words, it is clear from it what the `user` is (a parameter), but it is not clear what `query` is in isolation. When we say “actually, `query` refers to the variable declared outside”, we are “closing” that open binding. In other words, we get a *closure*.
243 | 
244 | Not all languages implement closures. For example, in some languages like C, it is not allowed to nest functions at all. As a result, a function may only access its own local variables or global variables, but there is never a situation in which it can access a parent function’s local variables. Naturally, that limitation is painful.
245 | 
246 | There are also languages like Rust which implement closures, but have a separate syntax for closures and regular functions. So if you want to read a variable from outside a function, you would have to opt into that in Rust. This is because under the hood, closures may require the engine to keep the outer variables (called “the environment”) around even after the function call. This overhead is acceptable in JavaScript, but it can be a performance concern for the very low-level languages.
247 | 
248 | And with that, I hope you can get a closure on the concept of closures!
249 | 
250 | >If you prefer a more visual approach to the JavaScript fundamentals, check out [Just JavaScript](https://justjavascript.com/). It is my illustrated course in collaboration with [Maggie Appleton](https://maggieappleton.com/).
251 | 


--------------------------------------------------------------------------------
/posts/composition.md:
--------------------------------------------------------------------------------
  1 | ---
  2 | title: composition
  3 | slug: composition
  4 | date: July 11, 2020
  5 | category: computer science
  6 | ---
  7 | 
  8 | Broadly speaking, **composition is putting two or more different things together, and getting the same “kind” of thing — a combination of the inputs — as a result.**
  9 | 
 10 | The concrete meaning might depend on the context, so we’ll look at a few examples that come up in front-end JavaScript development.
 11 | 
 12 | ### Composition in Math
 13 | 
 14 | While math is somewhat unrelated to front-end development, it’s useful to recap the [mathematical definition](https://en.wikipedia.org/wiki/Function_composition) — if only to show where the term originates.
 15 | 
 16 | Let’s say we have two functions. One function is `y = 2 * x`, which doubles its argument. The other function is `y = x + 10`, which adds 10 to its argument.
 17 | 
 18 | If we put them together so that the output of one function is fed to the other function, we get `y = (2 * x) + 10`. This is an example of composition — we’ve “composed” this function out of two other functions. **That’s all this term means.**
 19 | 
 20 | Note how composition of two functions gives us another function. It doesn’t give us something entirely different. As a result, we can keep composing it many times.
 21 | 
 22 | Composition is a broad term, but we will only use it when the result of putting things together is the same “kind” of thing — whether it’s a function, a component, etc.
 23 | 
 24 | ### Function Composition
 25 | 
 26 | Composition often comes up in the context of functional programming. There, it refers to the same concept as in mathematics, but expressed in code.
 27 | 
 28 | Let’s say we have code like this:
 29 | 
 30 | ```js
 31 | let date = getDate();
 32 | let text = formatDate(date);
 33 | let label = createLabel(text);
 34 | showLabel(label);
 35 | ```
 36 | 
 37 | There is some repetition to this code. A [rhythm](https://twitter.com/dan_abramov/status/1216142369181093889), if you will. We take a thing, convert it to something else, take *that* thing, convert it to something else, and so on.
 38 | 
 39 | But can we go further and remove the repetition, leaving only the steps?
 40 | 
 41 | ```js
 42 | let steps = [
 43 |   getDate,
 44 |   formatDate,
 45 |   createLabel,
 46 |   showLabel
 47 | ];
 48 | ```
 49 | 
 50 | Some might say this code is [cleaner](https://overreacted.io/goodbye-clean-code/).
 51 | 
 52 | Let’s write a function that we’ll call `runSteps` that applies each step one by one:
 53 | 
 54 | ```js
 55 | function runSteps(steps) {
 56 |   let result;
 57 |   for (let i = 0; i < steps.length; i++) {
 58 |     let step = steps[i];
 59 |     // Apply next step in the chain
 60 |     result = step(result);
 61 |   }
 62 |   return result;
 63 | }
 64 | ```
 65 | 
 66 | With this function, our original code becomes:
 67 | 
 68 | ```js
 69 | runSteps([
 70 |   getDate,
 71 |   formatDate,
 72 |   createLabel,
 73 |   showLabel
 74 | ]);
 75 | ```
 76 | 
 77 | Now let’s say we want to perform all of these steps, but from different places in our program, and at different times. We could write a function that does this for us:
 78 | 
 79 | ```js
 80 | function showDateLabel() {
 81 |   runSteps([
 82 |     getDate,
 83 |     formatDate,
 84 |     createLabel,
 85 |     showLabel
 86 |   ]);
 87 | }
 88 | 
 89 | // We can call it whenever!
 90 | showDateLabel();
 91 | showDateLabel();
 92 | ```
 93 | 
 94 | Or we could have a function — we’ll call it `pipe` — that *generates* our function:
 95 | 
 96 | ```js
 97 | let showDateLabel = pipe(
 98 |   getDate,
 99 |   formatDate,
100 |   createLabel,
101 |   showLabel
102 | );
103 | 
104 | // We can call it whenever!
105 | showDateLabel();
106 | showDateLabel();
107 | ```
108 | 
109 | This code does exactly the same thing, but we didn’t have to explicitly implement `showDateLabel` (which merely called `runSteps`). We’ve [tucked it away](/closure) inside `pipe`:
110 | 
111 | ```js
112 | function pipe(...steps) {
113 |   // Return a function that will do this for me
114 |   return function runSteps() {
115 |     let result;
116 |     for (let i = 0; i < steps.length; i++) {
117 |       let step = steps[i];
118 |       result = step(result);
119 |     }
120 |     return result;
121 |   }
122 | }
123 | ```
124 | 
125 | This reusable function lets us rewrite our code so that **instead of manually calling functions one by one in a sequence, we only specify the steps**. We called it `pipe` because it “pipes” the output of each previous function to the next one.
126 | 
127 | Recall our original code:
128 | 
129 | ```js
130 | let date = getDate();
131 | let text = formatDate(date);
132 | let label = createLabel(text);
133 | showLabel(label);
134 | ```
135 | 
136 | And this is what it looks like with `pipe`:
137 | 
138 | ```js
139 | let showDateLabel = pipe(
140 |   getDate,
141 |   formatDate,
142 |   createLabel,
143 |   showLabel
144 | );
145 | showDateLabel();
146 | ```
147 | 
148 | If you’re not sold on expressing everything using function composition, you might be wondering — what was the point? Why did we go through all of these steps? Isn’t the first example a bit more readable? Are you the only person not “getting” it?
149 | 
150 | #### Functional Eureka
151 | 
152 | Understanding `pipe` and function composition for the first time is a lightbulb moment. We don’t have to call our functions manually — instead, we can feed our functions to another function that will give us back a function to call our functions!
153 | 
154 | How “beautiful”.
155 | 
156 | There is definitely a profound insight there that we shouldn’t disregard. We have *raised the level of abstraction* by making the structure of our program itself — a sequence of steps — into something our code can manipulate. For example, we could teach `pipe` to wrap every step with some logging, or to run every step asynchronously. This is a powerful technique that deserves our understanding.
157 | 
158 | This programming style can also be a nightmare to work with. We’ve “outsourced” the actual business of function calls to helpers like `pipe`, and as a result we can no longer clearly see how each piece of data flows in and out of our functions because it all happens inside of `pipe`. We’ve added a piece of “indirection” — our code is more flexible, but less direct. Add too many layers, and [our heads will overflow](https://overreacted.io/the-wet-codebase/).
159 | 
160 | While this programming style can be used with great success (especially in strongly typed languages that enforce *which* things can “fit” into other things), it’s a tad overused by enthusiastic programmers who get a dopamine rush from writing clever one-liners and hiding the control flow in “elegant” helpers. I did that, too.
161 | 
162 | #### Function Composition Is Neat, Though
163 | 
164 | That being said, the fundamental idea of function composition is important. Essentially, it means that when we have `doX(doY(doZ(thing)))`, we can *first* compose `doX`, `doY`, and `doZ`, and *then* apply the resulting function together.
165 | 
166 | In the trivial cases like above, using it directly brings more trouble than it’s worth. But it might get more useful if the problem is more challenging. Perhaps, we want each step to be [memoized](/memoization). Perhaps, each step happens asynchronously and the control flow is more complex. There can be cases where we want something to happen before or after each step, without repeating that fragile logic everywhere. Perhaps, the steps themselves need to be “interpreted” in different ways by our program, so we want to separate their order from how they are being executed.
167 | 
168 | Function composition can inspire interesting solutions if we keep it mind. This doesn’t mean that we need to take out a `pipe` every time we want to put two functions together. We don’t need to prove to the computer that we’re smart and learned our lessons about composition. Usually, plain function calls are enough.
169 | 
170 | ### Component Composition
171 | 
172 | Another context in which we might hear the word “composition” is related to declarative UI programming. We’ll take React components as an example.
173 | 
174 | React components render other components, all the way from `<App>` to a `<Button>`:
175 | 
176 | ```js
177 | function App() {
178 |   return <Screen />;
179 | }
180 | 
181 | function Screen() {
182 |   return <Form />;
183 | }
184 | 
185 | function Form() {
186 |   return <Button />;
187 | }
188 | 
189 | function Button() {
190 |   return <button>Hey there.</button>;
191 | }
192 | ```
193 | 
194 | This is also called “composition” because we are putting things (components) into other things (components), and they fit with each other (“compose”) pretty well.
195 | 
196 | One interesting variant of composition is when a component has “slots”:
197 | 
198 | ```js
199 | function Layout({ sidebar, content }) {
200 |   return (
201 |     <div>
202 |       <div className="sidebar">{sidebar}</div>
203 |       <div className="content">{content}</div>
204 |     </div>
205 |   )
206 | }
207 | ```
208 | 
209 | Then we can “fill in” those slots from different parent components:
210 | 
211 | ```js
212 | function HomePage() {
213 |   return (
214 |     <Layout
215 |       sidebar={<HomeSidebar />}
216 |       content={<HomeContent />}
217 |     >
218 |   )
219 | }
220 | 
221 | function AboutPage() {
222 |   return (
223 |     <Layout
224 |       sidebar={<AboutSidebar />}
225 |       content={<AboutContent />}
226 |     >
227 |   )
228 | }
229 | ```
230 | 
231 | Note how these “slots” aren’t a special React feature. They are a consequence of our ability to pass down pieces of UI the same way we would pass any other data.
232 | 
233 | This is also called “composition” because we compose (“fill in”) `Layout` with different child components. Putting things inside other things.
234 | 
235 | ### Composition vs Inheritance
236 | 
237 | People sometimes say “composition” when contrasting it with inheritance. This has less to do with functions (which we’ve been discussing all along) and more to do with objects and classes — that is, with traditional object-oriented programming.
238 | 
239 | In particular, if you express your code as classes, it is tempting to reuse behavior from another class by extending it (inheritance). However, this makes it somewhat difficult to adjust the behavior later. For example, you may want to similarly reuse behavior from *another* class, but you can’t extend more than one base class.
240 | 
241 | Sometimes, people say that inheritance “locks you into” your first design because the cost of changing the class hierarchy later is too high. When people suggest *composition* is an alternative to inheritance, they mean that instead of extending a class, you can keep an instance of that class as a field. Then you can “delegate” to that instance when necessary, but you are also free to do something different.
242 | 
243 | Overall, the industry has largely moved away from modeling UI components as deep inheritance hierarchies, as was common in the 2000s.
244 | 
245 | This doesn’t mean inheritance is always “bad”. But it is a very blunt tool, and it should be approached with moderation. In particular, inheritance hierarchies deeper than a few levels often cause problems that shallow inheritance doesn’t.
246 | 
247 | Modern front-end codebases rarely use inheritance for their UI because all of the popular UI libraries today feature powerful built-in support for composition. Say, in React, instead of extending a `Button`, you would *render* a `<Button>` in a parent component. Even JavaScript UI libraries that embrace classes typically don’t use inheritance as a way to reuse rendering code. And this is probably for the best.
248 | 
249 | ### Recap
250 | 
251 | To sum up, we say that we compose two things when we make a third thing out of them that is similarly shaped. The term has a mathematical meaning, and it is close to its meaning in functional programming. But the further we move away from pure functional programming, the less formal and more colloquial this term becomes.
252 | 
253 | Function composition is a powerful concept, but it raises the level of abstraction and makes your code less direct. If you write your code in a style that composes functions in some way before calling them, and there are other humans on your team, make sure that you’re getting concrete benefits from this approach. It is not “cleaner” or “better”, and there is a price to pay for “beautiful” but indirect code.
254 | 


--------------------------------------------------------------------------------
/posts/dynamic-dispatch.md:
--------------------------------------------------------------------------------
 1 | ---
 2 | title: dynamic dispatch
 3 | slug: dynamic-dispatch
 4 | date: July 20, 2020
 5 | category: computer science
 6 | ---
 7 | 
 8 | In JavaScript, we use *dynamic dispatch* whenever we call a method on an object:
 9 | 
10 | ```js
11 | obj.method();
12 | ```
13 | 
14 | In some object-oriented languages that predate JavaScript (notably, in Smalltalk), “calling a method on an object” was known as “sending a message to an object”. The words “dispatch” and “send” are synonyms, which explains the term’s origin.
15 | 
16 | Why do we say this is “dynamic dispatch”? Can it be static? This depends on the language. For example, in JavaScript, calls like `obj.method()` are always “dynamic”. But this is not universally true for other languages. Let’s unpack the difference.
17 | 
18 | To understand this, we need to dive deeper into how computer code works under the hood. Generally speaking, the functions we write eventually become sequences of computer instructions that are stored somewhere in memory. Calling a function means the computer will “jump” to its code in memory and execute its operations.
19 | 
20 | When we say that a language has *static dispatch*, it means that when the computer compiles `obj.method()`, it knows exactly *which* function this code would “jump” to. It won’t need to do any extra operations to “find” the function location in memory.
21 | 
22 | **When we say that a language has *dynamic dispatch*, it means we can’t be 100% sure which function `obj.method()` will call until this code runs**. For example, any JavaScript engine would need to take the current value of the `obj` variable, search for a property called `method` on that object, keep searching for it on the prototype chain if needed — and finally, “jump” into the function (or error if it wasn’t found).
23 | 
24 | Static dispatch is more commonly found in lower-level languages like C++ where performance must stay very close to metal. Dynamic dispatch lets us write more expressive code, but at a slight performance cost. This cost is negligible in most scenarios in web development, and JavaScript engines do a really good job to optimize the common cases and make dynamic dispatch as cheap as possible. So in practice, this difference is rarely relevant to JavaScript application development.
25 | 
26 | In fact, because of the JavaScript engine optimizations, the end result may be counterintuitive. For example, modern JS engines can often optimize a dynamic `obj.method()` call to match performance of a static call in a running program as long as their initial assumptions about your object remain true. The inverse may also happen, where a `fn()` call that seemingly doesn’t involve “searching” for properties, may still require some extra steps. Small benchmarks are usually [a lie](https://mrale.ph/blog/2014/12/24/array-length-caching.html), so you should generally let the engine do its job and write the code in the most natural way to you.
27 | 
28 | **To sum up,** “dynamic dispatch” means that when we write `obj.method()`, the JS engine may need to “find” our function before it can call it. But in JavaScript, the distinction is fuzzy because at the micro level, the exact steps that happen may change between different concrete examples and even between browser versions.
29 | 
30 | Unless you’re specifically talking about the tradeoffs of `obj.method()` versus `fn()` calls or comparing different programming languages, saying “method call” is clearer.
31 | 


--------------------------------------------------------------------------------
/posts/memoization.md:
--------------------------------------------------------------------------------
  1 | ---
  2 | title: memoization
  3 | slug: memoization
  4 | date: July 12, 2020
  5 | category: computer science
  6 | ---
  7 | 
  8 | Before we start, let’s make sure we don’t confuse memoization and memorization. Memorization is when you put something in *your* head. It has the letter “r” in it.
  9 | 
 10 | But *memoization* (no “r”) is a nerdy computer science topic. Although in practice it also (unsurprisingly!) has to do with remembering things, there exist snobs who will hold it against you if you say “memorize” when you really mean “memoize”. I am not personally one of them, but you’ll need to keep this in mind for some job interviews.
 11 | 
 12 | Okay, if these are different things, then what *is* memoization?
 13 | 
 14 | ### tl;dr
 15 | 
 16 | To illustrate it, imagine you’re writing a JavaScript app that calculates the weather based on the atmosphere readings. You don’t know how to do it, but luckily, there is an npm package with a `getChanceOfRain` function that you can use from your code:
 17 | 
 18 | ```js
 19 | import { getChanceOfRain } from 'magic-weather-calculator';
 20 | 
 21 | function showWeatherReport() {
 22 |   let result = getChanceOfRain(); // Let the magic happen
 23 |   console.log("The chance of rain tomorrow is:", result);
 24 | }
 25 | ```
 26 | 
 27 | There is just one problem. No matter what you do, calling `getChanceOfRain()` takes 100 milliseconds. So if the user rage-clicks “Show me the weather!” button, the weather will have to be recalculated and the app will briefly freeze on every click:
 28 | 
 29 | ```js
 30 | showWeatherReport(); // (!) Triggers the calculation
 31 | showWeatherReport(); // (!) Triggers the calculation
 32 | showWeatherReport(); // (!) Triggers the calculation
 33 | ```
 34 | 
 35 | This is not ideal. In the real life, if you already knew the answer, you wouldn’t be calculating it over and over! You would reuse the result from your previous calculation. That’s what memoization is. **Memoization means storing the result so you can use it next time instead of calculating the same thing again and again.**
 36 | 
 37 | In the below example, we call `memoizedGetChanceOfRain()` instead. This is a new function that we added which will check if we already have an answer — and if we do, it will return the previous answer instead of re-running `getChanceOfRain()`:
 38 | 
 39 | ```js
 40 | import { getChanceOfRain } from 'magic-weather-calculator';
 41 | 
 42 | let isCalculated = false;
 43 | let lastResult;
 44 | 
 45 | // We added this function!
 46 | function memoizedGetChanceOfRain() {
 47 |   if (isCalculated) {
 48 |     // No need to calculate it again.
 49 |     return lastResult;
 50 |   }
 51 |   // Gotta calculate it for the first time.
 52 |   let result = getChanceOfRain();
 53 |   // Remember it for the next time.
 54 |   lastResult = result;
 55 |   isCalculated = true;
 56 |   return result;
 57 | }
 58 | 
 59 | function showWeatherReport() {
 60 |   // Use the memoized function instead of the original function.
 61 |   let result = memoizedGetChanceOfRain();
 62 |   console.log("The chance of rain tomorrow is:", result);
 63 | }
 64 | ```
 65 | 
 66 | This makes every call to `showWeatherReport()` except the first one instant:
 67 | 
 68 | ```js
 69 | showWeatherReport(); // (!) Triggers the calculation
 70 | showWeatherReport(); // Uses the calculated result
 71 | showWeatherReport(); // Uses the calculated result
 72 | showWeatherReport(); // Uses the calculated result
 73 | ```
 74 | 
 75 | That’s what memoization is. When we say a function is “memoized”, it doesn’t mean we do something special to it from the JavaScript language perspective. It only means that we avoid calling it unnecessarily if we know its result won’t change.
 76 | 
 77 | ---
 78 | 
 79 | **You can stop reading here, if you want.** We’ll continue with a few extra details.
 80 | 
 81 | ---
 82 | 
 83 | ### Memoization and Parameters
 84 | 
 85 | Generally, memoization always follows the same scheme:
 86 | 
 87 | 1. Check if we already have a result.
 88 | 2. If yes, return it.
 89 | 3. If no, calculate the result and store it for the next time.
 90 | 
 91 | However, the actual implementation depends on our circumstances. For example, let’s say that the `getChanceOfRain` function takes a `city` parameter:
 92 | 
 93 | ```js
 94 | function showWeatherReport(city) {
 95 |   let result = getChanceOfRain(city); // Pass the city
 96 |   console.log("The chance of rain tomorrow is:", result);
 97 | }
 98 | ```
 99 | 
100 | If we naïvely memoized this function like we did before, we would introduce a bug:
101 | 
102 | ```js
103 | showWeatherReport('Tokyo');  // (!) Triggers the calculation
104 | showWeatherReport('London'); // Uses the calculated answer
105 | ```
106 | 
107 | Do you see the bug yet? The weather in Tokyo and London might be very different, so we can’t safely reuse the previous answer. This means that **when we memoize a function and reuse its result, we have to take its parameters into account too**.
108 | 
109 | #### Solution 1: Only Keep the Last Result
110 | 
111 | The easiest solution to our example is to remember both the result *and* the parameters for which that result was calculated. Here is how we could do it:
112 | 
113 | ```js
114 | import { getChanceOfRain } from 'magic-weather-calculator';
115 | 
116 | let lastCity;
117 | let lastResult;
118 | 
119 | function memoizedGetChanceOfRain(city) {
120 |   if (city === lastCity) { // Notice this check!
121 |     // Same parameters, so we can reuse the last result.
122 |     return lastResult;
123 |   }
124 |   // Either we're called for the first time,
125 |   // or we're called with different parameters.
126 |   // We have to perform the calculation.
127 |   let result = getChanceOfRain(city);
128 |   // Remember both the parameters and the result.
129 |   lastCity = city;
130 |   lastResult = result;
131 |   return result;
132 | }
133 | 
134 | function showWeatherReport(city) {
135 |   // Pass the parameters to the memoized function.
136 |   let result = memoizedGetChanceOfRain(city);
137 |   console.log("The chance of rain tomorrow is:", result);
138 | }
139 | ```
140 | 
141 | Notice how this example is subtly different from our original one. Instead of *always* returning the last result when it exists, we check `city === lastCity` first. If the city changes midway, we’re going to have to re-calculate the result again:
142 | 
143 | ```js
144 | showWeatherReport('Tokyo');  // (!) Triggers the calculation
145 | showWeatherReport('Tokyo');  // Uses the calculated result
146 | showWeatherReport('Tokyo');  // Uses the calculated result
147 | showWeatherReport('London'); // (!) Triggers the calculation
148 | showWeatherReport('London'); // Uses the calculated result
149 | ```
150 | 
151 | This fixes our bug, but it’s not always an optimal solution. In particular, if our `city` parameter changes on every function call, our memoization becomes useless:
152 | 
153 | ```js
154 | showWeatherReport('Tokyo');  // (!) Triggers the calculation
155 | showWeatherReport('London'); // (!) Triggers the calculation
156 | showWeatherReport('Tokyo');  // (!) Triggers the calculation
157 | showWeatherReport('London'); // (!) Triggers the calculation
158 | showWeatherReport('Tokyo');  // (!) Triggers the calculation
159 | ```
160 | 
161 | Whenever you add memoization, remember to check if it actually helps!
162 | 
163 | #### Solution 2: Keep Many Results
164 | 
165 | Another thing we could do is to keep *many* results instead of just the last one. Although we could define variables like `lastTokyoResult`, `lastLondonResult`, and so on, it is much easier to use a [`Map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) which lets us hold a result for every `city` value:
166 | 
167 | ```js
168 | // Remember the last result *for every city*.
169 | let resultsPerCity = new Map();
170 | 
171 | function memoizedGetChanceOfRain(city) {
172 |   if (resultsPerCity.has(city)) {
173 |     // We already have a result for this city.
174 |     return resultsPerCity.get(city);
175 |   }
176 |   // We're called for the first time for this city.
177 |   let result = getChanceOfRain(city);
178 |   // Remember the result for this city.
179 |   resultsPerCity.set(city, result);
180 |   return result;
181 | }
182 | 
183 | function showWeatherReport(city) {
184 |   // Pass the parameters to the memoized function.
185 |   let result = memoizedGetChanceOfRain(city);
186 |   console.log("The chance of rain tomorrow is:", result);
187 | }
188 | ```
189 | 
190 | This solution works great for our use case because it only ever calculates the result the first time it encounters a particular value of `city`. Every next call with the same `city` parameter will reuse the previously calculated result that we stored in our `Map`:
191 | 
192 | ```js
193 | showWeatherReport('Tokyo');  // (!) Triggers the calculation
194 | showWeatherReport('London'); // (!) Triggers the calculation
195 | showWeatherReport('Tokyo');  // Uses the calculated result
196 | showWeatherReport('London'); // Uses the calculated result
197 | showWeatherReport('Tokyo');  // Uses the calculated result
198 | showWeatherReport('Paris');  // (!) Triggers the calculation
199 | ```
200 | 
201 | However, this solution isn’t without its downsides too. **In particular, if we keep passing more different `city` values to our function, our `Map` will keep growing.**
202 | 
203 | So this solution trades faster performance for potentially unbounded memory growth. In the very worst cases, this can result in our browser tab crashing, especially if every result uses a significant part of memory (e.g. a DOM tree).
204 | 
205 | #### Other Solutions
206 | 
207 | There are many solutions on the spectrum between “keep only the last result” and “keep all results”. For example, you may keep results for the last N parameter values, which is known as an LRU, or a “least recently used”, cache. It’s nothing more than a Map with extra logic. You may also decide to *remove* past results after some time passes — similar to how browsers removes the cached assets after they expire. If our parameter were an object (rather than a string, like it is above), we could use [`WeakMap`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap) instead of `Map` in modern browsers. The benefit of `WeakMap` is that it would automatically “clean up” the entries when our object key is no longer accessible. Memoization is a flexible technique, and you can choose between many tradeoffs.
208 | 
209 | ### Memoization and Purity
210 | 
211 | We should note memoization isn’t always safe to do.
212 | 
213 | For example, imagine that the `getChanceOfRain` function from our magical npm package does not receive `city` as a parameter, but shows an input box to the user:
214 | 
215 | ```js
216 | // Inside the magical npm package
217 | function getChanceOfRain() {
218 |   // Show the input box!
219 |   let city = prompt('Where do you live?');
220 |   // ... calculation ...
221 | }
222 | 
223 | 
224 | // Our code
225 | function showWeatherReport() {
226 |   let result = getChanceOfRain();
227 |   console.log("The chance of rain tomorrow is:", result);
228 | }
229 | ```
230 | 
231 | If we call `showWeatherReport()` two times, we will see two input boxes. We could input different cities, and see different results in the console. But if we memoized the `getChanceOfRain` function, we would only see one input box! Each next call would keep returning the same result and wouldn’t let us enter a different city.
232 | 
233 | So memoization is only safe to do when the function is “pure” — that is, **if it only reads its parameters and doesn’t interact with the “outside world”.** With a pure function, it doesn’t matter whether you call it once or if you reuse its previous result.
234 | 
235 | This is why in a complex algorithm, it can be beneficial to separate the parts that *do* something from the parts that merely *calculate* something. The parts that calculate something can be pure, so they can be safely memoized and skipped on repeated calls. But the code that *does* something can’t be skipped without behavior changes:
236 | 
237 | ```js
238 | // If this function only calculates things,
239 | // we would call it "pure".
240 | // It is safe to memoize this function.
241 | function getChanceOfRain(city) {
242 |   // ... calculation ...
243 | }
244 | 
245 | 
246 | // This function is "impure" because
247 | // it shows a prompt to the user.
248 | function showWeatherReport() {
249 |   // The prompt is now here
250 |   let city = prompt('Where do you live?');
251 |   let result = getChanceOfRain(city);
252 |   console.log("The chance of rain tomorrow is:", result);
253 | }
254 | ```
255 | 
256 | Now it would be safe to memoize `getChanceOfRain` — because it takes `city` as a parameter rather than showing the input box to the user. In other words, it is pure. Concretely, it would mean that you would still see an input box on every call to `showWeatherReport`, but the calculation would be skipped if we had the result.
257 | 
258 | ### Reusable Memoization
259 | 
260 | If you want to memoize several functions, it can be annoying to repeat the same steps every time: add a variable, check if we have the result, store the result.
261 | 
262 | Luckily, we have a tool at our disposal — *functions!* — that lets us automate this. **We will use a function to “write” this repetitive memoization logic for us.**
263 | 
264 | Let’s take our first example that memoizes the last result:
265 | 
266 | ```js
267 | let isCalculated = false;
268 | let lastResult;
269 | 
270 | function memoizedGetChanceOfRain() {
271 |   if (isCalculated) {
272 |     return lastResult;
273 |   }
274 |   let result = getChanceOfRain();
275 |   lastResult = result;
276 |   isCalculated = true;
277 |   return result;
278 | }
279 | ```
280 | 
281 | Then let’s wrap it all (without any changes) in a function called `memoize`:
282 | 
283 | ```js
284 | function memoize() {
285 |   let isCalculated = false;
286 |   let lastResult;
287 | 
288 |   function memoizedGetChanceOfRain() {
289 |     if (isCalculated) {
290 |       return lastResult;
291 |     }
292 |     let result = getChanceOfRain();
293 |     lastResult = result;
294 |     isCalculated = true;
295 |     return result;
296 |   }
297 | }
298 | ```
299 | 
300 | We want to make this function more useful than just calculating the chance of rain. So instead of `getChanceOfRain`, we’ll add a function parameter that we’ll call `fn`:
301 | 
302 | ```js
303 | function memoize(fn) { // Declare the fn parameter
304 |   let isCalculated = false;
305 |   let lastResult;
306 | 
307 |   function memoizedGetChanceOfRain() {
308 |     if (isCalculated) {
309 |       return lastResult;
310 |     }
311 |     let result = fn(); // Call the passed function
312 |     lastResult = result;
313 |     isCalculated = true;
314 |     return result;
315 |   }
316 | }
317 | ```
318 | 
319 | Finally, let’s also rename `memoizedGetChanceOfRain` to `memoizedFn` and *return* it:
320 | 
321 | ```js
322 | function memoize(fn) {
323 |   let isCalculated = false;
324 |   let lastResult;
325 | 
326 |   return function memoizedFn() { // Return the generated function!
327 |     if (isCalculated) {
328 |       return lastResult;
329 |     }
330 |     let result = fn();
331 |     lastResult = result;
332 |     isCalculated = true;
333 |     return result;
334 |   }
335 | }
336 | ```
337 | 
338 | What we got as a result is a *reusable* function that does memoization for us.
339 | 
340 | Now our original example could shrink to this:
341 | 
342 | ```js
343 | import { getChanceOfRain } from 'magic-weather-calculator';
344 | 
345 | // Instead of writing it by hand, generate it.
346 | let memoizedGetChanceOfRain = memoize(getChanceOfRain);
347 | 
348 | function showWeatherReport() {
349 |   let result = memoizedGetChanceOfRain();
350 |   console.log("The chance of rain tomorrow is:", result);
351 | }
352 | ```
353 | 
354 | The `isCalculated` and `lastResult` variables still exist, but they are neatly tucked away inside the `memoize` function. In other words, they are part of a [closure](/closure). We can now use `memoize` anywhere, with each call to `memoize` independent from the others:
355 | 
356 | ```js
357 | import { getChanceOfRain, getNextEarthquake, getCosmicRaysProbability } from 'magic-weather-calculator';
358 | 
359 | // Each of these generated functions has its own lastResult and isCalculated.
360 | // It's like if we wrote each of them by hand, but shorter.
361 | let memoizedGetChanceOfRain = memoize(getChanceOfRain);
362 | let memoizedGetNextEarthquake = memoize(getNextEarthquake);
363 | let memoizedGetCosmicRaysProbability = memoize(getCosmicRaysProbability);
364 | ```
365 | 
366 | Here, the goal of `memoize` is to *generate a memoized version* of the function we provide to it, so that we don’t have to write each of them by hand every time.
367 | 
368 | A reusable `memoize` function like this exists in [Lodash](https://lodash.com/docs/4.17.15#memoize) and many other packages.
369 | 
370 | ### Recap
371 | 
372 | Now let’s quickly recap what we learned. Memoization (without “r”) is a way to make your program faster. It works when there is a section of code that executes many times, but that code only performs a calculation (in other words, it is “pure”) — so it is safe to reuse the previous result. There are different approaches to memoization: you can memoize only the last result, the last N results, or even all previous results. You should use your judgement to choose the approach that makes sense in each specific case. Generally speaking, it is not hard to implement memoization manually, but there are also packages that do that for you. Here is how Lodash [implements it](https://github.com/lodash/lodash/blob/master/memoize.js).
373 | 
374 | But at its core, memoization is about taking code like this:
375 | 
376 | ```js
377 | import { getChanceOfRain } from 'magic-weather-calculator';
378 | 
379 | function showWeatherReport() {
380 |   let result = getChanceOfRain();
381 |   console.log("The chance of rain tomorrow is:", result);
382 | }
383 | ```
384 | 
385 | And turning it into this:
386 | 
387 | ```js
388 | import { getChanceOfRain } from 'magic-weather-calculator';
389 | 
390 | let isCalculated = false;
391 | let lastResult;
392 | 
393 | function memoizedGetChanceOfRain() {
394 |   if (isCalculated) {
395 |     return lastResult;
396 |   }
397 |   let result = getChanceOfRain();
398 |   lastResult = result;
399 |   isCalculated = true;
400 |   return result;
401 | }
402 | 
403 | function showWeatherReport() {
404 |   let result = memoizedGetChanceOfRain();
405 |   console.log("The chance of rain tomorrow is:", result);
406 | }
407 | ```
408 | 
409 | Use memoization wisely and only where it brings concrete performance improvements. Otherwise, you’re adding complexity and potential bugs.
410 | 


--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | User-agent: *
2 | Disallow:
3 | 


--------------------------------------------------------------------------------
/styles/global.css:
--------------------------------------------------------------------------------
  1 | @import './syntax.css';
  2 | @import './markdown.css';
  3 | @import './nprogress.css';
  4 | @import './inter.css';
  5 | 
  6 | :root {
  7 |   /* Spacing */
  8 |   --gap-quarter: 0.25rem;
  9 |   --gap-half: 0.5rem;
 10 |   --gap: 1rem;
 11 |   --gap-double: 2rem;
 12 |   --small-gap: 4rem;
 13 |   --big-gap: 4rem;
 14 |   --main-content: 45rem;
 15 |   --radius: 8px;
 16 |   --inline-radius: 5px;
 17 | 
 18 |   /* Typography */
 19 |   --font-sans: 'Inter', -apple-system, BlinkMacSystemFont, Segoe UI, Roboto,
 20 |     Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
 21 |   --font-mono: 'SFMono-Regular', 'Consolas', 'Liberation Mono', 'Menlo',
 22 |     monospace;
 23 | 
 24 |   /* Transitions */
 25 |   --transition: 0.1s ease-in-out;
 26 |   --transition-slow: 0.3s ease-in-out;
 27 | }
 28 | 
 29 | :root {
 30 |   /* Dark Mode Colors */
 31 |   --bg: #131415;
 32 |   --fg: #fafbfc;
 33 |   --gray: #888;
 34 |   --light-gray: #444;
 35 |   --lighter-gray: #222;
 36 |   --lightest-gray: #1a1a1a;
 37 |   --article-color: #eaeaea;
 38 |   --header-bg: rgb(19, 20, 21);
 39 |   --gray-alpha: rgba(255, 255, 255, 0.5);
 40 |   --selection: rgba(255, 255, 255, 0.99);
 41 |   --accent: #ffe77a;
 42 |   --lighter-accent: #fff3bd;
 43 | 
 44 |   /* Syntax Highlighting */
 45 |   --token: var(--lighter-accent);
 46 |   --comment: #ccc;
 47 |   --keyword: var(--accent);
 48 |   --name: #fff;
 49 |   --highlight: #2e2e2e;
 50 | }
 51 | 
 52 | .light {
 53 |   --bg: #fff;
 54 |   --fg: #222;
 55 |   --gray: #666;
 56 |   --light-gray: #dedede;
 57 |   --lighter-gray: #f5f5f5;
 58 |   --lightest-gray: #fafafa;
 59 |   --article-color: #222;
 60 |   --header-bg: rgb(255, 255, 255);
 61 |   --gray-alpha: rgba(19, 20, 21, 0.5);
 62 |   --selection: rgba(0, 0, 0, 0.99);
 63 |   --accent: #0493d3;
 64 |   --lighter-accent: #1274a0;
 65 | 
 66 |   --token: var(--lighter-accent);
 67 |   --comment: #404040;
 68 |   --keyword: var(--accent);
 69 |   --name: #353535;
 70 |   --highlight: #eaeaea;
 71 | }
 72 | 
 73 | * {
 74 |   box-sizing: border-box;
 75 | }
 76 | 
 77 | ::selection {
 78 |   text-shadow: none;
 79 |   background: var(--selection);
 80 |   color: var(--bg);
 81 | }
 82 | 
 83 | html {
 84 |   -ms-text-size-adjust: 100%;
 85 |   -webkit-text-size-adjust: 100%;
 86 |   line-height: 1.5;
 87 | }
 88 | 
 89 | html,
 90 | body {
 91 |   padding: 0;
 92 |   margin: 0;
 93 |   font-size: 16px;
 94 |   background: var(--bg);
 95 |   color: var(--fg);
 96 |   font-display: block;
 97 |   font-feature-settings: 'calt' 'case' 'rlig';
 98 |   text-rendering: optimizeLegibility;
 99 |   -webkit-font-smoothing: antialiased;
100 |   -moz-osx-font-smoothing: grayscale;
101 | }
102 | 
103 | body {
104 |   min-height: 100vh;
105 |   font-family: var(--font-sans);
106 |   display: flex;
107 |   flex-direction: column;
108 |   transition: color 0.1s ease-in-out, background 0.1s ease-in-out;
109 | }
110 | 
111 | svg {
112 |   transition: fill 0.1s ease-in-out, stroke 0.1s ease-in-out;
113 | }
114 | 
115 | p,
116 | li {
117 |   letter-spacing: -0.33px;
118 |   font-size: 1.125rem;
119 | }
120 | 
121 | h1,
122 | h2,
123 | h3,
124 | h4,
125 | h5,
126 | h6 {
127 |   font-family: var(--font-sans);
128 |   font-weight: 600;
129 |   line-height: 1.75;
130 | }
131 | 
132 | h1 {
133 |   font-size: 2.5rem;
134 |   font-weight: 600;
135 |   line-height: 1.25;
136 |   letter-spacing: -0.89px;
137 | }
138 | 
139 | h2 {
140 |   font-size: 2rem;
141 |   letter-spacing: -0.69px;
142 | }
143 | 
144 | h3 {
145 |   font-size: 1.5rem;
146 |   letter-spacing: -0.47px;
147 | }
148 | 
149 | h4 {
150 |   font-size: 1.25rem;
151 |   letter-spacing: -0.33px;
152 | }
153 | 
154 | hr {
155 |   border: none;
156 |   border-bottom: 1px solid var(--light-gray);
157 |   transition: border-color 0.1s ease-in-out;
158 | }
159 | 
160 | blockquote {
161 |   font-style: italic;
162 |   margin: 0;
163 |   padding-left: 1rem;
164 |   border-left: 3px solid var(--light-gray);
165 |   transition: border-color var(--transition);
166 | }
167 | 
168 | button {
169 |   border: none;
170 |   padding: 0;
171 |   margin: 0;
172 |   line-height: inherit;
173 |   font-size: inherit;
174 | }
175 | 
176 | p a,
177 | a.reset {
178 |   outline: none;
179 |   color: inherit;
180 |   text-decoration: none;
181 |   transition: color var(--transition);
182 | }
183 | 
184 | p a:hover,
185 | p a:focus,
186 | p a:active,
187 | a.reset:hover,
188 | a.reset:focus {
189 |   color: var(--gray);
190 | }
191 | 
192 | pre,
193 | code {
194 |   font-family: var(--font-mono);
195 | }
196 | 
197 | .clamp {
198 |   display: -webkit-box;
199 |   -webkit-box-orient: vertical;
200 |   -webkit-line-clamp: 1;
201 |   overflow: hidden;
202 | }
203 | 
204 | .clamp-2 {
205 |   display: -webkit-box;
206 |   -webkit-box-orient: vertical;
207 |   -webkit-line-clamp: 2;
208 |   overflow: hidden;
209 | }
210 | 
211 | .flex {
212 |   display: flex;
213 | }
214 | 
215 | kbd {
216 |   font-family: var(--font-sans);
217 |   font-size: 1rem;
218 |   padding: 2px 7px;
219 |   font-weight: 600;
220 |   background: var(--lighter-gray);
221 |   border-radius: 5px;
222 | }
223 | 
224 | [id]::before {
225 |   content: '';
226 |   display: block;
227 |   height: 70px;
228 |   margin-top: -70px;
229 |   visibility: hidden;
230 | }
231 | 
232 | summary {
233 |   cursor: pointer;
234 |   outline: none;
235 | }
236 | 
237 | details {
238 |   border-radius: var(--radius);
239 |   background: var(--lightest-gray);
240 |   padding: 1rem;
241 |   border-radius: var(--radius);
242 | }
243 | 


--------------------------------------------------------------------------------
/styles/inter.css:
--------------------------------------------------------------------------------
  1 | /* latin */
  2 | @font-face {
  3 |   font-family: 'Inter';
  4 |   font-style: normal;
  5 |   font-weight: 100;
  6 |   font-display: block;
  7 |   src: url(https://assets.vercel.com/raw/upload/v1587415301/fonts/2/inter-var-latin.woff2)
  8 |     format('woff2');
  9 |   unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
 10 |     U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215,
 11 |     U+FEFF, U+FFFD;
 12 | }
 13 | @font-face {
 14 |   font-family: 'Inter';
 15 |   font-style: normal;
 16 |   font-weight: 200;
 17 |   font-display: block;
 18 |   src: url(https://assets.vercel.com/raw/upload/v1587415301/fonts/2/inter-var-latin.woff2)
 19 |     format('woff2');
 20 |   unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
 21 |     U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215,
 22 |     U+FEFF, U+FFFD;
 23 | }
 24 | @font-face {
 25 |   font-family: 'Inter';
 26 |   font-style: normal;
 27 |   font-weight: 300;
 28 |   font-display: block;
 29 |   src: url(https://assets.vercel.com/raw/upload/v1587415301/fonts/2/inter-var-latin.woff2)
 30 |     format('woff2');
 31 |   unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
 32 |     U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215,
 33 |     U+FEFF, U+FFFD;
 34 | }
 35 | @font-face {
 36 |   font-family: 'Inter';
 37 |   font-style: normal;
 38 |   font-weight: 400;
 39 |   font-display: block;
 40 |   src: url(https://assets.vercel.com/raw/upload/v1587415301/fonts/2/inter-var-latin.woff2)
 41 |     format('woff2');
 42 |   unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
 43 |     U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215,
 44 |     U+FEFF, U+FFFD;
 45 | }
 46 | @font-face {
 47 |   font-family: 'Inter';
 48 |   font-style: normal;
 49 |   font-weight: 500;
 50 |   font-display: block;
 51 |   src: url(https://assets.vercel.com/raw/upload/v1587415301/fonts/2/inter-var-latin.woff2)
 52 |     format('woff2');
 53 |   unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
 54 |     U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215,
 55 |     U+FEFF, U+FFFD;
 56 | }
 57 | @font-face {
 58 |   font-family: 'Inter';
 59 |   font-style: normal;
 60 |   font-weight: 600;
 61 |   font-display: block;
 62 |   src: url(https://assets.vercel.com/raw/upload/v1587415301/fonts/2/inter-var-latin.woff2)
 63 |     format('woff2');
 64 |   unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
 65 |     U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215,
 66 |     U+FEFF, U+FFFD;
 67 | }
 68 | @font-face {
 69 |   font-family: 'Inter';
 70 |   font-style: normal;
 71 |   font-weight: 700;
 72 |   font-display: block;
 73 |   src: url(https://assets.vercel.com/raw/upload/v1587415301/fonts/2/inter-var-latin.woff2)
 74 |     format('woff2');
 75 |   unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
 76 |     U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215,
 77 |     U+FEFF, U+FFFD;
 78 | }
 79 | @font-face {
 80 |   font-family: 'Inter';
 81 |   font-style: normal;
 82 |   font-weight: 800;
 83 |   font-display: block;
 84 |   src: url(https://assets.vercel.com/raw/upload/v1587415301/fonts/2/inter-var-latin.woff2)
 85 |     format('woff2');
 86 |   unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
 87 |     U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215,
 88 |     U+FEFF, U+FFFD;
 89 | }
 90 | @font-face {
 91 |   font-family: 'Inter';
 92 |   font-style: normal;
 93 |   font-weight: 900;
 94 |   font-display: block;
 95 |   src: url(https://assets.vercel.com/raw/upload/v1587415301/fonts/2/inter-var-latin.woff2)
 96 |     format('woff2');
 97 |   unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
 98 |     U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215,
 99 |     U+FEFF, U+FFFD;
100 | }
101 | 
102 | /* these other fonts are only used and downloaded when a special chars is shown
103 | in most cases, they are not downloaded at all */
104 | 
105 | /* latin-ext */
106 | @font-face {
107 |   font-family: 'Inter';
108 |   font-style: normal;
109 |   font-weight: 100;
110 |   font-display: swap;
111 |   src: url(https://assets.vercel.com/raw/upload/v1587418275/fonts/2/inter-var-latin-ext.woff2)
112 |     format('woff2');
113 |   unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB,
114 |     U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
115 | }
116 | @font-face {
117 |   font-family: 'Inter';
118 |   font-style: normal;
119 |   font-weight: 200;
120 |   font-display: swap;
121 |   src: url(https://assets.vercel.com/raw/upload/v1587418275/fonts/2/inter-var-latin-ext.woff2)
122 |     format('woff2');
123 |   unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB,
124 |     U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
125 | }
126 | @font-face {
127 |   font-family: 'Inter';
128 |   font-style: normal;
129 |   font-weight: 300;
130 |   font-display: swap;
131 |   src: url(https://assets.vercel.com/raw/upload/v1587418275/fonts/2/inter-var-latin-ext.woff2)
132 |     format('woff2');
133 |   unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB,
134 |     U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
135 | }
136 | @font-face {
137 |   font-family: 'Inter';
138 |   font-style: normal;
139 |   font-weight: 400;
140 |   font-display: swap;
141 |   src: url(https://assets.vercel.com/raw/upload/v1587418275/fonts/2/inter-var-latin-ext.woff2)
142 |     format('woff2');
143 |   unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB,
144 |     U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
145 | }
146 | @font-face {
147 |   font-family: 'Inter';
148 |   font-style: normal;
149 |   font-weight: 500;
150 |   font-display: swap;
151 |   src: url(https://assets.vercel.com/raw/upload/v1587418275/fonts/2/inter-var-latin-ext.woff2)
152 |     format('woff2');
153 |   unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB,
154 |     U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
155 | }
156 | @font-face {
157 |   font-family: 'Inter';
158 |   font-style: normal;
159 |   font-weight: 600;
160 |   font-display: swap;
161 |   src: url(https://assets.vercel.com/raw/upload/v1587418275/fonts/2/inter-var-latin-ext.woff2)
162 |     format('woff2');
163 |   unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB,
164 |     U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
165 | }
166 | @font-face {
167 |   font-family: 'Inter';
168 |   font-style: normal;
169 |   font-weight: 700;
170 |   font-display: swap;
171 |   src: url(https://assets.vercel.com/raw/upload/v1587418275/fonts/2/inter-var-latin-ext.woff2)
172 |     format('woff2');
173 |   unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB,
174 |     U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
175 | }
176 | @font-face {
177 |   font-family: 'Inter';
178 |   font-style: normal;
179 |   font-weight: 800;
180 |   font-display: swap;
181 |   src: url(https://assets.vercel.com/raw/upload/v1587418275/fonts/2/inter-var-latin-ext.woff2)
182 |     format('woff2');
183 |   unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB,
184 |     U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
185 | }
186 | @font-face {
187 |   font-family: 'Inter';
188 |   font-style: normal;
189 |   font-weight: 900;
190 |   font-display: swap;
191 |   src: url(https://assets.vercel.com/raw/upload/v1587418275/fonts/2/inter-var-latin-ext.woff2)
192 |     format('woff2');
193 |   unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB,
194 |     U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
195 | }
196 | 
197 | /* cyrillic */
198 | @font-face {
199 |   font-family: 'Inter';
200 |   font-style: normal;
201 |   font-weight: 100;
202 |   font-display: swap;
203 |   src: url(https://assets.vercel.com/raw/upload/v1587418276/fonts/2/inter-var-cyrillic.woff2)
204 |     format('woff2');
205 |   unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
206 | }
207 | @font-face {
208 |   font-family: 'Inter';
209 |   font-style: normal;
210 |   font-weight: 200;
211 |   font-display: swap;
212 |   src: url(https://assets.vercel.com/raw/upload/v1587418276/fonts/2/inter-var-cyrillic.woff2)
213 |     format('woff2');
214 |   unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
215 | }
216 | @font-face {
217 |   font-family: 'Inter';
218 |   font-style: normal;
219 |   font-weight: 300;
220 |   font-display: swap;
221 |   src: url(https://assets.vercel.com/raw/upload/v1587418276/fonts/2/inter-var-cyrillic.woff2)
222 |     format('woff2');
223 |   unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
224 | }
225 | @font-face {
226 |   font-family: 'Inter';
227 |   font-style: normal;
228 |   font-weight: 400;
229 |   font-display: swap;
230 |   src: url(https://assets.vercel.com/raw/upload/v1587418276/fonts/2/inter-var-cyrillic.woff2)
231 |     format('woff2');
232 |   unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
233 | }
234 | @font-face {
235 |   font-family: 'Inter';
236 |   font-style: normal;
237 |   font-weight: 500;
238 |   font-display: swap;
239 |   src: url(https://assets.vercel.com/raw/upload/v1587418276/fonts/2/inter-var-cyrillic.woff2)
240 |     format('woff2');
241 |   unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
242 | }
243 | @font-face {
244 |   font-family: 'Inter';
245 |   font-style: normal;
246 |   font-weight: 600;
247 |   font-display: swap;
248 |   src: url(https://assets.vercel.com/raw/upload/v1587418276/fonts/2/inter-var-cyrillic.woff2)
249 |     format('woff2');
250 |   unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
251 | }
252 | @font-face {
253 |   font-family: 'Inter';
254 |   font-style: normal;
255 |   font-weight: 700;
256 |   font-display: swap;
257 |   src: url(https://assets.vercel.com/raw/upload/v1587418276/fonts/2/inter-var-cyrillic.woff2)
258 |     format('woff2');
259 |   unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
260 | }
261 | @font-face {
262 |   font-family: 'Inter';
263 |   font-style: normal;
264 |   font-weight: 800;
265 |   font-display: swap;
266 |   src: url(https://assets.vercel.com/raw/upload/v1587418276/fonts/2/inter-var-cyrillic.woff2)
267 |     format('woff2');
268 |   unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
269 | }
270 | @font-face {
271 |   font-family: 'Inter';
272 |   font-style: normal;
273 |   font-weight: 900;
274 |   font-display: swap;
275 |   src: url(https://assets.vercel.com/raw/upload/v1587418276/fonts/2/inter-var-cyrillic.woff2)
276 |     format('woff2');
277 |   unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
278 | }
279 | 
280 | /* cyrillic-ext */
281 | @font-face {
282 |   font-family: 'Inter';
283 |   font-style: swap;
284 |   font-weight: 100;
285 |   font-display: block;
286 |   src: url(https://assets.vercel.com/raw/upload/v1587418127/fonts/2/inter-var-cyrillic-ext.woff2)
287 |     format('woff2');
288 |   unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F,
289 |     U+FE2E-FE2F;
290 | }
291 | @font-face {
292 |   font-family: 'Inter';
293 |   font-style: swap;
294 |   font-weight: 200;
295 |   font-display: block;
296 |   src: url(https://assets.vercel.com/raw/upload/v1587418127/fonts/2/inter-var-cyrillic-ext.woff2)
297 |     format('woff2');
298 |   unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F,
299 |     U+FE2E-FE2F;
300 | }
301 | @font-face {
302 |   font-family: 'Inter';
303 |   font-style: swap;
304 |   font-weight: 300;
305 |   font-display: block;
306 |   src: url(https://assets.vercel.com/raw/upload/v1587418127/fonts/2/inter-var-cyrillic-ext.woff2)
307 |     format('woff2');
308 |   unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F,
309 |     U+FE2E-FE2F;
310 | }
311 | @font-face {
312 |   font-family: 'Inter';
313 |   font-style: swap;
314 |   font-weight: 400;
315 |   font-display: block;
316 |   src: url(https://assets.vercel.com/raw/upload/v1587418127/fonts/2/inter-var-cyrillic-ext.woff2)
317 |     format('woff2');
318 |   unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F,
319 |     U+FE2E-FE2F;
320 | }
321 | @font-face {
322 |   font-family: 'Inter';
323 |   font-style: swap;
324 |   font-weight: 500;
325 |   font-display: block;
326 |   src: url(https://assets.vercel.com/raw/upload/v1587418127/fonts/2/inter-var-cyrillic-ext.woff2)
327 |     format('woff2');
328 |   unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F,
329 |     U+FE2E-FE2F;
330 | }
331 | @font-face {
332 |   font-family: 'Inter';
333 |   font-style: swap;
334 |   font-weight: 600;
335 |   font-display: block;
336 |   src: url(https://assets.vercel.com/raw/upload/v1587418127/fonts/2/inter-var-cyrillic-ext.woff2)
337 |     format('woff2');
338 |   unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F,
339 |     U+FE2E-FE2F;
340 | }
341 | @font-face {
342 |   font-family: 'Inter';
343 |   font-style: swap;
344 |   font-weight: 700;
345 |   font-display: block;
346 |   src: url(https://assets.vercel.com/raw/upload/v1587418127/fonts/2/inter-var-cyrillic-ext.woff2)
347 |     format('woff2');
348 |   unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F,
349 |     U+FE2E-FE2F;
350 | }
351 | @font-face {
352 |   font-family: 'Inter';
353 |   font-style: swap;
354 |   font-weight: 800;
355 |   font-display: block;
356 |   src: url(https://assets.vercel.com/raw/upload/v1587418127/fonts/2/inter-var-cyrillic-ext.woff2)
357 |     format('woff2');
358 |   unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F,
359 |     U+FE2E-FE2F;
360 | }
361 | @font-face {
362 |   font-family: 'Inter';
363 |   font-style: swap;
364 |   font-weight: 900;
365 |   font-display: block;
366 |   src: url(https://assets.vercel.com/raw/upload/v1587418127/fonts/2/inter-var-cyrillic-ext.woff2)
367 |     format('woff2');
368 |   unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F,
369 |     U+FE2E-FE2F;
370 | }
371 | 
372 | /* greek */
373 | @font-face {
374 |   font-family: 'Inter';
375 |   font-style: normal;
376 |   font-weight: 100;
377 |   font-display: swap;
378 |   src: url(https://assets.vercel.com/raw/upload/v1587418276/fonts/2/inter-var-greek.woff2)
379 |     format('woff2');
380 |   unicode-range: U+0370-03FF;
381 | }
382 | @font-face {
383 |   font-family: 'Inter';
384 |   font-style: normal;
385 |   font-weight: 200;
386 |   font-display: swap;
387 |   src: url(https://assets.vercel.com/raw/upload/v1587418276/fonts/2/inter-var-greek.woff2)
388 |     format('woff2');
389 |   unicode-range: U+0370-03FF;
390 | }
391 | @font-face {
392 |   font-family: 'Inter';
393 |   font-style: normal;
394 |   font-weight: 300;
395 |   font-display: swap;
396 |   src: url(https://assets.vercel.com/raw/upload/v1587418276/fonts/2/inter-var-greek.woff2)
397 |     format('woff2');
398 |   unicode-range: U+0370-03FF;
399 | }
400 | @font-face {
401 |   font-family: 'Inter';
402 |   font-style: normal;
403 |   font-weight: 400;
404 |   font-display: swap;
405 |   src: url(https://assets.vercel.com/raw/upload/v1587418276/fonts/2/inter-var-greek.woff2)
406 |     format('woff2');
407 |   unicode-range: U+0370-03FF;
408 | }
409 | @font-face {
410 |   font-family: 'Inter';
411 |   font-style: normal;
412 |   font-weight: 500;
413 |   font-display: swap;
414 |   src: url(https://assets.vercel.com/raw/upload/v1587418276/fonts/2/inter-var-greek.woff2)
415 |     format('woff2');
416 |   unicode-range: U+0370-03FF;
417 | }
418 | @font-face {
419 |   font-family: 'Inter';
420 |   font-style: normal;
421 |   font-weight: 600;
422 |   font-display: swap;
423 |   src: url(https://assets.vercel.com/raw/upload/v1587418276/fonts/2/inter-var-greek.woff2)
424 |     format('woff2');
425 |   unicode-range: U+0370-03FF;
426 | }
427 | @font-face {
428 |   font-family: 'Inter';
429 |   font-style: normal;
430 |   font-weight: 700;
431 |   font-display: swap;
432 |   src: url(https://assets.vercel.com/raw/upload/v1587418276/fonts/2/inter-var-greek.woff2)
433 |     format('woff2');
434 |   unicode-range: U+0370-03FF;
435 | }
436 | @font-face {
437 |   font-family: 'Inter';
438 |   font-style: normal;
439 |   font-weight: 800;
440 |   font-display: swap;
441 |   src: url(https://assets.vercel.com/raw/upload/v1587418276/fonts/2/inter-var-greek.woff2)
442 |     format('woff2');
443 |   unicode-range: U+0370-03FF;
444 | }
445 | @font-face {
446 |   font-family: 'Inter';
447 |   font-style: normal;
448 |   font-weight: 900;
449 |   font-display: swap;
450 |   src: url(https://assets.vercel.com/raw/upload/v1587418276/fonts/2/inter-var-greek.woff2)
451 |     format('woff2');
452 |   unicode-range: U+0370-03FF;
453 | }
454 | 
455 | /* greek-ext */
456 | @font-face {
457 |   font-family: 'Inter';
458 |   font-style: normal;
459 |   font-weight: 100;
460 |   font-display: swap;
461 |   src: url(https://assets.vercel.com/raw/upload/v1587418275/fonts/2/inter-var-greek-ext.woff2)
462 |     format('woff2');
463 |   unicode-range: U+1F00-1FFF;
464 | }
465 | @font-face {
466 |   font-family: 'Inter';
467 |   font-style: normal;
468 |   font-weight: 200;
469 |   font-display: swap;
470 |   src: url(https://assets.vercel.com/raw/upload/v1587418275/fonts/2/inter-var-greek-ext.woff2)
471 |     format('woff2');
472 |   unicode-range: U+1F00-1FFF;
473 | }
474 | @font-face {
475 |   font-family: 'Inter';
476 |   font-style: normal;
477 |   font-weight: 300;
478 |   font-display: swap;
479 |   src: url(https://assets.vercel.com/raw/upload/v1587418275/fonts/2/inter-var-greek-ext.woff2)
480 |     format('woff2');
481 |   unicode-range: U+1F00-1FFF;
482 | }
483 | @font-face {
484 |   font-family: 'Inter';
485 |   font-style: normal;
486 |   font-weight: 400;
487 |   font-display: swap;
488 |   src: url(https://assets.vercel.com/raw/upload/v1587418275/fonts/2/inter-var-greek-ext.woff2)
489 |     format('woff2');
490 |   unicode-range: U+1F00-1FFF;
491 | }
492 | @font-face {
493 |   font-family: 'Inter';
494 |   font-style: normal;
495 |   font-weight: 500;
496 |   font-display: swap;
497 |   src: url(https://assets.vercel.com/raw/upload/v1587418275/fonts/2/inter-var-greek-ext.woff2)
498 |     format('woff2');
499 |   unicode-range: U+1F00-1FFF;
500 | }
501 | @font-face {
502 |   font-family: 'Inter';
503 |   font-style: normal;
504 |   font-weight: 600;
505 |   font-display: swap;
506 |   src: url(https://assets.vercel.com/raw/upload/v1587418275/fonts/2/inter-var-greek-ext.woff2)
507 |     format('woff2');
508 |   unicode-range: U+1F00-1FFF;
509 | }
510 | @font-face {
511 |   font-family: 'Inter';
512 |   font-style: normal;
513 |   font-weight: 700;
514 |   font-display: swap;
515 |   src: url(https://assets.vercel.com/raw/upload/v1587418275/fonts/2/inter-var-greek-ext.woff2)
516 |     format('woff2');
517 |   unicode-range: U+1F00-1FFF;
518 | }
519 | @font-face {
520 |   font-family: 'Inter';
521 |   font-style: normal;
522 |   font-weight: 800;
523 |   font-display: swap;
524 |   src: url(https://assets.vercel.com/raw/upload/v1587418275/fonts/2/inter-var-greek-ext.woff2)
525 |     format('woff2');
526 |   unicode-range: U+1F00-1FFF;
527 | }
528 | @font-face {
529 |   font-family: 'Inter';
530 |   font-style: normal;
531 |   font-weight: 900;
532 |   font-display: swap;
533 |   src: url(https://assets.vercel.com/raw/upload/v1587418275/fonts/2/inter-var-greek-ext.woff2)
534 |     format('woff2');
535 |   unicode-range: U+1F00-1FFF;
536 | }
537 | 
538 | /* vietnamese */
539 | @font-face {
540 |   font-family: 'Inter';
541 |   font-style: normal;
542 |   font-weight: 100;
543 |   font-display: swap;
544 |   src: url(https://assets.vercel.com/raw/upload/v1587418275/fonts/2/inter-var-vietnamese.woff2)
545 |     format('woff2');
546 |   unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1,
547 |     U+01AF-01B0, U+1EA0-1EF9, U+20AB;
548 | }
549 | @font-face {
550 |   font-family: 'Inter';
551 |   font-style: normal;
552 |   font-weight: 200;
553 |   font-display: swap;
554 |   src: url(https://assets.vercel.com/raw/upload/v1587418275/fonts/2/inter-var-vietnamese.woff2)
555 |     format('woff2');
556 |   unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1,
557 |     U+01AF-01B0, U+1EA0-1EF9, U+20AB;
558 | }
559 | @font-face {
560 |   font-family: 'Inter';
561 |   font-style: normal;
562 |   font-weight: 300;
563 |   font-display: swap;
564 |   src: url(https://assets.vercel.com/raw/upload/v1587418275/fonts/2/inter-var-vietnamese.woff2)
565 |     format('woff2');
566 |   unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1,
567 |     U+01AF-01B0, U+1EA0-1EF9, U+20AB;
568 | }
569 | @font-face {
570 |   font-family: 'Inter';
571 |   font-style: normal;
572 |   font-weight: 400;
573 |   font-display: swap;
574 |   src: url(https://assets.vercel.com/raw/upload/v1587418275/fonts/2/inter-var-vietnamese.woff2)
575 |     format('woff2');
576 |   unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1,
577 |     U+01AF-01B0, U+1EA0-1EF9, U+20AB;
578 | }
579 | @font-face {
580 |   font-family: 'Inter';
581 |   font-style: normal;
582 |   font-weight: 500;
583 |   font-display: swap;
584 |   src: url(https://assets.vercel.com/raw/upload/v1587418275/fonts/2/inter-var-vietnamese.woff2)
585 |     format('woff2');
586 |   unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1,
587 |     U+01AF-01B0, U+1EA0-1EF9, U+20AB;
588 | }
589 | @font-face {
590 |   font-family: 'Inter';
591 |   font-style: normal;
592 |   font-weight: 600;
593 |   font-display: swap;
594 |   src: url(https://assets.vercel.com/raw/upload/v1587418275/fonts/2/inter-var-vietnamese.woff2)
595 |     format('woff2');
596 |   unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1,
597 |     U+01AF-01B0, U+1EA0-1EF9, U+20AB;
598 | }
599 | @font-face {
600 |   font-family: 'Inter';
601 |   font-style: normal;
602 |   font-weight: 700;
603 |   font-display: swap;
604 |   src: url(https://assets.vercel.com/raw/upload/v1587418275/fonts/2/inter-var-vietnamese.woff2)
605 |     format('woff2');
606 |   unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1,
607 |     U+01AF-01B0, U+1EA0-1EF9, U+20AB;
608 | }
609 | @font-face {
610 |   font-family: 'Inter';
611 |   font-style: normal;
612 |   font-weight: 800;
613 |   font-display: swap;
614 |   src: url(https://assets.vercel.com/raw/upload/v1587418275/fonts/2/inter-var-vietnamese.woff2)
615 |     format('woff2');
616 |   unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1,
617 |     U+01AF-01B0, U+1EA0-1EF9, U+20AB;
618 | }
619 | @font-face {
620 |   font-family: 'Inter';
621 |   font-style: normal;
622 |   font-weight: 900;
623 |   font-display: swap;
624 |   src: url(https://assets.vercel.com/raw/upload/v1587418275/fonts/2/inter-var-vietnamese.woff2)
625 |     format('woff2');
626 |   unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1,
627 |     U+01AF-01B0, U+1EA0-1EF9, U+20AB;
628 | }
629 | 


--------------------------------------------------------------------------------
/styles/markdown.css:
--------------------------------------------------------------------------------
  1 | article {
  2 |   max-width: var(--main-content);
  3 |   margin: 0 auto;
  4 |   line-height: 1.9;
  5 | }
  6 | 
  7 | article > * + * {
  8 |   margin-top: 2em;
  9 | }
 10 | 
 11 | article p {
 12 |   color: var(--article-color);
 13 | }
 14 | 
 15 | article img {
 16 |   max-width: 100%;
 17 |   /* width: var(--main-content); */
 18 |   width: auto;
 19 |   margin: auto;
 20 |   display: block;
 21 |   border-radius: var(--radius);
 22 | }
 23 | 
 24 | /* Lists */
 25 | 
 26 | article ul {
 27 |   padding: 0;
 28 |   list-style-position: inside;
 29 |   list-style-type: circle;
 30 | }
 31 | 
 32 | article ol {
 33 |   padding: 0;
 34 |   list-style-position: inside;
 35 | }
 36 | 
 37 | article ul li.reset {
 38 |   display: flex;
 39 |   align-items: flex-start;
 40 | 
 41 |   list-style-type: none;
 42 |   margin-left: -0.5rem;
 43 | }
 44 | 
 45 | article ul li.reset .check {
 46 |   display: flex;
 47 |   align-items: center;
 48 |   margin-right: 0.51rem;
 49 | }
 50 | 
 51 | /* Checkbox */
 52 | 
 53 | input[type='checkbox'] {
 54 |   vertical-align: middle;
 55 |   appearance: none;
 56 |   display: inline-block;
 57 |   background-origin: border-box;
 58 |   user-select: none;
 59 |   flex-shrink: 0;
 60 |   height: 1rem;
 61 |   width: 1rem;
 62 |   background-color: var(--bg);
 63 |   color: var(--fg);
 64 |   border: 1px solid var(--fg);
 65 |   border-radius: 3px;
 66 | }
 67 | 
 68 | input[type='checkbox']:checked {
 69 |   background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='black' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M5.707 7.293a1 1 0 0 0-1.414 1.414l2 2a1 1 0 0 0 1.414 0l4-4a1 1 0 0 0-1.414-1.414L7 8.586 5.707 7.293z'/%3e%3c/svg%3e");
 70 |   border-color: transparent;
 71 |   background-color: currentColor;
 72 |   background-size: 100% 100%;
 73 |   background-position: center;
 74 |   background-repeat: no-repeat;
 75 | }
 76 | 
 77 | html.light input[type='checkbox']:checked {
 78 |   background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M5.707 7.293a1 1 0 0 0-1.414 1.414l2 2a1 1 0 0 0 1.414 0l4-4a1 1 0 0 0-1.414-1.414L7 8.586 5.707 7.293z'/%3e%3c/svg%3e");
 79 | }
 80 | 
 81 | input[type='checkbox']:focus {
 82 |   outline: none;
 83 |   box-shadow: 0 0 0 2px var(--gray);
 84 |   border-color: var(--fg);
 85 | }
 86 | 
 87 | /* Code Snippets */
 88 | 
 89 | .token-line:not(:last-child) {
 90 |   min-height: 1.4rem;
 91 | }
 92 | 
 93 | article pre {
 94 |   overflow-x: auto;
 95 |   background: var(--lightest-gray);
 96 |   border-radius: var(--inline-radius);
 97 |   line-height: 1.8;
 98 |   padding: 1rem;
 99 |   font-size: 0.875rem;
100 | }
101 | 
102 | /* Linkable Headers */
103 | 
104 | .header-link {
105 |   color: inherit;
106 |   text-decoration: none;
107 | }
108 | 
109 | .header-link::after {
110 |   opacity: 0;
111 |   content: '#';
112 |   margin-left: var(--gap-half);
113 |   color: var(--gray);
114 | }
115 | 
116 | .header-link:hover::after {
117 |   opacity: 1;
118 | }
119 | 


--------------------------------------------------------------------------------
/styles/nprogress.css:
--------------------------------------------------------------------------------
 1 | #nprogress {
 2 |   pointer-events: none;
 3 | }
 4 | 
 5 | #nprogress .bar {
 6 |   position: fixed;
 7 |   z-index: 2000;
 8 |   top: 0;
 9 |   left: 0;
10 |   width: 100%;
11 |   height: 5px;
12 |   background: var(--fg);
13 | }
14 | 
15 | #nprogress::after {
16 |   content: '';
17 |   position: fixed;
18 |   top: 0;
19 |   left: 0;
20 |   width: 100%;
21 |   height: 5px;
22 |   background: transparent;
23 | }
24 | 


--------------------------------------------------------------------------------
/styles/syntax.css:
--------------------------------------------------------------------------------
 1 | .keyword {
 2 |   font-weight: bold;
 3 |   color: var(--keyword);
 4 | }
 5 | 
 6 | .token.operator,
 7 | .token.punctuation,
 8 | .token.string,
 9 | .token.number,
10 | .token.builtin,
11 | .token.variable {
12 |   color: var(--token);
13 | }
14 | 
15 | .token.comment {
16 |   color: var(--comment);
17 | }
18 | 
19 | .token.class-name,
20 | .token.function,
21 | .token.tag,
22 | .token.attr-name {
23 |   color: var(--name);
24 | }
25 | 


--------------------------------------------------------------------------------