0w0

npm-scripts를 적을 때 습관

이래저래 5년 정도 취미 개발로 nps-scripts를 작성하고 있다.

긴 시간 사용하면 노하우가 쌓여서, "이렇게 쓰면 깔끔하다" "나중에 확장하고 싶을 때, 간단하게 확장할 수 있다" 같이 적는 방법을 몸 익혔다. 내 경우 어느 정도 습관이 있다 생각한다.

모처럼이니, id:mizdra의 npm-scripts를 쓸 때 습관을 공유하겠다.

기본형

{
  "scripts": {
    "build": "webpack --mode production",
    "dev": "webpack-dev-server --mode development",
    "lint": "eslint .",
    "test": "jest"
  }
}

가장 단순한 패턴, 4개의 script를 작성한다.

취미 개발하고 있는 프로젝트는 기본 이 4가지 script를 사용했다. 어떤 프로젝트를 해도, npm i && npm run dev하면 개발 빌드, npm run lint하면 정적 해석, 모든 프로젝트에서 script 명을 통일함으로 "이 프로젝트는 script가 뭐였지?" 상황이 나오지 않도록 함

여기까지가 기본형으로 프로젝트에서 하고 싶은 일이 많아지는 경우에 따라 npm-scripts도 확장

lint, dev로 여러 커맨드를 실행

"eslint만이 아니라 tsc --noEmit도 실행하고 싶다" 같이 각 기본 script 안에 여러 커맨드를 실행하고 싶으면 이렇게 확장.

 {
   "scripts": {
     "build": "webpack --mode production",
-    "dev": "webpack-dev-server --mode development",
+    "dev": "run-p dev:*",
+    "dev:webpack": "webpack-dev-server --mode development",
+    "dev:graphql": "graphql-codegen --watch",
+    "dev:tsm": "typed-scss-modules src --nameFormat none --exportType default --watch"
-    "lint": "eslint .",
+    "lint": "run-s -c lint:*",
+    "lint:eslint": "eslint .",
+    "lint:tsc": "tsc --noEmit",
+    "lint:prettier": "prettier --check .",
     "test": "jest"
   }
 }

주요 관점:

반드시 lint를 병렬로 실행하고 싶다면 npx run-p "lint:*" / yarn run-p "lint:*"라 터미널 상에서 입력하면 된다 생각

lint, test의 여러 종류 준비

"eslint --fix, prettier --write 같이 lint script의 여러 종류가 필요하다", "watch 모드로 실행하는 test script가 필요하다" 싶을 때는 이하와 같이 확장

 {
   "scripts": {
     "build": "webpack --mode production",
     "dev": "run-p dev:*",
     "dev:webpack": "webpack-dev-server --mode development",
     "dev:graphql": "graphql-codegen --watch",
     "dev:tsm": "typed-scss-modules src --nameFormat none --exportType default --watch"
     "lint": "run-s -c lint:*",
     "lint:eslint": "eslint .",
     "lint:tsc": "tsc --noEmit",
     "lint:prettier": "prettier --check .",
+    "lint-fix": "run-s -c lint-fix:*",
+    "lint-fix:eslint": "npm run lint:eslint -- --fix",
+    "lint-fix:prettier": "prettier --write .",
     "test": "jest",
+    "test-watch": "npm run test -- --watch"
   }
 }

주요 관점:

:::note 여담

여러 종류를 준비하면 npm-scripts가 확대되어 보기 나쁘고, 관리가 어려우므로 취미 개발에서는 기본 하지 않는다. jest를 watch 모드로 하려면 npm run test -- --watch 같이 필자는 빈 칸을 적으며, 굳이 script를 준비하지는 않는다. 팀 개발하고 있을 때 등에서는 준비해두면 꼼꼼하다 느낄 것 같음 :::

코드 생성용 script 준비

GraphQL Code Generator 등 코드 생성계 도구를 도입하고 싶다면 이렇게 확장

 {
   "scripts": {
+    "gen": "run-s gen:*"
+    "gen:graphql": "graphql-codegen",
+    "gen:tsm": "typed-scss-modules src --nameFormat none --exportType default",
     "prebuild": "npm run gen"
     "build": "webpack --mode production",
     "dev": "run-p dev:*",
     "dev:webpack": "webpack-dev-server --mode development",
-    "dev:graphql": "graphql-codegen --watch",
-    "dev:tsm": "typed-scss-modules src --nameFormat none --exportType default --watch"
+    "dev:graphql": "npm run gen:graphql -- --watch",
+    "dev:tsm": "npm run gen:tsm -- --watch",
     "lint": "run-s -c lint:*",
     "lint:eslint": "eslint .",
     "lint:tsc": "tsc --noEmit",
     "lint:prettier": "prettier --check .",
     "lint-fix": "run-s -c lint-fix:*",
     "lint-fix:eslint": "npm run lint:eslint -- --fix",
     "lint-fix:prettier": "prettier --write .",
     "test": "jest",
     "test-watch": "npm run test -- --watch"
   }
 }

주요 관점:

Unit 테스트 타입 검사 script를 준비

애플리케이션과 Unit 테스트 코드를 각기 tsconfig.json를 사용해서 타입검사하고 싶은 경우, lint:tsc로 분할.

 {
   "scripts": {
     "gen": "run-s gen:*"
     "gen:graphql": "graphql-codegen",
     "gen:tsm": "typed-scss-modules src --nameFormat none --exportType default",
     "prebuild": "npm run gen"
     "build": "webpack --mode production",
     "dev": "run-p dev:*",
     "dev:webpack": "webpack-dev-server --mode development",
     "dev:graphql": "npm run gen:graphql -- --watch",
     "dev:tsm": "npm run gen:tsm -- --watch",
     "lint": "run-s -c lint:*",
     "lint:eslint": "eslint .",
-    "lint:tsc": "tsc --noEmit",
+    "lint:tsc": "run-s -c lint:tsc:*",
+    "lint:tsc:src": "tsc -p tsconfig.src.json --noEmit",
+    "lint:tsc:test": "tsc -p tsconfig.test.json --noEmit",
     "lint:prettier": "prettier --check .",
     "lint-fix": "run-s -c lint-fix:*",
     "lint-fix:eslint": "npm run lint:eslint -- --fix",
     "lint-fix:prettier": "prettier --write .",
     "test": "jest",
     "test-watch": "npm run test -- --watch"
   }
 }

주요 관점:

여담

tsconfig.json이 아니라, tsconfig.src.json / tsconfig.test.json 파일명으로 하면, tsserver(vsc에서 이용되는 ts language server)에서 설정 파일을 알 수 없으므로, 에디터상에서 리얼타임 타입검사를 기본 설정으로 실행하고 싶다면 주의해야함. 보완하다보면 어느새 이상해지므로 이를 의심하는 것이 좋음. tsserver에 설정 파일이 잘 읽혀지는지는 vsc 하부에 있는 툴바에서 확인 가능.

vsc tsserver check

그럼 반드시 tsconfig.json로만 해야 하는 것인가?

Solution Style tsconfig.json를 준비하면 마음대로 이름을 설정할 수 있다

{
  // files 는 반드시 비울 것
  "files": [],
  "references": [
    // 여기에 tsserver에서 참조했으면 하는 설정 파일 경로를 적는다.
    { "path": "./tsconfig.src.json" },
    { "path": "./tsconfig.test.json" }
  ]
}

soultion style tsconfig.json

편리한 테크닉이라 생각하지만 필자외에는 사용하는 사람을 그다지 보지 못함. 알려져 있지 않은 것 뿐인지, 애초에 이렇게 복잡하게 만드는 것을 안하는 것 뿐인가... 여러분은 어떠한가...?

E2E 테스트용 script

E2E 테스를 도입하고 싶다면, 이렇게 확장

 {
   "scripts": {
     "gen": "run-s gen:*"
     "gen:graphql": "graphql-codegen",
     "gen:tsm": "typed-scss-modules src --nameFormat none --exportType default",
     "prebuild": "npm run gen"
     "build": "webpack --mode production",
     "dev": "run-p dev:*",
     "dev:webpack": "webpack-dev-server --mode development",
     "dev:graphql": "npm run gen:graphql -- --watch",
     "dev:tsm": "npm run gen:tsm -- --watch",
     "lint": "run-s -c lint:*",
     "lint:eslint": "eslint .",
     "lint:tsc": "run-s -c lint:tsc:*",
     "lint:tsc:src": "tsc -p tsconfig.src.json --noEmit",
     "lint:tsc:test": "tsc -p tsconfig.test.json --noEmit",
     "lint:prettier": "prettier --check .",
     "lint-fix": "run-s -c lint-fix:*",
     "lint-fix:eslint": "npm run lint:eslint -- --fix",
     "lint-fix:prettier": "prettier --write .",
-    "test": "jest",
+    "test": "jest -c jest.test.js",
     "test-watch": "npm run test -- --watch",
+    "e2e-test": "jest -c jest.e2e-test.js",
+    "e2e-test-watch": "jest -c jest.e2e-test.js --watch",
   }
 }

주요 관점:

사전에 빌드하지 않으면 실행하지 못하는 script 준비

아까부터 E2E 테스트 script는 이런 느낌이라 소개했지만, 보통은 사전에 빌드가 필요하므로, 저대로는 동작하지 않을 것이라 생각함. 또 빌드한 결과를 토대로 lint 하고 싶을 때, 사전에 빌드하지 않으면 안 되는 script가 종종 있음. 만약 그럴 때, 이렇게 확장

 {
   "scripts": {
     "gen": "run-s gen:*"
     "gen:graphql": "graphql-codegen",
     "gen:tsm": "typed-scss-modules src --nameFormat none --exportType default",
     "prebuild": "npm run gen"
     "build": "webpack --mode production",
+    "postbuild": "run-s -c postbuild:*",
+    "postbuild:e2e-test": "jest -c jest.e2e-test.js",
+    "postbuild:lint": "bundlesize",
     "dev": "run-p dev:*",
     "dev:webpack": "webpack-dev-server --mode development",
     "dev:graphql": "npm run gen:graphql -- --watch",
     "dev:tsm": "npm run gen:tsm -- --watch",
     "lint": "run-s -c lint:*",
     "lint:eslint": "eslint .",
     "lint:tsc": "run-s -c lint:tsc:*",
     "lint:tsc:src": "tsc -p tsconfig.src.json --noEmit",
     "lint:tsc:test": "tsc -p tsconfig.test.json --noEmit",
     "lint:prettier": "prettier --check .",
     "lint-fix": "run-s -c lint-fix:*",
     "lint-fix:eslint": "npm run lint:eslint -- --fix",
     "lint-fix:prettier": "prettier --write .",
     "test": "jest -c jest.test.js",
     "test-watch": "jest --watch",
-    "e2e-test": "jest -c jest.e2e-test.js",
-    "e2e-test-watch": "jest -c jest.e2e-test.js --watch",
   }
 }

주요 관점:

근래는 이런 습관이지만, npm run build로 자동으로 실행되는 것은 조금 별로라 생각. postbuild:lint은 그나마 허용하지만 postbuild:e2e-test도 매번 실행하는 것은 별로 같음. 디플로이 전용과 lint/test 전용 github workflow가 각각 있다면, 원래라면 lint/test 용 workflow에만 postbuild:e2e-test가 실행되면 좋겠는데, 디플로이 workflow에도 build할 때 postbuild:e2e-test 되버림

사전에 빌드가 필수라 알 수 있도록 require-build:e2e-test 같이 명백히 하는 게 좋을 지도. 다른 좋은 아이디어가 있다면 알려주면 좋겠음

커맨드 연결 표현이 어려운 script 준비

programmable하다면 다음 script를 추가하면 좋음

{
  "scripts": {
    "dev": "node scripts/dev.js"
  }
}

어려운 것을 하고 싶다면 눈 딱 감고 script 파일을 쓰는게 좋다 생각함. 만병통치약은 없음

실제 예시

이왕 쓴 것 실제 예시도 소개.

역사적 경위에 의해서 지금까지 소개한 습관과는 다른 경우도 있으니 그냥 느껴주시길.

여러분도 자신만의 습관이 있다면 알려주면 좋겠습니다.