├── .nvmrc ├── gatsby ├── gatsby-ssr.ts ├── gatsby-browser.ts ├── gatsby-node.ts └── gatsby-config.ts ├── netlify.toml ├── icon.png ├── src ├── @primer │ └── gatsby-theme-doctocat │ │ ├── primer-nav.yml │ │ ├── images │ │ └── favicon.png │ │ ├── components │ │ ├── Logo.tsx │ │ ├── paragraph.js │ │ ├── caption.js │ │ ├── image.js │ │ ├── container.js │ │ ├── wrap-page-element.js │ │ ├── dark-button.js │ │ ├── horizontal-rule.js │ │ ├── image-container.js │ │ ├── inline-code.js │ │ ├── source-link.js │ │ ├── list.js │ │ ├── live-preview-wrapper.js │ │ ├── blockquote.js │ │ ├── description-list.js │ │ ├── hero.js │ │ ├── dark-text-input.js │ │ ├── table.js │ │ ├── status-label.js │ │ ├── note.js │ │ ├── head.tsx │ │ ├── page-footer.js │ │ ├── table-of-contents.js │ │ ├── clipboard-copy.js │ │ ├── skip-link.js │ │ ├── hero-layout.js │ │ ├── nav-dropdown.js │ │ ├── frame.js │ │ ├── do-dont.js │ │ ├── wrap-root-element.js │ │ ├── search-results.js │ │ ├── __tests__ │ │ │ ├── page-footer.test.js │ │ │ └── contributors.test.js │ │ ├── sidebar.js │ │ ├── details.js │ │ ├── drawer.js │ │ ├── code.js │ │ ├── contributors.js │ │ ├── nav-items.js │ │ ├── heading.js │ │ ├── search.js │ │ ├── live-code.js │ │ ├── header.tsx │ │ ├── nav-drawer.js │ │ ├── mobile-search.js │ │ └── layout.tsx │ │ ├── live-code-scope.js │ │ ├── use-site-metadata.ts │ │ ├── prism.js │ │ ├── search.worker.js │ │ ├── nav.yml │ │ ├── github.js │ │ └── use-search.js └── pages │ └── 404.tsx ├── .prettierrc ├── .editorconfig ├── content ├── keyshape │ ├── README.md │ ├── LazySVG.tsx │ └── TreeSVG.tsx ├── pages │ ├── 999-misc.mdx │ ├── 160-null-undefined.mdx │ ├── 150-boolean.mdx │ ├── 120-value-variable-type.mdx │ ├── 100-javascript.mdx │ ├── 293-module.mdx │ ├── 282-data-structures.mdx │ ├── 140-string.mdx │ ├── 020-tutorial.mdx │ ├── 290-exception.mdx │ └── 170-function.mdx ├── about.mdx └── index.mdx ├── gatsby-config.js ├── tests ├── fix-internal-links.sh └── check-html-links.js ├── .gitignore ├── tsconfig.json ├── .specify ├── templates │ ├── agent-file-template.md │ ├── spec-template.md │ └── tasks-template.md ├── scripts │ └── bash │ │ ├── setup-plan.sh │ │ ├── create-new-feature.sh │ │ ├── common.sh │ │ └── check-prerequisites.sh └── memory │ └── constitution.md ├── .github └── dependabot.yml ├── CLAUDE.md ├── styles └── website.css ├── package.json ├── README.md ├── .claude └── commands │ ├── specify.md │ ├── plan.md │ ├── tasks.md │ ├── implement.md │ ├── constitution.md │ └── analyze.md └── specs └── 001-it-s-been └── spec.md /.nvmrc: -------------------------------------------------------------------------------- 1 | 22 2 | -------------------------------------------------------------------------------- /gatsby/gatsby-ssr.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /gatsby/gatsby-browser.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | publish = "public/" 3 | command = "npm run build" -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/helloworld-javascript/hwjs-book/HEAD/icon.png -------------------------------------------------------------------------------- /src/@primer/gatsby-theme-doctocat/primer-nav.yml: -------------------------------------------------------------------------------- 1 | - title: About 2 | url: /about 3 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "all", 3 | "bracketSpacing": false, 4 | "semi": false, 5 | "singleQuote": true 6 | } 7 | -------------------------------------------------------------------------------- /src/@primer/gatsby-theme-doctocat/images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/helloworld-javascript/hwjs-book/HEAD/src/@primer/gatsby-theme-doctocat/images/favicon.png -------------------------------------------------------------------------------- /src/@primer/gatsby-theme-doctocat/components/Logo.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | 3 | export default function Logo(): React.ReactElement { 4 | return
HWJS
5 | } 6 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | #test 11 | -------------------------------------------------------------------------------- /content/keyshape/README.md: -------------------------------------------------------------------------------- 1 | 이 폴더의 컴포넌트들은 원래 Keyshape 라는 애니메이션 프로그램으로 만들어진 SVG 파일이었으나, 2 | Gatsby 에서 SVG 를 바로 사용하기 어려워서 React 컴포넌트 형태로 만든 후 mdx 파일에서 가져다 사용 중 3 | 4 | LazySVG 및 다른 컴포넌트들은 FF, Chrome, Safari 에서 정상작동 확인됨 5 | -------------------------------------------------------------------------------- /src/@primer/gatsby-theme-doctocat/live-code-scope.js: -------------------------------------------------------------------------------- 1 | // Users can shadow this file to define the scope of live code examples. 2 | // See https://primer.style/doctocat/usage/live-code for more details 3 | export default {} 4 | -------------------------------------------------------------------------------- /src/pages/404.tsx: -------------------------------------------------------------------------------- 1 | import React, {FC} from 'react' 2 | 3 | export const NotFoundPage: FC = () => { 4 | return

Not Found

5 | } 6 | 7 | NotFoundPage.displayName = 'NotFoundPage' 8 | 9 | export default NotFoundPage 10 | -------------------------------------------------------------------------------- /gatsby-config.js: -------------------------------------------------------------------------------- 1 | const {generateConfig} = require('gatsby-plugin-ts-config') 2 | 3 | module.exports = generateConfig({ 4 | configDir: 'gatsby', // or wherever you would like to store your gatsby files 5 | projectRoot: __dirname, 6 | }) 7 | -------------------------------------------------------------------------------- /src/@primer/gatsby-theme-doctocat/components/paragraph.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | import themeGet from '@styled-system/theme-get' 3 | 4 | const Paragraph = styled.p` 5 | margin: 0 0 ${themeGet('space.3')}; 6 | ` 7 | 8 | export default Paragraph 9 | -------------------------------------------------------------------------------- /src/@primer/gatsby-theme-doctocat/components/caption.js: -------------------------------------------------------------------------------- 1 | import {Text} from '@primer/components' 2 | import React from 'react' 3 | 4 | function Caption(props) { 5 | return 6 | } 7 | 8 | export default Caption 9 | -------------------------------------------------------------------------------- /src/@primer/gatsby-theme-doctocat/components/image.js: -------------------------------------------------------------------------------- 1 | import themeGet from '@styled-system/theme-get' 2 | import styled from 'styled-components' 3 | 4 | const Image = styled.img` 5 | max-width: 100%; 6 | box-sizing: content-box; 7 | background-color: ${themeGet('colors.white')}; 8 | ` 9 | 10 | export default Image 11 | -------------------------------------------------------------------------------- /src/@primer/gatsby-theme-doctocat/components/container.js: -------------------------------------------------------------------------------- 1 | import {Box} from '@primer/components' 2 | import React from 'react' 3 | 4 | function Container({children}) { 5 | return ( 6 | 7 | {children} 8 | 9 | ) 10 | } 11 | 12 | export default Container 13 | -------------------------------------------------------------------------------- /src/@primer/gatsby-theme-doctocat/components/wrap-page-element.js: -------------------------------------------------------------------------------- 1 | import {BaseStyles} from '@primer/components' 2 | import React from 'react' 3 | import SkipLink from './skip-link' 4 | 5 | function wrapPageElement({element}) { 6 | return ( 7 | 8 | 9 | {element} 10 | 11 | ) 12 | } 13 | 14 | export default wrapPageElement 15 | -------------------------------------------------------------------------------- /tests/fix-internal-links.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Fix all internal links by removing .md and .mdx extensions 4 | 5 | echo "Fixing internal links in MDX files..." 6 | 7 | find content/pages -name "*.mdx" | while read file; do 8 | # Remove .md) and .mdx) from links 9 | sed -i '' 's/\](\.\/[^)]*\)\.mdx*)/](\1)/g' "$file" 10 | echo "Fixed: $file" 11 | done 12 | 13 | echo "Done!" 14 | -------------------------------------------------------------------------------- /src/@primer/gatsby-theme-doctocat/components/dark-button.js: -------------------------------------------------------------------------------- 1 | import {ButtonOutline, themeGet} from '@primer/components' 2 | import styled from 'styled-components' 3 | 4 | const DarkButton = styled(ButtonOutline)` 5 | color: ${themeGet('colors.blue.2')}; 6 | background-color: transparent; 7 | border: 1px solid ${themeGet('colors.gray.7')}; 8 | box-shadow: none; 9 | ` 10 | 11 | export default DarkButton 12 | -------------------------------------------------------------------------------- /src/@primer/gatsby-theme-doctocat/components/horizontal-rule.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | import themeGet from '@styled-system/theme-get' 3 | 4 | const HorizontalRule = styled.hr` 5 | height: ${themeGet('space.1')}; 6 | padding: 0; 7 | margin: ${themeGet('space.4')} 0; 8 | background-color: ${themeGet('colors.gray.2')}; 9 | border: 0; 10 | ` 11 | 12 | export default HorizontalRule 13 | -------------------------------------------------------------------------------- /src/@primer/gatsby-theme-doctocat/components/image-container.js: -------------------------------------------------------------------------------- 1 | import {BorderBox, Flex} from '@primer/components' 2 | import React from 'react' 3 | 4 | function ImageContainer({children}) { 5 | return ( 6 | 7 | 8 | {children} 9 | 10 | 11 | ) 12 | } 13 | 14 | export default ImageContainer 15 | -------------------------------------------------------------------------------- /src/@primer/gatsby-theme-doctocat/components/inline-code.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | import themeGet from '@styled-system/theme-get' 3 | 4 | const InlineCode = styled.code` 5 | padding: 0.2em 0.4em; 6 | font-family: ${themeGet('fonts.mono')}; 7 | font-size: 85%; 8 | background-color: rgba(27, 31, 35, 0.05); // Copied from github.com 9 | border-radius: ${themeGet('radii.1')}; 10 | ` 11 | 12 | export default InlineCode 13 | -------------------------------------------------------------------------------- /src/@primer/gatsby-theme-doctocat/components/source-link.js: -------------------------------------------------------------------------------- 1 | import {Link, StyledOcticon} from '@primer/components' 2 | import {CodeIcon} from '@primer/octicons-react' 3 | import React from 'react' 4 | 5 | function SourceLink({href}) { 6 | return ( 7 | 8 | 9 | View source 10 | 11 | ) 12 | } 13 | 14 | export default SourceLink 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Node rules: 2 | ## Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 3 | .grunt 4 | 5 | ## Dependency directory 6 | ## Commenting this out is preferred by some people, see 7 | ## https://docs.npmjs.com/misc/faq#should-i-check-my-node_modules-folder-into-git 8 | node_modules/ 9 | 10 | # Book build output 11 | _book/ 12 | 13 | # eBook build output 14 | *.epub 15 | *.mobi 16 | *.pdf 17 | 18 | # Gatsby 19 | .cache/ 20 | public/ 21 | -------------------------------------------------------------------------------- /gatsby/gatsby-node.ts: -------------------------------------------------------------------------------- 1 | // gatsby-node.js 2 | exports.onCreatePage = ({ page, actions }, { suffix }) => { 3 | const { createPage, deletePage } = actions; 4 | 5 | return new Promise(resolve => { 6 | const oldPage = Object.assign({}, page); 7 | console.log(page) 8 | if (page.path.startsWith('/pages/')) { 9 | page.path += '.html'; 10 | } 11 | if (page.path !== oldPage.path) { 12 | deletePage(oldPage); 13 | createPage(page); 14 | } 15 | resolve(); 16 | }); 17 | }; -------------------------------------------------------------------------------- /src/@primer/gatsby-theme-doctocat/components/list.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | import themeGet from '@styled-system/theme-get' 3 | 4 | const List = styled.ul` 5 | padding-left: 2em; 6 | 7 | ul, 8 | ol { 9 | margin-top: 0; 10 | margin-bottom: 0; 11 | } 12 | 13 | li { 14 | word-wrap: break-all; 15 | } 16 | 17 | li > p { 18 | margin-top: ${themeGet('space.3')}; 19 | } 20 | 21 | li + li { 22 | margin-top: ${themeGet('space.1')}; 23 | } 24 | ` 25 | 26 | export default List 27 | -------------------------------------------------------------------------------- /src/@primer/gatsby-theme-doctocat/components/live-preview-wrapper.js: -------------------------------------------------------------------------------- 1 | import {BaseStyles, Box} from '@primer/components' 2 | import Frame from './frame' 3 | import React from 'react' 4 | 5 | // Users can shadow this file to wrap live previews. 6 | // This is useful for applying global styles. 7 | function LivePreviewWrapper({children}) { 8 | return ( 9 | 10 | 11 | {children} 12 | 13 | 14 | ) 15 | } 16 | 17 | export default LivePreviewWrapper 18 | -------------------------------------------------------------------------------- /src/@primer/gatsby-theme-doctocat/components/blockquote.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | import themeGet from '@styled-system/theme-get' 3 | 4 | const Blockquote = styled.blockquote` 5 | margin: 0 0 ${themeGet('space.3')}; 6 | padding: 0 ${themeGet('space.3')}; 7 | color: ${themeGet('colors.gray.5')}; 8 | border-left: 0.25em solid ${themeGet('colors.gray.2')}; 9 | 10 | > :first-child { 11 | margin-top: 0; 12 | } 13 | 14 | > :last-child { 15 | margin-bottom: 0; 16 | } 17 | ` 18 | 19 | export default Blockquote 20 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["./src/**/*"], 3 | "compilerOptions": { 4 | "target": "es2019", 5 | "module": "commonjs", 6 | "lib": ["dom", "es2020"], 7 | "jsx": "react", 8 | "strict": true, 9 | "esModuleInterop": true, 10 | "experimentalDecorators": true, 11 | "emitDecoratorMetadata": true, 12 | "skipLibCheck": true, 13 | "noImplicitAny": false, 14 | "resolveJsonModule": true, 15 | "baseUrl": "./", 16 | "paths": { 17 | "src": ["./src"] 18 | }, 19 | "moduleResolution": "node" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/@primer/gatsby-theme-doctocat/components/description-list.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | import themeGet from '@styled-system/theme-get' 3 | 4 | const DescriptionList = styled.dl` 5 | padding: 0; 6 | 7 | dt { 8 | padding: 0; 9 | margin-top: ${themeGet('space.3')}; 10 | font-size: 1em; 11 | font-style: italic; 12 | font-weight: ${themeGet('fontWeights.bold')}; 13 | } 14 | 15 | dd { 16 | padding: 0 ${themeGet('space.3')}; 17 | margin: 0 0 ${themeGet('space.3')}; 18 | } 19 | ` 20 | 21 | export default DescriptionList 22 | -------------------------------------------------------------------------------- /.specify/templates/agent-file-template.md: -------------------------------------------------------------------------------- 1 | # [PROJECT NAME] Development Guidelines 2 | 3 | Auto-generated from all feature plans. Last updated: [DATE] 4 | 5 | ## Active Technologies 6 | [EXTRACTED FROM ALL PLAN.MD FILES] 7 | 8 | ## Project Structure 9 | ``` 10 | [ACTUAL STRUCTURE FROM PLANS] 11 | ``` 12 | 13 | ## Commands 14 | [ONLY COMMANDS FOR ACTIVE TECHNOLOGIES] 15 | 16 | ## Code Style 17 | [LANGUAGE-SPECIFIC, ONLY FOR LANGUAGES IN USE] 18 | 19 | ## Recent Changes 20 | [LAST 3 FEATURES AND WHAT THEY ADDED] 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/@primer/gatsby-theme-doctocat/use-site-metadata.ts: -------------------------------------------------------------------------------- 1 | import {useStaticQuery, graphql} from 'gatsby' 2 | 3 | export type SiteMetadata = { 4 | title?: string 5 | shortName?: string 6 | description?: string 7 | imageUrl?: string 8 | } 9 | 10 | function useSiteMetadata(): SiteMetadata { 11 | const data = useStaticQuery(graphql` 12 | { 13 | site { 14 | siteMetadata { 15 | title 16 | shortName 17 | description 18 | imageUrl 19 | } 20 | } 21 | } 22 | `) 23 | return data.site.siteMetadata 24 | } 25 | 26 | export default useSiteMetadata 27 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "npm" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "daily" 12 | allow: 13 | - dependency-name: "@primer/gatsby-theme-doctocat" 14 | -------------------------------------------------------------------------------- /src/@primer/gatsby-theme-doctocat/components/hero.js: -------------------------------------------------------------------------------- 1 | import {Box, Heading, Text} from '@primer/components' 2 | import React from 'react' 3 | import useSiteMetadata from '../use-site-metadata' 4 | import Container from './container' 5 | 6 | function Hero() { 7 | const {title, description} = useSiteMetadata() 8 | 9 | return ( 10 | 11 | 12 | 13 | {title} 14 | 15 | 16 | {description} 17 | 18 | 19 | 20 | ) 21 | } 22 | 23 | export default Hero 24 | -------------------------------------------------------------------------------- /gatsby/gatsby-config.ts: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | 3 | export default { 4 | siteMetadata: { 5 | title: 'JavaScript로 만나는 세상', 6 | shortName: 'Hello World JavaScript', 7 | description: 8 | '처음 시작하는 사람들을 위한 JavaScript 교재', 9 | }, 10 | plugins: [ 11 | '@primer/gatsby-theme-doctocat', 12 | 'gatsby-plugin-typescript', 13 | { 14 | resolve: 'gatsby-plugin-root-import', 15 | options: { 16 | src: path.resolve(__dirname, '..', 'src'), 17 | }, 18 | }, 19 | { 20 | resolve: '@primer/gatsby-theme-doctocat', 21 | options: { 22 | icon: path.resolve(__dirname, '..', 'icon.png'), 23 | }, 24 | }, 25 | ], 26 | } 27 | -------------------------------------------------------------------------------- /src/@primer/gatsby-theme-doctocat/components/dark-text-input.js: -------------------------------------------------------------------------------- 1 | import {TextInput, themeGet} from '@primer/components' 2 | import styled from 'styled-components' 3 | 4 | const DarkTextInput = styled(TextInput)` 5 | /* The font-size of inputs should never be less than 16px. 6 | * Otherwise, iOS browsers will zoom in when the input is focused. 7 | * TODO: Update font-size of TextInput in @primer/components. 8 | */ 9 | font-size: ${themeGet('fontSizes.2')} !important; 10 | color: ${themeGet('colors.white')}; 11 | background-color: rgba(255, 255, 255, 0.07); 12 | border: 1px solid transparent; 13 | box-shadow: none; 14 | 15 | &:focus { 16 | border: 1px solid rgba(255, 255, 255, 0.15); 17 | outline: none; 18 | box-shadow: none; 19 | } 20 | ` 21 | export default DarkTextInput 22 | -------------------------------------------------------------------------------- /src/@primer/gatsby-theme-doctocat/components/table.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | import themeGet from '@styled-system/theme-get' 3 | 4 | const Table = styled.table` 5 | display: block; 6 | width: 100%; 7 | margin: 0 0 ${themeGet('space.3')}; 8 | overflow: auto; 9 | 10 | th { 11 | font-weight: ${themeGet('fontWeights.bold')}; 12 | } 13 | 14 | th, 15 | td { 16 | padding: ${themeGet('space.2')} ${themeGet('space.3')}; 17 | border: 1px solid ${themeGet('colors.gray.2')}; 18 | } 19 | 20 | tr { 21 | background-color: ${themeGet('colors.white')}; 22 | border-top: 1px solid ${themeGet('colors.gray.2')}; 23 | 24 | &:nth-child(2n) { 25 | background-color: ${themeGet('colors.gray.1')}; 26 | } 27 | } 28 | 29 | img { 30 | background-color: transparent; 31 | } 32 | ` 33 | 34 | export default Table 35 | -------------------------------------------------------------------------------- /src/@primer/gatsby-theme-doctocat/components/status-label.js: -------------------------------------------------------------------------------- 1 | import {BorderBox, StyledOcticon, Flex, Text} from '@primer/components' 2 | import {DotFillIcon} from '@primer/octicons-react' 3 | import React from 'react' 4 | 5 | const STATUS_COLORS = { 6 | stable: 'green.6', 7 | new: 'green.6', 8 | experimental: 'yellow.7', 9 | review: 'yellow.7', 10 | deprecated: 'red.6', 11 | } 12 | 13 | function getStatusColor(status) { 14 | return STATUS_COLORS[status.toLowerCase()] || 'gray.5' 15 | } 16 | 17 | function StatusLabel({status}) { 18 | return ( 19 | 20 | 21 | 22 | {status} 23 | 24 | 25 | ) 26 | } 27 | 28 | export default StatusLabel 29 | -------------------------------------------------------------------------------- /src/@primer/gatsby-theme-doctocat/components/note.js: -------------------------------------------------------------------------------- 1 | import themeGet from '@styled-system/theme-get' 2 | import styled from 'styled-components' 3 | import {variant} from 'styled-system' 4 | 5 | const Note = styled.div` 6 | padding: ${themeGet('space.3')}; 7 | margin-bottom: ${themeGet('space.3')}; 8 | border-radius: ${themeGet('radii.2')}; 9 | border-left: ${themeGet('radii.2')} solid; 10 | 11 | & *:last-child { 12 | margin-bottom: 0; 13 | } 14 | 15 | ${variant({ 16 | variants: { 17 | info: { 18 | borderColor: 'blue.4', 19 | bg: 'blue.0', 20 | }, 21 | warning: { 22 | borderColor: 'yellow.5', 23 | bg: 'yellow.1', 24 | }, 25 | danger: { 26 | borderColor: 'red.4', 27 | bg: 'red.0', 28 | }, 29 | }, 30 | })} 31 | ` 32 | 33 | Note.defaultProps = { 34 | variant: 'info', 35 | } 36 | 37 | export default Note 38 | -------------------------------------------------------------------------------- /CLAUDE.md: -------------------------------------------------------------------------------- 1 | # hwjs-book Development Guidelines 2 | 3 | Auto-generated from all feature plans. Last updated: 2025-10-02 4 | 5 | ## Active Technologies 6 | - JavaScript (ES2025/ES2024), Gatsby 2.x (static site generator), TypeScript 4.2+ + Gatsby, @primer/gatsby-theme-doctocat, React 16.13+, MDX for conten (001-it-s-been) 7 | 8 | ## Project Structure 9 | ``` 10 | src/ 11 | tests/ 12 | ``` 13 | 14 | ## Commands 15 | npm test [ONLY COMMANDS FOR ACTIVE TECHNOLOGIES][ONLY COMMANDS FOR ACTIVE TECHNOLOGIES] npm run lint 16 | 17 | ## Code Style 18 | JavaScript (ES2025/ES2024), Gatsby 2.x (static site generator), TypeScript 4.2+: Follow standard conventions 19 | 20 | ## Recent Changes 21 | - 001-it-s-been: Added JavaScript (ES2025/ES2024), Gatsby 2.x (static site generator), TypeScript 4.2+ + Gatsby, @primer/gatsby-theme-doctocat, React 16.13+, MDX for conten 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/@primer/gatsby-theme-doctocat/components/head.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Helmet from 'react-helmet' 3 | import useSiteMetadata, {SiteMetadata} from '../use-site-metadata' 4 | 5 | function Head(props: Pick) { 6 | const siteMetadata = useSiteMetadata() 7 | const title = props.title 8 | ? `${props.title} | ${siteMetadata.title}` 9 | : siteMetadata.title 10 | const description = props.description || siteMetadata.description 11 | 12 | return ( 13 | 14 | {title} 15 | 16 | 17 | 18 | 19 | 20 | 21 | ) 22 | } 23 | 24 | export default Head 25 | -------------------------------------------------------------------------------- /src/@primer/gatsby-theme-doctocat/prism.js: -------------------------------------------------------------------------------- 1 | import Prism from 'prismjs/components/prism-core' 2 | 3 | // Core languages 4 | import 'prismjs/components/prism-clike'; 5 | import 'prismjs/components/prism-markup'; 6 | import 'prismjs/components/prism-markup-templating'; 7 | 8 | // Supported languages 9 | import 'prismjs/components/prism-bash'; 10 | import 'prismjs/components/prism-css'; 11 | import 'prismjs/components/prism-diff'; 12 | import 'prismjs/components/prism-go'; 13 | import 'prismjs/components/prism-json'; 14 | import 'prismjs/components/prism-markdown'; 15 | import 'prismjs/components/prism-yaml'; 16 | 17 | // JS like 18 | import 'prismjs/components/prism-javascript'; 19 | import 'prismjs/components/prism-jsx'; 20 | import 'prismjs/components/prism-typescript'; 21 | import 'prismjs/components/prism-tsx'; 22 | 23 | // Ruby like 24 | import 'prismjs/components/prism-ruby'; 25 | import 'prismjs/components/prism-erb'; 26 | 27 | export default Prism 28 | -------------------------------------------------------------------------------- /src/@primer/gatsby-theme-doctocat/components/page-footer.js: -------------------------------------------------------------------------------- 1 | import {BorderBox, Grid, StyledOcticon, Link} from '@primer/components' 2 | import {PencilIcon} from '@primer/octicons-react' 3 | import React from 'react' 4 | import Contributors from './contributors' 5 | 6 | function PageFooter({editUrl, contributors}) { 7 | return editUrl || contributors.length > 0 ? ( 8 | 9 | 10 | {editUrl ? ( 11 | 12 | 13 | Edit this page on GitHub 14 | 15 | ) : null} 16 | 17 | {contributors.length > 0 ? ( 18 | 19 | ) : null} 20 | 21 | 22 | ) : null 23 | } 24 | 25 | PageFooter.defaultProps = { 26 | contributors: [], 27 | } 28 | 29 | export default PageFooter 30 | -------------------------------------------------------------------------------- /src/@primer/gatsby-theme-doctocat/components/table-of-contents.js: -------------------------------------------------------------------------------- 1 | import {Box, Link} from '@primer/components' 2 | import React from 'react' 3 | 4 | function TableOfContents({items, depth}) { 5 | return ( 6 | 7 | {items.map((item) => ( 8 | 0 ? 3 : 0}> 9 | {item.title ? ( 10 | 17 | {item.title} 18 | 19 | ) : null} 20 | {item.items ? ( 21 | 22 | ) : null} 23 | 24 | ))} 25 | 26 | ) 27 | } 28 | 29 | TableOfContents.defaultProps = { 30 | depth: 0, 31 | } 32 | 33 | export default TableOfContents 34 | -------------------------------------------------------------------------------- /src/@primer/gatsby-theme-doctocat/components/clipboard-copy.js: -------------------------------------------------------------------------------- 1 | import {Button, StyledOcticon} from '@primer/components' 2 | import {CheckIcon, ClippyIcon} from '@primer/octicons-react' 3 | import copy from 'copy-to-clipboard' 4 | import React from 'react' 5 | 6 | function ClipboardCopy({value}) { 7 | const [copied, setCopied] = React.useState(false) 8 | 9 | React.useEffect(() => { 10 | const timeout = setTimeout(() => { 11 | if (copied) setCopied(false) 12 | }, 1000) 13 | 14 | return () => clearTimeout(timeout) 15 | }, [copied]) 16 | 17 | return ( 18 | 32 | ) 33 | } 34 | 35 | export default ClipboardCopy 36 | -------------------------------------------------------------------------------- /styles/website.css: -------------------------------------------------------------------------------- 1 | /* @import 'https://unpkg.com/open-color@1.5.1/open-color.css'; */ 2 | 3 | .markdown-section h2 { 4 | font-size: 1.6em; 5 | } 6 | 7 | .markdown-section h3 { 8 | font-size: 1.2em; 9 | } 10 | 11 | .markdown-section h4 { 12 | font-size: 1em; 13 | } 14 | 15 | .markdown-section pre { 16 | position: relative; 17 | } 18 | 19 | .markdown-section pre::before { 20 | display: block; 21 | content: attr(data-index); 22 | position: absolute; 23 | top: 0; 24 | right: 0; 25 | background-color: #dee2e6; 26 | text-align: center; 27 | width: 2em; 28 | color: white; 29 | } 30 | 31 | li.chapter { 32 | display: flex; 33 | } 34 | 35 | li.chapter::before { 36 | display: block; 37 | content: attr(data-level); 38 | padding: 10px 5px 10px 15px; 39 | color: #939da3; 40 | } 41 | 42 | li.chapter > span { 43 | color: #939da3 !important; 44 | } 45 | 46 | li.chapter > span, li.chapter > a { 47 | padding: 10px 15px 10px 0 !important; 48 | } 49 | 50 | embed.hover[src*=".svg"] { 51 | opacity: 0.8; 52 | } 53 | -------------------------------------------------------------------------------- /src/@primer/gatsby-theme-doctocat/components/skip-link.js: -------------------------------------------------------------------------------- 1 | import {Link} from '@primer/components' 2 | import styled from 'styled-components' 3 | import React from 'react' 4 | 5 | function SkipLinkBase(props) { 6 | return ( 7 | 15 | Skip to content 16 | 17 | ) 18 | } 19 | 20 | const SkipLink = styled(SkipLinkBase)` 21 | z-index: 20; 22 | width: auto; 23 | height: auto; 24 | clip: auto; 25 | position: absolute; 26 | overflow: hidden; 27 | 28 | // The following rules are to ensure that the element 29 | // is visually hidden, unless it has focus. This is the recommended 30 | // way to hide content from: 31 | // https://webaim.org/techniques/css/invisiblecontent/#techniques 32 | 33 | &:not(:focus) { 34 | clip: rect(1px, 1px, 1px, 1px); 35 | clip-path: inset(50%); 36 | height: 1px; 37 | width: 1px; 38 | margin: -1px; 39 | padding: 0; 40 | } 41 | ` 42 | 43 | export default SkipLink 44 | -------------------------------------------------------------------------------- /content/pages/999-misc.mdx: -------------------------------------------------------------------------------- 1 | ### 객체 - new.target 2 | 3 | ES2015에서 도입된 `new.target` 문법은 생성자 내부에서 사용됩니다. 만약 생성자가 `new`를 통해 호출되면 `new.target`에는 해당 생성자가 저장됩니다. 만약 생성자가 일반적인 함수로서 호출되면, `new.target`에는 `undefined`가 저장됩니다. 4 | 5 | ```js 6 | function Person() { 7 | if (new.target) { 8 | console.log('생성자로 호출되었습니다.') 9 | } else { 10 | console.log('생성자로 호출되지 않았습니다.') 11 | } 12 | } 13 | 14 | new Person() // 생성자로 호출되었습니다. 15 | Person() // 생성자로 호출되지 않았습니다. 16 | ``` 17 | 18 | 이 기능을 이용해, 실수로 `new`를 빠트렸을 때도 문제없이 객체가 생성되도록 코드를 작성할 수 있습니다. 19 | 20 | ```js 21 | function Person(name) { 22 | if (!new.target) { 23 | // `new` 없이 호출되면, 직접 객체를 생성해 반환합니다. 24 | return new Person(name) 25 | } else { 26 | this.name = name 27 | } 28 | } 29 | ``` 30 | 31 | ### 객체 - instanceof 32 | 33 | 앞에서 봤던 `instanceof` 연산자의 동작은 사실 생각보다 조금 복잡합니다. `instanceof` 연산자는 **생성자의 `prototype` 속성**이 **객체의 프로토타입 체인에 등장하는지**를 검사합니다. 그래서, 특별한 경우가 아니라면 생성자를 통해 생성된 객체는 `Object` 생성자의 인스턴스이기도 합니다. 34 | 35 | ```js 36 | function Person() {} 37 | const person = new Person() 38 | person1 instanceof Person // true 39 | person instanceof Object // true 40 | ``` 41 | -------------------------------------------------------------------------------- /src/@primer/gatsby-theme-doctocat/search.worker.js: -------------------------------------------------------------------------------- 1 | import Fuse from 'fuse.js' 2 | import debounce from 'lodash.debounce' 3 | 4 | (function searchWorker() { 5 | let fuse = null 6 | 7 | // [MKT]: I landed on the debouce wait value of 50 based mostly on 8 | // experimentation. With both `leading` and `trailing` set to `true`, this 9 | // feels pretty snappy. 10 | // 11 | // From https://lodash.com/docs/#debounce: 12 | // 13 | // > Note: If `leading` and `trailing` options are `true`, `func` is invoked 14 | // > on the trailing edge of the timeout only if the debounced function is 15 | // > invoked more than once during the wait timeout. 16 | const performSearch = debounce(function performSearch(query) { 17 | const results = fuse.search(query).slice(0, 20) 18 | postMessage({results: results, query: query}) 19 | }, 50, {leading: true, trailing: true}) 20 | 21 | onmessage = function({data}) { 22 | if (data.list) { 23 | fuse = new Fuse(data.list, { 24 | threshold: 0.2, 25 | keys: ['title', 'rawBody'], 26 | tokenize: true, 27 | }) 28 | } else if (data.query) { 29 | performSearch(data.query) 30 | } 31 | } 32 | })() 33 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fds-js", 3 | "version": "1.0.0", 4 | "description": "처음 시작하는 사람들을 위한 JavaScript 교재", 5 | "main": "index.js", 6 | "dependencies": { 7 | "@primer/gatsby-theme-doctocat": "^1.4.0", 8 | "gatsby": "^2.32.8", 9 | "gatsby-plugin-typescript": "^2.12.1", 10 | "react": "^16.13.1", 11 | "react-dom": "^16.13.1", 12 | "ts-node": "^9.1.1", 13 | "typescript": "^4.2.2" 14 | }, 15 | "devDependencies": { 16 | "@types/node": "^14.14.31", 17 | "@types/react": "^17.0.2", 18 | "@types/react-dom": "^17.0.1", 19 | "@types/react-helmet": "^6.1.0", 20 | "gatsby-plugin-root-import": "^2.0.6", 21 | "gatsby-plugin-ts-config": "^1.1.5" 22 | }, 23 | "scripts": { 24 | "test": "npm run test:links", 25 | "test:links": "npm run build && node tests/check-html-links.js", 26 | "dev": "NODE_OPTIONS=\"--openssl-legacy-provider\" gatsby develop", 27 | "build": "NODE_OPTIONS=\"--openssl-legacy-provider\" gatsby build" 28 | }, 29 | "repository": { 30 | "type": "git", 31 | "url": "git+ssh://git@github.com:helloworld-javascript/hwjs-book.git" 32 | }, 33 | "keywords": [], 34 | "author": "", 35 | "license": "ISC", 36 | "homepage": "https://helloworldjavascript.net/" 37 | } 38 | -------------------------------------------------------------------------------- /content/keyshape/LazySVG.tsx: -------------------------------------------------------------------------------- 1 | import React, {useEffect, useState} from 'react' 2 | 3 | export interface LazySVGProps { 4 | svgString: string 5 | } 6 | 7 | /// Node.js 에 btoa 함수가 없어서 SSR 시 에러가 나는 문제를 회피 8 | function LazySVG({svgString}: LazySVGProps) { 9 | const [content, setContent] = useState(null) 10 | 11 | useEffect(() => { 12 | const handleLoad = (e) => { 13 | const el = e.target 14 | const doc = el.getSVGDocument() 15 | doc.documentElement.style.cursor = 'pointer' 16 | doc.addEventListener('click', function () { 17 | doc.ks && doc.ks.time(0) 18 | }) 19 | doc.addEventListener('mouseenter', function () { 20 | el.classList.add('hover') 21 | }) 22 | doc.addEventListener('mouseleave', function () { 23 | el.classList.remove('hover') 24 | }) 25 | if (doc.ks && el.hasAttribute('data-loop')) { 26 | setInterval(function () { 27 | doc.ks.play() 28 | }, 500) 29 | } 30 | } 31 | 32 | var url = URL.createObjectURL( 33 | new Blob([svgString], {type: 'image/svg+xml'}), 34 | ) 35 | setContent() 36 | }, []) 37 | 38 | return content 39 | } 40 | 41 | export default LazySVG 42 | -------------------------------------------------------------------------------- /src/@primer/gatsby-theme-doctocat/components/hero-layout.js: -------------------------------------------------------------------------------- 1 | import {Box, Flex} from '@primer/components' 2 | import React from 'react' 3 | import Container from './container' 4 | import PageFooter from './page-footer' 5 | import Head from './head' 6 | import Header from './header' 7 | import Hero from './hero' 8 | import Sidebar from './sidebar' 9 | 10 | function HeroLayout({children, pageContext}) { 11 | let {additionalContributors} = pageContext.frontmatter 12 | 13 | if (!additionalContributors) { 14 | additionalContributors = [] 15 | } 16 | 17 | return ( 18 | 19 | 20 |
21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | {children} 29 | ({login})), 33 | )} 34 | /> 35 | 36 | 37 | 38 | 39 | ) 40 | } 41 | 42 | export default HeroLayout 43 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JavaScript로 만나는 세상 2 | 3 | 이 교재는 프로그래밍 경험이 전무한 독자를 대상으로, 최대한 빠른 시간 내에 실무에서 사용되는 JavaScript 언어의 문법과 기능들을 습득할 수 있도록 제작했습니다. 4 | 5 | ## 이 교재의 특징 6 | 7 | - 책을 여러 파트로 나누어서, 학습 단계별로 교재의 적절한 파트를 골라서 활용할 수 있도록 구성했습니다. 8 | - 최신 JavaScript의 문법 및 기능을 다루었습니다. 9 | 10 | ## 기여자 11 | 12 | - [김승하](https://github.com/seungha-kim) 13 | - [김찬연](https://github.com/chayeoi) 14 | - [김재명](https://github.com/stared329) 15 | - [김보영](https://github.com/underbleu) 16 | - [김범연](https://github.com/BeomyeonAndrewKim) 17 | - [김지원](https://github.com/jiwonkirn) 18 | - [정세현](https://github.com/sehyunchung) 19 | - [신창선](https://github.com/changseon-shin) 20 | - [이근환](https://github.com/leekeunhwan) 21 | - [김세준](https://github.com/KimSejune) 22 | - [이강산](https://github.com/hellomac87) 23 | - [성중원](https://github.com/yoeubi) 24 | 25 | --- 26 | 27 | 크리에이티브 커먼즈 라이선스
이 저작물은 크리에이티브 커먼즈 저작자표시-동일조건변경허락 4.0 국제 라이선스에 따라 이용할 수 있습니다. 28 | -------------------------------------------------------------------------------- /content/about.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: About 3 | --- 4 | 5 | 이 교재는 프로그래밍 경험이 전무한 독자를 대상으로, 최대한 빠른 시간 내에 실무에서 사용되는 JavaScript 언어의 문법과 기능들을 습득할 수 있도록 제작했습니다. 6 | 7 | ## 이 교재의 특징 8 | 9 | - 책을 여러 파트로 나누어서, 학습 단계별로 교재의 적절한 파트를 골라서 활용할 수 있도록 구성했습니다. 10 | - 최신 JavaScript의 문법 및 기능을 다루었습니다. 11 | 12 | ## 기여자 13 | 14 | - [김승하](https://github.com/seungha-kim) 15 | - [김찬연](https://github.com/chayeoi) 16 | - [김재명](https://github.com/stared329) 17 | - [김보영](https://github.com/underbleu) 18 | - [김범연](https://github.com/BeomyeonAndrewKim) 19 | - [김지원](https://github.com/jiwonkirn) 20 | - [정세현](https://github.com/sehyunchung) 21 | - [신창선](https://github.com/changseon-shin) 22 | - [이근환](https://github.com/leekeunhwan) 23 | - [김세준](https://github.com/KimSejune) 24 | - [이강산](https://github.com/hellomac87) 25 | - [성중원](https://github.com/yoeubi) 26 | 27 | --- 28 | 29 | 크리에이티브 커먼즈 라이선스
이 저작물은 크리에이티브 커먼즈 저작자표시-동일조건변경허락 4.0 국제 라이선스에 따라 이용할 수 있습니다. 30 | 31 | -------------------------------------------------------------------------------- /src/@primer/gatsby-theme-doctocat/components/nav-dropdown.js: -------------------------------------------------------------------------------- 1 | import { 2 | Absolute, 3 | BorderBox, 4 | StyledOcticon, 5 | Text, 6 | themeGet, 7 | } from '@primer/components' 8 | import {TriangleDownIcon} from '@primer/octicons-react' 9 | import React from 'react' 10 | import styled from 'styled-components' 11 | import Details from './details' 12 | 13 | function NavDropdown({title, children}) { 14 | return ( 15 |
16 | {({toggle}) => ( 17 | <> 18 | 19 | {title} 20 | 21 | 22 | 23 | 31 | {children} 32 | 33 | 34 | 35 | )} 36 |
37 | ) 38 | } 39 | 40 | export const NavDropdownItem = styled.a` 41 | display: block; 42 | padding: ${themeGet('space.2')} ${themeGet('space.3')}; 43 | color: inherit; 44 | text-decoration: none; 45 | 46 | &:hover { 47 | color: ${themeGet('colors.white')}; 48 | background-color: ${themeGet('colors.blue.5')}; 49 | text-decoration: none; 50 | } 51 | ` 52 | 53 | export default NavDropdown 54 | -------------------------------------------------------------------------------- /content/index.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: JavaScript로 만나는 세상 3 | --- 4 | 5 | 이 교재는 프로그래밍 경험이 전무한 독자를 대상으로, 최대한 빠른 시간 내에 실무에서 사용되는 JavaScript 언어의 문법과 기능들을 습득할 수 있도록 제작했습니다. 6 | 7 | > 2025년 10월 기준 최신 JavaScript 표준을 반영하여 모든 내용이 업데이트되었습니다. 8 | 9 | ## 이 교재의 특징 10 | 11 | - 책을 여러 파트로 나누어서, 학습 단계별로 교재의 적절한 파트를 골라서 활용할 수 있도록 구성했습니다. 12 | - 최신 JavaScript의 문법 및 기능을 다루었습니다. 13 | 14 | ## 목차 15 | 16 | ### JavaScript 소개 17 | 18 | - [튜토리얼](/pages/020-tutorial.html) 19 | - [JavaScript 소개](/pages/100-javascript.html) 20 | 21 | ### JavaScript 기초 22 | 23 | - [값 다루기](/pages/120-value-variable-type.html) 24 | - [number 타입](/pages/130-number.html) 25 | - [string 타입](/pages/140-string.html) 26 | - [boolean 타입](/pages/150-boolean.html) 27 | - [null과 undefined](/pages/160-null-undefined.html) 28 | - [함수](/pages/170-function.html) 29 | - [제어 구문](/pages/175-control-statement.html) 30 | - [객체](/pages/180-object.html) 31 | - [배열](/pages/190-array.html) 32 | 33 | ### JavaScript 심화 1 34 | 35 | - [값 더 알아보기](/pages/220-value-in-depth.html) 36 | - [함수 더 알아보기](/pages/230-function-in-depth.html) 37 | - [함수형 프로그래밍](/pages/255-fp.html) 38 | - [객체 더 알아보기](/pages/240-object-in-depth.html) 39 | - [연산자 더 알아보기](/pages/245-operator-in-depth.html) 40 | - [내장 객체 및 생성자](/pages/250-builtins.html) 41 | 42 | ### JavaScript 심화 2 43 | 44 | - [Iterable](/pages/260-iteration.html) 45 | - [클래스](/pages/270-class.html) 46 | - [큐, 스택, 트리](/pages/282-data-structures.html) 47 | - [비동기 프로그래밍](/pages/285-async.html) 48 | - [예외 처리](/pages/290-exception.html) 49 | - [모듈](/pages/293-module.html) 50 | -------------------------------------------------------------------------------- /src/@primer/gatsby-theme-doctocat/nav.yml: -------------------------------------------------------------------------------- 1 | - title: Index 2 | url: / 3 | - title: 1.1 튜토리얼 4 | url: /pages/020-tutorial.html 5 | - title: 1.2 JavaScript 소개 6 | url: /pages/100-javascript.html 7 | - title: 2.1 값 다루기 8 | url: /pages/120-value-variable-type.html 9 | - title: 2.2 number 타입 10 | url: /pages/130-number.html 11 | - title: 2.3 string 타입 12 | url: /pages/140-string.html 13 | - title: 2.4 boolean 타입 14 | url: /pages/150-boolean.html 15 | - title: 2.5 null과 undefined 16 | url: /pages/160-null-undefined.html 17 | - title: 2.6 함수 18 | url: /pages/170-function.html 19 | - title: 2.7 제어 구문 20 | url: /pages/175-control-statement.html 21 | - title: 2.8 객체 22 | url: /pages/180-object.html 23 | - title: 2.9 배열 24 | url: /pages/190-array.html 25 | - title: 3.1 값 더 알아보기 26 | url: /pages/220-value-in-depth.html 27 | - title: 3.2 함수 더 알아보기 28 | url: /pages/230-function-in-depth.html 29 | - title: 3.3 함수형 프로그래밍 30 | url: /pages/255-fp.html 31 | - title: 3.4 객체 더 알아보기 32 | url: /pages/240-object-in-depth.html 33 | - title: 3.5 연산자 더 알아보기 34 | url: /pages/245-operator-in-depth.html 35 | - title: 3.6 내장 객체 및 생성자 36 | url: /pages/250-builtins.html 37 | - title: 4.1 Iterable 38 | url: /pages/260-iteration.html 39 | - title: 4.2 클래스 40 | url: /pages/270-class.html 41 | - title: 4.3 큐, 스택, 트리 42 | url: /pages/282-data-structures.html 43 | - title: 4.4 비동기 프로그래밍 44 | url: /pages/285-async.html 45 | - title: 4.5 예외 처리 46 | url: /pages/290-exception.html 47 | - title: 4.6 모듈 48 | url: /pages/293-module.html 49 | -------------------------------------------------------------------------------- /.claude/commands/specify.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Create or update the feature specification from a natural language feature description. 3 | --- 4 | 5 | The user input to you can be provided directly by the agent or as a command argument - you **MUST** consider it before proceeding with the prompt (if not empty). 6 | 7 | User input: 8 | 9 | $ARGUMENTS 10 | 11 | The text the user typed after `/specify` in the triggering message **is** the feature description. Assume you always have it available in this conversation even if `$ARGUMENTS` appears literally below. Do not ask the user to repeat it unless they provided an empty command. 12 | 13 | Given that feature description, do this: 14 | 15 | 1. Run the script `.specify/scripts/bash/create-new-feature.sh --json "$ARGUMENTS"` from repo root and parse its JSON output for BRANCH_NAME and SPEC_FILE. All file paths must be absolute. 16 | **IMPORTANT** You must only ever run this script once. The JSON is provided in the terminal as output - always refer to it to get the actual content you're looking for. 17 | 2. Load `.specify/templates/spec-template.md` to understand required sections. 18 | 3. Write the specification to SPEC_FILE using the template structure, replacing placeholders with concrete details derived from the feature description (arguments) while preserving section order and headings. 19 | 4. Report completion with branch name, spec file path, and readiness for the next phase. 20 | 21 | Note: The script creates and checks out the new branch and initializes the spec file before writing. 22 | -------------------------------------------------------------------------------- /src/@primer/gatsby-theme-doctocat/components/frame.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import FrameComponent, {FrameContextConsumer} from 'react-frame-component' 3 | import {StyleSheetManager} from 'styled-components' 4 | import Measure from 'react-measure' 5 | 6 | function Frame({children}) { 7 | const [height, setHeight] = React.useState('auto') 8 | return ( 9 | 10 | 11 | {({document}) => { 12 | // By default, styled-components injects styles in the head of the page. 13 | // However, styles from the page's head don't apply inside iframes. 14 | // We're using StyleSheetManager to make styled-components inject styles 15 | // into the head of the iframe instead. 16 | return ( 17 | 18 | setHeight(rect.bounds.height)} 24 | > 25 | {({measureRef}) =>
{children}
} 26 |
27 |
28 | ) 29 | }} 30 |
31 |
32 | ) 33 | } 34 | 35 | export default Frame 36 | -------------------------------------------------------------------------------- /src/@primer/gatsby-theme-doctocat/components/do-dont.js: -------------------------------------------------------------------------------- 1 | import {Flex, Grid, StyledOcticon, Text} from '@primer/components' 2 | import {CheckCircleFillIcon, XCircleFillIcon} from '@primer/octicons-react' 3 | import React from 'react' 4 | 5 | export function DoDontContainer({stacked, children}) { 6 | return ( 7 | 12 | {children} 13 | 14 | ) 15 | } 16 | 17 | DoDontContainer.defaultProps = { 18 | stacked: false, 19 | } 20 | 21 | export function Do(props) { 22 | return ( 23 | 29 | ) 30 | } 31 | 32 | export function Dont(props) { 33 | return ( 34 | 40 | ) 41 | } 42 | 43 | function DoDontBase({children, title, icon: Icon, iconBg}) { 44 | return ( 45 | 46 | 47 | 48 | 49 | {title} 50 | 51 | 52 | 56 | {children} 57 | 58 | 59 | ) 60 | } 61 | -------------------------------------------------------------------------------- /src/@primer/gatsby-theme-doctocat/github.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plain: { 3 | color: "#393A34", 4 | backgroundColor: "#f6f8fa", 5 | }, 6 | styles: [ 7 | { 8 | types: ["comment", "prolog", "doctype", "cdata"], 9 | style: { 10 | color: "#999988", 11 | fontStyle: "italic", 12 | }, 13 | }, 14 | { 15 | types: ["namespace"], 16 | style: { 17 | opacity: 0.7, 18 | }, 19 | }, 20 | { 21 | types: ["string", "attr-value"], 22 | style: { 23 | color: "#e3116c", 24 | }, 25 | }, 26 | { 27 | types: ["punctuation", "operator"], 28 | style: { 29 | color: "#393A34", 30 | }, 31 | }, 32 | { 33 | types: [ 34 | "entity", 35 | "url", 36 | "symbol", 37 | "number", 38 | "boolean", 39 | "variable", 40 | "constant", 41 | "property", 42 | "regex", 43 | "inserted", 44 | ], 45 | style: { 46 | color: "#36acaa", 47 | }, 48 | }, 49 | { 50 | types: ["atrule", "keyword", "attr-name", "selector"], 51 | style: { 52 | color: "#00a4db", 53 | }, 54 | }, 55 | { 56 | types: ["function", "deleted", "tag"], 57 | style: { 58 | color: "#d73a49", 59 | }, 60 | }, 61 | { 62 | types: ["function-variable"], 63 | style: { 64 | color: "#6f42c1", 65 | }, 66 | }, 67 | { 68 | types: ["tag", "selector", "keyword"], 69 | style: { 70 | color: "#00009f", 71 | }, 72 | }, 73 | ], 74 | }; 75 | -------------------------------------------------------------------------------- /src/@primer/gatsby-theme-doctocat/components/wrap-root-element.js: -------------------------------------------------------------------------------- 1 | import {MDXProvider} from '@mdx-js/react' 2 | import {Link, theme} from '@primer/components' 3 | import React from 'react' 4 | import {ThemeProvider} from 'styled-components' 5 | import Blockquote from './blockquote' 6 | import Caption from './caption' 7 | import Code from './code' 8 | import DescriptionList from './description-list' 9 | import {Do, DoDontContainer, Dont} from './do-dont' 10 | import {H1, H2, H3, H4, H5, H6} from './heading' 11 | import HorizontalRule from './horizontal-rule' 12 | import Image from './image' 13 | import ImageContainer from './image-container' 14 | import InlineCode from './inline-code' 15 | import List from './list' 16 | import Note from './note' 17 | import Paragraph from './paragraph' 18 | import Table from './table' 19 | 20 | const components = { 21 | a: Link, 22 | pre: props => props.children, 23 | code: Code, 24 | inlineCode: InlineCode, 25 | table: Table, 26 | img: Image, 27 | p: Paragraph, 28 | hr: HorizontalRule, 29 | blockquote: Blockquote, 30 | h1: H1, 31 | h2: H2, 32 | h3: H3, 33 | h4: H4, 34 | h5: H5, 35 | h6: H6, 36 | ul: List, 37 | ol: List.withComponent('ol'), 38 | dl: DescriptionList, 39 | 40 | // Shortcodes (https://mdxjs.com/blog/shortcodes) 41 | Note, 42 | Do, 43 | Dont, 44 | DoDontContainer, 45 | Caption, 46 | ImageContainer, 47 | } 48 | 49 | function wrapRootElement({element}) { 50 | return ( 51 | 52 | {element} 53 | 54 | ) 55 | } 56 | 57 | export default wrapRootElement 58 | -------------------------------------------------------------------------------- /src/@primer/gatsby-theme-doctocat/components/search-results.js: -------------------------------------------------------------------------------- 1 | import {Flex, Text} from '@primer/components' 2 | import React from 'react' 3 | import sentenceCase from 'sentence-case' 4 | import useSiteMetadata from '../use-site-metadata' 5 | 6 | function SearchResults({results, getItemProps, highlightedIndex}) { 7 | const siteMetadata = useSiteMetadata() 8 | 9 | if (results.length === 0) { 10 | return ( 11 | 12 | No results 13 | 14 | ) 15 | } 16 | 17 | return results.map((item, index) => ( 18 | 31 | 35 | {getBreadcrumbs(siteMetadata.shortName, item.path).join(' / ')} 36 | 37 | {item.title} 38 | 39 | )) 40 | } 41 | 42 | function getBreadcrumbs(siteTitle, path) { 43 | return [ 44 | siteTitle, 45 | ...path 46 | .split('/') 47 | .filter(Boolean) 48 | // The last title will be displayed separately, so we exclude it 49 | // from the breadcrumbs to avoid displaying it twice. 50 | .slice(0, -1) 51 | .map(sentenceCase), 52 | ] 53 | } 54 | 55 | export default SearchResults 56 | -------------------------------------------------------------------------------- /src/@primer/gatsby-theme-doctocat/components/__tests__/page-footer.test.js: -------------------------------------------------------------------------------- 1 | import {render} from '@testing-library/react' 2 | import React from 'react' 3 | import PageFooter from '../page-footer' 4 | 5 | test('renders correctly when editUrl and contributors are defined', () => { 6 | const {queryByText} = render( 7 | , 8 | ) 9 | 10 | expect(queryByText(/Edit this page on GitHub/)).toBeInTheDocument() 11 | expect(queryByText(/contributor/)).toBeInTheDocument() 12 | }) 13 | 14 | test('renders correctly when editUrl and contributors are undefined', () => { 15 | const {queryByText} = render() 16 | 17 | expect(queryByText(/Edit this page on GitHub/)).toBeNull() 18 | expect(queryByText(/contributor/)).toBeNull() 19 | }) 20 | 21 | test('renders correctly when editUrl is defined but contributors is undefined', () => { 22 | const {queryByText} = render() 23 | 24 | expect(queryByText(/Edit this page on GitHub/)).toBeInTheDocument() 25 | expect(queryByText(/contributor/)).toBeNull() 26 | }) 27 | 28 | test('renders correctly when contributors is defined but editUrl is undefined', () => { 29 | const {queryByText} = render( 30 | , 31 | ) 32 | 33 | expect(queryByText(/Edit this page on GitHub/)).toBeNull() 34 | expect(queryByText(/contributor/)).toBeInTheDocument() 35 | }) 36 | 37 | test('does not render contributors if contributors is an empty array', () => { 38 | const {queryByText} = render() 39 | 40 | expect(queryByText(/contributor/)).toBeNull() 41 | }) 42 | -------------------------------------------------------------------------------- /src/@primer/gatsby-theme-doctocat/components/sidebar.js: -------------------------------------------------------------------------------- 1 | import {BorderBox, Flex, Position} from '@primer/components' 2 | import React from 'react' 3 | import navItems from '../nav.yml' 4 | import {HEADER_HEIGHT} from './header' 5 | import NavItems from './nav-items' 6 | 7 | function usePersistentScroll(id) { 8 | const ref = React.useRef() 9 | 10 | const handleScroll = React.useCallback( 11 | // Save scroll position in session storage on every scroll change 12 | (event) => window.sessionStorage.setItem(id, event.target.scrollTop), 13 | [id], 14 | ) 15 | 16 | React.useLayoutEffect(() => { 17 | // Restore scroll position when component mounts 18 | const scrollPosition = window.sessionStorage.getItem(id) 19 | if (scrollPosition && ref.current) { 20 | ref.current.scrollTop = scrollPosition 21 | } 22 | }, []) 23 | 24 | // Return props to spread onto the scroll container 25 | return { 26 | ref, 27 | onScroll: handleScroll, 28 | } 29 | } 30 | 31 | function Sidebar() { 32 | const scrollContainerProps = usePersistentScroll('sidebar') 33 | 34 | return ( 35 | 43 | 51 | 52 | 53 | 54 | 55 | 56 | ) 57 | } 58 | 59 | export default Sidebar 60 | -------------------------------------------------------------------------------- /.specify/scripts/bash/setup-plan.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | # Parse command line arguments 6 | JSON_MODE=false 7 | ARGS=() 8 | 9 | for arg in "$@"; do 10 | case "$arg" in 11 | --json) 12 | JSON_MODE=true 13 | ;; 14 | --help|-h) 15 | echo "Usage: $0 [--json]" 16 | echo " --json Output results in JSON format" 17 | echo " --help Show this help message" 18 | exit 0 19 | ;; 20 | *) 21 | ARGS+=("$arg") 22 | ;; 23 | esac 24 | done 25 | 26 | # Get script directory and load common functions 27 | SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 28 | source "$SCRIPT_DIR/common.sh" 29 | 30 | # Get all paths and variables from common functions 31 | eval $(get_feature_paths) 32 | 33 | # Check if we're on a proper feature branch (only for git repos) 34 | check_feature_branch "$CURRENT_BRANCH" "$HAS_GIT" || exit 1 35 | 36 | # Ensure the feature directory exists 37 | mkdir -p "$FEATURE_DIR" 38 | 39 | # Copy plan template if it exists 40 | TEMPLATE="$REPO_ROOT/.specify/templates/plan-template.md" 41 | if [[ -f "$TEMPLATE" ]]; then 42 | cp "$TEMPLATE" "$IMPL_PLAN" 43 | echo "Copied plan template to $IMPL_PLAN" 44 | else 45 | echo "Warning: Plan template not found at $TEMPLATE" 46 | # Create a basic plan file if template doesn't exist 47 | touch "$IMPL_PLAN" 48 | fi 49 | 50 | # Output results 51 | if $JSON_MODE; then 52 | printf '{"FEATURE_SPEC":"%s","IMPL_PLAN":"%s","SPECS_DIR":"%s","BRANCH":"%s","HAS_GIT":"%s"}\n' \ 53 | "$FEATURE_SPEC" "$IMPL_PLAN" "$FEATURE_DIR" "$CURRENT_BRANCH" "$HAS_GIT" 54 | else 55 | echo "FEATURE_SPEC: $FEATURE_SPEC" 56 | echo "IMPL_PLAN: $IMPL_PLAN" 57 | echo "SPECS_DIR: $FEATURE_DIR" 58 | echo "BRANCH: $CURRENT_BRANCH" 59 | echo "HAS_GIT: $HAS_GIT" 60 | fi 61 | -------------------------------------------------------------------------------- /src/@primer/gatsby-theme-doctocat/components/details.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | 4 | // The
element is not yet supported in Edge so we have to use a polyfill. 5 | // We have to check if window is defined before importing the polyfill 6 | // so the code doesn’t run while Gatsby is building. 7 | if (typeof window !== 'undefined') { 8 | // eslint-disable-next-line no-unused-expressions 9 | import('details-element-polyfill') 10 | } 11 | 12 | // TODO: Replace this Details component with the one from @primer/components when 14.0.0 is released. 13 | // Reference: https://github.com/primer/components/pull/499 14 | 15 | const DetailsReset = styled.details` 16 | & > summary { 17 | list-style: none; 18 | } 19 | 20 | & > summary::-webkit-details-marker { 21 | display: none; 22 | } 23 | 24 | & > summary::before { 25 | display: none; 26 | } 27 | ` 28 | 29 | function getRenderer(children) { 30 | return typeof children === 'function' ? children : () => children 31 | } 32 | 33 | function Details({children, overlay, render = getRenderer(children), ...rest}) { 34 | const [open, setOpen] = React.useState(Boolean(rest.open)) 35 | 36 | function toggle(event) { 37 | if (event) event.preventDefault() 38 | if (overlay) { 39 | openMenu() 40 | } else { 41 | setOpen(!open) 42 | } 43 | } 44 | 45 | function openMenu() { 46 | if (!open) { 47 | setOpen(true) 48 | document.addEventListener('click', closeMenu) 49 | } 50 | } 51 | 52 | function closeMenu() { 53 | setOpen(false) 54 | document.removeEventListener('click', closeMenu) 55 | } 56 | 57 | return ( 58 | 59 | {render({open, toggle})} 60 | 61 | ) 62 | } 63 | 64 | Details.defaultProps = { 65 | overlay: false, 66 | } 67 | 68 | export default Details 69 | -------------------------------------------------------------------------------- /src/@primer/gatsby-theme-doctocat/components/drawer.js: -------------------------------------------------------------------------------- 1 | import {Fixed} from '@primer/components' 2 | import {AnimatePresence, motion} from 'framer-motion' 3 | import React from 'react' 4 | import {FocusOn} from 'react-focus-on' 5 | 6 | function Drawer({isOpen, onDismiss, children}) { 7 | return ( 8 | 9 | {isOpen ? ( 10 |
event.preventDefault()} 15 | onClick={event => event.target.focus()} 16 | > 17 | onDismiss()}> 18 | onDismiss()} 31 | /> 32 | 33 | 47 | {children} 48 | 49 | 50 |
51 | ) : null} 52 |
53 | ) 54 | } 55 | 56 | export default Drawer 57 | -------------------------------------------------------------------------------- /src/@primer/gatsby-theme-doctocat/components/code.js: -------------------------------------------------------------------------------- 1 | import { Absolute, Box, Relative, Text } from '@primer/components' 2 | import Highlight, { defaultProps } from 'prism-react-renderer' 3 | import Prism from '../prism' 4 | import githubTheme from '../github' 5 | import React from 'react' 6 | import ClipboardCopy from './clipboard-copy' 7 | import LiveCode from './live-code' 8 | 9 | function Code({className, children, live, noinline}) { 10 | const language = className ? className.replace(/language-/, '') : '' 11 | const code = children.trim() 12 | 13 | if (live) { 14 | return 15 | } 16 | 17 | return ( 18 | 19 | 20 | 21 | 22 | 29 | {({className, style, tokens, getLineProps, getTokenProps}) => ( 30 | 40 | {tokens.map((line, i) => ( 41 |
42 | {line.map((token, key) => ( 43 | 49 | ))} 50 |
51 | ))} 52 |
53 | )} 54 |
55 |
56 | ) 57 | } 58 | 59 | export default Code 60 | -------------------------------------------------------------------------------- /src/@primer/gatsby-theme-doctocat/components/contributors.js: -------------------------------------------------------------------------------- 1 | import {Avatar, Flex, Link, Text, Tooltip} from '@primer/components' 2 | import {format} from 'date-fns' 3 | import uniqBy from 'lodash.uniqby' 4 | import pluralize from 'pluralize' 5 | import React from 'react' 6 | 7 | // The `contributors` array is fetched in gatsby-node.js at build-time. 8 | 9 | function Contributors({contributors}) { 10 | const uniqueContributors = uniqBy(contributors, 'login') 11 | const latestContributor = uniqueContributors[0] || {} 12 | 13 | return ( 14 |
15 | 16 | 17 | {uniqueContributors.length}{' '} 18 | {pluralize('contributor', uniqueContributors.length)} 19 | 20 | {uniqueContributors.map(contributor => ( 21 | 27 | 28 | 32 | 33 | 34 | ))} 35 | 36 | 37 | {latestContributor.latestCommit ? ( 38 | 39 | Last edited by{' '} 40 | 41 | {latestContributor.login} 42 | {' '} 43 | on{' '} 44 | 45 | {format(new Date(latestContributor.latestCommit.date), 'MMMM d, y')} 46 | 47 | 48 | ) : null} 49 |
50 | ) 51 | } 52 | 53 | export default Contributors 54 | -------------------------------------------------------------------------------- /src/@primer/gatsby-theme-doctocat/use-search.js: -------------------------------------------------------------------------------- 1 | import {graphql, useStaticQuery} from 'gatsby' 2 | import path from 'path' 3 | import React from 'react' 4 | import SearchWorker from 'worker-loader!./search.worker.js' 5 | 6 | const ensureAbsolute = uri => uri.startsWith('/') ? uri : `/${uri}` 7 | 8 | function useSearch(query) { 9 | const latestQuery = React.useRef(query) 10 | const workerRef = React.useRef() 11 | 12 | const data = useStaticQuery(graphql` 13 | { 14 | allMdx { 15 | nodes { 16 | fileAbsolutePath 17 | frontmatter { 18 | title 19 | } 20 | rawBody 21 | parent { 22 | ... on File { 23 | relativeDirectory 24 | name 25 | } 26 | } 27 | } 28 | } 29 | } 30 | `) 31 | 32 | const list = React.useMemo( 33 | () => 34 | data.allMdx.nodes.map(node => ({ 35 | path: ensureAbsolute(path.join( 36 | node.parent.relativeDirectory, 37 | node.parent.name === 'index' ? '/' : node.parent.name, 38 | )), 39 | title: node.frontmatter.title, 40 | rawBody: node.rawBody, 41 | })), 42 | [data], 43 | ) 44 | 45 | const [results, setResults] = React.useState(list) 46 | 47 | const handleSearchResults = React.useCallback(({data}) => { 48 | if (data.query && data.results && data.query === latestQuery.current) { 49 | setResults(data.results) 50 | } 51 | }, []) 52 | 53 | React.useEffect(() => { 54 | const worker = new SearchWorker() 55 | worker.addEventListener('message', handleSearchResults) 56 | worker.postMessage({list}) 57 | workerRef.current = worker 58 | 59 | return () => { 60 | workerRef.current.terminate() 61 | } 62 | }, [list, handleSearchResults]) 63 | 64 | React.useEffect(() => { 65 | latestQuery.current = query 66 | if (query && workerRef.current) { 67 | workerRef.current.postMessage({query: query}) 68 | } else { 69 | setResults(list) 70 | } 71 | }, [query, list]) 72 | 73 | return results 74 | } 75 | 76 | export default useSearch 77 | -------------------------------------------------------------------------------- /src/@primer/gatsby-theme-doctocat/components/__tests__/contributors.test.js: -------------------------------------------------------------------------------- 1 | import {render} from '@testing-library/react' 2 | import React from 'react' 3 | import Contributors from '../contributors' 4 | 5 | test('renders contributors', () => { 6 | const {queryByText} = render( 7 | , 25 | ) 26 | 27 | expect(queryByText(/2 contributors/)).toBeInTheDocument() 28 | expect(queryByText(/Last edited by/)).toBeInTheDocument() 29 | expect(queryByText(/colebemis/)).toBeInTheDocument() 30 | expect(queryByText(/August 15, 2019/)).toBeInTheDocument() 31 | }) 32 | 33 | test('does not render "last edited by" if latest contributor does not have a latest commit', () => { 34 | const {queryByText} = render( 35 | , 36 | ) 37 | 38 | expect(queryByText(/1 contributor/)).toBeInTheDocument() 39 | expect(queryByText(/Last edited by/)).toBeNull() 40 | }) 41 | 42 | // The `Contributors` component is unlikely to be passed an empty array 43 | // but it should be able to handle an empty array gracefully just in case. 44 | test('handles no contributors', () => { 45 | const {queryByText} = render() 46 | 47 | expect(queryByText(/0 contributors/)).toBeInTheDocument() 48 | }) 49 | 50 | test('does not render duplicate contributors', () => { 51 | const {queryByText} = render( 52 | , 70 | ) 71 | 72 | expect(queryByText(/1 contributor/)).toBeInTheDocument() 73 | }) 74 | -------------------------------------------------------------------------------- /.claude/commands/plan.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Execute the implementation planning workflow using the plan template to generate design artifacts. 3 | --- 4 | 5 | The user input to you can be provided directly by the agent or as a command argument - you **MUST** consider it before proceeding with the prompt (if not empty). 6 | 7 | User input: 8 | 9 | $ARGUMENTS 10 | 11 | Given the implementation details provided as an argument, do this: 12 | 13 | 1. Run `.specify/scripts/bash/setup-plan.sh --json` from the repo root and parse JSON for FEATURE_SPEC, IMPL_PLAN, SPECS_DIR, BRANCH. All future file paths must be absolute. 14 | - BEFORE proceeding, inspect FEATURE_SPEC for a `## Clarifications` section with at least one `Session` subheading. If missing or clearly ambiguous areas remain (vague adjectives, unresolved critical choices), PAUSE and instruct the user to run `/clarify` first to reduce rework. Only continue if: (a) Clarifications exist OR (b) an explicit user override is provided (e.g., "proceed without clarification"). Do not attempt to fabricate clarifications yourself. 15 | 2. Read and analyze the feature specification to understand: 16 | - The feature requirements and user stories 17 | - Functional and non-functional requirements 18 | - Success criteria and acceptance criteria 19 | - Any technical constraints or dependencies mentioned 20 | 21 | 3. Read the constitution at `.specify/memory/constitution.md` to understand constitutional requirements. 22 | 23 | 4. Execute the implementation plan template: 24 | - Load `.specify/templates/plan-template.md` (already copied to IMPL_PLAN path) 25 | - Set Input path to FEATURE_SPEC 26 | - Run the Execution Flow (main) function steps 1-9 27 | - The template is self-contained and executable 28 | - Follow error handling and gate checks as specified 29 | - Let the template guide artifact generation in $SPECS_DIR: 30 | * Phase 0 generates research.md 31 | * Phase 1 generates data-model.md, contracts/, quickstart.md 32 | * Phase 2 generates tasks.md 33 | - Incorporate user-provided details from arguments into Technical Context: $ARGUMENTS 34 | - Update Progress Tracking as you complete each phase 35 | 36 | 5. Verify execution completed: 37 | - Check Progress Tracking shows all phases complete 38 | - Ensure all required artifacts were generated 39 | - Confirm no ERROR states in execution 40 | 41 | 6. Report results with branch name, file paths, and generated artifacts. 42 | 43 | Use absolute paths with the repository root for all file operations to avoid path issues. 44 | -------------------------------------------------------------------------------- /src/@primer/gatsby-theme-doctocat/components/nav-items.js: -------------------------------------------------------------------------------- 1 | import {BorderBox, Flex, StyledOcticon, Link, themeGet} from '@primer/components' 2 | import {LinkExternalIcon} from '@primer/octicons-react' 3 | import {Link as GatsbyLink} from 'gatsby' 4 | import preval from 'preval.macro' 5 | import React from 'react' 6 | import styled from 'styled-components' 7 | 8 | // This code needs to run at build-time so it can access the file system. 9 | const repositoryUrl = preval` 10 | const readPkgUp = require('read-pkg-up') 11 | const getPkgRepo = require('get-pkg-repo') 12 | try { 13 | const repo = getPkgRepo(readPkgUp.sync().package) 14 | module.exports = \`https://github.com/\${repo.user}/\${repo.project}\` 15 | } catch (error) { 16 | module.exports = '' 17 | } 18 | ` 19 | 20 | const NavLink = styled(Link)` 21 | &.active { 22 | font-weight: ${themeGet('fontWeights.bold')}; 23 | color: ${themeGet('colors.gray.8')}; 24 | } 25 | ` 26 | 27 | function NavItems({items}) { 28 | return ( 29 | <> 30 | {items.map((item) => ( 31 | 38 | 39 | 46 | {item.title} 47 | 48 | {item.children ? ( 49 | 50 | {item.children.map((child) => ( 51 | 61 | {child.title} 62 | 63 | ))} 64 | 65 | ) : null} 66 | 67 | 68 | ))} 69 | {repositoryUrl ? ( 70 | 71 | 72 | 73 | GitHub 74 | 75 | 76 | 77 | 78 | ) : null} 79 | 80 | ) 81 | } 82 | 83 | export default NavItems 84 | -------------------------------------------------------------------------------- /.claude/commands/tasks.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Generate an actionable, dependency-ordered tasks.md for the feature based on available design artifacts. 3 | --- 4 | 5 | The user input to you can be provided directly by the agent or as a command argument - you **MUST** consider it before proceeding with the prompt (if not empty). 6 | 7 | User input: 8 | 9 | $ARGUMENTS 10 | 11 | 1. Run `.specify/scripts/bash/check-prerequisites.sh --json` from repo root and parse FEATURE_DIR and AVAILABLE_DOCS list. All paths must be absolute. 12 | 2. Load and analyze available design documents: 13 | - Always read plan.md for tech stack and libraries 14 | - IF EXISTS: Read data-model.md for entities 15 | - IF EXISTS: Read contracts/ for API endpoints 16 | - IF EXISTS: Read research.md for technical decisions 17 | - IF EXISTS: Read quickstart.md for test scenarios 18 | 19 | Note: Not all projects have all documents. For example: 20 | - CLI tools might not have contracts/ 21 | - Simple libraries might not need data-model.md 22 | - Generate tasks based on what's available 23 | 24 | 3. Generate tasks following the template: 25 | - Use `.specify/templates/tasks-template.md` as the base 26 | - Replace example tasks with actual tasks based on: 27 | * **Setup tasks**: Project init, dependencies, linting 28 | * **Test tasks [P]**: One per contract, one per integration scenario 29 | * **Core tasks**: One per entity, service, CLI command, endpoint 30 | * **Integration tasks**: DB connections, middleware, logging 31 | * **Polish tasks [P]**: Unit tests, performance, docs 32 | 33 | 4. Task generation rules: 34 | - Each contract file → contract test task marked [P] 35 | - Each entity in data-model → model creation task marked [P] 36 | - Each endpoint → implementation task (not parallel if shared files) 37 | - Each user story → integration test marked [P] 38 | - Different files = can be parallel [P] 39 | - Same file = sequential (no [P]) 40 | 41 | 5. Order tasks by dependencies: 42 | - Setup before everything 43 | - Tests before implementation (TDD) 44 | - Models before services 45 | - Services before endpoints 46 | - Core before integration 47 | - Everything before polish 48 | 49 | 6. Include parallel execution examples: 50 | - Group [P] tasks that can run together 51 | - Show actual Task agent commands 52 | 53 | 7. Create FEATURE_DIR/tasks.md with: 54 | - Correct feature name from implementation plan 55 | - Numbered tasks (T001, T002, etc.) 56 | - Clear file paths for each task 57 | - Dependency notes 58 | - Parallel execution guidance 59 | 60 | Context for task generation: $ARGUMENTS 61 | 62 | The tasks.md should be immediately executable - each task must be specific enough that an LLM can complete it without additional context. 63 | -------------------------------------------------------------------------------- /src/@primer/gatsby-theme-doctocat/components/heading.js: -------------------------------------------------------------------------------- 1 | import {Heading, Link} from '@primer/components' 2 | import {LinkIcon} from '@primer/octicons-react' 3 | import themeGet from '@styled-system/theme-get' 4 | import GithubSlugger from 'github-slugger' 5 | import React from 'react' 6 | import textContent from 'react-addons-text-content' 7 | import styled from 'styled-components' 8 | import {HEADER_HEIGHT} from './header' 9 | 10 | const StyledHeading = styled(Heading)` 11 | margin-top: ${themeGet('space.4')}; 12 | margin-bottom: ${themeGet('space.3')}; 13 | scroll-margin-top: ${HEADER_HEIGHT + 24}px; 14 | 15 | & .octicon-link { 16 | visibility: hidden; 17 | } 18 | 19 | &:hover .octicon-link, 20 | &:focus-within .octicon-link { 21 | visibility: visible; 22 | } 23 | ` 24 | 25 | function MarkdownHeading({children, ...props}) { 26 | const slugger = new GithubSlugger() 27 | const text = children ? textContent(children) : '' 28 | const id = text ? slugger.slug(text) : '' 29 | 30 | return ( 31 | 32 | 39 | 40 | 41 | {children} 42 | 43 | ) 44 | } 45 | 46 | const StyledH1 = styled(StyledHeading).attrs({as: 'h1'})` 47 | padding-bottom: ${themeGet('space.1')}; 48 | font-size: ${themeGet('fontSizes.5')}; 49 | border-bottom: 1px solid ${themeGet('colors.gray.2')}; 50 | ` 51 | 52 | const StyledH2 = styled(StyledHeading).attrs({as: 'h2'})` 53 | padding-bottom: ${themeGet('space.1')}; 54 | font-size: ${themeGet('fontSizes.4')}; 55 | border-bottom: 1px solid ${themeGet('colors.gray.2')}; 56 | ` 57 | 58 | const StyledH3 = styled(StyledHeading).attrs({as: 'h3'})` 59 | font-size: ${themeGet('fontSizes.3')}; 60 | ` 61 | 62 | const StyledH4 = styled(StyledHeading).attrs({as: 'h4'})` 63 | font-size: ${themeGet('fontSizes.2')}; 64 | ` 65 | 66 | const StyledH5 = styled(StyledHeading).attrs({as: 'h5'})` 67 | font-size: ${themeGet('fontSizes.1')}; 68 | ` 69 | 70 | const StyledH6 = styled(StyledHeading).attrs({as: 'h6'})` 71 | font-size: ${themeGet('fontSizes.1')}; 72 | color: ${themeGet('colors.gray.5')}; 73 | ` 74 | 75 | export const H1 = (props) => 76 | export const H2 = (props) => 77 | export const H3 = (props) => 78 | export const H4 = (props) => 79 | export const H5 = (props) => 80 | export const H6 = (props) => 81 | -------------------------------------------------------------------------------- /src/@primer/gatsby-theme-doctocat/components/search.js: -------------------------------------------------------------------------------- 1 | import {BorderBox, Position} from '@primer/components' 2 | import Downshift from 'downshift' 3 | import {navigate} from 'gatsby' 4 | import React from 'react' 5 | import useSearch from '../use-search' 6 | import useSiteMetadata from '../use-site-metadata' 7 | import DarkTextInput from './dark-text-input' 8 | import SearchResults from './search-results' 9 | 10 | function stateReducer(state, changes) { 11 | switch (changes.type) { 12 | case Downshift.stateChangeTypes.changeInput: 13 | if (!changes.inputValue) { 14 | // Close the menu if the input is empty. 15 | return {...changes, isOpen: false} 16 | } 17 | return changes 18 | default: 19 | return changes 20 | } 21 | } 22 | 23 | function Search() { 24 | const [query, setQuery] = React.useState('') 25 | const results = useSearch(query) 26 | const siteMetadata = useSiteMetadata() 27 | 28 | return ( 29 | setQuery(inputValue)} 32 | // We don't need Downshift to keep track of a selected item because as 33 | // soon as an item is selected we navigate to a new page. 34 | // Let's avoid any unexpected states related to the selected item 35 | // by setting it to always be `null`. 36 | selectedItem={null} 37 | onSelect={item => { 38 | if (item) { 39 | navigate(item.path) 40 | setQuery('') 41 | } 42 | }} 43 | itemToString={item => (item ? item.title : '')} 44 | stateReducer={stateReducer} 45 | > 46 | {({ 47 | getInputProps, 48 | getItemProps, 49 | getMenuProps, 50 | getRootProps, 51 | isOpen, 52 | highlightedIndex, 53 | }) => ( 54 | 55 | 61 | {isOpen ? ( 62 | 70 | 78 | 83 | 84 | 85 | ) : null} 86 | 87 | )} 88 | 89 | ) 90 | } 91 | 92 | export default Search 93 | -------------------------------------------------------------------------------- /src/@primer/gatsby-theme-doctocat/components/live-code.js: -------------------------------------------------------------------------------- 1 | import {Absolute, BorderBox, Flex, Relative, Text} from '@primer/components' 2 | import htmlReactParser from 'html-react-parser' 3 | import githubTheme from '../github' 4 | import React, {useState} from 'react' 5 | import reactElementToJsxString from 'react-element-to-jsx-string' 6 | import {LiveEditor, LiveError, LivePreview, LiveProvider} from 'react-live' 7 | import {ThemeContext} from 'styled-components' 8 | import scope from '../live-code-scope' 9 | import ClipboardCopy from './clipboard-copy' 10 | import LivePreviewWrapper from './live-preview-wrapper' 11 | 12 | const languageTransformers = { 13 | html: (html) => htmlToJsx(html), 14 | jsx: (jsx) => wrapWithFragment(jsx), 15 | } 16 | 17 | function htmlToJsx(html) { 18 | try { 19 | const reactElement = htmlReactParser(removeNewlines(html)) 20 | // The output of htmlReactParser could be a single React element 21 | // or an array of React elements. reactElementToJsxString does not accept arrays 22 | // so we have to wrap the output in React fragment. 23 | return reactElementToJsxString(<>{reactElement}) 24 | } catch (error) { 25 | return wrapWithFragment(html) 26 | } 27 | } 28 | 29 | function removeNewlines(string) { 30 | return string.replace(/(\r\n|\n|\r)/gm, '') 31 | } 32 | 33 | function wrapWithFragment(jsx) { 34 | return `${jsx}` 35 | } 36 | 37 | function LiveCode({code, language, noinline}) { 38 | const theme = React.useContext(ThemeContext) 39 | const [liveCode, setLiveCode] = useState(code) 40 | const handleChange = (updatedLiveCode) => setLiveCode(updatedLiveCode) 41 | 42 | return ( 43 | 44 | 50 | 58 | 59 | 60 | 61 | 62 | 63 | 75 | 76 | 77 | 78 | 79 | 88 | 89 | 90 | ) 91 | } 92 | 93 | export default LiveCode 94 | -------------------------------------------------------------------------------- /content/pages/160-null-undefined.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: null과 undefined 3 | --- 4 | 5 | JavaScript에는 '없음'를 나타내는 값이 두 개 있는데, 바로 `null`와 `undefined`입니다. 두 값의 의미는 비슷하지만, 각각이 사용되는 목적과 장소가 다릅니다. 6 | 7 | JavaScript는 값이 대입되지 않은 변수 혹은 속성을 사용하려고 하면 `undefined`를 반환합니다. 8 | 9 | ```js 10 | let foo 11 | foo // undefined 12 | 13 | const obj = {} 14 | obj.prop // undefined 15 | ``` 16 | 17 | `null`은 '객체가 없음'을 나타냅니다. 실제로 `typeof` 연산을 해보면 아래와 같은 값을 반환합니다. 18 | 19 | ```js 20 | typeof null // 'object' 21 | typeof undefined // 'undefined' 22 | ``` 23 | 24 | 그렇다면 개발자의 입장에서 **'없음'**을 저장하기 위해 둘 중 어떤 것을 써야 할까요? `undefined`를 쓴다고 가정해보면, 아래와 같은 코드는 그 의미가 불분명해집니다. 25 | 26 | ```js 27 | let foo // 값을 대입한 적 없음 28 | let bar = undefined // 값을 대입함 29 | foo // undefined 30 | bar // undefined (??) 31 | let obj1 = {} // 속성을 지정하지 않음 32 | let obj2 = {prop: undefined} // 속성을 지정함 33 | obj1.prop // undefined 34 | obj2.prop // undefined (??) 35 | ``` 36 | 37 | 비록 `undefined`가 '없음'을 나타내는 값일지라도, 대입한 적 없는 변수 혹은 속성과, 명시적으로 '없음'을 나타내는 경우를 **구분**을 할 수 있어야 코드의 의미가 명확해 질 것입니다. **프로그래머의 입장에서 명시적으로 부재를 나타내고 싶다면 항상 null을 사용**하는 것이 좋습니다. 38 | 39 | 다만, 객체를 사용할 때 어떤 속성의 부재를 `null`을 통해서 나타내는 쪽보다는, 그냥 그 속성을 정의하지 않는 방식이 간편하므로 더 널리 사용됩니다. 40 | 41 | ```js 42 | // 이렇게 하는 경우는 많지 않습니다. 43 | { 44 | name: 'Seungha', 45 | address: null 46 | } 47 | 48 | // 그냥 이렇게 하는 경우가 많습니다. 49 | { 50 | name: 'Seungha' 51 | } 52 | 53 | // 어쨌든 이렇게 하지는 말아주세요. 54 | { 55 | name: 'Seungha', 56 | address: undefined 57 | } 58 | ``` 59 | 60 | ## Null Check 61 | 62 | `null`이나 `undefined`는 어떤 변수에도, 어떤 속성에도 들어있을 수 있기 때문에 우리는 코드를 짤 때 값이 있는 경우와 없는 경우 (즉 `null` 혹은 `undefined`인 경우)를 모두 고려해서 코드를 짜야 할 필요가 있습니다. 어떤 값이 `null` 혹은 `undefined`인지 확인하는 작업을 **null check**라고 합니다. null check는 간단히 등호를 이용해서 할 수 있습니다. 63 | 64 | ```js 65 | function printIfNotNull(input) { 66 | if (input !== null && input !== undefined) { 67 | console.log(input) 68 | } 69 | } 70 | ``` 71 | 72 | 그런데 매 번 위처럼 긴 비교를 해야 한다는 것은 골치아픈 일입니다. 사실은 위 `if` 구문 안에 있는 식을 다음과 같이 줄여 쓸 수 있습니다. 73 | 74 | ```js 75 | // 아래 세 개의 식은 완전히 같은 의미입니다. 76 | input !== null && input !== undefined 77 | input != null 78 | input != undefined 79 | 80 | // 아래 세 개의 식은 완전히 같은 의미입니다. 81 | input === null || input === undefined 82 | input == null 83 | input == undefined 84 | ``` 85 | 86 | 이제까지 세 글자 짜리 등호만을 소개했는데, 사실 JavaScript에는 **두 글자 짜리 등호**도 있습니다. 각각의 공식적인 명칭은 **strict equality** comparison operator, **abstract equality** comparison operator 입니다. 이름에서도 알 수 있듯이, 대개 `===`는 값이 정확히 같을 때 `true`라는 결과값을 반환하고, `==`는 그렇지 않을 때가 많습니다. 그래서 보통의 경우 `===`를 사용하는 것이 권장되는 편입니다. 87 | 88 | 다만 null check를 할 때 만큼은 `==`를 쓰는 것이 편리합니다. 아래 예제를 통해 설명하겠습니다. 89 | 90 | `==`와 `===` 두 연산자는 `null`과 `undefined`에 대해서 아래와 같이 동작합니다. 91 | 92 | ```js 93 | null === undefined // false 94 | null == undefined // true 95 | 96 | null == 1 // false 97 | null == 'hello' // false 98 | null == false // false 99 | 100 | undefined == 1 // false 101 | undefined == 'hello' // false 102 | undefined == false // false 103 | ``` 104 | 105 | 즉, `==` 연산자는 한 쪽 피연산자에 `null` 혹은 `undefined`가 오면, 다른 쪽 피연산자에 `null` 혹은 `undefined`가 왔을 때만 `true`를 반환하고, 다른 모든 경우에 `false`를 반환합니다. 106 | 107 | 따라서 null check를 할 때 만큼은 `==`를 사용하는 것이 편합니다. 다른 모든 경우에는 `===`를 사용하는 것이 좋습니다. 108 | 109 | `===`와 `==`에 대한 자세한 내용은 [연산자 더 알아보기](./245-operator-in-depth.html) 챕터에서 다룹니다. 110 | -------------------------------------------------------------------------------- /.specify/scripts/bash/create-new-feature.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | JSON_MODE=false 6 | ARGS=() 7 | for arg in "$@"; do 8 | case "$arg" in 9 | --json) JSON_MODE=true ;; 10 | --help|-h) echo "Usage: $0 [--json] "; exit 0 ;; 11 | *) ARGS+=("$arg") ;; 12 | esac 13 | done 14 | 15 | FEATURE_DESCRIPTION="${ARGS[*]}" 16 | if [ -z "$FEATURE_DESCRIPTION" ]; then 17 | echo "Usage: $0 [--json] " >&2 18 | exit 1 19 | fi 20 | 21 | # Function to find the repository root by searching for existing project markers 22 | find_repo_root() { 23 | local dir="$1" 24 | while [ "$dir" != "/" ]; do 25 | if [ -d "$dir/.git" ] || [ -d "$dir/.specify" ]; then 26 | echo "$dir" 27 | return 0 28 | fi 29 | dir="$(dirname "$dir")" 30 | done 31 | return 1 32 | } 33 | 34 | # Resolve repository root. Prefer git information when available, but fall back 35 | # to searching for repository markers so the workflow still functions in repositories that 36 | # were initialised with --no-git. 37 | SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 38 | 39 | if git rev-parse --show-toplevel >/dev/null 2>&1; then 40 | REPO_ROOT=$(git rev-parse --show-toplevel) 41 | HAS_GIT=true 42 | else 43 | REPO_ROOT="$(find_repo_root "$SCRIPT_DIR")" 44 | if [ -z "$REPO_ROOT" ]; then 45 | echo "Error: Could not determine repository root. Please run this script from within the repository." >&2 46 | exit 1 47 | fi 48 | HAS_GIT=false 49 | fi 50 | 51 | cd "$REPO_ROOT" 52 | 53 | SPECS_DIR="$REPO_ROOT/specs" 54 | mkdir -p "$SPECS_DIR" 55 | 56 | HIGHEST=0 57 | if [ -d "$SPECS_DIR" ]; then 58 | for dir in "$SPECS_DIR"/*; do 59 | [ -d "$dir" ] || continue 60 | dirname=$(basename "$dir") 61 | number=$(echo "$dirname" | grep -o '^[0-9]\+' || echo "0") 62 | number=$((10#$number)) 63 | if [ "$number" -gt "$HIGHEST" ]; then HIGHEST=$number; fi 64 | done 65 | fi 66 | 67 | NEXT=$((HIGHEST + 1)) 68 | FEATURE_NUM=$(printf "%03d" "$NEXT") 69 | 70 | BRANCH_NAME=$(echo "$FEATURE_DESCRIPTION" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/-\+/-/g' | sed 's/^-//' | sed 's/-$//') 71 | WORDS=$(echo "$BRANCH_NAME" | tr '-' '\n' | grep -v '^$' | head -3 | tr '\n' '-' | sed 's/-$//') 72 | BRANCH_NAME="${FEATURE_NUM}-${WORDS}" 73 | 74 | if [ "$HAS_GIT" = true ]; then 75 | git checkout -b "$BRANCH_NAME" 76 | else 77 | >&2 echo "[specify] Warning: Git repository not detected; skipped branch creation for $BRANCH_NAME" 78 | fi 79 | 80 | FEATURE_DIR="$SPECS_DIR/$BRANCH_NAME" 81 | mkdir -p "$FEATURE_DIR" 82 | 83 | TEMPLATE="$REPO_ROOT/.specify/templates/spec-template.md" 84 | SPEC_FILE="$FEATURE_DIR/spec.md" 85 | if [ -f "$TEMPLATE" ]; then cp "$TEMPLATE" "$SPEC_FILE"; else touch "$SPEC_FILE"; fi 86 | 87 | # Set the SPECIFY_FEATURE environment variable for the current session 88 | export SPECIFY_FEATURE="$BRANCH_NAME" 89 | 90 | if $JSON_MODE; then 91 | printf '{"BRANCH_NAME":"%s","SPEC_FILE":"%s","FEATURE_NUM":"%s"}\n' "$BRANCH_NAME" "$SPEC_FILE" "$FEATURE_NUM" 92 | else 93 | echo "BRANCH_NAME: $BRANCH_NAME" 94 | echo "SPEC_FILE: $SPEC_FILE" 95 | echo "FEATURE_NUM: $FEATURE_NUM" 96 | echo "SPECIFY_FEATURE environment variable set to: $BRANCH_NAME" 97 | fi 98 | -------------------------------------------------------------------------------- /.claude/commands/implement.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Execute the implementation plan by processing and executing all tasks defined in tasks.md 3 | --- 4 | 5 | The user input can be provided directly by the agent or as a command argument - you **MUST** consider it before proceeding with the prompt (if not empty). 6 | 7 | User input: 8 | 9 | $ARGUMENTS 10 | 11 | 1. Run `.specify/scripts/bash/check-prerequisites.sh --json --require-tasks --include-tasks` from repo root and parse FEATURE_DIR and AVAILABLE_DOCS list. All paths must be absolute. 12 | 13 | 2. Load and analyze the implementation context: 14 | - **REQUIRED**: Read tasks.md for the complete task list and execution plan 15 | - **REQUIRED**: Read plan.md for tech stack, architecture, and file structure 16 | - **IF EXISTS**: Read data-model.md for entities and relationships 17 | - **IF EXISTS**: Read contracts/ for API specifications and test requirements 18 | - **IF EXISTS**: Read research.md for technical decisions and constraints 19 | - **IF EXISTS**: Read quickstart.md for integration scenarios 20 | 21 | 3. Parse tasks.md structure and extract: 22 | - **Task phases**: Setup, Tests, Core, Integration, Polish 23 | - **Task dependencies**: Sequential vs parallel execution rules 24 | - **Task details**: ID, description, file paths, parallel markers [P] 25 | - **Execution flow**: Order and dependency requirements 26 | 27 | 4. Execute implementation following the task plan: 28 | - **Phase-by-phase execution**: Complete each phase before moving to the next 29 | - **Respect dependencies**: Run sequential tasks in order, parallel tasks [P] can run together 30 | - **Follow TDD approach**: Execute test tasks before their corresponding implementation tasks 31 | - **File-based coordination**: Tasks affecting the same files must run sequentially 32 | - **Validation checkpoints**: Verify each phase completion before proceeding 33 | 34 | 5. Implementation execution rules: 35 | - **Setup first**: Initialize project structure, dependencies, configuration 36 | - **Tests before code**: If you need to write tests for contracts, entities, and integration scenarios 37 | - **Core development**: Implement models, services, CLI commands, endpoints 38 | - **Integration work**: Database connections, middleware, logging, external services 39 | - **Polish and validation**: Unit tests, performance optimization, documentation 40 | 41 | 6. Progress tracking and error handling: 42 | - Report progress after each completed task 43 | - Halt execution if any non-parallel task fails 44 | - For parallel tasks [P], continue with successful tasks, report failed ones 45 | - Provide clear error messages with context for debugging 46 | - Suggest next steps if implementation cannot proceed 47 | - **IMPORTANT** For completed tasks, make sure to mark the task off as [X] in the tasks file. 48 | 49 | 7. Completion validation: 50 | - Verify all required tasks are completed 51 | - Check that implemented features match the original specification 52 | - Validate that tests pass and coverage meets requirements 53 | - Confirm the implementation follows the technical plan 54 | - Report final status with summary of completed work 55 | 56 | Note: This command assumes a complete task breakdown exists in tasks.md. If tasks are incomplete or missing, suggest running `/tasks` first to regenerate the task list. 57 | -------------------------------------------------------------------------------- /content/keyshape/TreeSVG.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import LazySVG from './LazySVG' 3 | 4 | const svgString = ` 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | ` 31 | 32 | export default function TreeSVG() { 33 | return 34 | } 35 | -------------------------------------------------------------------------------- /content/pages/150-boolean.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: boolean 타입 3 | --- 4 | 5 | boolean 타입에 해당하는 값은 `true`, `false` 두 가지 밖에 없습니다. 이 값들을 '진리값'이라고 부릅니다. 프로그래밍에서의 진리값은 어떤 조건이 참인지 거짓인지를 나타내기 위해 사용됩니다. 6 | 7 | ```js 8 | 1 < 2 // true 9 | 1 > 2 // false 10 | 3 === 3 // true 11 | 3 !== 3 // false 12 | Number.isFinite(Infinity) // false 13 | Number.isNaN(NaN) // true 14 | 'hello'.includes('ll') // true 15 | ``` 16 | 17 | ## 논리 연산자 18 | 19 | JavaScript는 진리값에 대한 여러 연산을 지원합니다. 20 | 21 | ```js 22 | // 논리 부정 (logical NOT) 23 | !true // false 24 | !false // true 25 | 26 | // 논리합 (logical OR) 27 | true || true // true 28 | true || false // true 29 | false || true // true 30 | false || false // false 31 | 32 | // 논리곱 (logical AND) 33 | true && true // true 34 | true && false // false 35 | false && true // false 36 | false && false // false 37 | 38 | // 할당 연산 (assignment operators), ES2021 39 | // ||= 는 변수의 값이 true이면 아무 변화가 일어나지 않고 false이면 우항의 값이 변수에 할당됩니다. 40 | let x = false 41 | x ||= true // true 42 | 43 | // &&= 는 변수의 값이 false이면 아무 변화가 일어나지 않고 true이면 우항의 값이 변수에 할당됩니다. 44 | let y = true 45 | y &&= false // false 46 | 47 | // ||=와 &&=는 각각 아래 연산과 같은 동작을 합니다. 48 | x = x || true 49 | y = y && false 50 | 51 | // 삼항 연산자 (ternary operator) 52 | true ? 1 : 2 // 1 53 | false ? 1 : 2 // 2 54 | ``` 55 | 56 | ## 연산자 우선순위 (Operator Precedence) 57 | 58 | 한 구문에 여러 개의 연산자를 이어서 쓴 경우, 어떤 연산자는 먼저 계산되고 어떤 연산자는 나중에 계산됩니다. 이는 연산자 우선순위(operator precedence)에 의해 결정됩니다. 자세한 내용은 [MDN 링크](https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Operators/%EC%97%B0%EC%82%B0%EC%9E%90_%EC%9A%B0%EC%84%A0%EC%88%9C%EC%9C%84)를 참고해주세요. 59 | 60 | ```js 61 | true || (true && false) // true 62 | ;(true || true) && false // false 63 | true || (false && false) // true 64 | ;(true || false) && false // false 65 | ``` 66 | 67 | ## 논리 연산의 여러 가지 법칙 68 | 69 | 논리 연산에는 여러 가지 법칙이 있습니다. 프로그래밍을 하며 논리 연산을 할 일이 많기 때문에, 이 법칙들을 알아두면 도움이 됩니다. 각 법칙이 성립하는 이유를 잘 생각해 보세요. 경우의 수를 모두 생각해보거나, 벤 다이어그램을 그려보세요.[^1] 70 | 71 | ```js 72 | // a, b, c가 **모두 boolean 타입**이라고 할 때, 다음 식의 결과값은 a, b, c의 값과 관계 없이 모두 true 입니다. 73 | 74 | // 이중 부정 75 | !!a === a 76 | 77 | // 교환 법칙 78 | a || b === b || a 79 | a && b === b && a 80 | 81 | // 결합 법칙 82 | a || b || c === a || b || c 83 | a && b && c === a && b && c 84 | 85 | // 분배 법칙 86 | a || ((b && c) === (a || b) && (a || c)) 87 | ;(a && (b || c) === (a && b)) || (a && c) 88 | 89 | // 흡수 법칙 90 | a && (a || b) === a 91 | a || (a && b) === a 92 | 93 | // 드 모르간의 법칙 94 | !(a || b) === !a && !b 95 | !(a && b) === !a || !b 96 | 97 | // 그 밖에... 98 | a || true === true 99 | a || false === a 100 | a && true === a 101 | a && false === false 102 | 103 | a || !a === true 104 | a && !a === false 105 | 106 | a || a === a 107 | a && a === a 108 | ``` 109 | 110 | ## truthy & falsy 111 | 112 | JavaScript에서는 boolean 타입이 와야 하는 자리에 다른 타입의 값이 와도 에러가 나지 않고 실행됩니다. 113 | 114 | ```js 115 | if (1) { 116 | console.log('이 코드는 실행됩니다.') 117 | } 118 | 119 | if (0) { 120 | console.log('이 코드는 실행되지 않습니다.') 121 | } 122 | ``` 123 | 124 | 이렇게 어떤 값들은 `true`로, 어떤 값들은 `false`로 취급되는데, 전자를 **truthy**, 후자를 **falsy**라고 부릅니다. JavaScript에서는 아래의 값들은 모두 falsy이고, 이를 제외한 모든 값들은 truthy입니다. 125 | 126 | - `false` 127 | - `null` 128 | - `undefined` 129 | - `0` 130 | - `NaN` 131 | - `''` 132 | 133 | truthy와 falsy를 활용하면 짧은 코드를 작성할 수 있지만, 코드의 의미가 불분명해지거나 논리적으로 놓치는 부분이 생길 수 있기 때문에 주의해서 사용해야 합니다. 134 | 135 | ## 다른 타입의 값을 진리값으로 변환하기 136 | 137 | 어떤 값을 명시적으로 boolean 타입으로 변환해야 할 때가 있는데, 그 때에는 두 가지 방법을 사용할 수 있습니다. 138 | 139 | ```js 140 | !!'hello' // true 141 | Boolean('hello') // true 142 | ``` 143 | 144 | 부정 연산자(`!`) 뒤의 값이 truthy이면 `false`, falsy이면 `true`를 반환하는 성질을 이용해서 이중 부정을 통해 값을 boolean 타입으로 변환할 수 있습니다. 혹은 `Boolean` 함수를 사용해도 됩니다. 전자가 간편하기 때문에 많이 사용되는 편입니다. 145 | 146 | [^1]: `!`은 여집합, `||`는 합집합, `&&`는 교집합, `true`는 전체집합, `false`는 공집합으로 생각하면 됩니다. 147 | -------------------------------------------------------------------------------- /.specify/scripts/bash/common.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Common functions and variables for all scripts 3 | 4 | # Get repository root, with fallback for non-git repositories 5 | get_repo_root() { 6 | if git rev-parse --show-toplevel >/dev/null 2>&1; then 7 | git rev-parse --show-toplevel 8 | else 9 | # Fall back to script location for non-git repos 10 | local script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 11 | (cd "$script_dir/../../.." && pwd) 12 | fi 13 | } 14 | 15 | # Get current branch, with fallback for non-git repositories 16 | get_current_branch() { 17 | # First check if SPECIFY_FEATURE environment variable is set 18 | if [[ -n "${SPECIFY_FEATURE:-}" ]]; then 19 | echo "$SPECIFY_FEATURE" 20 | return 21 | fi 22 | 23 | # Then check git if available 24 | if git rev-parse --abbrev-ref HEAD >/dev/null 2>&1; then 25 | git rev-parse --abbrev-ref HEAD 26 | return 27 | fi 28 | 29 | # For non-git repos, try to find the latest feature directory 30 | local repo_root=$(get_repo_root) 31 | local specs_dir="$repo_root/specs" 32 | 33 | if [[ -d "$specs_dir" ]]; then 34 | local latest_feature="" 35 | local highest=0 36 | 37 | for dir in "$specs_dir"/*; do 38 | if [[ -d "$dir" ]]; then 39 | local dirname=$(basename "$dir") 40 | if [[ "$dirname" =~ ^([0-9]{3})- ]]; then 41 | local number=${BASH_REMATCH[1]} 42 | number=$((10#$number)) 43 | if [[ "$number" -gt "$highest" ]]; then 44 | highest=$number 45 | latest_feature=$dirname 46 | fi 47 | fi 48 | fi 49 | done 50 | 51 | if [[ -n "$latest_feature" ]]; then 52 | echo "$latest_feature" 53 | return 54 | fi 55 | fi 56 | 57 | echo "main" # Final fallback 58 | } 59 | 60 | # Check if we have git available 61 | has_git() { 62 | git rev-parse --show-toplevel >/dev/null 2>&1 63 | } 64 | 65 | check_feature_branch() { 66 | local branch="$1" 67 | local has_git_repo="$2" 68 | 69 | # For non-git repos, we can't enforce branch naming but still provide output 70 | if [[ "$has_git_repo" != "true" ]]; then 71 | echo "[specify] Warning: Git repository not detected; skipped branch validation" >&2 72 | return 0 73 | fi 74 | 75 | if [[ ! "$branch" =~ ^[0-9]{3}- ]]; then 76 | echo "ERROR: Not on a feature branch. Current branch: $branch" >&2 77 | echo "Feature branches should be named like: 001-feature-name" >&2 78 | return 1 79 | fi 80 | 81 | return 0 82 | } 83 | 84 | get_feature_dir() { echo "$1/specs/$2"; } 85 | 86 | get_feature_paths() { 87 | local repo_root=$(get_repo_root) 88 | local current_branch=$(get_current_branch) 89 | local has_git_repo="false" 90 | 91 | if has_git; then 92 | has_git_repo="true" 93 | fi 94 | 95 | local feature_dir=$(get_feature_dir "$repo_root" "$current_branch") 96 | 97 | cat </dev/null) ]] && echo " ✓ $2" || echo " ✗ $2"; } 114 | -------------------------------------------------------------------------------- /content/pages/120-value-variable-type.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: 값 다루기 3 | --- 4 | 5 | ## 값과 리터럴 6 | 7 | 프로그래밍을 하며 가장 많이 하는 일은 값(value)을 다루는 것입니다. 8 | 9 | 프로그래밍 언어에서 값을 생성하는 가장 쉬운 방법은 리터럴(literal)을 사용하는 것입니다. 리터럴은 **값의 표기법**으로, 프로그래밍 언어마다 값을 표현하는 여러 가지 리터럴을 가지고 있습니다. 10 | 11 | ```js 12 | 1 // 정수 리터럴 13 | 2.5 // 부동 소수점 리터럴 14 | ;('hello') // 문자열 리터럴 15 | true // 진리값 리터럴 16 | ``` 17 | 18 | 리터럴과 연산자(operator)를 이용해 REPL에서 간단한 계산을 할 수 있습니다. 19 | 20 | ```js 21 | 1 + 2 // 3 22 | 3 * 4 // 12 23 | 'hello' + 'world' // 'helloworld' 24 | true || false // true 25 | ``` 26 | 27 | 여러 연산자에 대한 자세한 사용법은 이어지는 챕터에서 다룹니다. 28 | 29 | ## 변수 (Variable) 30 | 31 | 값을 한 번 생성한 뒤에 다시 쓰지 못한다면 아주 간단한 프로그램밖에 만들지 못할 것입니다. 그래서 프로그래밍 언어에는 대개 **값에 이름을 붙여서 다시 쓸 수 있게 만드는 기능**이 존재합니다. JavaScript에서는 여러 가지 방법을 통해 값에 이름을 붙일 수 있는데, 그 중에 두 가지[^1]를 여기서 다룹니다. 32 | 33 | `let`은 변수(variable)를 선언(declare)할 때 쓰는 키워드로, ES2015에서 도입되었습니다. 변수의 사용법은 다음과 같습니다. 34 | 35 | ```js 36 | let seven = 7 37 | ``` 38 | 39 | 위에서는 `7`이라는 값에 `seven`이라는 이름을 붙이기 위해서 다음과 같이 선언과 동시에 대입(assign)을 했습니다. 물론 변수의 선언이 끝난 이후에 대입을 하거나, 이미 값이 대입되어 있는 변수에 다른 값을 대입할 수도 있습니다. 40 | 41 | ```js 42 | let eight 43 | eight = 8 44 | let seven = 7 45 | seven = 77 46 | seven = 777 47 | ``` 48 | 49 | `const`는 재대입(reassign)이 불가능한 변수를 선언할 때 쓰는 키워드로, 역시 ES2015에 도입되었습니다. 50 | 51 | ```js 52 | const myConstant = 7 53 | ``` 54 | 55 | `const`로 변수를 선언할 때는 반드시 선언 시에 값을 대입해주어야 합니다. 값 없이 선언만 하게 되면 에러가 발생합니다. 또한 추후에 다른 값을 대입할 수 없습니다. 56 | 57 | ```js 58 | const notAssigned // Uncaught SyntaxError: Missing initializer in const declaration 59 | ``` 60 | 61 | ```js 62 | const assigned; = 1 63 | assigned = 2; // Uncaught TypeError: Assignment to constant variable. 64 | ``` 65 | 66 | `let`과 `const` 모두 한꺼번에 여러 개의 변수를 선언하는 문법을 지원합니다. 67 | 68 | ```js 69 | let one = 1, 70 | two = 2, 71 | nothing 72 | const three = 3, 73 | four = 4 74 | ``` 75 | 76 | `let`과 `const`로 선언한 이름은 다시 선언될 수 없습니다. 77 | 78 | ```js 79 | let seven = 7 80 | let seven = 77 // Uncaught SyntaxError: Identifier 'seven' has already been declared 81 | ``` 82 | 83 | ## `let`과 `const` 중 무엇을 쓸 것인가? 84 | 85 | 항상 **let 보다 const**를 사용하는 것이 좋습니다. `let`을 사용하면 의도치 않게 다른 값이 대입되어 버리는 일이 생길 수 있기 때문입니다. 정말로 재대입이 필요한 경우에만 `let`을 사용하는 것이 좋은 습관입니다. 86 | 87 | ## 식별자 88 | 89 | 위에서 사용한 변수의 이름은 모두 **식별자(Identifier)**입니다. 프로그래밍 언어에서 식별자는 어떤 개체를 유일하게 식별하기 위해 사용됩니다. JavaScript 식별자는 아래와 같이 정해진 규칙에 따라 지어져야 합니다. 90 | 91 | - 숫자, 알파벳, 달러 문자($), 언더스코어(\_)가 포함될 수 있다. 92 | - 단, 숫자로 시작되어서는 안 된다. 93 | - 예약어는 식별자가 될 수 없다. 94 | 95 | ```js 96 | const foo; // O 97 | const _bar123; // O 98 | const $; // O - jQuery가 이 식별자를 사용합니다. 99 | const 7seven; // X 100 | const const; // X - 예약어는 식별자가 될 수 없습니다. 101 | ``` 102 | 103 | 여기서 예약어(reserved word)란 JavaScript 언어의 기능을 위해 미리 예약해둔 단어들을 말합니다. JavaScript의 예약어 목록을 [이 링크](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#reserved_words)에서 확인할 수 있습니다. 104 | 105 | `let 패스트 = '캠퍼스'`와 같이 한글도 식별자에 포함될 수 있지만, 좋은 습관은 아닙니다. 106 | 107 | ### Camel Case 108 | 109 | 식별자 이름을 지을 때 JavaScript에서 널리 사용되는 관례(convention)가 있는데, 이 관례를 Camel case라고 부릅니다. 식별자에 들어가는 각 단어의 첫 글자를 대문자로 써 주는 방식입니다. 110 | 111 | ```js 112 | // Camel case 113 | let fastCampus 114 | let fooBar 115 | ``` 116 | 117 | 이와는 다르게 대문자를 사용하지 않고 단어 사이에 언더스코어(\_)를 사용해서 단어를 구분해주는 관례도 있는데, 이를 Snake case라고 부릅니다. 이 관례는 JavaScript에서는 잘 사용되지 않고, Python 등의 프로그래밍 언어에서 많이 사용됩니다. 118 | 119 | ```js 120 | // Snake case 121 | let fast_campus 122 | let foo_bar 123 | ``` 124 | 125 | 이 밖에 식별자 이름을 지을 때 사용하는 다른 관례들도 있는데, 그에 대해서는 [객체](./180-object) 챕터에서 자세히 다룹니다. 126 | 127 | ## 타입 128 | 129 | JavaScript를 비롯한 대부분의 프로그래밍 언어는 여러 가지 종류의 값을 지원하는데, 이러한 **값의 종류**를 가지고 자료형(data type)이라고 부릅니다. 줄여서 **타입**이라고 부르기도 합니다. 130 | 131 | 값의 타입을 알아보기 위해 `typeof` 연산자를 사용할 수 있습니다. 132 | 133 | ```js 134 | typeof 1 // 'number' 135 | typeof 'hello' // 'string' 136 | ``` 137 | 138 | 이어지는 챕터에서는 JavaScript가 지원하는 여러 가지 타입의 값의 사용법에 대해 다룰 것입니다. 139 | 140 | [^1]: 독자 중에 `var` 키워드에 대해 알고 있는 분도 계실 것입니다. `var`는 `let`과 비슷한 역할을 하지만 동작하는 방식과 제약 사항이 다릅니다. 자세한 차이점은 [값 더 알아보기](./220-value-in-depth) 챕터에서 다룹니다. 141 | -------------------------------------------------------------------------------- /src/@primer/gatsby-theme-doctocat/components/header.tsx: -------------------------------------------------------------------------------- 1 | import {Box, Flex, Link, StyledOcticon, Sticky} from '@primer/components' 2 | import { 3 | ChevronRightIcon, 4 | MarkGithubIcon, 5 | SearchIcon, 6 | ThreeBarsIcon, 7 | } from '@primer/octicons-react' 8 | import {Link as GatsbyLink} from 'gatsby' 9 | import React from 'react' 10 | import {ThemeContext} from 'styled-components' 11 | import primerNavItems from '../primer-nav.yml' 12 | import useSiteMetadata from '../use-site-metadata' 13 | import DarkButton from './dark-button' 14 | import MobileSearch from './mobile-search' 15 | import NavDrawer, {useNavDrawerState} from './nav-drawer' 16 | import NavDropdown, {NavDropdownItem} from './nav-dropdown' 17 | import Search from './search' 18 | 19 | export const HEADER_HEIGHT = 66 20 | 21 | function Header({isSearchEnabled = true}) { 22 | const theme = React.useContext(ThemeContext) 23 | const [isNavDrawerOpen, setIsNavDrawerOpen] = useNavDrawerState( 24 | theme.breakpoints[2], 25 | ) 26 | const [isMobileSearchOpen, setIsMobileSearchOpen] = React.useState(false) 27 | const siteMetadata = useSiteMetadata() 28 | return ( 29 | 30 | 37 | 38 | {siteMetadata.shortName ? ( 39 | 40 | {siteMetadata.shortName} 41 | 42 | ) : null} 43 | 44 | {isSearchEnabled ? ( 45 | 46 | 47 | 48 | ) : null} 49 | 50 | 51 | 52 | 53 | 54 | 55 | {isSearchEnabled ? ( 56 | <> 57 | setIsMobileSearchOpen(true)} 61 | > 62 | 63 | 64 | setIsMobileSearchOpen(false)} 67 | /> 68 | 69 | ) : null} 70 | setIsNavDrawerOpen(true)} 74 | ml={3} 75 | > 76 | 77 | 78 | setIsNavDrawerOpen(false)} 81 | /> 82 | 83 | 84 | 85 | 86 | ) 87 | } 88 | 89 | function PrimerNavItems({items}) { 90 | return ( 91 | 92 | {items.map((item, index) => { 93 | if (item.children) { 94 | return ( 95 | 96 | 97 | {item.children.map((child) => ( 98 | 99 | {child.title} 100 | 101 | ))} 102 | 103 | 104 | ) 105 | } 106 | 107 | return ( 108 | 115 | {item.title} 116 | 117 | ) 118 | })} 119 | 120 | ) 121 | } 122 | 123 | export default Header 124 | -------------------------------------------------------------------------------- /content/pages/100-javascript.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: JavaScript 소개 3 | --- 4 | 5 | JavaScript는 웹의 초창기였던 1995년에 Netscape Navigator라는 웹 브라우저에 처음으로 탑재되어 세상에 공개됐습니다. JavaScript는 Java와 많은 부분에서 다르지만, 마케팅 상의 이유로 그 문법과 이름이 Java와 유사하게 만들어진 덕분에 아직도 Java와 JavaScript를 혼동하는 사람들이 많습니다. **"Java와 JavaScript 사이의 관계는 햄과 햄스터 사이의 관계와 같다"**와 같이 그 미묘한 관계를 풍자하는 많은 [농담](http://javascriptisnotjava.io/)들이 있습니다. 6 | 7 | ## 언어와 구동 환경 8 | 9 | JavaScript 언어 자체에는 다른 범용 프로그래밍 언어(general-purpose programming language)에 비해 적은 양의 기능(주로 **코드의 실행**과 관련된 것)을 포함하고 있습니다. 하지만 이 JavaScript를 구동할 수 있는 **구동 환경**에 여러 기능(주로 **입출력**과 관련된 것)이 포함되어 있어서, 우리는 이 기능을 이용해 쓸모있는 응용 프로그램을 만들 수 있게 됩니다. JavaScript 구동 환경에는 웹 브라우저, 웹 서버 (Node.js), 게임 엔진, 포토샵(!) 등 많은 프로그램들이 있습니다. 10 | 11 | ## ECMAScript와 브라우저 잔혹사 12 | 13 | 몇몇 유명한 프로그래밍 언어와 마찬가지로, JavaScript라는 언어에는 표준 명세(standard specification)라는 것이 존재합니다. 여러 브라우저 개발사에서 통일된 JavaScript 기능을 구현할 수 있도록, 언어의 문법과 기능을 세부적으로 정의해놓은 설계도라고 생각하면 됩니다. JavaScript의 표준 명세는 **ECMAScript**라는 이름을 갖고 있는데, Netscape에 탑재되었던 JavaScript 구현체(implementation)를 ECMA(European Computer Manufacturer’s Association)라는 단체에서 표준화한 것입니다. 이 표준은 1997년에 처음 제정되어 계속 발전하고 있는 중입니다. 14 | 15 | ### 브라우저 간 호환성 16 | 17 | 1999년에 ECMAScript 3판(줄여서 ES3)이 공개될 때까지만 해도 여러 웹 브라우저들의 독자 표준이 난립하는 상황이었습니다. 예를 들어, Internet Explorer에서는 잘 동작하는 기능이 Firefox에서는 동작하지 않는다거나, 그 반대와 같은 경우가 많았습니다. 그래서 이 시절에는 어느 브라우저를 사용하든 잘 동작하는 JavaScript 코드를 짜는 것, 즉 **브라우저 간 호환성(cross-browser compatibility)**를 확보하는 것이 웹 개발자들에게 큰 고민거리였습니다. (물론 "이 웹 사이트는 IE에서 잘 동작합니다"라는 문구를 웹 사이트에 집어넣고 다른 브라우저를 무시하는 경우도 많았습니다...) 18 | 19 | 이런 호환성 문제를 해소하기 위해, 한 편으로는 Dojo, Prototype, jQuery와 같이 개발자 대신 브라우저 간 호환성 문제를 해결해주는 **라이브러리**들이 개발되었고, 또 한 편으로는 각 브라우저 개발사들이 **ECMAScript 표준**을 따라 브라우저를 구현하기 시작했습니다. 20 | 21 | ES5가 공개된 2009년 경부터는 브라우저 간 호환성 문제가 조금씩 해결되기 시작했고, 그 전까지는 천덕꾸러기 취급을 받던 JavaScript에 여러 가지 기능이 추가되어 쓸만한 범용 프로그래밍 언어로서의 면모를 갖추기 시작했습니다. 현재 모든 주요 브라우저는 ES2015 이후의 대부분의 기능을 지원하며, 브라우저 간 호환성 문제는 크게 개선되었습니다. 22 | 23 | ### ES2015, 그 이후 24 | 25 | ES5의 다음 버전부터는 해당 버전이 공개된 연도를 버전 번호로 사용하고 있습니다. 즉, ES5의 다음 버전의 이름은 ES6가 아니라 **ES2015** 입니다. 다만 ES2015라는 이름이 확정되기 전까지는 ES5의 다음 버전이라는 의미에서 ES6라는 이름이 널리 사용되었고, 아직까지도 ES6라는 이름이 사용되는 경우가 있습니다. 하지만 정식 명칭은 ES2015라는 사실을 기억하세요. 26 | 27 | ES2015에서 엄청나게 많은 문법과 기능(클래스, 모듈, 분해대입, 템플릿 문자열, 블록 스코프, 반복자, 프록시 등등...)이 추가되고, Node.js 등 웹 브라우저 외에도 JavaScript를 구동할 수 있는 구동 환경의 종류가 많아지면서, 이제 JavaScript는 Python 등 다른 범용 프로그래밍 언어와 비교해도 전혀 뒤쳐지지 않는 범용 프로그래밍 언어가 되었습니다. 28 | 29 | ES2015부터는 매년 새로운 버전의 ECMAScript가 공개되고 있습니다. ES2022에는 클래스 필드와 `at()` 메소드가, ES2023에는 배열의 불변 메소드(`toSorted()`, `toReversed()` 등)가, ES2024에는 `Object.groupBy()`와 Set 메소드가 추가되었습니다. 최신 명세는 [이 곳](https://262.ecma-international.org/)에서, 브라우저 별 기능 개발 현황은 [이 곳](https://compat-table.github.io/compat-table/es2016plus/)에서 확인해볼 수 있습니다. 30 | 31 | ## 빠르게 발전하는 언어, 따라가는 개발자 32 | 33 | 이렇게 나날이 발전하는 JavaScript에 새롭게 추가된 우아한 기능을 이용해서 개발을 할 수 있다면 행복할 테지만, 한 가지 문제가 있습니다. 최신 버전의 JavaScript를 지원하지 않는 브라우저가 **항상 존재**한다는 점입니다. 구형 브라우저를 업데이트하지 않고 사용하는 사용자는 굉장히 많습니다. 최신 버전의 브라우저를 사용한다고 하더라도, 각 브라우저마다 업데이트 주기가 다르므로 브라우저마다 지원하는 JavaScript 기능이 조금씩 다릅니다. 34 | 35 | 이런 문제를 해결하기 위해, 현재 두 가지 도구를 사용하는데 바로 **트랜스파일러(transpiler)**와 **폴리필(polyfill)**입니다. 트랜스파일러와 폴리필이 없는 웹 개발을 상상하기 어려울 정도로, 이런 도구들이 널리 활용되고 있습니다. 36 | 37 | ### 트랜스파일러 (Transpiler) 38 | 39 | 트랜스파일러는 최신 버전 JavaScript의 **문법**을 **똑같이 동작하는 이전 버전 JavaScript의 문법**으로 바꾸어주는 도구입니다. 마법같은 도구죠! 이를 이용해 개발자는 최신 버전으로 코딩을 하고, 실제로 사용자에게 배포할 코드는 구형 브라우저에서도 잘 동작하도록 변환해줄 수 있습니다. 많이 사용되는 트랜스파일러에는 [Babel](https://babeljs.io/), [TypeScript](https://www.typescriptlang.org/) 같은 것들이 있습니다. 또한 [Vite](https://vite.dev/) 같은 빌드 도구는 개발 속도가 매우 빠르며, 트랜스파일과 번들링을 함께 처리해줍니다. 40 | 41 | ### 폴리필 (Polyfill) 42 | 43 | JavaScript를 실행하는 구동 환경에는 여러 가지 **문법과 기능**이 추가됩니다. 이를 구형 환경에서도 사용할 수 있도록 똑같이 구현해놓은 라이브러리를 폴리필(polyfill) 혹은 심(shim)이라고 부릅니다. 대부분의 주요 브라우저가 ES2015 이후의 기능을 지원하므로, 폴리필의 필요성이 많이 줄어들었습니다. 본인이 개발하는 프로그램이 어느 정도까지의 구형 환경을 지원해야 하는지를 결정한 후, 필요한 경우에만 적절한 폴리필을 프로젝트에 추가하세요. 44 | 45 | ### 이용중인 환경의 JavaScript 기능 지원 여부 확인하기 46 | 47 | 다만 최신 버전에 포함된 문법과 기능을 전부 이전 버전에서 구현할 수 있는 것은 아니고, 또 트랜스파일러마다 지원하는 기능의 종류가 달라서, 항상 모든 최신 기능을 사용할 수 있는 것은 아닙니다. 최신 기능을 사용하고 싶다면, 본인이 사용중인 트랜스파일러가 해당 기능을 지원하도록 설정되어 있는지, 프로젝트에 적절한 폴리필이 포함되어 있는지 확인하세요. JavaScript 최신 버전에 관심이 있다면, 페이스북이나 트위터 등의 매체를 통해 JavaScript 최신 버전에 대한 뉴스를 수집하고, 48 | [브라우저별 지원 기능 일람](https://compat-table.github.io/compat-table/es6/)을 제공하는 웹사이트를 주기적으로 확인해보세요. 49 | -------------------------------------------------------------------------------- /src/@primer/gatsby-theme-doctocat/components/nav-drawer.js: -------------------------------------------------------------------------------- 1 | import {BorderBox, Flex, Link, Text} from '@primer/components' 2 | import {ChevronDownIcon, ChevronUpIcon, XIcon} from '@primer/octicons-react' 3 | import {Link as GatsbyLink} from 'gatsby' 4 | import debounce from 'lodash.debounce' 5 | import React from 'react' 6 | import navItems from '../nav.yml' 7 | import primerNavItems from '../primer-nav.yml' 8 | import useSiteMetadata from '../use-site-metadata' 9 | import DarkButton from './dark-button' 10 | import Details from './details' 11 | import Drawer from './drawer' 12 | import NavItems from './nav-items' 13 | 14 | export function useNavDrawerState(breakpoint) { 15 | // Handle string values from themes with units at the end 16 | if (typeof breakpoint === 'string') { 17 | breakpoint = parseInt(breakpoint, 10) 18 | } 19 | const [isOpen, setOpen] = React.useState(false) 20 | 21 | const onResize = React.useCallback(() => { 22 | if (window.innerWidth >= breakpoint) { 23 | setOpen(false) 24 | } 25 | }, [setOpen]) 26 | 27 | const debouncedOnResize = React.useCallback(debounce(onResize, 250), [ 28 | onResize, 29 | ]) 30 | 31 | React.useEffect(() => { 32 | if (isOpen) { 33 | window.addEventListener('resize', debouncedOnResize) 34 | return () => { 35 | // cancel any debounced invocation of the resize handler 36 | debouncedOnResize.cancel() 37 | window.removeEventListener('resize', debouncedOnResize) 38 | } 39 | } 40 | }, [isOpen, debouncedOnResize]) 41 | 42 | return [isOpen, setOpen] 43 | } 44 | 45 | function NavDrawer({isOpen, onDismiss}) { 46 | const siteMetadata = useSiteMetadata() 47 | return ( 48 | 49 | 55 | 56 | 57 | 58 | 59 | 60 | {navItems.length > 0 ? ( 61 | 67 | 76 | {siteMetadata.title} 77 | 78 | 79 | 80 | ) : null} 81 | 82 | 83 | ) 84 | } 85 | 86 | function PrimerNavItems({items}) { 87 | return items.map((item, index) => { 88 | return ( 89 | 97 | {item.children ? ( 98 |
99 | {({open, toggle}) => ( 100 | <> 101 | 102 | 103 | {item.title} 104 | {open ? : } 105 | 106 | 107 | 108 | {item.children.map((child) => ( 109 | 117 | {child.title} 118 | 119 | ))} 120 | 121 | 122 | )} 123 |
124 | ) : ( 125 | 126 | {item.title} 127 | 128 | )} 129 |
130 | ) 131 | }) 132 | } 133 | 134 | export default NavDrawer 135 | -------------------------------------------------------------------------------- /.specify/templates/spec-template.md: -------------------------------------------------------------------------------- 1 | # Feature Specification: [FEATURE NAME] 2 | 3 | **Feature Branch**: `[###-feature-name]` 4 | **Created**: [DATE] 5 | **Status**: Draft 6 | **Input**: User description: "$ARGUMENTS" 7 | 8 | ## Execution Flow (main) 9 | ``` 10 | 1. Parse user description from Input 11 | → If empty: ERROR "No feature description provided" 12 | 2. Extract key concepts from description 13 | → Identify: actors, actions, data, constraints 14 | 3. For each unclear aspect: 15 | → Mark with [NEEDS CLARIFICATION: specific question] 16 | 4. Fill User Scenarios & Testing section 17 | → If no clear user flow: ERROR "Cannot determine user scenarios" 18 | 5. Generate Functional Requirements 19 | → Each requirement must be testable 20 | → Mark ambiguous requirements 21 | 6. Identify Key Entities (if data involved) 22 | 7. Run Review Checklist 23 | → If any [NEEDS CLARIFICATION]: WARN "Spec has uncertainties" 24 | → If implementation details found: ERROR "Remove tech details" 25 | 8. Return: SUCCESS (spec ready for planning) 26 | ``` 27 | 28 | --- 29 | 30 | ## ⚡ Quick Guidelines 31 | - ✅ Focus on WHAT users need and WHY 32 | - ❌ Avoid HOW to implement (no tech stack, APIs, code structure) 33 | - 👥 Written for business stakeholders, not developers 34 | 35 | ### Section Requirements 36 | - **Mandatory sections**: Must be completed for every feature 37 | - **Optional sections**: Include only when relevant to the feature 38 | - When a section doesn't apply, remove it entirely (don't leave as "N/A") 39 | 40 | ### For AI Generation 41 | When creating this spec from a user prompt: 42 | 1. **Mark all ambiguities**: Use [NEEDS CLARIFICATION: specific question] for any assumption you'd need to make 43 | 2. **Don't guess**: If the prompt doesn't specify something (e.g., "login system" without auth method), mark it 44 | 3. **Think like a tester**: Every vague requirement should fail the "testable and unambiguous" checklist item 45 | 4. **Common underspecified areas**: 46 | - User types and permissions 47 | - Data retention/deletion policies 48 | - Performance targets and scale 49 | - Error handling behaviors 50 | - Integration requirements 51 | - Security/compliance needs 52 | 53 | --- 54 | 55 | ## User Scenarios & Testing *(mandatory)* 56 | 57 | ### Primary User Story 58 | [Describe the main user journey in plain language] 59 | 60 | ### Acceptance Scenarios 61 | 1. **Given** [initial state], **When** [action], **Then** [expected outcome] 62 | 2. **Given** [initial state], **When** [action], **Then** [expected outcome] 63 | 64 | ### Edge Cases 65 | - What happens when [boundary condition]? 66 | - How does system handle [error scenario]? 67 | 68 | ## Requirements *(mandatory)* 69 | 70 | ### Functional Requirements 71 | - **FR-001**: System MUST [specific capability, e.g., "allow users to create accounts"] 72 | - **FR-002**: System MUST [specific capability, e.g., "validate email addresses"] 73 | - **FR-003**: Users MUST be able to [key interaction, e.g., "reset their password"] 74 | - **FR-004**: System MUST [data requirement, e.g., "persist user preferences"] 75 | - **FR-005**: System MUST [behavior, e.g., "log all security events"] 76 | 77 | *Example of marking unclear requirements:* 78 | - **FR-006**: System MUST authenticate users via [NEEDS CLARIFICATION: auth method not specified - email/password, SSO, OAuth?] 79 | - **FR-007**: System MUST retain user data for [NEEDS CLARIFICATION: retention period not specified] 80 | 81 | ### Key Entities *(include if feature involves data)* 82 | - **[Entity 1]**: [What it represents, key attributes without implementation] 83 | - **[Entity 2]**: [What it represents, relationships to other entities] 84 | 85 | --- 86 | 87 | ## Review & Acceptance Checklist 88 | *GATE: Automated checks run during main() execution* 89 | 90 | ### Content Quality 91 | - [ ] No implementation details (languages, frameworks, APIs) 92 | - [ ] Focused on user value and business needs 93 | - [ ] Written for non-technical stakeholders 94 | - [ ] All mandatory sections completed 95 | 96 | ### Requirement Completeness 97 | - [ ] No [NEEDS CLARIFICATION] markers remain 98 | - [ ] Requirements are testable and unambiguous 99 | - [ ] Success criteria are measurable 100 | - [ ] Scope is clearly bounded 101 | - [ ] Dependencies and assumptions identified 102 | 103 | --- 104 | 105 | ## Execution Status 106 | *Updated by main() during processing* 107 | 108 | - [ ] User description parsed 109 | - [ ] Key concepts extracted 110 | - [ ] Ambiguities marked 111 | - [ ] User scenarios defined 112 | - [ ] Requirements generated 113 | - [ ] Entities identified 114 | - [ ] Review checklist passed 115 | 116 | --- 117 | -------------------------------------------------------------------------------- /content/pages/293-module.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: 모듈 3 | --- 4 | 5 | 최근들어 프론트엔드 프로젝트의 규모가 커짐에 따라, JavaScript 코드를 **여러 파일과 폴더에 나누어 작성**하고 **서로가 서로를 효율적으로 불러올 수 있도록** 해주는 시스템의 필요성이 절실해졌습니다. 이에 따라 모듈 시스템이 ES2015에 추가되었습니다. 6 | 7 | ES2015 모듈(ESM)은 이제 **모든 주요 브라우저와 Node.js에서 표준으로 지원**됩니다. `script` 태그에 `type="module"` 어트리뷰트를 추가해주면, 이 파일은 모듈로서 동작합니다. 파일 확장자로는 `.js` 또는 `.mjs`가 사용됩니다. 8 | 9 | ```html 10 | 11 | ``` 12 | 13 | Node.js에서는 `package.json`에 `"type": "module"`을 추가하거나 `.mjs` 확장자를 사용하여 ES 모듈을 활성화할 수 있습니다: 14 | 15 | ```json 16 | { 17 | "type": "module" 18 | } 19 | ``` 20 | 21 | 현재는 대부분의 프로젝트에서 **ES 모듈이 표준**으로 자리 잡았으며, Vite, Webpack 등의 빌드 도구를 통해 개발 환경에서 효율적으로 모듈을 관리하고 있습니다. 22 | 23 | ## 모듈이란? 24 | 25 | ES2015 모듈은 기본적으로 JavaScript 코드를 담고 있는 **파일**입니다. 다만 일반적인 JavaScript 파일과는 다른 여러가지 차이점을 갖고 있습니다. 26 | 27 | - `import` 혹은 `export` 구문을 사용할 수 있습니다. 28 | - 별다른 처리를 해주지 않아도 엄격 모드(strict mode)로 동작합니다. 29 | - 모듈의 가장 바깥쪽에서 선언된 이름은 전역 스코프가 아니라 **모듈 스코프**에서 선언됩니다. 30 | 31 | ## 모듈 스코프 32 | 33 | 모듈 내부의 가장 바깥 스코프에서 이름을 선언하더라도, 전역 스코프가 아니라 **모듈 스코프**에서 선언됩니다. 모듈 스코프에 선언된 이름은 (export 해주지 않는다면) 해당 모듈 내부에서만 접근할 수 있습니다. 34 | 35 | ```js 36 | // variables.js 37 | 38 | const foo = 'bar' 39 | 40 | // 이 파일이 모듈로서 사용되고 있다면, `undefined`가 출력됩니다. 41 | console.log(window.foo) 42 | ``` 43 | 44 | 따라서 여러 모듈의 가장 바깥쪽에서 같은 이름으로 변수, 함수, 클래스를 선언하더라도, 서로 다른 스코프에서 선언되기 때문에 이름의 충돌이 생길 일이 없습니다. 45 | 46 | ## export & import 47 | 48 | 모듈 스코프에서 정의된 이름은 `export` 구문을 통해 다른 파일에서 사용할 수 있습니다. 이를 **'이름이 지정된 export'**라는 뜻에서 **named export**라 부릅니다. 49 | 50 | ```js 51 | // variables.js 52 | const foo = 'bar' 53 | const spam = 'eggs' 54 | 55 | // foo, spam을 다른 파일에서 사용할 수 있도록 export 해주었습니다. 56 | export {foo, spam} 57 | ``` 58 | 59 | 위에서 `export`된 이름을 다른 파일에서 `import` 구문을 통해 가져온 뒤 사용할 수 있습니다. 60 | 61 | ```js 62 | // main.js 63 | 64 | // variables 모듈에 선언된 이름을 사용하기 위해 import 해주었습니다. 65 | import {foo, spam} from './variables.js' 66 | 67 | console.log(foo) 68 | console.log(spam) 69 | ``` 70 | 71 | 단순히 값을 저장하고 있는 변수뿐만 아니라, 함수나 클래스도 `export`를 통해 여러 모듈에서 재사용할 수 있습니다. 72 | 73 | ```js 74 | // functions.js 75 | 76 | function add(x, y) { 77 | return x + y 78 | } 79 | 80 | class Person { 81 | // ... 82 | } 83 | 84 | export {add, Person} 85 | ``` 86 | 87 | 다른 모듈에 있는 이름을 사용하려면, 반드시 해당 모듈에서 **이름**을 `export` 해주어야 합니다. `export` 해주지 않은 이름을 다른 모듈에서 `import` 하면 의도대로 동작하지 않습니다. (모듈 실행 환경에 따라 에러가 날 수도 있고, 이름에 `undefined`가 들어있을 수도 있습니다.) 88 | 89 | ```js 90 | // variables.js 91 | 92 | const foo = 'bar' 93 | ``` 94 | 95 | ```js 96 | // main.js 97 | import {foo} from './variables.js' 98 | 99 | console.log(foo) // 에러가 나거나, `undefined`가 출력됨 100 | ``` 101 | 102 | ## 선언과 동시에 export 하기 103 | 104 | 이름을 선언하는 구문 앞에 `export`를 붙여주면, 선언과 `export`를 한꺼번에 할 수 있습니다. 105 | 106 | ```js 107 | // common.js 108 | export const foo = 'bar' 109 | export const spam = 'eggs' 110 | export function add(x, y) { 111 | return x + y 112 | } 113 | export class Person { 114 | // ... 115 | } 116 | ``` 117 | 118 | ## default export 119 | 120 | `export default` 구문을 통해, 모듈을 대표하는 하나의 **값**을 지정하고 그 값을 다른 모듈에서 편하게 불러와서 사용할 수 있습니다. 이렇게 사용하는 값을 **default export**라고 부릅니다. 121 | 122 | ```js 123 | // foo.js 124 | 125 | export default 'bar' 126 | ``` 127 | 128 | `import` 구문에서 이름을 적어주는 부분에 중괄호를 생략하면, 모듈의 default export를 가져옵니다. 129 | 130 | ```js 131 | // main.js 132 | 133 | import foo from './foo.js' 134 | 135 | console.log(foo) // bar 136 | ``` 137 | 138 | `export default` 뒤에는 임의의 표현식이 올 수 있습니다. 즉, 함수 표현식이나 클래스 표현식도 올 수 있습니다. 139 | 140 | ```js 141 | // add.js 142 | 143 | export default function (x, y) { 144 | return x + y 145 | } 146 | ``` 147 | 148 | ```js 149 | import add from './add.js' 150 | 151 | console.log(add(1, 2)) // 3 152 | ``` 153 | 154 | `import` 구문에서 default export와 일반적인 export를 동시에 가져올 수 있습니다. 155 | 156 | ```js 157 | // `React`라는 이름의 default export와, 158 | // Component, Fragment라는 일반적인 export를 동시에 가져오기 159 | import React, {Component, Fragment} from 'react' 160 | ``` 161 | 162 | ## 다른 이름으로 export & import 하기 163 | 164 | `export` 혹은 `import` 하는 이름의 뒤에 `as`를 붙여서, 다른 이름이 대신 사용되게 할 수 있습니다. 165 | 166 | ```js 167 | const foo = 'bar' 168 | 169 | export {foo as FOO} // FOO 라는 이름으로 export 됩니다. 170 | ``` 171 | 172 | ```js 173 | import {Component as Comp} from 'react' // Comp라는 이름으로 import 됩니다. 174 | ``` 175 | 176 | ## 모듈 사용 시 주의할 점 177 | 178 | 이제까지 모듈 시스템의 문법을 배워봤습니다. 여기서 주의할 점이 한 가지 있습니다. `import` 구문과 `export` 구문은 모듈 간 의존 관계를 나타내는 것일 뿐, 코드를 실행시키라는 명령이 아니라는 것입니다. 179 | 180 | - 같은 모듈을 여러 다른 모듈에서 불러와도, 모듈 내부의 코드는 단 한 번만 실행됩니다. 181 | - `import` 구문과 `export` 구문은 모듈의 가장 바깥쪽 스코프에서만 사용할 수 있습니다. 182 | - ECMAScript 공식 명세에는 모듈을 불러오는 방법에 대한 내용이 포함되어있지 않고, 이와 관련된 내용을 전적으로 모듈 구현체에 맡겼습니다. 따라서, 모듈을 어떤 환경에서 실행하느냐에 따라서 구체적인 로딩 순서나 동작방식이 조금씩 달라질 수 있습니다. 183 | 184 | ## ES2015 이전의 모듈들 185 | 186 | 사실 ES2015가 JavaScript 생태계에서 사용된 첫 모듈 시스템은 아닙니다. ES2015 모듈 이전에 CommonJS, AMD 등의 모듈 시스템이 있었고, 이 모듈 시스템들도 실제로 널리 사용되었던 시기가 있습니다. 다른 모듈 시스템의 특징과 역사가 궁금하시다면 [이 글](https://d2.naver.com/helloworld/12864)을 읽어보세요. 187 | -------------------------------------------------------------------------------- /src/@primer/gatsby-theme-doctocat/components/mobile-search.js: -------------------------------------------------------------------------------- 1 | import {Absolute, Fixed, Flex} from '@primer/components' 2 | import {XIcon} from '@primer/octicons-react' 3 | import Downshift from 'downshift' 4 | import {AnimatePresence, motion} from 'framer-motion' 5 | import {navigate} from 'gatsby' 6 | import React from 'react' 7 | import {FocusOn} from 'react-focus-on' 8 | import useSearch from '../use-search' 9 | import DarkButton from './dark-button' 10 | import DarkTextInput from './dark-text-input' 11 | import SearchResults from './search-results' 12 | 13 | function stateReducer(state, changes) { 14 | switch (changes.type) { 15 | case Downshift.stateChangeTypes.changeInput: 16 | if (!changes.inputValue) { 17 | // Close the menu if the input is empty. 18 | return {...changes, isOpen: false} 19 | } 20 | return changes 21 | case Downshift.stateChangeTypes.blurInput: 22 | // Don't let a blur event change the state of `inputValue` or `isOpen`. 23 | return {...changes, inputValue: state.inputValue, isOpen: state.isOpen} 24 | default: 25 | return changes 26 | } 27 | } 28 | 29 | function MobileSearch({isOpen, onDismiss}) { 30 | const [query, setQuery] = React.useState('') 31 | const results = useSearch(query) 32 | 33 | function handleDismiss() { 34 | setQuery('') 35 | onDismiss() 36 | } 37 | 38 | return ( 39 | 40 | {isOpen ? ( 41 | handleDismiss()}> 42 | 43 | 56 | setQuery(inputValue)} 59 | selectedItem={null} 60 | onSelect={(item) => { 61 | if (item) { 62 | navigate(item.path) 63 | handleDismiss() 64 | } 65 | }} 66 | itemToString={(item) => (item ? item.title : '')} 67 | stateReducer={stateReducer} 68 | > 69 | {({ 70 | getInputProps, 71 | getItemProps, 72 | getMenuProps, 73 | getRootProps, 74 | isOpen: isMenuOpen, 75 | highlightedIndex, 76 | }) => ( 77 | 83 | 84 | 91 | 97 | 98 | 103 | 104 | 105 | 106 | {isMenuOpen ? ( 107 | 119 | 124 | 125 | ) : null} 126 | 127 | )} 128 | 129 | 130 | 131 | ) : null} 132 | 133 | ) 134 | } 135 | 136 | export default MobileSearch 137 | -------------------------------------------------------------------------------- /.specify/templates/tasks-template.md: -------------------------------------------------------------------------------- 1 | # Tasks: [FEATURE NAME] 2 | 3 | **Input**: Design documents from `/specs/[###-feature-name]/` 4 | **Prerequisites**: plan.md (required), research.md, data-model.md, contracts/ 5 | 6 | ## Execution Flow (main) 7 | ``` 8 | 1. Load plan.md from feature directory 9 | → If not found: ERROR "No implementation plan found" 10 | → Extract: tech stack, libraries, structure 11 | 2. Load optional design documents: 12 | → data-model.md: Extract entities → model tasks 13 | → contracts/: Each file → contract test task 14 | → research.md: Extract decisions → setup tasks 15 | 3. Generate tasks by category: 16 | → Setup: project init, dependencies, linting 17 | → Tests: contract tests, integration tests 18 | → Core: models, services, CLI commands 19 | → Integration: DB, middleware, logging 20 | → Polish: unit tests, performance, docs 21 | 4. Apply task rules: 22 | → Different files = mark [P] for parallel 23 | → Same file = sequential (no [P]) 24 | → Tests before implementation (TDD) 25 | 5. Number tasks sequentially (T001, T002...) 26 | 6. Generate dependency graph 27 | 7. Create parallel execution examples 28 | 8. Validate task completeness: 29 | → All contracts have tests? 30 | → All entities have models? 31 | → All endpoints implemented? 32 | 9. Return: SUCCESS (tasks ready for execution) 33 | ``` 34 | 35 | ## Format: `[ID] [P?] Description` 36 | - **[P]**: Can run in parallel (different files, no dependencies) 37 | - Include exact file paths in descriptions 38 | 39 | ## Path Conventions 40 | - **Single project**: `src/`, `tests/` at repository root 41 | - **Web app**: `backend/src/`, `frontend/src/` 42 | - **Mobile**: `api/src/`, `ios/src/` or `android/src/` 43 | - Paths shown below assume single project - adjust based on plan.md structure 44 | 45 | ## Phase 3.1: Setup 46 | - [ ] T001 Create project structure per implementation plan 47 | - [ ] T002 Initialize [language] project with [framework] dependencies 48 | - [ ] T003 [P] Configure linting and formatting tools 49 | 50 | ## Phase 3.2: Tests First (TDD) ⚠️ MUST COMPLETE BEFORE 3.3 51 | **CRITICAL: These tests MUST be written and MUST FAIL before ANY implementation** 52 | - [ ] T004 [P] Contract test POST /api/users in tests/contract/test_users_post.py 53 | - [ ] T005 [P] Contract test GET /api/users/{id} in tests/contract/test_users_get.py 54 | - [ ] T006 [P] Integration test user registration in tests/integration/test_registration.py 55 | - [ ] T007 [P] Integration test auth flow in tests/integration/test_auth.py 56 | 57 | ## Phase 3.3: Core Implementation (ONLY after tests are failing) 58 | - [ ] T008 [P] User model in src/models/user.py 59 | - [ ] T009 [P] UserService CRUD in src/services/user_service.py 60 | - [ ] T010 [P] CLI --create-user in src/cli/user_commands.py 61 | - [ ] T011 POST /api/users endpoint 62 | - [ ] T012 GET /api/users/{id} endpoint 63 | - [ ] T013 Input validation 64 | - [ ] T014 Error handling and logging 65 | 66 | ## Phase 3.4: Integration 67 | - [ ] T015 Connect UserService to DB 68 | - [ ] T016 Auth middleware 69 | - [ ] T017 Request/response logging 70 | - [ ] T018 CORS and security headers 71 | 72 | ## Phase 3.5: Polish 73 | - [ ] T019 [P] Unit tests for validation in tests/unit/test_validation.py 74 | - [ ] T020 Performance tests (<200ms) 75 | - [ ] T021 [P] Update docs/api.md 76 | - [ ] T022 Remove duplication 77 | - [ ] T023 Run manual-testing.md 78 | 79 | ## Dependencies 80 | - Tests (T004-T007) before implementation (T008-T014) 81 | - T008 blocks T009, T015 82 | - T016 blocks T018 83 | - Implementation before polish (T019-T023) 84 | 85 | ## Parallel Example 86 | ``` 87 | # Launch T004-T007 together: 88 | Task: "Contract test POST /api/users in tests/contract/test_users_post.py" 89 | Task: "Contract test GET /api/users/{id} in tests/contract/test_users_get.py" 90 | Task: "Integration test registration in tests/integration/test_registration.py" 91 | Task: "Integration test auth in tests/integration/test_auth.py" 92 | ``` 93 | 94 | ## Notes 95 | - [P] tasks = different files, no dependencies 96 | - Verify tests fail before implementing 97 | - Commit after each task 98 | - Avoid: vague tasks, same file conflicts 99 | 100 | ## Task Generation Rules 101 | *Applied during main() execution* 102 | 103 | 1. **From Contracts**: 104 | - Each contract file → contract test task [P] 105 | - Each endpoint → implementation task 106 | 107 | 2. **From Data Model**: 108 | - Each entity → model creation task [P] 109 | - Relationships → service layer tasks 110 | 111 | 3. **From User Stories**: 112 | - Each story → integration test [P] 113 | - Quickstart scenarios → validation tasks 114 | 115 | 4. **Ordering**: 116 | - Setup → Tests → Models → Services → Endpoints → Polish 117 | - Dependencies block parallel execution 118 | 119 | ## Validation Checklist 120 | *GATE: Checked by main() before returning* 121 | 122 | - [ ] All contracts have corresponding tests 123 | - [ ] All entities have model tasks 124 | - [ ] All tests come before implementation 125 | - [ ] Parallel tasks truly independent 126 | - [ ] Each task specifies exact file path 127 | - [ ] No task modifies same file as another [P] task -------------------------------------------------------------------------------- /.claude/commands/constitution.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Create or update the project constitution from interactive or provided principle inputs, ensuring all dependent templates stay in sync. 3 | --- 4 | 5 | The user input to you can be provided directly by the agent or as a command argument - you **MUST** consider it before proceeding with the prompt (if not empty). 6 | 7 | User input: 8 | 9 | $ARGUMENTS 10 | 11 | You are updating the project constitution at `.specify/memory/constitution.md`. This file is a TEMPLATE containing placeholder tokens in square brackets (e.g. `[PROJECT_NAME]`, `[PRINCIPLE_1_NAME]`). Your job is to (a) collect/derive concrete values, (b) fill the template precisely, and (c) propagate any amendments across dependent artifacts. 12 | 13 | Follow this execution flow: 14 | 15 | 1. Load the existing constitution template at `.specify/memory/constitution.md`. 16 | - Identify every placeholder token of the form `[ALL_CAPS_IDENTIFIER]`. 17 | **IMPORTANT**: The user might require less or more principles than the ones used in the template. If a number is specified, respect that - follow the general template. You will update the doc accordingly. 18 | 19 | 2. Collect/derive values for placeholders: 20 | - If user input (conversation) supplies a value, use it. 21 | - Otherwise infer from existing repo context (README, docs, prior constitution versions if embedded). 22 | - For governance dates: `RATIFICATION_DATE` is the original adoption date (if unknown ask or mark TODO), `LAST_AMENDED_DATE` is today if changes are made, otherwise keep previous. 23 | - `CONSTITUTION_VERSION` must increment according to semantic versioning rules: 24 | * MAJOR: Backward incompatible governance/principle removals or redefinitions. 25 | * MINOR: New principle/section added or materially expanded guidance. 26 | * PATCH: Clarifications, wording, typo fixes, non-semantic refinements. 27 | - If version bump type ambiguous, propose reasoning before finalizing. 28 | 29 | 3. Draft the updated constitution content: 30 | - Replace every placeholder with concrete text (no bracketed tokens left except intentionally retained template slots that the project has chosen not to define yet—explicitly justify any left). 31 | - Preserve heading hierarchy and comments can be removed once replaced unless they still add clarifying guidance. 32 | - Ensure each Principle section: succinct name line, paragraph (or bullet list) capturing non‑negotiable rules, explicit rationale if not obvious. 33 | - Ensure Governance section lists amendment procedure, versioning policy, and compliance review expectations. 34 | 35 | 4. Consistency propagation checklist (convert prior checklist into active validations): 36 | - Read `.specify/templates/plan-template.md` and ensure any "Constitution Check" or rules align with updated principles. 37 | - Read `.specify/templates/spec-template.md` for scope/requirements alignment—update if constitution adds/removes mandatory sections or constraints. 38 | - Read `.specify/templates/tasks-template.md` and ensure task categorization reflects new or removed principle-driven task types (e.g., observability, versioning, testing discipline). 39 | - Read each command file in `.specify/templates/commands/*.md` (including this one) to verify no outdated references (agent-specific names like CLAUDE only) remain when generic guidance is required. 40 | - Read any runtime guidance docs (e.g., `README.md`, `docs/quickstart.md`, or agent-specific guidance files if present). Update references to principles changed. 41 | 42 | 5. Produce a Sync Impact Report (prepend as an HTML comment at top of the constitution file after update): 43 | - Version change: old → new 44 | - List of modified principles (old title → new title if renamed) 45 | - Added sections 46 | - Removed sections 47 | - Templates requiring updates (✅ updated / ⚠ pending) with file paths 48 | - Follow-up TODOs if any placeholders intentionally deferred. 49 | 50 | 6. Validation before final output: 51 | - No remaining unexplained bracket tokens. 52 | - Version line matches report. 53 | - Dates ISO format YYYY-MM-DD. 54 | - Principles are declarative, testable, and free of vague language ("should" → replace with MUST/SHOULD rationale where appropriate). 55 | 56 | 7. Write the completed constitution back to `.specify/memory/constitution.md` (overwrite). 57 | 58 | 8. Output a final summary to the user with: 59 | - New version and bump rationale. 60 | - Any files flagged for manual follow-up. 61 | - Suggested commit message (e.g., `docs: amend constitution to vX.Y.Z (principle additions + governance update)`). 62 | 63 | Formatting & Style Requirements: 64 | - Use Markdown headings exactly as in the template (do not demote/promote levels). 65 | - Wrap long rationale lines to keep readability (<100 chars ideally) but do not hard enforce with awkward breaks. 66 | - Keep a single blank line between sections. 67 | - Avoid trailing whitespace. 68 | 69 | If the user supplies partial updates (e.g., only one principle revision), still perform validation and version decision steps. 70 | 71 | If critical info missing (e.g., ratification date truly unknown), insert `TODO(): explanation` and include in the Sync Impact Report under deferred items. 72 | 73 | Do not create a new template; always operate on the existing `.specify/memory/constitution.md` file. 74 | -------------------------------------------------------------------------------- /src/@primer/gatsby-theme-doctocat/components/layout.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | BorderBox, 3 | Box, 4 | Details, 5 | Flex, 6 | Grid, 7 | Heading, 8 | Position, 9 | StyledOcticon, 10 | Text, 11 | } from '@primer/components' 12 | import {ChevronDownIcon, ChevronUpIcon} from '@primer/octicons-react' 13 | import React from 'react' 14 | import Head from './head' 15 | import Header, {HEADER_HEIGHT} from './header' 16 | import PageFooter from './page-footer' 17 | import Sidebar from './sidebar' 18 | import SourceLink from './source-link' 19 | import StatusLabel from './status-label' 20 | import TableOfContents from './table-of-contents' 21 | 22 | function Layout({children, pageContext}) { 23 | let { 24 | title, 25 | description, 26 | status, 27 | source, 28 | additionalContributors, 29 | } = pageContext.frontmatter 30 | 31 | if (!additionalContributors) { 32 | additionalContributors = [] 33 | } 34 | 35 | return ( 36 | 37 | 38 |
39 | 40 | 41 | 42 | 43 | 58 | 65 | {title} 66 | 67 | {pageContext.tableOfContents.items ? ( 68 | 75 | 76 | Table of contents 77 | 78 | 79 | 80 | ) : null} 81 | 82 | {status || source ? ( 83 | 84 | {status ? : null} 85 | 86 | {source ? : null} 87 | 88 | ) : null} 89 | {pageContext.tableOfContents.items ? ( 90 | 91 |
92 | {({open}) => ( 93 | <> 94 | 95 | 100 | Table of contents 101 | {open ? ( 102 | 107 | ) : ( 108 | 113 | )} 114 | 115 | 116 | 123 | 126 | 127 | 128 | )} 129 |
130 |
131 | ) : null} 132 | {children} 133 | ({login})), 137 | )} 138 | /> 139 |
140 |
141 |
142 | 143 | ) 144 | } 145 | 146 | export default Layout 147 | -------------------------------------------------------------------------------- /content/pages/282-data-structures.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: 큐, 스택, 트리 3 | --- 4 | 5 | import QueueSVG from '../keyshape/QueueSVG.tsx' 6 | import StackSVG from '../keyshape/StackSVG.tsx' 7 | import TreeSVG from '../keyshape/TreeSVG.tsx' 8 | 9 | 어떤 데이터의 구체적인 구현 방식은 생략한 채, **데이터의 추상적 형태**와 **그 데이터를 다루는 방법**만을 정해놓은 것을 가지고 **ADT(Abstract Data Type)** 혹은 **추상 자료형**이라고 합니다. 이 챕터에서는 널리 사용되는 ADT인 큐, 스택, 트리에 대해 배웁니다. 10 | 11 | ## 큐 (Queue) 12 | 13 | 큐(queue)는 다음과 같은 성질을 갖는 자료형입니다. 14 | 15 | - 데이터를 집어넣을 수 있는 선형(linear) 자료형입니다. 16 | - **먼저 집어넣은 데이터가 먼저 나옵니다.** 이 특징을 줄여서 FIFO(First In First Out)라고 부릅니다. 17 | - 데이터를 집어넣는 enqueue, 데이터를 추출하는 dequeue 등의 작업을 할 수 있습니다. 18 | 19 | 20 | 21 | JavaScript에서는 배열을 이용해서 간단하게 큐를 구현할 수 있습니다. 22 | 23 | ```js 24 | class Queue { 25 | constructor() { 26 | this._arr = [] 27 | } 28 | enqueue(item) { 29 | this._arr.push(item) 30 | } 31 | dequeue() { 32 | return this._arr.shift() 33 | } 34 | } 35 | 36 | const queue = new Queue() 37 | queue.enqueue(1) 38 | queue.enqueue(2) 39 | queue.enqueue(3) 40 | queue.dequeue() // 1 41 | ``` 42 | 43 | 큐는 **순서대로 처리해야 하는 작업을 임시로 저장해두는 버퍼(buffer)**로서 많이 사용됩니다. 44 | 45 | ## 스택 (Stack) 46 | 47 | 스택(stack) 다음과 같은 성질을 갖는 자료형입니다. 48 | 49 | - 데이터를 집어넣을 수 있는 선형(linear) 자료형입니다. 50 | - **나중에 집어넣은 데이터가 먼저 나옵니다.** 이 특징을 줄여서 LIFO(Last In First Out)라고 부릅니다. 51 | - 데이터를 집어넣는 push, 데이터를 추출하는 pop, 맨 나중에 집어넣은 데이터를 확인하는 peek 등의 작업을 할 수 있습니다. 52 | 53 | 54 | 55 | JavaScript에서는 배열을 이용해서 간단하게 스택을 구현할 수 있습니다. 56 | 57 | ```js 58 | class Stack { 59 | constructor() { 60 | this._arr = [] 61 | } 62 | push(item) { 63 | this._arr.push(item) 64 | } 65 | pop() { 66 | return this._arr.pop() 67 | } 68 | peek() { 69 | return this._arr[this._arr.length - 1] 70 | } 71 | } 72 | 73 | const stack = new Stack() 74 | stack.push(1) 75 | stack.push(2) 76 | stack.push(3) 77 | stack.pop() // 3 78 | ``` 79 | 80 | 스택은 서로 관계가 있는 여러 작업을 연달아 수행하면서 **이전의 작업 내용을 저장해 둘 필요가 있을 때** 널리 사용됩니다. 81 | 82 | ## 트리 (Tree) 83 | 84 | 트리(tree)는 여러 데이터가 **계층 구조** 안에서 서로 연결된 형태를 나타낼 때 사용됩니다. 85 | 86 | 87 | 88 | 트리를 다룰 때 사용되는 몇 가지 용어를 살펴보겠습니다. 89 | 90 | - 노드(node) - 트리 안에 들어있는 각 항목을 말합니다. 91 | - 자식 노드(child node) - 노드는 여러 자식 노드를 가질 수 있습니다. 92 | - 부모 노드(parent node) - 노드 A가 노드 B를 자식으로 갖고 있다면, 노드 A를 노드 B의 '부모 노드'라고 부릅니다. 93 | - 뿌리 노드(root node) - 트리의 가장 상층부에 있는 노드를 말합니다. 94 | - 잎 노드(leaf node) - 자식 노드가 없는 노드를 말합니다. 95 | - 조상 노드(ancestor node) - 노드 A의 자식을 따라 내려갔을 때 노드 B에 도달할 수 있다면, 노드 A를 노드 B의 조상 노드라고 부릅니다. 96 | - 자손 노드(descendant node) - 노드 A가 노드 B의 조상 노드일 때, 노드 B를 노드 A의 자손 노드라고 부릅니다. 97 | - 형제 노드(sibling node) - 같은 부모 노드를 갖는 다른 노드를 보고 형제 노드라고 부릅니다. 98 | 99 | 아래는 아주 간단한 형태의 `Node`를 구현한 예입니다. 100 | 101 | ```js 102 | class Node { 103 | constructor(content, children = []) { 104 | this.content = content 105 | this.children = children 106 | } 107 | } 108 | 109 | const tree = new Node('hello', [ 110 | new Node('world'), 111 | new Node('and'), 112 | new Node('fun', [new Node('javascript!')]), 113 | ]) 114 | 115 | function traverse(node) { 116 | console.log(node.content) 117 | for (let child of node.children) { 118 | traverse(child) 119 | } 120 | } 121 | 122 | traverse(tree) 123 | // hello world and fun javascript! 124 | ``` 125 | 126 | 트리는 계층 구조를 나타내기 위해, 또한 계층 구조를 통해 알고리즘의 효율을 높이고자 할 때 널리 사용됩니다. 127 | 128 | ## Set 집합 연산 129 | 130 | Set은 중복되지 않는 값들의 집합을 나타내는 자료구조입니다. ES2024와 ES2025에서는 Set에 집합 이론의 기본 연산들이 추가되어, 수학적인 집합 연산을 쉽게 수행할 수 있게 되었습니다. 131 | 132 | ### 집합 연산 메소드 133 | 134 | ```js 135 | const setA = new Set([1, 2, 3, 4]) 136 | const setB = new Set([3, 4, 5, 6]) 137 | 138 | // 합집합 (Union) - 두 집합의 모든 요소 139 | const union = setA.union(setB) 140 | console.log([...union]) // [1, 2, 3, 4, 5, 6] 141 | 142 | // 교집합 (Intersection) - 양쪽 집합 모두에 있는 요소 143 | const intersection = setA.intersection(setB) 144 | console.log([...intersection]) // [3, 4] 145 | 146 | // 차집합 (Difference) - setA에는 있지만 setB에는 없는 요소 147 | const difference = setA.difference(setB) 148 | console.log([...difference]) // [1, 2] 149 | 150 | // 대칭 차집합 (Symmetric Difference) - 한쪽에만 있는 요소 151 | const symDiff = setA.symmetricDifference(setB) 152 | console.log([...symDiff]) // [1, 2, 5, 6] 153 | ``` 154 | 155 | ### 집합 비교 메소드 156 | 157 | Set 간의 포함 관계를 확인하는 메소드들도 추가되었습니다. 158 | 159 | ```js 160 | const setA = new Set([1, 2, 3, 4]) 161 | const setB = new Set([1, 2]) 162 | const setC = new Set([5, 6]) 163 | 164 | // 부분집합 확인 (Subset) - setB의 모든 요소가 setA에 포함되는가? 165 | setB.isSubsetOf(setA) // true 166 | setA.isSubsetOf(setB) // false 167 | 168 | // 상위집합 확인 (Superset) - setA가 setB의 모든 요소를 포함하는가? 169 | setA.isSupersetOf(setB) // true 170 | setB.isSupersetOf(setA) // false 171 | 172 | // 서로소 집합 확인 (Disjoint) - 공통 요소가 없는가? 173 | setA.isDisjointFrom(setC) // true (공통 요소 없음) 174 | setA.isDisjointFrom(setB) // false (공통 요소 있음) 175 | ``` 176 | 177 | ### 실용적인 예시 178 | 179 | 이러한 Set 연산은 데이터 분석이나 권한 관리 등 다양한 실무 상황에서 유용하게 사용될 수 있습니다. 180 | 181 | ```js 182 | // 예시: 사용자 권한 관리 183 | const adminPermissions = new Set(['read', 'write', 'delete']) 184 | const userPermissions = new Set(['read', 'write']) 185 | 186 | // 사용자에게 없는 권한 찾기 187 | const missingPermissions = adminPermissions.difference(userPermissions) 188 | console.log([...missingPermissions]) // ['delete'] 189 | 190 | // 공통 권한 찾기 191 | const commonPermissions = adminPermissions.intersection(userPermissions) 192 | console.log([...commonPermissions]) // ['read', 'write'] 193 | ``` 194 | 195 | 이러한 Set 메소드들은 코드를 더 간결하고 읽기 쉽게 만들어주며, 집합 관련 로직을 직관적으로 표현할 수 있게 해줍니다. 196 | -------------------------------------------------------------------------------- /specs/001-it-s-been/spec.md: -------------------------------------------------------------------------------- 1 | # Feature Specification: JavaScript Tutorial Book Content Update 2 | 3 | **Feature Branch**: `001-it-s-been` 4 | **Created**: 2025-10-02 5 | **Status**: Draft 6 | **Input**: User description: "It's been a while from last update of this book. Let's keep up-to-date the contents of this book." 7 | 8 | --- 9 | 10 | ## ⚡ Quick Guidelines 11 | - ✅ Focus on WHAT users need and WHY 12 | - ❌ Avoid HOW to implement (no tech stack, APIs, code structure) 13 | - 👥 Written for business stakeholders, not developers 14 | 15 | --- 16 | 17 | ## User Scenarios & Testing *(mandatory)* 18 | 19 | ### Primary User Story 20 | As a learner studying JavaScript through this tutorial book, I need the content to reflect current JavaScript standards and best practices so that I can learn relevant, up-to-date knowledge applicable to today's development environment. The book's core content was last substantially updated in 2021 (4 years ago), with minor updates in April 2023. 21 | 22 | ### Acceptance Scenarios 23 | 1. **Given** a learner reads the ECMAScript version references, **When** they check the current year and latest ECMAScript version, **Then** the book should reference the most recent stable ECMAScript version (ES2024 or ES2025) 24 | 2. **Given** a learner reads about JavaScript features, **When** they encounter feature descriptions and examples, **Then** the content should reflect modern JavaScript practices widely adopted in the industry as of 2025 25 | 3. **Given** a learner follows external reference links in the book, **When** they click on documentation or resource links, **Then** the links should point to current, accessible resources 26 | 4. **Given** a learner reads about browser compatibility, **When** they see information about which browsers support which features, **Then** the compatibility information should reflect the current state of modern browsers (Chrome, Firefox, Safari, Edge as of 2025) 27 | 5. **Given** a learner encounters deprecated patterns or features, **When** they read the content, **Then** outdated approaches should be clearly marked as legacy with guidance toward modern alternatives 28 | 29 | ### Edge Cases 30 | - What happens when external links (MDN, TC39, compatibility tables) have moved or changed URLs? 31 | - How does the book handle features that were experimental in 2021 but are now stable or standardized? 32 | - How should content address features that were recommended in 2021 but are now considered anti-patterns? 33 | - What about JavaScript features introduced between 2021-2025 (ES2022, ES2023, ES2024, ES2025) that should be included? 34 | 35 | ## Requirements *(mandatory)* 36 | 37 | ### Functional Requirements 38 | - **FR-001**: Content MUST reference the latest stable ECMAScript version as of 2025 (ES2024 or ES2025) 39 | - **FR-002**: All external links (MDN documentation, TC39 specifications, compatibility tables, etc.) MUST be verified and updated to current URLs 40 | - **FR-003**: Browser compatibility information MUST reflect the current state of major browsers as of 2025 41 | - **FR-004**: Code examples MUST use modern JavaScript syntax and patterns that are widely supported in 2025 42 | - **FR-005**: Features and APIs that have become deprecated or obsolete since 2021 MUST be marked as such with alternatives provided 43 | - **FR-006**: New JavaScript features introduced between 2021-2025 (ES2022, ES2023, ES2024, ES2025) that are relevant for beginners MUST be evaluated for inclusion 44 | - **FR-007**: Content about transpilers and polyfills MUST reflect the current state of these tools (Babel, TypeScript, etc.) as of 2025 45 | - **FR-008**: References to browser versions, Node.js versions, and other runtime environments MUST be updated to current stable versions 46 | - **FR-009**: Performance recommendations and best practices MUST align with current industry standards as of 2025 47 | - **FR-010**: All factual claims about JavaScript ecosystem (frameworks, libraries, tools) MUST be verified against current reality 48 | 49 | ### Key Entities *(include if feature involves data)* 50 | - **Content Section**: A chapter or page in the tutorial book containing explanations, code examples, and references about a specific JavaScript topic 51 | - **Code Example**: Executable JavaScript code snippet demonstrating a concept or feature 52 | - **External Reference**: Links to authoritative sources (MDN, ECMAScript spec, compatibility tables, tool documentation) 53 | - **Version Reference**: Mentions of specific versions (ECMAScript editions, browser versions, Node.js versions) 54 | - **Feature Description**: Explanation of a JavaScript language feature, API, or pattern 55 | 56 | --- 57 | 58 | ## Review & Acceptance Checklist 59 | *GATE: Automated checks run during main() execution* 60 | 61 | ### Content Quality 62 | - [x] No implementation details (languages, frameworks, APIs) 63 | - [x] Focused on user value and business needs 64 | - [x] Written for non-technical stakeholders 65 | - [x] All mandatory sections completed 66 | 67 | ### Requirement Completeness 68 | - [x] No [NEEDS CLARIFICATION] markers remain 69 | - [x] Requirements are testable and unambiguous 70 | - [x] Success criteria are measurable 71 | - [x] Scope is clearly bounded 72 | - [x] Dependencies and assumptions identified 73 | 74 | --- 75 | 76 | ## Execution Status 77 | *Updated by main() during processing* 78 | 79 | - [x] User description parsed 80 | - [x] Key concepts extracted 81 | - [x] Ambiguities marked 82 | - [x] User scenarios defined 83 | - [x] Requirements generated 84 | - [x] Entities identified 85 | - [x] Review checklist passed 86 | 87 | --- 88 | -------------------------------------------------------------------------------- /.specify/scripts/bash/check-prerequisites.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Consolidated prerequisite checking script 4 | # 5 | # This script provides unified prerequisite checking for Spec-Driven Development workflow. 6 | # It replaces the functionality previously spread across multiple scripts. 7 | # 8 | # Usage: ./check-prerequisites.sh [OPTIONS] 9 | # 10 | # OPTIONS: 11 | # --json Output in JSON format 12 | # --require-tasks Require tasks.md to exist (for implementation phase) 13 | # --include-tasks Include tasks.md in AVAILABLE_DOCS list 14 | # --paths-only Only output path variables (no validation) 15 | # --help, -h Show help message 16 | # 17 | # OUTPUTS: 18 | # JSON mode: {"FEATURE_DIR":"...", "AVAILABLE_DOCS":["..."]} 19 | # Text mode: FEATURE_DIR:... \n AVAILABLE_DOCS: \n ✓/✗ file.md 20 | # Paths only: REPO_ROOT: ... \n BRANCH: ... \n FEATURE_DIR: ... etc. 21 | 22 | set -e 23 | 24 | # Parse command line arguments 25 | JSON_MODE=false 26 | REQUIRE_TASKS=false 27 | INCLUDE_TASKS=false 28 | PATHS_ONLY=false 29 | 30 | for arg in "$@"; do 31 | case "$arg" in 32 | --json) 33 | JSON_MODE=true 34 | ;; 35 | --require-tasks) 36 | REQUIRE_TASKS=true 37 | ;; 38 | --include-tasks) 39 | INCLUDE_TASKS=true 40 | ;; 41 | --paths-only) 42 | PATHS_ONLY=true 43 | ;; 44 | --help|-h) 45 | cat << 'EOF' 46 | Usage: check-prerequisites.sh [OPTIONS] 47 | 48 | Consolidated prerequisite checking for Spec-Driven Development workflow. 49 | 50 | OPTIONS: 51 | --json Output in JSON format 52 | --require-tasks Require tasks.md to exist (for implementation phase) 53 | --include-tasks Include tasks.md in AVAILABLE_DOCS list 54 | --paths-only Only output path variables (no prerequisite validation) 55 | --help, -h Show this help message 56 | 57 | EXAMPLES: 58 | # Check task prerequisites (plan.md required) 59 | ./check-prerequisites.sh --json 60 | 61 | # Check implementation prerequisites (plan.md + tasks.md required) 62 | ./check-prerequisites.sh --json --require-tasks --include-tasks 63 | 64 | # Get feature paths only (no validation) 65 | ./check-prerequisites.sh --paths-only 66 | 67 | EOF 68 | exit 0 69 | ;; 70 | *) 71 | echo "ERROR: Unknown option '$arg'. Use --help for usage information." >&2 72 | exit 1 73 | ;; 74 | esac 75 | done 76 | 77 | # Source common functions 78 | SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 79 | source "$SCRIPT_DIR/common.sh" 80 | 81 | # Get feature paths and validate branch 82 | eval $(get_feature_paths) 83 | check_feature_branch "$CURRENT_BRANCH" "$HAS_GIT" || exit 1 84 | 85 | # If paths-only mode, output paths and exit (support JSON + paths-only combined) 86 | if $PATHS_ONLY; then 87 | if $JSON_MODE; then 88 | # Minimal JSON paths payload (no validation performed) 89 | printf '{"REPO_ROOT":"%s","BRANCH":"%s","FEATURE_DIR":"%s","FEATURE_SPEC":"%s","IMPL_PLAN":"%s","TASKS":"%s"}\n' \ 90 | "$REPO_ROOT" "$CURRENT_BRANCH" "$FEATURE_DIR" "$FEATURE_SPEC" "$IMPL_PLAN" "$TASKS" 91 | else 92 | echo "REPO_ROOT: $REPO_ROOT" 93 | echo "BRANCH: $CURRENT_BRANCH" 94 | echo "FEATURE_DIR: $FEATURE_DIR" 95 | echo "FEATURE_SPEC: $FEATURE_SPEC" 96 | echo "IMPL_PLAN: $IMPL_PLAN" 97 | echo "TASKS: $TASKS" 98 | fi 99 | exit 0 100 | fi 101 | 102 | # Validate required directories and files 103 | if [[ ! -d "$FEATURE_DIR" ]]; then 104 | echo "ERROR: Feature directory not found: $FEATURE_DIR" >&2 105 | echo "Run /specify first to create the feature structure." >&2 106 | exit 1 107 | fi 108 | 109 | if [[ ! -f "$IMPL_PLAN" ]]; then 110 | echo "ERROR: plan.md not found in $FEATURE_DIR" >&2 111 | echo "Run /plan first to create the implementation plan." >&2 112 | exit 1 113 | fi 114 | 115 | # Check for tasks.md if required 116 | if $REQUIRE_TASKS && [[ ! -f "$TASKS" ]]; then 117 | echo "ERROR: tasks.md not found in $FEATURE_DIR" >&2 118 | echo "Run /tasks first to create the task list." >&2 119 | exit 1 120 | fi 121 | 122 | # Build list of available documents 123 | docs=() 124 | 125 | # Always check these optional docs 126 | [[ -f "$RESEARCH" ]] && docs+=("research.md") 127 | [[ -f "$DATA_MODEL" ]] && docs+=("data-model.md") 128 | 129 | # Check contracts directory (only if it exists and has files) 130 | if [[ -d "$CONTRACTS_DIR" ]] && [[ -n "$(ls -A "$CONTRACTS_DIR" 2>/dev/null)" ]]; then 131 | docs+=("contracts/") 132 | fi 133 | 134 | [[ -f "$QUICKSTART" ]] && docs+=("quickstart.md") 135 | 136 | # Include tasks.md if requested and it exists 137 | if $INCLUDE_TASKS && [[ -f "$TASKS" ]]; then 138 | docs+=("tasks.md") 139 | fi 140 | 141 | # Output results 142 | if $JSON_MODE; then 143 | # Build JSON array of documents 144 | if [[ ${#docs[@]} -eq 0 ]]; then 145 | json_docs="[]" 146 | else 147 | json_docs=$(printf '"%s",' "${docs[@]}") 148 | json_docs="[${json_docs%,}]" 149 | fi 150 | 151 | printf '{"FEATURE_DIR":"%s","AVAILABLE_DOCS":%s}\n' "$FEATURE_DIR" "$json_docs" 152 | else 153 | # Text output 154 | echo "FEATURE_DIR:$FEATURE_DIR" 155 | echo "AVAILABLE_DOCS:" 156 | 157 | # Show status of each potential document 158 | check_file "$RESEARCH" "research.md" 159 | check_file "$DATA_MODEL" "data-model.md" 160 | check_dir "$CONTRACTS_DIR" "contracts/" 161 | check_file "$QUICKSTART" "quickstart.md" 162 | 163 | if $INCLUDE_TASKS; then 164 | check_file "$TASKS" "tasks.md" 165 | fi 166 | fi -------------------------------------------------------------------------------- /content/pages/140-string.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: string 타입 3 | --- 4 | 5 | 문자열에 `typeof` 연산을 해보면 다음과 같은 결과가 나옵니다. 6 | 7 | ```js 8 | typeof 'hello' // 'string' 9 | ``` 10 | 11 | 컴퓨터 분야에서는 문자의 나열(string)이라는 뜻에서 문자열을 'string'이라 부릅니다. string 타입을 통해 일반적인 텍스트 데이터를 다룰 수 있습니다. JavaScript 문자열은 내부적으로 [유니코드(Unicode)](https://ko.wikipedia.org/wiki/%EC%9C%A0%EB%8B%88%EC%BD%94%EB%93%9C)를 통해 표현됩니다.[^1] 12 | 13 | ## 문자열 리터럴 14 | 15 | JavaScript는 문자열 값을 표현하기 위한 여러 가지 리터럴을 제공합니다. 16 | 17 | ```js 18 | 'hello' 19 | 'hello 안녕하세요'`hello world` // template literal 20 | ``` 21 | 22 | 따옴표는 표기법일 뿐, 실제 저장되는 값에 영향을 미치지는 않습니다. 예를 들어, `hello`라는 문자열을 표현하기 위 셋 중에 어떤 리터럴을 사용해도 실제 저장되는 값이 달라지지는 않습니다.[^2] 23 | 24 | ```js 25 | 'hello' === 'hello' // true 26 | ``` 27 | 28 | ## 템플릿 리터럴 (Template Literal) 29 | 30 | ES2015에서 도입된 템플릿 리터럴(template literal)은 문자열 리터럴의 일종으로, 추가적인 기능을 지원합니다. 템플릿 리터럴을 사용하려면 backtick(`)으로 문자열을 둘러싸면 됩니다. 31 | 32 | ```js 33 | ;`hello world` 34 | ``` 35 | 36 | 템플릿 리터럴의 내삽(interpolation) 기능을 이용하면, 문자열을 동적으로 생성하는 코드를 쉽게 작성할 수 있습니다. 37 | 38 | ```js 39 | const name1 = 'Foo' 40 | const name2 = 'Bar' 41 | const sentence = `${name1} meets ${name2}!` 42 | console.log(sentence) 43 | 44 | // 일반적인 문자열 리터럴로는 아래와 같이 해야 합니다. 45 | name1 + ' meets ' + name2 + '!' 46 | ``` 47 | 48 | 또한, 템플릿 리터럴을 사용하면 **여러 줄로 이루어진 문자열**을 쉽게 표현할 수 있습니다. 49 | 50 | ```js 51 | ;`hello 52 | world 53 | hello 54 | javascript! 55 | ` 56 | 57 | // 일반적인 문자열 리터럴로는 아래와 같이 해야 합니다. 58 | ;('hello\nworld\nhello\njavascript!\n') 59 | ``` 60 | 61 | 템플릿 리터럴은 아래와 같이 특이한 형태의 함수 호출 기능을 지원하는데. 이를 'tagged template literal'이라고 합니다. 주로 다른 언어를 JavaScript와 통합할 때 사용되고, 라이브러리 제작자가 아니라면 보통은 tagged template literal을 위한 함수를 직접 제작할 일은 없습니다. 자세한 내용을 알고 싶다면 [이 문서](https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Template_literals#Tagged_template_literals)를 참고하세요. 62 | 63 | ```js 64 | styled.div` 65 | display: flex; 66 | border: 1px solid black; 67 | ` // styled-components 68 | gql` 69 | query { 70 | user { 71 | name 72 | } 73 | } 74 | ` // graphql-tag 75 | html`Hello tagged template literal!` // lit-html 76 | ``` 77 | 78 | ## Escape Sequence 79 | 80 | JavaScript는 특수 문자를 문자열에 넣거나, 혹은 직접 유니코드 코드포인트를 사용해서 문자를 넣을 수 있도록 해주는 escape sequence를 제공합니다. 81 | 82 | | 표기법 | 문자 | 83 | | -------- | ------------- | 84 | | \' | 홑따옴표 | 85 | | \" | 쌍따옴표 | 86 | | \\\\ | 백슬래시 | 87 | | \n | 라인 피드 | 88 | | \r | 캐리지 리턴 | 89 | | \t | 탭 | 90 | | \uXXXX | 유니코드 문자 | 91 | | \u{X...} | 유니코드 문자 | 92 | | \\$ | 달러 | 93 | | \\` | 백틱 | 94 | 95 | 위 escape sequence를 문자열 안에서 사용할 수 있습니다. 96 | 97 | ```js 98 | console.log("lorem 'ipsum'") // lorem 'ipsum' 99 | console.log('line\nfeed') // line(줄바꿈)feed 100 | console.log('\uD55C\uAE00') // 한글 101 | console.log('\u{1F435}') // 🐵 102 | ``` 103 | 104 | 다만 리터럴을 위해 사용한 따옴표와 **다른 종류의 따옴표**들은 문자열 내에서 자유롭게 쓸 수 있으므로 굳이 escape sequence로 표기해주어야 할 필요가 없습니다. 105 | 106 | ```js 107 | "`lorem` 'ipsum'" 108 | '`lorem` "ipsum"' 109 | ;`'lorem' "ipsum"` 110 | ``` 111 | 112 | 위 표의 라인 피드(line feed)와 캐리지 리턴(carage return)은 **개행 문자**로, 우리가 보통 엔터를 누를 때 입력되는 문자입니다. 각각을 줄여서 `LF`, `CR` 이라고 표기하고, 맥과 리눅스에서는 `LF`, 윈도우에서는 `CR+LF`가 개행문자로 사용됩니다. 개행 문자에 대해서 자세히 알아보려면 [이 링크](https://ko.wikipedia.org/wiki/%EC%83%88%EC%A4%84_%EB%AC%B8%EC%9E%90)를 참고하세요. 113 | 114 | ## 문자열과 연산자 115 | 116 | 수 타입 뿐 아니라 문자열에 대해서도 여러 가지 연산자를 쓸 수 있습니다. 117 | 118 | ```js 119 | // 문자열 연결하기 120 | 'hello' + 'world' // 'helloworld' 121 | 122 | // 등호 비교 123 | 'hello' === 'hello' // true 124 | 'hello' !== 'hello' // false 125 | 126 | // 유니코드 코드포인트 비교. 앞에서부터 한 글자씩 차례대로 비교합니다. 127 | 'a' < 'b' // true 128 | 'aaa' < 'abc' // true 129 | 'a' < 'Z' // false 130 | '한글' < '한국어' // false 131 | '2' < '10' // false 132 | 133 | // 문자열을 배열로 바꾸기 134 | ;[...'hello'] // ['h', 'e', 'l', 'l', 'o'] 135 | ``` 136 | 137 | 위에서 알 수 있는 것처럼 유니코드 코드포인트 비교는 사전순 비교가 아니므로 주의해야 합니다. 사전순 비교를 하려면 `localeCompare` 메소드를 사용하세요. 138 | 139 | ```js 140 | 'b'.localeCompare('a') // 1 141 | 'b'.localeCompare('b') // 0 142 | 'b'.localeCompare('z') // -1 143 | 'b'.localeCompare('Z') // -1 144 | '가나다'.localeCompare('마바사') // -1 145 | ``` 146 | 147 | ## 속성 및 메소드 148 | 149 | number 타입과 마찬가지로 string 타입도 래퍼 객체의 속성과 메소드를 사용할 수 있습니다. 아래는 자주 쓰이는 몇 개의 속성과 메소드에 대한 예제입니다. 이 밖의 내용을 확인하려면 [MDN 문서](https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/String#String_instances)를 참고하세요. 150 | 151 | ```js 152 | // 문자열의 길이 알아내기 153 | 'hello'.length // 5 154 | 155 | // 여러 문자열 연결하기 156 | 'hello'.concat('fun', 'javascript') // 'hellofunjavascript' 157 | 158 | // 특정 문자열을 반복하는 새 문자열 생성하기 159 | '*'.repeat(3) // '***' 160 | 161 | // 특정 문자열이 포함되어 있는지 확인하기 162 | 'hello javascript'.includes('hello') // true 163 | 'hello javascript'.startsWith('he') // true 164 | 'hello javascript'.endsWith('ript') // true 165 | 'hello javascript'.indexOf('java') // 6 166 | 167 | // 문자열의 특정 부분을 바꾼 새 문자열 생성하기 168 | 'hello javascript'.replace('java', 'type') // 'hello typescript' 169 | 170 | // 문자열의 일부를 잘라낸 새 문자열 생성하기 171 | 'hello'.slice(2, 4) // 'll' 172 | 173 | // 좌우 공백문자를 제거한 새 문자열 생성하기 174 | ' hello '.trim() // 'hello' 175 | ' hello '.trimLeft() // 'hello ' 176 | ' hello '.trimRight() // ' hello' 177 | 178 | // 좌우 공백문자를 추가한 새 문자열 생성하기 179 | 'hello'.padStart(8) // ' hello' 180 | 'hello'.padEnd(8) // 'hello ' 181 | 182 | // 문자열을 특정 문자를 기준으로 잘라 새 배열 생성하기 183 | 'hello!fun!javavscript'.split('!') // ['hello', 'fun', 'javascript'] 184 | 'hello'.split('') // ['h', 'e', 'l', 'l', 'o'] 185 | 186 | // 모든 문자를 소문자, 혹은 대문자로 변환한 새 문자열 생성하기 187 | 'Hello JavaScript'.toLowerCase() // 'hello javascript' 188 | 'Hello JavaScript'.toUpperCase() // 'HELLO JAVASCRIPT' 189 | ``` 190 | 191 | [^1]: 정확히 말하면, 문자열은 JavaScript 내부적으로 UTF-16 형식으로 인코딩된 값으로 다뤄집니다. ([명세](https://tc39.github.io/ecma262/#sec-ecmascript-language-types-string-type)) 192 | 193 | [^2]: 그렇다고 해서 따옴표를 마구잡이로 섞어 쓰는 게 좋은 것은 아니며, 꼭 필요한 경우가 아니라면 코드의 일관성을 위해 한 가지 종류의 따옴표만을 사용하는 것이 좋습니다. 주로 홑따옴표(`'`)가 널리 사용됩니다. 194 | -------------------------------------------------------------------------------- /content/pages/020-tutorial.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: 튜토리얼 3 | description: 교재를 읽어나가기 위해 JavaScript 언어의 기본적인 구성요소를 이해하고 넘어갈 필요가 있습니다. 4 | --- 5 | 6 | 교재를 읽어나가기 위해 JavaScript 언어의 기본적인 구성요소를 이해하고 넘어갈 필요가 있습니다. 아래에 나오는 간단한 예제들을 직접 실행해 봅시다. 각 요소들에 대한 자세한 설명은 이어지는 챕터에서 다룹니다. 7 | 8 | ## 코드의 실행 9 | 10 | 기본적으로 JavaScript 코드는 세미콜론(`;`)으로 구분된 구문(statement) 단위로 위에서부터 차례대로 실행됩니다. 그러나 제어 흐름, 예외 처리, 함수 호출 등을 만나면 코드의 실행 흐름이 이리저리 옮겨다니기도 합니다. 11 | 12 | JavaScript 코드는 REPL이라는 도구를 이용해서 조금씩 실행시킬 수도 있고, 혹은 미리 작성해둔 많은 양의 코드를 한 번에 실행시킬 수도 있습니다. 여기서 REPL(Read-Evaluate-Print-Loop)는 코드를 실행시키고 그 결과를 바로 확인할 수 있는 컴퓨터 프로그램을 총칭합니다. 아래에 나오는 코드 예제를 실행시키려면 웹 브라우저의 개발자 도구 콘솔(F12 키 또는 Cmd+Opt+I)을 이용하거나, [Replit](https://replit.com/)과 같은 온라인 REPL을 이용하세요. 13 | 14 | ## 기본 문법 15 | 16 | ### 대소문자의 구분 17 | 18 | JavaScript 언어는 모든 부분에서 대소문자 구분을 하기 때문에 주의해야 합니다. 예를 들어 `function`과 `Function`은 JavaScript에서 완전히 다른 의미를 가집니다. 19 | 20 | ### 세미콜론 21 | 22 | JavaScript는 세미콜론(;)을 이용해서 각 구문을 구분합니다. 23 | 24 | ```js 25 | const a = 1 26 | const b = 2 27 | 28 | // 혹은 이렇게 할 수도 있습니다만, 좋아 보이지는 않습니다. 29 | const c = 1 30 | const d = 2 31 | ``` 32 | 33 | 특별한 경우가 아니라면, 세미콜론을 쓰고 나서는 개행을 해서 각각의 구문이 구분되어 보일 수 있도록 하는 것이 좋습니다. 34 | 35 | ### 공백 36 | 37 | JavaScript 언어는 공백에 민감하지 않은 언어입니다. 즉, 다른 문법 요소만 잘 지킨다면 공백의 수가 코드의 실행에 별 영향을 미치지 않습니다.[^1] 38 | 39 | ```js 40 | // 아래 세 구문은 완벽히 똑같은 동작을 합니다. 41 | const x = 1 42 | 43 | const x = 1 44 | 45 | const x = 1 46 | ``` 47 | 48 | 또한 코드의 동작 여부와는 별개로, 코드를 읽기 쉽도록 공백을 깔끔하게 유지해야 할 필요가 있습니다. 49 | 50 | ## 주석(comment) 51 | 52 | 주석은 실행되는 코드는 아니지만, 코드에 부가적인 설명을 넣고 싶을 때 사용합니다. 53 | 54 | ```js 55 | // 한 줄 주석 56 | 57 | /* 여러 줄 주석 */ 58 | 59 | /* 60 | 여러 61 | 줄 62 | 주석 63 | */ 64 | ``` 65 | 66 | ## 값(value)과 그 리터럴(literal) 67 | 68 | 프로그래밍은 근본적으로 '값'을 다루는 행위라 할 수 있습니다. JavaScript에도 여러 가지 값을 표현하는 문법이 있으며, 이를 리터럴(literal)이라고 합니다. 69 | 70 | ```js 71 | 1 // 정수 72 | 2.5 // 부동소수점 실수 73 | ;('hello world') // 작은 따옴표 문자열 74 | ;('JavaScript') // 큰 따옴표 문자열 75 | true // 참임을 나타내는 진리값 76 | false // 거짓임을 나타내는 진리값 77 | null // '존재하지 않음'을 나타내는 값 78 | undefined // '정의된 적 없음'을 나타내는 값 79 | ``` 80 | 81 | ## 값의 타입(type) 82 | 83 | JavaScript에서 다루어지는 모든 값은 그 '타입'을 가지고 있습니다. 쉽게 '값의 종류'라고 생각하시면 됩니다. `typeof` 연산자는 피연산자의 타입을 가리키는 문자열을 반환합니다. 84 | 85 | ```js 86 | typeof 1 // 'number' 87 | typeof 2.5 // 'number' 88 | typeof 'hello world' // 'string' 89 | typeof true // 'boolean' 90 | typeof null // 'object' 91 | typeof undefined // 'undefined' 92 | // ... 93 | ``` 94 | 95 | ## 표현식(expression)과 연산자(operator) 96 | 97 | 표현식이란 JavaScript 문장의 구성요소를 구분하는 하나의 단위로, **값으로 변환될 수 있는 부분**을 가리킵니다. 98 | 99 | JavaScript에서는 수학에서 사용하던 것과 비슷한 여러 가지 종류의 연산자를 통해 연산을 수행할 수 있습니다. 하지만 수학에서 보던 것과는 동작 방식이 달라서 입문자에게는 익숙하지 않은 연산자들도 있습니다. 100 | 101 | ```js 102 | // 사칙 연산 103 | 1 + 3 104 | 2 - 4 105 | 3 * 5 106 | 4 / 6 107 | 108 | // 연산자 우선순위 (operator precedence) 109 | 1 + 2 * 3 110 | ;(1 + 2) * 3 111 | 112 | // 논리 연산 113 | true || false 114 | true && false 115 | 116 | // 진리값으로 취급하기 117 | 1 || 0 118 | 1 && 0 119 | ``` 120 | 121 | ## 변수(variable) 122 | 123 | 값을 재사용하기 위해 값에 붙일 이름을 선언하고 그 이름에 값을 대입할 수 있습니다. 이 때 이 이름을 변수(variable)라고 부릅니다. `let` 변수에는 값을 여러 번 다시 대입할 수 있지만, `const` 변수에는 값을 한 번만 대입할 수 있습니다. 124 | 125 | ```js 126 | // 한 변수의 선언 127 | let v 128 | 129 | // 값의 대입 130 | v = 1 131 | 132 | // 변수의 선언과 값의 대입을 동시에 133 | let x = 1 134 | 135 | // 두 변수의 선언과 대입 136 | let y = 2, 137 | z = x + y 138 | 139 | // const 변수 140 | const c = 1 141 | c = 2 // 에러! 142 | 143 | // 선언한 변수를 사용하기 144 | x 145 | typeof x 146 | x * z 147 | ``` 148 | 149 | ## 제어 흐름 150 | 151 | JavaScript는 특정 조건을 만족할 때에만 코드를 실행시키거나, 혹은 여러 번 실행시킬 수 있는 구문을 제공합니다. 152 | 153 | ```js 154 | // if 구문 155 | if (2 > 1) { 156 | console.log('괄호 안의 값이 `true`이면 이 영역의 코드가 실행됩니다.') 157 | } else { 158 | console.log('괄호 안의 값이 `false`면 이 영역의 코드가 실행됩니다.') 159 | } 160 | 161 | // while 구문 162 | let i = 0 163 | while (i < 5) { 164 | console.log(`이 코드는 ${i + 1}번 째 실행되고 있습니다.`) 165 | i++ 166 | } 167 | 168 | // for 구문 169 | for (let i = 0; i < 5; i++) { 170 | // (초기값; 조건; 갱신) 171 | console.log(`이 코드는 ${i + 1}번 째 실행되고 있습니다.`) 172 | } 173 | ``` 174 | 175 | ## 함수 (function) 176 | 177 | 값 뿐만 아니라 **코드 뭉치**에 이름을 붙여서 **재사용**을 할 수도 있는데, 이를 함수라 부릅니다. JavaScript에는 함수를 선언할 수 있는 여러 가지 방법이 있습니다. 178 | 179 | ```js 180 | // `function` 키워드를 이용한 함수 선언 181 | function add(x, y) { 182 | return x + y 183 | } 184 | 185 | // arrow function 186 | const multiply = (x, y) => x * y 187 | 188 | // 함수 호출하기 189 | add(1, 2) // 3 190 | multiply(3, 4) // 12 191 | ``` 192 | 193 | JavaScript 및 그 구동 환경에 내장되어 있는 여러 가지 함수를 사용해서 사용자와 상호작용을 하는 코드를 작성할 수 있습니다. 194 | 195 | ```js 196 | // 브라우저 내장 함수인 `prompt`, `console.log`, `alert` 사용하기 197 | const answer = prompt('이름이 무엇인가요?') 198 | console.log(answer) 199 | alert(answer) 200 | ``` 201 | 202 | ## 객체 203 | 204 | 대부분의 프로그래밍 언어는 여러 개의 값을 한꺼번에 담아 통(container)처럼 사용할 수 있는 자료구조를 내장하고 있습니다. JavaScript에는 **객체(object)**라는 자료구조가 있습니다. 205 | 206 | 객체에는 **이름(name)**에 **값(value)**이 연결되어 저장됩니다. 이를 이름-값 쌍(name-value pair), 혹은 객체의 **속성(property)**이라고 합니다. 객체를 사용할 때는 속성 이름을 이용해서, 속성 값을 읽거나 쓸 수 있습니다. 또는 아래와 같이 여러 속성을 한꺼번에 정의할 수도 있습니다. 207 | 208 | ```js 209 | // 객체의 생성 210 | const obj = { 211 | x: 0, // 객체의 속성. 속성 이름: x, 속성 값: 0 212 | y: 1, // 객체의 속성. 속성 이름: y, 속성 값: 1 213 | } 214 | 215 | // 객체의 속성에 접근하기 216 | obj.x 217 | obj['y'] 218 | 219 | // 객체의 속성 변경하기 220 | obj.x += 1 221 | obj['y'] -= 1 222 | 223 | // 객체의 속성 삭제하기 224 | delete obj.x 225 | ``` 226 | 227 | 객체의 속성을 통해 사용하는 함수를 **메소드(method)**라고 부릅니다. 228 | 229 | ```js 230 | const obj = { 231 | x: 0, 232 | increaseX: function () { 233 | this.x = this.x + 1 234 | }, 235 | } 236 | 237 | obj.increaseX() 238 | console.log(obj.x) // 1 239 | ``` 240 | 241 | ## 배열 242 | 243 | JavaScript에서의 배열은 객체의 일종으로, 다른 객체와는 다르게 특별히 취급됩니다. 배열에 담는 데이터는 객체의 경우와 달리 **요소(element)** 혹은 **항목(item)**이라고 부릅니다. 객체와 마찬가지로 배열 안에 여러 개의 값을 저장할 수 있지만, 배열 요소 간에는 **순서**가 존재하며, 이름 대신에 **인덱스(index)**를 이용해 값에 접근합니다. 244 | 245 | ```js 246 | // 배열의 생성 247 | const arr = ['one', 'two', 'three'] 248 | 249 | // 인덱스를 사용해 배열의 요소(element)에 접근할 수 있습니다. 250 | // 배열 인덱스(index)는 0부터 시작합니다. 251 | arr[0] // === 'one' 252 | arr[1] // === 'two' 253 | 254 | // 여러 타입의 값이 들어있는 배열 255 | ;[1, 2, 3, 'a', 'b', {x: 0, y: 0, name: '원점'}] 256 | 257 | // 배열에 요소 추가하기 258 | arr.push('four') 259 | 260 | // 배열의 요소 삭제하기 261 | arr.splice(3, 1) // 인덱스가 3인 요소부터 시작해서 하나를 삭제합니다. 262 | ``` 263 | 264 | [^1]: 다만, 정말로 공백을 아무렇게나 넣어도 되는 것은 아닙니다. JavaScript에는 몇몇 조건을 만족하면 개행을 세미콜론처럼 취급하는 세미콜론 자동 삽입(ASI, Auto Semicolon Insertion) 기능이 내장되어 있기 때문에 개행을 할 때는 주의해야 합니다. 265 | -------------------------------------------------------------------------------- /.specify/memory/constitution.md: -------------------------------------------------------------------------------- 1 | 15 | 16 | # JavaScript로 만나는 세상 Constitution 17 | 18 | ## Core Principles 19 | 20 | ### I. Accuracy & Currency 21 | Content MUST reflect the latest JavaScript standards and best practices. All technical information must be researched and verified before publication. 22 | 23 | **Rationale**: Educational content requires factual accuracy to build correct mental models. Outdated or incorrect information undermines learning outcomes and student trust. 24 | 25 | ### II. Clarity & Accessibility 26 | Use concise, easily understandable expressions appropriate for readers with no programming experience. Avoid jargon without explanation; prefer concrete examples over abstract theory. 27 | 28 | **Rationale**: The target audience (독자) has zero programming experience. Complex explanations create barriers to entry and reduce learning effectiveness. 29 | 30 | ### III. Progressive Learning Structure 31 | Content must be organized in logical progression from fundamentals to advanced topics. Each section builds on previously introduced concepts without forward dependencies. 32 | 33 | **Rationale**: Learners need scaffolded knowledge construction. Breaking the dependency order forces readers to skip around, fragmenting comprehension. 34 | 35 | ### IV. Practical Examples 36 | Every concept MUST include executable code examples demonstrating real-world usage. Examples should be minimal, focused, and immediately testable. 37 | 38 | **Rationale**: Programming is learned through practice. Abstract explanations without concrete examples fail to activate hands-on learning pathways. 39 | 40 | ### V. Modern JavaScript Focus 41 | Prioritize ES6+ syntax and modern JavaScript features. Legacy patterns should be mentioned only for historical context with clear "do not use" guidance. 42 | 43 | **Rationale**: Students entering the field should learn current industry practices. Teaching outdated patterns creates technical debt in learners' skillsets. 44 | 45 | ## Content Standards 46 | 47 | ### Technical Accuracy 48 | - All code examples MUST be syntax-checked and tested 49 | - Feature compatibility MUST specify browser/Node.js version requirements 50 | - Research current MDN documentation and ECMAScript specifications before writing 51 | - Verify examples work in latest stable JavaScript environments 52 | 53 | ### Language & Style 54 | - Write in Korean (한국어) for explanatory text 55 | - Use English for code, technical terms, and identifiers 56 | - Maintain consistent terminology throughout the book 57 | - Keep sentences short (prefer <50 characters per clause) 58 | - Use active voice over passive constructions 59 | 60 | ### Example Quality 61 | - Examples should be self-contained (runnable without external dependencies) 62 | - Prefer console.log output demonstration over abstract concepts 63 | - Include both "correct usage" and "common mistakes" where helpful 64 | - Code snippets MUST follow Prettier formatting rules (.prettierrc) 65 | 66 | ### Link Integrity 67 | - **Internal links MUST NOT include file extensions** (`.md`, `.mdx`) 68 | - Correct: `[배열](./190-array)` 69 | - Incorrect: `[배열](./190-array.mdx)` 70 | - All links MUST be validated before committing via `npm run test:links` 71 | - External links returning 4XX status codes MUST be updated or removed 72 | - Broken links undermine user experience and content credibility 73 | 74 | ## Development Workflow 75 | 76 | ### Content Creation Process 77 | 1. **Research Phase**: Verify feature specifications against authoritative sources (MDN, TC39, ECMAScript spec) 78 | 2. **Draft Phase**: Write content following clarity and accessibility principles 79 | 3. **Example Phase**: Create tested, working code examples 80 | 4. **Review Phase**: Check against constitution compliance (accuracy, clarity, progression) 81 | 5. **Publication Phase**: Commit changes with descriptive messages 82 | 83 | ### Review Checklist (Per Content Section) 84 | - [ ] Code examples tested and working 85 | - [ ] **All hyperlinks verified (internal and external) - MUST run `npm run test:links` before every commit** 86 | - [ ] Technical accuracy verified against current standards 87 | - [ ] Language is accessible to programming beginners 88 | - [ ] Section fits logically in learning progression 89 | - [ ] Modern JavaScript practices demonstrated 90 | - [ ] No unexplained jargon or undefined terms 91 | 92 | ### Change Management 93 | - Content updates MUST maintain backward compatibility with existing learning paths 94 | - Corrections to errors take priority over new content 95 | - User-reported issues and pull requests should be reviewed for accuracy before merging 96 | 97 | ## Governance 98 | 99 | ### Amendment Procedure 100 | 1. Propose change with rationale and impact analysis 101 | 2. Document which principles/standards are affected 102 | 3. Update constitution version following semantic versioning 103 | 4. Propagate changes to affected templates and workflows 104 | 105 | ### Compliance Expectations 106 | - All content contributions must be reviewed against this constitution 107 | - Template files (.specify/templates/*.md) must align with constitutional principles 108 | - Deviations from principles require explicit justification in commit messages 109 | - Constitution supersedes informal practices or undocumented conventions 110 | 111 | ### Version Policy 112 | - **MAJOR** version: Fundamental restructuring of learning approach or removal of core principles 113 | - **MINOR** version: Addition of new principles or significant expansion of standards 114 | - **PATCH** version: Clarifications, wording improvements, non-semantic refinements 115 | 116 | **Version**: 1.1.0 | **Ratified**: 2025-10-02 | **Last Amended**: 2025-10-03 117 | -------------------------------------------------------------------------------- /.claude/commands/analyze.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Perform a non-destructive cross-artifact consistency and quality analysis across spec.md, plan.md, and tasks.md after task generation. 3 | --- 4 | 5 | The user input to you can be provided directly by the agent or as a command argument - you **MUST** consider it before proceeding with the prompt (if not empty). 6 | 7 | User input: 8 | 9 | $ARGUMENTS 10 | 11 | Goal: Identify inconsistencies, duplications, ambiguities, and underspecified items across the three core artifacts (`spec.md`, `plan.md`, `tasks.md`) before implementation. This command MUST run only after `/tasks` has successfully produced a complete `tasks.md`. 12 | 13 | STRICTLY READ-ONLY: Do **not** modify any files. Output a structured analysis report. Offer an optional remediation plan (user must explicitly approve before any follow-up editing commands would be invoked manually). 14 | 15 | Constitution Authority: The project constitution (`.specify/memory/constitution.md`) is **non-negotiable** within this analysis scope. Constitution conflicts are automatically CRITICAL and require adjustment of the spec, plan, or tasks—not dilution, reinterpretation, or silent ignoring of the principle. If a principle itself needs to change, that must occur in a separate, explicit constitution update outside `/analyze`. 16 | 17 | Execution steps: 18 | 19 | 1. Run `.specify/scripts/bash/check-prerequisites.sh --json --require-tasks --include-tasks` once from repo root and parse JSON for FEATURE_DIR and AVAILABLE_DOCS. Derive absolute paths: 20 | - SPEC = FEATURE_DIR/spec.md 21 | - PLAN = FEATURE_DIR/plan.md 22 | - TASKS = FEATURE_DIR/tasks.md 23 | Abort with an error message if any required file is missing (instruct the user to run missing prerequisite command). 24 | 25 | 2. Load artifacts: 26 | - Parse spec.md sections: Overview/Context, Functional Requirements, Non-Functional Requirements, User Stories, Edge Cases (if present). 27 | - Parse plan.md: Architecture/stack choices, Data Model references, Phases, Technical constraints. 28 | - Parse tasks.md: Task IDs, descriptions, phase grouping, parallel markers [P], referenced file paths. 29 | - Load constitution `.specify/memory/constitution.md` for principle validation. 30 | 31 | 3. Build internal semantic models: 32 | - Requirements inventory: Each functional + non-functional requirement with a stable key (derive slug based on imperative phrase; e.g., "User can upload file" -> `user-can-upload-file`). 33 | - User story/action inventory. 34 | - Task coverage mapping: Map each task to one or more requirements or stories (inference by keyword / explicit reference patterns like IDs or key phrases). 35 | - Constitution rule set: Extract principle names and any MUST/SHOULD normative statements. 36 | 37 | 4. Detection passes: 38 | A. Duplication detection: 39 | - Identify near-duplicate requirements. Mark lower-quality phrasing for consolidation. 40 | B. Ambiguity detection: 41 | - Flag vague adjectives (fast, scalable, secure, intuitive, robust) lacking measurable criteria. 42 | - Flag unresolved placeholders (TODO, TKTK, ???, , etc.). 43 | C. Underspecification: 44 | - Requirements with verbs but missing object or measurable outcome. 45 | - User stories missing acceptance criteria alignment. 46 | - Tasks referencing files or components not defined in spec/plan. 47 | D. Constitution alignment: 48 | - Any requirement or plan element conflicting with a MUST principle. 49 | - Missing mandated sections or quality gates from constitution. 50 | E. Coverage gaps: 51 | - Requirements with zero associated tasks. 52 | - Tasks with no mapped requirement/story. 53 | - Non-functional requirements not reflected in tasks (e.g., performance, security). 54 | F. Inconsistency: 55 | - Terminology drift (same concept named differently across files). 56 | - Data entities referenced in plan but absent in spec (or vice versa). 57 | - Task ordering contradictions (e.g., integration tasks before foundational setup tasks without dependency note). 58 | - Conflicting requirements (e.g., one requires to use Next.js while other says to use Vue as the framework). 59 | 60 | 5. Severity assignment heuristic: 61 | - CRITICAL: Violates constitution MUST, missing core spec artifact, or requirement with zero coverage that blocks baseline functionality. 62 | - HIGH: Duplicate or conflicting requirement, ambiguous security/performance attribute, untestable acceptance criterion. 63 | - MEDIUM: Terminology drift, missing non-functional task coverage, underspecified edge case. 64 | - LOW: Style/wording improvements, minor redundancy not affecting execution order. 65 | 66 | 6. Produce a Markdown report (no file writes) with sections: 67 | 68 | ### Specification Analysis Report 69 | | ID | Category | Severity | Location(s) | Summary | Recommendation | 70 | |----|----------|----------|-------------|---------|----------------| 71 | | A1 | Duplication | HIGH | spec.md:L120-134 | Two similar requirements ... | Merge phrasing; keep clearer version | 72 | (Add one row per finding; generate stable IDs prefixed by category initial.) 73 | 74 | Additional subsections: 75 | - Coverage Summary Table: 76 | | Requirement Key | Has Task? | Task IDs | Notes | 77 | - Constitution Alignment Issues (if any) 78 | - Unmapped Tasks (if any) 79 | - Metrics: 80 | * Total Requirements 81 | * Total Tasks 82 | * Coverage % (requirements with >=1 task) 83 | * Ambiguity Count 84 | * Duplication Count 85 | * Critical Issues Count 86 | 87 | 7. At end of report, output a concise Next Actions block: 88 | - If CRITICAL issues exist: Recommend resolving before `/implement`. 89 | - If only LOW/MEDIUM: User may proceed, but provide improvement suggestions. 90 | - Provide explicit command suggestions: e.g., "Run /specify with refinement", "Run /plan to adjust architecture", "Manually edit tasks.md to add coverage for 'performance-metrics'". 91 | 92 | 8. Ask the user: "Would you like me to suggest concrete remediation edits for the top N issues?" (Do NOT apply them automatically.) 93 | 94 | Behavior rules: 95 | - NEVER modify files. 96 | - NEVER hallucinate missing sections—if absent, report them. 97 | - KEEP findings deterministic: if rerun without changes, produce consistent IDs and counts. 98 | - LIMIT total findings in the main table to 50; aggregate remainder in a summarized overflow note. 99 | - If zero issues found, emit a success report with coverage statistics and proceed recommendation. 100 | 101 | Context: $ARGUMENTS 102 | -------------------------------------------------------------------------------- /content/pages/290-exception.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: 예외 처리 3 | --- 4 | 5 | JavaScript에는 코드 실행 중에 예기치 못한 에러가 발생했을 때, 이로부터 코드의 실행 흐름을 복구할 수 있는 기능이 내장되어 있습니다. 이런 기능을 일러 **예외 처리(exception handling)**라고 합니다. 6 | 7 | ## 동기식 코드에서의 예외 처리 8 | 9 | JavaScript 코드에서 발생할 수 있는 에러에는 다양한 것들이 있습니다. 문법 에러와 같이 프로그래머의 실수로 인해 에러가 발생하는 경우도 있지만, 네트워크 에러와 같이 코드와는 무관한 이유로 발생하는 에러도 있습니다. 10 | 11 | ```js 12 | new Array(-1) // RangeError: Invalid array length 13 | ``` 14 | 15 | ```js 16 | console.log(foo) // ReferenceError: foo is not defined 17 | ``` 18 | 19 | ```js 20 | fetch('https://nonexistent-domain.nowhere') // TypeError: Failed to fetch 21 | ``` 22 | 23 | 코드 실행 중에 에러가 발생하면, **코드의 실행이 중단되어 그 시점에 실행 중이었던 작업을 완료할 수 없게 됩니다.** JavaScript는 이로부터 **코드의 실행 흐름을 원상복구**할 수 있는 기능을 제공하며, `try...catch...finally` 구문을 사용하면 에러가 나더라도 코드의 실행을 지속할 수 있습니다. 24 | 25 | ```js 26 | try { 27 | console.log('에러가 나기 직전까지의 코드는 잘 실행됩니다.') 28 | new Array(-1) // RangeError: Invalid array length 29 | console.log('에러가 난 이후의 코드는 실행되지 않습니다.') 30 | } catch (e) { 31 | console.log('코드의 실행 흐름이 catch 블록으로 옮겨집니다.') 32 | alert(`다음과 같은 에러가 발생했습니다: ${e.name}: ${e.message}`) 33 | } 34 | ``` 35 | 36 | 에러가 났을 때 원상복구를 시도할 코드를 `try` 블록 내부에 작성하면, 에러가 발생했을 때 코드의 실행 흐름이 `try` 블록에서 `catch` 블록으로 옮겨갑니다. 이 때, `catch` 블록 안에서는 에러에 대한 정보를 담고 있는 객체(위 코드의 `e`)를 사용할 수 있습니다. 만약 `e` 객체를 사용하지 않는다면 아래 예제처럼 생략할 수 있습니다. 37 | 38 | ```js 39 | try { 40 | new Array(-1) // RangeError: Invalid array length 41 | } catch { 42 | alert(`에러가 발생했습니다.`) 43 | } 44 | ``` 45 | 46 | `try` 블록은 예외 처리를 위해서만 쓰이는 것은 아닙니다. `try` 블록 바로 뒤에 `finally` 블록이 오면, `finally` 블록에 있는 코드는 `try` 블록 안에서의 에러 발생 여부와 관계 없이 **무조건 실행됩니다.** 심지어 `try` 블록 내에서 `return`, `break`, `continue` 등으로 인해 **코드의 실행 흐름이 즉시 이동될 때에도 마찬가지**입니다. 47 | 48 | ```js 49 | for (let i of [1, 2, 3]) { 50 | try { 51 | if (i === 3) { 52 | break 53 | } 54 | } finally { 55 | console.log(`현재 i의 값: ${i}`) 56 | } 57 | } 58 | ``` 59 | 60 | `finally` 블록은 `catch` 블록과도 같이 사용됩니다. 이 때 코드의 실행 순서를 정리해 보면 다음과 같습니다. 61 | 62 | - 에러가 안 났을 때: `try` - `finally` 63 | - 에러가 났을 때: `try` - **에러 발생** - `catch` - `finally` 64 | 65 | 아래 코드를 통해 코드의 실행 순서를 시험해보세요. 66 | 67 | ```js 68 | try { 69 | console.log('try') 70 | new Array(-1) // RangeError: Invalid array length 71 | } catch (e) { 72 | console.log('catch') 73 | } finally { 74 | console.log('finally') 75 | } 76 | ``` 77 | 78 | ## 직접 에러 발생시키기 79 | 80 | `Error` 생성자와 `throw` 구문을 사용해서 프로그래머가 직접 에러를 발생시킬 수 있습니다. 81 | 82 | ```js 83 | const even = parseInt(prompt('짝수를 입력하세요')) 84 | if (even % 2 !== 0) { 85 | throw new Error('짝수가 아닙니다.') 86 | } 87 | ``` 88 | 89 | ### 에러 원인 추적하기 (Error Cause) 90 | 91 | ES2022부터는 에러를 생성할 때 **`cause`** 옵션을 사용하여 에러의 원인이 된 다른 에러를 함께 저장할 수 있습니다. 이를 통해 **에러 체이닝(error chaining)**을 구현하여 디버깅을 더 쉽게 만들 수 있습니다. 92 | 93 | ```js 94 | try { 95 | // 데이터베이스 연결 시도 96 | connectToDatabase() 97 | } catch (originalError) { 98 | // 원본 에러를 cause로 포함하여 새로운 에러 발생 99 | throw new Error('데이터베이스 연결 실패', {cause: originalError}) 100 | } 101 | ``` 102 | 103 | `cause` 속성을 통해 원본 에러 정보를 추적할 수 있습니다: 104 | 105 | ```js 106 | try { 107 | try { 108 | throw new Error('원본 에러') 109 | } catch (err) { 110 | throw new Error('상위 레벨 에러', {cause: err}) 111 | } 112 | } catch (err) { 113 | console.log(err.message) // '상위 레벨 에러' 114 | console.log(err.cause.message) // '원본 에러' 115 | } 116 | ``` 117 | 118 | 간혹 프로그램을 작성하면서 **에러의 종류를 구분**해야 하거나 **에러 객체에 기능을 추가**해야 할 필요가 있습니다. 이런 경우에는 **`Error`를 상속받는 클래스**를 만들어서, `throw` 구문에서 이 클래스를 대신 사용할 수 있습니다. 119 | 120 | 예를 들어, 아래 `MyError` 클래스를 통해 에러 객체를 생성할 때 에러에 대한 상세 정보를 포함시키면, `catch` 블록 안에서 원상복구를 위해 이 값을 활용할 수 있습니다. 121 | 122 | ```js 123 | class MyError extends Error { 124 | constructor(value, ...params) { 125 | super(...params) 126 | this.value = value 127 | this.name = 'MyError' 128 | } 129 | } 130 | 131 | try { 132 | const even = parseInt(prompt('짝수를 입력하세요')) 133 | if (even % 2 !== 0) { 134 | throw new MyError(even, '짝수가 아닙니다.') 135 | } 136 | } catch (e) { 137 | if (e instanceof MyError) { 138 | console.log(e.value) 139 | } 140 | } 141 | ``` 142 | 143 | ## 비동기식 코드에서의 예외 처리 144 | 145 | ### 비동기 콜백 146 | 147 | **비동기식으로 작동하는 콜백**의 **내부**에서 발생한 에러는, **콜백 바깥**에 있는 `try` 블록으로는 잡아낼 수 없습니다. 148 | 149 | ```js 150 | try { 151 | setTimeout(() => { 152 | throw new Error('에러!') 153 | }) 154 | } catch (e) { 155 | console.error(e) 156 | } 157 | ``` 158 | 159 | JavaScript 엔진은 에러가 발생하는 순간 **호출 스택을 되감는 과정**을 거칩니다. **이 과정 중에 `try` 블록을 만나야** 코드의 실행 흐름을 원상복구시킬 수 있습니다. 위 예제에서 `setTimeout`에 넘겨진 콜백에서 에러가 발생하면, 호출 스택을 따라 올라가도 `try` 블록을 만나는 것이 아니므로, 코드의 실행 흐름이 `catch` 블록으로 옮겨지지 않는 것입니다. 160 | 161 | 따라서, 위 예제의 `try` 블록을 비동기 콜백 내부에 작성해주어야 합니다. 162 | 163 | ```js 164 | setTimeout(() => { 165 | try { 166 | throw new Error('에러!') 167 | } catch (e) { 168 | console.error(e) 169 | } 170 | }) 171 | ``` 172 | 173 | ### Promise 174 | 175 | Promise 객체는 세 가지 상태를 가질 수 있습니다. 176 | 177 | - pending - Promise 객체에 결과값이 채워지지 않은 상태 178 | - fulfilled - Promise 객체에 결과값이 채워진 상태 179 | - **rejected - Promise 객체에 결과값을 채우려고 시도하다가 에러가 난 상태** 180 | 181 | Promise 객체가 rejected 상태가 되면, 이 Promise에 대해서는 `then` 메소드에 첫 번째 인수로 넘겨준 콜백이 실행되지 않고, **두 번째 인수로 넘겨준 콜백**이 대신 실행됩니다. 그리고 이 콜백에는 **에러 객체가 첫 번째 인수**로 주어집니다. 182 | 183 | ```js 184 | const p = new Promise((resolve) => { 185 | const even = parseInt(prompt('짝수를 입력하세요')) 186 | if (even % 2 !== 0) { 187 | throw new Error('짝수가 아닙니다.') 188 | } else { 189 | resolve(even) 190 | } 191 | }) 192 | 193 | p.then( 194 | (even) => { 195 | return '짝수입니다.' 196 | }, 197 | (e) => { 198 | return e.message 199 | }, 200 | ).then(alert) 201 | ``` 202 | 203 | 혹은, **`catch` 메소드**를 통해 에러 처리 콜백을 지정해줄 수도 있습니다. 204 | 205 | ```js 206 | p.then((even) => { 207 | return '짝수입니다.' 208 | }) 209 | .catch((e) => { 210 | return e.message 211 | }) 212 | .then(alert) 213 | ``` 214 | 215 | 만약, `then` 메소드의 연쇄 안에서 에러가 발생하면, `try...catch` 구문과 유사하게 **처음 만나는 에러 처리 콜백으로 코드의 실행 흐름이 건너뛰는 결과**를 낳게 됩니다. 216 | 217 | ```js 218 | Promise.resolve() 219 | .then(() => { 220 | throw new Error('catch 메소드를 통해 예외 처리를 할 수 있습니다.') 221 | }) 222 | .then(() => { 223 | console.log('이 코드는 실행되지 않습니다.') 224 | }) 225 | .catch((e) => { 226 | return e.message 227 | }) 228 | .then(console.log) 229 | ``` 230 | 231 | ### 비동기 함수 232 | 233 | 앞에서 봤던 Promise 객체의 예외 처리 방식은, 일반적인 동기식 예외 처리 방식과 다르게 **콜백**을 사용하고 있어서 코드를 복잡하게 만드는 원인이 됩니다. 234 | 235 | 비동기 함수 내부에서는, **rejected 상태가 된 Promise 객체**를 동기식 예외 처리 방식과 동일하게 **`try...catch...finally` 구문으로 처리할 수 있습니다.** 236 | 237 | ```js 238 | async function func() { 239 | try { 240 | const res = await fetch('https://nonexistent-domain.nowhere') 241 | } catch (e) { 242 | console.log(e.message) 243 | } 244 | } 245 | 246 | func() // 출력 결과: Failed to fetch 247 | ``` 248 | 249 | 단, Promise 객체에 대해 **`await` 구문**을 사용하지 않는 경우, 에러가 발생해도 `catch` 블록으로 코드의 실행 흐름이 이동하지 않는다는 사실을 기억하세요. 250 | 251 | ```js 252 | async function func() { 253 | try { 254 | fetch('https://nonexistent-domain.nowhere') 255 | } catch (e) { 256 | console.log(e.message) 257 | } 258 | } 259 | 260 | func() // 아무것도 출력되지 않습니다. 261 | ``` 262 | -------------------------------------------------------------------------------- /tests/check-html-links.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * HTML Link Checker 5 | * 6 | * Checks the built HTML artifacts for broken links by: 7 | * 1. Internal links - verifying file existence 8 | * 2. External links - checking HTTP status codes (not 4xx) 9 | */ 10 | 11 | const fs = require('fs'); 12 | const path = require('path'); 13 | const https = require('https'); 14 | const http = require('http'); 15 | 16 | const PUBLIC_DIR = path.join(__dirname, '../public'); 17 | const errors = []; 18 | const warnings = []; 19 | const checkedUrls = new Map(); // Cache for external URL checks 20 | 21 | // Get all HTML files 22 | function getAllHtmlFiles(dir) { 23 | const files = []; 24 | 25 | if (!fs.existsSync(dir)) { 26 | console.error(`❌ Directory ${dir} does not exist. Run 'yarn build' first.`); 27 | process.exit(1); 28 | } 29 | 30 | const items = fs.readdirSync(dir); 31 | 32 | for (const item of items) { 33 | const fullPath = path.join(dir, item); 34 | const stat = fs.statSync(fullPath); 35 | 36 | if (stat.isDirectory()) { 37 | files.push(...getAllHtmlFiles(fullPath)); 38 | } else if (item.endsWith('.html')) { 39 | files.push(fullPath); 40 | } 41 | } 42 | 43 | return files; 44 | } 45 | 46 | // Extract all links from HTML 47 | function extractLinks(content) { 48 | // Match href="..." attributes 49 | const linkRegex = /href="([^"]+)"/g; 50 | const links = []; 51 | let match; 52 | 53 | while ((match = linkRegex.exec(content)) !== null) { 54 | const url = match[1]; 55 | // Skip anchors and special protocols 56 | if (url && url !== '#' && !url.startsWith('mailto:') && !url.startsWith('tel:')) { 57 | links.push(url); 58 | } 59 | } 60 | 61 | return [...new Set(links)]; // Remove duplicates 62 | } 63 | 64 | // Check if URL is external 65 | function isExternalLink(url) { 66 | return url.startsWith('http://') || url.startsWith('https://'); 67 | } 68 | 69 | // Check external URL with HTTP request 70 | function checkExternalUrl(url) { 71 | return new Promise((resolve) => { 72 | // Check cache first 73 | if (checkedUrls.has(url)) { 74 | resolve(checkedUrls.get(url)); 75 | return; 76 | } 77 | 78 | const client = url.startsWith('https://') ? https : http; 79 | 80 | const options = { 81 | method: 'HEAD', 82 | timeout: 10000, 83 | headers: { 84 | 'User-Agent': 'Mozilla/5.0 (compatible; LinkChecker/1.0)' 85 | } 86 | }; 87 | 88 | const req = client.request(url, options, (res) => { 89 | const result = { 90 | statusCode: res.statusCode, 91 | ok: res.statusCode !== 404 // Only fail on 404, allow other status codes 92 | }; 93 | checkedUrls.set(url, result); 94 | resolve(result); 95 | }); 96 | 97 | req.on('error', (err) => { 98 | const result = { 99 | statusCode: null, 100 | error: err.message, 101 | ok: true // Network errors are acceptable (timeouts, connection refused, etc.) 102 | }; 103 | checkedUrls.set(url, result); 104 | resolve(result); 105 | }); 106 | 107 | req.on('timeout', () => { 108 | req.destroy(); 109 | const result = { 110 | statusCode: null, 111 | error: 'Timeout', 112 | ok: true // Timeouts are acceptable 113 | }; 114 | checkedUrls.set(url, result); 115 | resolve(result); 116 | }); 117 | 118 | req.end(); 119 | }); 120 | } 121 | 122 | // Resolve internal URL to file path 123 | function resolveInternalUrl(url, fromFile) { 124 | // Remove query strings and anchors 125 | const cleanUrl = url.split('?')[0].split('#')[0]; 126 | 127 | if (!cleanUrl || cleanUrl === '/') { 128 | return path.join(PUBLIC_DIR, 'index.html'); 129 | } 130 | 131 | // Handle absolute paths 132 | if (cleanUrl.startsWith('/')) { 133 | const filePath = path.join(PUBLIC_DIR, cleanUrl); 134 | 135 | // If it's a directory, check for index.html 136 | if (fs.existsSync(filePath) && fs.statSync(filePath).isDirectory()) { 137 | return path.join(filePath, 'index.html'); 138 | } 139 | 140 | // If no extension, try adding .html 141 | if (!path.extname(filePath)) { 142 | return filePath + '.html'; 143 | } 144 | 145 | return filePath; 146 | } 147 | 148 | // Handle relative paths 149 | const dir = path.dirname(fromFile); 150 | const resolvedPath = path.resolve(dir, cleanUrl); 151 | 152 | // If no extension, try adding .html 153 | if (!path.extname(resolvedPath)) { 154 | return resolvedPath + '.html'; 155 | } 156 | 157 | return resolvedPath; 158 | } 159 | 160 | // Check a single HTML file 161 | async function checkFile(filePath) { 162 | const content = fs.readFileSync(filePath, 'utf-8'); 163 | const links = extractLinks(content); 164 | const relPath = path.relative(PUBLIC_DIR, filePath); 165 | 166 | for (const link of links) { 167 | if (isExternalLink(link)) { 168 | // Check external link 169 | const result = await checkExternalUrl(link); 170 | 171 | if (!result.ok) { 172 | errors.push({ 173 | file: relPath, 174 | issue: result.error ? `Failed to fetch: ${result.error}` : `HTTP ${result.statusCode}`, 175 | link: link, 176 | type: 'external' 177 | }); 178 | } 179 | } else { 180 | // Check internal link 181 | const targetPath = resolveInternalUrl(link, filePath); 182 | 183 | if (!fs.existsSync(targetPath)) { 184 | errors.push({ 185 | file: relPath, 186 | issue: 'File does not exist', 187 | link: link, 188 | resolvedTo: path.relative(PUBLIC_DIR, targetPath), 189 | type: 'internal' 190 | }); 191 | } 192 | } 193 | } 194 | } 195 | 196 | // Main execution 197 | async function main() { 198 | console.log('🔍 Checking links in built HTML files...\n'); 199 | 200 | const htmlFiles = getAllHtmlFiles(PUBLIC_DIR); 201 | console.log(`Found ${htmlFiles.length} HTML files to check\n`); 202 | 203 | for (const file of htmlFiles) { 204 | process.stdout.write(`Checking ${path.relative(PUBLIC_DIR, file)}...\r`); 205 | await checkFile(file); 206 | } 207 | 208 | console.log('\n'); 209 | 210 | // Report results 211 | if (errors.length === 0) { 212 | console.log('✅ All links are valid!\n'); 213 | console.log(` Checked ${checkedUrls.size} unique external URLs`); 214 | console.log(` Checked ${htmlFiles.length} HTML files\n`); 215 | process.exit(0); 216 | } 217 | 218 | // Separate internal and external errors 219 | const internalErrors = errors.filter(e => e.type === 'internal'); 220 | const externalErrors = errors.filter(e => e.type === 'external'); 221 | 222 | if (internalErrors.length > 0) { 223 | console.log(`❌ Found ${internalErrors.length} broken internal link(s):\n`); 224 | internalErrors.forEach((err, i) => { 225 | console.log(`${i + 1}. ${err.file}`); 226 | console.log(` Link: ${err.link}`); 227 | console.log(` Issue: ${err.issue}`); 228 | if (err.resolvedTo) { 229 | console.log(` Resolved to: ${err.resolvedTo}`); 230 | } 231 | console.log(''); 232 | }); 233 | } 234 | 235 | if (externalErrors.length > 0) { 236 | console.log(`❌ Found ${externalErrors.length} broken external link(s):\n`); 237 | externalErrors.forEach((err, i) => { 238 | console.log(`${i + 1}. ${err.file}`); 239 | console.log(` Link: ${err.link}`); 240 | console.log(` Issue: ${err.issue}`); 241 | console.log(''); 242 | }); 243 | } 244 | 245 | console.log(`Total errors: ${errors.length}`); 246 | console.log(`Checked ${checkedUrls.size} unique external URLs\n`); 247 | 248 | process.exit(1); 249 | } 250 | 251 | main().catch(err => { 252 | console.error('Unexpected error:', err); 253 | process.exit(1); 254 | }); 255 | -------------------------------------------------------------------------------- /content/pages/170-function.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: 함수 3 | --- 4 | 5 | 프로그래밍에서의 함수란, **큰 프로그램을 잘게 쪼개어 특정 코드 뭉치를 반복해서 사용할 수 있도록 묶어놓은 코드 뭉치의 단위**를 말합니다. 함수를 어떻게 작성하느냐에 따라서 코드의 유지보수성과 가독성이 크게 달라집니다. 또 JavaScript의 함수는 굉장히 많은 기능을 갖고 있는데, 사실 함수의 성질을 모두 이해하면 프로그래밍 언어로서의 JavaScript를 전부 이해한거나 마찬가지라고 할 수 있을 정도입니다. 본 챕터에서는 함수의 기본적인 성질만을 다루고, 이어지는 챕터들에서 나머지 내용을 차근차근 다룰 것입니다. 6 | 7 | ## 함수의 구성 요소 8 | 9 | 두 값을 더하는 아주 간단한 함수를 정의해 보겠습니다. 10 | 11 | ```js 12 | function add(x, y) { 13 | const result = x + y 14 | return result 15 | } 16 | ``` 17 | 18 | 위에서 `add`라는 **이름**을 갖는 함수를 정의했습니다. 괄호 안의 `x`와 `y`를 **매개변수(parameter)**라 하며, `return` 뒤에 오는 값을 **반환값(return value)**이라고 합니다. 19 | 20 | 함수를 정의했다면, 아래와 같이 함수 이름 뒤에 괄호를 붙여서 이 함수를 실행시킬 수 있습니다. 이를 함수의 **호출(function call)**이라고 합니다. 21 | 22 | ```js 23 | add(2, 3) // 5 24 | ``` 25 | 26 | 여기서 괄호 안에 넘겨준 `2`, `3`을 **인수(argument)**라고 부릅니다. 27 | 28 | ### 실행 순서 29 | 30 | JavaScript는 기본적으로는 쓰여진 순서대로 실행되지만, 여러 가지 예외가 존재해서 코드의 실행 순서가 이리저리 옮겨다니기도 합니다. 함수 호출도 그 중 하나로, 함수 호출 코드를 만나면 코드의 실행 흐름이 호출된 함수의 내부로 옮겨갑니다. 함수가 값을 반환하면 다시 이전 위치부터 코드의 실행이 재개됩니다. 31 | 32 | ```js 33 | // 1 - 함수 정의 34 | function add(x, y) { 35 | return x + y // 3 - 함수 실행 36 | } 37 | // 2 - 함수 호출 38 | const result = add(2, 3) 39 | // 4 - 나머지 코드 실행 40 | console.log(result) 41 | ``` 42 | 43 | 여기서 유의할 점은, 함수를 정의하는 것만으로는 함수 내부에 있는 코드가 실행되지 않는다는 것입니다. 함수 내부의 코드를 실행하려면, 반드시 함수를 호출해주어야 합니다. 44 | 45 | ### 매개변수와 인수 46 | 47 | 위 코드의 `x`와 `y`를 가지고 **매개변수**라고 합니다. 매개변수는 변수의 일종으로, 함수 호출 시마다 인수가 매개변수에 대입됩니다. 위의 코드에서 `add(2, 3)`과 같이 호출하면 매개변수 `x`에는 `2`가, `y`에는 `3`이 대입된 채로 나머지 코드가 실행됩니다. 48 | 49 | 여기서 주의할 점은 매개변수는 바깥에서 선언된 변수와는 관계없는 독립적인 변수라는 것입니다. 예를 들어, 함수 호출 시 인수가 들어갈 자리에 변수를 써주고, 함수 내부에서 매개변수에 새로운 값을 대입한다고 하더라도 인수로 써준 변수의 값이 변경되지 않습니다. 50 | 51 | ```js 52 | function reassign(x) { 53 | x = 2 54 | return x 55 | } 56 | 57 | const y = 1 58 | const result = reassign(y) 59 | 60 | console.log(y) // 1 61 | console.log(result) // 2 62 | ``` 63 | 64 | 매개변수는 `let`으로 선언한 변수와 비슷하게 동작하지만 미묘하게 다른 점이 있습니다. 이에 대해서는 [값 더 알아보기](./220-value-in-depth)에서 자세히 다룹니다. 65 | 66 | ### 반환값 67 | 68 | `return` 구문은 함수의 반환값을 결정합니다. `return` 키워드 바로 다음에 오는 값이 함수 호출의 결과값으로 반환되며, 반환되는 즉시 함수 실행이 끝납니다. 69 | 70 | ```js 71 | function add(x, y) { 72 | return x + y 73 | console.log('이 부분은 실행되지 않습니다.') 74 | } 75 | add(1, 2) // 3 76 | // 3 외에 따로 출력되는 내용이 없습니다. 77 | ``` 78 | 79 | 아래와 같이 `return` 뒤에 아무 값도 써주지 않거나, 아예 `return` 구문을 쓰지 않으면 함수는 `undefined`를 반환합니다. 80 | 81 | ```js 82 | function returnUndefined() { 83 | return 84 | } 85 | function noReturn() {} 86 | returnUndefined() // undefined 87 | noReturn() // undefined 88 | ``` 89 | 90 | ## 스코프 (Scope) 91 | 92 | 함수의 매개변수를 비롯한, 모든 변수들은 특별한 성질을 갖습니다. 93 | 94 | ```js 95 | function add(x, y) { 96 | // 변수 `x`와 `y`가 정의됨 97 | return x + y 98 | } 99 | add(2, 3) 100 | console.log(x) // 에러! 101 | ``` 102 | 103 | 이렇게, 매개변수와 같이 함수 안에서 정의된 변수는 함수 바깥에서는 접근할 수 없기 때문에 함수 안에서만 사용할 수 있습니다. 즉, **변수는 코드의 일정 범위 안에서만 유효하다**는 성질이 있는 것입니다. 이렇게, 특정 변수가 유효한 코드 상의 **유효 범위**를 가지고 **스코프(scope)**라고 합니다. 104 | 105 | 위 예제에서의 `x`와 `y`는 함수 `add`의 내부 코드 안에서만 접근할 수 있습니다. 즉, 매개변수는 **함수 스코프**를 갖습니다. 106 | 107 | 스코프의 성질이 미묘해서, 이를 잘 이해하지 못하면 코드를 작성하거나 읽기 어려울 수 있습니다. 아래에서 스코프의 몇 가지 성질들을 살펴보겠습니다. 108 | 109 | ### 스코프 연쇄 (Scope Chain) 110 | 111 | 함수 내부 코드에서, 매개변수 혹은 그 함수 안에서 정의된 변수만 사용할 수 있는 것은 아닙니다. 112 | 113 | ```js 114 | const five = 5 115 | function add5(x) { 116 | return x + five // 바깥 스코프의 `five` 변수에 접근 117 | } 118 | add5(3) // 8 119 | ``` 120 | 121 | `add5` 함수의 `return` 구문에서 함수 바깥에 있는 변수 `five`의 값을 가져와 사용했습니다. 이는 심지어 함수가 여러 겹 중첩(nested)되어 있다고 하더라도 가능합니다. 122 | 123 | ```js 124 | const five = 5 125 | function add5(x) { 126 | function add(y) { 127 | return x + y 128 | } 129 | return add(five) 130 | } 131 | add5(3) // 8 132 | ``` 133 | 134 | 코드의 실행 흐름이 식별자에 도달하면, 먼저 그 식별자와 같은 이름을 갖는 변수를 현재 스코프에서 찾아보고, 변수가 존재하면 그것을 그대로 사용합니다. 만약 현재 스코프에서 변수를 찾지 못하면 바로 바깥쪽 스코프에서 변수를 찾아봅니다. 있으면 사용하고 없으면 바깥쪽 스코프로 올라가서 다시 찾아보는, 이 과정이 되풀이됩니다. 이런 과정을 **스코프 연쇄(scope chain)**라 하고, 이 과정은 가장 바깥쪽에 있는 스코프를 만날 때까지 반복됩니다. 가장 바깥쪽 스코프까지 찾아봤는데도 같은 이름의 변수를 찾지 못하면, 그제서야 에러가 발생됩니다. 135 | 136 | 가장 바깥에 있는 스코프를 최상위 스코프(top-level scope) 혹은 **전역 스코프(global scope)**라고 부릅니다. 위 코드에서 `five`가 정의된 스코프가 바로 전역 스코프입니다.[^1] 137 | 138 | ### 변수 가리기 (Variable Shadowing) 139 | 140 | 단일 스코프에서는 같은 이름을 갖는 서로 다른 변수가 존재할 수 없습니다. 하지만 스코프 연쇄가 일어나면 이야기가 달라집니다. 아래의 코드에서는 `x`라는 이름을 갖는 변수가 세 번 정의되었습니다. 141 | 142 | ```js 143 | const x = 3 144 | 145 | function add5(x) { 146 | // `x`라는 변수가 다시 정의됨 147 | function add(x, y) { 148 | // `x`라는 변수가 다시 정의됨 149 | return x + y 150 | } 151 | return add(x, 5) 152 | } 153 | 154 | add5(x) 155 | ``` 156 | 157 | 위와 같이, 바깥쪽 스코프에 존재하는 변수와 같은 이름을 갖는 변수를 안쪽 스코프에서 재정의할 수 있습니다. 그렇게 되면 안쪽 스코프에서는 바깥쪽 스코프에 있는 이름이 **무시**됩니다. 이런 현상을 **변수 가리기(variable shadowing)**라고 합니다. 158 | 159 | ### 어휘적 스코핑 (Lexical Scoping) 160 | 161 | 스코프는 **코드가 작성된 구조**에 의해서 결정되는 것이지, **함수 호출의 형태**에 의해 결정되는 것이 아닙니다. 예를 들어 봅시다. 162 | 163 | ```js 164 | function add5(x) { 165 | const five = 5 166 | return add(x) 167 | } 168 | 169 | add5(3) // 8 170 | 171 | function add(x) { 172 | return five + x // ReferenceError: five is not defined 173 | } 174 | ``` 175 | 176 | `add`라는 함수가 `add5`라는 함수 안에서 **호출**되었다고 해서, `add` 함수 내부에서 `add5` 함수의 스코프 안에 있는 변수에 접근할 수 있는 것은 아닙니다. 스코프는 코드가 **작성**된 구조에 의해 결정되는 성질입니다. 위 코드를 동작시키려면, 아래와 같이 작성해야 합니다. 177 | 178 | ```js 179 | function add5(x) { 180 | const five = 5 181 | function add(x) { 182 | return five + x 183 | } 184 | return add(x) 185 | } 186 | ``` 187 | 188 | ### 스코프의 종류 189 | 190 | 이 챕터에서는 매개변수에 대한 함수 스코프를 중점적으로 다루었는데, 사실 스코프의 종류가 더 있습니다. 특히, `let`과 `const`로 선언된 변수는 함수 스코프가 아니라 조금 다른 종류의 스코프를 가집니다. 이에 대해서는 [값 더 알아보기](./220-value-in-depth)에서 자세히 다룹니다. 191 | 192 | ## 값으로서의 함수 193 | 194 | JavaScript에서는 함수도 값입니다! 195 | 196 | ```js 197 | function add(x, y) { 198 | return x + y 199 | } 200 | 201 | const plus = add 202 | plus(1, 2) // 3 203 | ``` 204 | 205 | 다른 값과 마찬가지로, 함수를 선언한 뒤 변수에 대입해서 호출할 수도 있고, 혹은 배열이나 객체에 넣을 수도 있고, 심지어는 함수를 다른 함수에 인수로 넘기거나, 함수에서 함수를 반환할 수도 있습니다. 206 | 207 | ```js 208 | // 함수를 배열이나 객체에 넣기 209 | function add(x, y) { 210 | return x + y 211 | } 212 | ;[add] 213 | { 214 | addFunc: add 215 | } 216 | 217 | // 함수를 인수로 넘기기 218 | function isEven(x) { 219 | return x % 2 === 0 220 | } 221 | ;[1, 2, 3, 4, 5].filter(isEven) // [2, 4] 222 | 223 | // 함수에서 함수 반환하기 224 | function createEmptyFunc() { 225 | function func() {} 226 | return func 227 | } 228 | ``` 229 | 230 | 컴퓨터 과학 분야에서 사용되는 용어 중에 [1급 시민(First-Class Citizen)](https://ko.wikipedia.org/wiki/%EC%9D%BC%EA%B8%89_%EA%B0%9D%EC%B2%B4)이라는 특이한 용어가 있습니다. 값으로 사용할 수 있는 JavaScript의 함수는 1급 시민입니다. 1급 시민인 함수를 줄여서 **1급 함수**라 부르기도 합니다. 231 | 232 | JavaScript에서는 1급 함수의 성질을 여러 가지 흥미로운 방식으로 활용할 수 있습니다. 이에 대한 자세한 내용은 [함수형 프로그래밍](./255-fp)에서 다룹니다. 233 | 234 | ## 익명 함수 (Anonymous Function) 235 | 236 | JavaScript에서 함수를 선언할 때 꼭 **이름**을 붙여주어야 하는 것은 아닙니다. 아래와 같이 이름을 붙이지 않은 함수를 가지고 **익명 함수(anonymous function)**, 혹은 함수 리터럴(function literal)이라고 합니다. 237 | 238 | ```js 239 | // 두 수를 더해서 반환하는 익명 함수 240 | function(x, y) { 241 | return x + y; 242 | } 243 | // 위의 익명 함수는 이름이 없어서 이름을 가지고 호출을 할 수 없습니다. 244 | 245 | // 호출을 하려면 변수에 저장한 후에 변수의 이름을 통해 호출해야 합니다. 246 | const add = function(x, y) { 247 | return x + y; 248 | } 249 | add(1, 2); // 3 250 | ``` 251 | 252 | 익명 함수는 **함수를 만든 쪽이 아니라 다른 쪽에서 그 함수를 호출할 때** 많이 사용됩니다. 대표적인 경우는 **함수를 인수로 넘겨줄 때**입니다. 예를 들어, 배열의 `filter` 메소드에 필터링할 조건을 표현하는 함수를 넘겨주면, `filter` 메소드쪽에서 배열 각 요소에 대해 함수를 호출한 뒤, `true`를 반환한 요소만을 필터링해서 반환합니다. 253 | 254 | ```js 255 | ;[1, 2, 3, 4, 5].filter(function (x) { 256 | return x % 2 === 0 257 | }) // [2, 4] 258 | ``` 259 | 260 | ## 화살표 함수 (Arrow Function) 261 | 262 | 함수 정의를 위한 새로운 표기법인 화살표 함수(arrow function)은 ES2015에서 도입되었습니다. 263 | 264 | ```js 265 | // 여기에서 x + y 는 **바로 반환됩니다.** 266 | const add = (x, y) => x + y 267 | ``` 268 | 269 | ```js 270 | // 바로 반환시키지 않고 function 키워드를 통한 함수 정의처럼 여러 구문을 사용하려면 curly braces({...}) 로 둘러싸주어야 합니다. 271 | // `=>` 다음 부분을 중괄호로 둘러싸면, 명시적으로 `return` 하지 않는 한 아무것도 반환되지 않습니다. 272 | const add = (x, y) => { 273 | const result = x + y 274 | return result 275 | } 276 | ``` 277 | 278 | ```js 279 | // 매개변수가 하나밖에 없다면, 매개변수 부분의 괄호를 쓰지 않아도 무방합니다. 280 | const negate = (x) => !x 281 | ``` 282 | 283 | 화살표 함수는 표기법이 간단하기 때문에 익명 함수를 다른 함수의 인수로 넘길 때 주로 사용됩니다. 284 | 285 | ```js 286 | ;[1, 2, 3, 4, 5].filter((x) => x % 2 === 0) 287 | ``` 288 | 289 | 일반적인 함수와 화살표 함수는 표기법에서만 다른 것이 아니고, 몇 가지 미묘한 차이점이 있습니다. 이에 대해서는 [함수 더 알아보기](./230-function-in-depth) 챕터에서 자세히 다룹니다. 290 | 291 | [^1]: 다만, JavaScript 파일의 가장 바깥쪽에서 선언된 변수가 항상 전역 스코프를 갖는 것은 아닙니다. 어떤 스코프를 가질 지는 JavaScript 파일이 사용되는 방식에 따라 달라지는데, 이에 대해서는 [모듈]() 챕터에서 자세히 다룹니다. 292 | --------------------------------------------------------------------------------