js-xlsx

该库支持各种电子表格格式的解析与生成。它由 JavaScript 实现,适用于前端和
Node。详情>>

目前支持读入的格式有(不断更新):

  • Excel 2007+ XML Formats (XLSX/XLSM)
  • Excel 2007+ Binary Format (XLSB)
  • Excel 2003-2004 XML Format (XML “SpreadsheetML”)
  • Excel 97-2004 (XLS BIFF8)
  • Excel 5.0/95 (XLS BIFF5)
  • OpenDocument Spreadsheet (ODS)

支持写出的格式有:

  • XLSX
  • CSV (and general DSV)
  • JSON and JS objects (various styles)

目前该库提供的 sheet_to_json 方法能将读入的 Excel 数据转为 JSON
格式。而对于导出操作,我们需要为 js-xlsx 提供指定的 JSON 格式。

更多关于 Excel 在 JavaScript
中处理的知识可查看凹凸实验室的《Node读写Excel文件探究实践》。但该文章存在两处问题(均在
js-xlsx 实战的导出表格部分):

  1. 生成头部时,Excel 的列信息简单地通过 String.fromCharCode(65+j)
    生成。当列大于 26 时会出现问题。这个问题会在后面章节中给出解决方案;
  2. 转换成 worksheet
    需要的结构处,出现逻辑性错误,并且会导致严重的性能问题。逻辑问题在此不讲述,我们看看性能问题:
    随着 ECMAScript 的不断更新,JavaScript
    变得更加强大和易用。尽管如此,我们还是要做到『物尽所用』,而不要『大材小用』,否则可能会得到“反效果”。这里导致性能问题的正是
    Object.assign()
    方法,该方法可以把任意多个源对象的可枚举属性拷贝至目标对象,并返回目标对象。由于该方法自身的实现机制,会在此案例中产生大量的冗余操作。在该案例中,单元格信息是唯一的,所以直接通过
    forEach 为一个空对象赋值即可。提升 N
    倍性能的同时,也把逻辑性错误解决了。

原来的:

JavaScript

var result = 某数组.reduce((prev, next) => Object.assign({}, prev,
{[next.position]: {v: next.v}}), {});

1
2
var result = 某数组.reduce((prev, next) => Object.assign({}, prev, {[next.position]: {v: next.v}}), {});
 

改为:

JavaScript

var result = 某数组.forEach((v, i) => data[v.position]= {v: v.v})

1
2
var result = 某数组.forEach((v, i) => data[v.position]= {v: v.v})
 

实践是检验真理的唯一标准

在理解上述知识后,下面就谈谈在该项目实践中总结出来的技巧、难点和重点

手工下载 Electron 二进制文件

如果你手工下载了 Electron
的二进制文件,你也可以直接使用其中的二进制文件直接运行你的应用。

Brightray

Brightray是一个使libchromiumcontent更容易使用应用的静态库。它是专门为了Electron而创造的,但是也可以允许没有基于Electron的原生应用使用Chromium的渲染引擎。

Brightray是Electron的一个底层的依赖,大多数Electron的使用者并不用担心它。

七牛云大数据平台建设实践

Vue 全家桶

该工具使用了 Vue、Vuex、Vuex-router。在工具基本定型阶段,由 1.x 升级到了
2.x。

参照下面例子

复制并且运行这个库
electron/electron-quick-start

注意:运行时需要你的系统已经安装了
Git 和
Node.js(包含
npm)。

# 克隆这仓库
$ git clone https://github.com/electron/electron-quick-start
# 进入仓库
$ cd electron-quick-start
# 安装依赖库并运行应用
$ npm install && npm start

更多 apps 例子,查看 electron 社区创建的 list of
boilerplates。

process

一个进程是一个正在运行的计算机程序的实例。Electron应用实际上是利用主进程和一个或几个渲染进程同时运行几个程序。

Node.js和Electron中,每一个运行着的进程都是一个process对象。这个对象是一个全局的并提供关于当前进程的信息和控制。作为一个全局的,它在应用中不使用require()也是有效的。

参见:main
process, renderer
process

Vue.js 2.0 后端渲染

 

相关技术

如果对某项技术比较熟悉,则可略读/跳过。

Windows

$ .\node_modules\.bin\electron .

这个页面定义了一些在Electron中经常使用的专有名词。

前端框架的繁荣及成熟

为 DOM 的 File 对象增加了 path 属性

Electron 为 File 对象额外增了 path
属性,该属性可得到文件在文件系统上的真实路径。因此,你可以利用 Node
为所欲为😈。应用场景有:拖拽文件后,通过 Node 提供的 File API
读取文件等。

macOS

$ ./Electron.app/Contents/MacOS/Electron your-app/

Electron.app 里面是 Electron 发布包,你可以在
这里
下载到。

NSIS

Nullsoft Scriptable Install System是一个Microsoft
Windows下的脚本驱动的安装制作工具。它发布在免费软件许可证下,是一个类似于InstallShield的广泛的被用来替代商业专有产品的工具。electron-builder支持NSIS作为一个编译目标。

Why  Vue.js

我们为什么选择 Vue.js,这是一个很幸运、很偶然的选择。掘金用 Vue.js 是在
0.12 版本,现在已经是 2.15 版本。当时选择最早版本的时候,掘金只有 4
个人。Vue.js
发展到现在,可以看到是一个增长非常疯狂的项目,从一开始的个人开源,到现在许多大公司使用,这和那些有大公司支持的开源库有了非常大的区别。到现在,Vue 在
NPM 上每月有超过 22 万次下载,这是很高的量。

 

斜分割线

如图:欧博国际网站 1

分割线可以通过 ::after/::before 伪类元素实现一条直线,然后通过
transform:rotate();
旋转特定角度实现。但这种实现的一个问题是:由于宽度是不定的,因此需要通过
JavaScript 运算才能得到准确的对角分割线。

因此,这里可以通过 CSS 线性渐变
linear-gradient(to top right, transparent, transparent calc(50% - .5px), #d3d6db calc(50% - .5px), #d3d6db calc(50% + .5px), transparent calc(50% + .5px))
实现。无论宽高如何变,依然妥妥地自适应。

electron-prebuilt

electron
是一个 npm 模块,包含所使用的 Electron 预编译版本。
如果你已经用 npm 全局安装了它,你只需要按照如下方式直接运行你的应用:

electron .

如果你是局部安装,那运行:

userland

这个术语来自于Unix社区,”userland”或”userspace”在运行在操作系统内核之外的程序中被提及。最近,这个术语已经在Node和npm社区中普及,用于区分”Node
core”和npm上记录的通过更大的”user”社区发布的包。

像Node,Electron是一个专注于有一个小的接口集合,并且这个集合提供所有的必须的为了开发多平台桌面程序的原生接口。这个设计理念使得Electron保持为一个灵活的工具,而不是过多的规定如何来使用它。Userland使得用户可以创建并分享工具,而这些工具提供基于“core”中有效内容之上的附加功能。

后端渲染 Nuxt.js 的开发实践

Vue.js
最基础的后端渲染,如果对于这样一个业务,每个公司都要根据自己的业务代码做一套后端渲染的逻辑,这不太可能。对于通用解决方案,一定是有更好的库,感谢有人造轮子。刚开始做后端渲染的时候是没有轮子的,掘金后端渲染都是自己写的,现在如果有轮子会好些。

 

自动更新

如果 Electron
应用没有提供自动更新功能,那么就意味着用户想体验新开发的功能或用上修复
Bug
后的新版本,只能靠用户自己主动地去官网下载,这无疑是糟糕的体验。Electron
提供的 autoUpdater
模块可实现自动更新功能,该模块提供了第三方框架
Squirrel 的接口,但 Electron 目前只内置了
Squirrel.Mac,且它与
Squirrel.Windows(需要额外引入)的处理方式也不一致(在客户端与服务器端两方面)。因此如果对该模块不熟悉,处理起来会相对比较繁琐。具体可以参考笔者的另一篇译文《Electron
自动更新的完整教程(Windows 和
OSX)》。

目前 Electron 的 autoUpdater 模块不支持 Linux 系统。

另外,XCel 目前并没有采用 autoUpdater 模块实现自动更新功能,而是利用
Electron 的
DownloadItem
模块实现,而服务器端则采用了 Nuts。

渲染进程

由于 Electron 使用 Chromium 来展示页面,所以 Chromium
的多进程结构也被充分利用。每个 Electron
的页面都在运行着自己的进程,这样的进程我们称之为渲染进程

在一般浏览器中,网页通常会在沙盒环境下运行,并且不允许访问原生资源。然而,Electron
用户拥有在网页中调用 Node.js 的 APIs
的能力,可以与底层操作系统直接交互。

native modules

Native
modules(在Node.js中也叫插件)是C或C++写的模块,使用require()函数可以被加载到Node.js或Electron中,然后就可以像一个普通Node.js模块一样使用了。它们主要用来提供一个把js运行在Node.js和C/C++库上的接口。

Electron支持Native Node
modules,但是由于Electron非常有可能使用安装在你电脑上的Node二进制文件中的不同版本的V8,你在编译native
modules的时候需要手动指定Electron的头部位置。

参考Using Native Node
Modules。

阴明(掘金联合创始人、CEO)

如何在渲染进程调用原生弹框?

在渲染进程中调用原本专属于主进程中的 API (如弹框)的方式有两种:

  1. IPC 通讯模块:先在主进程通过 ipcMain 进行监听,然后在渲染进程通过
    ipcRenderer 进行触发;
  2. remote 模块:该模块为渲染进程和主进程之间提供了快捷的通讯方式。

对于第二种方式,在渲染进程中,运行以下代码即可:

JavaScript

const remote = require(‘electron’).remote remote.dialog.showMessageBox({
type: ‘question’, buttons: [‘不告诉你’, ‘没有梦想’], defaultId: 0,
title: ‘XCel’, message: ‘你的梦想是什么?’ }

1
2
3
4
5
6
7
8
9
10
const remote = require(‘electron’).remote
 
remote.dialog.showMessageBox({
  type: ‘question’,
  buttons: [‘不告诉你’, ‘没有梦想’],
  defaultId: 0,
  title: ‘XCel’,
  message: ‘你的梦想是什么?’
}
 

快速入门

Electron 可以让你使用纯 JavaScript 调用丰富的原生 APIs
来创造桌面应用。你可以把它看作一个专注于桌面应用的 Node.js
的变体,而不是 Web 服务器。

这不意味着 Electron 是绑定了 GUI 库的 JavaScript。相反,Electron 使用
web 页面作为它的 GUI,所以你能把它看作成一个被 JavaScript
控制的,精简版的 Chromium 浏览器。

main process

主进程,通常是一个叫做main.js的文件,是指向每一个Electron应用的入口。它控制着应用从打开到关闭的生命周期。它也管理着原生控件,比如MenuMenu BarDockTray等。主进程在应用中承担着创建每一个新的渲染进程的责任。全部的Node接口都在它里面。

每一个应用的主线程文件是在package.json文件中的main属性中被指定的。这是electron .如何知道启动时要执行哪个文件的原因。

参见:process,renderer
process

pages

对于 Vue 来讲,把它的 template 侧写在一个 export 的文件里面,layout
、transition 和 scrollToTop
是纯前端应用都会遇到的问题,这套页面用的是哪个 layout
展示?在页面切换之间是否要有动画效果?以及在纯前端应用中每次页面之间切换是否要滚到最上面?因为它是一个单纯的页面,如果不设置滚到最上面,会发现跳到另外一个页面还是在中间的位置,但是在浏览器来看其实是在一个网页里面,没有跳到新的网页,它把通用的需求封装得很漂亮。validate
是解检测 url 的,middleware
是一些其他的功能,可以再加进去。这里面最好的事情是 head
,在纯前端应用中会有不同的页面,在每个页面中 title
一定会变,单独页面里面移动端的展示模式和特殊的配置文件等等,这一套东西以前都得单独来写,每一个页面都得单独解决,而现在通解来实现了,而且通解没有做得太深,有时候开源库定义得太死,可活动性太差,但是它定义好的东西都是所有人需要的。

 

它由什么组成?

Electron 结合了 ChromiumNode.js 和用于调用操作系统本地功能的
API(如打开文件窗口、通知、图标等)。

  • Chromium:Google 创造的一个开源库,并用于 Google 的浏览器
    Chrome。
  • Node.js(Node):一个在服务器运行 JavaScript
    的运行时(runtime),它拥有访问文件系统和网络权限(你的电脑也可以是一台服务器!)。

欧博国际网站 2

macOS / Linux

$ ./node_modules/.bin/electron .

Squirrel

Squirrel是一个开源的框架,可以允许Electron应用自动升级到已经发布的最新版本。查看autoUpdater接口的使用Squirrel启动的信息。

从百家争鸣到三足鼎立

欧博国际网站 3

图 1 

 

这是从网上找到的前端的状态(图 1
),每一个颜色均是某一个前端库的分类。前端的世界就是如此,需要在一群的选项中选择一个,并且要跟其他的选项
PK 。

 

如图 1 所示,方框的部分写具体的业务代码,例如早期的 jQuery。Prototype
曾经完成了 2000 年内有复杂业务代码的前端,写了大量的页面,传统的后台
admin 等都是这样。再往上 Ember
比较适合业务稳定的系统使用,因为它一直坚持着向前兼容,它不像新的库,如果出了一个新版本基本上需要推倒重写;而
Backbone 是写比较复杂页面的一个库, Angular 、React 等等。

 

在这么繁杂的前端中,单纯写前端业务有很多选择。曾看到一个评论:“ 2016
年,你完成一个巨简单的业务,就需要 TypeScript 写代码,用 Fetch
发起异步请求,所有的代码编译程 ES6
……”用了几十个库完成一个非常简单的问题。那么,在这样的前端生态下,它一定会是繁荣的,如果不繁荣,不会有很多人在这里做事情。

 

支持常见的编辑功能,如粘贴和复制

Electron 应用在 MacOS
中默认不支持『复制』『粘贴』等常见编辑功能,因此需要为 MacOS
显式地设置复制粘贴等编辑功能的菜单栏,并为此设置相应的快捷键。

JavaScript

// darwin 就是 MacOS if (process.platform === ‘darwin’) { var template =
[{ label: ‘FromScratch’, submenu: [{ label: ‘Quit’, accelerator:
‘CmdOrCtrl+Q’, click: function() { app.quit(); } }] }, { label: ‘Edit’,
submenu: [{ label: ‘Undo’, accelerator: ‘CmdOrCtrl+Z’, selector:
‘undo:’ }, { label: ‘Redo’, accelerator: ‘Shift+CmdOrCtrl+Z’, selector:
‘redo:’ }, { type: ‘separator’ }, { label: ‘Cut’, accelerator:
‘CmdOrCtrl+X’, selector: ‘cut:’ }, { label: ‘Copy’, accelerator:
‘CmdOrCtrl+C’, selector: ‘copy:’ }, { label: ‘Paste’, accelerator:
‘CmdOrCtrl+V’, selector: ‘paste:’ }, { label: ‘Select All’, accelerator:
‘CmdOrCtrl+A’, selector: ‘selectAll:’ }] }]; var osxMenu =
menu.buildFromTemplate(template); menu.setApplicationMenu(osxMenu); }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// darwin 就是 MacOS
if (process.platform === ‘darwin’) {
    var template = [{
      label: ‘FromScratch’,
      submenu: [{
        label: ‘Quit’,
        accelerator: ‘CmdOrCtrl+Q’,
        click: function() { app.quit(); }
      }]
    }, {
      label: ‘Edit’,
      submenu: [{
        label: ‘Undo’,
        accelerator: ‘CmdOrCtrl+Z’,
        selector: ‘undo:’
      }, {
        label: ‘Redo’,
        accelerator: ‘Shift+CmdOrCtrl+Z’,
        selector: ‘redo:’
      }, {
        type: ‘separator’
      }, {
        label: ‘Cut’,
        accelerator: ‘CmdOrCtrl+X’,
        selector: ‘cut:’
      }, {
        label: ‘Copy’,
        accelerator: ‘CmdOrCtrl+C’,
        selector: ‘copy:’
      }, {
        label: ‘Paste’,
        accelerator: ‘CmdOrCtrl+V’,
        selector: ‘paste:’
      }, {
        label: ‘Select All’,
        accelerator: ‘CmdOrCtrl+A’,
        selector: ‘selectAll:’
      }]
    }];
    var osxMenu = menu.buildFromTemplate(template);
    menu.setApplicationMenu(osxMenu);
}
 

打造你第一个 Electron 应用

大体上,一个 Electron 应用的目录结构如下:

your-app/
├── package.json
├── main.js
└── index.html

package.json 的格式和 Node 的完全一致,并且那个被 main
字段声明的脚本文件是你的应用的启动脚本,它运行在主进程上。你应用里的
package.json 看起来应该像:

{
  "name"    : "your-app",
  "version" : "0.1.0",
  "main"    : "main.js"
}

注意:如果 main 字段没有在 package.json 声明,Electron会优先加载
index.js

main.js 应该用于创建窗口和处理系统事件,一个典型的例子如下:

const {app, BrowserWindow} = require('electron')
const path = require('path')
const url = require('url')

// 保持一个对于 window 对象的全局引用,如果你不这样做,
// 当 JavaScript 对象被垃圾回收, window 会被自动地关闭
let win

function createWindow () {
  // 创建浏览器窗口。
  win = new BrowserWindow({width: 800, height: 600})

  // 加载应用的 index.html。
  win.loadURL(url.format({
    pathname: path.join(__dirname, 'index.html'),
    protocol: 'file:',
    slashes: true
  }))

  // 打开开发者工具。
  win.webContents.openDevTools()

  // 当 window 被关闭,这个事件会被触发。
  win.on('closed', () => {
    // 取消引用 window 对象,如果你的应用支持多窗口的话,
    // 通常会把多个 window 对象存放在一个数组里面,
    // 与此同时,你应该删除相应的元素。
    win = null
  })
}

// Electron 会在初始化后并准备
// 创建浏览器窗口时,调用这个函数。
// 部分 API 在 ready 事件触发后才能使用。
app.on('ready', createWindow)

// 当全部窗口关闭时退出。
app.on('window-all-closed', () => {
  // 在 macOS 上,除非用户用 Cmd + Q 确定地退出,
  // 否则绝大部分应用及其菜单栏会保持激活。
  if (process.platform !== 'darwin') {
    app.quit()
  }
})

app.on('activate', () => {
  // 在这文件,你可以续写应用剩下主进程代码。
  // 也可以拆分成几个文件,然后用 require 导入。
  if (win === null) {
    createWindow()
  }
})

// 在这文件,你可以续写应用剩下主进程代码。
// 也可以拆分成几个文件,然后用 require 导入。

最后,你想展示的 index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Hello World!</title>
  </head>
  <body>
    <h1>Hello World!</h1>
    We are using node <script>document.write(process.versions.node)</script>,
    Chrome <script>document.write(process.versions.chrome)</script>,
    and Electron <script>document.write(process.versions.electron)</script>.
  </body>
</html>

DMG

Apple Disk
Image是一个在MacOS上使用的打包类型。DMG文件通常用来分发应用的“安装文件”。electron-builder支持dmg作为一个打包目标。


为什么选择 Vue

对于笔者来说:

  • 简单易用,一般使用只需看官方文档。
  • 数据驱动视图,所以基本不用操作 DOM 了。
  • 框架的存在是为了帮助我们应对复杂度。
  • 全家桶的好处是:对于一般场景,我们就不需要考虑用哪些个库(插件)。

Vue 1.x -> Vue 2.0 的版本迁移用
vue-migration-helper
即可分析出大部分需要更改的地方。

网上已有很多关于 Vue 的教程,故在此不再赘述。至此,Vue 部分介绍完毕。


主进程与渲染进程的区别

主进程使用 BrowserWindow 实例创建页面。每个 BrowserWindow
实例都在自己的渲染进程里运行页面。当一个 BrowserWindow
实例被销毁后,相应的渲染进程也会被终止。

主进程管理所有页面和与之对应的渲染进程。每个渲染进程都是相互独立的,并且只关心他们自己的页面。

由于在页面里管理原生 GUI
资源是非常危险而且容易造成资源泄露,所以在页面调用 GUI 相关的 APIs
是不被允许的。如果你想在网页里使用 GUI
操作,其对应的渲染进程必须与主进程进行通讯,请求主进程进行相关的 GUI
操作。

在 Electron,我们提供几种方法用于主进程和渲染进程之间的通讯。像
ipcRenderer
ipcMain
模块用于发送消息, remote
模块用于 RPC 方式通讯。这些内容都可以在一个 FAQ 中查看 how to share
data between web
pages。

ASAR

ASAR是Atom Shell Archive
Format的简称。一个asar文档是一个把文件都放在一个单独的文件中的简单的tar-like类型文件。Electron可以从中读取全部的文件而不用解压整个文件。

创造ASAR类型主要是为了在Windows下提高性能… TODO

Web 技术和 JavaScript 到达各个领域

  • 后端:Node.js 在业务开发中已经比较广泛使用,而且 v8 性能较好。

  • 移动:最常用的 Hybrid ,React  Native ,NativeScript ,Weex 。

  • 欧博国际网站,桌面:Electron,nw.js 来实现 Web 端的应用,其实都是网页。

  • VR:WebVR ,A-Frame ,WebGL 

  • 硬件:Cylon.js ,Tessel ,Johnny-Five

  • 数据可视化:d3.js ,vis.js ,HighCharts ,Charts

 

因为 JavaScript
本身的代码,学习陡峭程度非常低,入门门槛低,并且网页端需求大,因此 JavaScript 异常繁荣。慢慢地,JavaScript
的性能越来越好,有更多人使用,进而写 JavaScript 的人想用 JavaScript
写更多的东西,一步步迈到了各个技术生态。

 

Vue 性能真的好?

Vue 一直标榜着自己性能优异,但当数据量上升到一定量级时(如 1913 x 180 ≈
34 万个数据单元),会出现严重的性能问题(未做相应优化的前提下)。

如直接通过列表渲染 v-for 渲染数据时,会导致程序卡死。
答:通过查阅相关资料可得, v-for
在初次渲染时,需要对每个子项进行初始化(如数据绑定等操作,以便拥有更快的更新速度),这对于数据量较大时,无疑会造成严重的性能问题。

当时,我想到了两种解决思路:

  1. Vue 是数据驱动视图的,对数据分段 push,即将一个庞大的任务分割为 N
    份。
  2. 自己拼接 HTML 字符串,再通过 innerHTML 一次性插入。

最终,我选择了第二条,理由是:

  1. 性能最佳,因为每次执行数据过滤时,Vue 都要进行 diff,性能不佳。
  2. 更符合当前应用的需求:纯展示且无需动画过渡等。
  3. 实现更简单

将原本繁重的 DOM 操作(Vue)转换为 JavaScript
的拼接字符串后,性能得到了很大提升(不会导致程序卡死而渲染不出视图)。这种优化方式难道不就是
Vue、React
等框架解决的问题之一吗?只不过框架考虑的场景更广,有些地方需要我们自己根据实际情况进行优化而已。

欧博国际平台,在浏览器当中,JavaScript 的运算在现代的引擎中非常快,但 DOM
本身是非常缓慢的东西。当你调用原生 DOM API 的时候,浏览器需要在
JavaScript 引擎的语境下去接触原生的 DOM
的实现,这个过程有相当的性能损耗。所以,本质的考量是,要把耗费时间的操作尽量放在纯粹的计算中去做,保证最后计算出来的需要实际接触真实
DOM 的操作是最少的。 —— 《Vue
2.0——渐进式前端解决方案》

当然,由于 JavaScript
天生单线程,即使执行数速度再快,也难免会导致页面有短暂的时间拒绝用户的输入。此时可通过
Web Worker 或其它方式解决,这也将是我们后续讲到的问题。

也有网友提供了优化大量列表的方法:。但在此案例中笔者并没有采用此方式。

Linux

$ ./electron/electron your-app/

原文:https://github.com/electron/electron/blob/master/docs/glossary.md
译者:Lin

 

Author

发表评论

电子邮件地址不会被公开。 必填项已用*标注