/vanilla-vercel-clone

๐Ÿ“ฆ vanilla js๋กœ vercel ํŽ˜์ด์ง€ ์ผ๋ถ€๋ฅผ ํด๋†ˆ์ฝ”๋”ฉ ํ•ด๋ด…๋‹ˆ๋‹ค!

Primary LanguageJavaScript

Vercel Clone / 7์ผ

์ค‘์ ์ ์ธ ๊ณ ๋ฏผ ์‚ฌํ•ญ ๐Ÿง

vanilla js & CSS๋งŒ์„ ์‚ฌ์šฉํ•˜๋ฉด์„œ ๋Š๊ปด์ง€๋Š” ๋ถˆํŽธํ•จ ๊ฐœ์„ ํ•ด๋ณด๊ธฐ

  1. ์ค‘๋ณต๋˜๋Š” html ๋งˆํฌ์—…์˜ ๋ฌธ์ œ์ 
  2. ์ ˆ์ฐจ์  ํ”„๋กœ๊ทธ๋ž˜๋ฐ์˜ ๋ฌธ์ œ์ 
  3. ์ „์—ญ์  ์Šคํƒ€์ผ๋ง๊ณผ ์ค‘๋ณต class naming์˜ ๋ฌธ์ œ์ 
  4. ๋ชจ๋“ ๊ฒƒ์„ ํ—ˆ์šฉํ•ด์ฃผ๋Š” ์ง€๋‚˜์น˜๊ฒŒ ์นœ์ ˆํ•œ js

ํ•ด๊ฒฐ ๋ฐฉ๋ฒ• ๐ŸŽ

์ค‘๋ณต๊ณผ ์ ˆ์ฐจํ˜• ํ”„๋กœ๊ทธ๋ž˜๋ฐ โžก๏ธ ์„ ์–ธํ˜• ์ปดํฌ๋„ŒํŠธ

  1. Component class

์„ ์–ธํ˜• ์ปดํฌ๋„ŒํŠธ

const Container = (children) =>
    new Component({
        template: `
            <div class="${style.box}">
                ${children}
            </div>`,
    }).html()

์ปดํฌ๋„ŒํŠธ ํ™œ์šฉ ๋ฐ ์žฌ์‚ฌ์šฉ

import { Container } from "์–ด๋””์„ ๊ฐ€"

const Section = () =>
    new Component({
        template: `
            <div>
                ${Container(`
                    <button>์•ˆ๋…•?</button>
                `)}
            </div>
            `,
    }).html()
  1. EventListener class

์ปดํฌ๋„ŒํŠธ์— ๋ช…์‹œ์ ์œผ๋กœ ์ด๋ฒคํŠธ ๋ถ€์ฐฉ ๋ฐ ์ œ๊ฑฐ

const Container = (children) => {
    const clickHandler = (e) => {
        console.log(e.clientX)
    }

    return new Component({
        template: `
            <div class="${style.box}">
                ${children}
            </div>`,
    })
        .addEvent((target) => ({
            type: "click",
            handler: clickHandler,
        }))
        .removeEvent((target) => ({
            type: "click",
            handler: clickHandler,
        }))
}
  1. atom state management

๋ช…์‹œ์ ์ธ ๋ณ€์ˆ˜๊ด€๋ฆฌ์˜ ์žฅ์ 

const Counter = () => {
    const [count, setCount] = atom(0)
    const increase = () => {
        setCount(count() + 1)
    }

    return new Component({
        template: `
            <button>
                Counter is ${count()}
            </button>`,
    })
        .addEvent(() => ({
            type: "click",
            handler: increase,
        }))
        .render()
}

์Šคํƒ€์ผ๋ง โžก๏ธ CSS module์„ ํ™œ์šฉํ•œ scoped CSS

์ปดํฌ๋„ŒํŠธ ํŒŒ์ผ์„ ์ปดํฌ๋„ŒํŠธ ๋ฐ ์Šคํƒ€์ผ๋กœ ๊ตฌ๋ถ„ํ•˜๊ธฐ

๐Ÿ“ฆmy-component
 โ”ฃ ๐Ÿ“œindex.js
 โ”— ๐Ÿ’„index.module.css

์ง€๋‚˜์นœ ์ž์œ  โžก๏ธ jsdoc๊ณผ jsconfig.json์œผ๋กœ ํƒ„์••

๋“ ๋“ ํ•œ ๋ฌด๊ธฐ ์ค€๋น„ jsconfig.json

{
    "compilerOptions": {
        "strict": true,
        "allowJs": true,
        "checkJs": true,
        "noEmit": true,
        "module": "NodeNext",
        "moduleResolution": "NodeNext",
        "typeRoots": ["./node_modules/@types"],
        "forceConsistentCasingInFileNames": true
    },
    "include": ["src"],
    "exclude": ["node_modules", "dist"]
}

๊ธฐ๋งฅํžŒ jsdoc๊ณผ ์ž ์ž ํ•ด์ง„ js

/**@type string */
let ๋ฐ˜๋“œ์‹œ_์ŠคํŠธ๋ง = "์ด๊ฑฐ ๋ฐ˜๋“œ์‹œ ๊ธ€์ž์—์šฉ"

// โŒ ๊ฐ€๋Šฅ์€ ํ•˜์ง€๋งŒ, ์˜ค๋ฅ˜ ๋งค์„ธ์ง€๊ฐ€ ๋œจ๋Š” ๋งค์ง
๋ฐ˜๋“œ์‹œ_์ŠคํŠธ๋ง = 1

๊ตฌํ˜„์‚ฌํ•ญ โœ…

  1. ๋งˆ์šฐ์Šค hover์‹œ ์ขŒํ‘œ์—๋”ฐ๋ผ ๋™์ ์œผ๋กœ ๋ฐ˜์‘ํ•˜๋Š” ์นด๋“œ
  2. radial gradient์™€ blur api๋ฅผ ์‚ฌ์šฉํ•œ ํšจ๊ณผ
  3. ๋ฒ„ํŠผ ring ํšŒ์ „
  4. gradient ํ…์ŠคํŠธ ๋ณ€ํ˜• ๋ฐ ์ „ํ™˜

๊ตฌํ˜„๊ฒฐ๊ณผ ๐ŸŽ‰

์ตœ์ดˆ ๋กœ๋”ฉ ํŽ˜์ด์ง€!

์ข‹์•˜๋˜ ์  โœ…

  1. ์„ ์–ธ์ ์ด๋ฉฐ ์žฌ์‚ฌ์šฉ์„ฑ์ด ๋†’์€ ํ•จ์ˆ˜์ปดํฌ๋„ŒํŠธ ๊ธฐ๋ฐ˜์œผ๋กœ ์ œ์ž‘ํ•ด ๋งŽ์€ ์ค‘๋ณต์„ ์ค„์ผ ์ˆ˜ ์žˆ์—ˆ์Œ
  2. ๋ฐ”๋‹๋ผ js์˜ DOM api์™€ ํ™œ์šฉ๋ฒ•์— ๋Œ€ํ•ด ๊นŠ๊ฒŒ ํ•™์Šตํ•  ์ˆ˜ ์žˆ์—ˆ์Œ
  3. reactivness๋ฅผ ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉํ•˜๋Š” ์›นํ”„๋ ˆ์ž„์›Œํฌ๋“ค์˜ ์ „๋žต(compile / reactiveness / v-dom & diff)์˜ ํ•„์š”์„ฑ์„ ๋Š๋‚Œ

๊ฐœ์„ ํ•  ์  ๐Ÿ”ธ

  1. reactiveness๊ฐ€ ์—†์Œ. state๋ณ€๊ฒฝ์„ ๊ฐ์ง€ํ•˜๊ณ  ์žฌ๋ Œ๋”๋งํ•˜๋Š” ๋กœ์ง์ด ์—†๊ธฐ์— ์ง์ ‘ DOM api๋ฅผ ํ˜ธ์ถœํ•˜๊ฑฐ๋‚˜ CSS variable๋ฅผ ์‚ฌ์šฉํ•ด์•ผํ•œ๋‹ค๋Š” ๋‹จ์ ์ด ์žˆ์Œ

    Proxy๋ฅผ ์ด์šฉํ•œ ๋ณ€์ˆ˜ ๊ตฌ๋…๊ณผ ๋ Œ๋”๋ง ๋“ฑ์˜ ๋ฐฉ๋ฒ•

  2. string์„ HTML๋กœ ์‚ฌ์šฉํ•˜๋ฏ€๋กœ jsx์™€ ๊ฐ™์€ ์ง๊ด€์„ฑ๊ณผ ๊ฐœ๋ฐœ ํŽธ์˜์„ฑ์ด ์—†์Œ.

  3. render()ํ˜ธ์ถœ์‹œ parent๊ฐ€ ์กด์žฌํ•˜๋ฉฐ, event๋“ฑ์ด ๋ถ€์ฐฉ๋œ ๋ฐ˜์‘์„ฑ์ด ์žˆ๋Š” ์ปดํฌ๋„ŒํŠธ๋Š” ์›ํ•˜๋Š” ์œ„์น˜์— ๋ Œ๋”๋งํ•˜๊ธฐ๊ฐ€ ๊นŒ๋‹ค๋กœ์›€

๋ Œ๋”๋ง ์œ„์น˜ ์ง€์ •ํ•˜๊ธฐ โœ…

const StaticParent = () => new Component({
    template: "<div id="์—ฌ๊ธฐ์—-๋ Œ๋”๋ง">{...}</div>"
})

const ReactiveComponent = () => {...}

// โŒ DOM-tree์—์„œ id์š”์†Œ๋ฅผ ํƒ์ƒ‰ ๋ถˆ๊ฐ€๋Šฅ
ReactiveComponent.render("์—ฌ๊ธฐ์—-๋ Œ๋”๋ง")

StaticParent.render()

// โœ… DOM-tree์—์„œ id์š”์†Œ๋ฅผ ํƒ์ƒ‰ ๊ฐ€๋Šฅ
ReactiveComponent.render("์—ฌ๊ธฐ์—-๋ Œ๋”๋ง")

๋ Œ๋”๋ง ์ง์ ‘ ํ•˜๊ธฐ โŒ

const ReactiveComponent = () => {...}

const StaticParent = () => new Component({
    // โŒ html ๋ Œ๋”๋ง ๋ถˆ๊ฐ€
    template: `${ReactiveComponent.render()}`
    template: `${ReactiveComponent.html()}`
})