블로그
전체 62023. 09. 17.
3
turbo prune는 도커하고 친해지고 싶어
서론turbo prune이 다시 돌아왔습니다! 저번에는 이 명령어가 터보레포의 특정 워크스페이스를 도커 이미지로 빌드할 때 왜 필요한지, 또한 기본적으로 어떻게 동작하는지 확인했습니다. 거기에 더해 하나를 더 알아볼 겁니다. 기존의 방법으로도 도커 이미지를 빌드하기에는 충분하지만 완벽하지는 않습니다. 해결하지 못한 작은 문제가 하나 있거든요.문제점질문입니다! 만약, 도커 이미지의 빌드에 사용될 파일들 중에 디펜던시와 관련이 없는 것이 변경된다면 어떻게 될까요? 이런 상황에서 도커는 기존의 캐시를 활용하지 않습니다! 디펜던시가 변하지 않았어도 계속 새로 설치가 된다는 괴담을 맞이하게 되는 겁니다. 정말 무서운 일입니다.이 질문은 turbo prune으로 생성된 가지치기된 모노레포를 그대로 가져와 빌드할 때 생기는 문제와도 이어져 있습니다. 사실 그 상황이 그대로 발생합니다! 해당 모노레포에 해당하는 워크스페이스의 파일이 하나라도 추가 혹은 변경된다면 디펜던시는 언제나 다시 처음부터 설치됩니다... 으아악!!해결방법?모든 문제는 디펜던시와 관련 없는 파일이 수정될 때 도커가 이를 감지하고 캐시를 깨트리면서 생겼습니다. 그렇다면 도커가 캐시를 깨트리지 않게 만들면 되겠군요! 아예 디펜던시와 관련있는 파일만 모아 설치를 진행하면 모든 문제가 해결될 것만 같습니다.도커에서 이런 일을 더욱 쉽게 만들어주는 기능이 무엇일까요? 바로 스테이지입니다! 각각의 스테이지는 독립된 캐시를 가지고, 파일을 입맛에 맞게 골라 사용할 수 있으니까요.그렇다면 우리는 디펜던시를 설치하는 스테이지와 빌드하는 스테이지를 나누고 설치 스테이지에 디펜던시와 관련있는 파일만 포함시켜야 합니다. 즉, 모노레포에서 락파일과 package.json만 쏙 빼서 설치 스테이지에 넣어주는 과정을 거쳐야 하죠. 이제 해결 방법에 좀 더 가까워진 느낌이 듭니다! 하지만 머릿 속 한켠에 "완전 귀찮아!" 같은 생각이 무럭무럭 피어오르지는 않나요..? 경험담입니다.괜찮습니다. 여러분들은 터보레포를 사용하고 있으니까요!--docker역시나! 터보레포는 이 문제도 이미 해결할 수단을 마련해 놓았습니다. --docker 옵션이 바로 그 것입니다.turbo prune --scope="a-workspace"이전에 우리는 다음과 같은 명령어로 가지치기된 모노레포를 만들었습니다. 그 모노레포의 구조는 다음과 같습니다:. ├── apps/ │ └── a-workspaces/ // example-package-1만 사용 │ ├── package.json │ └── src/ ├── packages/ │ └── example-package-1/ │ ├── index.ts │ └── package.json └── pnpm-lock.yaml언제나 보던 익숙한 구조입니다. 그렇다면 이번에는 --docker 옵션을 사용해 봅시다.turbo prune --scope="a-workspace" --docker기존의 명령어의 뒤에 --docker를 적어주면 됩니다. 이 것을 통해 만들어진 모노레포의 구조는 다음과 같습니다:. ├── full │ ├── apps/ │ │ └── a-workspaces/ // example-package-1만 사용 │ │ ├── package.json │ │ └── src/ │ └── packages/ │ └── example-package-1/ │ ├── index.ts │ └── package.json ├── json │ ├── apps/ │ │ └── a-workspaces/ │ │ └── package.json │ └── packages/ │ └── example-package-1/ │ └── package.json └── pnpm-lock.yaml전의 구조와 무언가 달라보입니다. 우리가 살펴봐야 할 부분은 나눠져 있는 full, json 디렉토리의 용도입니다.json 디렉토리는 이 옵션의 핵심입니다. full 디렉토리와 비교해 보세요. 무언가 빠진 부분이 있지 않나요? json 디렉토리는 바로 full 디렉토리에서 오직 package.json만을 가져와 만들어졌습니다. 그렇습니다, 바로 이겁니다! 우리가 원하던 거에요!--docker로 분리된 디렉토리 덕분에 설치, 빌드 스테이지를 분리해 줄 수 있습니다. 설치 스테이지에서는 락파일과 json 디렉토리를 가져와 오직 디펜던시의 설치만, 빌드 스테이지에서는 설치 스테이지에서 설치된 디펜던시들과 full 디렉토리에서 가져온 소스 파일들을 가져와 빌드만 하게 만드는 것이죠.이제 설치 스테이지는 디펜던시 설치에 반드시 필요한 락파일과 package.json만을 가질 수 있게 되었습니다. 더 이상 설치 스테이지에서 디펜던시와 관련되지 않은 다른 파일의 변경으로 캐시가 깨지는 일은 생기지 않습니다. 문제 해결입니다!TMI도움이 됐나요?다행입니다! 해피 해킹!References[\`turbo prune\` – Turborepo](https://turbo.build/repo/docs/reference/command-line-reference/prune)[Deploying with Docker – Turborepo](https://turbo.build/repo/docs/handbook/deploying-with-docker)[Optimizing builds with cache management | Docker Docs](https://docs.docker.com/build/cache/)
프론트엔드
・
turborepo
2023. 09. 12.
6
turborepo는 가지치기가 필요해
서론요즘 핫하고 핫한 모노레포 빌드 시스템이 있습니다. 이름하야 터보레포(turborepo), 무려 Nextjs를 만들고 있는 Vercel에서 제작했습니다.Nextjs를 무진장 사랑하는 저는 반드시 찍어 먹어봐야 되겠다고 생각을 했습니다. 그래서 간단한 앱을 만들어 빌드까지 해보기로 했죠!오늘의 주제는 바로 그 빌드와 관련 있습니다. 재밌는 부분을 찾았거든요!간단하게 말씀드리자면, 터보레포는 가지치기가 필요합니다. 엥?도커는 빠른 최적화를 원해요갑자기 가지치기라니, 이게 무슨 말일까요? 터보레포가 드디어 그 사악한 속을 드러내고 우리의 소중한 코드를 잘라내 버리겠다는 의미일까요? 다행히도 그건 아닙니다.터보레포의 가지치기에 대해 말하기에 앞서 우리는 도커가 어떻게 nodejs 환경에서 이미지를 만들어 내는지 살펴봐야 합니다. => [separator 2/4] RUN pnpm add turbo 17.1s => [installer 2/5] COPY .gitignore .gitignore 0.0s => [separator 3/4] COPY . . 0.4s => [separator 4/4] RUN pnpm turbo prune --scope=api --docker 1.0s => [installer 3/5] COPY --from=separator /app/out/json/ . 0.0s => [installer 4/5] COPY --from=separator /app/out/pnpm-lock.yaml ./pnpm-lock.yaml 0.0s => [installer 5/5] RUN pnpm install 16.1s => [builder 2/4] COPY --from=installer /app . 6.5s => [builder 3/4] COPY --from=separator /app/out/full . 0.0s => [builder 4/4] RUN pnpm turbo run build --filter=api 7.2s => [runner 2/2] COPY --from=builder /app . 7.5s => CACHED [separator 2/4] RUN pnpm add turbo 0.0s => CACHED [separator 3/4] COPY . . 0.0s => CACHED [separator 4/4] RUN pnpm turbo prune --scope=api --docker 0.0s => CACHED [installer 3/5] COPY --from=separator /app/out/json/ . 0.0s => CACHED [installer 4/5] COPY --from=separator /app/out/pnpm-lock.yaml ./pnpm-lock.yaml 0.0s => CACHED [installer 5/5] RUN pnpm install 0.0s => CACHED [builder 2/4] COPY --from=installer /app . 0.0s => CACHED [builder 3/4] COPY --from=separator /app/out/full . 0.0s => CACHED [builder 4/4] RUN pnpm turbo run build --filter=api 0.0s => CACHED [runner 2/2] COPY --from=builder /app . 0.0s도커는 package.json와 락파일(lockfile)을 전의 빌드와 비교해 변화가 있을 때 비로소 패키지를 설치합니다. 변화가 없다면 이전의 캐시된 결과를 그대로 가져다 쓰는 것이죠. 위의 로그가 그 예시 중 하나입니다.첫번째는 package.json이 변경된 후 빌드된 이미지의 로그입니다. 옆의 시간을 확인해 보세요. 두번째는 첫번째 빌드 이후 package.json을 변경하지 않고 빌드한 이미지의 로그입니다. CACHED라고 나와 있는 것이 보이나요? 다시 한 번 시간을 확인해 보세요. 0.0s, 압도적 시간! 바로 도커가 이 전에 캐시된 것을 그대로 가져와 사용했기 때문에 가능했던 일입니다.천방지축 어리둥절 빙글빙글 돌아가는 터포레포 디펜던시이 좋은 방법을 터포레포에서도 사용해야 되겠죠. 그런데요, 문제가 하나 있습니다. 터보레포는 바로 모노레포 빌드 시스템이라는 겁니다!모노레포, 즉 터보레포는 락파일을 전역으로 관리합니다. A, B 워크스페이스가 있을 때 B 워크스페이스에서 디펜던시가 변경된다면 그 사항이 그대로 락파일에 반영되는 것이죠. 이 때 디펜던시가 변경되지 않은 A 워크스페이스를 도커로 빌드한다면요? 아차차, 도커는 락파일이 변경된 것을 보고 새로 디펜던시들을 설치하기 시작합니다. 락파일! 락파일이 도커를 완벽하게 속여 먹인 겁니다!대체 이 문제를 어떻게 해결해야 할까요, 맞습니다. 가지치기가 등장할 시간입니다.가지치기된 모노레포터포레포도 진작에 이 문제를 파악하고 있었습니다:a. 터포레포는 락파일을 전역으로 관리하기 때문에 효율적인 도커 빌드가 불가능하다b. 그럼 빌드할 워크스페이스만 똑때서 락파일을 만든다음 도커에게 주면 되는 거 아닌가a. 헐b. ?이렇게 turbo prune가 탄생합니다. 그 이름에서도 보여지듯이 이 명령어의 기능은 간단합니다. 바로 특정 워크스페이스의 가지치기된 모노레포를 만들어 주는 것이죠!. ├── apps/ │ ├── a-workspace/ // example-package-1만 사용 │ │ ├── package.json │ │ └── src/ │ └── b-workspace/ // example-package-1, 2 둘 다 사용 │ ├── package.json │ └── src/ ├── packages/ │ ├── example-package-1/ │ │ ├── index.ts │ │ └── package.json │ └── example-package-2/ │ ├── index.ts │ └── package.json └── pnpm-lock.yaml예제 앱의 디렉토리 구조입니다. a-workspace는 example-package-1 패키지만 사용하고 있고 a-workspace는 1, 2 둘 다 사용하고 있네요. 그렇다면 turbo prune을 사용해 a-workspace만 빼내봅시다.turbo prune --scope="a-workspace"이 명령어를 실행한다면:. ├── apps/ │ └── a-workspaces/ // example-package-1만 사용 │ ├── package.json │ └── src/ ├── packages/ │ └── example-package-1/ │ ├── index.ts │ └── package.json └── pnpm-lock.yamlA 워크스페이스의 가지치기된 모노레포를 만들어 줍니다. 물론 가지치기된 락파일도 같이요! 이 락파일은 오직 A 워크스페이스의 디펜던시만을 담고 있어 B 워크스페이스의 디펜던시가 변경된다고 하더라도 영향을 받지 않습니다.이 가지치기된 모노레포를 도커로 빌드하게 된다면 더 이상 락파일에 머리를 감싸매지 않아도 됩니다!TMI도움이 됐나요?다행입니다! 오늘도 좋은 하루 보내세요!References[Best practices for writing Dockerfiles | Docker Docs](https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#use-multi-stage-builds)[\`turbo prune\` – Turborepo](https://turbo.build/repo/docs/reference/command-line-reference/prune)[Deploying with Docker – Turborepo](https://turbo.build/repo/docs/handbook/deploying-with-docker)
프론트엔드
・
turborepo
・
docker
2023. 01. 28.
1
RTK 쿼리는 SSR을 하고 싶어!
서론요즘 RTK 쿼리와 함께 Nextjs에서 SSR을 적용하는데 꽤 시간을 들이고 있습니다. 물론 리액트 쿼리라는 걸출한 패키지가 존재합니다. 그런데 어쩌겠습니까, 저의 마음 속 한 켠에는 이미 RTK 쿼리에 대한 열망이 불타올랐던 겁니다!... 사실은 리덕스로 노는 와중에 찾아볼 게 있어서 문서에 들어갔다가 영업당한 것에 가깝습니다. 이 글을 작성할 동기를 제공한 리덕스 공식 문서에게 크나큰 박수를 보내주시면 감사하겠습니다.그래서 사용하는 김에 SSR까지 적용해보면 어떨까? 라는 생각을 했습니다. 그렇습니다, 여러분. 오늘 글의 주제입니다.RTK 쿼리는 외로워요SSR의 원리는 간단합니다. 클라이언트사이드에서 요청을 보내면 서버사이드에서 원래 클라이언트사이드에서 했어야 할 작업들을 대신 수행해 주는 겁니다. 그리고 그 결과가 적용된 말끔한 HTML을 응답해 줍니다.Nextjs는 이런 작업을 아주 멋지게 처리해 줄 getServerSideProps라는 함수를 제공합니다. 해야 하는 작업을 수행한 후 얻은 데이터를 페이지 컴포넌트의 props로 넣어 주는거죠.이 쯤에서 문제가 생깁니다. 우리는 props가 아닌 RTK 쿼리를 사용해야 하기 때문입니다. getServerSideProps 함수 안에서 RTK 쿼리를 디스패치하는 부분까지는 가능합니다. 그러나 클라이언트사이드의 RTK 쿼리는 서버사이드에서 무슨 일이 생겼는지 알지 못합니다. 서버사이드의 RTK 쿼리 혼자 외롭게 남아있게 되는 겁니다.그럼 어떻게 해야 할까요? 어떤 방식으로 서버사이드의 RTK 쿼리 스토어를 클라이언트사이드로 전달해 줄 수 있을까요?next-redux-wrappernext-redux-wrapper 패키지가 나설 차례입니다. 이 패키지는 서버사이드의 스토어를 클라이언트사이드 스토어에 덮어 씌워 줍니다. 서버사이드에서 실행된 RTK 쿼리를 클라이언트사이드로 가져오는 게 가능해진다는 의미이기도 합니다.store.tsnext-redux-wrapper를 적용하기 위해 약간의 수정이 필요합니다.// store.ts /** * configureStore를 감싸는 makeStore 함수를 작성 * @param context `next-redux-wrapper`에서 제공하는 context */ export const makeStore = (context: Context) => { return configureStore({ reducer: { something: somethingReducer, [rtkQueryApi.reducerPath]: rtkQueryApi.reducer, }, middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat( rtkQueryApi.middleware, ), }); }; // makeStore 함수의 ReturnType를 가져와 타입 작성 type AppStore = ReturnType; export type AppState = ReturnType; export type AppDispatch = AppStore["dispatch"]; /** * `next-redux-wrapper`에서 제공하는 `createWrapper` 함수로 만들어진 wrapper를 export */ export const wrapper = createWrapper(makeStore, {debug: true}); // ^ // 작성된 makeStore 함수를 넘김다음과 같이 리덕스 스토어를 설정합니다. configureStore 함수로 만들어진 store를 바로 쓰는 대신 next-redux-wrapper 패키지에서 제공하는 createWrapper 함수로 wrapper를 만들어 사용합니다._app.tsx// _app.tsx function MyApp({ Component, pageProps }: AppProps) { return ( ); } // `store.ts`에서 생성된 wrapper로 MyApp 컴포넌트를 감쌈 export default wrapper.withRedux(MyApp)react-redux 패키지의 Provider 컴포넌트로 감싸던 기존의 방식 대신 wrapper.withRedux 함수로 MyApp 컴포넌트를 감쌉니다.좋습니다. next-redux-wrapper를 사용할 준비가 끝났습니다.SSR이제 RTK 쿼리를 서버사이드에서 사용해봅시다. 생각보다 정말 간단합니다.// store를 쓰기 위해 작성한 `wrapper`의 `getServerSideProps` 함수를 사용함 export const getServerSideProps = wrapper.getServerSideProps( (store) => async (context) => { // RTK 쿼리 디스패치 store.dispatch(rtkQueryApi.endpoints.getSomething.initiate({})); // 실행된 것이 끝날 때까지 대기 await Promise.all(cropApi.util.getRunningOperationPromises()); return { props: {}, }; } );다음과 같이 RTK 쿼리를 서버사이드에서 실행할 수 있습니다.하지만 문제가 생기게 됩니다. 클라이언트사이드의 스토어는 아무 것도 바뀌지 않았습니다. next-reudx-wrapper 패키지를 설정하는 과정에서 문제라도 생겼던 걸까요?RTK Query 설정이런, RTK Query의 api를 확인해 보니 한 가지 설정을 빠뜨렸습니다.export const rtkQueryApi = createApi({ ... // 서버사이드에서 실행된 쿼리를 클라이언트에 hydrate extractRehydrationInfo(action, { reducerPath }) { if (action.type === HYDRATE) { // ^ // next-redux-wrapper에서 제공 // 실행된 쿼리(reducerPath)를 리턴 return action.payload[reducerPath]; } } ... });HYDRATE 액션 때 서버사이드에서 실행된 쿼리를 전해주는 extractRehydrationInfo 함수를 설정해줘야 합니다.혹시 다 설정해줘야 하냐구요? 맞습니다. 서버사이드에서 실행될 예정인 쿼리와 뮤테이션을 다루는 createApi 함수는 모두 같은 설정을 해야 합니다. 안하면 쿼리와 뮤테이션들이 외로워 합니다.확인해봅시다이제 제대로 데이터가 전해졌는지 확인해봅시다.redux devtools에 들어가 액션을 살펴보세요. __NEXT_REDUX_WRAPPER_HYDRATE__ 안에 원하는 데이터가 들어 있다면 성공입니다.사용const ExampleComponent = () => { const { data } = useGetMyCropsQuery({}); return ( { data ? {data.something} : ... } ) }기존과 같이 훅으로 사용합니다.TMI왜 Provider 컴포넌트를 사용하지 않나요?next-redux-wrapper가 Provider 컴포넌트의 역할을 합니다.도움이 됐나요?날씨가 추우니 손도 어는 기분이네요. 여러분들은 따뜻한 손으로 개발하고 있으시죠?그렇다고요? 좋습니다, 오늘도 즐거운 개발 되세요!References[createApi | Redux Toolkit # extractRehydrationInfo](https://redux-toolkit.js.org/rtk-query/api/createApi#extractrehydrationinfo)[Server Side Rendering | Redux Toolkit](https://redux-toolkit.js.org/rtk-query/usage/server-side-rendering)[GitHub - kirill-konshin/next-redux-wrapper: Redux wrapper for Next.js](https://github.com/kirill-konshin/next-redux-wrapper)
프론트엔드
・
SSR
・
RTKQuery
・
Redux
・
ReduxToolkit
・
ReduxToolkitQuery
・
Nextjs
2022. 12. 25.
3
nextjs는 typescript가 무섭대요
서론어느 한 겨울의 이야기 입니다. 그는 nextjs와 함께 즐겁게 코드를 짜고 있었습니다. 어느 때와 같이 리덕스 스토어에 사용되는 union type을 수정하고 저장을 한 다음 정상적으로 컴파일된 걸 확인합니다.그 순간 그의 등줄기를 타고 소름이 돋습니다. 무언가 이상함을 느낍니다.그는 스크롤을 내립니다. 드르륵- 드드륵- 한 오브젝트가 보입니다. 그는 떨리는 눈으로 오브젝트의 타입을 봤습니다. mapped type, 그는 오래 전에 어느 타입을 이 오브젝트의 키로 사용할 수 있게 만든겁니다. 그렇습니다. 그가 방금 수정한 타입이었습니다. 그는 이윽고 오브젝트를 선명하게 가로지르는 구불구불한 빨간색 선을 보게 됩니다.그는 키보드에 툭 떨어진 식은땀을 볼 틈도 없이 모니터에 온 신경을 집중합니다. 이게 무슨, 타입 에러가 있다면.. 대체 어떻게 컴파일이 된거지? 으아악! ...... 이라는 괴담을 아시나요?대부분 타입을 등한시한 타입스크립트의 저주라고 웃고 넘기지만 사실 더 큰 비밀이 숨겨져 있습니다.사실 nextjs는.. 타입 체킹을 안합니다!!!! 으아아악!dev와 타입 체킹nextjs가 타입 체킹을 지원하지 않는다니 이게 무슨 소리인가요, 빌드에서 타입 에러가 뜨는 걸 이 두 눈으로 똑똑히 봤는데!여러분의 말이 맞습니다. 빌드 때 타입 에러가 잡힙니다. 그렇다면 dev는 어떨까요? 위의 오싹오싹한 괴담과 같은 상황을 만들고 저장을 해보겠습니다.new의 경우를 입력하지 않았습니다. 여러분은 someObject를 가로지르는 빨간 선을 보게 될 겁니다. 타입 에러가 발생했습니다!터미널에는 어떤 일이 일어났을까요?우리의 dev는 그런 건 상관하지 않습니다. 미동없이 고요한 터미널을 볼 수 있을 겁니다.맞습니다. dev는 타입 체킹을 하지 않습니다. 이럴수가...nextjs는 fast refresh가 필요해요모든 일은 nextjs 9.4에서 fast refresh가 정식으로 적용되며 일어났습니다. 기존의 핫 리로딩 방식은 변경이 생길 때마다 앱 전체를 리로드 했습니다. 이런 방식은 느리고 에러 해결에도 좋지 않았습니다. 그걸 극복하기 위해 등장한 것이 fast refresh 입니다.좋은 변화였습니다. 수정한 코드로 인한 변화를 즉각적으로 확인할 수 있음과 더불어 더 정확하게 런타임 에러를 확인할 수 있게 되었습니다. 그런데요, 문제가 발생합니다. 타입 체킹은 너무 시간이 오래 걸렸던 겁니다. 최대한 빠른 리로딩 경험을 제공하고 싶었던 nextjs는 타입 체킹을 빼 버립니다!이를 되돌리는 옵션도 제공하지 않았습니다. nextjs 개발자 분들의 의지는 확고했던 겁니다.우리는 타입 체킹이 필요해요그 확고함과는 달리 항상 typescript의 든든함에 기대고 있던 저는 수정한 즉시 진행되는 타입 체킹이 그리웠습니다. 하지만 nextjs의 버전이 13이 될 때까지도 타입 체킹 지원은 까마득히 멀기만 합니다. 방법을 찾아야만 합니다. 타입 체킹을 다시 불러낼 방법을요. 다행히 저와 같은 생각을 가지고 있는 분들이 꽤 있었습니다.여러분들에게 두가지 방법을 소개합니다.vscodetypescript.tsserver.experimental.enableProjectDiagnosticsvscode는 자체적으로 타입 체킹을 지원하지만 열려있는 파일만 가능합니다. 그렇다면 모든 파일을 체크하게 만든다면 어떨까요?typescript.tsserver.experimental.enableProjectDiagnostics가 가능하게 합니다. 프로젝트의 파일들을 열지 않아도 발생하는 에러를 확인할 수 있게 만들어 줍니다.다만 이 기능은 실험적입니다. 에러들이 발생할 가능성이 높으며 개선될 여지는 낮습니다.tasksvscode의 tasks를 사용하는 방법도 있습니다. tasks는 실행 중 발생한 에러를 Problems view에 띄워줄 수 있습니다. 이를 통해 tsc를 실행하면 어떻게 될까요?tsc로 변경된 파일들을 감시하고 발생한 오류를 Problems view에 표시되게 만들 수 있습니다.terminal잠시만요, 타입 체킹 후 발생한 에러를 vscode를 통해 확인하는 게 마음에 들지 않는 경우는요? 수상할 정도로 터미널을 사랑하는 분들은 오직 터미널에서 모든 걸 확인하고 처리하고 싶을 겁니다.그렇다면 이 방법을 사용하세요.concurrently 패키지는 명령어의 동시 실행과 함께 [TS]와 같은 접두사로 어떤 명령어에서 나온 로그인지 구별하기 쉽게 만들어 줍니다. 이를 사용해 nextjs dev 서버와 tsc를 동시에 실행시켜 로그를 확인할 수 있습니다.TMItsconfig.json을 확인하세요tsconfig.json가 다음처럼 설정되어 있는지 확인하세요.noEmit이 true가 되어야 출력 파일을 만들지 않습니다. tsc를 타입 체킹의 용도로만 사용할 것이기 때문입니다incremental이 true여야 incremental compilation이 됩니다. 속도가 더 빠릅니다타입 에러 발생 시 중단은 안됩니다 사실 타입 에러가 발생했을 때 앱의 실행을 막고 싶었습니다. 무심코 지나치는 일 없이 확실히 해결할 수 있게요!안타깝게도 그런 방법은 찾을 수 없었습니다. nextjs 자체에서 지원해야 될텐데 현재로서는 가망이 없다고 생각이 듭니다. 타입 체킹 자체가 굉장히x3 빨라지는 날이 온다면 가능할 수도 있지만요! 그 날이 오기까지는 마음 한 켠에 담아두는 걸로 했습니다. 흑흑...도움이 되었나요?되었다고요? 다행입니다!!날씨가 아직도 꽤 춥네요. 오늘도 따뜻하고 즐거운 개발이 되셨음 합니다.Referencetasks[Tasks in Visual Studio Code](https://code.visualstudio.com/Docs/editor/tasks#_examples-of-tasks-in-action)[Visual Studio Code Tasks Appendix](https://code.visualstudio.com/docs/editor/tasks-appendix)[Blog - Next.js 9.4 | Next.js](https://nextjs.org/blog/next-9-4)(https://github.com/microsoft/vscode/issues/13953)[TypeScript: Documentation - tsc CLI Options](https://www.typescriptlang.org/ko/docs/handbook/compiler-options.html)[enableProjectDiagnostics problem · Issue #168410 · microsoft/vscode](https://github.com/microsoft/vscode/issues/168410)[Feature Request: Show all errors and warnings in project for all JavaScript and TypeScript files, not just opened ones · Issue #13953 · microsoft/vscode](https://github.com/microsoft/vscode/issues/13953)[Typescript \`next dev\` error reporting · Discussion #33634 · vercel/next.js](https://github.com/vercel/next.js/discussions/33634)
프론트엔드
・
nextjs
・
typescript
・
vscode
2022. 12. 15.
4
vscode는 tw.macro 자동 완성의 꿈을 꾸는가?
서론emotion과 tw.macro로 즐거운 시간을 보내고 있던 어느 날, 여러분은 이상한 점을 발견하게 됩니다.자동 완성에 아무 것도 나타나지 않습니다! 당황한 나머지 익스텐션을 다시 깔아봐도 자동 완성은 꿈쩍도 않습니다.기존의 방식대로 입력을 하니 비로소 제대로 작동합니다.이게 무슨 일인가요? 혹시 얼어 죽어도 class단이 마침내 그 사악한 손길을 자동 완성으로까지 뻗은 건 아닐까요?! 꽤 흥미롭지만, 아닙니다. 해결 방법이 있으니까요.자동 완성을 돌려줘!모든 문제는 공식 tailwindcss 익스텐션이 attributes를 사용하지 않는 상황을 염두하지 않고 만들어져 발생했습니다.tailwindCSS.classAttributes The HTML attributes for which to provide class completions, hover previews, linting etc. Default: class, className, ngClass tailwindCss.classAttribute에 tailwindcss 익스텐션은 HTML attributes에만 자동 완성을 제공합니다. 즉, class=""와 같은 형식에 자동 완성을 제공합니다. 아, 그럼 저기에 tw를 집어넣으면 되지 않겠나고요? 좋은 시도지만 일부분만 해결해 줄 뿐입니다. tw=""은 자동 완성이 되겠지만 emotion을 사용하는 경우는 해결되지 않으니까요.그래서 익스텐션 개발자는 여러 상황에도 자동 완성을 가능하게 만들기 위해서 experimental 설정을 제공했습니다. 이름하야, tailwindCSS.experimental.classRegex입니다.{ ..., "tailwindCSS.experimental.classRegex": [ "tw`([^`]*)", // tw`...` "tw=\"([^\"]*)", // "tw={\"([^\"}]*)", // "tw\\.\\w+`([^`]*)", // tw.xxx`...` "tw\\(.*?\\)`([^`]*)" // tw(Component)`...` ] }이 설정은 간단합니다. Regex로 자동 완성의 적용을 원하는 경우를 싹 다 적어서 넣으렴! 이라는거죠.해당 설정을 입맛에 맞게 수정한 다음, vscode의 settings.json에 적어주세요. 이렇게 한다면...우리의 친구 자동 완성이 돌아왔습니다!우리 오래 가자......!TMI도움이 됐나요?그렇다구요? 다행입니다!!오늘도 즐거운 개발이 되셨음 합니다!Reference[Custom class name completion contexts · Issue #7553 · tailwindlabs/tailwindcss · GitHub](https://github.com/tailwindlabs/tailwindcss/issues/7553)
프론트엔드
・
tailwindcss
・
twin.macro
・
completions
・
autocomplete
・
vscode
2022. 12. 14.
4
nextjs 12에서 emotion과 함께하는 tailwindcss
Nextjs 12 is coming...nextjs 12에서 놀라운 소식이 전해졌습니다. 컴파일러로 swc를 채택했다는 것을요! 여러가지 이유가 있겠지만 가장 중요한 건 기존에 사용하던 babel보다 빌드가 최대 5배나 빨라졌다는 겁니다. 참고로 fast refresh는 최대 3배나 빨라졌다네요!좋은 소식입니다. 하지만 변화에는 언제나 문제가 발생하기 마련입니다. swc를 활성화하기 위해서는 .babelrc와 같은 바벨 설정 파일을 완전히 제거해야 합니다. 이런, 그럼 무조건 바벨을 설정해야 하는 경우는요?그렇습니다. 문제의 발생입니다! 특히 twin.macro을 아끼는 저는 그만 눈물을 닦아낼 수 밖에 없었습니다. 어떤 분께서 이를 대체하는 stailwc를 만들었지만 아직은 이르다는 느낌이 어렴풋이 들고 맙니다. 그렇다면, twin.macro를 포기할 수 밖에 없는 걸까요? 아닙니다!emotionyarn add @emotion/react @emotion/styled or npm install @emotion/react @emotaion/styledemotion을 설치합니다.tailwindcssyarn add tailwindcss or npm install tailwindcss tailwindcss만 설치하면 됩니다.twin.macropackagesyarn add twin.macro babel-loader babel-plugin-macros @babel/plugin-syntax-typescript @babel/preset-react or npm install twin.macro babel-loader babel-plugin-macros @babel/plugin-syntax-typescript @babel/preset-reacttwin.macro와 함께 설정에 필요한 babel 관련 패키지를 설치합니다. 상황에 따라 더 늘어날 수도 있습니다.configurationwithTwin.jsconst path = require("path"); const includedDirs = [ path.resolve(__dirname, "components"), path.resolve(__dirname, "pages"), path.resolve(__dirname, "styles"), ]; module.exports = function withTwin(nextConfig) { return { ...nextConfig, webpack(config, options) { const { dev, isServer } = options; config.module = config.module || {}; config.module.rules = config.module.rules || []; config.module.rules.push({ test: /\.(tsx|ts)$/, include: includedDirs, use: [ options.defaultLoaders.babel, { loader: "babel-loader", options: { sourceMaps: dev, presets: [ [ "@babel/preset-react", { runtime: "automatic", importSource: "@emotion/react" }, ], ], plugins: [ require.resolve("babel-plugin-macros"), require.resolve("@emotion/babel-plugin"), [ require.resolve("@babel/plugin-syntax-typescript"), { isTSX: true }, ], ], }, }, ], }); if (!isServer) { config.resolve.fallback = { ...(config.resolve.fallback || {}), fs: false, module: false, path: false, os: false, crypto: false, }; } if (typeof nextConfig.webpack === "function") { return nextConfig.webpack(config, options); } else { return config; } }, }; };babel 설정 파일을 대신해 babel을 설정합니다.next.config.jsconst withTwin = require("./withTwin"); const nextConfig = withTwin({ // 작성한 withTwin 함수를 적용해 nextjs를 설정합니다.typestsconfig.json{ ..., "types": [ "@types" ] }타입을 인식할 디렉토리를 설정합니다.@types/twin.d.tsimport "twin.macro"; import { css as cssImport } from "@emotion/react"; import styledImport from "@emotion/styled"; import { CSSInterpolation } from "@emotion/serialize"; // `twin.macro`에 다음 타입을 넣음 declare module "twin.macro" { const styled: typeof styledImport; const css: typeof cssImport; } // DOM의 attribute에 다음 타입을 넣음 declare module "react" { interface DOMAttributes { tw?: string; css?: CSSInterpolation; } }twin.macro와 관련된 타입을 설정합니다.TMI사용하고 있는 tailwindcss에 적용할 수 있나요?네, class로 tailwindcss를 사용하고 있더라도 설치, 설정을 통해 twin.macro를 사용할 수 있습니다.왜 css 관련 설정을 하지 않나요?twin.macro는 입력받은 클래스들을 독립적으로 사용 가능한 css로 변환합니다. 따로 정의된 css의 도움을 받지 않고서요.이 때 변환은 twin.macro로 이뤄집니다. class로도 tailwindcss를 사용해야 한다면 css 설정을 거쳐야 합니다.도움이 됐나요?됐다구요? 다행입니다!즐거운 개발이 되길 바랄게요!reference[GitHub - ben-rogerson/twin.macro: 🦹♂️ Twin blends the magic of Tailwind with the flexibility of css-in-js (emotion, styled-components, stitches and goober) at build time.](https://github.com/ben-rogerson/twin.macro)[Enable SWC on next examples · ben-rogerson/twin.examples@36ac8c6 · GitHub](https://github.com/ben-rogerson/twin.examples/commit/36ac8c6dcfa80fcf9cfd65b5c4835b8f3aa79c00#diff-8e7430aee7d110ee12e0366c43b2e8328e0eae8fe870a88eae4bbb7532ec26e1)[Support SWC · Discussion #516 · ben-rogerson/twin.macro · GitHub](https://github.com/ben-rogerson/twin.macro/discussions/516)[How to config Nextjs for Babel Plugin Macros like twin.macro without disabling swc complier](https://blog.mrcatdev.com/how-to-config-nextjs-for-babel-plugin-macros-like-twinmacro-without-disabling-swc-compiler)
프론트엔드
・
nextjs
・
tailwindcss
・
emotion
・
swc
・
twin.macro