ReScript in Korean

자바스크립트 파일을 변환하기

원문

리스크립트는 프로젝트를 변환하는 고유한 방법을 제공합니다.

  • 동료를 귀찮게 하지 않고 진행할 수 있습니다. (아주 중요합니다!)
  • 변환이 정확한지, 성능이 보장되는지 확인하는 일반적인 충돌을 제거합니다.
  • 다른 사람이 미리 만들어둔 바인딩 라이브러리를 검색하도록 강요하지 않습니다. 리스크립트는 타입스크립트의 DefinitelyTyped 같은 것이 필요하지 않습니다.

따라해보세요!

1단계: 리스크립트 설치

프로젝트에서 npm install --save-dev bs-platform를 실행하고, 새로운 프로젝트를 생성하는것 처럼 bsconfig.json을 프로젝트 루트에 추가하세요. 그리고 bsb -w를 실행합니다.

2단계: 모든 자바스크립트 파일 복사-붙여넣기

src/main.js 파일을 변환해보겠습니다.

const school = require('school');
const defaultId = 10;
function queryResult(usePayload, payload) {
if (usePayload) {
return payload.student;
} else {
return school.getStudentById(defaultId);
}
}

첫번째로 %%raw 자바스크립트 임베딩 기법을 사용해 파일 내용을 새로운 파일 src/Main.res 로 복사합니다.

%%raw(`
const school = require('school');
const defaultId = 10;
function queryResult(usePayload, payload) {
if (usePayload) {
return payload.student;
} else {
return school.getStudentById(defaultId);
}
}
`)
// Generated by BUCKLESCRIPT, PLEASE EDIT WITH CARE
'use strict';
const school = require('school');
const defaultId = 10;
function queryResult(usePayload, payload) {
if (usePayload) {
return payload.student;
} else {
return school.getStudentById(defaultId);
}
}
/* Not a pure module */

bsconfig.json 에 다음 내용을 추가하세요.

"sources": {
"dir" : "src",
"subdirs" : true
},

에디터로 src/Main.bs.js 파일을 열고 커멘드라인에서 diff -u src/main.js src/Main.bs.js 를 실행하세요. 공백을 제외하고 사소한 차이만 있을겁니다. 벌써 1/3을 완료했네요!

항상 각 단계에서 기존 자바스크립트 파일과 리스크립트 결과물인 .bs.js 파일을 열어 비교하세요. 컴파일 결과물은 직접 작성한 자바스크립트와 매우 유사합니다. 변환시 버그가 있어도 쉽게 확인할 수 있습니다!

3단계: 일반적인 리스크립트로 부분 전환

defaultId 변수를 리스크립트 let 바인딩으로 바꿔보겠습니다.

let defaultId = 10
%%raw(`
const school = require('school');
function queryResult(usePayload, payload) {
if (usePayload) {
return payload.student;
} else {
return school.getStudentById(defaultId);
}
}
`)
// Generated by BUCKLESCRIPT, PLEASE EDIT WITH CARE
'use strict';
const school = require('school');
function queryResult(usePayload, payload) {
if usePayload {
return payload.student
} else {
return school.getStudentById(defaultId)
}
}
var defaultId = 10;
exports.defaultId = defaultId;
/* Not a pure module */

결과물을 확인하고 비교하세요. 코드는 여전히 동작합니다. 계속해서 함수를 추출해보겠습니다.

%%raw(`
const school = require('school');
`)
let defaultId = 10
let queryResult = (usePayload, payload) => {
if usePayload {
payload.student
} else {
school.getStudentById(defaultId)
}
}

./node_modules/.bin/bsc -format src/Main.res 를 실행해 코드 포멧을 보정해보세요.

하지만 "The record field student can't be found" 라고 타입 오류가 발생할 것입니다. 괜찮습니다. 변환시 항상 코드가 유효한 구문인지 먼저 확인하세요. 타입 오류 수정은 다음 섹션에서 이어집니다.

4단계: "external" 추가 및 타입 수정

이전 섹션의 타입 오류는 payload의 레코드 선언을 찾을 수 없었기 때문에 발생했습니다. (student 필드를 참조하려 했음) 가능한 한 빠르게 변환하려 하므로 객체를 사용해 타입 선언을 하지 않아도 되도록 합니다.

%%raw(`
const school = require('school');
`)
let defaultId = 10
let queryResult = (usePayload, payload) => {
if usePayload {
payload["student"]
} else {
school["getStudentById"](defaultId)
}
}

이제 다음 타입 오류가 발생했는데요, school을 찾을 수 없다는 오류입니다. external 을 사용해 모듈을 바인딩 해봅시다.

@bs.module external school: 'whatever = "school"
let defaultId = 10
let queryResult = (usePayload, payload) => {
if usePayload {
payload["student"]
} else {
school["getStudentById"](defaultId)
}
}
// Generated by BUCKLESCRIPT, PLEASE EDIT WITH CARE
'use strict';
var School = require('school');
function queryResult(usePayload, payload) {
if (usePayload) {
return payload.student;
} else {
return School.getStudentById(10);
}
}
var defaultId = 10;
exports.defaultId = defaultId;
exports.queryResult = queryResult;
/* school Not a pure module */

급한대로 school을 'whatever라는 폴리모픽 타입으로 컴파일러에게 알리고 이후 어떻게 사용되는지에 따라 알아서 추론되도록 했습니다. 추론기법 자체에는 문제가 없지만 자바스크립트의 값을 적당히 가져오는 행위가 어느정도 위험성을 지닙니다. 이것이 external 페이지에서 보여준 인터롭 기법입니다.

어쨌든 파일은 다시 타입 검사를 통과합니다. .bs.js 출력을 확인하고 원본 .js 파일과 비교하세요. 여기까지가 리스크립트로 변환하는 과정입니다!

이제 직접 작성한 원본 main.js를 지울 수 있습니다. main.js 파일을 불러오는 파일을 찾아 Main.bs.js를 부르도록 바꾸세요.

(선택) 5단계: 정리

payloadschool의 타입을 엄격히 정의하고 싶다면 다음과 같이 변경하세요.

type school
type student
type payload = {
student: student
}
@bs.module external school: school = "school"
@bs.send external getStudentById: (school, int) => student = "getStudentById"
let defaultId = 10
let queryResult = (usePayload, payload) => {
if usePayload {
payload.student
} else {
school->getStudentById(defaultId)
}
}

무엇을 했냐면

  • schoolstudent 값이 오용되는 것을 막기 위해 추상화된(opaque) 타입으로 만들었습니다.
  • payloadstudent 필드를 가지는 타입으로 선언했습니다.
  • getStudentByIdstudent를 접근한다는 메소드로 선언했습니다.

.bs.js 결과물이 변경되지 않은 것을 확인보실 수 있습니다. 자바스크립트 코드를 얼마나 엄밀하게 타이핑할 것인지는 개인의 선택에 달렸습니다만, 너무 공들일 필요까지는 없습니다. 엄밀하자면 한도 끝도 없이 엄밀하게 만들 수 있는데, 점점 체감되는 이득이 줄어들기도 하고 무엇보다 잘 모르는 동료들이 봤을 때 리스크립트가 싫어질 수도 있습니다.

동일한 맥락에서 변환중인 자바스크립트 코드를 래핑하는 함수를 작성하려고 하지 마세요. 결과물에서 지워지는게 보장되는 external을 사용하세요. 그리고 자바스크립트 자료구조를 리스크립트 자료구조로 변환하려 하지 마세요. (특히 배리언트나 리스트) 지금은 고려할 시점이 아닙니다.

결과물에 다른 변환 코드를 추가하는 순간, 전환을 회의적으로 생각하는 동료가 "결과물을 이해했어" 에서 "이 변환으로 해결되는것 보다 더 많은 문제가 발생 할 수 있을 것 같은데... 이럴거면 리스크립트를 왜 써야하지?" 로 태세전환 할 수 있습니다. 그런 순간 더 이상 진행하기 어려울 것입니다.

결론

  • 자바스크립트 코드를 raw 임베딩한 리스크립트 파일로 붙여넣기 합니다.
  • 컴파일 결과물을 열어두고 원본과 비교 및 확인하세요. 테스트가 필요없게요.
  • 문법이 정확한지 항상 확인하세요. 타입은 나중에 수정하니까요.
  • 객체를 (과하게) 사용해 빠르게 전환하세요.
  • 견고하게 하려면 타입을 정리하세요. (선택)
  • 너무 오버하면 상사나 팀원들의 반감을 살 수 있습니다.
  • 기존 코드의 의미나 성능을 그대로 유지하면서 변환을 완료했다는 점을 어필하세요. 변환된 코드는 팀 동료들이 봤을 때에도 아주 친숙할 겁니다.
  • 더 안전하고 완성도 높은 기술을 도입한 성과를 인정받고 승진하세요.