Skip to content

Instantly share code, notes, and snippets.

@yano3nora
Last active August 21, 2023 03:42
Show Gist options
  • Save yano3nora/182316a8a1a6e860b57e1297606bcee9 to your computer and use it in GitHub Desktop.
Save yano3nora/182316a8a1a6e860b57e1297606bcee9 to your computer and use it in GitHub Desktop.
[js: exceljs] #js

Overview

github.com/exceljs/exceljs

js で excel / csv うにょるやつ。そのまんま。

$ npm i exceljs

Blob Download with URL.createObjectURL

URL.createObjectURL
BOMやBlobを理解してJavaScriptでCSVを出力する
[React / JavaScript] ExcelJSで実装したCSV出力機能でSJIS変換対応をしてみた

  • client 側で api から返ったデータを blob にして csv とか download させるやつ
  • server 側で複雑な stream donwload 組むまでもないデータ量なら使えるかな
  • 将来 File System Access API が普及すれば blob 生成を複数 api kick の promise で構成して client side 主体で streaming download とかできるようになる気がする

以下は react query 使って react component 側から操作する例。

import ExcelJS from 'exceljs'
import { useMutation } from '@tanstack/react-query'

/**
 * client side で取得済み data を utf8 csv へ変換して download させる
 *
 * NOTE client 主体なので、大きいファイルには利用しないこと
 *      将来 File System Access API が普及すれば client 主体で streaming
 *      download できるようになるかも
 */
export const useDownloadCsv = ({ filename, header, rows }: {
  filename: string
  header: Partial<ExcelJS.Column>[]
  rows: { [key: NonNullable<ExcelJS.Column['key']>]: any }[]
}) => {
  return useMutation(
    ['download-csv'],
    async () => {
      const worksheet = new ExcelJS.Workbook().addWorksheet('sheet1')
      worksheet.columns = header
      worksheet.addRows(rows)

      /**
       * @link https://dev.classmethod.jp/articles/react-i-tried-sjis-conversion-support-with-csv-output-function-implemented-in-exceljs/
       */
      const dynamicATag = document.createElement('a')
      const url = window.URL.createObjectURL(new Blob(
        [
          new Uint8Array([0xEF, 0xBB, 0xBF]), // UTF8 BOM for Windows
          new Uint8Array(await worksheet.workbook.csv.writeBuffer())
        ],
        { type: 'application/octet-binary' },
      ))
      dynamicATag.href = url
      dynamicATag.download = filename
      dynamicATag.click()
      dynamicATag.remove()
      window.URL.revokeObjectURL(url) // destruct

      return url
    },
  )
}
const users = [
  { id: 1, name: 'john' },
  { id: 2, name: 'jane' },
]

const { mutate, isLoading } = useDownloadCsv({
  filename: `example.csv`,
  header: [
    { header: 'ID', key: 'id' },
    { header: 'ユーザ名', key: 'name' },
  ],
  rows: users,
})

return <>
  <Button onClick={async () => await mutate()}>
    Download Users
  </Button>
  <LoadingOverlay isOpen={isLoading} />  
</>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment