February 18, 2023 • ☕️☕️ 11 min read
수요일 꿀휴가
?raw
vite에서 이미지, 미디어, 폰트 파일 타입 이외의 asset을 import하려면 [assetsInclude](https://vitejs-kr.github.io/config/shared-options.html#assetsinclude)
설정이 필요하다.
assetsInclude
없어도 가능한 방법이 있는데, 바로 접미사를 이용해 URL로 에셋을 가져오는 것이다.
?raw
도 그중 하나의 접미사인데, 이 접미사로 import하는 애셋은 문자열로 취급한다.
import shaderString from "./shader.glsl?raw";
기존에는, 표현식이 특정 타입과 매칭되는지 체크하는 것과 표현식의 추론 타입에서 가장 구체적인 타입을 정상적으로 추론하는 것이 상충됐다.
type Colors = "red" | "green" | "blue";
type RGB = [red: number, green: number, blue: number];
const palette: Record<Colors, string | RGB> = {
red: [255, 0, 0],
green: "#00ff00",
bleu: [0, 0, 255],
// ✅ 'blue'를 'bleu'라고 잘못 적은 typo는 잡힌다.
};
// 🚨 Property 'at' does not exist on type 'string | RGB'.
const redComponent = palette.red.at(0);
객체의 키를 Colors
타입으로 한정했기 때문에, palette
객체에 잘못 작성한 'blue'
typo의 오타는 잡힌다.
그러나 palette.red
의 타입이 number[]
가 아닌 string
이 될 수 있어서, at()
메서드에서 타입 에러가 난다.
타입스크립트 4.9에 추가된 satisfies
연산자는 표현식의 결과 타입을 바꾸지 않으면서, 표현식의 타입이 특정한 타입과 매칭되는지 검증한다.
type Colors = "red" | "green" | "blue";
type RGB = [red: number, green: number, blue: number];
const palette = {
red: [255, 0, 0],
green: "#00ff00",
bleu: [0, 0, 255],
// ✅ 'blue'를 'bleu'라고 잘못 적은 typo는 잡힌다.
} satisfies Record<Colors, string | RGB>;
// ✅ 이제 에러가 나지 않는다!
const redComponent = palette.red.at(0);
const greenNormalized = palette.green.toUpperCase();
satisfies
연산자를 통해 palette
의 모든 프로퍼티가 string | number[]
와 호환될 수 있음을 보장한다.
Ref https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-9.html
vanilla-extract는 ‘바닐라 추출액’으로 식재료를 가리키긴 하는데… 😖 자바스크립트 세계에서는 vanilla-extract라는, 타입스크립트로 작성된 제로-런타임 스타일시트를 가리킨다.
vanilla-extract의 Sprinkles는 제로-런타임 아토믹 CSS 프레임워크로, 커스텀 유틸리티 클래스들을 만들고 빌드타임이나 런타임에 이들을 조합해서 사용할 수 있다.
sprinkles.css.ts
파일을 만들고, sprinkles
함수를 설정하고 export한다.
// sprinkles.css.ts
import { defineProperties, createSprinkles } from "@vanilla-extract/sprinkles";
const responsiveProperties = defineProperties({
conditions: {
mobile: {},
tablet: { "@media": "screen and (min-width: 768px)" },
desktop: { "@media": "screen and (min-width: 1024px)" },
},
defaultCondition: "mobile",
// ...
});
const colors = {
"blue-50": "#eff6ff",
"blue-100": "#dbeafe",
"blue-200": "#bfdbfe",
// ...
};
const colorProperties = defineProperties({
conditions: {
lightMode: {},
darkMode: { "@media": "(prefers-color-scheme: dark)" },
},
defaultCondition: "lightMode",
properties: {
color: colors,
background: colors,
// etc.
},
});
export const sprinkles = createSprinkles(responsiveProperties, colorProperties);
Ref https://vanilla-extract.style/documentation/sprinkles-api/
React.suspense는 Promise가 발생하면 자식의 스타일을 display: none
으로 바꾼다. Promise가 resolve 되면 display: none
속성은 삭제된다.
Ref https://github.com/facebook/react/issues/14536
React useEffect의 clean up 함수는 useEffect가 실행되고, 다음 실행되기 전에 다시 실행된다.
useFieldArray
를 쓸 수 있다.string[]
은 안 되고, { id: string }[]
은 가능하다. 말 그대로 필드의 배열이기 때문에 각각이 필드여야 한다.~field.${index}.id
를 useController
hook의 name prop으로 전달한다.module federation을 사용해서 여러 remote app을 불러올 때, 각 자바스크립트 chunk 파일의 이름은 다를 수 있지만, id는 동일할 수 있다.
만약 동일한 id의 chunk를 호출하면 위에서 chunkId를 불러온 기록이 남겨져서 호출에 실패할수도 있다. 따라서 chunk 파일의 이름이 다르다고 안심하지 말고 id가 같다면 optimize 내용을 바꿔 빌드하거나, chunkId 길이를 바꿔서 id를 서로 다르게 해줘야 한다.
onBlur
→ onClick
onBlur
이벤트가 onClick
보다 이벤트 우선순위가 높다onMouseDown
이벤트는 onBlur
이벤트보다 이벤트 우선순위가 높다따라서 onClick
핸들러를 onBlur
보다 먼저 사용하고 싶다면, onMouseDown
을 사용한다.
자바스크립트 모든 이벤트의 순서를 알고 싶었는데, 못 찾겠다 😵💫
Ref https://velog.io/@broccoliindb/onBlur-on-react
new Axios()
vs axios.create()
new Axios()
로 axios 인스턴스를 생성하면, 배열이나 객체 등의 데이터가 JSON.stringify()
된 형식의 string으로 내려오는 경우가 있다. 🥲
대신 axios.create()
로 axios 인스턴스를 생성하면 똑바로 내려온다.
이유는… axios 깃헙 코드에서 찾을 수 있다.
new Axios()
를 호출하면 transformResponse()
의 설정 기본값이 없기 때문에 문자열 응답을 그냥 내려준다.
하지만 axios.create()
는 내부적으로 axios.createInstance() 함수를 호출하여 기본 설정을 넣어주기 때문에 응답이 정상적으로 JSON 형태로 반환된다.
suspense
옵션을 true로 켜두니 리액트 컴포넌트의 state를 deps로 받는 dependent query를 실행할 때 자꾸 이전 state 값으로 쿼리를 보내는 문제가 발생했다.
suspense를 켜놓기만 하고 fallback을 제공하는 <Suspense />
컴포넌트로 감싸지 않아서 버그가 난 듯… 싶었지만?
리액트 컴포넌트의 setState가 바라보고 있는 state는 최초 mount된 컴포넌트의 state이고, 우리가 변경하고자 하는 state는 API call이 성공한 후 다시 re-mount된 컴포넌트의 state이기 때문에, 이전 state의 값으로 api call을 하고 있었기 때문이다.
suspense를 유지하면서 이 문제를 해결하려면 어떻게 해야할까? 다음 링크로 알아보자 🤗
Ref https://sangminnn.tistory.com/76
정적 파일 최적화(Automatic Static Optimization)에 의해 정적으로 최적화된 페이지는 루트 매개 변수가 제공되지 않아서, query가 빈 객체가 된다
Next ver.10부터는 router.isReady
를 사용해서 router 필드가 클라이언트 측에서 업데이트되고 사용할 준비가 되었는지 여부를 체크할 수 있다.
:root
의사 클래스를 사용해서 전역 root tag를 명시적으로 선택할 수 있다. (html의 {}
과 기능적으로 동일하다.)satisfies
를 사용하면 required prop이 빠졌을 때 에러를 표시해주는 것은 유용해 보인다.-
npm run swizzle @docusaurus/theme-classic Footer -- --eject
import.meta.env
로 환경변수에 접근할 수 있다. "files.associations": {
"*.myformat": "json"
}
.d.ts
파일에서 import나 export를 하면 다른 파일에서 .d.ts
파일에서 선언된 전역 타입에 자동으로 접근할 수 없게 된다..md
에 [[_TOC_]]
를 쓰면 목차가 튀어나온다. Gitlab 전용 문법이고, 일부 목차를 제외하는 기능은 아직 없다. 누군가 깃랩에 건의했으나 4년째 묵혀있다.index.html
파일은 application의 entry point가 된다.width: fit-content
속성으로 flex component의 자식의 크기를 딱 맞게 지정할 수 있다.getElementsByTagName
을 사용한다. 왜 이렇게 헷갈리게 뒤죽박죽 해놨어! 🤯 (Ref)upsert()
- ON CONFLICT DO UPDATE
쿼리를 대신하는 함수로, 이미 존재하지 ‘않는’ 경우에만 새 엔티티를 삽입해준다. 짱! 👍구글의 ChatGPT, MS의 Bing을 잇는 AI의 붐… 노션AI가 발빠르게 탄생해버렸다.
원하는 주제로 입력하면, 글을 대신 써준다. 아이디어 브레인스토밍도 같이 해주고, 메일도 대신 써준다. 꽤나 재밌어 보인다. 알파 버전을 사용해보기 위해 웨이팅리스트에 넣고 기다려야 한다.
근데 이렇게 기계가 글 다 써주면 인간은 이제 모하나 🤷♀️
Ref https://www.notion.so/product/ai
전 세계 많은 웹사이트들에서, 그리고 나도 사용하는 core-js가 한 사람에 의해 시작된 오픈소스 라이브러리였다니 믿기지 않는다. 그리고 이 메인테이너가 큰 사고를 당하며 잠시 사라졌던 이야기, 돈이 되지 않는 오픈소스를 붙들고 온갖 미친 사람들한테 욕을 먹어가며 금전적으로 어려움을 겪어왔던 이야기들을 읽으며 충격을 받았다.
그의 말대로, 요즘 인터넷 세상을 장악한 온갖 대단한 사이트들이 core-js에 지탱하고 있는데, 아직도 고쳐야 할 코드들이 산더미인데 아무런 펀딩 없이 이렇게 진행되어왔다니 놀랍고 안타까울 따름이다… ‘익숙함에 속아 소중함을 잃지 말자’라는 말이 떠오른다. core-js가 무너지지 않았으면, 그리고 그가 그의 가족을 지킬 수 있었으면 좋겠다.
Ref https://github.com/zloirock/core-js/blob/master/docs/2023-02-14-so-whats-next.md
어떻게 읽는..? 처음에 ‘와이렛’ -> ‘와이래’ -> ‘와이라노…’라고 생각했다.
npm 스크립트를 더 똑똑하고 효율적으로 업그레이드 시켜주는 툴이라고 한다.
머하는 앤진 잘 모르겠다.
Node.js를 브라우저에서 실행하게 해주는 런타임이다. 익숙한 단어들의 조합이지만 오… 좀 혁신적인 것 같다.
사용자 코드를 실행하기 위해 가상머신(VM)을 사용했던 앱들은 이제 WebContainer를 사용해서 클라이언트에서 돌릴 수 있다.
import { WebContainer } from "@webcontainer/api";
// Call only once
const webcontainerInstance = await WebContainer.boot();
요렇게… 인스턴스를 만들어서 실행시키나 보다.
webcontainerInstance.on("server-ready", (port, url) => (iframeEl.src = url));
😲😲😲
수요일에 휴가를 써서 그런가, 일주일이 더 빨리 사라졌다. 평일 동안에 잠을 많이 잔 건지, 주말에 뻗어서 꿀낮잠을 오래오래 자버리고 말았다. 그래도 늦은 밤 잠이 또 온다 🥱
시간은 잘만 흐르고, 호주 갈 날이 점점 다가오고 있다… 기대 반 걱정 반 🙃