개발환경 구축[Next+Emotion+Tailwind+Twin.macro]

Aiden·2021년 6월 24일
5
post-thumbnail

네트워크엔지니어 주제에 이것저것 해보고 싶은게 많다.
백엔드는 NestJS를 사용을 하고있고, 앞으로도 그럴 것 같다. 프론트엔트는 React 경험이 있지만, 개인프로 젝트 목적상 SEO 가 가능해야하여 Next를 사용해 보기로 했다.

하여,
Typescript+Next+Emotion+Tailwind+Twin.macro+apollo로 개발환경을 구축하려 한다.

프로젝트 시작

Nextjs Github에는 Nextjs와의 다양한 조합의 예제들이 있다. 그 중 아래의 Boilerplate 사용한다.

https://github.com/vercel/next.js/tree/master/examples/with-typescript-eslint-jest

yarn create next-app --example with-typescript-eslint-jest test-next

디렉토리 구조 변경

쉬운 관리, 기존의 react와 유사한 디렉토리 구조로 변경하기 위해 pages폴더를 src폴더 바로 아래로 이동한다.

mkdir src && mv ./pages ./src/pages

절대경로 사용

import 시 ../../../ 와 같은 복잡한 형태를 피하기 위해 절대경로를 사용한다.

yarn add -D babel-plugin-module-resolver

1. Babel 설정

{
  "presets": ["next/babel"],
  "plugins": [
		"@emotion/babel-plugin",
	  "babel-plugin-macros",
    [
      "module-resolver",
      {
        "root": ["."],
        "alias": {
          "~": "./src"
        }
      }
    ]
  ]
}

.babelrc.js vs .babelrc.config.json

  • babel 설정 파일은 json, js, cjs, mjs 확장자로 사용 가능
  • js: 속도가 빠르지만, 캐싱과 관련하여 복잡성이 증가. babel 구성 api가 노출
  • json: config 함수를 캐싱해서 한 번만 호출

.babelrc.json vs .babelrc.config.json

  • .babelrc.json: 지역 설정
  • .babelrc.config.json: 전역 설정

하위 디렉토리에 override 할 별도의 설정이 필요하지 않으므로 .babelrc.json로 설정

mv .babelrc .babelrc.json

2. tsconfig.json 경로 설정

import 시 src/~/ 로 표현 가능

"compilerOptions": {
  ... 생략 ...
  "baseUrl": ".",
  "paths": { "~/*": ["src/*"] }
},

3. 디렉토리 구조

.
├── .babelrc.json
├── .eslintignore
├── .eslintrc.json
├── .git
├── .gitignore
├── .prettierignore
├── .prettierrc
├── README.md
├── jest.config.js
├── next-env.d.ts
├── node_modules
├── package.json
├── public
├── src
├── test
├── tsconfig.json
└── yarn.lock

Twin.macro

Tailwind CSS

  • Utility-First 컨셉을 가지고있는 CSS프레임 워크

Emotion

  • Styled-components 와 비슷한 CSS-in-JS 프레임워크

기존에 Styled-components는 써본터라 다른 CSS 프레임워크를 써보려는중, Tailwind CSS에 관심이 갔다. Tailwind의 경우 CSS를 깔끔하고 직관적으로 작성 가능한 장점이 있다. 하지만 이 역시도 코드안에서 className을 작성하다보면 지저분해지는 느낌을 받게 되었다.

그래서 Tailwind를 사용하면서 CSS-in-JS 와 같이 컴포넌트 별로 CSS를 작성해 분할 수 없을까 찾아보던 중 Twin.macro를 알게 되었다.

Twin.macro를 통해 Tailwind CSS와 emotion을 통합하여 사용한다.

1. 설치

yarn add @emotion/react @emotion/styled @emotion/css @emotion/server
yarn add -D twin.macro tailwindcss postcss@latest autoprefixer@latest @emotion/babel-plugin babel-plugin-macros

2. tailwind.config.js 생성

npx tailwindcss-cli@latest init -p

3. _app.tsx

  • 최초로 실행되며, 이곳에서 렌더링 하는 겂은 모든페이지에 영향

src/pages/_app.tsx

import { FC } from 'react'
import Head from 'next/head'
import { GlobalStyles } from 'twin.macro'
import { AppProps } from 'next/app'

const App: FC<AppProps>= ({ Component, pageProps }: AppProps) => (
  <>
    <Head>
      <title>
        Nextjs App with TypeScript, ESlint, Jest, Emotion, Tailwind and Twin
      </title>
      <link rel="icon" href="/favicon.ico" />
    </Head>
    <GlobalStyles />
    <Component {...pageProps} />
  </>
)

export default App

4. eslint 설정 변경

JSX.Element의 Props의 형태가 명시되어 있지 않으면 오류를 내도록 ESlint 규칙을 변경

.esllintrc.json

"rules": {
		... 생략 ...
    ~~"react/prop-types": 0,~~ // 삭제
    "@typescript-eslint/explicit-module-boundary-types": "off",
    ... 생략 ...

5. _document.tsx

  • meta 태그를 정의하거나, 전체 페이지에 관여하는 컴포넌트
  • 즉, 전역 파일이므로, 가능한 적은 코드만 넣어야 함
  • _app.tsx 이후에 실행 됨

src/pages/_document.tsx

import Document, {
  Html,
  Head,
  Main,
  NextScript,
  DocumentContext,
} from 'next/document'
import { extractCritical } from '@emotion/server'

export default class MyDocument extends Document {
  static async getInitialProps(ctx: DocumentContext) {
    const initialProps = await Document.getInitialProps(ctx)
    const page = await ctx.renderPage()
    const styles = extractCritical(page.html)
    return {
      ...initialProps,
      ...page,
      styles: (
        <>
          {initialProps.styles}
          <style
            data-emotion-css={styles.ids.join(' ')}
            dangerouslySetInnerHTML={{ __html: styles.css }}
          />
        </>
      ),
    }
  }

  render() {
    return (
      <Html lang="ko-kr">
        <Head />
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    )
  }
}

6. twin preset 설정

package.json

"lint-staged": {
    ... 생략 ...
  },
  "babelMacros": {
    "twin": {
      "preset": "emotion"
    }
  },
  "dependencies": {
		... 생략 ...

7. .babelrc.json 설정

.babelrc.json

{
  "presets": [
    [
      "next/babel",
      {
        "preset-react": {
          "runtime": "automatic",
          "importSource": "@emotion/react"
        }
      }
    ]
  ],
  "plugins": [
		... 생략 ...
	]
}

8. twin.macro 설정

twin.d.ts

import 'twin.macro'
import styledImport from '@emotion/styled'
import { css as cssImport } from '@emotion/react'

declare module 'twin.macro' {
  // The styled and css imports
  const styled: typeof styledImport
  const css: typeof cssImport
}
declare module 'react' {
  // The css prop
  interface HTMLAttributes<T> extends DOMAttributes<T> {
    css?: CSSProp
  }
  // The inline svg css prop
  interface SVGProps<T> extends SVGProps<SVGSVGElement> {
    css?: CSSProp
  }
}
// The 'as' prop on styled components
declare global {
  namespace JSX {
    interface IntrinsicAttributes<T> extends DOMAttributes<T> {
      as?: string
    }
  }
}

9. tsconfig.json 설정

tsconfig.json

{
 "compilerOptions": {
   ...
 },
 "files": ["twin.d.ts"], // 추가
 "exclude": ["node_modules", ".next", "out"],
 "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "**/*.js"]
}

GraphQL(Apollo Client)

1. 설치

yarn add graphql @apollo/client

2. apollo.ts

apollo client 사용을 위한 apollo 설정

src/apollo.ts

import {
  ApolloClient,
  InMemoryCache,
  createHttpLink,
  from,
} from '@apollo/client'
import { onError } from '@apollo/client/link/error'

const httpLink = createHttpLink({
  uri: 'http://localhost:4000/graphql',
})

const errorLink = onError(({ graphQLErrors, networkError }) => {
  if (graphQLErrors)
    graphQLErrors.map(({ message, locations, path }) =>
      console.log(
        `[GraphQL error]: Message: ${message}, Location: ${JSON.stringify(
          locations
        )}, Path: ${path}`
      )
    )
  if (networkError) console.log(`[Network error]: ${networkError}`)
})

export const client = new ApolloClient({
  link: from([errorLink, httpLink]),
  cache: new InMemoryCache(),
})

3. ApolloProvider

apollo client 사용을 위한 ApolloProvider 지정

src/pages/_app.tsx

import { FC } from 'react'
import { AppProps } from 'next/app'
import Head from 'next/head'
import { ApolloProvider } from '@apollo/client' // 추가
import { GlobalStyles, css } from 'twin.macro'
import { Global } from '@emotion/react'
import { client } from '~/apollo' // 추가

const App: FC<AppProps> = ({ Component, pageProps }: AppProps) => (
  <>
    <ApolloProvider client={client}> // 추가
      <Head>
        ... 생략 ...
      <Component {...pageProps} />
    </ApolloProvider> // 추가
  </>
)
...생략...

개인프로젝트를 진행할 프론트엔트 개발환경은 이렇게 진행할 예정🎉


참고

https://github.com/vercel/next.js/tree/canary/examples/with-typescript-eslint-jest

https://github.com/Dylan-Oleary/next-apollo-tailwind-typescript

https://github.com/ben-rogerson/twin.examples/tree/master/next-emotion

https://blog.songc.io/react/how-to-use-tailwind/

https://so-so.dev/web/tailwindcss-w-twin-macro-emotion/

profile
기억이 안되면 기록이라도🐳

0개의 댓글