Sonamu νλ‘μ νΈλ TypeScriptλ‘ μμ±λλ©°, APIμ Web κ°κ° λ
립μ μΈ tsconfig.jsonμ κ°μ§κ³ μμ΅λλ€. κ° νκ²½μ μ΅μ νλ μ€μ μ μ¬μ©ν©λλ€.
νλ‘μ νΈ κ΅¬μ‘°
APIμ Webμ κ°κ° λ
립μ μΈ TypeScript νλ‘μ νΈμ
λλ€. μ€μ μ΄ μλ‘ λΆλ¦¬λμ΄ μμ΅λλ€.
API μλ² μ€μ
κΈ°λ³Έ tsconfig.json
{
"compilerOptions": {
/* Basic Options */
"target": "esnext",
"module": "esnext",
"outDir": "dist",
"sourceMap": true,
"lib": ["esnext", "dom"],
/* Strict Type-Checking Options */
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"strictPropertyInitialization": true,
"noImplicitThis": true,
"alwaysStrict": true,
/* Additional Checks */
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"useUnknownInCatchVariables": true,
/* Module Resolution Options */
"moduleResolution": "bundler",
"esModuleInterop": true,
/* Experimental Options */
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
/* Advanced Options */
"forceConsistentCasingInFileNames": true,
"noErrorTruncation": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"noUncheckedIndexedAccess": true
},
"include": ["src/**/*.ts", "src/**/*.json"],
"exclude": [
"node_modules",
"dist/*",
"public",
// "src/**/*.test.ts", // ν
μ€νΈ νμΌλ νμ
체ν¬νλ―λ‘ μ£Όμ μ²λ¦¬
"**/__mocks__/**",
"vite.config.ts"
]
}
μ£Όμ μ΅μ
μ€λͺ
λͺ¨λ μ€μ
λ°μ½λ μ΄ν°
Strict λͺ¨λ
μΆκ° 체ν¬
{
"target": "esnext", // μ΅μ ECMAScript λ¬Έλ² μ¬μ©
"module": "esnext", // ES λͺ¨λ μΆλ ₯
"moduleResolution": "bundler" // Vite/esbuildμ νΈν
}
moduleResolution: βbundlerβ
- Node.jsμ
node λͺ¨λ λμ λ²λ€λ¬ λͺ¨λ μ¬μ©
- Viteμ esbuildκ° λͺ¨λμ ν΄μνλ λ°©μκ³Ό μΌμΉ
package.jsonμ exports νλ μ§μ
moduleResolution: "node"λ₯Ό μ¬μ©νλ©΄ Viteμ μΆ©λν μ μμ΅λλ€.
{
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
Sonamu APIμμ νμ
- API λ©μλμ
@api λ°μ½λ μ΄ν° μ¬μ©
- νΈλμμ
μ
@transactional λ°μ½λ μ΄ν° μ¬μ©
- νμΌ μ
λ‘λμ
@upload λ°μ½λ μ΄ν° μ¬μ©
μ¬μ© μμ:import { BaseModelClass, api, transactional } from "sonamu";
class UserModelClass extends BaseModelClass<
UserSubsetKey,
UserSubsetMapping,
UserSubsetQueries
> {
@api({ httpMethod: "GET" })
async findById(subset: UserSubsetKey, id: number) {
const rdb = this.getPuri("r");
return rdb.table("users").where("id", id).first();
}
@api({ httpMethod: "POST" })
@transactional()
async save(data: UserSaveParams) {
const wdb = this.getDB("w");
return this.upsert(wdb, data);
}
}
export const UserModel = new UserModelClass();
Entityλ entity.json νμΌλ‘ μ μνλ©°, TypeScript λ°μ½λ μ΄ν°λ μ¬μ©νμ§ μμ΅λλ€. μμΈν λ΄μ©μ Entity μ μνκΈ°λ₯Ό μ°Έκ³ νμΈμ. {
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"strictPropertyInitialization": true,
"noImplicitThis": true,
"alwaysStrict": true
}
Sonamuλ λͺ¨λ strict μ΅μ
μ νμ±νν©λλ€.
- νμ
μμ μ± κ·Ήλν
- λ°νμ μλ¬ μ¬μ λ°©μ§
- μ½λ νμ§ ν₯μ
μμΈν λ΄μ©μ νμ
체ν¬λ₯Ό μ°Έκ³ νμΈμ.{
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"useUnknownInCatchVariables": true,
"noUncheckedIndexedAccess": true
}
μ½λ νμ§ ν₯μ μ΅μ
:
noUnusedLocals: μ¬μ©νμ§ μλ λ³μ κ²½κ³
noUnusedParameters: μ¬μ©νμ§ μλ νλΌλ―Έν° κ²½κ³
noImplicitReturns: λͺ¨λ κ²½λ‘μμ return νμ
noUncheckedIndexedAccess: λ°°μ΄/κ°μ²΄ μ κ·Ό μ undefined 체ν¬
νμ₯ μ€μ νμΌλ€
Sonamu APIλ μ½λ μμ±μ μν΄ μΆκ° tsconfig νμΌμ μ¬μ©ν©λλ€.
tsconfig.schemas.json - μ€ν€λ§ μμ±μ©
api/tsconfig.schemas.json
{
"extends": "./tsconfig.json",
"include": ["src/sonamu.generated.ts"],
"exclude": ["src/**/*.types.ts"]
}
μ©λ:
sonamu.generated.ts νμΌ μμ±
- Entity μ€ν€λ§ νμ
μμ±
- DB μ€ν€λ§ β TypeScript νμ
λ³ν
μ¬μ© μμ :# Sonamuκ° μλμΌλ‘ μ¬μ©
pnpm sonamu entity:load
tsconfig.types.json - νμ
μμ±μ©
{
"extends": "./tsconfig.json",
"include": ["src/**/*.types.ts"],
"references": [{ "path": "./tsconfig.schemas.json" }]
}
μ©λ:
- API μλ΅ νμ
μμ±
- Request/Response νμ
μ μ
- νλ‘ νΈμλμ νμ
곡μ
μ¬μ© μμ :# WebμΌλ‘ νμ
λκΈ°ν
pnpm sonamu sync:types
Web (React) μ€μ
{
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"lib": ["DOM", "DOM.Iterable", "ESNext"],
"allowJs": false,
"skipLibCheck": true,
"esModuleInterop": false,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"module": "ESNext",
"moduleResolution": "Bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["src", "src/routeTree.gen.ts"],
"references": [{ "path": "./tsconfig.node.json" }]
}
APIμμ μ°¨μ΄μ
| μ΅μ
| API | Web | μ΄μ |
|---|
moduleResolution | bundler | Bundler | Viteκ° λ²λ€λ¬ λ°©μ μ¬μ© |
jsx | - | react-jsx | React 17+ JSX Transform |
noEmit | false | true | Viteκ° λΉλ λ΄λΉ |
isolatedModules | - | true | Viteμ esbuild μꡬμ¬ν |
paths | - | @/* | μ λ κ²½λ‘ import |
APIλ moduleResolution: "bundler" (μλ¬Έμ), Webμ moduleResolution: "Bundler" (λλ¬Έμ)λ₯Ό μ¬μ©ν©λλ€. λ λ€ Viteκ° λ²λ€λ¬ λ°©μμΌλ‘ λͺ¨λμ ν΄μν©λλ€.
Path Mapping
{
"paths": {
"@/*": ["./src/*"]
}
}
μ λ κ²½λ‘λ‘ importν μ μμ΅λλ€:
// β μλ κ²½λ‘
import { Button } from "../../../components/Button";
// β
μ λ κ²½λ‘
import { Button } from "@/components/Button";
μμΈν λ΄μ©μ Path Mappingμ μ°Έκ³ νμΈμ.
νμ
μ²΄ν¬ μ€ν
cd api
# νμ
체ν¬λ§
pnpm tsc --noEmit
# Watch λͺ¨λ
pnpm tsc --noEmit --watch
cd web
# νμ
체ν¬λ§
pnpm tsc --noEmit
# Watch λͺ¨λ
pnpm tsc --noEmit --watch
# νλ‘μ νΈ λ£¨νΈμμ
pnpm -r tsc --noEmit
# APIμ Webμ λμμ 체ν¬
κ°λ° μ€μλ --watch λͺ¨λλ‘ μ€μκ° νμ
체ν¬λ₯Ό κΆμ₯ν©λλ€.
μΌλ°μ μΈ λ¬Έμ ν΄κ²°
λͺ¨λμ μ°Ύμ μ μμ
μ¦μ:Cannot find module 'sonamu' or its corresponding type declarations.
μμΈ:
node_modulesκ° μ€μΉλμ§ μμ
- μλͺ»λ import κ²½λ‘
ν΄κ²°:# μμ‘΄μ± μ¬μ€μΉ
pnpm install
# νμ
μ μ νμΈ
ls node_modules/sonamu/dist/*.d.ts
μ¦μ:Experimental support for decorators is a feature that is subject to change
μμΈ:
experimentalDecoratorsκ° λΉνμ±νλ¨
ν΄κ²°:{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}
μ¦μ:Cannot use JSX unless the '--jsx' flag is provided.
μμΈ:
jsx μ΅μ
μ΄ μ€μ λμ§ μμ
ν΄κ²°:{
"compilerOptions": {
"jsx": "react-jsx"
}
}
React 17+ νμ μ€μ
Path Mapping μλ μ ν¨
μ¦μ:Cannot find module '@/components/Button'
μμΈ:
paths μ€μ λλ½
baseUrl μ€μ νμ
ν΄κ²°:{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
}
}
Vite μ€μ λ νμ:import path from "path";
export default {
resolve: {
alias: {
"@": path.resolve(__dirname, "./src"),
},
},
};
컀μ€ν°λ§μ΄μ§
μΆκ° λΌμ΄λΈλ¬λ¦¬ νμ
{
"compilerOptions": {
"lib": ["esnext", "dom", "dom.iterable", "webworker"]
}
}
λ μ격ν 체ν¬
{
"compilerOptions": {
"noPropertyAccessFromIndexSignature": true,
"exactOptionalPropertyTypes": true,
"noUncheckedIndexedAccess": true
}
}
μ΄ μ΅μ
λ€μ κΈ°μ‘΄ μ½λμ νΈνμ± λ¬Έμ λ₯Ό μΌμΌν¬ μ μμ΅λλ€.
include/exclude ν¨ν΄
{
"include": [
"src/**/*.ts",
"src/**/*.tsx",
"src/**/*.json"
],
"exclude": [
"node_modules",
"dist",
"**/*.test.ts",
"**/*.spec.ts",
"**/__mocks__/**"
]
}
λ€μ λ¨κ³