얼마 전 Intellij 에서 Cursor로 IDE를 옮겼다. Intellij도 나름 세팅을 잘 해놔서 꽤 효율적으로 사용하긴 했는데... Cursor를 체험해 보고 갈아탈지 엄청 고민했다. 회사에서 앱 개발을 혼자 맡다 보니, 최대한 낭비하는 시간이 없이 빠르게 개발 사이클을 돌려야 하는데, 그런 측면에서 Cursor는 효율이 잘 나올거라 생각하고, 과감하게 Cursor로 갈아탔다.
회사 특성상 기능들이 빠르게 추가, 제거가 되는 일이 빈번한데, 그럴때마다 유사한 Class
, Page
, View
등을 매번 만들었다 지우기를 반복했다. 물론 스니펫을 사용해서 어느 정도 코드 레벨에서의 템플릿을 구축하고 있긴 했는데, vscode에서 폴더 레벨까지 템플릿으로 만드는 방법을 찾을 수가 없었다. (내가 못 찾는 거일 수도 있음 ㅎㅎ;) 물론 템플릿 생성을 지원하는 extension 들은 본 것 같긴 한데, 왠지 사용하기 싫었다. 그리고 그건 나 개인의 IDE에 적용되기 때문에, 다른 사람들이랑 공유가 안 되는 단점도 있다.
그냥 반복되는 모든 구조를 다 템플릿으로 만들어버리는 방법이 없는지 찾아봤다. 그러다 구글링을 좀 해보니, Mason 이라는 Dart 기반 파일/폴더 템플릿 생성 툴을 사용하면 쉽게 템플릿을 만들 수 있을 것 같아서 재미삼아 도입해 보려 했다.
Mason은 Dart 생태계에서 반복되는 파일/폴더 구조를 템플릿으로 만들어주는 CLI 도구라고 한다. Dart 패키지이며, 내가 현재 앱에서 사용 중인 Spider(Asset 관리에 특화된 코드 생성기)
와 비슷한 역할을 하는 코드 제네레이터다. 그냥 내가 딱 찾던 역할을 하는 툴이다.
반복되는 코드, 폴더 생성을 자동화해서 생산성을 높이기 위함이다.다수의 개발자가 있는 팀에서는 통일된 구조로 템플릿을 만들어서 사용하면 컨벤션 지키기도 괜찮을 것 같고, 온보딩 같은 곳에도 쓰이면 좋을 것 같다.
dart pub global activate mason_cli
Dart 패키지 설치 명령어를 사용한다. 다른 팀원이 Mason 을 사용하기 위해서도 해당 팀원의 환경에도 mason이 설치되어 있어야 한다.
설치 후 mason --version
으로 정상 설치되었는지 확인한다.
mason init
Init 명령어를 실행하면 mason.yaml
파일이 Init 명령어를 실행한 경로에 생성된다. 이 mason.yaml
파일은 Flutter의 pubspec.yaml 이랑 동일한 역할을 한다고 생각해도 된다. 내부, 외부에 있는 Brick
이라는 템플릿 패키지를 등록하고, 다운받는다. pubspec.yaml처럼 내부 Brick은 Path를 지정해서 등록한다.
Brick은 그냥 템플릿 그 자체라고 생각하면 된다.
source ~/.zshrc
Init이후엔 안내하는 대로 source명령어를 통해 Dart cli설정을 적용시켜주자.
mason new <template_name>
mason new <template_name> -o <path>
새로운 템플릿(Brick)을 만드는 과정이다. -o
옵션은 특정 Path에 Brick을 만들고, 옵션을 따로 넣지 않으면 명령어를 실행한 디렉토리에 새로운 Brick을 생성한다.
명령어가 정상적으로 실행되면 이렇게 <template_name>으로 지정한 폴더와 하위에 기본 템플릿 폴더 및 파일들이 생성된다.
// 예시
sealed class {{name.pascalCase()}}State {
const {{name.pascalCase()}}State();
}
class InitialState extends {{name.pascalCase()}}State {
const InitialState();
}
이제 진짜 사용할 템플릿 코드를 작성하면 된다. 템플릿은 __brick__
폴더 하위에 작성한다. 대략 위와 같은 형식으로 작성한다. Brick을 만들 때도 문법이 있는데, 자세한 문법은 Brick Syntax를 참조하면 좋다.
템플릿을 다 작성했으면, 이제 brick 폴더 하위 구조가 위 사진과 같이 구성될 것이다.
이제 템플릿을 다 작성했으니, yaml 파일 등에 어떤 템플릿을 사용할지 등록해야 한다. 등록 및 해제하는 방법은 간단하다.
bricks:
page:
path: mason_templates/page
mason.yaml
파일에 만들어둔 Brick
을 등록한다. 나의 경우엔, brick들을 mason_templates이라는 폴더 하위에 만들어서 path 가 위와 같다.
mason add <brick_name> --path <brick_path>
직접 작성하지 않고 CLI 로 등록할 수도 있다. pub_dev 에서 설치하는 패키지처럼, 직접 pubspec.yaml 에 등록할 수도 있지만, CLI 를 통해 설치하는 방법도 있듯이 mason도 동일하다.
mason remove <brick_name>
remove
명령어는 템플릿 자체를 지우는 게 아니고, yaml 파일에서 <brick_name> 을 갖는 brick의 의존성만을 없애는 명령어다. 이것 또한 수기로 mason.yaml 파일에서 그냥 지워도 된다.
mason make <brick_name>
make 명령어를 사용해 템플릿 코드를 생성한다. brick 을 생성하면 brick 폴더 내부에 brick.yaml 파일이 존재하는데, 이 brick.yaml
파일은 템플릿을 만들 때 사용 할 변수, 프롬프트 등을 설정하는 파일이다. 그래서 brick 하나당 한 개씩 맵핑되어 있다.
위의 brick 폴더 내부에 정의된 brick 이 make 명령어를 통해 위 이미지에 보이는 코드를 생성해준다.
mason.yaml # mason init 을 한 디렉토리 기준
< ------------------>
<brick_name>/ # mason new 명령어로 생성된 템플릿 폴더
├── brick.yaml # 템플릿 메타 정보 및 변수 정의 파일
└── __brick__/ # 템플릿 코드가 들어있는 실제 폴더
├── {{variable}}.dart # 변수 치환 가능한 파일명과 내용
└── ...
mason init, mason new <brick_name> 을 통해 생성되는 폴더 및 파일들이다. 하나씩 간략하게 각 파일 및 폴더가 무슨 역할을 하는지만 보면 좋을 것 같다.
bricks:
page:
path: mason_templates/page
mason add
명령어를 쓰면 해당 파일이 자동으로 수정됨Mason은 초기화 위치에 따라 템플릿이 적용되는 범위가 결정된다.
예를 들어, lib/ 디렉토리 하위에서mason init
을 수행하면, 해당 위치를 기준으로 생성된 mason.yaml은 lib/ 이하에서만 유효하며, 상위 디렉토리에서는 해당 브릭들을 인식할 수 없다. 따라서 프로젝트 규모가 크거나 패키지/모듈 단위로 관리되는 구조라면, 각 모듈별로 필요한 브릭만 별도로 구성해 모듈 단위로 Mason을 운영할 수 있다.
name: page
description: A simple page template
vars:
name:
type: string
description: Your name
default: snake_case
prompt: input file name
mason make를 통해 직접 선언된 name을 받을 수 있다.
Argument로 var 값을 넣어서 명령어 실행 시, 따로 프롬프트 없이 입력된 변수 값으로 실행된다.
Brick 템플릿에 사용되는 {{변수}}
문법은 Dart 문법이 아니기 때문에 Syntax Error가 발생한다. 물론 빌드하는데 영향은 없지만, Analyzer에 잡혀서 거슬린다.
analyzer:
errors:
constant_identifier_names: ignore
exclude:
- "**/__brick__/**"
include: package:flutter_lints/flutter.yaml
그럴 땐 analysis.yaml
에서 직접 Mason 템플릿 관련 디렉토리를 exclude 한다.
CLI 기반이다 보니 템플릿을 구성해야 할 디렉토리 위치를 찾아 들어가서 명령어를 입력해야 해서 약간 귀찮긴 하다. 그래도 직접 폴더 구성하고 파일 만드는 시간보다는 훨씬 적으니 그거로 만족...
컨벤션이 확실하게 정해져 있다면 더 편리할 것 같다. 예를 들어, "page 템플릿은 무조건 lib/app/module 밑에만 생성된다"
. 라는 컨벤션이 있으면 스크립트를 만들어서 의도에 맞게 Path를 미리 지정해주면 굳이 생성할 위치를 찾아가지 않아도 될 것 같다.