본문으로 건너뛰기
Advertisement

18.2 환경 설정

Solid.js 프로젝트를 시작하는 방법은 크게 두 가지입니다. 공식 템플릿을 degit으로 복사하거나 Vite에 Solid 플러그인을 추가하는 방법입니다. 이 장에서는 두 방법 모두 다루고, TypeScript 설정, 개발 도구, ESLint/Prettier까지 완전한 개발 환경을 구성합니다.


프로젝트 생성 방법

방법 1: 공식 Solid 템플릿 (추천)

# JavaScript 템플릿
npx degit solidjs/templates/js my-solid-app
cd my-solid-app
npm install
npm run dev
# TypeScript 템플릿 (권장)
npx degit solidjs/templates/ts my-solid-app
cd my-solid-app
npm install
npm run dev

degit은 Git 저장소를 히스토리 없이 복사하는 도구입니다. git clone보다 빠르고, 시작점을 깨끗하게 유지할 수 있습니다.

방법 2: Vite + Solid 플러그인으로 직접 구성

# Vite 프로젝트 생성 (interactive)
npm create vite@latest my-solid-app -- --template solid
cd my-solid-app
npm install
npm run dev

# 또는 TypeScript 버전
npm create vite@latest my-solid-app -- --template solid-ts

방법 3: SolidStart (풀스택 프레임워크)

npm create solid@latest
# 대화형 설치 진행
# ✔ Which template would you like to use? › bare / hackernews / with-auth / ...
# ✔ Use TypeScript? › Yes
# ✔ Install dependencies? › Yes

SolidStart는 Solid.js의 풀스택 메타프레임워크로, Next.js의 Solid 버전입니다. SSR, SSG, 파일 기반 라우팅을 제공합니다.


프로젝트 구조 설명

solidjs/templates/ts 기반의 TypeScript 프로젝트 구조:

my-solid-app/
├── src/
│ ├── App.tsx # 루트 컴포넌트
│ ├── App.module.css # App 전용 CSS Module
│ ├── index.tsx # 진입점 (render 호출)
│ ├── logo.svg # 예시 에셋
│ └── components/ # (직접 생성) 컴포넌트 폴더
│ ├── Header.tsx
│ ├── Counter.tsx
│ └── ...
├── public/
│ └── favicon.ico # 정적 에셋
├── index.html # HTML 진입점 (Vite)
├── vite.config.ts # Vite 설정
├── tsconfig.json # TypeScript 설정
└── package.json

src/index.tsx — 진입점

/* @refresh reload */
import { render } from 'solid-js/web';
import './index.css';
import App from './App';

const root = document.getElementById('root');

if (import.meta.env.DEV && !(root instanceof HTMLElement)) {
throw new Error(
'Root element not found. Did you forget to add it to your index.html? Or maybe the id attribute got misspelled?',
);
}

render(() => <App />, root!);

/* @refresh reload */ 주석은 HMR(핫 모듈 교체)에서 이 파일이 변경되면 전체 페이지를 새로고침하도록 Solid의 Babel 플러그인에게 알려줍니다.

src/App.tsx — 루트 컴포넌트

import type { Component } from 'solid-js';
import { createSignal } from 'solid-js';
import styles from './App.module.css';
import logo from './logo.svg';

const App: Component = () => {
const [count, setCount] = createSignal(0);

return (
<div class={styles.App}>
<header class={styles.header}>
<img src={logo} class={styles.logo} alt="logo" />
<p>
편집하세요 <code>src/App.tsx</code> 그리고 저장하세요.
</p>
<p>
카운트: {count()}
<button onClick={() => setCount(count() + 1)}>+1</button>
</p>
</header>
</div>
);
};

export default App;

Vite 설정 (vite-plugin-solid)

vite.config.ts 파일이 Solid.js 프로젝트의 핵심입니다:

// vite.config.ts
import { defineConfig } from 'vite';
import solidPlugin from 'vite-plugin-solid';

export default defineConfig({
plugins: [
solidPlugin({
// Solid babel 변환 옵션
solid: {
// 개발 환경에서 컴포넌트 이름 유지 (디버깅용)
generate: 'dom',
},
}),
],

// 개발 서버 옵션
server: {
port: 3000,
open: true, // 브라우저 자동 오픈
},

// 빌드 옵션
build: {
target: 'esnext',
outDir: 'dist',
sourcemap: true, // 소스맵 생성
},

// 경로 별칭
resolve: {
alias: {
'@': '/src', // import '@/components/Button'
'~': '/src',
},
},
});

경로 별칭 TypeScript 설정

tsconfig.json에도 별칭을 추가해야 IDE 자동완성이 동작합니다:

{
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"jsx": "preserve",
"jsxImportSource": "solid-js",
"strict": true,
"noEmit": true,
"baseUrl": ".",
"paths": {
"@/*": ["src/*"],
"~/*": ["src/*"]
}
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}

TypeScript 템플릿 상세 설정

Solid.js 전용 TypeScript 타입

// solid-js 타입 활용 예시
import type { Component, JSX, ParentComponent, FlowComponent } from 'solid-js';

// 기본 컴포넌트
const Button: Component<{ onClick: () => void; label: string }> = (props) => (
<button onClick={props.onClick}>{props.label}</button>
);

// children을 받는 컴포넌트
const Card: ParentComponent<{ title: string }> = (props) => (
<div class="card">
<h2>{props.title}</h2>
{props.children}
</div>
);

// render prop 패턴
const List: FlowComponent<{ items: string[] }, JSX.Element> = (props) => (
<ul>
{props.items.map(item => <li>{props.children(item)}</li>)}
</ul>
);

CSS Module 타입 설정

Vite는 CSS Module을 기본 지원합니다:

// *.module.css 파일의 타입 선언이 필요한 경우
// src/vite-env.d.ts (자동 생성됨)
/// <reference types="vite/client" />

개발 서버 실행과 HMR

# 개발 서버 시작 (기본 포트: 3000)
npm run dev

# 특정 포트로 실행
npm run dev -- --port 4000

# 네트워크 공개 (다른 기기에서 접근 가능)
npm run dev -- --host

HMR (Hot Module Replacement) 동작 방식

Solid.js의 HMR은 vite-plugin-solid가 처리합니다. 컴포넌트 파일을 수정하면:

  1. 변경된 파일만 재컴파일
  2. 브라우저에 WebSocket으로 업데이트 전달
  3. 상태를 유지한 채로 컴포넌트만 교체
# 빌드 (프로덕션)
npm run build

# 빌드 결과물 미리보기
npm run preview

빌드 결과물 구조

dist/
├── assets/
│ ├── index-[hash].js # 번들된 JavaScript
│ └── index-[hash].css # 번들된 CSS
└── index.html # 진입점 HTML

Solid DevTools 설치 및 사용

브라우저 확장 설치

Chrome/Edge: Chrome Web Store에서 Solid DevTools 검색

또는 직접 설치:

# 개발 의존성으로 설치
npm install --save-dev solid-devtools

vite.config.ts에 플러그인 추가:

import { defineConfig } from 'vite';
import solidPlugin from 'vite-plugin-solid';
import devtools from 'solid-devtools/vite'; // 추가

export default defineConfig({
plugins: [
devtools({
autoname: true, // 컴포넌트 이름 자동 추론
locator: {
targetIDE: 'vscode', // 클릭 시 VS Code에서 파일 열기
componentLocation: true,
jsxLocation: true,
},
}),
solidPlugin(),
],
});

src/index.tsx에 import 추가:

import 'solid-devtools'; // 맨 위에 추가
/* @refresh reload */
import { render } from 'solid-js/web';
// ...

DevTools 주요 기능

기능
Component컴포넌트 트리, props/signals 실시간 확인
Signal앱 전체의 Signal 목록과 현재 값
Owner GraphSignal과 Effect의 의존성 그래프
LocatorDOM 요소 클릭 시 소스 파일 바로 열기

ESLint + Prettier 설정

패키지 설치

# ESLint + Solid 플러그인
npm install --save-dev \
eslint \
@typescript-eslint/parser \
@typescript-eslint/eslint-plugin \
eslint-plugin-solid \
eslint-config-prettier \
prettier

ESLint 설정 (eslint.config.js, Flat Config 방식)

// eslint.config.js
import js from '@eslint/js';
import ts from '@typescript-eslint/eslint-plugin';
import tsParser from '@typescript-eslint/parser';
import solid from 'eslint-plugin-solid';
import prettier from 'eslint-config-prettier';

export default [
js.configs.recommended,

// TypeScript
{
files: ['**/*.{ts,tsx}'],
plugins: {
'@typescript-eslint': ts,
},
languageOptions: {
parser: tsParser,
parserOptions: {
project: './tsconfig.json',
},
},
rules: {
...ts.configs.recommended.rules,
'@typescript-eslint/no-explicit-any': 'warn',
'@typescript-eslint/explicit-function-return-type': 'off',
},
},

// Solid.js 전용 규칙
{
files: ['**/*.{jsx,tsx}'],
plugins: { solid },
rules: {
...solid.configs.typescript.rules,
// Solid.js 핵심 규칙
'solid/no-destructure': 'error', // props 구조 분해 금지
'solid/reactivity': 'warn', // 반응성 패턴 경고
'solid/event-handlers': 'error', // 이벤트 핸들러 명명 규칙
},
},

// Prettier 충돌 규칙 비활성화 (항상 마지막에)
prettier,

// 제외 파일
{
ignores: ['dist/**', 'node_modules/**'],
},
];

Prettier 설정 (.prettierrc)

{
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "es5",
"printWidth": 100,
"jsxSingleQuote": false,
"bracketSameLine": false
}

VS Code 설정 (.vscode/settings.json)

{
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
},
"[typescriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"typescript.preferences.importModuleSpecifier": "shortest"
}

package.json 스크립트 추가

{
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview",
"lint": "eslint src --ext .ts,.tsx",
"lint:fix": "eslint src --ext .ts,.tsx --fix",
"format": "prettier --write src/**/*.{ts,tsx,css}",
"type-check": "tsc --noEmit"
}
}

권장 VS Code 확장 프로그램

// .vscode/extensions.json
{
"recommendations": [
"esbenp.prettier-vscode", // Prettier 포맷터
"dbaeumer.vscode-eslint", // ESLint 통합
"bradlc.vscode-tailwindcss", // TailwindCSS (사용 시)
"ms-vscode.vscode-typescript-next", // 최신 TypeScript
"YoavBls.pretty-ts-errors", // TS 에러 가독성 향상
"solid.solid-snippets" // Solid.js 스니펫 (있을 경우)
]
}

실전 예제: 완전한 프로젝트 초기 구조

실제 프로젝트에서 권장하는 디렉토리 구조:

src/
├── components/ # 재사용 가능한 UI 컴포넌트
│ ├── ui/ # 기본 UI (Button, Input, Card 등)
│ │ ├── Button.tsx
│ │ ├── Input.tsx
│ │ └── index.ts # barrel export
│ └── features/ # 기능별 컴포넌트
│ ├── auth/
│ └── dashboard/
├── stores/ # 전역 상태 (createStore)
│ ├── userStore.ts
│ └── appStore.ts
├── hooks/ # 커스텀 Signal/Effect 훅
│ ├── useLocalStorage.ts
│ └── useFetch.ts
├── pages/ # 라우트별 페이지 컴포넌트
│ ├── Home.tsx
│ └── About.tsx
├── lib/ # 유틸리티 함수, API 클라이언트
│ ├── api.ts
│ └── utils.ts
├── styles/ # 전역 CSS
│ ├── global.css
│ └── variables.css
├── App.tsx
└── index.tsx

src/lib/utils.ts — 공통 유틸리티

// 타입 안전한 클래스 이름 합성 유틸리티
export function cx(...classes: (string | undefined | false | null)[]): string {
return classes.filter(Boolean).join(' ');
}

// 디바운스 함수
export function debounce<T extends (...args: any[]) => any>(
fn: T,
delay: number
): (...args: Parameters<T>) => void {
let timer: ReturnType<typeof setTimeout>;
return (...args: Parameters<T>) => {
clearTimeout(timer);
timer = setTimeout(() => fn(...args), delay);
};
}

src/components/ui/Button.tsx — 재사용 버튼

import type { Component, JSX } from 'solid-js';
import { splitProps } from 'solid-js';

type ButtonVariant = 'primary' | 'secondary' | 'danger' | 'ghost';
type ButtonSize = 'sm' | 'md' | 'lg';

interface ButtonProps extends JSX.ButtonHTMLAttributes<HTMLButtonElement> {
variant?: ButtonVariant;
size?: ButtonSize;
loading?: boolean;
}

const variantStyles: Record<ButtonVariant, string> = {
primary: 'bg-blue-600 text-white hover:bg-blue-700',
secondary: 'bg-gray-200 text-gray-800 hover:bg-gray-300',
danger: 'bg-red-600 text-white hover:bg-red-700',
ghost: 'bg-transparent text-gray-600 hover:bg-gray-100',
};

const sizeStyles: Record<ButtonSize, string> = {
sm: 'px-2 py-1 text-sm',
md: 'px-4 py-2',
lg: 'px-6 py-3 text-lg',
};

const Button: Component<ButtonProps> = (props) => {
// splitProps로 컴포넌트 전용 props와 HTML 기본 props 분리
const [local, rest] = splitProps(props, ['variant', 'size', 'loading', 'class', 'children']);

const variant = () => local.variant ?? 'primary';
const size = () => local.size ?? 'md';

return (
<button
{...rest}
disabled={local.loading || rest.disabled}
class={`
inline-flex items-center justify-center rounded font-medium
transition-colors focus:outline-none focus:ring-2
disabled:opacity-50 disabled:cursor-not-allowed
${variantStyles[variant()]}
${sizeStyles[size()]}
${local.class ?? ''}
`.trim()}
>
{local.loading ? '로딩 중...' : local.children}
</button>
);
};

export default Button;

고수 팁

팁 1: /* @refresh reload */ 주석의 의미

이 주석은 HMR 시 컴포넌트 상태를 보존할 수 없을 때 전체 페이지를 새로고침하도록 지시합니다. 진입점(index.tsx)에만 넣고, 일반 컴포넌트에는 불필요합니다.

팁 2: Vite 환경 변수 타입 설정

// src/env.d.ts
/// <reference types="vite/client" />

interface ImportMetaEnv {
readonly VITE_API_URL: string;
readonly VITE_APP_TITLE: string;
// 사용할 환경 변수 모두 선언
}

interface ImportMeta {
readonly env: ImportMetaEnv;
}
# .env.local (gitignore에 포함)
VITE_API_URL=http://localhost:8080
VITE_APP_TITLE=My Solid App
// 사용
const API_URL = import.meta.env.VITE_API_URL;

팁 3: 번들 크기 분석

# rollup-plugin-visualizer로 번들 시각화
npm install --save-dev rollup-plugin-visualizer
// vite.config.ts
import { visualizer } from 'rollup-plugin-visualizer';

export default defineConfig({
plugins: [
solidPlugin(),
visualizer({
open: true, // 빌드 후 자동으로 브라우저 열기
filename: 'dist/stats.html',
gzipSize: true,
}),
],
});
npm run build  # 빌드 후 dist/stats.html이 브라우저에서 열림

팁 4: 절대 경로 import로 유지보수성 향상

// 상대 경로 (나쁜 예)
import Button from '../../../components/ui/Button';

// 절대 경로 (좋은 예)
import Button from '@/components/ui/Button';

vite.config.tsresolve.aliastsconfig.jsonpaths 설정이 모두 되어있어야 합니다.

팁 5: Solid.js와 Tailwind CSS 함께 사용

npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
// tailwind.config.js
export default {
content: [
'./index.html',
'./src/**/*.{js,ts,jsx,tsx}', // Solid 파일 포함
],
theme: {
extend: {},
},
plugins: [],
};
/* src/index.css */
@tailwind base;
@tailwind components;
@tailwind utilities;

정리

항목명령/파일
JS 템플릿 생성npx degit solidjs/templates/js my-app
TS 템플릿 생성npx degit solidjs/templates/ts my-app
Vite 설정vite.config.ts + vite-plugin-solid
개발 서버npm run dev (기본 포트 3000)
HMR/* @refresh reload */ 주석으로 제어
DevToolssolid-devtools 패키지 + 브라우저 확장
ESLinteslint-plugin-solid (no-destructure 규칙 필수)
경로 별칭vite.config.ts alias + tsconfig.json paths

다음 장에서는 Solid.js의 핵심인 반응형 프리미티브(Signal, Effect, Memo, Resource)를 깊이 살펴봅니다.

Advertisement