-
[toy blog service] React Component를 Package화 하기(hayoung-markdown)Web Dev/5. Projects 2021. 4. 16. 23:10728x90
이걸 한 이유
이걸 왜했을까...를 하다보니 webpack설정과 npm link 관련 문제로 나도 너무 괴로워서 고민을 했는데, React 컴포넌트자체를 패키지화해서 내 개인 블로그(gatsby 기반)와 이번 toy blog service에서 계속 사용하고 싶었기 때문이다.
React Component를 Package화 하는 전반적인 흐름은
- webpack을 설정하고(typescript설정까지)
- react/react-dom 설치하고 설정하고
- library화를 하기위한 설정
- types관련 설정(Typescript를 사용하는 패키지를 위한 설정)
- 스타일관련 설정하는 것
- next.js에서 client-side-rendering만하기
React 컴포넌트를 패키지화한게 처음이라(프로젝트 내에서 공통적으로 사용하기 위해서 분리한적은 있지만) 모르는게 화수분처럼 쏟아져나와 괴로울때도 있으나 지금은 어찌어찌 산과산을 넘어 되긴되는 상태로 이르러서 내가 공부한것을 정리를 좀 해보려고 한다. 아마 틀린 내용이나 부족한 부분이 많을건데, 그런건 차차 한번 채워보기로 하고 이번엔 내가 파악한데까지만 정리를 하려고 한다.
그리고 아마 내가 한 방법이 최선은 아닌것 같은데, 내가 이해한 선에서는 일단 이방식으로 공유하려고 한다.
내 결과물
github.com/hayoung0Lee/hayoung-markdown
개발 과정
- webpack 설정하기
ts-loader, typescript 설정하기
프로젝트내에서 typescript파일을 처리하도록 하기 위해서 ts-loader를 사용하기로 했다. 해당 문서를 살펴보면 설정을 더 상세하게 볼수 있다.
const HtmlWebpackPlugin = require("html-webpack-plugin"); //installed via npm const webpack = require("webpack"); //to access built-in plugins const path = require("path"); module.exports = (env) => { // development: local mode, production: library mode const curMode = env.mode; // or "Production" const isDevelopment = curMode === "development"; return { mode: curMode, entry: isDevelopment ? "./src/index.tsx" : "./src/open.tsx", devtool: "source-map", module: { rules: [ { test: /\.tsx?$/, use: "ts-loader", exclude: /node_modules/, }, { test: /\.css$/, use: [ { loader: "style-loader" }, // to inject the result into the DOM as a style block { loader: "css-modules-typescript-loader" }, // to generate a .d.ts module next to the .scss file (also requires a declaration.d.ts with "declare modules '*.scss';" in it to tell TypeScript that "import styles from './styles.scss';" means to load the module "./styles.scss.d.td") { loader: "css-loader", options: { modules: true } }, // to convert the resulting CSS to Javascript to be bundled (modules:true to rename CSS classes in output to cryptic identifiers, except if wrapped in a :global(...) pseudo class) // { loader: "sass-loader" }, // to convert SASS to CSS ], }, ], }, // build:prod 했을때 lib 폴더에 파일을 생성한다. output: isDevelopment ? { filename: "main.js", path: path.resolve(__dirname, "./dist"), } : { path: path.resolve("lib"), filename: "open.js", libraryTarget: "commonjs2", }, resolve: { extensions: [".tsx", ".ts", ".js", ".css"], }, plugins: [new HtmlWebpackPlugin({ template: "./src/index.html" })], devServer: { contentBase: path.join(__dirname, "dist"), compress: true, port: 9000, }, // 잘 모르는 부분 externals: isDevelopment ? {} : { // Don't bundle react or react-dom react: { commonjs: "react", commonjs2: "react", amd: "React", root: "React", }, "react-dom": { commonjs: "react-dom", commonjs2: "react-dom", amd: "ReactDOM", root: "ReactDOM", }, }, }; };
ts-loader 내부에서 tsc를 사용한다고 한다. tsconfig 설정은 아래에 있다.
다른 것들은 [여기]에 대강 정리를 했는데, externals은 여기서 정리를 한다.
externals는 output bundle에 dependency를 추가하지 않기 위해 필요한 설정이다. 내 패키지의 경우, 이 패키지를 쓰는 프로젝트에 react, react-dom이 있다고 가정하기 때문에(consumer 쪽) output 번들에 포함되지 않도록 설정했다. 이 설정은 library 개발자들에게 특히 유용하다. (more)
- react, react-dom 설정하기
npm install react react-dom npm install -D @types/react @types/react-dom
- lib 설정하기(배포에 포함시키고 있다)
배포할때 lib 폴더를 포함시키고, 아래처럼 이 라이브러리를 사용할때 lib폴더에 있는 open.js 파일을 읽도록 구성했다. 그리고 types 은 lib/src/open.d.ts 에 있도록 구성했다.
- types .d.ts파일 생성하기
{ "compilerOptions": { "outDir": "./lib", "noImplicitAny": true, "module": "esnext", "target": "es5", "jsx": "react", "allowJs": true, "sourceMap": true, "isolatedModules": true, "declaration": true, // declaration파일을 생성 "moduleResolution": "node", "resolveJsonModule": true } }
이렇게 해서 번들링하면 webpack의 ts-loader가 이 설정대로 types관련 파일을 생성한다.
[Creating .d.ts Files from .js files]
- style 설정하기
style은 css-modules-typescript-loader 라는 loader를 사용했다. [css-modules-typescript-loader]
상당한 삽질이 있었는데 [이 글]을 보고 webpack 설정을 마무리해서 해결했다.
// webpack.config.js module: { rules: [ { test: /\.tsx?$/, use: "ts-loader", exclude: /node_modules/, }, { test: /\.css$/, use: [ { loader: "style-loader" }, // to inject the result into the DOM as a style block { loader: "css-modules-typescript-loader" }, // to generate a .d.ts module next to the .scss file (also requires a declaration.d.ts with "declare modules '*.scss';" in it to tell TypeScript that "import styles from './styles.scss';" means to load the module "./styles.scss.d.td") { loader: "css-loader", options: { modules: true } }, // to convert the resulting CSS to Javascript to be bundled (modules:true to rename CSS classes in output to cryptic identifiers, except if wrapped in a :global(...) pseudo class) // { loader: "sass-loader" }, // to convert SASS to CSS ], }, ], },
이렇게 해서 ts-loader를 통해서 typescript파일을 읽고, css 파일은 읽어서 .d.ts 파일을 만들고, css-loader를 통해서 css를 javascript로 번들될 수 있도록 하는순서라고 한다.
- next.js에서 client-side-rendering만하기
blog.hao.dev/render-client-side-only-component-in-next-js
{process.browser ? ( <App passedContents={contents} passedSetContents={setContents} /> ) : null}
이번에 만든 패키지가 아직 완전하진 않아서인지, client-side에서만 rendering가능해서 Next.js에서 제공하는 process.browser(webpack으로 부터 상속받는다고..?한다)를 통해서 client에서만 해당 컴포넌트가 보이도록 구성했다.
그런데 import 자체가 문제가 있어서 server-side에서는 componenet를 import 하지 않도록 설정을 했다.
nextjs.org/docs/advanced-features/dynamic-import#with-no-ssr
You may not always want to include a module on server-side. For example, when the module includes a library that only works in the browser.
공식 문서에도 client에서만 동작하는 모듈을 포함시키고 싶을떄 dynamic import 에서 설정을 통해서 사용할 수 있다.
쓰는 방법은 아래와 같다. 기존에는 import { App } from 'hayoung-markdown'으로 모듈을 불러왔었다.
import Head from "next/head"; import React, { useState } from "react"; import dynamic from "next/dynamic"; // 모듈 자체도 ssr때는 포함시키지 않는다. const DynamicComponentWithNoSSR = dynamic( () => import("hayoung-markdown").then((mod) => mod.App), { ssr: false, } ); const UserWrite: React.FC = () => { const [contents, setContents] = useState<string>(""); return ( <div> <Head> <title>UserWrite</title> <link rel="icon" href="/favicon.ico" /> </Head> // client에서만 렌더링 된다 {process.browser ? ( // <App passedContents={contents} passedSetContents={setContents} /> <DynamicComponentWithNoSSR passedContents={contents} passedSetContents={setContents} /> ) : ( <div></div> )} </div> ); }; export default UserWrite;
Package test하기
Npm link를 사용하는 것의 문제점(로컬에서 바로 패키지를 확인하려고 했으나, 해결법을 찾지 못했다, 개발하고 배포한다음 확인하는 것이 최선이었다)
- local에서 test가 안되는 이슈가 있다. 나는 결국에 안되서 배포를 해서 확인하는 방향으로 바꾸었다.
# Invalid Hook Call Warning.
https://www.npmjs.com/package/create-react-library#use-with-react-hooks
# Issue
https://github.com/facebook/react/issues/14257
이방법 말고 npm pack 같은것을 사용하는 방법이 있다고 한다. [more]
Create-react-libary소개
github.com/transitive-bullshit/create-react-library#readme
너무 괴로운 시간이었는데, 이걸 사용하는게 좋겠다고 한다.
후기
이제 계속 개발하겠지만, 조금 머리가 터질것같은 시간이었다! 해보니까 되긴 되는구나 싶은 부분이긴하나, 어려웠다. 다음에 진득하니 다시 만들어보면 좋을것같다. 되긴 되는구나를 알 수 있었던 고통의 시간..
'Web Dev > 5. Projects' 카테고리의 다른 글
[toy blog service] Next.js의 API와 json (0) 2021.04.19 [toy blog service] getServerSideProps 이용하기 (0) 2021.04.18 [toy blog service] Webpack, TypeScript와 React(Babel은 천천히!) (0) 2021.04.14 [toy blog service] Preview Mode 이해하기 (0) 2021.04.13 [toy blog service] Dynamic route를 가진 페이지에서 getStaticProps를 하려면 getStaticPaths가 필요하다! (0) 2021.04.13