- Rethinking the JavaScript ternary operator1
- Break a forEach Loop with JavaScript2
- You Can Label a JavaScript
if
Statement | CSS-Tricks3 - Refactoring optional chaining into a large codebase: lessons learned – Lea Verou4
- [HTML5] 꼼꼼히 살펴보는 SCRIPT 엘리먼트 - 코드쓰는사람5
- Single Page Applications using Rust6
- Fast and maintainable patterns for fetching from a database – Sophie Alpert7
- Deep-copying in JavaScript using structuredClone8
- Holistic Review of TC39 “Dataflow” Proposals — Tab Completion9
Footnotes
-
삼항연산자(ternary)의 어두운면과 우려를 나타내는 글 ↩
-
forEach
루프break
하는 트릭. 참조하는 배열의 length값을 0으로. ↩ -
조건문에서
label
을 지정해서 해당 블록으로break
가 가능하다는걸 보여주는 내용. 실제 적용할만한 사례는 거의 없고 글쓴이는 while+switch문에서 break를 좀 더 효율적으로 사용한 예를 보여주고 있다. ↩ -
optional chaining 문법으로 리팩토링 하면서…변경 가능한 부분(삼항연산자, 배열, 기능탐지)과 주의사항(값 할당, 잘못된 위치에 표기), 조심할 부분(null/undefined, 연산 순서와 순위, return 항상 호출됨)으로 구분해서 정리한 글. 커밋을 보면 실제로 어떻게 작업했는지 확인도 가능하다. ↩
-
<script />
태그와 속성값에 대한 정리 ↩ -
rust로 작성하고 wasm으로 컴파일해서 클라이언트 프로그램을 작성하는 방법을 자세하게 설명하고 있다. rust에 관심이 있거나 하다면 볼만한 글. ↩
-
종속성 구조를 파악해서 최적의 병렬화로 빠르게 데이터 가져오기 ↩
-
structuredClone()
↩ -
pipe, flow 제안(초안)사항을 검토하는 관점에서 보는 시각 ↩
- Introduction - Learn RxJS
- RxJS and Reactive Programming - Animations and visual lessons
- RxViz - Animated playground for Rx Observables
- RxMarbles: Interactive diagrams of Rx Observables
- A simple explanation of functional pipe in JavaScript - DEV Community1
- [Korean ver.] The introduction to Reactive Programming you’ve been missing · GitHub
- Streams for reactive programming — surma.dev2
Footnotes
- Git Pathspecs and How to Use Them | CSS-Tricks1
- 🌳🚀 CS Visualized: Useful Git Commands - DEV Community2
- 좋은 git commit 메시지를 위한 영어 사전
- How to Write a Git Commit Message
- Write Better Commits, Build Better Projects - The GitHub Blog
- Creating The Perfect Commit In Git | CSS-Tricks
- A Guide To Undoing Mistakes With Git (Part 1) — Smashing Magazine
- A Guide To Undoing Mistakes With Git (Part 2) — Smashing Magazine
- CMD+Z for Git is Here | CSS-Tricks
Footnotes
최신 CSS 기능 및 트렌드
- Things You Can Do With CSS Today — Smashing Magazine[^106-1]
- The Future of CSS: Cascade Layers (CSS @layer)
- The Undeniable Utility Of CSS :has • Josh W. Comeau
- An Interactive Guide to CSS Container Queries
- 컨테이너 쿼리 사용 방법 | web.dev
- What if you used Container Units for everything?
레이아웃과 반응형 디자인
- Fluid Sizing Instead Of Multiple Media Queries? — Smashing Magazine
- Complex conditional width using flex-basis with clamp
- Using grid to split a table cell
- TablesNG — Improvements to
<table>
rendering in Chromium
CSS 애니메이션 및 효과
- A CSS-only, animated, wrapping underline.[^106-2]
- Cubic Bézier: from math to motion
- Zero Trickery Custom Radios and Checkboxes - CSS-Tricks
- CSS One-Liners to Improve (Almost) Every Project
- 6 CSS Snippets Every Front-End Developer Should Know In 2025
텍스트 및 타이포그래피
스타일 가이드 및 접근성
- Naming Variables In CSS
- Margin considered harmful
- Defensive CSS - Ahmad Shadeed
- The wasted potential of CSS attribute selectors
CSS와 JavaScript의 조합
- Constructable Stylesheets: seamless reusable styles
- Replace JavaScript Dialogs With New HTML Dialog | CSS-Tricks
- How to prevent scrolling the page on iOS Safari 15
CSS 아키텍처 및 모듈화
background
인라인 요소에 bold
스타일이 적용될 경우 레이아웃 시프팅 현상이 발생하기 때문에 해당 이슈를 해결하는 방법들.
- html - Inline elements shifting when made bold on hover - Stack Overflow1
- Bold on Hover… Without the Layout Shift | CSS-Tricks2
Footnotes
- Temporal documentation1
- Is It Time for the JavaScript Temporal API?2
- JS Dates Are About to Be Fixed | TimeTime
- Using Intl.RelativeTimeFormat for Localized Relative Timings
- Everything You Need to Know About Date in JavaScript | CSS-Tricks3
- 자바스크립트에서 타임존 다루기 (1) : NHN Cloud Meetup4
- 자바스크립트에서 타임존 다루기 (2) : NHN Cloud Meetup
Footnotes
# 마지막 커밋 해시
git log --pretty=format:'%h' -n 1
# 유저정보
git config user.name
git config user.email
git checkout -- ./index.js
// 마지막 커밋 시의 상태로 되돌립니다.
git reset --hard
리모트 브랜치 가져오기
git remote update
Footnotes
-
명령어로 discard를 하려면 ↩
Footnotes
-
z-index
값을 체계적으로 관리하기 위한 방법(변수로 설정해서 상대적으로 값을 설정)을 제시하고 있다. ↩
offset-path
값을 지정해서 keyframes
로 애니메이션을 적용하는 방법과 예제들
Footnotes
-
element가 따라갈 모션 경로를 지정하고 상위 컨테이너 또는 SVG 좌표 시스템 내에서 요소의 위치를 정의 ↩
Footnotes
-
미디어쿼리로
hover
기능이 탐지하는 방법을 소개하고 있다. ↩
- Your Image Is Probably Not Decorative — Smashing Magazine1
- JPG vs JPEG: Understanding the Most Common Image Format
- Using Modern Image Formats: AVIF And WebP — Smashing Magazine2
- Building an effective Image Component3
- Using Performant Next-Gen Images in CSS with image-set | CSS-Tricks4
- A Guide to the Responsive Images Syntax in HTML | CSS-Tricks
- Frequently Asked Questions | WebP | Google Developers5
Footnotes
- hbi99/defiant.js1
- jsonata-js/jsonata2
- bvaughn/js-search3
- sanity-io/GROQ45
- mistql6
- lunr.js
- What is Fuse.js? | Fuse.js
Footnotes
-
Defiant는 XPath 표현식을 사용하여 빠른 검색을 가능하게 하는 방법으로 전역 JSON 개체를 확장합니다. ↩
-
JSONata는 JSON 데이터를 위한 경량 쿼리 및 변환 언어입니다. XPath 3.1의 ‘위치 경로’ 의미에서 영감을 받아 정교한 쿼리를 간결하고 직관적인 표기법으로 표현할 수 있습니다. ↩
-
JS Search는 JavaScript 및 JSON 객체를 위한 효율적인 클라이언트 측 검색 라이브러리입니다. ↩
-
GROQ는 Sanity의 오픈소스 쿼리 언어입니다. 배우기 쉬운 강력하고 직관적인 언어입니다. GROQ를 사용하면 애플리케이션에 필요한 정보를 정확히 설명하고, 여러 문서 세트의 정보를 결합하고, 필요한 필드만으로 매우 구체적인 응답을 결합할 수 있습니다. ↩
-
Query JSON documents in the Terminal with GROQ | CSS-Tricks ↩
-
JSON과 유사한 구조에서 계산을 수행하기 위한 소형 내장형 언어 ↩
- Beating JSON performance with Protobuf1
- GitHub - protobufjs/protobuf.js: Protocol Buffers for JavaScript (& TypeScript).
- GitHub - grpc/grpc-web: gRPC for Web Clients
- gRPC: 내부
- 4. gRPC: Under the Hood - gRPC: Up and Running [Book]
- pbtk - A toolset for reverse engineering and fuzzing Protobuf-based apps
- Working with Protobufs in Node.js | www.thecodebarbarian.com
- What is “Content-Type: application/x-protobuf”: Protobuf Explained For Hackers – TechKranti
Footnotes
-
구조화된 데이터의 직렬화 및 역직렬화를 허용하기 위해 Google에서 개발한 프로토콜입니다. Google은 시스템이 통신할 수 있도록 XML에 비해 더 나은 방법을 제공하는 것을 목표로 개발했습니다. 그래서 그들은 XML보다 더 간단하고, 더 작고, 더 빠르고, 유지 관리하기 쉽게 만드는 데 초점을 맞췄습니다. ↩
🏗 TypeScript 관련 자료 정리
기초 및 개념 이해
- Understanding TypeScript Generics — Smashing Magazine
- Why Unknown Types Are Useful • Michael Uloth
- How TypeScript Conditional Types Work
- A different way to think about TypeScript
React와 TypeScript
- How to write a React Component in TypeScript
- How to Simplify Component Imports with TypeScript Namespaces
타입 시스템 심화 및 패턴
- Implementing a type-safe object builder in TypeScript
- Branded Types | Learning TypeScript
- TypeScript enums: use cases and alternatives
- Pattern Matching in TypeScript with Record and Wildcard Patterns
- Did you know you can write your own typesafe React router in 500 lines? · OlegWock
성능 및 효율적인 코드 작성
- Performance · microsoft/TypeScript Wiki · GitHub
- Efficient Typescript
- One Thing Nobody Explained To You About TypeScript
스타일 가이드 및 실전 활용
- TypeScript Style Guide
- DefinitelyTyped/README.ko.md at master · DefinitelyTyped/DefinitelyTyped
- Effective TypeScript
- Blogged Answers: Learning and Using TypeScript as an App Dev and a Library Maintainer
Node.js 및 빌드 관련
함수형 프로그래밍과 TypeScript
이렇게 그룹화하면 필요할 때 더 빠르게 참고할 수 있을 것 같아요! 😊
function getTiltedHeight(angle) {
const a = 100;
const A = 90 - angle;
const c = a / Math.sin(Math.PI * A / 180);
const b = Math.sqrt(Math.pow(c, 2) - Math.pow(a, 2));
return `${Math.abs(b)}%`;
}
TIL — The power of JSON.stringify replacer parameter | pawelgrzybek.com
JSON.stringify(dude, (key, value) =>
value instanceof Set ? [...value] : value
);
JSON.stringify(dude, null, "🍆");
디자인 된 <select />
에 placeholder
개념이 있어서 어떻게 하면 좋을지 찾아봤다. 간략하게 설명하자면 선택이 불가능하게 disabled
추가하고 hidden
으로 숨김 마지막으로 selected
로 디폴트값을 처리하면 완성.
function Select({ placeholder, children }) {
return (
<select>
<option value="" disabled hidden selected>
{placeholder}
</option>
{children}
</select>
)
}
Under-Engineered Select Menus | Adrian Roselli
font
,letter-spacing
,word-spacing
상속appearance
화살표 수정- 상태(focus, required, invalid)에 따른 스타일 추가
- DevTools architecture refresh: migrating DevTools to TypeScript - Chrome Developers1
- Migrating Puppeteer to TypeScript - Chrome Developers
- ts-migrate: A Tool for Migrating to TypeScript at Scale | by Sergii Rudenko | The Airbnb Tech Blog | Medium2
- Migrating millions of lines of code to TypeScript3
- Etsy’s Journey to TypeScript - Code as Craft4
Footnotes
-
Chrome DevTools 및 Puppeteer를 TypeScript로 마이그레이션한 경험에 대한 설명 ↩
-
대규모 JavaScript 코드베이스를 TypeScript로 자동 마이그레이션하는 도구인 “ts-migrate”를 소개하는 Airbnb 엔지니어링 팀의 블로그 게시물 ↩
-
Flow에서 TypeScript로 마이그레이션한 경험을 설명하고 오픈소스 flow-to-typescript-codemod 도구에 대한 링크를 포함하는 Stripe 엔지니어링 팀의 블로그 게시물 ↩
-
Etsy 엔지니어링 팀이 대규모 웹 애플리케이션에서 TypeScript를 채택한 방법, 직면한 문제 및 얻은 이점을 설명 ↩
- AWS S3: how to download file instead of displaying in-browser
- How to set Amazon S3 files download names? | Jun711 blog
Building a multi-select component
다중 선택 UI를 구현하기위해서 checkbox, select 두가지 방법으로 작업하는 방식을 소개하고 있다. 그외 선택된 상태값을 얻기위한 counter()
함수, 모바일 체크를 위한 미디어쿼리도 알려주고 있다.
aside {
counter-reset: filters;
& :checked {
counter-increment: filters;
}
&::after {
content: counter(filters);
}
}
@media (pointer: coarse) {
//
}
html
<head />
구조화된 데이터
- Using Structured Data to Enhance Search Engine Optimization | CSS-Tricks
- Understand How Structured Data Works | Google Search Central
lang
favicon
- How to Favicon in 2021: Six files that fit most needs — Martian Chronicles, Evil Martians’ team blog
- How to Create a Favicon That Changes Automatically | CSS-Tricks
- Emojis as Favicons | CSS-Tricks
- We Analyzed 425,909 Favicons • iconmap.io1
Microdata
attributes
Footnotes
-
다양한 관점에서 파비콘을 분석한 글 ↩
- Hyperlinking Beyond the Web | CSS-Tricks1
- Make your own custom URI scheme resolver | Kai Hao
- 웹에서 앱으로 이동하기 (feat.딥링크) | 필오의 개발일지
- 딥링크 실전에서 잘 사용하는 방법
Footnotes
-
uri에 대한 설명과 어플리케이션에서 어떻게 설정하는지 정리한 글. ↩
📌 테스트 전반에 대한 가이드 및 베스트 프랙티스
- 이 가이드가 당신의 테스트 기술을 한 단계 끌어 올리는 이유
- 테스트 함정과 해결 방법
- GitHub - mhevery/guide-to-testable-code
- Good Code, Testable Code | Epic Web Dev
- The True Purpose of Testing | Epic Web Dev
🛠 Cypress 관련 리소스
- Cypress Tips and Tricks | Better world by better software
- cypress-example-recipes/examples at master · cypress-io/cypress-example-recipes · GitHub
🔎 React 테스트 관련 자료
- Common mistakes with React Testing Library
- Best Practices for Writing Tests with React Testing Library | ClarityDev blog
- Avoid Nesting when you’re Testing
📏 테스트 기법 및 접근법
- How to get started with property-based testing in JavaScript using fast-check
- Inverse Assertions | Epic Web Dev
- Cram tests: a hidden gem of dune | sancho.dev
🔧 테스트 환경 및 도구
- Testing Playground
- Testing Types | Guide | Vitest
- Testing Types in TypeScript – Frontend Masters Boost
🏗 테스트 아키텍처 및 설계
# 개발할때 편하다
yarn add -D @types/cypress
// react-devtools 같은 확장도구가 필요할 경우
// /plugins/index.js
const path = require('path')
module.exports = (on, _config) => {
on('before:browser:launch', (browser, launchOptions) => {
if (browser.family === 'chromium') {
const extensionFolder = path.resolve(__dirname, '..', '..', '4.7.0_1')
launchOptions.args.push(`--load-extension=${extensionFolder}`)
return launchOptions
}
})
}
// /support/commands.js
Cypress.on('window:before:load', (win) => {
win.__REACT_DEVTOOLS_GLOBAL_HOOK__ = window.top.__REACT_DEVTOOLS_GLOBAL_HOOK__
})
// 파일업로드 기능 테스트
Cypress.Commands.add(
'uploadFile',
{ prevSubject: true },
(subject, fileName) => {
cy.fixture(fileName).then((content) => {
const el = subject[0]
const testFile = new File([content], fileName)
const dataTransfer = new DataTransfer()
dataTransfer.items.add(testFile)
el.files = dataTransfer.files
cy.wrap(subject).trigger('change', { force: true })
})
}
)
// 에러때문에 테스트가 끊길 경우
Cypress.on('uncaught:exception', (err, runnable) => {
console.log(err)
return false
})
테스트 코드에서 mock으로 처리하는 경우1
- Mocking React custom hook with Jest - Stack Overflow
- mrbenhowl/mocking-firebase-initializeApp-and-firebase-auth-using-jest
- Avoid Nesting when you’re Testing
Footnotes
-
그래서 내가 내린 결론은 저렇게 까지는 테스트할 필요가 없고 오히려 애매하게 결합된 컴포넌트들을 분리해서 관리하는게 맞을 것 같다는 생각을 해봤다. ↩
- The Best Anti-Patterns for BDD | Cucumber Blog
- Cucumber for BDD - Using Anti Patterns | Cucumber Blog
- Best practices for scenario writing | CucumberStudio Documentation
- Writing scenarios with Gherkin syntax | CucumberStudio Documentation
- Getting Started with BDD (Part 1) | Cucumber Blog
- 🥒 Cucumber 이해하고 잘 쓰는 방법
- BDD with Cucumber (JavaScript)
- Integrating Cypress with Cucumber and Gherkin
- BDD (Behaviour-Driven Development)에 대한 간략한 정리
live2d 구현이 필요할 경우 pixi+plugin을 사용하면 된다
- How to Make Motion Graphics in 4 Simple Steps
- 구글, 리프트에서 배운 모션그래픽 법칙 6가지
- Animation Handbook - DesignBetter
- A guide to Motion Design principles | by Micah Bowers | UX Collective
- Applying Disney’s basic principles of animation to UI design | Dribbble Design Blog
- Motion Design Doesn’t Have to be Hard | by Jonas Naimark | Google Design | Medium
모션 블러 효과
- motion blur with svg gaussianblur (tween only the x value) - GSAP - GreenSock
- Motion Blur Effect with SVG - Codrops
- How to Create a Realistic Motion Blur with CSS Transitions | CSS-Tricks1
Footnotes
-
여러개 엘리먼트에 transition-delay로 시간차를 주는 방법으로 구현하고 있다. ↩
- Playing With Particles Using the Web Animations API | CSS-Tricks
- Ground Rules for Web Animations | CSS-Tricks
- Motion One: The Web Animations API for everyone1
Footnotes
-
가장 작은 파일 크기와 가장 빠른 성능을 위해 Web Animations API를 기반으로 구축된 새로운 애니메이션 라이브러리입니다. ↩
**title**: 반품 및 교환은 재고로 이동합니다.
**as a** 가게 주인으로서,
**I want** 반품 또는 교환 시 상품을 재고에 다시 추가하고 싶습니다.
**so that** 인벤토리를 추적할 수 있습니다.
**Scenario 1:** 환불을 위해 반품된 항목은 인벤토리에 추가되어야 합니다.
**Given** 고객이 이전에 나에게서 검은색 스웨터를 구매했다는 점을 감안할 때
**and** 인벤토리에 검은색 스웨터 3개가 있습니다.
**when** 환불을 위해 검은색 스웨터를 반환할 때
**then** 그러면 인벤토리에 4개의 검은색 스웨터가 있어야 합니다.
Feature > […Scenario] > […Step]
Given
: Given단계는 시스템의 초기 컨텍스트( 시나리오 장면) 를 설명하는 데 사용됩니다 . 그것은 일반적으로 과거에 일어난 일입니다.When
: When단계는 이벤트 또는 작업 을 설명하는 데 사용됩니다 . 이것은 시스템과 상호 작용하는 사람이거나 다른 시스템에 의해 트리거되는 이벤트일 수 있습니다.Then
: Then단계는 예상되는 결과 또는 결과 를 설명하는 데 사용됩니다 .And
,But
Scenario: 항목 검색 후 결제 페이지로 이동
Given 사용자가 Greencart 방문 페이지에 있다
When 사용자가 야채<이름>를 검색했을 때
And 장바구니에 항목이 추가된다
And 사용자는 구매를 위해 Checkout 페이지로 이동한다
Then 선택한 <이름> 항목이 체크아웃 페이지에 표시된다
- https://github.com/Financial-Times/polyfill-library/issues1
- 194235 – Bottom 44px area is not clickable and shows navbar2
- css - Why is font-size different on iOS Chrome v. iOS Safari? - Stack Overflow3
- html - ReactJS render string with non-breaking spaces - Stack Overflow4
- Back button does not trigger getInitialProps - Google Search
- Is this the bug of react-error-overlay?5
- Property name expected type of string but got null
- Bogus import in generated dist ESM file · Issue #1632 · bvaughn/react-virtualized · GitHub6
- Twitter Bootstrap modal blocks text input field - Stack Overflow7
Footnotes
interface MathFn {
(a: number, b: number): number
}
const sum: MathFn = (a, b) => a + b
Footnotes
-
function declarations, function expressions, arrow functions, methods등 TypeScript에서 함수를 선언하는 다양한 방법들. ↩
import isAfter from 'date-fns/isAfter';
isAfter(new Date(), new Date(DATE))
날짜 비교 할 일이 있어서 별 생각 없이 new Date
를 때렸는데 safari에서 안되는 문제가 발견되었다. 콘솔을 확인해보니 yyyy-MM-dd HH:mm:ss 해당 형태의 포멧 에서는 안된다. 평소에 new Date
보다는 moment나 date-fns같은 라이브러리를 당연하게 써오다 보니 몰랐다. 그런데 또 다른 생각을 해보자면 저런 문제가 있기 때문에 더 적극적으로 라이브러리를 사용해야 한다는 게 함정.
import isAfter from 'date-fns/isAfter';
import format from 'date-fns/format';
isAfter(new Date(), format(DATE))
Google Optimize에서 Redirect 테스트를 진행했는데 기능 구현은 문제없는데 세션수가 안 잡히는 문제가 발생했다.
이번에 문제되었던 부분은 query 처리를 인지하지 못했던 점이었다. Optimize에서 redirect 처리가 이루어질 때 query
에 정보(예. utm_expid
)들이 추가되는데 리다이렉트 url 변경하는 부분이 있었고 optimize에서 참조되어야 할 query를 날려버리면서 측정이 불가능했던 것. 결국 replace
실행 코드를 이전 query를 assign 시켜주도록 변경해서 해결.
여담이지만 Charles를 사용하고 있어서 배포 없이 테스트 가능했던 점도 같이 메모.
connect가 실행된 컴포넌트를 (enzyme) mount
로 테스트한 경우 실패 케이스가 발생해서 찾아본 내용들 인 것 같은데 정확하게 기억이 안남. shallow
로 변경했더니 이번에는 ref를 못쓰는 문제도 있었다고 하고…
여담이지만 컴포넌트를 명확하게 정의하고 분리해서 테스팅 스트레스를 줄이는 게 중요합니다.
brew로 ffmpeg 설치할때1
brew install ffmpeg $(brew options ffmpeg | grep -vE '\s' | grep -- --with-' | tr '\n' ' ')
Footnotes
youtube-dl 업데이트가 안되면서 다운로드 속도가 굉장히 저조하다. 그래서 yt-dlp로 일단 변경해서 사용 중.
여러 개 주소를 한꺼번에 받으려면 주소를 txt 파일로 저장하고 -a(-a, --batch-file FILE)
옵션을 추가하면 된다.
youtube-dl -a list.txt
가끔 중간에 에러가 발생하는 경우도 있는데 이럴 경우 ignore로 방어 코드를 설정하고 실행시키면 잘 된다.
youtube-dl -i, --ignore-errors
자막을 다운로드해야 된다면 —sub-lang 옵션을 추가한다. 자동 자막이 필요하다면 --write-auto-sub
youtube-dl --write-sub --sub-lang ko
youtube-dl --write-auto-sub
playlist를 다운 받을때 index도 파일에 포함시키고 싶을 경우1
-o '%(playlist)s/%(playlist_index)s - %(title)s.%(ext)s'
기본적으로 평소 사용하는 alias 설정
alias dm="youtube-dl"
alias da="youtube-dl -x --audio-format mp3"
Footnotes
스트리밍 비디오 다운로드 할 경우 403 에러가 발생할때 해당 옵션으로 우회한다.1
youtube-dl --referer "URL" --user-agent "UA"
dmRef() {
youtube-dl $1 --referer $1 --user-agent "UA"
}
Footnotes
::-webkit-input-placeholder /* for (Chrome/Safari/Opera) */
:-ms-input-placeholder /* for IE. */
::-ms-input-placeholder /* for Edge (also supports webkit prefix) */
::-ms-clear {}
::-ms-reveal {}
자동완성 기능은 편리할수도 있지만 비활성화가 필요한 경우도 있다. 하지만 chrome에서 무시되는 경우가 있는데…이게 거의 된다 지금은 안된다…되는 것 같다…이렇게 변질이 되는 것 같다.
- html - Disabling Chrome Autofill - Stack Overflow1
- Disabling autofill in Chrome | Codementor2
- How to turn off form autocompletion - Web security | MDN
- The Autofill Dark Pattern — Smashing Magazine
Footnotes
- TypeScript without TypeScript — JSDoc superpowers12
- TypeScript: Documentation - Creating .d.ts Files from .js files3
- TypeScript: Documentation - JSDoc Reference4
- Type Guards in Javascript Using JSDoc Comments - goulet.dev5
- Type-safe JavaScript code with JsDoc6
- How to add JSDoc Typechecking to SvelteKit
- Home - jsdoc-ts-utils docs
- [Node.js] JSdoc 으로 타입 명시하여 VSCode 에서 편하게 코딩하기 – Under The Pencil
- TypeScript, Minus TypeScript | CSS-Tricks - CSS-Tricks5
- javascript - How to use typescript jsdoc annotations for React PropTypes - Stack Overflow6
- JSDoc as an alternative TypeScript syntax
Footnotes
-
JSDoc(주석)으로 타입 체크하는 방법을 소개 ↩
-
TypeScript가 작동하는 방식과 유사하게 자바스크립트 코드에 유형 어노테이션과 유형 검사를 추가하는 데 JSDoc을 사용하는 방법을 설명 ↩
-
기존 JavaScript 코드에 대한 선언 파일(.d.ts)을 생성하여 TypeScript가 JavaScript 코드를 유형 검사할 수 있도록 하는 방법을 설명 ↩
-
JSDoc 구문에 대한 포괄적인 참조 ↩
-
TypeScript를 사용하는 대신 JSDoc을 사용하여 JavaScript 코드에 타입 어노테이션을 추가할 때의 이점에 대해 설명. 경우에 따라 가볍고 유연한 대안이 될 수 있음. ↩ ↩2
- Creating websites with prefers-reduced-data | Polypane, The browser for ambitious developers1
- Modifying Specific Letters with CSS and JavaScript | CSS-Tricks2
- Stroke Text CSS: 📕 The Definitive Guide - Coding Dude3
- Use advanced typography with local fonts4
- Leading-Trim: The Future of Digital Typesetting | by Ethan Wang | Microsoft Design | Medium5
- Google Fonts6
- Using Font Variant Numeric7
Footnotes
- JavaScript: Why Reflect APIs?
- JavaScript Proxy. 근데 이제 Reflect를 곁들인 | TOAST UI :: Make Your Web Delicious!
- The global
Reflect
object, its use cases and things to watch out for | Stefan Judis Web Development - How JavaScript works: Proxy and Reflect | by Ukpaiugochi | SessionStack Blog
- Reflection at Reflect: The Reflect and Proxy APIs | Reflect
- 5 steps to faster web fonts /// Iain Bean
- How to Stop Lighthouse Complaining About Render Blocking Google Fonts | Codeboosh
- A Comprehensive Guide to Font Loading Strategies—zachleat.com
- How to avoid layout shifts caused by web fonts – Simon Hearne
- The Fastest Google Fonts – CSS Wizardry – Web Performance Optimisation
- Time to Say Goodbye to Google Fonts
- The Best Font Loading Strategies and How to Execute Them | CSS-Tricks
요약하자면
- woff2
- font-display로 레이아웃 이슈 최소화
- preload
- self (feat. google webfonts helper)
몇몇 권장사항은 한글 폰트에서는 사실상 불가능한 부분이라 가능한 부분들을 적용하면 된다.
가변 글꼴은 진보된 OpenType 사양입니다. CSS를 사용할때 가짜 굵기 또는 기울임꼴과 같은 브라우저 왜곡 에 대해 걱정하지 않고 단일 글꼴 파일에 포함된 모든 스타일에 액세스할 수 있습니다.
이전에는 여러 스타일을 사용한다는 것은 모든 너비, 두께 또는 기울임꼴에 대해 하나씩 여러 파일을 로드하는 것을 의미했습니다. 이로 인해 디자인 표현력(사용된 글꼴 수)과 웹사이트 성능(다운로드할 데이터의 양) 사이에 긴장감이 생겼습니다. 가변 글꼴을 사용하면 전체 방정식이 변경됩니다.
- A Variable Fonts Primer
- Google Fonts
- Getting the Most Out of Variable Fonts on Google Fonts | CSS-Tricks
- Variable Fonts for Developers
- Using CSS Custom Properties to Adjust Variable Font Weights in Dark Mode | CSS-Tricks1
Footnotes
-
다크모드에서 weight가 시각적으로 차이가 날수 있기때문에 그럴경우 변수로 분리해서 대응하는 방법 ↩
autocomplete="one-time-code"
사용자가 SMS 메시지를 수신 할 때마다, 운영 체제는 SMS에서 OTP 구문을 분석하고 키보드는 OTP를 제안합니다. iOS, iPadOS 및 macOS의 Safari 12 이상에서만 작동하지만 해당 플랫폼에서 SMS OTP 환경을 쉽게 개선할 수 있는 방법이므로 사용하는 것이 좋습니다.
<input
type="text"
inputmode="numeric"
autocomplete="one-time-code"
pattern="\d{6}"
required
/>
const otp = await navigator.credentials.get({
otp: {
transport: ['sms']
}
})
input.value = otp.code
참고
- How I Built a Cross-Platform Desktop Application with Svelte, Redis, and Rust | CSS-Tricks1
- Golang Desktop App: Webview vs. Lorca vs. Electron | by Graham Jenson | Maori Geek2
- GitHub - kofigumbs/multi: Create a custom, lightweight macOS app from a group of websites3
- Proton-Native vs Vuido vs NodeGUI - DEV Community4
- Build lightweight cross-platform desktop apps with JavaScript, HTML, and CSS | Neutralinojs5
Footnotes
feTurbulence
Perlin noise12를 사용하여 구름이나 대리석과 같은 인공 질감을 합성. feDisplacementMap
in2의 이미지 픽셀 값을 사용하여 in의 이미지를 공간적으로 대체합니다.
- SVG Filter Effects: Creating Texture with
<feTurbulence>
- Codrops - Drawing Realistic Clouds with SVG and CSS | CSS-Tricks
- The Art Of SVG Filters And Why It Is Awesome
- A Deep Dive Into The Wonderful World Of SVG Displacement Filtering — Smashing Magazine
- SVG Filter Effects: Conforming Text to Surface Texture with
<feDisplacementMap>
- Codrops
Footnotes
mask
현재 개체를 배경으로 합성하기 위한 알파 마스크를 정의
- Creating a Gauge in React - Amelia Wattenberger1
- Stop Using ‘Drop-down’ — Adrian Roselli2
- Simulating Mouse Movement | CSS-Tricks3
- The perfect responsive menu (2021) | Polypane, The browser for ambitious developers4
- Of Course We Can Make a CSS-Only Clock That Tells the Current Time! | CSS-Tricks5
- Capturing an Image from the User | Web Fundamentals6
- Closing on outside click | Kitty Giraudel7
- Sendtric - Email Countdown Timers8
- GitHub - robflaherty/screentime: Measure how much time things spend on-screen9
- 7GUIs10
- Headless UI – Unstyled, fully accessible UI components11
- Front End Tables: Sorting, Filtering, and Pagination | Tania Rascia12
Footnotes
-
어떻게 arc를 그릴 수 있는지 d3를 사용해서 알려주고 있다. ↩
-
드롭다운 이라는 용어(단어)가 갖고 있는 모호함이 있어서 혼란을 줄 수 있으므로 사용을 중지 하고, 대신 원하는 컨트롤을 정확하게 설명하는 용어를 선택하도록 정리한 글. ↩
-
애니메이션 데모를 시연할 경우 가상의 마우스 움직임이 필요한 경우가 있어서 noisejs를 사용해서 시뮬레이션 하는 방법을 소개하고 있다. ↩
-
2021 완벽한 햄버거 ↩
-
css로 구현하는 아날로그, 디지털 시계. 움직임도 가능하다. ↩
-
사용자로 부터 이미지(미디어)를 입력받기 위해 인풋, 클립보드, 카메라에 접근 가능한 방법을 알려주고 있다. ↩
-
카운트다운 타이머를 이미지 형식으로 제공하고 있다 ↩
-
사용자가 어떤 영역에 머무르고 있는지 체크할 수 있다 ↩
-
GUI 프로그래밍의 대표적인 과제라 할수있는 7가지 작업을 정의 ↩
-
Tailwind CSS와 통합되도록 설계된 액세스 가능한 UI 구성 요소입니다. ↩
-
정렬, 필터링 및 페이징 기능이 있는 테이블 UI 구현을 어떻게 접근하는지에 대한 설명 ↩
- Adding Shadows to SVG Icons With CSS and SVG Filters | CSS-Tricks1
- How to Animate a SVG with border-image | CSS-Tricks2
- Text Along a Path on the Web Using SVG textPath ← Alligator.io3
- Morphing SVG With react-spring | CSS-Tricks4
- Accessible SVGs: Perfect Patterns For Screen Reader Users — Smashing Magazine5
- SVG Favicons in Action | CSS-Tricks6
- A Practical Guide To SVG And Design Tools — Smashing Magazine7
- Fun with stroke-dasharray
Footnotes
- Can I email… Support tables for HTML and CSS in emails1
- Revolvapp Email Editor2
- GitHub - unlayer/react-email-editor: Drag-n-Drop Email Editor Component for React.js3
- Creating a Drag-n-Drop Email Editor with React | by Adeel Raza | Unlayer Blog | Medium4
- A Complete Guide To HTML Email — Smashing Magazine5
- Dark Mode Email: Your Ultimate How-to Guide - Litmus6
Footnotes
- WYSIWYG
- Markdown
- Textarea
- App
Footnotes
- Generator Plug-in for Adobe Photoshop CC that helps users export image assets
- Generator Architecture
- Generate Image Assets Functional Spec
이 리포지토리에는 Adobe Photoshop CC의 Generator 확장성 레이어용 플러그인이 포함되어 있습니다. 이 플러그인을 사용하면 Photoshop 파일에서 이미지 자산을 더 쉽게 내보낼 수 있습니다. 사용자는 파일 이름과 같은 구문을 사용하여 내보내려는 문서 및 이름 레이어(또는 레이어 그룹 또는 스마트 개체)에 대한 이미지 자산 생성을 활성화하기만 하면 됩니다. 그런 다음 Generator는 이러한 레이어를 감시하고 변경될 때마다 디스크의 해당 자산을 자동으로 업데이트합니다.
하지만…그닥 부정적이다.
PhotoShop과 Node.js를 함께 사용할 수 있다는 점은 매우 훌륭하지만 Node.js와 PhotoShop을 동일한 서버에 유지하는 것은 실용적이지 않습니다. Plus Photoshop은 Windows 및 MacOS에서만 실행됩니다.
GitHub - meltingice/psd.js: A Photoshop PSD file parser for NodeJS and browsers
psd 파서. 이를 통해 관리 가능한 트리 구조에서 Photoshop 문서로 작업하고 다음과 같은 중요한 데이터를 찾을 수 있습니다.
- 문서 구조
- 문서 크기
- 레이어/폴더 크기 + 위치 지정
- 레이어/폴더 이름
- 레이어/폴더 가시성 및 불투명도
- 글꼴 데이터( psd-enginedata 를 통해 )
- 텍스트 영역 내용
- 글꼴 이름, 크기 및 색상
- 색상 모드 및 비트 심도
- 벡터 마스크 데이터
- 병합된 이미지 데이터
- 레이어 구성 요소
포토샵 스크립트 관련 링크(액션과는 다르다 액션과는!) 정리. 액션도 편하기는 하지만 개발적인 관점에서 보자면 너 유연한 작업이 가능한 스크립트를 작성하는게 맞다.
유튜브 강의 - 보면 시각적으로 뭐가뭔지 나오니까 이해가 바로 된다
- JavaScript + Photoshop — Basic Automation Workflows - YouTube
- Introduction to Photoshop Generator - YouTube
- Importing text or images in batch with Variables in Adobe Photoshop - The Complete Tutorial - YouTube
- Photoshop Scripting Tutorial - YouTube
- How to get started with Scripting in Photoshop - YouTube
어도비 공식 문서 - 스펙 정의에 가까워서 예제코드로 감을 익히고 백과사전용으로 사용하는게 좋을 것 같다.
그래서 어떻게 작성하면 되는거야?…에 대한 예제들. 아마도 es3로 작성해야하는 부분때문에 문법은 복잡해 보인다.
- OpenGraph - Preview Social Media Share and Generate Metatags - OpenGraph1
- The Open Graph protocol2
- SEO 기본 가이드: 기본사항 | Google 검색 센터 | 문서 | Google Developers
- General Structured Data Guidelines | Google Search Central | Google Developers3
- Getting started with Cards | Docs | Twitter Developer Platform
Footnotes
# .gitignore가 규칙이 적용되지 않을때
git rm -rf --cached .
git add .
premiumSampleRate
The percentage of tracked sessions with Browser Premium pricing features: 100 for all, 0 for none. For more details about premiumSampleRate, see the sampling configuration.
계산기 응용 프로그램의 다양한 측면을 설명합니다.
- The creation of Soulver. How two teenagers made a new kind of… | by Zac Cohan | Soulver | Medium1
- Designing calculator apps2
- Numi. Beautiful calculator app for Mac.3
- Parsify Desktop4
- Math Notepad5
Footnotes
-
사용자가 평범한 문장을 사용하여 계산을 수행할 수 있는 고유한 인터페이스가 있는 계산기 응용 프로그램인 Soulver를 만든 방법을 알려주는 내용. 제작자가 직면한 어려움과 Soulver를 돋보이게 하는 기능에 대해 설명합니다. ↩
-
계산기 응용 프로그램의 설계 원칙에 대해 설명합니다. 다양한 유형의 계산기와 사용자 친화적인 계산기 앱을 만드는 핵심 디자인 요소에 대한 자신의 생각을 공유합니다. ↩
-
자연어 인터페이스와 단위 환산, 통화 환산, 계산 내역과 같은 고급 기능을 제공하는 Mac용 계산기 애플리케이션인 Numi의 웹사이트. Numi의 기능 및 가격에 대한 정보가 포함. ↩
-
Parsify Desktop은 엔지니어, 과학자 및 학생이 사용하도록 설계되었으며 복잡한 계산 및 단위 변환을 지원합니다. (자연어 인터페이스도 제공) ↩
const obj = {
a: 1,
b: 2,
}
console.log(obj[['a']]) // 1
console.log(obj[['b']]) // 2
이게 되네 🤔
- Set up Feature Flags in a React JS App - Flagship.io
- GitHub - paralleldrive/react-feature-toggles: Feature Toggles for React Projects
- GitHub - romaindso/react-feature-flags: Simplify feature flipping with flags sharing across your React app
- React hook – incremental release with feature flags | by Maurice Wallbott | Medium
- Feature Flags in React with Flagged by sergiodxa
- Adding Feature Flags in Your React Codebase - DEV Community 👩💻👨💻
- GitHub - sergiodxa/flagged: Feature Flags for React made easy with hooks, HOC and Render Props
- The 8 best free and open-source feature flag services
- 참고
- whack-a-mole/index.html at master · ccs-summer-2020/whack-a-mole · GitHub
- pukul-tikus-tanah/script.js at master · sandhikagalih/pukul-tikus-tanah · GitHub
- whack-a-mole/App.js at master · oscarplatoon/whack-a-mole · GitHub
- whack-a-mole-project/script.js at main · JS-Beginners/whack-a-mole-project · GitHub
- 영상
- React
- 소프트웨어 알고리즘
- 알고리즘 | 컴퓨터과학 | 컴퓨팅 | Khan Academy
- Tree Traversal via JavaScript
- Data Structures in JavaScript
- Linked List - Front-End Engineering Curriculum - Turing School of Software and Design
- Lecture 1: Algorithms and Computation | Introduction to Algorithms | Electrical Engineering and Computer Science | MIT OpenCourseWare
- 17 Equations that Changed the World - Rewritten in JavaScript
- Turing Machines
- 네이버
- Redirect
- Sitemap
# 아마도 git을 사용하고 있을테니까 `git mv`를 사용해서 변경해주자
find src -type f | grep "\.[jt]s$" | xargs -n1 grep -HE "^[^*\n]*(<\/?[a-zA-Z]*>)[\s\w]*$" | cut -d: -f1 | uniq | awk '{print "git mv "$1" "$1"x"}' | sh
Footnotes
Footnotes
-
cli를 사용할 경우
CHROMATIC_PROJECT_TOKEN
을.env
에서 관리하면 된다 ↩
지도같은 화면 구현이 보여서 확인해보니 드래그로 구현이 되어 있었다. 그래서 혹시 지도로 구현된게 없을까 해서 찾아본 자료들. leafletjs 보니까 Non-geographical maps 라는 표현을 사용했다.
Leaflet 공식 예제
활용 및 스토리맵
- How to Make an Interactive Story Map Using Leaflet and Non-Geographical Images
- Pictorial St. Louis 프로젝트
- GitHub - nielsenjared/pictorial-st-louis
- GitHub - HandsOnDataViz/leaflet-storymaps-with-google-sheets
대형 이미지 타일링 및 플러그인
OpenLayers 관련 자료
네이버 지도 API 및 기타 지도 유형
// cannot be used as a JSX component
@types/react
, @types/react-dom
에 대한 참조가 잘못되면서 발생하는 이슈
<div
dangerouslySetInnerHTML={{
__html: `
<img src="http://unsplash.it/100/100?random" onload="console.log('you got hacked');" />
`,
}}
/>
가끔 아주 가끔 이상한 일을 해야할때가 있는데 그럴때는 이렇게 하면 된다.
git remote add {alias} {url}
git fetch {alias}
git merge --allow-unrelated-histories {alias}/{branch}
- React + Webpack: ChunkLoadError: Loading chunk X failed. | by Raphaël Léger | Medium
- How to fix ChunkLoadError in your ReactJS application - Codemzy’s Blog
lazy
로 import
했을 경우 ChunkLoadError가 발생하는데 이럴 경우 어떻게 대응할 수 있는지 정리해둔 글들.
- GitHub - softonic/axios-retry: Axios plugin that intercepts failed requests and retries them whenever possible
- GitHub - kuitos/axios-extensions: 🍱 axios extensions lib, including throttle, cache, retry features etc…
- cacheAdapterEnhancer
- throttleAdapterEnhancer
- retryAdapterEnhancer
- Using Axios interceptors for refreshing your API token.
- API Fetch Retry로직 작성해보기 (with Axios) | by FlyingSquirrel | Medium
- https://blog.chayeoi.site/posts/applying-api-cache-using-axios/
Footnotes
-
어떤 방식으로 접근하는지에 대한 설명들이 있다. ↩
Vercel Edge & Next.js Middleware
- GitHub - vercel-labs/react-on-the-edge: Server-rendered React using Vercel Edge Functions.
- Vercel Edge Functions with Next.js
- How to Use Next.js Middleware • CTNicholas
- examples/edge-functions at main · vercel/examples · GitHub
App Router & Routing
- Password protecting routes in Next.js App Router — Alex Chan
- How to Build a Blog with Next.js App Router & Headless Hashnode - Space Jelly
- Managing Advanced Search Param Filtering in the Next.js App Router | Aurora Scharff
- Next.js Router - Listen to route (location) change with useRouter | Jason Watmore’s Blog
- Understanding Next.js routeChangeStart and router events - LogRocket Blog
Next.js 내부 구조 분석
- Check whether user has a Chrome extension installed 특정 확장도구가 설치되어 있는지 판별하는 방법
# 크롬 익스텐션이 설치되는 경로는 다음과 같다
/Users/USERNAME/Library/Application Support/Google/Chrome/Default/Extensions
function isInstalled(extensionId) {
return new Promise((resolve, reject) => {
const img = new Image()
img.src = `chrome-extension://${extensionId}/icon-128.png` // 해당 리소스가 `web_accessible_resources`에 선언되어 있는지 확인이 필요하다.
img.onload = () => {
resolve(true)
}
img.onerror = () => {
reject()
}
})
}
Clear CloudFront Cache with AWS CLI | bobbyhadz
/**
* CloudFront 배포에 대한 캐시를 무효화하려면 경로와 함께 명령을 실행합니다
*
* @param {object} params
* @param {string} params.distributionId
* @param {string} params.paths
* @returns {CreateInvalidationResult}
*/
function createInvalidation({
distributionId,
paths = `"/*"`
}) {
const result = $`aws cloudfront create-invalidation --distribution-id ${distributionId} --paths ${paths}`
return result
}
드래그 앤 드롭 기능이나 위지윅 편집기를 사용하여 사용자 인터페이스와 레이아웃을 구축하기 위한 도구와 라이브러리입니다. 이러한 도구는 수동으로 코드를 작성하지 않고도 복잡한 UI 디자인과 레이아웃을 만드는 과정을 간소화하는 것을 목표로 합니다.
- Builder.io - Drag and drop experience builder - Builder.io1
- Blocks UI2
- Craft.js3
- Pagedraw — Effortlessly turn mockups into functional UI code4
- OpenChakra5
- GitHub - chriskitson/react-drag-drop-layout-builder: Drag and drop (DnD) UI layout builder using React and ImmutableJS6
- Unlayer Embed Plugin | Email and Page Editor for SaaS
- Puck
Footnotes
npm 레지스트리에서 특정 패키지에 대한 메타데이터 및 기타 정보를 볼 수 있습니다. 이 명령으로 패키지의 최신 버전, 패키지의 종속성, 작성자 및 라이선스 정보, 기타 세부 정보를 확인할 수 있습니다.
# 이전 버전 리스트를 확인하고 싶을때
npm view cowsay versions
# 각 버전이 게시된 시간을 확인
npm view cowsay time
Footnotes
-
설치된 npm 패키지의 버전을 확인하는 방법에 대한 가이드 ↩
커스텀 폰트 사용 시, 배포 중 Provided Edge Function is too large 에러가 발생할 경우 로컬 파일로 사용하지말고 fetch
로 해당 폰트를 가져온다.12
Footnotes
- monad
- pipe
- I’ve used the pipe() function 2,560 times and I can tell you it’s good! – Obvibase under the hood
- Hack Pipe for Functional Programmers: How I learned to stop worrying and love the placeholder
- A quick introduction to pipe() and compose() in JavaScript | by Yazeed Bzadough | We’ve moved to freeCodeCamp.org/news | Medium
- lodash
- hoc
Lambda@Edge (Node) 함수 작성 시 npm module을 사용하는 방법. esbuild를 사용하면 편하다.
{
"scripts": {
"build": "esbuild --bundle --minify --platform=node --target=node12 --outdir=build main.js",
"export": "cd build && zip main.js.zip main.js"
}
}
// 현재 실행된 stage 값을 참조하기
// 하지만 스크립트를 따로 작성하는게 더 효율적으로 보인다.
{
"scripts": {
"dev:hello": "echo ${npm_lifecycle_event//dev:/}"
}
}
- yarnpkg - Is there a way to get the name of the npm script passed to the command specified by that script? - Stack Overflow1
- scripts | npm Docs2
Footnotes
BDD는 무엇을 의미합니까?
BDD는 시스템의 원하는 동작을 협력적으로 지정하는 접근 방식입니다. 행동의 일부가 합의될 때마다 우리는 그 행동을 구현할 코드의 개발을 “추진”하기 위해 해당 사양을 사용합니다.
BDD의 세 가지 관행은 무엇이며 스토리에 어떤 순서로 적용합니까?
우리는 스토리에 필요한 행동의 범위를 공동으로 발견 하는 것으로 시작합니다. 일단 우리가 행동에 동의하면 우리는 비즈니스가 읽을 수 있는 언어로 사양을 공식화 합니다. 마지막으로 공식화된 사양을 자동화 하여 시스템이 실제로 예상대로 작동하는지 확인합니다.
Cucumber와 BDD는 어떤 관련이 있습니까?
Cucumber는 문서를 이해하고 자동화된 테스트로 변환하는 도구입니다.
BDD는 세 가지 방식으로 구성된 협업적 접근 방식입니다. BDD 실무자는 Cucumber를 사용하여 문서를 자동화할 수 있습니다.
“살아있는 문서”의 특별한 점은 무엇입니까?
문서가 애플리케이션의 동작과 동기화되지 않을 때 자동으로 알려 주기 때문에 “살아있는 문서”라고 부릅니다. 그것이 특별한 점입니다.
완료에 대한 정의의 일부로 이를 검토할 수 있지만 자동으로 유효성이 검사되지 않더라도 작성한 모든 사양 문서에 대해서도 마찬가지입니다.
그것은 자동화된 테스트에 의해 생성 되지 않습니다 - 여전히 작성해야 합니다! 자동화된 테스트는 귀하가 작성한 내용이 사실인지 아닌지를 알려줍니다.
이를 위한 변경 제어 프로세스가 있을 수 있습니다. 설명하는 코드와 함께 소스 제어에 유지하는 것이 좋습니다. 그러나 다시 말해서 특별한 것은 아닙니다. Word 문서에 대해 놀랍도록 투명한 변경 제어 프로세스를 가질 수 있지만 여전히 완전히 구식이고 잘못된 것일 수 있습니다.
- Welcome to the world of Statecharts - Statecharts1
- Formally Specifying UIs • Hillel Wayne2
- UML State Machine Diagrams - Overview of Graphical Notation3
- Graphviz 소개4
Footnotes
-
시스템의 동작을 모델링하는 형식주의인 스테이트차트를 설명. 기본 개념과 이점을 다루며 실제로 스테이트차트를 사용하는 방법에 대한 예제. ↩
-
공식적인 방법을 사용하여 사용자 인터페이스(UI)를 지정하고 그 정확성을 보장하는 방법을 설명. UI를 위한 공식 언어, 모델 검사 및 속성 기반 테스트와 같은 주제. ↩
-
소프트웨어 개발에 널리 사용되는 모델링 언어인 UML(통합 모델링 언어)의 상태 머신 다이어그램에 대한 개요를 제공. 상태 머신 다이어그램에 사용되는 그래픽 표기법을 다루고 그 사용 예제. ↩
-
그래프비즈(Graphviz)를 소개. 그래프 시각화의 기본 개념, Graphviz 설치 및 사용 방법, 소프트웨어를 사용하여 시각화를 만드는 방법에 대한 예제. ↩
- Interview with Håkon Wium Lie. The inventor of CSS explains how it… | by Oliver Lindberg | net magazine | Medium
- 리치 히키와의 인터뷰 (마이클 포거스)
- Thank you, Guido | Dropbox Blog
- 넥슨그룹 첫 정년퇴직자 ‘백영진’의 소회 - 인벤
- An interview with the creator of the Vue.js framework Evan You by Evrone
- 리눅스 30주년 맞이 리누스 토발즈 인터뷰 번역 - 파트 1 | hacklog
- 리눅스 30주년 맞이 리누스 토발즈 인터뷰 번역 - 파트 2 | hacklog
Footnotes
-
코드 리뷰는 비동기식 커뮤니케이션의 한 형태이므로 많은 컨텍스트(바디랭귀지 및 주고받을 기회 등)가 손실됩니다. 이것은 대부분의 원격회사인 우리에게 특히 중요하므로 피드백에 약간의 뉘앙스를 추가할 방법을 원했습니다. ↩
-
Feedback Ladder: ⛰ 산(Mountain) / 차단 및 즉각적인 조치 필요, 🧗♀️ 볼더(Boulder) / 블로킹, ⚪️ 자갈(Pebble) / 비차단하지만 향후 조치가 필요함, ⏳ 모래(Sand) / 논블로킹이지만 향후 고려 필요, 🌫 먼지(Dust) / 비차단, “받거나 놔두세요” ↩
-
코드 리뷰 없이 신뢰를 기반으로 엔지니어링 문화를 구축한 방법. 다른 사람들의 상황이 자신에게 적용되는지 자문해볼 필요가 있다. ↩
Debouncing and Throttling Explained Through Examples | CSS-Tricks1
- throttle: 함수가 호출되는 속도를 제한 (일정시간 동안)
- debounce: 추가 호출 없이 일정 시간이 경과할 때까지 함수 실행을 지연하거나 제한 (마지막 이벤트만)
Footnotes
-
검색 입력, 스크롤 이벤트, 크기 조정 이벤트 등 디바운싱과 스로틀링이 유용할 수 있는 사례들 ↩
- 프로젝트에 새롭고 반짝이는 기술보다는 지루한 기술을 선택하는 것이 좋습니다.
- 흥미로운 기술은 프로젝트에 불필요한 위험을 초래할 수 있습니다.
- 이미 확립되고 입증된 기술이 성공적인 결과로 이어질 가능성이 더 높습니다.
- 신기술을 둘러싼 과대 광고는 오해의 소지가 있습니다.
- 신기술의 수명을 예측하기는 어렵습니다.
- 지루한 기술은 화려하지 않을 수 있지만 안정적이고 신뢰할 수 있으며 예측 가능합니다.
- 최신 기술 유행보다는 비즈니스 문제 해결에 집중하세요.
- 사람과 프로세스에 투자하면 더 나은 결과를 얻을 수 있습니다.
- 지루한 기술을 선택하는 것은 보수적이거나 위험을 피하기 위한 것이 아니라 프로젝트의 장기적인 성공을 우선시하는 정보에 입각한 결정을 내리기 위한 것입니다.
Footnotes
-
항상 최신의 최첨단 솔루션을 쫓기보다는 확립되고 신뢰할 수 있는 기술을 사용하는 것을 지지하는 글. 특히 장기 프로젝트의 경우 기술 선택에서 안정성과 예측 가능성의 중요성을 강조. ↩
Core Web Vitals & Performance Metrics
- Web Vitals
- Largest Contentful Paint (LCP)
- Optimize Largest Contentful Paint
- Largest Contentful Paint(최대 콘텐츠풀 페인트, LCP)
- Interaction to Next Paint(INP)
LCP 최적화
- AddyOsmani.com - Preload late-discovered Hero images faster
- How to improve the performance of your hero images - MeasureWorks
- Hack your PageSpeed LCP metric by enlarging your hero image | Erwin Hofman
- Optimising images for better LCP web vitals scores | by Javier Villanueva | ITNEXT
- Improve Largest Contentful Paint (LCP) With A JS + CSS Trick - Propel Digital Media Solutions
- Get a perfect Largest Contentful Paint (LCP) time with a single line of code - DevisedLabs
이미지 및 리소스 로딩 최적화
- The Humble
<img />
Element And Core Web Vitals — Smashing Magazine - Preload, prefetch and other
<link />
tags: what they do and when to use them · PerfPerfPerf - Optimizing resource loading with Priority Hints
- 향후 탐색 속도를 높이기 위해 리소스 미리 가져오기 | web.dev
- 즉각적인 페이지 탐색을 위해 Chrome에서 페이지 사전 렌더링 | Chrome for Developers
사이트 속도 및 성능 최적화
- Fast load times
- Efficiently load third-party JavaScript
- How to Stop Lighthouse Complaining About Render Blocking Google Fonts | Codeboosh
- How To Leverage Browser Caching to Improve Site Speed | DebugBear
CSS 기반 성능 최적화
- Improving rendering performance with CSS content-visibility | Read the Tea Leaves
- CSS content-visibility for React devs - DEV Community
서드파티 스크립트 및 자원 관리
- Best practices for using third-party embeds
- Optimize loading third-parties
- A Next.js package for managing third-party libraries | Chrome for Developers
- Building a Faster Web Experience with the postTask Scheduler | by Callie | The Airbnb Tech Blog | Medium
Partytown 및 서드파티 스크립트 성능 개선
- Welcome To Partytown
- How we cut 99% of our JavaScript with Qwik + Partytown
- Introducing Partytown 🎉: Run Third-Party Scripts From a Web Worker - DEV Community
케이스 스터디: 웹 성능 개선 사례
- How We Improved Our Core Web Vitals (Case Study) — Smashing Magazine
- Case study: Analyzing Notion app performance · PerfPerfPerf
- A Netflix Web Performance Case Study | by Addy Osmani | Dev Channel | Medium
- React Performance Fixes on Airbnb Listing Pages | by Joe Lencioni | The Airbnb Tech Blog | Medium
- How We Improved SmashingMag Performance — Smashing Magazine
- Instagram 성능 개선 사례
메모리 및 성능 이슈 해결
https://github.com/mobxjs/mobx/blob/mobx4and5/docs/best/actions.md
비동기 작업 작성
action
래퍼/데코레이터는 현재 실행 중인 함수에만 영향을 미치며 현재 함수에 의해 예약된(그러나 호출되지는 않은) 함수에는 영향을 미치지 않습니다!
즉, setTimeout
, promise.then
또는 async
구성이 있고 해당 콜백에서 상태가 더 변경되면 해당 콜백도 action
으로 래핑되어야 합니다! 비동기 작업을 만드는 방법에는 여러 가지가 있습니다. 어떤 접근 방식도 다른 접근 방식보다 엄밀히 말하면 이 섹션에서는 비동기 코드를 작성하기 위해 취할 수 있는 다양한 접근 방식을 나열합니다.
기본 예부터 시작하겠습니다.
약속
mobx.configure({ enforceActions: "observed" }) // don't allow state modifications outside actions
class Store {
@observable githubProjects = []
@observable state = "pending" // "pending" / "done" / "error"
@action
fetchProjects() {
this.githubProjects = []
this.state = "pending"
fetchGithubProjectsSomehow().then(
(projects) => {
const filteredProjects = somePreprocessing(projects)
this.githubProjects = filteredProjects
this.state = "done"
},
(error) => {
this.state = "error"
}
)
}
}
위의 예에서는 fetchGithubProjectsSomehow
프라미스에 전달된 콜백이 fetchProjects
작업의 일부가 아니므로 예외가 발생합니다. 작업은 현재 스택에만 적용됩니다.
첫 번째 간단한 수정은 작업에 대한 콜백을 추출하는 것입니다. (올바른 this
를 얻으려면 action.bound
를 사용한 바인딩이 중요합니다!):
mobx.configure({ enforceActions: "observed" })
class Store {
@observable githubProjects = []
@observable state = "pending" // "pending" / "done" / "error"
@action
fetchProjects() {
this.githubProjects = []
this.state = "pending"
fetchGithubProjectsSomehow().then(this.fetchProjectsSuccess, this.fetchProjectsError)
}
@action.bound
fetchProjectsSuccess(projects) {
const filteredProjects = somePreprocessing(projects)
this.githubProjects = filteredProjects
this.state = "done"
}
@action.bound
fetchProjectsError(error) {
this.state = "error"
}
}
이것은 명확하고 명시적이지만 복잡한 비동기 흐름에서는 약간 장황해질 수 있습니다. 또는 ‘action’ 키워드로 promise 콜백을 래핑할 수 있습니다. 다음과 같이 이름을 지정하는 것이 권장되지만 필수는 아닙니다.
mobx.configure({ enforceActions: "observed" })
class Store {
@observable githubProjects = []
@observable state = "pending" // "pending" / "done" / "error"
@action
fetchProjects() {
this.githubProjects = []
this.state = "pending"
fetchGithubProjectsSomehow().then(
// inline created action
action("fetchSuccess", (projects) => {
const filteredProjects = somePreprocessing(projects)
this.githubProjects = filteredProjects
this.state = "done"
}),
// inline created action
action("fetchError", (error) => {
this.state = "error"
})
)
}
}
runInAction
유틸리티
인라인 작업의 단점은 TypeScript가 해당 작업에 유형 추론을 적용하지 않으므로 모든 콜백을 입력해야 한다는 것입니다. 전체 콜백에 대한 작업을 만드는 대신 작업에서 콜백의 일부를 수정하는 상태만 실행할 수도 있습니다. 이 패턴의 장점은 ‘액션’으로 장소를 어지럽히지 않도록 권장한다는 것입니다. 오히려 전체 프로세스의 마지막에 가능한 한 많은 상태 수정을 넣습니다.
mobx.configure({ enforceActions: "observed" })
class Store {
@observable githubProjects = []
@observable state = "pending" // "pending" / "done" / "error"
@action
fetchProjects() {
this.githubProjects = []
this.state = "pending"
fetchGithubProjectsSomehow().then(
(projects) => {
const filteredProjects = somePreprocessing(projects)
// put the 'final' modification in an anonymous action
runInAction(() => {
this.githubProjects = filteredProjects
this.state = "done"
})
},
(error) => {
// the alternative ending of this process:...
runInAction(() => {
this.state = "error"
})
}
)
}
}
runInAction
은 첫 번째 인수로 이름을 지정할 수도 있습니다. runInAction(f)
은 사실 action(f)()
에 대한 설탕일 뿐입니다.
비동기 / 대기
비동기/대기 기반 함수는 작업을 시작할 때 처음에 혼란스러워 보일 수 있습니다.
어휘적으로는 동기 함수에 나타나기 때문에 @action
이 전체 함수에 적용된다는 인상을 줍니다.
물론 그렇지 않습니다. async / await는 약속 기반 프로세스에 대한 구문상의 설탕일 뿐입니다.
결과적으로 @action
은 첫 번째 await
까지 코드 블록에만 적용됩니다.
그리고 각 await
후에 새로운 비동기 함수가 시작되므로 각 await
후에 상태 수정 코드를 작업으로 래핑해야 합니다.
여기에서 runInAction
이 다시 유용합니다.
mobx.configure({ enforceActions: "observed" })
class Store {
@observable githubProjects = []
@observable state = "pending" // "pending" / "done" / "error"
@action
async fetchProjects() {
this.githubProjects = []
this.state = "pending"
try {
const projects = await fetchGithubProjectsSomehow()
const filteredProjects = somePreprocessing(projects)
// after await, modifying state again, needs an actions:
runInAction(() => {
this.state = "done"
this.githubProjects = filteredProjects
})
} catch (error) {
runInAction(() => {
this.state = "error"
})
}
}
}
흐름
그러나 더 좋은 접근 방식은 내장된 ‘흐름’ 개념을 사용하는 것입니다. 그들은 발전기를 사용합니다. 처음에는 무섭게 보일 수 있지만 async
/ await
와 동일하게 작동합니다. async
대신 function *
을 사용하고 await
대신 yield
를 사용하세요.
‘flow’의 장점은 구문적으로 async / await(다른 키워드 사용)에 매우 가깝고 비동기 부분에 수동 작업 래핑이 필요하지 않아 매우 깨끗한 코드를 생성한다는 것입니다.
‘flow’는 데코레이터가 아닌 함수로만 사용할 수 있습니다.
flow
는 MobX 개발 도구와 깔끔하게 통합되어 비동기 기능의 프로세스를 쉽게 추적할 수 있습니다.
mobx.configure({ enforceActions: "observed" })
class Store {
@observable githubProjects = []
@observable state = "pending"
fetchProjects = flow(function* () {
// <- note the star, this a generator function!
this.githubProjects = []
this.state = "pending"
try {
const projects = yield fetchGithubProjectsSomehow() // yield instead of await
const filteredProjects = somePreprocessing(projects)
// the asynchronous blocks will automatically be wrapped in actions and can modify state
this.state = "done"
this.githubProjects = filteredProjects
} catch (error) {
this.state = "error"
}
})
}
흐름을 취소할 수 있습니다.
흐름은 취소 가능합니다. 즉, 반환된 약속에서 ‘cancel()‘을 호출할 수 있습니다. 이렇게 하면 생성기가 즉시 중지되지만 모든 finally 절은 계속 처리됩니다. 반환된 약속 자체는 메시지가 FLOW_CANCELLED
인 FlowCancellationError
(이 오류는 패키지에서 내보냄) 인스턴스로 거부됩니다. 또한 제공된 인수가 ‘FlowCancellationError’인 경우에만 true를 반환하는 ‘isFlowCancellationError(error)’ 도우미도 내보냅니다.
- three.js
- webgl
Footnotes
인터파크 API
interface PaginationProps {
/** 현재 페이지 번호 */
currentPage: number;
/** 전체 페이지 수 */
totalPages: number;
/** 페이지 변경 시 호출되는 콜백 함수 */
onPageChange: (pageNumber: number) => void;
/** 이전/다음 페이지 링크 표시 여부 (선택적) */
showPreviousNext?: boolean;
/** 페이지 번호 링크 표시 여부 (선택적) */
showPageNumbers?: boolean;
/** 페이지당 항목 수 (선택적) */
pageSize?: number;
}
- cbcruk/react-flat-pagination
- mayankshubham/react-pagination
- react-component/pagination
- wwwaiser/react-js-pagination
- material-ui/react-pagination/
- Pagination - NuxtLabs UI
- AdeleD/react-paginate
하지만 너무 많은 기능이 필요없기 때문에 최대한 단순한 컴포넌트를 만들었다.
# .env
REACT_APP_VERSION=0.0.0-${TURBO_HASH}
REACT_APP_VERSION을 자동으로 입력할 방법을 찾다가 결국 hash값을 추가했다.
다음은 Raspberry Pi를 사용하여 날씨 정보를 표시하는 프로젝트의 두 가지 예입니다. 이 두 프로젝트 모두 Raspberry Pi를 사용하여 날씨 표시 또는 날씨 앱을 만드는 방법을 보여줍니다. 또한 Python 스크립트 또는 웹 기반 애플리케이션을 사용하여 날씨 데이터를 가져오고 표시하는 다양한 방법을 보여줍니다. 이 프로젝트는 Raspberry Pi를 사용하여 자신만의 날씨 디스플레이 또는 앱을 구축하는 데 관심이 있는 모든 사람에게 좋은 출발점이 될 수 있습니다.1
- Raspberry Pi ‘WeatherClock’ shows you the hour’s forecast - Raspberry Pi2
- Show Dev: RaspberryPi Weather ⛈ fuelled by Netlify functions and Preact - DEV Community3
Footnotes
-
작은 화면에 현재 시간과 시간별 일기 예보를 표시하는 프로젝트입니다. 이 프로젝트는 Raspberry Pi Zero W와 1.3인치 OLED 디스플레이를 사용하며 OpenWeatherMap API에서 날씨 데이터를 가져와 화면에 표시하는 Python 스크립트로 구동됩니다. ↩
-
Preact, Netlify Functions를 사용하여 OpenWeatherMap API에서 날씨 데이터를 가져옵니다. ↩
yarn add -D react-app-rewired customize-cra
// package.json
"scripts": {
"start": "react-app-rewired start",
"build": "react-app-rewired build",
"test": "react-app-rewired test"
},
// .babelrc
{
"plugins": [
["@babel/plugin-proposal-decorators", { "version": "legacy" }],
["@babel/plugin-proposal-class-properties", { "loose": true }]
]
}
// config-overrides.js
const { useBabelRc, override } = require('customize-cra')
module.exports = override(useBabelRc())
CRACO로 구성하는게 지금은 맞는 것 같다.
- performance create-react-app의 속도를 개선
- mobx decorators를 제거하기 위해 v6로 가는 여정
- issues 넘어야 할 산들이 너무 많다
Footnotes
Footnotes
- 공통 컴포넌트
Tabs
만들면서 이건 어렵다는 결론
- 대안
좋은 시스템일수록 실제 서비스에 적용하기는 더 어렵다는 문제가 발생. 결국 필요한건 headless 인데 생각보다 많지 않고 어떤 선택이 좋은 선택인가에 대한 조심스러운 부분이 있다.
Footnotes
# `System limit for number of file watchers reached`
echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p
# 빌드 시 timestamp를 참조하기 위한 방법
timestamp=$(date +%s) # 1670759329
# 하지만 현실에서는 `TURBO_HASH`값을 참조하는 방식을 `.env`에서 사용
REACT_APP_VERSION=1.0.0-${TURBO_HASH}
Footnotes
# 비디오 자르기
ffmpeg -i INPUT.mp4 -ss 00:00:00 -to 00:01:00 -c:v copy -c:a copy OUTPUT.mp4
Footnotes
-
일반 텍스트 파일을 데이터 소스로 사용하여 간단한 웹 애플리케이션을 구축하고 배포하기 위한 플랫폼. 백엔드 서버나 데이터베이스 없이 배포 가능. 텍스트 파일에서 데이터를 읽고 쓰고 검색하는 데 사용할 수 있는 간단한 REST API를 제공. ↩
-
JavaScript 애플리케이션용 경량 데이터베이스 엔진. 브라우저에서 데이터를 저장, 쿼리 및 조작하기 위한 간단하고 직관적인 API를 제공하며 챗봇, 검색 엔진 및 게임을 포함한 다양한 유형의 애플리케이션을 구축하는 데 사용 가능. 이 데이터베이스는 오프라인에서 작동하도록 설계되어 PWA(Progressive Web Application) 및 기타 유형의 클라이언트측 애플리케이션을 구축하는 데 이상적. ↩
데이터 시각화, 교육 목적 또는 단순한 재미를 위해 자신만의 대화형 지구본을 구축하는 데 관심이 있는 모든 사람에게 도움이 될 수 있습니다. 그들은 지구본을 만들기 위한 다양한 접근 방식과 기술을 보여주고 지구본 데이터로 작업할 때의 문제와 기회에 대한 귀중한 통찰력을 제공합니다.
- To design and develop an interactive globe1
- How we built the GitHub globe | The GitHub Blog2
- Gatsby Serverless Functions And The International Space Station — Smashing Magazine3
- GitHub - shuding/cobe: 5kB WebGL globe lib.4
Footnotes
-
WebGL과 Three.js를 사용하여 지구본을 렌더링하고 Mapbox를 사용하여 데이터를 표시하는 지구본을 디자인하고 개발하는 과정을 설명합니다. ↩
-
GitHub 팀이 전 세계 GitHub 사용자의 위치를 보여주는 대화형 지구본을 구축한 방법을 설명합니다. 이 게시물은 WebGL 및 d3-geo 라이브러리를 사용하여 시각화를 생성하는 지구본 구축의 기술적 문제에 대해 자세히 설명합니다. ↩
-
Gatsby 서버리스 기능을 사용하여 국제 우주 정거장(ISS)의 위치를 추적하는 대화형 지구본을 구축하는 방법에 대해 설명합니다. 이 기사는 ISS의 위치를 표시하기 위해 Three.js 및 satellite.js를 사용하는 지구본 구축의 기술적 세부 사항을 다룹니다. ↩
-
개발자가 자신만의 대화형 지구본을 만들 수 있는 경량 WebGL 지구본 라이브러리입니다. ↩
version: 1
applications:
- frontend:
phases:
preBuild:
commands:
# AWS Amplify 에서 모노레포 구조를 사용 할 경우 root 레벨로 올라가서 install
- cd ../../
- echo "$PWD"
- yarn install
build:
commands:
- echo "$PWD"
# 현재 root로 이동한 상태이므로 $AMPLIFY_MONOREPO_APP_ROOT를 바로 참조하도록 설정
- if [ $NODE_ENV_VARIABLES = ".env.development" ]; then cat "./$AMPLIFY_MONOREPO_APP_ROOT/$NODE_ENV_VARIABLES" > "./$AMPLIFY_MONOREPO_APP_ROOT/.env.production"; fi
- yarn run "build:$AMPLIFY_MONOREPO_APP"
artifacts:
baseDirectory: build
files:
- '**/*'
cache:
paths:
- node_modules/**/*
appRoot: apps/app
async function loader() {
const family = 'Material Symbols Outlined'
const source =
'url(https://fonts.gstatic.com/s/materialsymbolsoutlined/v75/kJEhBvYX7BgnkSrUwT8OhrdQw4oELdPIeeII9v6oFsLjBuVY.woff2)'
const fontFace = new FontFace(family, source)
await fontFace.load()
}
CSS Font Loading API 는 비교적 새로운 웹 플랫폼 추가 기능입니다. API가 도입되기 전에 웹 개발자는 일반적으로 CSS에 의존하여 웹 글꼴을 로드해야 했으며 이로 인해 로드와 성능 문제가 발생할 수 있었습니다. 개발자는 로드 방법을 더 잘 제어할 수 있을 뿐만 아니라 오류를 처리하고 웹 사이트에서 사용되는 시기와 방법을 제어할 수 있습니다. 전반적으로 CSS Font Loading API는 웹 개발자에게 글꼴이 정확하고 효율적으로 로드 되도록 하는 강력한 도구를 제공하는 동시에 글꼴 로드 및 사용에 대한 더 많은 제어 기능을 제공합니다.
https://medium.com/smallcase-engineering/using-jsdoc-to-enable-intellisense-for-render-props-in-vscode-e655ae4e64c1 https://stackoverflow.com/questions/67779948/add-prop-types-to-render-props-function https://github.com/DavidWells/types-with-jsdocs https://gist.github.com/DeruiDENG/074b15de1ebc23ee8d307c14198c1231 https://react-styleguidist.js.org/docs/documenting/ https://www.typescriptlang.org/docs/handbook/type-checking-javascript-files.html#supported-jsdoc https://github.com/google/closure-compiler/wiki/Types-in-the-Closure-Type-System
전자 상거래 데이터 생성 및 테스트 가능한 API
Footnotes
-
전자 상거래 데이터를 생성하기 위한 사용하기 쉬운 REST API를 제공. 이 API는 전자 상거래 애플리케이션에서 작업하는 개발자와 테스터에게 유용할 뿐만 아니라 데이터 분석 및 시각화 도구를 위한 샘플 데이터를 생성하는 데 유용. ↩
- Human Interface Guidelines - Human Interface Guidelines - Design - Apple Developer: 디자인 원칙은 사용자 경험을 향상시키기 위한 기본 가이드라인입니다. Apple의 Human Interface Guidelines는 애플 제품을 위한 인터페이스 디자인에 대한 원칙과 모범 사례를 제공합니다. 예를 들어, 간결하고 일관된 디자인, 직관적인 상호 작용, 정보의 집중화, 애니메이션 및 트랜지션의 적절한 사용 등을 다룹니다. 이러한 가이드라인을 따르면 사용자들이 애플 제품과 앱을 쉽게 이해하고 사용할 수 있게 됩니다.
- All WCAG 2.1 Techniques | WAI | W3C: 접근성은 모든 사용자가 웹 콘텐츠에 접근하고 상호 작용할 수 있도록 보장하는 중요한 측면입니다. 웹 접근성은 장애를 가진 사용자, 고령자, 비전 및 청각 장애가 있는 사용자, 키보드 사용자 등을 고려하여 웹 콘텐츠가 포용적이고 이용 가능한지를 확인합니다. WCAG는 웹 접근성을 높이기 위한 일련의 표준과 기술을 제공합니다. 텍스트 대체, 키보드 접근성, 명도 대비, 컨텐츠의 가독성, 사용자 인터페이스 컨트롤의 명확성 등을 다룹니다. WCAG는 웹 개발자와 디자이너들이 웹 콘텐츠를 접근 가능하게 만들기 위한 방법과 기술을 제시합니다.
npm을 사용하여 패키지의 여러 버전을 설치하려면 npm 설치 명령 뒤에 패키지 이름과 설치하려는 버전 번호를 사용하면 됩니다. 이 명령을 다른 버전 번호로 반복하여 여러 버전의 패키지를 설치할 수 있습니다.
yarn add react-tooltip-5@npm:react-tooltip@5.8.3
{
"react-tooltip-5": "npm:react-tooltip@5.8.3"
}
https://redux.js.org/usage/writing-tests#unit-testing-individual-functions https://redux-toolkit.js.org/usage/immer-reducers#updating-nested-data https://developers.google.com/apps-script/reference/forms?hl=ko https://developers.google.com/forms/api/reference/rest https://www.101computing.net/minesweeper-in-javascript/
Immer가 생성하는 맵과 세트는 인위적으로 불변으로 만들어집니다. 즉, 프로듀서 외부에서 세트, 클리어 등과 같은 변경 메서드를 시도할 때 예외(throw an exception)가 발생합니다.
test('Map and Set', () => {
const baseMap = new Map();
const nextBaseMap = create(baseMap, (draft) => {
draft.set('a', 1);
});
expect(nextBaseMap).toMatchInlineSnapshot(`
Map {
"a" => 1,
}
`);
});
const { Parser } = require('acorn')
const JSXParser = Parser.extend(require('acorn-jsx')())
const isReactComponent = Boolean(
JSON.stringify(
JSXParser.parse(fileContent, {
sourceType: 'module',
ecmaVersion: 'latest',
})
).includes('JSXIdentifier')
)
모듈 페더레이션과 단일 SPA는 모두 개발자가 독립적으로 개발된 여러 애플리케이션을 단일 시스템에 통합하여 마이크로프론트엔드 및 분리형 애플리케이션을 구축할 수 있도록 지원하는 자바스크립트 라이브러리입니다.
모듈 페더레이션은 개발자가 별도의 웹팩 빌드 간에 모듈을 공유할 수 있도록 하는 웹팩의 기능입니다. 이를 통해 개발자는 애플리케이션을 여러 개의 마이크로프론트엔드로 분할하여 각각 독립적으로 빌드 및 배포하면서도 각 마이크로프론트엔드 간에 기능과 상태를 공유할 수 있습니다. 이는 여러 애플리케이션에서 로드할 수 있는 공유 모듈을 생성하고 각 애플리케이션이 공유 모듈의 내보내기에 액세스할 수 있도록 설정하면 됩니다.
단일 SPA는 독립적으로 개발된 여러 애플리케이션이 하나의 애플리케이션으로 함께 작동할 수 있도록 하는 마이크로프론트엔드 구축을 위한 JavaScript 프레임워크입니다. 개발자가 애플리케이션을 마이크로프론트엔드로 정의하고 런타임에 동적으로 로드 및 통합할 수 있는 일련의 API를 제공하는 방식으로 작동합니다. 이를 통해 개발자는 보다 쉽게 확장하고 유지 관리할 수 있는 복잡하고 분리된 애플리케이션을 구축할 수 있습니다.
모듈 페더레이션과 단일 SPA는 모두 모듈식 분리형 애플리케이션을 구축하기 위한 강력한 도구입니다. 모듈 페더레이션은 독립적인 빌드 간에 모듈을 공유하는 데 더 중점을 두는 반면, 단일 SPA는 독립적으로 개발된 여러 애플리케이션을 단일 시스템으로 통합하는 데 더 중점을 둡니다. 하지만 이 두 가지를 함께 사용하면 더욱 강력하고 유연한 애플리케이션을 만들 수 있습니다.
https://module-federation.github.io/ https://single-spa.js.org/
async function main({ onActivate, onError, onReading, onDenied }) {
const result = await navigator.permissions.query({
name: 'ambient-light-sensor',
})
if (result.state === 'denied') {
onDenied()
return
}
const ambientLightSensor = new AmbientLightSensor({ frequency: 20 })
ambientLightSensor.addEventListener('activate', onActivate)
ambientLightSensor.addEventListener('error', onError)
ambientLightSensor.addEventListener('reading', () => {
const ISO = 100
const C = 250
const EV = Math.round(Math.log2((ambientLightSensor.illuminance * ISO) / C))
onReading(EV)
})
ambientLightSensor.start()
}
- AmbientLightSensor - Web APIs | MDN
- Ambient Light Sensor
- Sensors for the web - Chrome Developers1
- Ambient Light Sensor | Can I use… Support tables for HTML5, CSS3, etc
Footnotes
-
주변광 센서 인터페이스가 포함된 일반 센서 API를 사용하는 방법을 설명 ↩
- Using PDFs with the Jamstack - Building a Document Viewer
- Wrangling Control Over PDFs with the Adobe PDF Embed API | CSS-Tricks
- Introducing Acrobat on the Web, Powered by WebAssembly | by Tapan Anand | Adobe Tech Blog | Medium1
Footnotes
-
Adobe PDF Embed API를 WebAssembly로 구현한 내용을 설명하고 있다. 최근 포토샵의 사례도 그렇고 adobe가 이쪽에 꽤나 공을 들이고 있는 것 같다. ↩
https://github.blog/changelog/2022-06-06-view-commit-history-across-file-renames-and-moves/ https://stackoverflow.com/questions/72165668/losing-history-when-moving-files-with-git
파일 이름이 변경되거나 새 디렉터리로 이동되었지만 콘텐츠의 절반은 그대로인 경우, 커밋 기록은 이제 git log --follow
와 유사하게 파일 이름이 변경되었음을 표시합니다.
https://github.com/brimdata/react-arborist https://github.com/Lodin/react-vtree https://github.com/frontend-collective/react-sortable-tree https://github.com/daweilv/treejs https://github.com/jakezatecky/react-checkbox-tree https://github.com/dgreene1/react-accessible-treeview https://github.com/minop1205/react-dnd-treeview
- 프롬프트 작성을 위한 가이드라인: 모델에 명확하고 구체적인 지침을 작성하는 방법에 대한 원칙을 다룹니다.
- 원칙 1: 명확하고 구체적인 지시사항 작성: 모델이 원하는 출력을 도출할 수 있도록 명확하고 구체적인 지시사항을 제공해야 합니다.
- 원칙 2: 모델에게 ‘생각’할 시간을 주기: 모델이 적절한 출력을 생성하도록 하기 위해 모델에게 충분한 시간을 주는 방법을 소개합니다.
- 프롬프트 전술:
- 전술 1: 입력의 구분을 명확하게 나타내기 위해 구분자 사용: 입력의 구분을 나타내기 위해 구분자를 사용하는 방법에 대해 다룹니다. (```, """, < >,
<tag> </tag>
,:
) - 전술 2: 구조화된 출력 요청: 구조화된 출력을 요청하기 위해 JSON, HTML 등의 형식을 사용하는 방법을 소개합니다.
- 전술 3: 조건 충족 여부 확인을 모델에 요청: 모델에게 조건 충족 여부를 확인하는 것을 요청하는 방법에 대해 다룹니다.
- 전술 4: “피유-샷” 프롬프팅: 모델에게 일관된 스타일로 응답하도록 하는 방법에 대해 다룹니다.
https://www.deeplearning.ai/short-courses/chatgpt-prompt-engineering-for-developers/
Tachyon 작동 방식
Tachyon은 사용자의 웹 브라우저에 내장된 기능을 활용하여 사용자가 <a href="..."></a>
태그에 커서를 50밀리초 이상 올려놓으면 콘텐츠를 미리 로드하는 <link rel="prerender" href="...">
태그를 생성합니다(기본값).
기본적으로 사용자가 링크를 실제로 클릭/탭하기 전에 방문하려는 페이지의 로딩을 시작하도록 브라우저에 지시합니다. 이는 웹 브라우저가 백그라운드에서 준비를 시작하도록 지시합니다.
사용자가 실제로 링크를 클릭하고 다음 페이지로 이동할 준비가 되면 해당 페이지는 이미 준비되어 프레임으로 가져와 페이지 로드 시간이 훨씬 빨라집니다.
이유; 방법
Tachyon은 단순성을 핵심으로 설계되었으며, 이는 결코 우연이 아닙니다. 단순성에 중점을 두었기 때문에 관리자부터 최종 사용자까지 Tachyon을 사용하는 모든 사람이 성능, 확장성, 유지보수성, 보안 및 사용 편의성에서 이점을 누릴 수 있습니다.
다른 대안에 비해 Tachyon이 개선한 주요 사항 중 하나는 일반적인 <link rel="prefetch" href="...">
대신 <link rel="prerender" href="...">
를 사용하여 페이지 로드가 훨씬 빨라졌다는 점입니다. 프리페치는 페이지를 다운로드하기만 하고 프리렌더는 페이지를 다운로드하여 렌더링을 시작한다는 점에서 두 방법의 차이는 자명합니다.
또한 Tachyon은 클릭 가능성이 높은 페이지만 미리 로드하고 사용자의 커서가 링크에서 벗어나면 페이지 미리 로드를 중지하는 등 다른 방식보다 훨씬 효율적이고 방해가 덜 되는 방식으로 프리로딩 동작을 구현합니다. 이것이 바로 제가 Tachyon을 만든 이유이며, 지금까지도 왜 다른 대안이 이 기능을 제공하지 않는지 모르겠습니다. 그 결과, Tachyon은 다른 대안에 비해 사이트에 대역폭 부하를 극히 일부만 추가합니다.
다른 프로젝트보다 기능이 적은 것도 아닙니다(인스턴트 페이지와 가상 기능 동등성 및 몇 가지 추가 기능이 있습니다). 단지 다른 프로젝트보다 간결한 방식으로 구현된 기능일 뿐입니다. 별도의 설정 없이 모바일을 지원하고 화이트리스트, 블랙리스트, 사용자 지정 타이밍 및 동일 출처 제한을 구현하며, 이러한 기능을 사용하기가 훨씬 쉽습니다. 매우 복잡한 기능이 필요한 경우 Tachyon이 최선의 선택이 아닐 수 있지만, 그 외의 모든 사용자에게는 처음부터 최고의 옵션이 될 수 있도록 설계된 Tachyon이 적합합니다.
모바일 웹뷰에서 특정 영역을 zoom할수 있게 해달라는 요청이 있어서 적용한 내역. 정확히 기억은 안나는데 react-prismazoom를 선택했다.
react-prismazoom은 CSS 변환을 사용하여 React에서 확대 및 이동 기능을 제공하는 팬 및 줌 컴포넌트입니다. 이 라이브러리는 prop-types, react, react-dom 모듈에만 의존하며, 데스크톱 및 모바일에서 모두 작동합니다.
주요 기능 및 특징
- 확대 기능 : 마우스 휠이나 두 손가락으로 확대할 수 있습니다. 더블 클릭 또는 더블 탭을 사용하여 확대할 수도 있으며, 선택한 영역을 확대하여 중앙에 배치할 수 있습니다.
- 이동 기능 : 마우스 포인터나 줌 인 상태에서 손가락을 사용하여 이동할 수 있습니다. 확대된 상태에서는 사용 가능한 공간에 따라 직관적으로 이동합니다. 요소를 이동할 수 있는 방향을 나타내기 위해 커서 스타일을 조정합니다.
그 외 비슷한
- How to use transparent videos on the web in 2021 - Rotato
- How to make HEVC, H265 and VP9 videos with an alpha channel for the web | Kit Cross
- Alpha Masking with FFMPEG | Curio Museum1
ffmpeg -framerate 25 -i image_%1d.png -c:v libvpx-vp9 -pix_fmt yuva420p output.webm
단점
- 두벌로 인코딩 작업을 해야한다. MacOS가 아닐경우 번거로운 부분이 존재한다.
- 브라우저 지원이 애매하게 걸친 부분이 존재한다.
- (개인적인 느낌) 사이즈가 커졌을 경우 프레임 드랍이 있다.
…그래서 어차피 안되는거 새로운 도전을 해보고 싶어서 크로마키 효과를 떠올렸다. sharp로 이미지 배경을 green 컬러로 채우고 그 이미지들을 합쳐서 동영상으로 변환. 그리고 canvas에 그리고 색상을 추출해서 green값을 alpha값으로 변환하면 완벽하지 않을까 싶었는데 겹치는 영역을 전혀 생각못했다. 이부분은 뭔가 특정 알고리즘이 있는 것 같은데 그냥 단순히 근사치2로 적용했을때 결과물이 완벽하지는 않다.
- 캔버스(canvas)를 이용한 비디오 조작하기 - Web API | MDN3
- Green Screen in the Browser With HTML Canvas4
- Canvas: Do cool stuff with video in the browser | Mux blog5
Footnotes
const root = createRoot('#confirm-root')
root.render(<Confirm />)
confirm ui를 만들다보면 window.confirm
을 호출하는 방식으로 사용하는게 가장 좋은 방법인데 (안그러면 state
로 관리해야하고 결국 이건 무의미한 코드의 반복이다.)
이걸 react로 구현하려면 결국 render를 사용해야함. react-confirm, react-confirm-alert 둘다 소스를 보면 비슷한 방식으로 접근한다.
deep-object-diff와 비슷한 비교 알고리즘을 구현하는 방법은 다양할 수 있지만, 대표적인 접근 방식은 재귀적으로 객체를 탐색하면서 속성을 비교하는 것입니다. 이를 위해 일반적으로 다음과 같은 과정을 따릅니다:
- 입력으로 받은 두 객체를 비교합니다.
- 첫 번째 객체의 속성을 순회하면서 두 번째 객체에 동일한 속성이 있는지 확인합니다.
- 동일한 속성이 있다면, 해당 속성의 값을 비교합니다.
- 값이 같다면, 두 객체의 해당 속성은 동일하므로 비교를 종료합니다.
- 값이 다르다면, 속성이 변경된 것으로 간주하고 변경된 값을 기록합니다.
- 첫 번째 객체의 속성을 순회하면서 두 번째 객체에 동일한 속성이 없는 경우, 해당 속성은 첫 번째 객체에서 삭제된 것으로 간주합니다.
- 두 번째 객체의 속성을 순회하면서 첫 번째 객체에 동일한 속성이 없는 경우, 해당 속성은 두 번째 객체에 추가된 것으로 간주합니다.
- 만약 속성이 객체나 배열인 경우, 재귀적으로 해당 객체나 배열을 탐색하면서 내부의 속성을 비교합니다.
이러한 과정을 재귀적으로 반복하면서 객체의 모든 속성을 비교하고 차이를 식별합니다. 재귀적으로 탐색하므로 중첩된 객체나 배열에 대해서도 동일한 비교 알고리즘을 적용할 수 있습니다.
이러한 비교 알고리즘을 구현하기 위해 각 언어나 라이브러리는 자체적으로 다양한 방식과 최적화 기법을 사용할 수 있습니다. 그리고 deep-object-diff나 비슷한 도구들은 이러한 알고리즘을 구현하여 사용자에게 편리한 인터페이스를 제공하는 것입니다.
Stacking Workflow는 큰 엔지니어링 작업을 작은 단위의 코드 변경으로 나누어 독립적으로 테스트, 검토 및 병합할 수 있는 프로세스입니다. 스택은 서로 의존하는 일련의 코드 변경으로 이루어집니다.
Why stack: Stacking은 작성자가 검토를 기다리는 동안 블록되지 않고 작업할 수 있게 하며, 더 품질 높은 검토 의견을 유도하고 작은 변경 사항을 빠르게 병합할 수 있게 합니다. 검토어는 작은 규모의 변경 사항을 검토하고, 재검토하는 시간을 줄이며, 무언가 잘못되었을 때 이를 분석하고 롤백할 수 있는 세분성을 갖추게 됩니다.
What is stacking: Stacking은 여러 개의 의존하는 Pull Request(PR)로 이루어진 것입니다. Stacking은 작성자가 빠른 검토를 받을 수 있도록 하고, 높은 품질의 검토 의견을 얻을 수 있게 하며, 작은 변경 사항을 빠르게 병합할 수 있습니다. 검토어는 작은 규모의 변경 사항을 검토하며, 재검토 시간이 줄어들고, 필요한 경우 분석 및 롤백할 수 있는 세분성을 갖게 됩니다.
Stacking vs feature branches: Stacking과 기능 브랜치 모두 트렁크 기반 개발을 가능하게 합니다. 그러나 Stacking은 기능 브랜치와 다른 점이 있는데, 기능 브랜치는 주로 main 브랜치에서 직접 분기되어야 하는 반면, Stacking은 서로 의존하는 PR들 간에 분기될 수 있다는 점입니다. 기능 브랜치는 비대해지고 검토하기 어려워질 수 있지만, 스택은 항상 가볍고 모듈식으로 유지될 수 있습니다. 기능 브랜치는 한꺼번에 검토되고 CI를 통과하고 한 번에 병합되어야 하지만, 스택은 작은 조각으로 검토되고 각각의 브랜치에서 CI를 통과한 후 언제든 병합될 수 있습니다.
Creating a change: Stacking은 이미 브랜치에서 분기하고 서로 의존하는 두 개의 PR을 열었다면 이미 사용한 것입니다. 이 워크플로우는 git에서 기본적으로 지원되며, main 대신에 부모 PR에서 개발을 시작하고 브랜치를 확인하면 됩니다.
Splitting: 일부 개발자는 처음에 큰 변경 사항을 작성하고 나중에 이를 작은 변경 사항의 스택으로 나누는 것을 선호합니다. 이는 최종 코드 구조를 미리 예측하기 어려울 때 도움이 되며, 작은, 독립적으로 검토 가능한 일련의 변경 사항을 제출하고자 할 때 유용합니다.
Updating a change: 스택된 브랜치를 업데이트하는 것은 전통적인 브랜치를 업데이트하는 것과 다르지 않습니다. 유일한 차이점은 스택된 브랜치를 업데이트한 후에는 의존하는 PR의 병합 베이스도 업데이트해야 한다는 것입니다. 변경 사항마다 리베이스하는 것은 불안하게 느껴질 수 있지만, 도구를 사용하면 자동화할 수 있습니다.
Opening and updating pull requests: 검토를 위해 준비되면 스택의 각 브랜치당 하나의 PR을 생성할 수 있습니다. 두 번째 브랜치에서 스택을 만들면 첫 번째 PR을 열 수 있습니다. 자주 변경 사항을 제출하면 PR이 작고 동료들이 쉽게 검토할 수 있습니다.
Merging PRs: 변경 사항이 승인되고 CI를 통과하면 병합할 준비가 됩니다. 스택된 변경 사항은 항상 트렁크로 병합되며, 스택의 하단부터 병합됩니다. PR이 병합되면 의존하는 PR의 베이스 브랜치를 트렁크로 업데이트해야 합니다.
한 마을에 개발자들이 모여 프로젝트를 진행하고 있었습니다. 그 중 한 명의 개발자인 에릭은 팀에서 핵심적인 역할을 맡고 있었고, 그의 전문적인 지식과 능력은 프로젝트의 성공에 큰 영향을 미칠 정도였습니다. 그러나 에릭은 여행을 갈 계획을 세우고 모두에게 알리지 않았습니다.
어느 날, 프로젝트 팀은 예기치 않은 문제에 직면했습니다. 시스템의 일부가 오작동을 일으켜 복구해야 할 상황이었는데, 당연히 에릭이 이를 해결할 수 있었습니다. 그러나 에릭은 이미 여행을 떠나버렸고, 팀은 그를 찾을 수 없었습니다. 에릭이 없는 상태에서는 아무도 그의 전문적인 지식을 대체할 수 없었기 때문에 팀은 큰 혼란에 빠지게 되었습니다.
프로젝트 팀은 에릭의 결석으로 인한 위기를 극복하기 위해 긴급 회의를 열었습니다. 모두가 버스 팩터에 대해 이야기하며, 이 사태로부터 배운 교훈에 대해 논의했습니다. 팀은 이제부터 지식을 공유하고 업무를 분산시키기로 결정했습니다. 각 개발자는 다른 팀원의 역할을 이해하고, 중요한 결정과 지식을 모두가 공유하도록 노력하기로 했습니다.
이제 마을의 개발자들은 에릭 없이도 프로젝트를 진행할 수 있었습니다. 에릭은 멋진 여행을 즐겼지만, 팀은 그의 결석으로 인한 위기를 극복하고 지속 가능한 개발 환경을 조성하는 데 성공했습니다. 그들은 버스 팩터를 기억하며, 이 작은 이야기는 그들에게 프로젝트 관리의 중요성을 상기시켜주었습니다.
https://www.google.com/search?q=%EB%B2%84%EC%8A%A4%ED%8C%A9%ED%84%B0
import { match } from 'ts-pattern'
type Format = 'webp' | 'jpg'
type Params = {
id: string
quality: keyof typeof QUALITY_MAP
format: Format
}
const QUALITY_MAP = {
player_background: '0',
video_frames_start: '1',
video_frames_middle: '2',
video_frames_end: '3',
lowest_quality: 'default',
medium_quality: 'mqdefault',
high_quality: 'hqdefault',
standard_quality: 'sddefault',
unscaled_resolution: 'maxresdefault',
}
const BASE_URL = 'https://i.ytimg.com'
const VI = (format: Format) =>
match(format)
.with('jpg', () => 'vi')
.otherwise(() => ['vi', format].join('_'))
export function getThumbnail({ id, quality, format }: Params) {
return [BASE_URL, VI(format), id, QUALITY_MAP[quality]]
.join('/')
.concat(`.${format}`)
}
SERVICE=wantedspace-dashboard
MINIFIED_PATH_PREFIX=https://dashboard.wantedspace.ai/static/js
VERSION=$(git log --pretty=format:'%h' -n 1);
yarn datadog-ci sourcemaps upload ./build --service $SERVICE --minified-path-prefix $MINIFIED_PATH_PREFIX --release-version $VERSION
rm ./build/static/js/*.map
{
"scripts": {
"zod": "openapi-zod-client -a \"./spec.yaml\" -o \"./spec.ts\"",
"convert": "swagger2openapi ./spec.json -o ./spec.yaml"
},
"dependencies": {
"openapi-zod-client": "^1.6.4",
"orval": "^6.15.0",
"prettier": "^2.8.8",
"swagger2openapi": "^7.0.8"
}
}
https://github.com/ecyrbe/zodios/issues/402 https://www.jacobparis.com/content/type-safe-env
if (err instanceof z.ZodError) {}
if(err instanceof ZodiosError) {
if (err.cause instanceof ZodError) {
console.log(fromZodError(err.cause).toString())
}
}
type Props = {
popover: 'auto' | 'manual'
popovertarget: string
popovertargetaction: 'hide' | 'show' | 'toggle'
}
type State = {
hasBackdrop: boolean
isPopoverOpen: boolean
}
type Methods = {
hidePopover: () => void
showPopover: () => void
togglePopover: () => void
}
type Events = {
beforetoggle: () => void
toggle: () => void
}
import z from 'zod'
const envSchema = z.object({
REACT_APP_FEATURE_VAC_ASK: z.string(),
REACT_APP_FEATURE_RECORDS: z.string(),
NODE_ENV: z.enum(['development', 'test', 'production']).default('development'),
})
const windowSchema = z.object({
SOMETHING_COOL: z.string()
})
export const ENV = envSchema.parse(process.env)
export const WINDOW = windowSchema.parse(window)
enum LogLevel {
DEBUG = 'debug',
INFO = 'info',
WARN = 'warn',
ERROR = 'error',
}
type Message = string
class Logger {
private level: LogLevel
constructor(level: LogLevel = LogLevel.DEBUG) {
this.level = level
}
private log(level: LogLevel, message: Message) {
if (this.level === LogLevel.DEBUG || level !== LogLevel.DEBUG) {
const label = level.toUpperCase()
console.log(`[${label}] ${message}`)
}
}
/**
* - 개발 혹은 테스트 단계
* - 운영 환경에서는 남기고 싶지 않은 로그 메세지
*/
public debug(message: Message) {
this.log(LogLevel.DEBUG, message)
}
/**
* - 정상 작동에 대한 정보
* - 시스템을 파악하는데 유익한 정보
*/
public info(message: Message) {
this.log(LogLevel.INFO, message)
}
/**
* - 잠재적으로 문제가 될 수 있는 상황
* - 언제든 발생할 수 있는 일반적인 문제 상황
* - 사용자에게 노출되는 메세지에 상세한 가이드가 필요
*/
public warn(message: Message) {
this.log(LogLevel.WARN, message)
}
/**
* - 심각한 오류나 예외 상황
* - 즉시 조치가 필요할때
*/
public error(message: Message) {
this.log(LogLevel.ERROR, message)
}
}
지속적인 폴링에 의존하지 않고 SPA를 업데이트하고 다시 로드하기 위한 다양한 기술과 전략에 대해 설명합니다. CloudFront와 같은 콘텐츠 전송 네트워크(CDN)를 API 프록시로 활용하여 SPA를 무효화하고, 새 콘텐츠를 사용할 수 있을 때 사용자가 애플리케이션을 새로 고치라는 메시지를 구현하고, 캐시 버스팅 기술과 서비스 워커를 사용하는 등의 방법을 살펴봅니다.
- Using a CloudFront API Proxy to Invalidate a Single-Page Application Without Polling
- Getting clients to reload your latest single page application version - Codemzy’s Blog
- https://twitter.com/housecor/status/1669722918392156162?s=20
- https://github.com/coryhouse/vite-pwa-app
- https://vite-pwa-org.netlify.app/
class CustomError extends Error {
name = 'CustomError'
constructor(message?: string) {
super(message)
Object.setPrototypeOf(this, CustomError.prototype)
}
}
try {
throw new CustomError('This is a custom error message.')
} catch (error) {
if (error instanceof CustomError) {
console.log('CustomError occurred:', error.message)
console.log('Error name:', error.name)
} else {
console.log('An error occurred:', error)
}
}
import {
addDays,
addMonths,
eachDayOfInterval,
eachWeekOfInterval,
startOfMonth,
} from 'date-fns'
const startOfMonthDate = startOfMonth(new Date())
const matrix = eachWeekOfInterval({
start: startOfMonthDate,
end: addMonths(startOfMonthDate, 1),
}).map((weekDay) => {
const startDate = new Date(weekDay)
return eachDayOfInterval({
start: startDate,
end: addDays(startDate, 6),
})
})
configureWebpack: {
module: {
rules: [
{
test: /\.mjs$/,
include: /node_modules/,
type: 'javascript/auto',
},
],
},
},
const bodyReadingMethods = ['arrayBuffer', 'blob', 'formData', 'text', 'json']
bodyReadingMethods.forEach((methodName) => {
request[methodName] = new Proxy(request[methodName], {
apply(...args) {
console.trace(`Premature "request.${methodName}" call!`)
return Reflect.apply(...args)
},
})
})
-
https://dev.to/this-is-learning/the-evolution-of-signals-in-javascript-8ob
-
https://dev.to/this-is-learning/react-vs-signals-10-years-later-3k71
-
https://www.lksh.dev/blog/writing-your-own-reactive-signal-library/
-
https://frontendmasters.com/blog/vanilla-javascript-reactivity/
-
https://dev.to/this-is-learning/thinking-locally-with-signals-3b7h
[data-scope='slider'][data-part='thumb']
[data-scope='slider'][data-part='track']
[data-scope='slider'][data-part='control']
Ark UI의 각 컴포넌트 파트는
data-scope
및data-part
속성으로 지정됩니다.data-scope
속성은 컴포넌트를 식별하고,data-part
속성은 컴포넌트의 개별 부분을 지정합니다.
ark-ui에서 사용하는 방식인데 적용해볼만한 컨셉이라고 생각한다.
360 이미지를 드래그해서 회전시키는 기능을 개발할때 3d 리소스를 제공 받을 수 있다면 three.js 같은 라이브러리를 사용해서 쉽게(?) 구현이 가능하다. three.js가 초반 진입 장벽이 높은 것 같지만 단순히 모델 리소스를 보여주는 정도는 배경지식이나 기본지식이 없더라도 어느정도 예제 코드들을 본다면 구현이 가능하다고 본다. 물론 제대로 하고 싶다면 배경지식과 three.js 자체에 대한 학습이 필요.
그런데 부득이하게 여러장으로 된 이미지만 제공 받을 수 있다면 직접 구현해야한다. 그런데 생각보다 드래그 기능을 처음부터 구현한다는게 보통의 개발자들에게는 불편한게 사실이므로 이미 존재하는 플러그인이나 라이브러리를 사용하는게 현실적이다.
그래서 찾아본 gsap의 Draggable. 그런데 문서를 보면 다이얼 같이 (뭐라 표현하는게 적당한지 모르겠지만) 직접적으로 드래그 하는 관점의 설명들이 대부분이다. 그런데 360 이미지 같은 경우는 직접적으로 드래그 라기보다는 액션을 빌린 동작이어서 처음에 약간 혼란스러웠는데 좀 더 찾아보니 proxy의 개념을 빌려서 설명된 부분이 있었고 저런식으로 하면 쉽게 구현이 가능하다.
참고
- Advanced tutorial: CubeDial, a 3D Carousel made with GSAP - Blog - GreenSock
- https://codepen.io/GreenSock/pen/nHpec/
- https://codepen.io/GreenSock/pen/LVEXrB
- https://codepen.io/jamiejefferson/pen/kFhKE/
반대 방향으로 진행시 계산법
로컬에서만 실행 해야하는 프로젝트가 있길래 아무래도 사전설정 같은 귀찮은 문제가 있으니 pkg를 사용하면 좋을 것 같아서 들어가봤는데 개발이 중단 되었다.
노드 21버젼에서 해당 기능이 지원되는데 재미있는 시도들이 많이 나왔으면 좋겠다.
Astro를 사용한 프로젝트에서 빌드를 하는데 JavaScript heap out of memory 에러가 발생했다.
쉽게 해결하자면 NODE_OPTIONS=--max_old_space_size
(in megabytes) 설정해서 우회할수는 있겠지만 정리가 필요해서 메모를 남겨본다.
일단 원인은 파일사이즈가 크다는 점, 그리고 카테고리(국가)별 데이터가 많다는 점인데 개선할만한 부분은 두가지 정도인 듯.
- 가능한 전처리해서 데이터를 다시 생성해서 참조
Astro.glob
을 사용해서 조건부로 데이터를 가져오기
예상치 못한 에러였는데 역시 트레이드오프는 존재하기 마련이다.
- Cornerstone.js HTML5 canvas를 지원하는 웹 브라우저에서 가벼운 의료 이미지 표시에 사용되는 JavaScript 라이브러리
- OpenSeadragon 데스크톱 및 모바일에서 사용 가능한 고해상도 줌 가능한 이미지를 위한 순수 JavaScript 뷰어
- dicomParser DICOM 파일을 처리하여 의료 이미지 데이터를 JavaScript 객체로 변환하는 라이브러리
- AMI Medical Imaging (AMI) 웹에서 의료 영상을 효과적으로 표시하고 주석을 추가하는 데 사용되는 JavaScript 라이브러리
- OHIF Medical Imaging Viewer
- itk-wasm 의료 영상 처리를 위한 JavaScript 라이브러리
- Brainchop
예전에 관련 회사 기술 블로그 보다가 생각나서 찾아본 라이브러리 리스트. 대용량, 의학용 이미지 포멧을 다루기 위한 도구들.
as
로 간단하게 처리가 가능할정도의 복잡도라면 괜찮다고 보는데 더 복잡하게 된다면 asChild
가 최선의 방법.
function Button({ asChild, ...props }) {
const Comp = asChild ? Slot : 'button'
return <Comp {...props} />
}
Grid View
grid view(또는 datagrid)는 데이터의 표 형식 보기를 제공하는 그래픽 제어 요소입니다.
일반적으로 다음 중 일부 또는 전부를 지원합니다.
- 열 머리글을 클릭하여 그리드의 정렬 순서 변경
- 열 머리글을 끌어서 크기 및 순서 변경
- 보기 데이터의 제자리 편집
- 행과 열 구분 기호 및 행 배경색 번갈아 지정하기
자연어 날짜 처리와 접근성 높은 데이트피커 - chrono와 inclusive-dates
chrono는 다양한 형식의 날짜/시간을 처리하고 주어진 텍스트에서 정보를 추출할 수 있도록 설계된 자연어 날짜 파서.
- “Today”, “Tomorrow”, “Yesterday”, “Last Friday” 등의 상대적 날짜 처리
- “17 August 2013 - 19 August 2013”와 같은 날짜 범위 처리
- “This Friday from 13:00 - 16.00”와 같은 시간 포함 날짜 처리
- “5 days ago”, “2 weeks from now”와 같은 상대적 시간 표현 처리
- “Sat Aug 17 2013 18:40:39 GMT+0900 (JST)“와 같은 표준 날짜 형식 처리
- “2014-11-30T08:15:30-05:30”와 같은 ISO 8601 형식 처리
parse(text: string, referenceDate?: ParsingReference | Date, option?: ParsingOption): ParsedResult[] {
// 1. 파싱 컨텍스트 생성
// 2. 모든 파서를 실행하고 결과 수집
// 3. 결과를 인덱스 기준으로 정렬
// 4. 모든 리파이너를 적용하여 결과 개선
// 5. 최종 결과 반환
}
inclusive-dates는 자연어 입력을 지원하는 사용자 친화적이고 완전히 접근 가능한 데이트피커. 내부적으로 chrono를 사용하여 자연어 날짜 입력을 처리. 이는 두 라이브러리의 장점을 결합한 좋은 예시.
const parsedDate = await chronoParseDate(text, {
locale: this.locale.slice(0, 2),
minDate: this.minDate,
maxDate: this.minDate,
referenceDate: removeTimezoneOffset(new Date(this.referenceDate)),
...chronoOptions,
})
- chrono의 강력한 자연어 날짜 파싱 능력을 활용.
- inclusive-dates는 이를 사용자 친화적이고 접근성 높은 UI 컴포넌트로 구현.
이러한 조합을 통해, 개발자들은 사용자에게 직관적이고 유연한 날짜 입력 방식을 제공하면서도 접근성과 사용성을 높일 수 있음.
Footnotes
Google Sheets API를 활용하기 위해서는 Google Cloud Platform(GCP)에서 필요한 설정을 하고, google-spreadsheet 라이브러리를 통해 스프레드시트를 조작할 수 있습니다. 다음은 이를 위한 단계별 가이드입니다.
- 서비스 계정 설정
- Google Cloud 프로젝트 생성
- Google Sheets API 활성화
- API 자격 증명 생성
- 서비스 계정 생성
- 서비스 계정 키 생성
- 환경 변수 설정
Next.js의 Route Loader와 Mini CSS Extract Plugin 살펴보기
Next.js의 router-loader.ts #prefetchViaDom()
function prefetchViaDom(
href: string,
as: string,
link?: HTMLLinkElement
): Promise<any> {
return new Promise<void>((resolve, reject) => {
const selector = `
link[rel="prefetch"][href^="${href}"],
link[rel="preload"][href^="${href}"],
script[src^="${href}"]`
if (document.querySelector(selector)) {
return resolve()
}
link = document.createElement('link')
// The order of property assignment here is intentional:
if (as) link!.as = as
link!.rel = `prefetch`
link!.crossOrigin = process.env.__NEXT_CROSS_ORIGIN!
link!.onload = resolve as any
link!.onerror = () =>
reject(markAssetError(new Error(`Failed to prefetch: ${href}`)))
// `href` should always be last:
link!.href = href
document.head.appendChild(link)
})
}
이 함수는 미리 가져오는(prefetch) 역할을 하며 주요 특징은 다음과 같다.
- 이미 prefetch 또는 preload된 리소스인지 확인.
- 새로운 요소를 생성하여 리소스를 prefetch.
- 로드 성공 또는 실패 시 적절한 처리.
Webpack의 Mini CSS Extract Plugin에서 CSS 청크 로드 실패 시 에러 처리 코드를 볼 수 있다.
Template.indent([
"var errorType = event && event.type;",
"var realHref = event && event.target && event.target.href || fullhref;",
'var err = new Error("Loading CSS chunk " + chunkId + " failed.\\n(" + errorType + ": " + realHref + ")");',
'err.name = "ChunkLoadError";',
// TODO remove `code` in the future major release to align with webpack
'err.code = "CSS_CHUNK_LOAD_FAILED";',
"err.type = errorType;",
"err.request = realHref;",
"if (linkTag.parentNode) linkTag.parentNode.removeChild(linkTag)",
"reject(err);",
]),
이 코드는 CSS 청크 로드 실패 시 발생하는 에러를 처리하며 주요 특징은 다음과 같다.
- 에러 타입과 실제 URL을 파악.
- 상세한 에러 메시지를 생성.
- 에러 객체에 추가 정보(name, code, type, request)를 설정.
- 실패한
<link>
태그를 DOM에서 제거. Promise
를reject
하여 에러를 전파.
이 플러그인은 CSS를 별도의 파일로 추출하는 데 사용되며, 위 코드는 그 과정에서 발생할 수 있는 오류를 처리하는 중요한 부분.
if (!data) {
throw fetch()
}
- 컴포넌트가 렌더링될 때, 비동기 작업(예: 데이터 패칭)이 시작됩니다. 이 작업은 일반적으로 promise를 반환합니다.
- 비동기 작업이 완료되지 않은 경우, 컴포넌트는 promise를 던집니다. 이는 JavaScript에서 예외를 던지는 것과 유사합니다. Suspense는 promise가 던져질 때 이를 캐치하고 fallback UI를 표시하는 역할을 합니다.
- React는 컴포넌트가 promise를 던졌을 때 이를 감지하고, Suspense 컴포넌트에서 이를 “캐치”합니다. Suspense는 이 promise가 해결될 때까지 대체 UI (fallback)를 렌더링합니다. Concurrent Mode에서는 React가 이 promise를 추적하고, 비동기 작업이 완료될 때까지 렌더링을 중단합니다.
- Promise가 해결되면(즉, 비동기 작업이 완료되면) React는 컴포넌트를 다시 렌더링합니다. Suspense는 현재 데이터 패칭 라이브러리(예: React Query, SWR)와 함께 사용되어 비동기 작업의 상태를 쉽게 관리할 수 있도록 도와줍니다.
- https://github.com/facebook/react/blob/main/packages/react/src/ReactLazy.js#L119C19-L119C26
- https://github.com/TanStack/query/blob/main/packages/react-query/src/suspense.ts#L62
- https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Statements/throw
- https://jser.pro/ddir/rie?reactVersion=18.3.1&codeKey=ud62nsxll29yy0dzba8
최근에 Enzyme을 제거하고 React Testing Library로 교체하는 작업을 진행했습니다. JSX 영역은 별 문제 없이 진행되었으나, state나 props를 다루는 구현 부분에서 약간의 애매함이 있었습니다. 개인적으로는 기존 접근 방식이 나쁘지 않았다고 생각했기 때문에, 새로운 접근 방식으로 전환하는 데 주저하게 되었습니다.
결론적으로, state와 props의 JSON 결과물을 렌더링하고 getByTestId를 사용하여 이를 참조하는 방식으로 테스트를 진행하기로 했습니다. 다음은 그 예시입니다:
render() {
return <>
{process.env.NODE_ENV === 'test' && (
<pre data-testid="ThumbnailDebug">
{JSON.stringify({
props: this.props,
state: this.state,
})}
</pre>
)}
</>
}
이 방법을 통해 Enzyme을 제거한다는 목표를 달성했기 때문에, 어느 정도는 해결된 것처럼 보이며, 추후 더 나은 테스트 코드를 작성하기 위한 고민을 할 수 있을 것 같습니다.
React Router의 Link 컴포넌트 테스트하기
React 애플리케이션에서 라우팅은 중요한 부분. 특히, 사용자가 링크를 클릭할 때 적절한 페이지로 이동하는지 테스트하는 것은 중요. 다음은 React Router의 <Link />
컴포넌트를 테스트하는 코드.
describe('Link', () => {
it('이동', () => {
const Home = () => <Link to="/about">About</Link>
const About = () => <h1>About</h1>
const { getByRole } = render(
<MemoryRouter initialEntries={['/']}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</MemoryRouter>
)
fireEvent.click(getByRole('link'))
expect(screen.getByRole('heading')).toHaveTextContent('About')
})
})
가끔 <Link />
도 테스트해야 할 때가 있다. 위 코드는 사용자가 “About” 링크를 클릭하면 “About” 페이지로 이동하는지 테스트. fireEvent.click을 사용해 링크 클릭 이벤트를 발생시키고, screen.getByRole을 통해 헤딩 요소의 텍스트가 ‘About’인지 확인. 이 테스트를 통해 라우팅이 제대로 동작하는지 확인할 수 있음.
Avoiding premature abstraction with Unstyled React Components (buildui.com)
React 컴포넌트를 작성할 때, 불필요한 추상화를 피하고 컴포넌트의 유연성을 유지하는 방법. 특히 스타일이 없는 컴포넌트를 통해 어떻게 컴포넌트의 기능에 집중할 수 있는지를 설명.
import React, { ComponentProps, FC, ReactNode } from 'react'
function Spinner() {
return (
<span className="absolute inset-0 flex items-center justify-center">
`<Spinner />`
</span>
)
}
type LabelProps = ComponentProps<'span'>
function Label({ children, ...rest }: LabelProps) {
return <span {...rest}>{children}</span>
}
type LoadingButtonProps = ComponentProps<'button'> & {
children: FC | ReactNode
}
/**
* @description 버튼이 비활성화되었을 때 로딩 스피너를 표시할 수 있는 버튼 컴포넌트입니다. 이 컴포넌트는 children 속성으로 함수나 React 노드를 받을 수 있으며, 버튼이 비활성화되면 스피너가 표시되고, 텍스트는 보이지 않게 됩니다.
*/
function LoadingButton({
disabled,
className,
children,
...rest
}: LoadingButtonProps) {
return (
<button {...rest} className={`${className} relative`} disabled={disabled}>
{typeof children === 'function' ? (
children({})
) : (
<>
{disabled && <Spinner />}
<Label className={disabled ? 'invisible' : ''}>{children}</Label>
</>
)}
</button>
)
}
LoadingButton.Spinner = Spinner
LoadingButton.Label = Label
이 패턴은 컴포넌트를 작성할 때 불필요한 스타일링이나 구조를 미리 정의하지 않고, 각 컴포넌트가 자신의 역할에 충실할 수 있도록 도와줍니다. 이를 통해 코드의 유연성을 유지하고, 필요에 따라 컴포넌트를 확장하거나 수정할 수 있는 여지를 남겨두게 됩니다.
Excel View1 라이브러리를 보던 중, Excel과 Node의 상호작용에 대한 궁금증이 생겼다. 아무래도 웹개발을 하다보니 어플리케이션과 통신할 수 있는 부분에 대해서 전혀 생각을 안했었다는 걸 깨닫고 이러한 부분을 보완하기 위해 Excel과의 통신 방식을 찾아봄.
우선, 위 라이브러리 코드를 통해 ActiveXObject('Excel.Application')
2로 Excel 객체를 생성하고, 이를 통해 Excel의 다양한 기능에 접근할 수 있다는 것을 확인했다. 그리고 node-activex의 문서에서 링크를 통해 추가적인 정보들을 확인할 수 있었는데 아무래도 자주 보던 영역이 아니라 일단 확인만 하는 단계에서 멈춤.
이 접근을 통해, Excel과 상호작용하는 방법에 대해 실마리를 찾을 수 있었음. 그러나 모든 과정이 순조롭지만은 않은게. ActiveXObject
를 사용하는 부분에서 개념적 이해가 부족했고, 그밖에 Excel의 객체모델에 대해서도 배경지식이 많이 부족하다는 걸 알게됨.
이번 경험을 통해, 웹 개발자가 어플리케이션 레벨의 개념들도 이해하면 좋겠다는 생각을 하게 됨. 나중에 기회가 되면 살펴보고 일단 view 기능을 스프레드시트로 구현해 봐야겠다.
Footnotes
-
Excel에서 긴 줄을 탐색하는 데 도움이 되는 도구입니다. 활성 셀을 추적하고 전체 행 데이터를 자동으로 가져와 별도의 창에 표시합니다. 이를 통해 좌우로 스크롤하지 않고도 행의 모든 열을 쉽게 볼 수 있습니다. ↩
-
https://github.com/timepp/excelview/blob/master/excel.js#L70 ↩
대화 상자(Dialog Box)는 사용자에게 정보를 전달하고 응답을 요청하는 그래픽 제어 요소이다. Dialog box
Modal
대화 상자를 연 소프트웨어와의 상호작용을 차단합니다.
- System Modal - 이 대화 상자를 닫기 전까지 다른 작업을 할 수 없게 하며, 과거 단일 작업 시스템에서 주로 사용되었습니다.
- Application Modal - 프로그램을 일시적으로 중단시키며, 대화 상자가 닫히기 전까지 다른 작업을 할 수 없습니다. 이는 워크플로우를 방해하거나 사용자 오류를 초래할 수 있어 종종 비판받습니다.
- Document Modal - 부모 창만 차단하며, 다른 창에서 작업을 계속할 수 있습니다. macOS에서 주로 사용되며, 부모 창에 연결된 시트 형태로 나타납니다.
Modeless
소프트웨어의 다른 부분과의 상호작용을 허용합니다. 이 대화 상자는 소프트웨어와의 상호작용을 차단하지 않으며, 대화 상자가 열려 있는 동안에도 사용자가 작업을 계속할 수 있습니다. 툴바가 모델리스 대화 상자의 예입니다.
고려 사항
- 모달 대화 상자의 문제점 - 모달 대화 상자는 사용자 흐름을 방해하고, 반복적인 사용으로 인해 사용자가 실수로 잘못된 선택을 하게 만들 수 있습니다. 사용자는 습관적으로 확인을 누르는 경향이 있으며, 이는 작업 손실로 이어질 수 있습니다.
- 경고 대신 실행 취소 - 경고 메시지로 실수를 방지하려는 접근은 한계가 있습니다. 경고를 더 강하게 만들어도 사용자는 이를 빠르게 무시하고 실수를 반복할 수 있습니다. 대신, 실행 취소(Undo) 기능을 제공하여 사용자가 언제든지 실수를 되돌릴 수 있도록 하는 것이 중요합니다. 이는 사용자의 스트레스를 줄이고 더 나은 사용자 경험을 제공합니다.
- 인간 중심의 디자인 - 소프트웨어 디자인은 사용자의 습관을 존중해야 합니다. 사용자가 실수를 하더라도 복구할 수 있는 실행 취소 기능을 제공하는 것이 인간 중심의 디자인입니다. 경고를 사용하는 대신, 실행 취소를 제공하라는 원칙이 바람직한 디자인 방향입니다.1
Footnotes
핵심 요점
-
비순수한 컴포넌트
new Date()
와 같은 비순수한 함수는 호출할 때마다 다른 결과를 선택하므로, 테스트 결과의 일관성이 없음
-
해결책 - 의존성 주입
- 비순수한 함수의 결과를 prop으로 전달하여 컴포넌트를 예측 가능하게 만들고, 테스트 시 특정 날짜를 제어
-
기본 매개변수
- 기본 매개변수 사용으로 오늘 날짜의 기본 동작을 유지하면서, 테스트에서 유연성을 제공
개선된 예시
function Date({ date = new Date() }) {
const [date, setDate] = React.useState(date)
return (
<input
type="date"
onChange={(e) => setDate(e.target.value)}
defaultValue={date}
/>
)
}
function Random({ randomizer = Math.random }) {
const [state, setState] = React.useState(randomizer())
return <div>{state}</div>
}
테스트
describe('Date Component', () => {
it('should update state on date change', () => {
render(<Date date={new Date('2023-01-01')} />)
const input = screen.getByRole('textbox')
expect(input.value).toBe('2023-01-01')
})
})
describe('Random Component', () => {
it('should render a random number', () => {
const mockRandomizer = () => 0.5
render(<Random randomizer={mockRandomizer} />)
const div = screen.getByText('0.5')
expect(div).toBeInTheDocument()
})
})
vitest와 jest에서 가짜 타이머를 사용하는 방법. 내부적으로는 @sinonjs/fake-timers를 사용. vitest와 jest 모두 시스템 시간을 조작할 수 있도록 하여 테스트가 일관되게 실행되도록 하고 다양한 시간과 날짜를 시뮬레이션하여 다양한 조건에서 코드가 예상대로 작동하는지 확인할 수 있음.
describe('isSameDate', () => {
beforeEach(() => {
vi.useFakeTimers()
vi.setSystemTime('2024-07-20')
})
afterEach(() => {
vi.useRealTimers()
})
it('현재 날짜와 동일한 날짜를 전달하면 true를 반환한다', () => {
expect(isSameDate('2024-07-20')).toBe(true)
})
it('현재 날짜와 다른 날짜를 전달하면 false를 반환한다', () => {
expect(isSameDate('2024-07-19')).toBe(false)
expect(isSameDate('2024-07-21')).toBe(false)
})
})
button
또는 a
요소를 선택적으로 렌더링할 수 있는 ButtonOrLink
컴포넌트를 구현한 부분. props로 전달된 as
값에 따라 해당 태그를 렌더링하며, React.JSX.IntrinsicElements
의 타입을 활용하여 각 태그에 맞는 props를 받을 수 있도록 한다.
import * as React from 'react'
type ComponentPropsWithAs<T extends keyof React.JSX.IntrinsicElements> = {
as: T
} & React.ComponentProps<T>
type ButtonOrLinkProps =
| ComponentPropsWithAs<'a'>
| ComponentPropsWithAs<'button'>
export function ButtonOrLink(props: ButtonOrLinkProps) {
switch (props.as) {
case 'a':
return <a {...props} />
case 'button':
return <button {...props} />
default:
return null
}
}
vitest에서 사용자 지정 assertion을 추가하여 Zod 스키마와 Response 객체를 비교하기
import { expect } from 'vitest'
import type { ZodTypeAny } from 'zod'
expect.extend({
/**
* @param received 테스트할 Response 객체
* @param schema 검증할 Zod 스키마
*/
async toMatchSchema(received: Response, schema: ZodTypeAny) {
const response = await received.json()
const result = await schema.safeParseAsync(response)
return {
message: () => '',
pass: result.success,
} satisfies ExpectationResult
},
})
vitest.d.ts
파일에서 CustomMatchers 인터페이스를 확장하여 TypeScript와의 통합성을 유지.
import type { ZodTypeAny } from 'zod'
interface CustomMatchers<R = unknown> {
toMatchSchema(schema: ZodTypeAny): Promise<R>
}
todoResponse
의 응답 데이터가 todoSchema
에 정의된 Zod 스키마와 일치하는지 확인한다.
test('todo', async () => {
expect(todoResponse.ok).toBeTruthy()
expect(todoResponse).toMatchSchema(todoSchema)
})
💡Blob URL 방식은 Firefox 58+에서 CSP 제한이 있는 페이지에서도 스크립트를 주입할 수 있는 방법입니다. 이 방식을 사용할 때, 비동기성 때문에 발생할 수 있는 문제를 방지하려면 초기화 코드가 준비된 후 스크립트를 전달해야 합니다.
const b = new Blob([script], { type: 'text/javascript' })
const u = URL.createObjectURL(b)
const s = document.createElement('script')
s.src = u
document.body.appendChild(s)
document.body.removeChild(s)
URL.revokeObjectURL(u)
💡이 패턴이 “상태 머신”처럼 들린다면, 그리 놀랄 일도 아닙니다. 결국, 선택의 문제는 상태 머신을 구축할지 말지가 아니라, 그것을 암시적으로 구축할지 명시적으로 구축할지에 달려 있습니다.
stateDiagram-v2
[*] --> Mounting
Mounting --> AwaitingEmailInput
Mounting --> AwaitingCodeInput
AwaitingEmailInput --> SubmittingEmail
SubmittingEmail --> AwaitingCodeInput
SubmittingEmail --> AwaitingEmailInput
AwaitingCodeInput --> SubmittingCode
SubmittingCode --> Success
SubmittingCode --> AwaitingCodeInput
Success --> [*]
변수의 prefix는 역할과 의미를 명확히 하기 위해 사용된다.
/**
* 초기값을 나타내며, 상태나 값이 처음 설정될 때 사용된다.
* 적합한 상황: 프로그램이 시작되거나 객체가 처음 생성될 때의 값을 정의한다.
*/
const initialCount = 0 // 카운터의 초기값
const initialState = { loggedIn: false, user: null } // 초기 상태
const initialPosition = { x: 0, y: 0 } // 초기 좌표
/**
* 기본값으로 널리 사용되는 표준 값이다.
* 적합한 상황: 값이 없을 경우 사용할 기본값을 정의한다.
*/
const defaultTheme = 'light' // 기본 테마
const defaultUser = { name: 'Guest', role: 'viewer' } // 기본 사용자
const defaultPageSize = 20 // 페이지당 기본 항목 수
/**
* 기준값 또는 다른 값의 참조점이 되는 값이다.
* 적합한 상황: 값을 계산하거나 파생할 때 기준이 되는 값을 정의한다.
*/
const baseSalary = 3000 // 기준 급여
const baseUrl = 'https://api.example.com' // API의 기준 URL
const baseColor = '#FFFFFF' // 기준 색상
/**
* 변경되기 전 원래 상태 또는 초기 상태를 강조한다.
* 적합한 상황: 데이터를 변경하기 전에 원래 값을 유지해야 할 때 사용한다.
*/
const originalText = 'Hello World' // 변경 전 텍스트
const originalSettings = { theme: 'dark', notifications: true } // 원래 설정값
const originalImage = image.clone() // 원본 이미지 복사
사용 가이드
-
initial*
vsdefault*
:initial*
은 주로 초기 상태를 강조하며, 값이 재설정될 가능성이 적다.default*
는 기본값으로 자주 사용되며 필요에 따라 다른 값으로 교체될 수 있다.
-
base*
vsoriginal*
:base*
는 기준값으로 사용되어 다른 값의 비교나 계산에 활용된다.original*
은 원래 상태를 보존하거나 복구를 위해 필요할 때 사용된다.
const color =
(isUnchecked && 'palette.label.alternative') ||
(isChecked && 'palette.primary.normal') ||
'palette.label.normal'
기존 접근과 문제점
기존 접근
- 중첩된 삼항 연산자로 상태에 따라 값을 반환하는 코드 작성.
- 간결하지만 가독성이 떨어지고, 상태가 늘어나면 유지보수가 어려워짐.
문법적 접근
if
문,switch
문, 객체 매핑,&&
와||
논리 연산자 등 다양한 문법으로 해결 가능.- 하지만 이는 상태 관리의 본질적인 문제를 해결하지 못할 수 있음.
핵심 문제
status
모델을 어떻게 정의하고 참조할 것인가?- 상태의 의미를 명확히 표현할 수 있어야 함.
- 상태와 그에 따른 동작이 쉽게 확장 가능해야 함.
- 코드의 가독성과 유지보수성을 높여야 함.
class StatusManager {
static Status = {
UNCHECKED: 'UNCHECKED',
CHECKED: 'CHECKED',
DEFAULT: 'DEFAULT',
} as const
static getColor(status: keyof typeof this.Status) {
switch (status) {
case this.Status.UNCHECKED:
return 'palette.label.alternative'
case this.Status.CHECKED:
return 'palette.primary.normal'
default:
return 'palette.label.normal'
}
}
}
const color = StatusManager.getColor(StatusManager.Status.CHECKED)
타이머를 제어하기 위해 vi.useFakeTimers
와 vi.advanceTimersByTime
을 사용하기.
describe('Countdown 컴포넌트', () => {
beforeEach(() => {
vi.useFakeTimers()
})
afterEach(() => {
vi.useRealTimers()
})
test('타이머가 동작하며 시간을 업데이트한다.', () => {
const endTime = new Date(Date.now() + 60000).toISOString()
const renderChild = vi.fn(({ targetRef }) => (
<div ref={targetRef} data-testid="countdown" />
))
render(<Countdown end_at={endTime}>{renderChild}</Countdown>)
expect(screen.getByTestId('countdown')).toBeEmptyDOMElement()
act(() => {
vi.advanceTimersByTime(10000)
})
expect(screen.getByTestId('countdown')).toHaveTextContent('00:00:50')
act(() => {
vi.advanceTimersByTime(50000)
})
expect(screen.getByTestId('countdown')).toHaveTextContent('00:00:00')
})
})
React 내부 동작 및 렌더링
- Will it render?
- When does React render your component?
- Preemptive memoization in React is probably not Evil (yet)
- React ref Callback Use Cases | JulesBlom.com
- Guide to React Suspense and use hook for busy bees · OlegWock
- How to Memoize with React.useMemo()
- Synchronizing State In React
- useContextSelector: Speeding Up React Apps With Large Context
- Learn Suspense by Building a Suspense-Enabled Library :: Building Better Software Slower
- React 파이버 아키텍처 분석
- Creating a useState hook from scratch - DEV Community
- react/packages/react/src/ReactHooks.js at main · facebook/react · GitHub (Line 30)
- react/packages/react/src/ReactHooks.js at main · facebook/react · GitHub (Line 93)
React 패턴 및 설계 원칙
- Lessons On Writing JavaScript and React From ClojureScript
- Delightful React File/Directory Structure
- Clean Architecture in React | Alex Kondov - Software Engineer
- Partially Controlled Components: A Declarative Design Pattern in React
- The only two custom React hooks we ever really use | Molecule.dev Blog
- Robin Weser | Clean React with TypeScript
- Types of React Components [2024]
- useEncapsulation | Kyle Shevlin
- Multipart Namespace Components: Addressing RSC and Dot Notation Issues | isBatak
React 최적화 및 성능 개선
- Use ternaries rather than && in JSX
- (번역) 실제 코드에서 리액트 컴파일러의 성능 | emewjin.log
- How To Improve INP: React⚛️ | Jacob ‘Kurt’ Groß
React 기반의 구현 실습
- Build your own React
- Implementing React from scratch
- Build Your Own React.js in 400 Lines of Code
- Building a Simple Virtual DOM from Scratch - DEV Community
- Building a Custom React Renderer | Hanna.Dev
- GitHub - sophiebits/react-dom-mini: Toy React renderer from my React Conf 2019 talk, “Building a Custom React Renderer”
- react/packages/react/src/ReactClient.js at main · facebook/react · GitHub
- GitHub - lazarv/react-server: The easiest way to build React apps with server-side rendering
React와 TypeScript
- How to use React Context with TypeScript - LogRocket Blog
- elanmed.dev | Conditional Props in React Using Type Discrimination
React와 상태 관리
- React Intersection Observer - A Practical Guide
- Upgrading React with micro-frontends
- Building a React Login Page Template
- Stop using isLoading booleans
- Creating your own store | Small Recipes for Disaster
React 활용 사례 및 외부 기술 연계
- Test your React Libraries Locally with Yalc | PropelAuth
- Using SQLite in Expo for Offline React Native Apps | by Forbes Lindesay | ITNEXT
- The Complete Developer Guide to React 19, Part 1: Async Handling | {callstack}
- Currency handling in React
- Build your own React state management library in under 40 lines of code
React 스타일링 및 CSS
기타
빈 PDF 파일이 필요해서 찾아본 방법들
import { createCanvas } from 'skia-canvas'
import { writeFile } from 'node:fs/promises'
/**
* A4 크기의 빈 PDF 파일을 생성합니다.
*
* @param {string} filename - 생성할 PDF 파일의 이름입니다.
* @returns {Promise<void>}
*/
async function createEmptyPDF(filename) {
// A4 크기 (595x842 포인트)
const width = 595
const height = 842
// PDF 형식의 캔버스를 생성합니다
const canvas = createCanvas(width, height, 'pdf')
const ctx = canvas.getContext('2d')
// 아무 내용도 그리지 않고 현재 상태를 저장합니다
ctx.save()
// 캔버스를 버퍼로 변환하여 PDF로 저장합니다
const buffer = await canvas.toBuffer()
await writeFile(filename, buffer)
console.log(`${filename} 파일이 생성되었습니다!`)
}
createEmptyPDF('empty_skia.pdf')
# Ghostscript를 사용한 빈 PDF 생성
gs -q -dNOPAUSE -dBATCH -sDEVICE=pdfwrite -sOutputFile=empty.pdf -c "[/PageSize [595 842]] setpagedevice" -f /dev/null
# ImageMagick의 `convert` 명령어로 빈 PDF 생성
convert xc:white -page A4 empty.pdf
# `touch` 명령어와 PDF 헤더 직접 작성
echo -e "%PDF-1.4\n1 0 obj\n<<>>\nendobj\nxref\n0 1\n0000000000 65535 f \ntrailer\n<<>>\nstartxref\n9\n%%EOF" > empty.pdf
조건부 렌더링을 한다고 했을때 예전에는 주로 B로 처리했던 것 같은데 디버깅 때문에 고생해서 그런지 생각이 바뀌었다.
function renderA() {
return <Item isPacked={true} name="Space suit" />
}
function renderB() {
return <>{isPacked ? <Item name="Space suit" /> : null}</>
}
조건부 렌더링을 보다 간결하게 표현하기 위해 If
, Then
, 그리고 Else
컴포넌트 개념을 차용하는 방법. 이 컴포넌트는 If
에서 condition
prop을 통해 조건을 받아들이고, 자식으로 Then
과 Else
를 받아 각각의 내용을 렌더링하도록 한다.
type Props = {
/** 렌더링할 조건 */
condition: boolean
/** 자식 컴포넌트 (Then, Else 포함) */
children: React.ReactNode
}
/**
* If 컴포넌트는 조건부 렌더링을 위한 컴포넌트
*/
function If({ condition, children }: Props) {
let thenChild = null
let elseChild = null
React.Children.forEach(children, (child) => {
if (!React.isValidElement(child)) return
if (child.type === Then) thenChild = child
if (child.type === Else) elseChild = child
})
return condition ? thenChild : elseChild
}
/**
* Then, Else 컴포넌트는 조건이 참 또는 거짓일 때 렌더링되는 콘텐츠를 포함
*/
const Then = ({ children }: React.PropsWithChildren) => <>{children}</>
const Else = ({ children }: React.PropsWithChildren) => <>{children}</>
구글 스프레드시트에서 중복 값을 찾고 그 값에 별도의 스타일을 적용하는 방법
=COUNTIF(A:A, A:A) > 1
-
셀 범위 선택: 중복 값을 확인하고 스타일을 적용할 셀 범위를 선택합니다. 예를 들어, A 열 전체를 선택하려면 A 열을 클릭합니다.
-
조건부 서식 열기: 상단 메뉴에서 **“서식”**을 클릭한 다음 **“조건부 서식”**을 선택합니다.
-
서식 규칙 설정: 조건부 서식 규칙 창이 열리면, “서식 규칙 추가” 옵션을 클릭합니다.
-
맞춤 수식 사용:
서식 규칙
드롭다운에서 **“맞춤 수식을 사용하여 서식 지정”**을 선택합니다. -
중복 값 조건 수식 입력: 중복을 찾는 수식을 입력합니다. 이 수식은 A 열 전체에서 A1과 같은 값을 가지는 셀의 개수를 세고, 그 개수가 1보다 크면 중복으로 간주합니다.
-
서식 선택: 스타일을 지정하려면 서식 스타일에서 텍스트 색상, 배경 색상 등을 원하는 대로 설정합니다.
-
완료: 설정이 완료되면 완료를 클릭하여 적용합니다.
class ColorManipulator {
private baseColor: string
private targetColor: string
constructor(baseColor: string, targetColor: string) {
this.baseColor = baseColor
this.targetColor = targetColor
}
private hexToRgb(hex: string) {
const bigint = parseInt(hex.slice(1), 16)
return {
r: (bigint >> 16) & 255,
g: (bigint >> 8) & 255,
b: bigint & 255,
}
}
public calculateOpacity() {
const target = this.hexToRgb(this.targetColor)
const baseRG = this.hexToRgb(this.baseColor)
const opacities = [
(target.r - baseRG.r) / (255 - baseRG.r),
(target.g - baseRG.g) / (255 - baseRG.g),
(target.b - baseRG.b) / (255 - baseRG.b),
]
const averageOpacity =
opacities.reduce((sum, value) => sum + value, 0) / opacities.length
return averageOpacity
}
public getCssRGBA() {
const opacity = this.calculateOpacity()
return `rgba(0, 0, 0, ${opacity.toFixed(2)})`
}
}
const manipulator = new ColorManipulator('#000000', '#D1D7DE')
const opacity = manipulator.calculateOpacity()
const cssRGBA = manipulator.getCssRGBA()
- 문제: 스프레드시트 데이터가 커서 내용 확인이 어려움.
- 해결: 구글 드라이브에 스프레드시트 데이터를 CSV 파일로 업로드하여 확인.
/**
* 현재 활성 스프레드시트의 첫 번째 시트 데이터를 CSV 파일로 저장하고,
* 구글 드라이브에 업로드합니다.
* 생성된 파일의 URL은 콘솔에 로그로 출력됩니다.
*/
function saveSpreadsheetDataAsCSV() {
/** 현재 활성 스프레드시트 */
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheets()[0]
/** 시트의 모든 데이터의 2차원 배열 */
const data = sheet.getDataRange().getValues()
/** CSV 데이터 */
const csv = data.map((row) => row.join(',')).join('\n')
/** 파일명 */
const fileName = 'spreadsheet_data.csv'
/** 구글 드라이브에 업로드 완료된 CSV 파일 */
const file = DriveApp.createFile(fileName, csv, MimeType.CSV)
console.log('파일이 생성되었습니다: ' + file.getUrl())
}
두 개의 오버로딩된 메서드에서 각각의 반환 타입을 명시적으로 추출하기
type Year = {
year(): number
year(u: string): string
}
type ReturnTo<T, R> = T extends R ? R : never
type GetYear = ReturnTo<Year['year'], () => number>
type SetYear = ReturnTo<Year['year'], (u: string) => string>
fetchAvailability
시간 슬롯 조회
지정된 조건(근무 요일, 시간, 이벤트 충돌 여부)에 따라 예약 가능한 시간 슬롯을 가져옵니다. 결과는 사용자에게 제공할 수 있는 예약 가능한 시간 슬롯 목록과 해당 시간 슬롯의 지속 시간(분 단위)입니다.
flowchart LR
A[시작] --> B[가장 가까운 슬롯 계산]
B --> C[예약 기간 설정]
C --> D[바쁜 일정 조회]
D --> E[가능한 슬롯 필터링]
E --> F[예약 가능한 슬롯 반환]
F --> G[종료]
- 현재 시간을 기준으로 가장 가까운 시간 슬롯을 계산합니다. (슬롯 길이는
TIMESLOT_DURATION
에 따라 설정됩니다.) - 28일(
DAYS_IN_ADVANCE
) 동안의 일정 기간을 설정합니다. Calendar.Freebusy.query
를 사용하여 지정된 캘린더(CALENDAR
)의 바쁜 일정(busy events)을 조회합니다.- 조회된 이벤트를 기반으로 조건에 맞지 않는 시간 슬롯을 제외합니다:
- 지정된 근무 시간(
WORKHOURS.start
,WORKHOURS.end
) 외의 시간. - 근무일(
WORKDAYS
)이 아닌 요일. - 다른 이벤트와 시간이 겹치는 경우.
- 지정된 근무 시간(
- 예약 가능한 시간 슬롯을 ISO 8601 형식의 문자열로 저장하고 반환합니다.
bookTimeslot
시간 슬롯 예약
사용자가 선택한 시간 슬롯에 이벤트를 생성하여 예약합니다. 예약이 성공하면 Google Calendar에 이벤트를 추가하고 사용자에게 확인 메시지를 반환합니다.
flowchart TD
A[시작] --> B[사용자 입력 수신]
B --> C[시간 슬롯 유효성 검증]
C --> D[이벤트 충돌 확인]
D --> E{이벤트가 존재합니까?}
E -- 예 --> F[에러 반환]
E -- 아니오 --> G[이벤트 생성]
G --> H[성공 메시지 반환]
F --> I[종료]
H --> I
- 사용자가 선택한 시간 슬롯(
timeslot
)과 추가 정보(이름, 이메일, 전화번호, 메모)를 인수로 받습니다. - 시간 슬롯의 유효성을 검증하고,
TIMESLOT_DURATION
을 기준으로 종료 시간을 계산합니다. Calendar.Freebusy.query
를 통해 선택한 시간 동안 다른 이벤트가 있는지 확인합니다.- 이벤트가 겹치면 예약이 불가능하다는 에러 메시지를 반환합니다.
- 겹치는 이벤트가 없다면,
CalendarApp.getCalendarById
를 사용하여 Google Calendar에 새로운 이벤트를 생성합니다:- 이벤트 제목: 사용자 이름이 포함된 약속 제목.
- 이벤트 설명: 전화번호와 메모를 포함합니다.
- 초대된 손님: 제공된 이메일로 초대합니다.
- 초대 메일 발송(
sendInvites: true
).
- 예약 성공 여부를 메시지로 반환합니다.
사용 플로우 및 사례
-
사용자가 예약 가능한 시간 조회: 사용자는 시스템에 접속하여 자신의 예약 가능 시간을 확인합니다. 이는 캘린더 기반 예약 시스템에서 Google Calendar와 동기화하여 관리됩니다.
-
사용자가 특정 시간에 예약: 사용자가 특정 시간을 선택하면,
- 선택된 시간과 정보를
bookTimeslot
에 전달하여 캘린더 이벤트가 생성됩니다. - 충돌이 없을 경우, 예약이 성공적으로 완료되며 이벤트 초대 이메일이 사용자의 메일로 전송됩니다. 이 과정은 이메일 알림으로 사용자가 이벤트에 초대받음을 알려줍니다.
- 선택된 시간과 정보를
-
자동화: 시스템은 특정 근무 시간과 일정을 고려하여 예약 가능성을 실시간으로 확인합니다. 사용자는 이를 통해 보다 효율적으로 예약을 진행할 수 있습니다.
템플릿 리터럴 타입을 활용하여 타입 정의하기
import * as React from 'react'
type RenderPropNames = 'Title' | 'Content' | 'Actions'
type RenderProps = {
[K in RenderPropNames as `render${K}`]: () => React.ReactNode
}
type Props = RenderProps
/**
* @example
*
* ```tsx
* <DialogComponent
* renderTitle={() => <h2>Title</h2>}
* renderContent={() => <p>Content</p>}
* renderActions={() => (
* <div>
* <button onClick={handleClose}>Close</button>
* <button onClick={handleSubmit}>Submit</button>
* </div>
* )}
* />
* ```
*/
function Dialog({
renderTitle,
renderContent,
renderActions,
}: Props) => {
return (
<div data-scope="root">
<div data-part="content">
{renderTitle()}
{renderContent()}
</div>
<div data-part="actions">{renderActions()}</div>
</div>
)
}
useSyncExternalStore
활용 가능한 부분들
thisisunsafe
-> 로컬 환경 또는 테스트 서버에서 강제 접근
- 웹사이트의 SSL/TLS 인증서를 신뢰할 수 없을 때, 이 사이트에 연결할 수 없음 또는 이 연결은 비공개로 설정되지 않았습니다
NET::ERR_CERT_AUTHORITY_INVALID
,NET::ERR_CERT_COMMON_NAME_INVALID
같은 오류 코드가 나타남
💡
useStateObject
는 React의useState
를 확장한 가벼운 래퍼로, 객체 상태 관리를 간편하게 할 수 있도록 설계되었습니다.
export type StateObject<T extends object> = T & {
set: React.Dispatch<React.SetStateAction<T>>
setItem: <K extends keyof T>(key: K, value: T[K]) => void
merge: (newState: Partial<T>) => void
reset: () => void
}
그렇다면 Map
과 Set
도 시도해보기
function useStateMap<K, V>(init: Iterable<[K, V]> = []) {
const [map, setMap] = useState(new Map<K, V>(init))
const update = useCallback(
(updater: (currentMap: Map<K, V>) => void) => {
setMap((prev) => {
const newMap = new Map(prev)
updater(newMap)
return newMap
})
},
[setMap]
)
return {
map,
set: (key: K, value: V) => update((m) => m.set(key, value)),
delete: (key: K) => update((m) => m.delete(key)),
clear: () => setMap(new Map()),
has: (key: K) => map.has(key),
get: (key: K) => map.get(key),
entries: () => Array.from(map.entries()),
size: map.size,
}
}
function useStateSet<T>(init: Iterable<T> = []) {
const [set, setSet] = useState(new Set<T>(init))
const update = useCallback(
(updater: (currentSet: Set<T>) => void) => {
setSet((prev) => {
const newSet = new Set(prev)
updater(newSet)
return newSet
})
},
[setSet]
)
return {
set,
add: (value: T) => update((s) => s.add(value)),
delete: (value: T) => update((s) => s.delete(value)),
has: (value: T) => set.has(value),
clear: () => setSet(new Set()),
entries: () => Array.from(set),
size: set.size,
}
}
- https://github.com/arthurfiorette/proposal-safe-assignment-operator
- https://github.com/arthurfiorette/tuple-it
tuple 함수 메모
- 목적: 비동기 호출의 결과와 오류를 튜플 형식으로 반환
- 사용 예:
const [error, data] = await tuple(someAsyncFunction());
동작 방식:
- 입력:
maybePromise
(Promise 또는 일반 값) - 처리:
try
블록에서await
로 비동기 결과를 기다림- 성공 시:
[null, 결과값]
반환 - 오류 발생 시:
Error
인스턴스이면:[error]
반환- 그 외의 경우:
[new TupleItError(error)]
반환
장점:
- 오류 처리 간소화 (단일 체크로 오류 관리 가능)
field-sizing
를 사용하면 콘텐츠를 기반으로 크기 조절을 사용 설정하는 데 CSS 한 줄이 필요합니다. 이 콘텐츠 기반 크기 조절 스타일은textarea
외에도 다른 요소에도 적용됩니다.
function toggleReducer(state, action) {
switch (action.type) {
default:
return state
}
}
function useToggle({ reducer = toggleReducer } = {}) {
const [state, dispatch] = useReducer(reducer, {})
return { state, dispatch }
}
export function Component() {
useToggle({
reducer(currentState, action) {
console.log(currentState, action)
},
})
}
useReducer
를 이용한 커스텀훅 사용에 대한 간단한 예시. 생각해보니 reducer를 전달해서 재사용하는 방법은 잘 생각못했는데 응용할 수 있을 것 같다.
Footnotes
-
리듀서를 사용하여 예측 가능하고 테스트 가능한 방식으로 상태 업데이트 및 작업을 캡슐화하는 방법과 State Reducer 패턴을 사용하여 이를 사용하는 구성 요소에서 상태 업데이트를 추상화하여 해당 구성 요소가 특정 기능에 더 집중하도록 만드는 방법을 설명. ↩
취소 가능한 fetch
요청을 생성하는 createCancelableFetch
함수 구현
/**
* 취소 가능한 fetch 요청
*
* @param url - 요청 URL
* @param options - fetch 옵션
* @returns {run, cancel} - run: 요청 실행 함수, cancel: 요청 취소 함수
*
* @example
*
* ```ts
* const { run, cancel } = createCancelableFetch('/api/data')
*
* run()
* .then((data) => console.log('Fetched data:', data))
* .catch((err) => console.error('Error or canceled:', err))
*
* cancel()
* ```
*/
function createCancelableFetch(url: string, options: RequestInit = {}) {
const abortController = new AbortController()
const run = async () => {
const response = await fetch(url, {
...options,
signal: abortController.signal,
})
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
return response.json()
}
const cancel = () => abortController.abort()
return {
run,
cancel,
}
}
AbortController
를 사용하여 요청을 중단할 수 있는 기능 제공run
함수로fetch
요청을 실행하고,cancel
함수로 요청을 취소할 수 있음- 요청 실패 시 오류를 처리하고 예외를 발생시킴
- https://mmazzarolo.com/blog/2024-08-13-async-chunk-preloading-on-load/
- https://mmazzarolo.com/blog/2024-07-29-data-preloading-script/
- https://martinfowler.com/articles/data-fetch-spa.html
- https://jakelazaroff.com/words/whats-a-single-page-app/
- https://jakelazaroff.com/words/building-a-single-page-app-with-htmx/
- https://saricden.com/how-to-make-fixed-elements-respect-the-virtual-keyboard-on-ios
- https://sophia-dev.io/blog/watching-keyboard-status-with-web-apis
- https://ishadeed.com/article/virtual-keyboard-api/
- https://velog.io/@th_velog/%EC%9B%B9%EB%B7%B0-Fixed-%EA%B0%80%EC%83%81-%ED%82%A4%EB%B3%B4%EB%93%9C-VisualViewport-%EC%82%AC%EC%9A%A9
- https://jooonho.dev/web/2023-01-09-webview-issue/
- https://ryanmulligan.dev/blog/sticky-header-scroll-shadow/
- https://www.taniarascia.com/horizontal-scroll-fixed-headers-table/
- https://www.paigeniedringhaus.com/blog/use-css-grid-to-make-a-fixed-sidebar-with-scrollable-main-body
- https://utilitybend.com/blog/is-the-sticky-thing-stuck-is-the-snappy-item-snapped-a-look-at-state-queries-in-css
- https://css-tricks.com/how-to-make-a-scroll-to-select-form-control/
- 검색 매개변수별로 데이터 유효성 검증
useSearchParams
와Zod
를 활용해 모든 검색 매개변수를 병합하고 유효성 검증
import { useMemo } from 'react'
import { useSearchParams } from 'react-router-dom'
import { z } from 'zod'
const CombinedSchema = PaginationSchema.merge(FilterSchema)
.merge(SortSchema)
.merge(SearchSchema)
.partial()
export function useSearchParamsWithSchema() {
const [searchParams, setSearchParams] = useSearchParams()
const parsedParams = useMemo(() => {
// searchParams를 객체로 변환
const paramsObject = Object.fromEntries(searchParams.entries())
// 결합된 스키마로 유효성 검사 및 파싱
const result = CombinedSchema.safeParse(paramsObject)
if (result.success) {
return result.data
}
console.error(result.error)
return {}
}, [searchParams])
/**
* 새로운 검색 매개변수로 업데이트하는 함수입니다.
*
* 1. 현재 검색 매개변수의 복사본을 생성합니다.
* 2. 새로운 매개변수를 순회하며 값을 설정하거나 삭제합니다.
* 3. 최종적으로 업데이트된 매개변수를 state에 설정합니다.
*/
const updateSearchParams = (newParams: Record<string, string>) => {
const updatedParams = new URLSearchParams(searchParams)
Object.entries(newParams).forEach(([key, value]) => {
if (value) {
// 업데이트된 매개변수로 상태 갱신
updatedParams.set(key, value)
} else {
// 값이 없으면 삭제
updatedParams.delete(key)
}
})
// 업데이트된 매개변수로 상태 갱신
setSearchParams(updatedParams)
}
return {
parsedParams,
updateSearchParams,
}
}
URL.parse() 메서드를 활용하여 URL 객체를 생성하고 처리하는 방법
URL.parse(url)
메서드는 주어진 URL에 따라 새 URL 객체를 생성- 유효하지 않은 URL 값이 주어질 경우
null
을 반환 - 두 번째 파라미터 base는 상대 URL을 해석하기 위한 기준 URL로 사용되며, 이를 통해 URL의 경로가 올바르게 조정됨
- URL 객체나 다른 문자열을 파라미터로 사용할 수 있으며, 내부에서 문자열로 변환됨
describe('null 입력에 대한 URL API 동작 테스트', () => {
test('new URL(null)은 TypeError를 발생시킨다', () => {
expect(() => new URL(null)).toThrow(TypeError)
})
test('URL.canParse(null)은 false를 반환한다', () => {
expect(URL.canParse(null)).toBe(false)
})
test('URL.parse(null)은 null을 반환한다', () => {
expect(() => URL.parse(null)).toBe(null)
})
})
@value b from "./b.module.css";
.root {
color: aquamarine;
}
.root :global(.b) {
text-decoration: line-through;
}
CSS 모듈에서 변수를 값으로 내보내고 사용하는 방법
- PostCSS와
postcss-modules-values
플러그인을 사용하여 CSS 모듈 내에서 변수 값 내보내기 지원 - 색상 변수를 정의하는 파일 생성
- 변수 선언:
@value
구문 사용
- 변수 선언:
- 다른 CSS 모듈 파일에서 해당 변수를 가져와서 사용
- 변수 가져오기 및 CSS 클래스에 적용
https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/kvs-with-functions.html
- URL rewrites or redirects
- A/B testing and feature flags
- Access authorization
navigate(-1)
의 위험성: 브라우저 히스토리에서 이전 위치로 이동, 앱 내부 네비게이션과 혼란을 초래할 수 있음- 대신
Link
컴포넌트의state
속성을 활용하여 안전하게 앱 내에서의 “Back” 네비게이션 구현 가능 - 사용자에게 현재 URL을 반환하는 커스텀 훅
useCurrentURL
구현 및 재사용의 용이성을 제공하는useBackNavigation
훅 정의
function PreserveStateLink(props) {
const location = useLocation()
const currentURL = location.pathname + location.search
return (
<Link state={{ back: currentURL }} {...props}>
{children}
</Link>
)
}
function BackLink() {
const navigate = useNavigate()
const location = useLocation()
const handleClick: LinkProps['onClick'] = (e) => {
const back = location.state?.back
if (back) {
e.preventDefault()
navigate(back)
}
}
return (
<Link to="/todos" onClick={handleBack}>
Back
</Link>
)
}
https://www.emgoto.com/jest-partial-match/
objectContaining
, arrayContaining
, 및 toHaveBeenCalledWith
를 사용하기
test('objectContaining으로 특정 키/값 매칭', () => {
const receivedObject = {
id: 1,
name: 'Alice',
age: 30,
city: 'Seoul',
}
expect(receivedObject).toEqual(
expect.objectContaining({
name: 'Alice',
city: 'Seoul',
})
)
})
test('arrayContaining으로 특정 값 배열 매칭', () => {
const receivedArray = [1, 2, 3, 4, 5]
expect(receivedArray).toEqual(expect.arrayContaining([2, 4]))
})
test('toHaveBeenCalledWith을 사용한 부분 매칭', () => {
const mockFunction = jest.fn()
// Mock 함수 호출
mockFunction({
id: 1,
name: 'Alice',
tags: ['developer', 'designer'],
})
expect(mockFunction).toHaveBeenCalledWith(
expect.objectContaining({
name: 'Alice',
tags: expect.arrayContaining(['designer']),
})
)
})
test('arrayContaining + objectContaining으로 복합 매칭', () => {
const receivedArray = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
{ id: 3, name: 'Charlie' },
]
expect(receivedArray).toEqual(
expect.arrayContaining([
expect.objectContaining({ name: 'Alice' }),
expect.objectContaining({ id: 2 }),
])
)
})
https://developer.mozilla.org/en-US/docs/Web/API/Document/activeElement
document.activeElement
는 현재 DOM에서 포커스를 받고 있는 요소를 반환하는 메서드입니다. 이를 활용하면 다양한 사용자 경험과 기능을 구현할 수 있습니다. 아래는 document.activeElement
를 활용해 만들 수 있는 기능들입니다:
1. 현재 포커스된 입력 필드 확인
사용자가 작성 중인 입력 필드나 텍스트 영역을 확인할 수 있습니다.
- 예제: 포커스된 입력 필드의 내용을 자동 저장하거나 하이라이트 처리.
const activeElement = document.activeElement
if (activeElement.tagName === 'INPUT' || activeElement.tagName === 'TEXTAREA') {
console.log('Current input:', activeElement.value)
}
2. 포커스 전환 관리
특정 이벤트가 발생했을 때 특정 요소로 포커스를 강제로 이동.
- 예제: 모달 창이 열릴 때 닫기 버튼에 자동으로 포커스 설정.
document.getElementById('openModal').addEventListener('click', () => {
const modalCloseButton = document.querySelector('.modal .close-button')
modalCloseButton.focus()
})
3. 키보드 내비게이션 개선
키보드만 사용하는 환경에서 포커스 상태를 추적해 순차적으로 이동 가능.
- 예제: 탭 키를 눌러 순환 내비게이션 구현.
document.addEventListener('keydown', (event) => {
if (event.key === 'Tab') {
const activeElement = document.activeElement
console.log('Currently focused element:', activeElement)
}
})
4. 포커스 아웃 이벤트 감지
document.activeElement
가 변경될 때마다 현재 포커스가 사라지는 경우를 처리.
- 예제: 입력 필드에서 포커스를 잃으면 경고 메시지 표시.
document.addEventListener('focusout', () => {
const activeElement = document.activeElement
if (!activeElement || activeElement.tagName !== 'INPUT') {
console.log('Input field lost focus!')
}
})
5. 접근성 기능 강화
화면 리더기와 같은 접근성을 위해 현재 포커스된 요소를 기반으로 안내 텍스트 제공.
- 예제: 현재 활성화된 요소에 추가 정보를 읽어주는 알림.
document.addEventListener('focusin', () => {
const activeElement = document.activeElement
const ariaLabel = activeElement.getAttribute('aria-label')
if (ariaLabel) {
console.log(`Focused on: ${ariaLabel}`)
}
})
6. 자동 입력 감지 및 처리
포커스된 필드가 텍스트 입력인지 버튼인지 구분해 동작을 다르게 처리.
- 예제: 포커스된 필드에 따라 Enter 키 이벤트 처리 방식 변경.
document.addEventListener('keydown', (event) => {
if (event.key === 'Enter') {
const activeElement = document.activeElement
if (activeElement.tagName === 'BUTTON') {
activeElement.click()
} else if (activeElement.tagName === 'INPUT') {
console.log('Input submitted:', activeElement.value)
}
}
})
7. 사용자 행동 분석
사용자가 어디에 더 많이 포커스를 두는지 분석해 UI 개선.
- 예제: 포커스된 요소별 통계 수집.
const focusCount = {}
document.addEventListener('focusin', () => {
const activeElement = document.activeElement
const id = activeElement.id || activeElement.tagName
focusCount[id] = (focusCount[id] || 0) + 1
console.log(`Focus count for ${id}:`, focusCount[id])
})
8. 폼 검증 피드백 제공
사용자가 입력 중인 필드에 실시간 검증 메시지 표시.
- 예제: 잘못된 입력값이 감지되면 사용자에게 즉각 알림.
document.addEventListener('input', () => {
const activeElement = document.activeElement
if (activeElement.tagName === 'INPUT' && activeElement.type === 'email') {
if (!activeElement.value.includes('@')) {
activeElement.setCustomValidity('Please enter a valid email address.')
} else {
activeElement.setCustomValidity('')
}
}
})
9. 커스텀 툴팁 제공
포커스된 요소에 맞춰 툴팁을 표시하거나 정보를 제공.
- 예제: 입력 필드에 포커스될 때 툴팁이 나타남.
document.addEventListener('focusin', () => {
const activeElement = document.activeElement
if (activeElement.tagName === 'INPUT') {
const tooltip = document.querySelector('.tooltip')
tooltip.textContent =
activeElement.getAttribute('data-tooltip') || 'Enter value'
tooltip.style.top = `${
activeElement.offsetTop + activeElement.offsetHeight
}px`
tooltip.style.left = `${activeElement.offsetLeft}px`
tooltip.style.display = 'block'
}
})
document.addEventListener('focusout', () => {
const tooltip = document.querySelector('.tooltip')
tooltip.style.display = 'none'
})
10. 다크모드 및 스타일 변경 제어
포커스된 요소에 따라 UI 스타일 변경.
- 예제: 특정 요소에 포커스되었을 때 테마 전환.
document.addEventListener('focusin', () => {
const activeElement = document.activeElement
if (activeElement.classList.contains('dark-mode-toggle')) {
document.body.classList.add('dark-mode')
}
})
위 기능들은 document.activeElement
를 다양한 상황에 응용한 사례입니다. 주로 사용자 행동 추적, UI 개선, 접근성 강화에 유용하게 사용될 수 있습니다.
- https://stackoverflow.com/questions/58136102/deploy-individual-services-from-a-monorepo-using-github-actions
- https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#example-including-paths
최소한 하나의 경로가 paths
필터의 패턴과 일치하면 워크플로가 실행
on:
push:
paths:
- '**.js'
type JsonPrimitive = string | number | boolean | null
type JsonObject = { [Key in string]: JsonValue } & {
[Key in string]?: JsonValue | undefined
}
type JsonArray = JsonValue[] | readonly JsonValue[]
type JsonValue = JsonPrimitive | JsonObject | JsonArray
예전에 JSON 타입 정의가 필요해서 찾아봤던 내용
JsonObject
: 문자열 키와 JsonValue 타입의 값을 가진 JSON 객체를 정의JsonArray
: JsonValue 타입의 요소를 포함하는 JSON 배열을 정의JsonPrimitive
: 문자열, 숫자, 불린, 또는 null과 같은 유효한 JSON 기본 값을 정의JsonValue
: 유효한 JSON 값을 나타내며, JsonPrimitive, JsonObject, 또는 JsonArray로 구성
Footnotes
Every type is defined by its intro and elim forms
- Intro forms: 타입의 인스턴스를 어떻게 “생성”하는지 정의.
- Elim forms: 생성된 타입 인스턴스를 어떻게 “사용”하거나 “해체”할지 정의.
타입을 정의할 때, 생성과 사용의 명확한 경계를 설정해서 Intro/Elim 설계를 명시적으로 표현하기
class Rectangle {
private constructor(public width: number, public height: number) {}
static create(width: number, height: number) {
if (width <= 0 || height <= 0) {
return null
}
return new Rectangle(width, height)
}
getArea() {
return this.width * this.height
}
}
const rect = Rectangle.create(10, 20)
if (rect) {
console.log(rect.getArea())
}
Types are not their elim forms
interface
와class
를 통해 Intro/Elim 모두를 명시적으로 정의- 팩토리 메서드 같은 패턴을 활용해 생성 방식을 추상화
interface Shape {
getArea(): number
}
class Circle implements Shape {
constructor(private radius: number) {}
getArea() {
return Math.PI * this.radius ** 2
}
}
class Rectangle implements Shape {
constructor(private width: number, private height: number) {}
getArea() {
return this.width * this.height
}
}
function createShape(type: 'circle' | 'rectangle', ...args: number[]) {
if (type === 'circle' && args.length === 1) {
return new Circle(args[0])
}
if (type === 'rectangle' && args.length === 2) {
return new Rectangle(args[0], args[1])
}
return null
}
const shape = createShape('circle', 10)
if (shape) {
console.log(shape.getArea())
}
static create
패턴이 유용한 상황
대부분의 경우 생성자를 사용하는 것이 더 직관적이고 단순하지만, 다음과 같은 상황에서는 static create
가 더 적합
- 생성 로직이 복잡하거나 조건부 처리가 필요할 때
- 팩토리 패턴을 간소화하고 싶을 때
- 생성 과정에서의 불변성을 강화하고 싶을 때
- 다른 타입을 반환하거나, 실패 가능성을 명시적으로 처리하고 싶을 때
1. 생성 로직 커스터마이징
- 생성자가 단순히 필드 초기화만 수행하는 경우는 일반 생성자로 충분하지만, 생성 과정에서 유효성 검사, 값 변환, 혹은 복잡한 비즈니스 로직이 필요하다면
static create
패턴이 더 적합 - 예를 들어, 유효성 검사를 통과하지 못하면 객체를 반환하지 않거나, 에러를 던질 수 있음
- 생성 과정에서의 제약 조건을 명시적으로 처리할 수 있음
class User {
private constructor(private readonly name: string) {}
static create(name: string): User | null {
if (!name || name.length < 3) {
console.error('Invalid name!')
return null
}
return new User(name)
}
}
const validUser = User.create('John') // 성공
const invalidUser = User.create('Jo') // 실패, null 반환
2. 팩토리 패턴의 간소화
static create
를 간단한 팩토리 메서드로 활용해서 객체 생성의 복잡성을 숨기기- 객체의 생성 방식이 호출자의 관점에서는 중요하지 않다면 내부 로직을 캡슐화할 수 있다
- 이런 방식은 타입에 따라 다른 구현체를 생성해야 하는 경우
class Shape {
static create(type: 'circle' | 'rectangle', size: number): Shape {
if (type === 'circle') {
return new Circle(size)
} else {
return new Rectangle(size)
}
}
}
3. 불변성 및 제약 강화
- 생성자를
private
로 설정하고,static
메서드만을 통해 객체를 생성하도록 제한하면 객체의 불변성을 더 강하게 유지할 수 있다 - 특정 조건에 따라 객체의 생성을 제한하거나 실패하도록 설계할 수 있다
class CardNumber {
private constructor(private readonly number: string) {}
static create(number: string): CardNumber | null {
if (!CardNumber.isValid(number)) {
return null
}
return new CardNumber(number)
}
private static isValid(number: string): boolean {
return number.length === 16
}
}
const card = CardNumber.create('1234567812345678') // 유효한 카드
const invalidCard = CardNumber.create('123') // null
4. 다른 타입 반환
생성자는 항상 해당 클래스의 인스턴스만 반환할 수 있다. 반면, static create
는 경우에 따라 다른 타입의 값을 반환할 수 있다.
class Result<T> {
private constructor(
public readonly value: T | null,
public readonly error: string | null
) {}
static success<T>(value: T): Result<T> {
return new Result(value, null)
}
static failure<T>(error: string): Result<T> {
return new Result(null, error)
}
}
class User {
private constructor(private readonly name: string) {}
static create(name: string): Result<User> {
if (!name || name.length < 3) {
return Result.failure('Name must be at least 3 characters long')
}
return Result.success(new User(name))
}
}
const result = User.create('Jo') // 실패 결과 반환
5. 객체 재사용
동일한 인스턴스를 재사용하고 싶을 때도 static create
패턴이 적합하다. 예를 들어, 싱글톤(Singleton) 패턴처럼 동작하거나 캐싱된 객체를 반환할 수 있다.
class Configuration {
private static instance: Configuration
private constructor(public readonly settings: Record<string, string>) {}
static create(): Configuration {
if (!Configuration.instance) {
Configuration.instance = new Configuration({ mode: 'production' })
}
return Configuration.instance
}
}
const config1 = Configuration.create()
const config2 = Configuration.create()
console.log(config1 === config2) // true
권한관리 문제점
- 불필요한 복잡성과 디버깅의 어려움
- 계층적 권한의 비효율성
- 데이터베이스 부하
- 여러 개의 진실 소스(Sources of Truth)
해결 방법
- JSON 직렬화 가능한 DSL을 정의하여 정책을 표현.
- TypeScript 기반 평가 엔진을 구현하여 데이터를 기반으로 정책을 평가.
- 기존 AST 기반보다 단순한 구조를 가진다.
type FieldName = string
type Value = string | boolean | number | Date | null
type ExpressionArgumentRef = {
type: 'field'
ref: FieldName
}
type BinaryExpressionDef = [
FieldName,
'=' | '<>' | '>' | '<' | '>=' | '<=',
Value | ExpressionArgumentRef
]
type OrExpressionDef = {
or: ExpressionDef[]
}
type AndExpressionDef = {
and: ExpressionDef[]
}
type ExpressionDef = BinaryExpressionDef | OrExpressionDef | AndExpressionDef
const binaryExpression = ['file.id', '<>', null] satisfies ExpressionDef
const andExpression = {
and: [
['file.id', '<>', null],
['team.permission', '=', 'open'],
['project.deleted_at', '<>', null],
],
} satisfies ExpressionDef
const orExpression = {
or: [
['team.id', '<>', null],
['file.id', '=', { type: 'field', ref: 'team.id' }],
],
} satisfies ExpressionDef
- 프런트엔드(클라이언트)에서 MP4 파일의 오디오 존재 여부를 확인하기
- 브라우저 API 사용 시 호환성 문제 발생 (Chrome, Safari, Firefox 각각 다른 API 사용)
- FileReader API를 사용하여 서버에서 응답받은 바이너리 데이터를 읽고, 이를 분석해 오디오 여부 확인.
- 오디오 정보가 발견되지 않으면 추가 데이터 요청하여 확인 범위를 증가시켜 반복.
await ffmpeg.writeFile('input.mp4', await fetchFile(file))
await ffmpeg.ffprobe(['-i', 'input.mp4', '-show_streams', '-o', 'output.txt'])
const data = await ffmpeg.readFile('output.txt')
…하지만 ffmpeg의 중요성을 깨달았다.
- https://github.com/huozhi/devjar
- https://github.com/TroyAlford/react-jsx-parser
- https://github.com/FormidableLabs/react-live
- https://github.com/seek-oss/playroom
- https://github.com/Paciolan/remote-component
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/Function#creating_a_function_object_from_a_function_declaration_or_function_expression
/**
* - 인자 수 확인: `curry` 함수는 전달된 함수 `fn`의 인자 수를 확인
* - 인자 수가 충분한 경우: 만약 `args`의 길이가 `fn`의 인자 수 이상이면, `fn`을 호출하고 결과를 반환
* - 인자 수가 부족한 경우: 그렇지 않으면 추가 인자(`moreArgs`)를 받을 수 있는 새 함수를 반환. 이 함수는 기존 인자(`args`)와 새로운 인자(`moreArgs`)를 합쳐 다시 `curry(fn)`을 호출하여 최종적으로 인자가 충분할 때까지 이 과정을 반복
*/
const curry =
(fn: Function) =>
(...args: any[]) =>
args.length >= fn.length
? fn(...args)
: (...moreArgs: any[]) => curry(fn)(...args, ...moreArgs)
/**
* - `...fns: Function[]` 여러 개의 함수를 인자로 받는다
* - 내부에서 `reduce` 메서드를 사용하여 함수 배열을 순회 초기값으로 `x`를 사용하며, 각 함수 `fn`을 차례로 호출하여 결과를 다음 함수로 전달
*/
const pipe =
(...fns: Function[]) =>
(x: any) =>
fns.reduce((acc, fn) => fn(acc), x)
/**
* - `...fns`: 여러 개의 함수를 매개변수로 받음
* - `(x)`: 초기값 `x`를 인자로 받아 결과를 리턴
* - `fns.reduceRight(...)`: 배열의 오른쪽부터 왼쪽으로 각 함수를 적용합니다. `y`는 이전 함수의 결과이며, `f`는 현재 함수
* - `f(y)`: 현재 함수 `f`를 이전 함수의 결과 `y`에 적용
*/
const compose =
(...fns) =>
(x) =>
fns.reduceRight((y, f) => f(y), x)
- 함수 조합(Composition): 작은 함수들을 조합하여 새로운 함수를 만들며, 예를 들어
compose(f, g, h)
는f(g(h(x)))
로 실행 compose
: 오른쪽에서 왼쪽으로 함수를 실행하며,compose(square, double)(3)
은 36을 반환pipe
: 왼쪽에서 오른쪽으로 함수를 실행하여,pipe(double, square)(3)
의 결과는 36- 커링(Currying):
f(a, b, c)
를f(a)(b)(c)
형태로 변환하고, 예를 들어add(1)(2)(3)
의 결과는 6 - 부분 적용(Partial Application): 일부 인자만 미리 적용하여 새로운 함수를 만들 수 있으며,
const double = multiply(2, _)
로 정의 - 포인트-프리 스타일: 변수를 사용하지 않고 함수를 조합하여 작성하며, 예를 들면
compose(square, double)
와 같은 형태 - 데이터 마지막 원칙(Data Last): 데이터를 마지막 인자로 배치하여 조합성을 높이는데, 예를 들어
const halve = divideDataLast(2)
와 같이 사용
import * as React from 'react'
type ContainerProps = {
children: typeof Body
}
type BodyProps = {
id: string
}
function Container({ children }: ContainerProps) {
const id = '1234'
return <>{children({ id })}</>
}
export function Body({ id }: BodyProps) {
return <>{id}</>
}
export function Page() {
return <Container>{Body}</Container>
}
컨테이너/프레젠테이션 패턴을 활용한 React 컴포넌트 구조.
- 컨테이너 컴포넌트(
Container
)는 상태를 관리하고 데이터를 하위 컴포넌트에 전달함. - 프레젠테이션 컴포넌트(
Body
)는 데이터를 받아서 UI를 렌더링함. - 이 구조는 테스트를 쉽게 하고, 컴포넌트의 역할을 명확하게 분리함.
- The Elegance Of React. Writing Elegant Code With React, Redux… | by A. Sharif | JavaScript Inside | Medium
- Immutability in React and Redux: The Complete Guide
- Redux modules and code-splitting – Nicolas Gallagher
- Answering Your App’s Questions with Redux Selectors - Denis Washington
- Improving Redux state transfer performance with JSON.parse(), a quick case study
- Redux is half of a pattern (1/2) - DEV Community
- React + Redux + Comlink = Off-main-thread — surma.dev
- Blogged Answers: Why React Context is Not a “State Management” Tool (and Why It Doesn’t Replace Redux) · Mark’s Dev Blog1
- Idiomatic Redux: The History and Implementation of React-Redux · Mark’s Dev Blog2
Footnotes
BroadcastChannel API
- 캔버스로 동영상 프레임을 캡쳐한다.
- 텍스트를 입력 받는다. 해당 텍스트를 서버에 보내고 음성파일을 응답받는다.
Blob
데이터는URL.createObjectURL()
로 변환해서img
,audio
태그에 연결한다.
getServerSideProps
|> getServerSideSitemapLegacy
|> withXMLResponseLegacy
|>
res.setHeader('Content-Type', 'text/xml')
res.write(content)
res.end()
SSR에서 XML 형식의 사이트맵을 생성하기.
- Paul Scanlon | styled-components Responsive Array Syntax1
- Paul Scanlon | Styled Components Style Objects2
Footnotes
-
media queries를 처리하기 위한
create-media-queries.js
함수 설명 ↩
브라우저에서 PDF 및 이미지 파일에 대한 OCR 실행
- PDF.js를 사용하여 PDF에서 이미지를 추출
- Tesseract OCR로 추출된 이미지에서 텍스트를 인식
- 직접 브라우저 상에서 OCR 작업을 실행
주요업무
- 최신 웹 기술을 사용하여 웹 애플리케이션의 사용자 인터페이스 설계 및 개발
- 디자이너, PO, 서버개발자와 협업하여 사용자 인터페이스 디자인이 최고 수준으로 구현되도록 보장
- 깔끔하고 유지 관리가 용이하며 효율적인 코드 작성
- 프론트엔드 문제 디버깅 및 문제 해결
- 프론트엔드 개발의 새로운 트렌드와 기술에 대한 최신 정보 파악
자격요건
- HTML, CSS, JavaScript에 대한 풍부한 경험
- React, Vue, Angular 또는 이와 유사한 최신 프런트엔드 프레임워크에 대한 경험
- MobX, React-Query 등 다양한 상태 관리 패턴 사용 경험
- Git 버전 관리에 익숙함
- 뛰어난 문제 해결 능력과 세부 사항에 대한 주의력
- 강력한 커뮤니케이션 및 협업 기술
- 빠르게 변화하는 환경에서 작업하고 동시에 여러 프로젝트를 처리할 수 있는 능력
- 능동적인 문제 해결사. 높은 신뢰성, 디테일 지향성, 뛰어난 후속 처리 능력
우대사항
- Vitest, Jest 그리고 React Testing Library와 같은 테스트 프레임워크에 대한 경험
- JS -> TS 마이그레이션 경험,
- 프론트엔드 개발에 열정을 갖고 역동적인 팀과 함께 흥미로운 프로젝트를 진행하고자 하는 자세
- 고객과 직접 작업한 경험 또는 고객과 직접 대면하는 역할을 수행한 경험
- 고객을 돕고 고객의 요구를 충족하는 최고의 전략 및 기술 솔루션을 조언하는 데 있어 능동적인 자세로 임하는 열정
- 새로운 웹 개발 트렌드를 포함하여 성장하는 비즈니스의 요구 사항에 대한 이해력
- 팀 환경 내에서 개별적으로 또는 협력적으로 일할 수 있는 능력
- 모호함을 편안하게 받아들이고 실패나 실수를 두려워하지 않는 자세. 피드백을 잘 받아들이고 빠르게 반복할 수 있음(스타트업 초기 단계의 경험 적극 선호)
위의 항목은 대화의 시작점일 뿐, 요구사항의 어려운 목록이 아닙니다. 이 역할에 흥미를 느낀다면 꼭 지원하시기 바랍니다!
OpenAPI에서 타입스크립트 코드 생성. 클라이언트, SDK, 유효성 검사기 등을 생성하세요.
- 설치하기 - The Rust Programming Language
- rust-blog/posts/rust-in-non-rust-servers.md at master · pretzelhammer/rust-blog · GitHub
- 추리 게임 - The Rust Programming Language
- A half-hour to learn Rust
- 프로그래밍 언어 러스트를 배웁시다! 001 Easy Rust in Korean: Intro - YouTube
- Rustfinity | Learn and Practice the Rust Programming Language
- Supercharge Your NodeJS With Rust | yield code();1
Footnotes
🛠 소프트웨어 개발 및 엔지니어링 문화
- Why you should include debugging in the interview process
- B2B 스타트업 개발자와 도메인 지식
- How and why we built our startup around small teams
- Scaling DevTools
- GitButler
🎯 사고 방식 및 문제 해결
- [번역] 당신은 엉뚱한 문제를 풀고 있다.
- [둠의 아버지 존 카맥] 가치의 원천이 코딩인 적이 있었나?
- AddyOsmani.com - Write about what you learn.
- Facebook’s Little Red Book
🚀 학습 및 성장 전략
- Teaching the science of learning
- Learning How to Learn: Effective Strategies for Learning & Overcoming Procrastination
- 모두를 위한 컴퓨터 과학 (CS50 2019)
🌏 커리어 및 업계 트렌드
- Titles for Designers/Programmers | progression.fyi
- Agile at 20: The Failed Rebellion
- The English Paradox: Four Decades of Life and Language in Japan
🍀 운과 기회에 대한 사고
- GitHub - ChromeDevTools/devtools-frontend: The Chrome DevTools UI
- GitHub - cyrus-and/chrome-remote-interface: Chrome Debugging Protocol interface for Node.js
- GitHub - paulirish/awesome-chrome-devtools: Awesome tooling and resources in the Chrome DevTools & DevTools Protocol ecosystem
- GitHub - Tencent/vConsole: A lightweight, extendable front-end developer tool for mobile web page.
- GitHub - liriliri/chii: Remote debugging tool
- Chrome Devtools를 활용하여 나만의 웹뷰 디버깅 환경 만들기
LLM 및 프롬프트 엔지니어링
- Using Composition in React to Avoid “Prop Drilling”1
- Merrick Christensen - Headless User Interface Components2
- Headless Component: a pattern for composing React UIs
- 유연성: 정책을 메커니즘에서 분리하고 인터페이스를 엔진에서 분리하면 소프트웨어 구성 요소를 설계하고 구현할 때 더 큰 유연성을 확보할 수 있습니다. 따라서 시스템의 나머지 부분에 영향을 주지 않고 구성 요소를 수정하거나 교체하기가 더 쉬워집니다.
- 재사용 가능성: 컴포넌트를 별개의 모듈로 분리하면 다양한 컨텍스트나 애플리케이션에서 사용할 수 있는 재사용 가능한 빌딩 블록을 만들 수 있습니다. 이를 통해 코드 재사용을 촉진하여 개발 시간을 단축하고 코드 품질을 개선할 수 있습니다.
- 테스트 가능성: 메커니즘을 정책에서 분리하고 인터페이스를 엔진에서 분리하면 개별 구성 요소를 개별적으로 테스트하기가 더 쉬워져 전반적인 테스트 범위가 개선되고 버그나 회귀의 위험이 줄어듭니다.
- 유지 관리 가능성: 컴포넌트를 별개의 모듈로 분리하면 시간이 지남에 따라 코드를 더 쉽게 유지 관리하고 디버그할 수 있습니다. 또한 시스템의 나머지 부분에 영향을 주지 않고 개별 컴포넌트의 문제나 버그를 더 쉽게 식별하고 수정할 수 있습니다.
- 확장성: 컴포넌트를 분리하면 특정 요구 사항에 따라 여러 컴포넌트를 독립적으로 확장할 수 있어 소프트웨어 애플리케이션을 더 쉽게 확장할 수 있습니다.
- 상호 운용성: 구성 요소를 분리하면 서로 다른 시스템 간에 통신하는 데 사용할 수 있는 잘 정의되고 표준화된 인터페이스를 생성하여 서로 다른 시스템 또는 구성 요소 간의 상호 운용성을 향상시킬 수 있습니다.
- 민첩성: 컴포넌트를 분리하면 나머지 시스템에 영향을 주지 않고 개별 컴포넌트를 더 빠르게 반복하고 변경할 수 있어 민첩성을 향상시킬 수 있습니다.
Footnotes
const emptyCsvFile = new File([''], 'default.csv', { type: 'text/csv' })
const emptyPngFile = new File([''], 'default.png', { type: 'image/png' })
formData.append('product_coupons', emptyCsvFile)
formData.append('product_imgs', emptyPngFile)
console.assert(emptyCsvFile.size === 0)
빈 파일을 FormData에 첨부하여 전송하는 방법
File
객체를 사용하여 빈 파일을 생성할 수 있다.- 파일의
MIME
타입을 지정하여 타입에 맞는 빈 파일을 만들 수 있다. - 생성된 빈 파일을
FormData
객체에 추가하여 전송할 수 있다.
이는 서버에 빈 형태의 파일을 전송할 때 유용하며, API 테스트 및 디폴트 값 설정에 활용될 수 있다.
import type { A } from 'a'
- The gotcha of unhandled promise rejections - JakeArchibald.com
- ⭐️🎀 JavaScript Visualized: Promises & Async/Await - DEV Community
- Running Promises In Parallel: A Visual Guide | JulesBlom.com
- JavaScript Visualized - Promise Execution
- Promises From The Ground Up • Josh W. Comeau
- ECMAScript 2024 feature:
Promise.withResolvers()
No, disabling a button is not app logic. - DEV Community
- "idle" 아무 것도 아직 일어나지 않았다.
- "loading" 진행중
- "success" 성공적
- "failure" 오류가 발생했음
- RSC From Scratch. Part 1: Server Components · reactwg/server-components · Discussion #5 · GitHub
- Understanding React Server Components | Tony Alicea
- Delicious Donut Components | Frontend at Scale
- Instant Search Params with React Server Components
- Why Server Functions Matter In A Server Component World
- Why are React Server Components actually beneficial? (full history)
- Component testing RSCs
- Experimenting with React Server Components and Vite
- React Server Components, without a framework?
- Simple RSC With Vinxi
- Ultimate Guide to Visual Testing with Playwright
- Drag and Drop with React. Writing E2E tests using Playwright
- Build Your first end-to-end test with Playwright - Training | Microsoft Learn
- Speed up your Playwright tests
- Modern React testing, part 5: Playwright by Artem Sapegin
- Running End-to-End Tests with Playwright on AWS Lambda | Lari Haataja
- 크고 작은 동적 표시 영역 단위
- viewport-resize-behavior/explainer.md at main · bramus/viewport-resize-behavior · GitHub
- react-spectrum/packages/@react-aria/utils/src/useViewportSize.ts at 1b425caa26b5c137263c8b15fe2ef74ed1bb34e6 · adobe/react-spectrum · GitHub
- react-spectrum/packages/@react-aria/overlays/src/useOverlayPosition.ts at 1b425caa26b5c137263c8b15fe2ef74ed1bb34e6 · adobe/react-spectrum · GitHub
- react-spectrum/packages/@react-aria/overlays/src/usePreventScroll.ts at 1b425caa26b5c137263c8b15fe2ef74ed1bb34e6 · adobe/react-spectrum · GitHub
- react-spectrum/packages/@react-spectrum/overlays/src/Modal.tsx at 1b425caa26b5c137263c8b15fe2ef74ed1bb34e6 · adobe/react-spectrum · GitHub
export function parseNativeEmoji(unified: string): string {
return unified
.split('-')
.map((hex) => String.fromCodePoint(parseInt(hex, 16)))
.join('')
}
unified
문자열을-
로 분리하여 개별 유니코드 코드 포인트를 얻는다.- 각 16진수 코드 포인트를 정수로 변환하고, 그에 해당하는 캐릭터를 반환한다.
- 최종적으로 변환된 캐릭터들을 연결하여 하나의 문자열로 생성한다.
How To Maintain A Large Next.js Application — Smashing Magazine
- TypeScript 사용
- Lerna , Nx , Rush , Turborepo , yarn workspaces를 사용하여 Mono-Repo 구조 사용
- Hygen과 같은 코드 생성기를 사용하여 상용구 코드 생성
- Redux 툴킷을 통해 하위 상용구와 함께 Redux와 같이 잘 설정된 패턴 사용
- 비동기 데이터를 가져오기 위해 React 쿼리 또는 SWR 사용
- Husky와 함께 Commitizen 및 Semantic Release 사용
- UI 구성 요소 시각화를 위해 스토리북 사용
- 처음부터 유지 관리 가능한 테스트 작성
- Dependabot을 사용하여 자동으로 패키지 업데이트
- Going to Production | Next.js
import * as React from 'react'
type Props<TRow> = {
rows: TRow[]
renderRow: (row: TRow, index: number) => React.ReactNode
}
const Table = <TRow extends Record<string, any>>({
rows,
renderRow,
}: Props<TRow>) => {
return (
<table>
<tbody>{rows.map((row, index) => renderRow(row, index))}</tbody>
</table>
)
}
function App() {
return (
<Table
rows={[{ name: 'lee' }]}
renderRow={(row, index) => (
<tr key={index}>
<td>{row.name}</td>
</tr>
)}
/>
)
}
import * as React from 'react'
import { UseComboboxProps, useCombobox } from 'downshift'
function Combobox<T extends Record<string, any>>(
props: UseComboboxProps<T> & {
renderItem: (item: T) => React.ReactNode
}
) {
const combobox = useCombobox(props)
return (
<div>
<input
placeholder="구성원을 검색해주세요."
{...combobox.getInputProps()}
/>
<div {...combobox.getMenuProps()}>
{combobox.isOpen &&
props.items.map((item, index) => (
<div key={item.id} {...combobox.getItemProps({ item, index })}>
{props.renderItem(item)}
</div>
))}
</div>
</div>
)
}
function App() {
return (
<Combobox
items={[{ id: 1, name: 'eunsoo' }]}
itemToString={(item) => `${item?.id}`}
renderItem={(item) => {
return <div>{item.name}</div>
}}
onInputValueChange={({ inputValue }) => {
console.log(inputValue)
}}
/>
)
}
import * as React from 'react'
type Prettify<T> = {
[K in keyof T]: T[K]
} & {}
type T = React.ComponentProps<'div'>
type P = Prettify<T>
import * as React from 'react'
import { IsEqual } from 'type-plus'
type ModalProps =
| {
variant: 'no-title'
}
| {
variant: 'title'
title: string
}
export const Modal = (props: ModalProps) => {
if (props.variant === 'no-title') {
return <div>No title</div>
} else {
type Test = IsEqual<
typeof props,
{
variant: 'title'
title: string
}
>
return <div>Title: {props.title}</div>
}
}
export const Test = () => {
return (
<div>
<Modal variant="title" title="Hello" />
<Modal variant="no-title" />
{/* @ts-expect-error */}
<Modal />
<Modal
variant="no-title"
// @ts-expect-error
title="Oh dear"
/>
</div>
)
}
import * as React from 'react'
export type NonEmptyString<T> = Exclude<T, ''>
type Props<T> = {
src: NonEmptyString<T>
}
function Image<T extends string>({ src }: Props<T>) {
return <img src={src} alt="" />
}
function App() {
return (
<>
{/* @ts-expect-error */}
<Image src="" />
<Image src="ab" />
</>
)
}
type Reducer<S, A> = (state: S, action: A) => S
type Listener<S> = (state: S) => void
interface Store<S, A> {
getState: () => S
subscribe: (listener: Listener<S>) => () => void
dispatch: (action: A) => A
}
function createStore<S, A>(
reducer: Reducer<S, A>,
preloadedState: S
): Store<S, A> {
let currentState: S = preloadedState
let listeners: Listener<S>[] = []
function getState(): S {
return currentState
}
function subscribe(listener: Listener<S>): () => void {
listener(currentState)
listeners.push(listener)
return function unsubscribe() {
listeners = listeners.filter((l) => l !== listener)
}
}
function dispatch(action: A): A {
currentState = reducer(currentState, action)
listeners.forEach((listener) => {
listener(currentState)
})
return action
}
return {
subscribe,
getState,
dispatch,
}
}
type Item = {
body: string
}
type State = {
items: Item[]
}
type Action = {
type: 'ADD'
payload: Item
}
const store = createStore<State, Action>(
(state, action) => {
switch (action.type) {
case 'ADD':
return {
...state,
items: state.items.concat(action.payload),
}
default:
return state
}
},
{
items: [],
}
)
store.subscribe((state) => {
console.log(state.items)
})
store.dispatch({
type: 'ADD',
payload: {
body: 'hello',
},
})
type Task = (callback: (result: string) => void) => void
type TasksCallback = (results: string[]) => void
class TaskRunner {
protected tasks: Task[] = []
protected results: Array<string> = []
addTask(task: Task) {
this.tasks.push(task)
}
}
class ParallelTaskRunner extends TaskRunner {
run(callback: TasksCallback) {
const totalTasks = this.tasks.length
this.tasks.forEach((task) => {
task((result) => {
this.results.push(result)
if (this.results.size === totalTasks) {
callback([...this.results])
}
})
})
}
}
class SerialTaskRunner extends TaskRunner {
index = 0
run(callback: TasksCallback) {
const executeTask = () => {
if (this.index >= this.tasks.length) {
callback([...this.results])
return
}
this.tasks[this.index]((result) => {
this.results.push(result)
this.index++
executeTask()
})
}
executeTask()
}
}
Footnotes
Advanced Features: Static HTML Export | Next.js
next export
앱의 HTML 버전을 빌드. .out
디렉토리에 빌드된 페이지 파일을 복사합니다.
const isVisible = document.visibilityState === 'visible'
const isHidden = document.visibilityState === 'hidden'
document.addEventListener('visibilitychange', onChange)
/**
* `visibilitychange` 이벤트는 정상적인 탭 전환 시에는 잘 작동하지만, 시스템 슬립, 화면 잠금, 또는 브라우저가 백그라운드에서 복귀할 때는 누락될 수 있다.
* 그래서 수동으로 처리되는 부분이 필요
*/
document.addEventListener('mousemove', setVisible)
document.addEventListener('keydown', setVisible)
const [isVisible, setIsVisible] = useState(true)
useEffect(() => {
const onChange = () => {
const newState = document.visibilityState !== 'hidden'
if (newState !== isVisible) {
setIsVisible(newState)
}
}
// addEventListener
return () => {
// removeEventListener
}
}, [isVisible])
llms.txt
는 웹사이트나 애플리케이션이 자신이 사용하는 LLM(대규모 언어 모델) 및 관련 설정에 대해 명시적으로 문서화할 수 있는 포맷이다.
아래 링크들은 llms.txt
포맷이 실제로 어떻게 사용되고 있는지 참고한 자료들. 각 사이트는 자신들의 문서를 llms.txt
에 구조적으로 명시하고 있다.
Serverless 환경에서 SQLite 문제 및 대안 정리
문제 상황
- 개인 프로젝트를 Vercel에 배포한 후, SQLite 관련 에러가 발생
- 읽기 전용으로 데이터 파일을 올려 사용하는 방식이 기존에는 잘 작동했으나, 최근 환경에서는 에러 발생
- Vercel 같은 플랫폼에서는 SQLite 사용이 공식적으로 지원되지 않음
문제 해결 접근
대안 라이브러리 탐색 (JSON 기반)
- 정적인 데이터로 쓸 경우 JSON 변환을 고려
- 후보 라이브러리:
해결: Turso 도입
- Turso: LibSQL 기반의 SQLite-compatible serverless DB
- 기존 SQLite 쿼리 그대로 사용 가능하며, Vercel에서도 정상 작동
- serverless 환경에 특화된 구조로 신뢰성 및 확장성 확보
console.assert(process.env.VERCEL === '1')
{
"tailwindcss": "tailwindcss -i ./src/index.css -o ./src/tailwind.css",
"start": "concurrently \"yarn tailwindcss --watch\"",
"prebuild": "yarn tailwindcss --minify",
}
create-react-app 구형버젼(+eject)에서 설치할 경우 연관된 부분이 많아서 차라리 cli를 사용하는게 편한 상황. 그런데 concurrently
1로 프로세스를 동시에 실행시켜야 되는 부분이 있다.
Footnotes
- Migrating to Next.js: Migrating from Create React App | Next.js
- Replacing Create React App with the Next.js CLI · GitHub
개인적으로는 Next.js를 이용한 방법이 가장 좋아보인다.
window.*
이 많다면 사전에 준비해두는게 좋을 것 같다.
Footnotes
const formData = new FormData();
const single = document.querySelector('input[type="file"]');
const multiple = document.querySelector('input[type="file"][multiple]');
formData.append('single', single.files[0]);
[...multiple].forEach((file, index) => {
formData.append(`multiple_${index}`, file);
})
fetch('https://example.com/posts', {
method: 'POST',
body: formData,
})
- Node.js Child Processes: Everything you need to know1
- The definitive Node.js handbook
- Tao of Node - Design, Architecture & Best Practices
- Useful Built-in Node.js APIs
glob
- GitHub - isaacs/node-glob
- GitHub - mrmlnc/fast-glob
- GitHub - sindresorhus/globby
- GitHub - SuperchupuDev/tinyglobby
Footnotes
-
spawn(), exec(), execFile(), fork() ↩
imagemin을 사용하려고 하는데 module로 라이브러리가 업데이트 되어서 찾아본 내용. 혼란스러운 부분도 있지만 성숙해지는 과정이라고 본다.
- Pure ESM package · GitHub
- Get Ready For ESM. JavaScript Modules will soon be a… | by Sindre Sorhus | 🦄 Sindre Sorhus’ blog
- Hello, Modules!. JavaScript Modules, also known as ESM… | by Sindre Sorhus | 🦄 Sindre Sorhus’ blog
- Publish ESM and CJS in a single package1
Footnotes
이미지 다운로드 구현. 이미지 응답값을 buffer로 변환해서 파일쓰기로 저장한다.
const fs = require('fs')
const util = require('util')
const fetch = require('node-fetch')
const writeFile = util.promisify(fs.writeFile)
const mkdir = util.promisify(fs.mkdir)
const FOLDER_PATH = 'FOLDER_PATH'
async function download({ url }) {
const response = await fetch(url)
const buffer = Buffer.from(await response.arrayBuffer())
if (!fs.existsSync(FOLDER_PATH)) {
await mkdir(FOLDER_PATH)
}
await writeFile(url, buffer)
}
- How to Modify Nodes in an Abstract Syntax Tree | CSS-Tricks1
- AST for JavaScript developers. TL;DR This article is my talk for… | by Bohdan Liashenko | ITNEXT2
- GitHub - NV/CSSOM: Unmaintained! ⚠️ CSS Object Model implemented in pure JavaScript. Also, a CSS parser.3
- GitHub - csstree/csstree: A tool set for CSS including fast detailed parser, walker, generator and lexer based on W3C specs and browser implementations
Footnotes
import sharp from 'sharp'
import fg from 'fast-glob'
const entries = await fg('./**/*.png')
for (const entry of entries) {
const trimmedBuffer = await sharp(entry).trim().toBuffer()
const trimmedImage = sharp(trimmedBuffer)
const trimmedMetadata = await trimmedImage.metadata()
trimmedImage
.resize({
width: Math.round(trimmedMetadata.width / 2),
height: Math.round(trimmedMetadata.height / 2),
})
.png()
.toFile('output.png')
}
offset(top, left)값, 이미지 사이즈 설정으로 crop 구현이 가능하다
sharp('img.png')
.extract({
left: 50,
top: 50,
width: 200,
height: 400
})
음악 파일에서 메타데이터를 읽고 쓰기 위한 라이브러리
Footnotes
- yarn upgrade-interactive | Yarn1
- Find newer versions of package dependencies than what your package.json allows2
Footnotes
-
이 명령은 업그레이드를 수행하기 전에 오래된 패키지를 표시하여 사용자가 업그레이드할 패키지를 선택할 수 있도록 합니다. ↩
-
npm-check-updates는 지정된 버전을 무시하고 package.json 종속성을 최신 버전으로 업그레이드합니다 . ↩
// package.json 파일에 로컬 경로를 지정하는 방법. 파일 시스템에 있는 패키지 디렉터리를 사용할 수 있음.
{
"dependencies": {
"bar": "file:../foo/bar"
}
}
CSV와는 다르다! CSV와는!
const fs = require('fs')
const XLSX = require('xlsx')
const buf = fs.readFileSync('developer_labels_20181221.xlsx')
const workbook = XLSX.read(buf, { type: 'buffer' })
const roa = XLSX.utils.sheet_to_json(workbook.Sheets[workbook.SheetNames[0]], {
header: 1,
})
const result = roa
.slice(1)
.map((item) => item.slice(1, 4))
.filter((item) => item.some(Boolean))
.reduce(
(p, n) => {
const [key, en, ko] = n
const newKey = key.toLowerCase().replace(/\-/g, '_')
p['en'][newKey] = en
p['ko'][newKey] = ko
return p
},
{ en: {}, ko: {} }
)
fs.writeFileSync('en.json', JSON.stringify(result.en), 'utf8')
fs.writeFileSync('ko.json', JSON.stringify(result.ko), 'utf8')
# 특정 패키지에 모듈을 설치해야한다면
yarn workspace <workspace_name> <command>
localtunnel은 쉽게 테스트하고 공유할 수 있도록 로컬 호스트를 공개합니다! 다른 사람들이 변경 사항을 테스트하도록 하기 위해 DNS를 엉망으로 만들거나 배포할 필요가 없습니다.
app.listen(PORT, async () => {
const tunnel = await localtunnel({
port: PORT,
subdomain: name,
})
하지만 너무 느려서 ngrok 쓰는게 현실적일수도 있겠다. -20220917
Footnotes
-
로컬 환경에서 웹훅 테스트를 위해, API 엔드포인트를 만들고 localtunnel이나 ngrok을 사용하기. ↩
charles
- The Android Emulator and Charles Proxy: A Love Story | by Mark Dappollone | Medium
- Is it possible to rewrite a status code with Charles Proxy? - Stack Overflow
fiddler
mitmproxy
- mitmproxy로 iOS 기기의 네트워크 트래픽 살펴보기 :: Outsider’s Dev Story
- Android nougat 이상 emulator에서 mitmproxy 사용하기 | by Jungwook Park | kjcoop | Medium
Footnotes
Designing a JavaScript Plugin System | CSS-Tricks1
Footnotes
-
플러그인은 라이브러리와 프레임워크의 공통 기능이며 개발자가 안전하고 확장 가능한 방식으로 기능을 추가할 수 있도록 한다. 그래서 추가 유지 관리 부담이 없다. ↩
서비스워커로 fetch를 감지해서 해당 기능을 구현한다는 내용. 개인적으로는 mock은 간단하게 구현 가능할 것 같은데 이미 같은 기능의 잘 만들어진 라이브러리들이 있으니까 아이디어 정도로 생각하면 될 것 같다.
addEventListener('fetch', e => {
// e.request
// e.respondWith
})
redux 사용하다가 trace가 필요하면 해당설정을 활성화 시켜주면 된다. 메모리릭 가능성이 있기 때문에…우회 한다면 약간 무식한 방법이긴 하지만 거기다 싶은 지점에서 console.trace()를
- Command Line Interface Guidelines
- 커맨드라인 사용법: 따라하며 배우는 리눅스 명령어와 관습들 | 44BITS
- GitHub - Idnan/bash-guide: A guide to learn bash
- View Mac Calendar from Command Line1
- Replit - CLUI: Building a Graphical Command Line2
- Getting started with oclif by creating a todo cli app | by Nguyễn Việt Hưng | The happy lone guy | Medium3
- Build a Command Line Weather App in Deno - SitePoint4
- 인간 친화적인 cURL의 대안, HTTPie56
- Create Aliases in Bash7
- CLI: improved8
- Command-line Basics: Creating Files and Directories ← Alligator.io9
- Command-line Basics: Finding and Replacing Text in Files with sed10
- Writing a debug script | Kitty Giraudel11
- fig12
- Powerful Terminal And Command-Line (CLI) Tools For Modern Web Development — Smashing Magazine13
- How To Write Shell Scripts in Node with Google’s zx Library - SitePoint
- Learn X in Y Minutes: Scenic Programming Language Tours
Footnotes
-
cal
이 단순한 달력을 보여주지만 ical-buddy는 맥에 연결된 달력을 보여주는 유틸리티 ↩ -
gui의 현재와 cli에서 개선될 수 있는 부분들(접근성, 발견가능성, 상호작용)을 만들어 나가는 내용 ↩
-
todo 예제로 oclif의 편리한 기능을 알려주고 있다 ↩
-
deno에서
std
,date_fns
,ascii_table
모듈을 활용해 날씨 cli앱을 만드는 과정 소개 ↩ -
curl보다 좀 더 직관적인 인터페이스로 진입장벽을 낮춘 클라이언트 ↩
-
간단한 alias 설정과, arguments값을 받을 수 있도록 .bash_profile에 설정하는 방법 소개 ↩
-
cat, du, find…등등등 기존 명령어에서 개선이 가능한 부분을 설명해주고 있다. ↩
-
mkdir, touch, 텍스트 > 파일로 생성하기에 대한 설명 ↩
-
sed
명령어 간단한 사용방법 ↩ -
doctor에서 영감을 받아 여러 작업 환경에 대한 정보를 출력해주는 스크립트를 작성한 내용을 알려주고 있다. 구두로 불필요하게 소비되는 부분이 많은데 정리해볼 만한 내용. ↩
-
Fig는 VSCode 스타일의 자동 완성을 기존 터미널에 추가합니다. ↩
-
여러가지 터미널 도구들 정리한 글 ↩
서버/프론트 구분이 없는 환경일 경우 옜날에는 이렇게 세팅해서 개발
const bs = require('browser-sync').create()
bs.init({
port: 8081,
https: false,
proxy: 'http://localhost:8080',
serveStatic: [
{
route: '/resources',
dir: 'src/main/webapp/resources',
},
],
files: 'src/main/webapp/resources/**/*',
open: false,
})
extractFrames() {
ffmpeg -i $1 -vf fps=30 output_frame_%d.png
}
# $1 = frames로 사용될 파일들
# $2 = output 파일
# Example: concatFrames ./frame_%5d.png output.webm
concatFrames() {
ffmpeg -framerate 30 -i $1 -c:v libvpx-vp9 -pix_fmt yuva420p $2
}
# $1 = frame로 사용될 파일
# $2 = output 파일
# Example: frameToVideo frame.jpg output.mp4
frameToVideo() {
ffmpeg -loop 1 -i $1 -c:v libx264 -t 10 -pix_fmt yuv420p $2
}
# $1 = 인코딩할 영상 video.mp4
# $2 = output 파일명
encodingVideo() {
ffmpeg -an -i $1 -vcodec libx264 -pix_fmt yuv420p -profile:v baseline -level 3 "${$2}.mp4"
ffmpeg -i "${$2}.mp4" -vcodec libvpx-vp9 -b:v 1M -acodec libvorbis "${$2}.webm"
}
# /home/USERNAME/.zshrc
HOME="/mnt/c/Users/cbcru"
DL="$HOME/Downloads"
if [[ $PWD == $HOME ]]; then
cd $DL
fi
- Poker API - Simple JSON poker API to calculate winning hand1
- 검색 API 백과사전 검색 개발가이드 - NAVER Developers
- Tasks API | Google Developers
- YouTube | Google Developers
- Cloud Talent Solution 구인/구직 API
- Likes introduction | Docs | Twitter Developer Platform2
- Weather API - OpenWeatherMap
- Unsplash Image API
- WrapAPI: APIs for the whole web
- Foreign exchange rates and currency conversion API
Footnotes
- 혜택은 웹뷰로 존재함
- GraphQL을 사용 중
useSWR(
`{
pages {
user_group
highlight_color
page_list {
type
page_id
page_name
is_new
}
}
}`,
(query) => request('https://api.zigzag.kr/api/2/graphql', query)
)
검색기능을 정말 간단하게 구현하고 싶을때: 프로그래밍 가능한 검색 엔진을 사용하면 웹사이트, 블로그 또는 웹사이트 모음에 대한 검색 엔진을 만들 수 있습니다. 웹 페이지와 이미지를 모두 검색하도록 엔진을 구성할 수 있습니다. 순위를 미세 조정하고 자신의 프로모션을 추가하고 검색 결과의 모양과 느낌을 사용자 지정할 수 있습니다. 엔진을 Google 애드센스 계정에 연결하여 검색으로 수익을 창출할 수 있습니다.
- Use the Notion API to Create a Quiz with JavaScript - SitePoint
- Collecting Email Signups With the Notion API | CSS-Tricks
- 통합(integration)을 생성합니다.1
- 통합과 데이터베이스 공유
2.1
•••
->Add connections
Footnotes
Quick tip: reusable Array search predicates - JASON Format
arr.filter(callback(element[, index[, array]])[, thisArg])
배열 메서드에서 2번째 인자 thisArg
에 참조값을 전달해서 재사용 가능한 함수를 만드는 트릭. 단 성능 이슈가 있으므로 주의해야 한다.
- Objects - GitHub Docs1
- GitHub Next | Flat Data2
- GitHub Code Search (Preview)34
- Writing workflows - GitHub Docs
Footnotes
-
GitHub GraphQL API를 통해 쿼리할 수 있는 다양한 개체에 대한 개요. ↩
-
GitHub에서 플랫 데이터를 사용하는 방법에 대한 튜토리얼. 플랫 데이터는 개발자가 API 또는 CSV 파일과 같은 다양한 소스의 데이터를 GitHub 리포지토리로 쉽게 가져올 수 있는 기능입니다. 이 자습서에서는 GitHub Actions와 함께 플랫 데이터를 사용하여 다양한 소스에서 데이터 가져오기를 자동화하는 방법과 가져온 데이터를 사용하여 시각화 및 대화형 웹 애플리케이션을 만드는 방법을 다룹니다. 또한 플랫 데이터를 사용하는 방법을 보여주는 예제 프로젝트에 대한 링크를 제공합니다. 여기에는 Google 스프레드시트를 사용한 플랫 데이터 데모 저장소와 독일의 코로나19 백신 접종 기록 데이터가 포함된 저장소가 포함됩니다. ↩
-
사용자가 GitHub의 공개 리포지토리 내에서 코드 조각을 검색할 수 있는 GitHub 코드 검색에 대한 링크입니다. 다른 개발자의 코드를 빠르게 찾고 탐색할 수 있는 방법을 제공하며 새로운 라이브러리, 프레임워크 및 모범 사례를 찾는 데 사용할 수 있습니다. 검색 결과는 리포지토리, 언어, 파일 유형 및 기타 기준으로 필터링할 수 있으며 관련성, 별 또는 분기별로 정렬할 수 있습니다. ↩
Cool Cats: The Coolest NFT’s on the Blockchain!
const imageSrc = `https://s3.amazonaws.com/api.coolcatsnft.com/thumbnails/${ID}_thumbnail.png`
const query = {
sortBy: 'token_id_asc',
limit: 48,
page: 1,
face: 'angry',
hats: 'admiral',
shirt: 'astro',
tier: 'cool_1',
}
fetch(
'https://prod-api.coolcatsnft.com/cat?sortBy=token_id_asc&limit=48&page=1&tier=${tier}'
)
- face angry, angry cute, angry scar, beard brown, beard pirate, beard tan, derp, ditto, dizzy, double face, face face, glasses, glasses funny, glossy, grin, happy, happy cute, hearts, mononoke, mummy, ninja black, ninja blue, ninja red, owo, pixel, rich, shocked, smirk, stunned, sunglasses blue, sunglasses cool, sunglasses cowboy, sunglasses heart, sunglasses pixel, sunglasses squad, sunglasses yellow, three eyes, tired, tvface 404, tvface bobross, tvface nosignal, tvface xp, unamused, uwu, wink, zombie
- hat admiral, admiral pink, afro black, afro brown, afro rainbow unicorn, antlers, apple, arrowhead, astro, astro cheeks, astro fishbowl, beanie black, beanie blue, beanie orange, beanie red, beret black, beret green, beret pink, beret red, bow, bucket hat blue, bucket hat green, bucket hat tan, bucket hat white, candle, costume dragon, costume frog, costume gorilla, cowboy black, cowboy brown, crown black, crown fire, crown gold, cupcake, deepsea bronze, deepsea orange, dutch, flower blue, flower pink, flower red, goggles seaweed, halo, halo fire, hat black, hat skull, hat visor blue, hat visor yellow, hat white, headband blue, headband red, helm army, helm biker, helm bronze, helm silver, horns, knight black, knight blue, knight red, mohawk green, mohawk purple, mohawk red, mullet blonde, mullet brown, ninja black, ninja blue, ninja red, nurse, piercings, pirate black, pirate red, prince, sunhat black, sunhat tan, sunhat white, sushi, top hat, tvhead grey, tvhead purple, tvhead white, unicorn horn, visor green, visor purple, wreath, wreath flowers
- shirt astro, astro black, astro orange, bandana green, bandana purple, bandana red, baseball blue, baseball red, buttondown black flannel, buttondown blue flannel, buttondown green, buttondown red flannel, buttondown tan, chain, combat black, combat green, costume dragon, costume frog, costume gorilla, costume hotdog, cowboy black, cowboy brown, deepsea bronze, deepsea orange, epaulette black, epaulette red, epaulette white, gown black, gown purple, gown white, hoodie black, hoodie purple, hoodie red, knight, knight black, knight leather, labcoat, lederhosen, monk, mononoke, ninja black, ninja blue, ninja red, nurse, overalls blue, overalls flannel, overalls pink, overalls red, overalls yellow, pirate black, pirate red, punk, robe blue, robe king, robe red, robe white, shirt bowtie, shirt white, shirt yellow, sweater black, sweater green chain, sweater orange, sweater pink, tanktop orange, tanktop pink, tanktop sailor black, tanktop sailor blue, tanktop sailor red, tanktop tattoo, tanktop white, tiger, toga, tshirt blue, tshirt green, tshirt metal, tshirt pink, tshirt red, tshirt white, tshirt yellow, viking brown, viking navy, wetsuit, winter blue, winter red, work blue, work red
- tier cool_1, cool_2, wild_1, wild_2, classy_1, classy_2, exotic_1, exotic_2
function getReverseGeocodeData() {
const response = Maps.newGeocoder()
.setLanguage('ko')
.reverseGeocode(40.758577, -73.984464)
return response.results.map((result) => {
return {
formatted_address: result.formatted_address,
lat: result.geometry.location.lat,
lng: result.geometry.location.lng,
}
})
}
스크롤바 스타일링 및 제어
- Scrollbars on Hover | CSS-Tricks
- Hide Scrollbars During an Animation | CSS-Tricks
- 🌟 스크롤 바(Scrollbar) 스타일링 💯 총정리
스크롤 동작 및 성능 최적화
- An Overview of Scroll Technologies | CSS-Tricks
- Memorize Scroll Position Across Page Loads | CSS-Tricks
- Now You See Me: How To Defer, Lazy-Load And Act With IntersectionObserver
커스텀 스크롤바 라이브러리
스크롤 기반 네비게이션 & 스크롤스파이
GSAP 기반 스크롤 애니메이션
Framer Motion & React 기반 스크롤 애니메이션
- Scroll Animations with Framer Motion
- Scroll animations | Motion for React (prev Framer Motion)
- GitHub - frontendfyi/scroll-animations-with-framer-motion-codesandbox-projects
Parallax
hash 링크로 연결될 경우 스크롤위치가 최상단으로 위치하기 때문에 문제(헤더가 고정일 경우)가 있을수도 있어서 scroll-margin-top
으로 제어가 가능한 부분을 설명하고 있다.
- Add scroll margin to all elements which can be targeted - Piccalilli1
- Fixed Headers and Jump Links? The Solution is scroll-margin-top | CSS-Tricks
- Prevent content from being hidden underneath a fixed header by using scroll-margin-top – Bram.us
Footnotes
-
2ex
유닛을 사용하여 선택한 글꼴의 x 높이의 상대적인 크기로 설정. ↩
- Let’s Make One of Those Fancy Scrolling Animations Used on Apple Product Pages | CSS-Tricks1
- Scroll-Linked Animations With the Web Animations API (WAAPI) and ScrollTimeline | CSS-Tricks
Footnotes
-
애플 웹사이트를 예제로 해서 스크롤 기반 canvas 이미지 교체하는 방식을 설명하고 있다. 문제는 이미지 리소스 크기가 너무 크다. ↩
- Automatic Social Share Images | ryanfiller.com1
- AddyOsmani.com - Web Performance Recipes With Puppeteer2
- Puppeteer is my new dev server - Eric Bidelman3
- GitHub - GoogleChromeLabs/carlo: Web rendering surface for Node applications
- List of Chromium Command Line Switches « Peter Beverloo4
- How to use Puppeteer in a Netlify (AWS Lambda) function5
- Dealing with file downloads in puppeteer · browserless docs6
- How to fix M1 Mac Puppeteer chromium arm64 bug7
Footnotes
httpOnly, secure 플래그 쿠키값이 필요한 경우 CDPSession 실행 후 클라이언트와 통신한다.
const client = await page.target().createCDPSession()
const data = await client.send('Network.getAllCookies')
로컬에 해당 쿠키정보를 임시로 저장해서 재활용하고 만료 시 갱신하는 방법을 사용.
fs.writeFileSync('cookies.json', JSON.stringify(data))
const { cookies } = JSON.parse(fs.readFileSync('cookies.json', 'utf8'))
await page.setCookie(...cookies)
setExtraHTTPHeaders
호출하기
await page.setExtraHTTPHeaders(headers)
인터셉트로 가로채기
await page.setRequestInterception(true)
page.on('request', (request) => {
const headers = {
...request.headers()
}
interceptedRequest({ headers })
})
- Comparing Browsers for Responsive Design | CSS-Tricks1
- 알아두면 도움되는 iTerm2 고급기능. 궁극의 Mac Terminal Client | by Harry The Great | 해리의 유목코딩 | Medium
- AddyOsmani.com - Visualize Data Structures in VSCode
- Typed webhook testing2
Footnotes
-
다양한 디바이스 사이즈 확인이 필요할 경우 유용한 앱 소개. 개인적으로는 responsively.app 추천 ↩
-
자동 유형 생성으로 페이로드 확인을 위한 웹훅 테스트 도구 ↩
이미지 저장1
- 페이지에서 JavaScript를 통해 이미지 추출
- 캔버스에서 이미지 추출
- 서버에서 이미지 가져오기
- DevTools 프로토콜을 사용하여 이미지 추출
const tree = await page._client.send('Page.getResourceTree')
for (const resource of tree.frameTree.resources) {
const { content } = await page._client.send(
'Page.getResourceContent',
{ frameId: String(page.mainFrame()._id), url: resource.url },
)
const contentBuffer = Buffer.from(content, 'base64')
}
page.on('response', async (response) => {
const url = response.url()
const buffer = await response.buffer()
})
Footnotes
- 파일질라 서버를 설치
- Edit->Users->Add / 사용자 추가
- 제어판->시스템 및 보안->Windows Defender 방화벽->허용되는 앱 / (설정변경|다른 앱 허용) 파일질라 서버 추가
Using Slack Slash Commands to Send Data from Slack into Google Sheets
- 사람들이 추천한 책을 기록한다
- 슬랙 -> 구글시트
- 슬랙 커맨드 설정
/book
+ 구글앱스 스크립트 url 연결1 - POST로 전송 받은 데이터값을 기반으로 데이터 처리 완료
Footnotes
-
꿀벌개발일지 :: 구글 앱스 스크립트에서 비동기 작업 추가하기 doPost에서 비동기 처리 ↩