** 번역에 의역과 오역이 충분히 있을 수 있으므로, 가능하신 분들은 그냥 아래의 링크로 가셔서 원문을 읽어보시길 추천합니다~
CSS Modules의 시리즈의 마지막 글에서는, webpack을 토대로 정적 React 사이트를 만드는 방법을 살펴보겠다. 이 정적인 사이트에는 홈페이지와 실제 작동 방식을 설명하는 몇가지 React 구성 요소가 있는 정보 페이지의 두가지 템플릿이 있다.
🔗 아티클 시리즈
part 1: What are CSS Modules and why do need them?
part 2: Getting Started with CSS Modules
part 3: React + CSS Modules = 😍 (지금 위치)
이전 글에서 우리는 종속성을 파일로 가져오는 방법과 빌드 프로세스를 사용하여 CSS와 HTML 모두에서 생성되는 고유한 클래스 이름을 만드는 방법을 보여주는 webpack으로 빠른 프로젝트를 설정하였다. 다음 예제는 튜토리얼에 크게 의존하므로 먼저 이전 예제를 훓고 오는 것길 바란다. 또한 이 게시물은 사용자가 React의 기본 사항에 익숙하다고 가정하고 진행되는 것임을 유의하시기 바랍니다.
이전 데모에서는 결론을 내릴 때 코드 베이스에 문제가 있었다. 마크업을 렌더링하기 위해 자바스크립트에 의존했고 프로젝트를 구조화하는 방법이 완전히 명확하지 않았다. 이번 글에서 새로운 Webpack 지식으로 몇가지 구성 요소를 만들려고 하기 보다 현실적인 예를 살펴 볼 것이다.
튜토리얼을 잘 따라오기 위해 css-module-react 레포를 확인할 수 있다. 이 레포지토리에는 마지막 데모가 중단될 부분까지 진행되어 있는 미완성 프로젝트일 뿐이며, 그 다음 단계는 이 곳에서 계속할 수 있다.
Webpack의 적정 사이트 생성기
정적 마크업을 생성하기 위해 우리는 정적 마크업을 생성하도록 도와줄 Webpack 플러그인을 설치해야 한다.
npm i -D static-site=generator-webpack-plugin
이제 플러그인을 webpack.config.js에 추가하고 경로를 추가한다.
경로는 home 페이지는 /로 about 페이지는 /about으로 설정한다.
경로는 생성할 정적 파일을 플러그인에 알려준다.
var StaticSiteGeneratorPlugin = require('static-site-generator-webpack-plugin');
var locals = {
routes: [
'/',
]
};
정적 마크업을 제공하고 이 시점에서 서버 측 코드를 피하는 것을 선호하여 StaticSiteGeneratorPlugin을 사용했다. 이 플러그인에 대해 문서에서 언급했듯이 다음을 제공한다.
"렌더링할 일련의 경로와 일치하는 index.html 파일 세트는 사용자 정의 웹팩 컴파일 렌더링 기능을 실행하여 출력 디렉토리에 렌더링 된다. "
무슨 얘기인지 못알아듣겠다 해도 너무 겁먹지 않아도 된다. 여전히 webpack.config.js에서 module.exports 객체를 업데이트 할 수 있다.
module.exports = {
entry: {
'main': './src',
},
output: {
path: 'build',
filename: 'bundle.js',
libraryTarget: 'umd' // this is super important
},
// ...
}
nodejs와 정적 사이트 플러그인이 제대로 동작하기 위한 필수 조건이기 때문에 libraryTarget을 설정했다. 또한 생성되는 모든것들이 /build 디렉토리에 생성되도록 추가한다.
webpack.config.js파일에는 StaticSiteGeneratorPlugin 이 하단에 추가해야 한다. 그렇게 생성하려는 경로를 전달한다.
plugins: [
new ExtractTextPlugin('styles.css'),
new StateSiteGeneratorPlugin('main', locals.routes),
]
최종 webpack.config.js 파일의 내용은 다음과 같다.
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var StaticSiteGeneratorPlugin = require('static-site-generator-webpack-plugin')
var locals = [
routes: [
'/',
]
}
module.exports = {
entry: './src',
output: {
path: 'build',
filename: 'bundle.js',
libraryTarget: 'umd' // This is important
},
module: {
loaders: [
{
test: /\.js$/,
loader: 'babel',
include: __dirname + '/src',
},
{
test: /\.css$/,
loader: ExtractTextPlugin.extract('css?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]'),
include: __dirname + '/src',
}
],
},
plugins: [
new StaticSiteGeneratorPlugin('main', locals.routes),
new ExtractTextPlugin('styles.css'),
]
};
src/index.js파일에 다음과 같이 추가한다.
// Exported Static site renderer
module.exports = function render(locals, callback) {
callback(null, 'Hello!');
};
지금은 홈페이지에 'Hello!'가 프린팅 된다. 결국 우리는 홈페이지를 좀 더 실용적으로 만들게 될 것이다.
이전 튜토리얼에서 설명한 package.json에는 다음과 같이 실행할 수 있는 webpack 명령어가 설정되어있다.
npm start
그리고 build 디렉토리를 확인하면 콘텐츠가 포함된 index.html 파일을 찾아야 한다. 정적 사이트 플러그인이 작동하는 것을 확인할 수 있다. 이제 모든것을 테스트 하기위해 webpack.config.js로 돌아가 경로를 업데이트할 수 있다.
var locals = {
routes: [
'/',
'/about'
]
};
npm start를 다시 실행하면 build/about/index.html 이라는 새로운 파일을 만들게 된다. 그러나 이것은 build/index.html 파일과 마찬가지로 "Hello!"라는 텍스트만 보일 것이다. 왜냐하면 우리는 두개의 파일에 동일한 컨텐츠를 보냈기 때문이다. 이 부분을 고치기 위해 우리는 라우터를 사용해야 하는데 그전에 우리는 React를 먼저 설정해야 한다.
그 전에 우리는 코드 리팩토링을 위해 라우트를 각각의 파일로 옮기도록 한다. 따라서 ./data.js라는 파일에 우리는 아래와 같이 적는다.
module.exports = {
routes: [
'/',
'/about'
]
}
그리고 webpack.config.js에 데이터를 require하고, locals 변수를 삭제한다.
var data = require'./data.js';
파일의 하단에 StaticSiteGenerator플러그인을 업데이트 한다.
plugins: [
new ExtractTextPlugin('style.css');
new StaticSiteGenerationPlugin('main', data.routes, data),
]
React 설정하기
우리는 여러개의 작은 html, css 번들을 만들어 템플릿이 되기를 바란다(마치 about이나 homepage처럼). 이것은 React, React-dom으로 실행할 수 있으며, 우선 먼저 설치해야 한다.
npm i -D react react-dom babel-preset-react
그 후 .babelrc파일을 업데이트 해준다.
{
"presets": "[es2016", "react"]
}
자 이제 새로운 폴더 src/templates를 만들고 Main.js 파일을 만들어야 한다. 여기서 모든 마크업관련 코드를 작성할 것이며 템플릿에 필요한 모든 assets을 이곳에 위치할 것이다. (예: 사이트의 <footer>와 마찬가지로)
import React from 'react'
import Head from '../components/Head'
export default class Main extends React.Component {
renter() {
return (
{ /* This is where our content for various pages will go */ }
)
}
}
두가지를 짚고 넘어가도록 하자. 첫번째, 만약 React가 사용하는 JSX 구문에 익숙하지 않다면 body 요소 내부의 텍스트가 주석이라는 것을 아는 것은 도움이 된다. 또한 표준 HTML 요소가 아니라 이상한 요소가 React 컴포넌트 요소이고 여기서 우리가 하는 것은 title 속성을 통해 데이터를 전달하는 것이다. 속성은 아니지만 React에서 사용하는 props이다.
src/components/Head.js 파일을 생성해야 한다.
import React from 'react'
export default class Head extends React.component {
render() {
return (
)
}
}
Head.js 파일의 모든 코드를 Main.js에 넣을 수 있지만 우리의 코드를 더 작은 조각으로 나누는 것은 도움이 된다. 만약, footer를 만들고자 한다면 src/components/Footer.js로 새로운 구성요소를 만든 다음 Main.js 파일로 가져오면 된다.
자, 이제 src/index.js 에서 모든것을 새로운 React 코드로 바꿀 수 있다.
import React from 'react'
import ReactDOMServer from 'react-dom/server'
import Main from './templates/Main.js'
module.exports = function render(locals, callback) {
var html = ReactDOMServer.renderToStaticMarkup(React.createElement(Main, locals))
callback(null, '' + html)
}
이것이 하는 일은 Main.js 파일의 모든 마크업을 가져올 것이다(이후에 Head React 구성요소를 가져옴). 그런 다음 이 모든 것을 React DOM으로 렌더링 한다. npm start를 한번 더 실행하고 bulid/index.html를 확인해보면 React가 Head 컴포넌트와 함께 Main.js React 컴포넌트에 추가한 다음 모든 것을 정적으로 렌더링 한다는 것을 알 수 있다.
그러나 About 페이지나 Homepage는 여전히 동일한 컨텐츠로 생성되고 있다. 이것을 수정하기 위해 router를 가져오자
Router 세팅하기
특정 경로(라우트)에 특정 코드 비트를 전달해야 한다. About 페이지에서는 About 페이지에 대한 콘텐츠가 필요하며 마찬가지로 홈페이지, 블로그 기타 등등 원하는 페이지에 대한 콘텐츠가 필요하다. 다른 말로 하면 우리는 주변의 콘텐츠를 관리하기 위해 라우터가 필요하다. 이를 위해 reart-router가 모든 작업을 수행하도록 할 수 있다.
시작하기 전, 이 튜토리얼에서 우리는 React Router 버전 2.0을 사용할 것이며 이전 버전 이후로 많이 변경했다는 것을 주목해야 한다.
우선, React 라우터는 React와 함께 번들로 설치되지 않기 때문에 아래와 같이 입력하여 설치해준다.
npm i -D react-router</code>
src 디렉토리에 routes.js파일을 생성하고 다음의 내용을 추가 한다.
import React from 'react'
import { Route, Redirect } from 'react-router'
import Main from './templates/Main.js'
import Home from './templates/Home.js'
import About from './templates/About.js'
module.exports = {
// Router code will be go here
}
페이지를 Home과 About으로 나누고, src/templates/About.js 파일을 다음과 같이 만든다.
import React from 'react'
export default class About extends React.component {
render() {
return (
<div>
<h1>About page</h1>
<p>This is an about page</p>
</div>
)
}
}
그리고 src/templates/Home.js 파일도 다음과 같이 만든다.
import React from 'react'
export default class Home React.component {
render() {
return (
<div>
<h1>Home page</h1>
<p>This is a home page</p>
</div>
)
}
}
이제 routes.js 파일로 돌아가 아까 작성하지 못했던 module.exports 다시 작성하도록 한다.
<Route component={Main}>
<Route path='/' component={Home}/>
<Route path='/about' component={About}/>
</Route>
src/templates/Main.js파일에는 모든 주변 마크업이 포함되어 있다. Home.js와 About.js React 컴포넌트는 Main.js의 요소 내부에 배치할 수 있다.
다음으로 src/router.js파일을 생성한다. 이것은 효과적으로 src/index.js를 대체하므로 해당 파일을 삭제하고 router.js 파일에 다음과 같이 작성한다.
import React from 'react'
import ReactDOM from 'react-dom'
import ReactDOMServer from 'react-dom/server'
import { Router, RouterContext, match, createMemoryHistory } from 'react-router'
import Route from './routes'
import Main from './templates/Main'
module.exports = function(locals, callback) {
const history = createMemoryHistory();
const location = history.createLocation(locals.path);
return match ({
routes: Routes,
location: location
}, function(error, redirectLocation, renderProps) {
var html = ReactDOMServer.renderToStaticMarkup(
<RouterContext {...renderProps} />
);
return callback(null, html);
})
}
만약 여기까지 진행된 것이 이해가지 않거나 익숙하지 않다면 Bread Westfall의 intro to React Router를 읽어보기를 바란다.
왜냐하면 우리는 index.js 파일을 삭제하고 라우터로 대체했으므로 webpack.config.js파일로 돌아가 entry키의 value 값을 변경해줘야 한다.
module.exports = {
entry: './src/router',
// other stuff..
}
마지막으로 src/templates/Main.js 파일로 이동한다.
export default class Main extends React.component {
renter () {
return (
<html>
<Head title='React and CSS Module' />
<body>
{this.props.children}
</body>
</html>
)
}
}
{this.props.children}이 다른 모든 템플릿들의 코드로 대체되는 위치이다. npm start를 다시 실행시켜 2개의 파일이 생성되는 것을 확인한다. build/index.html 와 build/about/index.html은 각각 개별적인 컨텐츠를 가진다.
CSS 모듈의 재구성
'hello world of CSS'이므로, 버튼 모듈을 만들 것이다. 그리고 Webpack의 css 로더와 이전 튜토리얼에서 사용한 것들을 계속 사용할 것이지만 대체 방안이 있다.
이것은 이번 프로젝트에서 우리가 만들어 사용하길 원하는 파일의 구조이다.
/components
/Button
Button.js
styles.css
커스텀된 React 컴포넌트를 템플릿 중 하나로 가져온다. 그러기 위해서는 우리는 src/components/Button/Button.js 파일을 만들어야 한다.
import React from 'react'
import btn from './styles.css'
export default class CoolButto extends React.component {
render() {
return (
<button className={btn.red}>{this.props.text}</button>
)
}
}
우리가 이전 튜토리얼에서 배운 것처럼, 클래스명 {btn.red}는 styles.css 파일에서 .red 클래스를 찾으면 Webpack이 gobbledygook css 모듈 클래스 이름을 생성할 것이다.
src/components/Button/styles.css에 간단한 스타일을 만들어보자.
.red {
font-size: 25px;
background-color: red;
color: white;
}
마지막으로 src/templates/Home.js 처럼 템플릿 페이지에 버튼 컴포넌트를 불러온다.
import React from 'react'
import CoolButton from '../components/Button/Button'
export default class Home extends React.Component {
render() {
return (
<div>
<h1>Home page</h1>
<p>This is a home page</p>
<CoolButton text='A super cool button' />
</div>
)
}
}
npm start를 한번 더 실행하면 원하는 결과를 확인할 수 있다. 새로운 템플릿, 컴포넌트를 빠르게 추가할 수 있는 정적 React 사이트와 CSS 모듈의 장점으로 클래스명이 아래와 같이 보여집니다.
React and CSS Modules 레포에 가면 더 정확한 버전의 데모를 확인해 볼 수 있다. 데모 확인 시 발견되는 에러에 있어서는 공유해주길 바란다.
프로젝트를 향상시킬 수 있는 방법으로 BrowserSync를 Webpack에 추가할 수 있다. 그러면 우리는 매번 npm 실행을 하지 않아도 된다.
또 Sass, PostCSS 그리고 로더들과 플러그인을 설치할 수 있다. 하지만 이번 프로젝트에서는 설치 하지 않는 것으로 하겠다.
마무리
우리는 여기서 무엇을 알게되었는가? 엄청난 작업처럼 보이지만 지금까지의 설명은 코드를 작성하기 위한 모듈식 환경을 셋팅 한 것이라고 할 수 있다. 원하는 만큼의 컴포넌트를 추가할 수 있다.
/components
Head.js
/Button
Button.js
styles.css
/Input
Input.js
style.css
/Title
Title.js
styles.css
결론적으로 우리가 만약 Head 컴포넌트 안의 스타일에 .large라는 클래스가 있어도 Button 컴포넌트의 .large 와 충돌하지 않는다. 또한, 우리는 'src/golbals.css'를 각 컴포넌트에 import 하거나 단순 변도의 파일을 추가하면 전역 스타일을 사용할 수 있다.
리액트로 정적인 사이트를 만드는 것은 상태관리를 포함한 리액트에서 제공하는 훌륭한 프로퍼티를 사용하지 않는다는 뜻이지만, 그래도 두가지의 웹사이트를 함께 혼용할 수 있다. 우선 지금까지 배운것처럼 정적인 사이트를 만든 후 나중에 점점 기능을 추가하면 된다.
그것은 깔끔하지만 CSS모듈, React, Webpack 의 조합은 조금 과한 경우가 많다. 프로젝트의 사이즈와 범위를 먼저 파악하지 않고 구현하는데에 시간을 보내는 것은 미친 짓이다. (만약 이게 싱글페이지 웹이었다면 더더욱)
그러나 매일 코드베이스에서 CSS를 작업하는 사람들이 많이 있다면 CSS 모듈은 많은 에러를 방지하기 위해 좋을 것이다. 그러나 이것은 디자이너가 이제 Javascript를 작성하는 방법도 배워야만 코드 베이스에 대한 접근할 수 있다는 말이기도 하다(이 부분은.... 저도 이해가 잘....) 이 방법이 올바르게 작동하려면 지원해야하는 dependency도 많이 있다.
이 말은 조만간의 미래에 우리는 모두 CSS 모듈을 사용하게 될것이란 의미인가? 그렇지 않다. 왜냐하면 모든 프론트엔트 기술과 마찬가지로 솔루션은 발생하는 문제에 따라 달라지며 모든 문제가 다 같지 않기 때문이다.
🔗 아티클 시리즈
part 1: What are CSS Modules and why do need them?
part 2: Getting Started with CSS Modules
part 3: React + CSS Modules = 😍 (지금위치)
'Articles' 카테고리의 다른 글
[번역] Strategies for Cache-Busting CSS (0) | 2023.03.16 |
---|---|
[번역] Part 2. CSS 모듈 사용하기 (0) | 2022.06.24 |
[번역] Part 1. CSS모듈은 무엇이며 우리는 왜 이것이 필요한가? (0) | 2022.06.24 |
[번역] React에서 CSS를 작성하는 방법 (0) | 2022.06.24 |
[번역] 자바스크립트 프레임워크 전쟁은 끝났다. (0) | 2022.05.12 |