• Home
  • About
    • lahuman photo

      lahuman

      열심히 사는 아저씨

    • Learn More
    • Facebook
    • LinkedIn
    • Github
  • Posts
    • All Posts
    • All Tags
  • Projects

HELLO DENO

20 Sep 2022

Reading time ~5 minutes

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, 의존성 관리 등의 명확한 장점을 가지고 있습니다.

앞으로의 행보가 기대되는 언어입니다.

참고자료

  • deno
  • Deploy failures from the firebase tutorial: https://deno.com/deploy/docs/tutorial-firebase/


denotypescript Share Tweet +1