6

纯前端生成Excel文件骚操作——WebAssembly & web workers

 2 years ago
source link: https://segmentfault.com/a/1190000041061339
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

最近业务上有数据大屏的需求,要求不仅能展示数据,同时能提供所选日期范围的数据下载。本文纯记录实现方案作为笔记,实现细节十分不完备。

xlsx电子表格的标准规范详见:Full XML Schema。下面两个库都基于这个规范实现了这类格式文件的读写(salute!∠(°ゝ°))。

SheetJS

SheetJS是用于多种电子表格格式的解析器和编写器。通过官方规范、相关文档以及测试文件实现简洁的JS方法。SheetJS强调解析和编写的稳健,其跨格式的特点和统一的JS规范兼容,并且ES3/ES5浏览器向后兼容IE6。

excelize

Go语言编写的可以读写电子表格类型文件的公共库

更静默:webworker

Web Worker为Web内容在后台线程中运行脚本提供了一种简单的方法。线程可以执行任务而不干扰用户界面。

我们将SheetJS处理数据、生成表格数据(book类型数据)的流程另起一个线程实现。(虽然另起一个线程从体验上不会过度影响主UI线程,但本身启动成本比较高)。

该组件的目录如下

NewDashboard
├── components
│   ├── LongCard
│   │   ├── echartsOption.ts
│   │   ├── index.tsx
│   │   └── style.module.less
│   └── ShortCard
│       ├── echartsOption.ts
│       ├── index.tsx
│       └── style.module.less
├── index.tsx                        # 在该文件与webworker通信
├── makeTable.ts                     # 在该文件实现webworker
└── style.module.less

mdn给的samples的worker都是加载外部代码的。在我们这种组织目录下,worker应该在一个文件内实现,并导出一个worker实例。这里需要借助URL.createObjectURL(blob)构造一个外链。

代码如下:

// @file makeTable.ts
const blob = new Blob(
  [
    `
    importScripts('https://g.alicdn.com/code/lib/xlsx/0.17.4/xlsx.full.min.js');
    const GOODS_EFFECT_TITLE = [
      '开播时间',
      '下播时间',
      '直播间',
      '商品名称',
      '商品',
      '点击人数',
      '成交人数',
      '粉丝成交比例',
      '引导成交金额',
    ];
    
    // 接收主进程的表格数据
    onmessage = function({ data }) {
      console.log('from main routine', data);
      const book = XLSX.utils.book_new();
      const sheet = XLSX.utils.aoa_to_sheet([GOODS_EFFECT_TITLE, ...data]);
      XLSX.utils.book_append_sheet(book, sheet, '工作表1');
      
      // book的数据回传给主进程
      postMessage({ book });
    };
`,
  ],
  { type: 'text/javascript' },
);

export const worker = new Worker(URL.createObjectURL(blob));

注意几个点:

  1. 由于在worker内没有DOM、windows等对象,所以没有办法直接使用 XLSX.utils.table_to_book 方法将table元素直接导出为xlsx表格数据。
  2. importScript 方法是并行加载所有列出的资源,但执行是同步的。这里需要将SheetJS的资源加载进worker里。
  3. 主进程的方法:
  // @file index.tsx
import { worker } from './makeTable';

function download() {
   // aoa_to_sheet 方法需要一个二维数组来形成电子表格
    worker.postMessage([[1, 2, 3]]);
    worker.onmessage = ({ data }) => {
      window.XLSX.writeFile(data.book, '测试.xlsx');
    };
  }

更高速:WebAssembly

对于网络平台而言,WebAssembly具有巨大的意义——它提供了一条途径,以使得以各种语言编写的代码都可以以接近原生的速度在Web中运行。在这种情况下,以前无法以此方式运行的客户端软件都将可以运行在Web中。

我们使用Go语言编译为wasm文件,核心代码如下:

// wasm.go
func main() {
    c := make(chan struct{}, 0)
  // js全局方法makeExcel
    js.Global().Set("makeExcel", js.FuncOf(jsMakeExcel))
  // 确保Go程序不退出
    <-c 
}

func makeExcel() []uint8 {
    f := excelize.NewFile()
    f.SetCellValue("Sheet1", "开播时间", now.Format(time.ANSIC))
    f.SetCellValue("Sheet1", "直播间", 1111)
  // 在js环境中无法实现文件的操作
    // if err := f.SaveAs("simple.xlsx"); err != nil {
    //     log.Fatal((err))
    // }
    buf, _ := f.WriteToBuffer()
    res := make([]uint8, buf.Len())
    buf.Read(res)
    return res
}

func jsMakeExcel(arg1 js.Value, arg2 []js.Value) interface{} {
    buf := makeExcel()
    js_uint := js.Global().Get("Uint8Array").New(len(buf))
    js.CopyBytesToJS(js_uint, buf)
  //go的uint8无法直接回传,需要创建js环境的Uint8Array类型数据并回传
    return js_uint
}

将编译好的wasm文件加载进js环境

  1. 引入桥接代码:https://github.com/golang/go/...。此时window下会有一个全局构造函数:Go
  2. 样板代码——实例化webassembly:
// WebAssembly.instantiateStreaming is not currently available in Safari 
if (WebAssembly && !WebAssembly.instantiateStreaming) {
      // polyfill
      WebAssembly.instantiateStreaming = async (resp, importObject) => {
        const source = await (await resp).arrayBuffer();
        return await WebAssembly.instantiate(source, importObject);
      };
    }

    const go = new Go();

    fetch('path/to/wasm.wasm')
      .then((response) => response.arrayBuffer())
      .then((bytes) => WebAssembly.instantiate(bytes, go.importObject))
      .then((res) => go.run(res.instance))
  1. 实现文件下载
function download() {
   // 与普通方法一样调用go写入全局的方法,拿到刚刚回传的uint8array数据
         const buf = makeExcel();
   // 创建下载链接,注意文件类型,并下载文件
    const blob = new Blob([buf], {
      type: 'application/vnd.ms-excel',
    });
    const url = URL.createObjectURL(blob);
    console.log({ blob, str });
    const a = document.createElement('a');
    a.download = 'test.xlsx';
    a.href = url;
    a.click();
}

webworker和webassembly是可以一起使用的,待补充……


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK