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, 의존성 관리 등의 명확한 장점을 가지고 있습니다.
앞으로의 행보가 기대되는 언어입니다.