Deno + react-bootstrap – XLSX를 CSV로 변환하기

어제 글을 존나 날림으로 썼는데 왜 그러냐 하면 그 글의 내용 이외에 mui 적용하려고 똥꼬쑈를 하다가 결국에는 안되었기 때문이었다. 아니 esm.sh 통해서 설치까지는 되는데 스타일이 제대로 안먹음… 쉬발…

오늘은 왜 늦게 쓰기 시작하냐면 우마무스메 3기 마지막화를 봐야했기 때문이다.

여튼 이어서…

mui가 안되므로 울며 겨자먹기로 부트스트랩을 써봅시다. 붙스트랩이 좋은 점은 css는 그냥 cdn 암거나 가져다 쓰거나 공식사이트에서 css받아서 적용하면 된다는 것이다. 물론 버전은 잘 맞춰야겠지..

사실 어제 부트스트랩도 시도를 안해본건 아닌데, esm.sh 명령어로 추가하려고 하면 @types/react-transition-group 관련 에러가 발생한다. 그러므로 deno.json에 수동으로 추가해줍시다.

deno.json

{
  "tasks": {
    "esm:add": "deno run -A https://esm.sh/v135 add",
    "esm:update": "deno run -A https://esm.sh/v135 update",
    "esm:remove": "deno run -A https://esm.sh/v135 remove",
    "bundle": "deno run -A bundle.ts",
    "cache": "deno cache --reload --lock=lock.json --lock-write deps.*"
  },
  "imports": {
    "react-bootstrap": "https://esm.sh/v135/react-bootstrap@2.9.2",
    "react-bootstrap/": "https://esm.sh/v135/react-bootstrap@2.9.2/",
    "react-dom": "https://esm.sh/v135/*react-dom@18.2.0",
    "react-dom/": "https://esm.sh/v135/*react-dom@18.2.0/",
    "react": "https://esm.sh/v135/react@18.2.0",
    "react/": "https://esm.sh/v135/react@18.2.0/",
    "$std": "https://deno.land/std@0.210.0",
    "$std/": "https://deno.land/std@0.210.0/"
  },
  "scopes": {
    "https://esm.sh/v135/": {
      "loose-envify": "https://esm.sh/v135/loose-envify@1.4.0",
      "scheduler": "https://esm.sh/v135/scheduler@0.23.0"
    }
  },
  "compilerOptions": {
    "lib": [
      "dom",
      "esnext",
      "deno.dom",
      "deno.window"
    ],
    "jsx": "react-jsx",
    "jsxImportSource": "react"
  }
}

deps.bundle.ts

export * as esbuild from "https://deno.land/x/esbuild@v0.19.2/mod.js";
export { denoPlugins } from "https://deno.land/x/esbuild_deno_loader@0.8.2/mod.ts";
export { resolve } from "$std/path/mod.ts";

deps.bootstrap.ts

export * from "react-bootstrap";

dist/index.html

<!DOCTYPE html>
<html lang="ko">
  <head>
    <title>Deno Bundle Tests</title>
    <meta charset="utf-8" />
    <link
      rel="stylesheet"
      href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css"
      integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM"
      crossorigin="anonymous"
    />
  </head>
  <body>
    <div id="root"></div>
    http://./bundle.js
  </body>
</html>

테스트이므로 그냥 cdn에 있는 걸 썼다.

bundle.ts

import { denoPlugins, esbuild, resolve } from "./deps.bundle.ts";

await esbuild.build({
  plugins: [...denoPlugins({
    configPath: resolve(Deno.cwd(), "deno.json")
  })],
  entryPoints: ["./src/index.tsx"],
  outfile: "./dist/bundle.js",
  bundle: true,
  format: "esm",
  minify: true,
});


esbuild.stop();

이렇게 설정해 두면 react-bootstrap을 쓸 수 있다!

fresh는 tailwindcss를 쓸 수 있지만… 난 그거 못하겠더라…


그래서 오늘 해볼 것은 xlsx 파일을 CSV 파일(UTF-8)로 변경하는 것입니다.

여기서는 sheetjs를 사용할 것인데… 이것도 업데이트 안된지 2년이나 됐네… index.tsx는 생략하고 App.tsx만 보면 다음과 같다.

src/App.tsx

import * as React from "react";
import { XLSX } from "../deps.xlsx.ts";
import { Button, Card, Form, InputGroup, Table } from "../deps.bootstrap.ts";

export default function App(): JSX.Element {
  const [workbook, setWorkbook] = React.useState<ReturnType<typeof XLSX.read> | null>(null);
  const [file, setFile] = React.useState<File | null>(null);
  const [sheets, setSheets] = React.useState<Array<string>>([]);

  function onChangeFile(e: React.ChangeEvent<HTMLInputElement>) {
    const currentTarget = e.currentTarget as HTMLInputElement;
    if (currentTarget?.files?.length === 1) {
      setFile(currentTarget.files[0]);
    }
  }

  async function onOpenFile() {
    if (file === null) {
      return;
    }

    const buffer = await file.arrayBuffer();
    const workbook = XLSX.read(buffer, { type: "buffer" });
    setWorkbook(workbook);
    setSheets(workbook.SheetNames as Array<string>);
  }

  function downloadAsCSV(sheetName: string) {
    const sheet = workbook?.Sheets[sheetName];
    if (file === null || sheet === undefined) {
      return;
    }
    const downloader = document.createElement("a");
    downloader.href = URL.createObjectURL(new Blob([XLSX.utils.sheet_to_csv(sheet)]));
    downloader.download = `${file.name} - ${sheetName}.csv`;
    downloader.click();
  }

  return (
    <Card style={{ width: "100%", height: "50vh", marginTop: "25vh" }}>
      <Card.Header>
        <Card.Title>XLSX 2 CSV : {file?.name}</Card.Title>
      </Card.Header>
      <Card.Body>
        <Card.Text>
          <Table striped bordered hover>
            <colgroup>
              <col style={{ width: "80%" }} />
              <col style={{ width: "20%" }} />
            </colgroup>
            <thead>
              <tr>
                <th>Sheet Name</th>
                <th>Controls</th>
              </tr>
            </thead>
            <tbody>
              {
                sheets.map((sheet, index) => (
                  <tr key={sheet}>
                    <td>{sheet}</td>
                    <td>
                      <Button variant="secondary" onClick={() => downloadAsCSV(sheet)}>Convert & Save</Button>
                    </td>
                  </tr>
                ))
              }
            </tbody>
          </Table>
        </Card.Text>
      </Card.Body>
      <Card.Footer>
        <InputGroup style={{ display: "flex" }}>
          <InputGroup.Text>File</InputGroup.Text>
          <Form.Control type="file" onChange={onChangeFile} accept=".xlsx"/>
          <Button type="button" onClick={onOpenFile} disabled={file === null}>Open</Button>
        </InputGroup>
      </Card.Footer>
    </Card>
  );
}

deps.xlsx.ts

// @deno-types="https://cdn.sheetjs.com/xlsx-0.20.1/package/types/index.d.ts"
export * as XLSX from "https://cdn.sheetjs.com/xlsx-0.20.1/package/xlsx.mjs";

XLSX를 esm.sh에서 참조하여 해보려고 했으나 제대로 작동하지 않았고, sheetjs의 deno 지원쪽 페이지에서 찾은 js 경로로 했더니 잘 작동하는데, 문제는 얘는 타입이 없.,…. 는 줄 알았는데 페이지 내 코드 주석인 // @deno-types…를 추가하니 잘 불러온다. XLSX.Workbook이나 ReturnType<typeof XLSX.read>나 같으므로 상관이 없다.

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다