HELLO DENO
개인적으로 deno가 나왔을때는 기대가 컸습니다. Node 개발자
Ryan Dahl이 Design Mistakes in Node을 작성하고 이후 새로운 판에 만든 언어이기 때문이었습니다. 특징으로 Node와는 다른 게Rust로 구축되어 안전하고 빠른며, TypeScript를 사용할 시 별다른 도구 없이 바로 사용할 수 있습니다.
특징
특징으로 보안, typescript 등이 가장 유명(?)합니다.
- 웹 플랫폼 기능을 제공하고 웹 플랫폼 표준을 채택합니다.
- 기본적으로 보안. 명시적으로 활성화하지 않는 한 파일, 네트워크 또는 환경에 액세스할 수 없습니다.
- 즉시 TypeScript를 지원합니다.
- 단일 실행 파일만 제공합니다.
- 종속성 검사기(deno info) 및 코드 포맷터(deno fmt)와 같은 내장 개발 도구가 있습니다.
- Deno와 함께 작동하도록 보장되는 검토된(감사된) 표준 모듈 세트가 있습니다: deno.land/std.
- Deno를 사용하고 탐색하는 데 관심이 있는 여러 회사가 있습니다.
설치
설치는 문서를 따라서 하면 쉽게 합니다.
$ curl -fsSL https://deno.land/install.sh | sh
or
$ brew install deno
첫 경험
간단하게 excel 파일을 읽어서 API 호출하는 프로그램을 작성했습니다.
graph TD; A[EXCEL파일] -->|읽기| B(데이터정재); B --> C{등록}; C -->|성공| D[완료]; C -->|실패| D;
// @deno-types="https://deno.land/x/sheetjs/types/index.d.ts"
import "https://deno.land/x/xhr@0.1.1/mod.ts";
import { installGlobals } from "https://deno.land/x/virtualstorage@0.1.0/mod.ts";
installGlobals();
import * as XLSX from 'https://deno.land/x/sheetjs/xlsx.mjs';
import { FirebaseApp, initializeApp } from 'npm:firebase/app';
import {
getAuth,
createUserWithEmailAndPassword,
signInWithEmailAndPassword,
Auth,
signInWithCustomToken,
} from 'npm:firebase/auth';
const firebaseConfig = {
apiKey: "apiKey",
authDomain: "authDomain",
projectId: "projectId",
storageBucket: "storageBucket",
messagingSenderId: "messagingSenderId",
appId: "appId",
};
const app = initializeApp(firebaseConfig);
const auth: Auth = getAuth(app);
const users = [
{ email: 'email1@lahuman.io', password: 'lahuman' },
{ email: 'email2@lahuman.io', password: 'lahuman' },
{ email: 'email3@lahuman.io', password: 'lahuman' },
{ email: 'email4@lahuman.io', password: 'lahuman' },
{ email: 'email5@lahuman.io', password: 'lahuman' },
{ email: 'email6@lahuman.io', password: 'lahuman' },
{ email: 'email7@lahuman.io', password: 'lahuman' },
{ email: 'email8@lahuman.io', password: 'lahuman' },
];
const firebaseLogin:any = await Promise.allSettled(users.map(async (u) => await signInWithEmailAndPassword(auth, u.email, u.password)));
const firebaseIdTokens:any = await Promise.allSettled(firebaseLogin.map(async (r) => await r.value.user.getIdToken()));;
const tokens:any = await Promise.allSettled(firebaseIdTokens.map(async token => {
const result = await fetch('https://user.testapp/v1/auth/chkUserAndsignIn', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token.value}`
},
});
return result.json();
}
));
const ID = {
"KIM": 7001,
"BO": 7002,
"JO": 7003,
"LATE": 7004,
"Je": 7005,
"ssie": 7006,
"kku": 7007,
"mom": 7008,
}
const TOKEN = Object.keys(ID).reduce((acc, cur, idx) => {
acc[cur] = tokens[idx].value.token
return acc;
}, {});
const filedata = Deno.readFileSync("dataExample.xlsx");
const workbook = XLSX.read(filedata, { type: "buffer" });
const workssheet = workbook.Sheets[workbook.SheetNames[0]];
const aoa = XLSX.utils.sheet_to_json(workssheet, { range: 3 });
const D_TCD_LIST = {
"궁금해요": "QST",
"견생일지": "LDR",
"댕댕상식": "NIF",
"리뷰한컷": "ROP",
}
const C_TCD_LIST = {
"궁금해요": "QST",
"묘생일지": "LDR",
"냥냥상식": "NIF",
"리뷰한컷": "ROP"
}
let result: any = [];
for (const idx in aoa) {
const d = aoa[idx];
const data = {
"pstTitle": d.pst_title,
"pstTxt": d.pst_txt,
"pstTcd": d.pet_dcd === '견' ? D_TCD_LIST[d.pst_tcd] : C_TCD_LIST[d.pst_tcd],
"fileDItems": d.atch_file_nm ? d.atch_file_nm.split(":").map((nm, idx) => {
const ext = d.extn_nm.split(":")[idx].toLowerCase();
const cd = d.atch_file_extn_cd.split(":")[idx];
const fileNm = nm.includes('.JPG') ? `${nm.split(".")[0]}.${ext}` : nm;
return {
atchFileNm: fileNm,
atchFilePath: `https://s3.ap-northeast-2.amazonaws.com/s3.testapp/priming/${ID[d.nknm_nm]}/${fileNm}`,
extnNm: ext,
atchFileExtnCd: cd
};
}) : [],
"tagItems": d.tag_nm.split(":").map(d => ({ tagNm: d }))
};
// console.log(data)
const jsonResponse = await fetch("https://user.testapp/v1/cmtPost", {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${TOKEN[d.nknm_nm]}`
},
body: JSON.stringify(data)
});
const json = await jsonResponse.json();
if (!json.id) {
result.push({
idx,
json,
data
})
}
};
await Deno.writeTextFile("./result.txt", JSON.stringify(result));
실행
deno의 특징인 보안옵션을 꼭 넣어야 합니다. 파일을 읽고, 쓰고 인터넷 연결등의 처리시 옵션을 추가 하게 되어, 취약점을 최소화 할 수 있습니다.
- –allow-read : 읽기 권한
- –allow-write : 쓰기 권한
- –allow-net : 인터넷 연결 권한
- –unstable : npm 모듈 사용 권한
$ deno run --allow-read --allow-net --allow-write --unstable main.ts
문제 해결
firebase 연동은 deno에서 제공되는 가이드를 테스트 해보았지만,
import "https://cdn.skypack.dev/firebase@8.7.0/auth";
import "https://cdn.skypack.dev/firebase@8.7.0/firestore";
import 구문에서 아래와 같이 오류가 발생합니다.
error: Uncaught TypeError: Cannot read properties of null (reading 'INTERNAL')
at Ke (https://cdn.skypack.dev/-/@firebase/auth@v0.16.8-fuIw7Baswv7gZ8Si3voa/dist=es2019,mode=imports/optimized/@firebase/auth.js:2184:21)
at new Ze (https://cdn.skypack.dev/-/@firebase/auth@v0.16.8-fuIw7Baswv7gZ8Si3voa/dist=es2019,mode=imports/optimized/@firebase/auth.js:2310:9)
at https://cdn.skypack.dev/-/@firebase/auth@v0.16.8-fuIw7Baswv7gZ8Si3voa/dist=es2019,mode=imports/optimized/@firebase/auth.js:3835:32
at https://cdn.skypack.dev/-/@firebase/auth@v0.16.8-fuIw7Baswv7gZ8Si3voa/dist=es2019,mode=imports/optimized/@firebase/auth.js:8108:4
해결 방안
deno git issues에 올라와있는 해결 방안으로 처리시 동작에 문제 없이 실행 됩니다.
// @deno-types="https://cdn.esm.sh/v58/firebase@9.6.0/app/dist/app/index.d.ts"
import { deleteApp, FirebaseOptions, initializeApp } from "https://www.gstatic.com/firebasejs/9.6.0/firebase-app.js";
// @deno-types="https://cdn.esm.sh/v58/firebase@9.6.0/database/dist/database/index.d.ts"
import { getDatabase, ref, set } from "https://www.gstatic.com/firebasejs/9.6.0/firebase-database.js";
해결 코드
// @deno-types="https://deno.land/x/sheetjs/types/index.d.ts"
import "https://deno.land/x/xhr@0.1.1/mod.ts";
import { installGlobals } from "https://deno.land/x/virtualstorage@0.1.0/mod.ts";
installGlobals();
import * as XLSX from 'https://deno.land/x/sheetjs/xlsx.mjs';
// @deno-types="https://cdn.esm.sh/v58/firebase@9.6.0/app/dist/app/index.d.ts"
import { FirebaseApp, initializeApp } from "https://www.gstatic.com/firebasejs/9.6.0/firebase-app.js";
// @deno-types="https://cdn.esm.sh/v58/firebase@9.6.0/database/dist/database/index.d.ts"
import {
getAuth,
createUserWithEmailAndPassword,
signInWithEmailAndPassword,
Auth,
signInWithCustomToken,
} from "https://www.gstatic.com/firebasejs/9.6.0/firebase-auth.js";
const firebaseConfig = {
apiKey: "apiKey",
authDomain: "authDomain",
projectId: "projectId",
storageBucket: "storageBucket",
messagingSenderId: "messagingSenderId",
appId: "appId",
};
const app = initializeApp(firebaseConfig);
const auth: Auth = getAuth(app);
const users = [
{ email: 'email1@lahuman.io', password: 'lahuman' },
{ email: 'email2@lahuman.io', password: 'lahuman' },
{ email: 'email3@lahuman.io', password: 'lahuman' },
{ email: 'email4@lahuman.io', password: 'lahuman' },
{ email: 'email5@lahuman.io', password: 'lahuman' },
{ email: 'email6@lahuman.io', password: 'lahuman' },
{ email: 'email7@lahuman.io', password: 'lahuman' },
{ email: 'email8@lahuman.io', password: 'lahuman' },
];
const firebaseLogin:any = await Promise.allSettled(users.map(async (u) => await signInWithEmailAndPassword(auth, u.email, u.password)));
const firebaseIdTokens:any = await Promise.allSettled(firebaseLogin.map(async (r) => await r.value.user.getIdToken()));;
const tokens:any = await Promise.allSettled(firebaseIdTokens.map(async token => {
const result = await fetch('https://user.testapp/v1/auth/chkUserAndsignIn', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token.value}`
},
});
return result.json();
}
));
const ID = {
"KIM": 7001,
"BO": 7002,
"JO": 7003,
"LATE": 7004,
"Je": 7005,
"ssie": 7006,
"kku": 7007,
"mom": 7008,
}
const TOKEN = Object.keys(ID).reduce((acc, cur, idx) => {
acc[cur] = tokens[idx].value.token
return acc;
}, {});
const filedata = Deno.readFileSync("dataExample.xlsx");
const workbook = XLSX.read(filedata, { type: "buffer" });
const workssheet = workbook.Sheets[workbook.SheetNames[0]];
const aoa = XLSX.utils.sheet_to_json(workssheet, { range: 3 });
const D_TCD_LIST = {
"궁금해요": "QST",
"견생일지": "LDR",
"댕댕상식": "NIF",
"리뷰한컷": "ROP",
}
const C_TCD_LIST = {
"궁금해요": "QST",
"묘생일지": "LDR",
"냥냥상식": "NIF",
"리뷰한컷": "ROP"
}
let result: any = [];
for (const idx in aoa) {
const d = aoa[idx];
const data = {
"pstTitle": d.pst_title,
"pstTxt": d.pst_txt,
"pstTcd": d.pet_dcd === '견' ? D_TCD_LIST[d.pst_tcd] : C_TCD_LIST[d.pst_tcd],
"fileDItems": d.atch_file_nm ? d.atch_file_nm.split(":").map((nm, idx) => {
const ext = d.extn_nm.split(":")[idx].toLowerCase();
const cd = d.atch_file_extn_cd.split(":")[idx];
const fileNm = nm.includes('.JPG') ? `${nm.split(".")[0]}.${ext}` : nm;
return {
atchFileNm: fileNm,
atchFilePath: `https://s3.ap-northeast-2.amazonaws.com/s3.testapp/priming/${ID[d.nknm_nm]}/${fileNm}`,
extnNm: ext,
atchFileExtnCd: cd
};
}) : [],
"tagItems": d.tag_nm.split(":").map(d => ({ tagNm: d }))
};
// console.log(data)
const jsonResponse = await fetch("https://user.testapp/v1/cmtPost", {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${TOKEN[d.nknm_nm]}`
},
body: JSON.stringify(data)
});
const json = await jsonResponse.json();
if (!json.id) {
result.push({
idx,
json,
data
})
}
};
await Deno.writeTextFile("./result.txt", JSON.stringify(result));
해결 코드 실행
npm 모듈을 사용하지 않음으로, 더이상
--unstable옵션의 필요가 없어집니다.
$ deno run --allow-read --allow-net --allow-write main.ts
마치며
deno는 분명 javascript 를 이용하여 개발하는 사용자에게 매력적인 언어입니다. 하지만 아직까지 생태계가 완성되지 않았으며, 예제도 부족합니다.
그래도 보안, typescript, 의존성 관리 등의 명확한 장점을 가지고 있습니다.
앞으로의 행보가 기대되는 언어입니다.