결론부터 말하면
스태틱 파일이 포함된 디렉터리를 순회하며 자동으로 ts
는 js
로 scss
는 css
로 트랜스파일을 해주는 코드를 작성하였다.
왜 도입하게 되었나
블렉스에서 프론트엔드 기술은 순도 100% JS
와 CSS
다. JS
의 경우 최대한 하위호환을 유지하기 위해서 ES5
문법으로 코딩하고 있었는데 최근에서야 백틱이 ES6
에 추가된 문법이라는 것을 알게 되었다. 🤔
var number = 5;
var mixed = `내가 가장 좋아하는 숫자는 ${number}다.`;
위 문법에서 사용된 문자가 백틱인데 상당히 충격이었다. 대부분의 경우 위와같이 코드를 작성했기 때문이다. 이후 모든 걸 내려놓고 ES6
문법을 사용중이다. 또한 jQuery
를 남발했던 예전의 나와 jQuery
를 사용하지 않으려는 지금의 내가 쌓아 온 코드들까지 뒤죽박죽 코드에 진절머리나기 시작했다.
TypeScript
그러다 내 눈엔 타입스크립트가 들어왔다. 자유도가 무척이나 높은 JavaScript
에 비해 TypeScript
는 좀 빡빡한 느낌을 줬다. 또한 문법이 다른 언어들과 비슷하게 직교성도 높았고 ES5
로 컴파일이 가능하다는 점 하나로 도입할 가치가 충분했다.
=> TypeScript
// 초기화와 동시에 값을 할당할 경우에는 타입을 안써도 상관없다.
let num: number = 5;
let mention: string = `내가 가장 좋아하는 숫자는 ${num}다.`;
=> ES5
// 컴파일 후 아래와 같이 변환된다.
var num = 5;
var mention = "내가 가장 좋아하는 숫자는" + num + "다.";
타입스크립트를 적용한 뒤 내 목표는 단 하나다. VSCode
에서 단 하나의 빨간줄도 보이지 않는 것. @types/jQuery
를 추가하지 않는 이상 jQuery
의 $
는 빨간줄로 표시된다. jQuery
고마웠어... 이제 보내줄게...
SCSS
부수적으로 타입스크립트를 도입하는 동시에 SCSS
도 도입하고 싶었다. 나는 SCSS
를 상당히 좋아한다. @mixin
, @include
와 같은 문법을 자주 사용하는 건 아니지만 블럭 단위로 스타일시트를 작성한다는 것만으로 정말 훌륭한 라이브러리다.
.blex-write-component input {
...style...
}
.blex-write-component input:foucs {
...style...
}
.blex-write-component textarea {
...style...
}
.blex-write-component .preview {
...style...
}
위와같은 문법을
.blex-write-component {
input {
...style...
:focus {
...style...
}
}
textarea {
...style...
}
.preview {
...style...
}
}
위처럼 작성할 수 있다. 확실히 보기에도 편하고 코딩하기도 좋다. 요즘은 늘어나는 스타일시트에 무언가를 추가하기가 두려워지는데 그럼에도 도입하지 않았던 이유는 당연 장고에서 쿨하게 SCSS
를 도입하는 방법을 몰랐기 때문이다. 스태틱 파일을 장고로 배포하고 있었다면 여러 꼼수를 부릴 수 있었겠지만 별도의 서버로 운용중이라 미뤄왔다. 하지만 언제까지나 미룰수는 없는 노릇. 트랜스파일링을 하더라도 빠르게 도입하고 싶어졌다.
Transpile with Python
일단 언어의 통일을 위해서 파이썬으로 트랜스파일링을 시도하고자 하였다. 사용하고자 한 라이브러리는 아래 두가지.
이들을 활용하여 완벽한 코드를 만들 수 있으리라 생각했는데 TypeScript
의 변환이 좀 이상했다. 리포에서 예시로 보여준 결과와 달리 보여졌으며 실행이 불가했다.
Transpile with Node
결론은 역시 프론트엔드와 관련된 모든 것은 Node
가 짱짱이다.
npm i typescript
npm i node-sass
아주 깔끔하게 작동했다. 필자의 프로젝트에 프론트엔드 파일들은 한 폴더에 몰려있으므로 이 폴더를 순회하며 자동으로 ts
는 js
로 scss
는 css
로 트랜스파일을 해주는 코드를 작성하였다. Python
에는 디렉터리를 순회하는 walk
라는 함수가 있기에 Node
에도 있지 않을까 생각했지만 헛된 생각이었다. 😅
const fs = require('fs');
const path = require('path');
const ts = require('typescript');
const sass = require('node-sass');
function walk(dir) {
fs.readdir(dir, (err, filenames) => {
filenames.forEach((filename) => {
fs.stat(path.join(dir, filename), (err, stat) => {
if(stat.isDirectory()) {
walk(path.join(dir, filename));
} else {
const filePath = path.join(dir, filename);
const ext = filePath.split('.').slice(-1).toString();
if(ext === 'ts') {
fs.readFile(filePath, 'utf8', (err, source) => {
const result = ts.transpileModule(source, {
compilerOptions:{
module: ts.ModuleKind.CommonJS
}
});
fs.writeFile(filePath.slice(0, -2) + 'js', result.outputText, (err) => {
err && console.log(err);
});
});
} else if(ext === 'scss' || ext === 'sass') {
fs.readFile(filePath, 'utf8', (err, source) => {
const result = sass.renderSync({
data: source,
outputStyle: 'compressed'
});
fs.writeFile(filePath.slice(0, -4) + 'css', result.css, (err) => {
err && console.log(err);
});
});
}
}
});
});
});
}
const targetDrectory = './assets';
walk(targetDrectory);
다만 문제라고 해야할지... 오류가 분명 있음에도 컴파일도 매우 잘된다. IDE에서도 확장자가 .ts
임에도 TypeScript
의 문법을 강제하지 않는 느낌도 들었고? 이 부분에 대해선 TypeScript
에 대해서 더 공부해 봐야 할 듯하다.
Ghost