-
[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
hayoung0Lee/hayoung-markdown
TypeScript Package(Markdown viewer, editor), but mainly for learning how to make a JavaScript Package, how Webpack and TypeScript work. - hayoung0Lee/hayoung-markdown
github.com
개발 과정
- 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 설정하기(배포에 포함시키고 있다)
Difference between lib and dist folders when packaging library using webpack?
Ive just published my first package (a react component) to npm but im having some trouble understanding the difference between what the lib directory is compared to the dist. Currently I generate ...
stackoverflow.com
배포할때 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
Render client-side only component in Next.js · Hao's learning log
If you are familiar with Next.js then you will know it is the React SSR (server-side rendering) framework created by Vercel. There are a lot of headaches in trying to build SSR sites in React, Next.js makes it drastically simpler by doing many of the not-s
blog.hao.dev
{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
Advanced Features: Dynamic Import | Next.js
Dynamically import JavaScript modules and React Components and split your code into manageable chunks.
nextjs.org
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
transitive-bullshit/create-react-library
⚡CLI for creating reusable react libraries. Contribute to transitive-bullshit/create-react-library development by creating an account on GitHub.
github.com
너무 괴로운 시간이었는데, 이걸 사용하는게 좋겠다고 한다.
후기
이제 계속 개발하겠지만, 조금 머리가 터질것같은 시간이었다! 해보니까 되긴 되는구나 싶은 부분이긴하나, 어려웠다. 다음에 진득하니 다시 만들어보면 좋을것같다. 되긴 되는구나를 알 수 있었던 고통의 시간..
'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