Git Product home page Git Product logo

cntchen.github.io's Introduction

cntchen.github.io's People

Contributors

cntchen avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

cntchen.github.io's Issues

Configuration Management System Compare

Configuration Management System Compare

背景

做个表格对比现在业界现有的配置管理系统.

Table

名称 语言 / 框架 介绍 特点
ACM(阿里云) 云服务 在微服务、DevOps、大数据等场景下极大地减轻配置管理的工作量 云服务, 实时推送, 版本管理
Apollo(携程) Spring Boot 和 Spring Cloud 分布式配置中心, 适用于微服务配置场景 配置实时生效,客户端支持 Java、.Net
Disconf(百度) SpringMvc Distributed Configuration Management Platform(分布式配置管理平台) 配置实时生效
Spring Cloud Config Spring Spring Cloud Config provides server and client-side support for externalized configuration in a distributed system Git 来存储配置信息
Etcd(CoreOS) go Distributed reliable key-value store for the most critical data of a distributed system gRPC, Secure

References

  • 配置中心设计思考 (提供了对比的表格)

https://www.jianshu.com/p/7b6871942745

  • 淘宝Diamond架构分析

https://yq.aliyun.com/articles/57409

  • 阿里云 ACM

https://www.aliyun.com/product/acm?spm=5176.8135679.793319.11.22ef5a7eAknOgP

  • ACM yu Zookeeper / ctcd / spring cloud config server 对比

https://help.aliyun.com/document_detail/59957.html?spm=5176.doc59972.6.541.bW7PxM

  • 配置常用名词解释

https://help.aliyun.com/document_detail/59968.html?spm=5176.doc59953.6.543.MnBu6E

  • apollo

https://github.com/ctripcorp/apollo

  • 开源配置中心Apollo的设计与实现-携程宋顺.pdf

http://techshow.ctrip.com/wp-content/uploads/2017/08/%E5%BC%80%E6%BA%90%E9%85%8D%E7%BD%AE%E4%B8%AD%E5%BF%83Apollo%E7%9A%84%E8%AE%BE%E8%AE%A1%E4%B8%8E%E5%AE%9E%E7%8E%B0-%E6%90%BA%E7%A8%8B%E5%AE%8B%E9%A1%BA.pdf

  • apollo 讲堂视频

http://www.itdks.com/dakalive/detail/3420

  • disconf

https://github.com/knightliao/disconf

  • ctcd

https://github.com/coreos/etcd/

EOF

接口聚合

背景

前端页面与后台的交互方式一般是在页面解析完成后,使用异步接口请求后台数据,然后根据数据渲染出前端页面.前后端交互的关键在于接口:

  • 接口协议为 HTTP/HTTPS,调用方式为 XHR/Fetch;
  • 接口的协议参数(headers),请求类型,状态码,缓存,内容类型,跨域;
  • 页面数据完成展示需要调用的接口数,影响用户的等待时间;
  • 接口交互的数据和数据的使用率,应该减少不必要数据的传输,以减少带宽;

存在的问题

前端的特定场景,比如首页,需要不同维度的多种数据,数据需要调用多个接口才能获得,导致前端的请求过多,在移动端这种非健壮性网络下导致页面白屏时间长。所以存在以下矛盾:

  • 前端不想调用原子接口,而是后台提供聚合的接口;
  • 后台接口设计思路是接口单一职责和原子性,不会直接提供大接口。

另外一方面,在不同场景,希望接口可以返回指定的数据,减少没必要数据的传输。在 RESTful 上的实现,通常是带上查询参数,接口使用场景多了后,接口中的处理逻辑变得复杂和难以维护。

RESTful

Representational state transfer 是目前主流的客户端和服务端交互方式。REST 服务允许客户端通过预先定义的无状态操作,访问或修改用文本表示的服务端资源.

  • 使用 URI 描述服务端资源;
  • 定义对资源的操作 GET/POST/PUT/DELETE。

后台微服务 microservice

将后台服务拆分为功能独立的系统,具有业务的独立性和完整性,减少服务间的依赖。微服务可以独立部署,便于负载管理和优化。微服务通过API Gateway 提供 RESTful 的接口。

外观接口/接口聚合

RESTful 下的接口聚合,服务端的业务复杂度,接口性能不高,并且复用性不高。

GraphQL

query language for APIs - GraphQL

GraphQL 是一种标准语言,类型系统,创建前后端强契约的规范,使得前后端的数据交互处理过程得到简化。客户端可以从服务端的数据集中获取自定义结构的数据.

特点

  • 调用方描述所需数据,并得到可预测的返回
  • 通过单接口获取多种资源,减少调用方调用的接口数量
  • 使用接口描述进行接口演变,不需要更新版本
  • 可以在已有数据和代码基础添加 GraphQL,支持多种编程语言

REST vs GraphQL

  • 大规模的应用中 REST 的请求多
  • GrapaQL 返回的数据可制定,都是有效的负载;REST存在冗余,使用带查询参数的方法拓展性差
  • REST 的接口变动通常需要新的 EndPoint,因为无法确定那些字段不再被使用,新的 EndPoint 接口代码和文档的增加;
  • GraphQL 可以统计接口字段的使用情况,接口演化通过修改 Schema 实现;
  • 开发体验相关

GraphQL 部署实践

关键思考

  • 如何将 GraphQL 与现有后台接口结合起来使用
  • Node 中间层

References

  • REST 2.0 Is Here and Its Name Is GraphQL

https://www.sitepoint.com/rest-2-0-graphql/

  • GraphQL文档

http://graphql.org/learn/

EOF

https://www.one-tab.com/page/lSkFh92GTB6S-iierEr7Kw

前后端分离开发模式的 mock 平台预研

tags: 前后端分离 mock AJAX 工作流 前端

引入

mock(模拟): 是在项目测试中,对项目外部或不容易获取的对象/接口,用一个虚拟的对象/接口来模拟,以便测试。

背景

前后端分离

  • 前后端仅仅通过异步接口(AJAX/JSONP)来编程
  • 前后端都各自有自己的开发流程,构建工具,测试集合
  • 关注点分离,前后端变得相对独立并松耦合

前后端分离.png

开发流程

  • 后台编写和维护接口文档,在 API 变化时更新接口文档
  • 后台根据接口文档进行接口开发
  • 前端根据接口文档进行开发
  • 开发完成后联调和提交测试

开发流程.png

面临问题

  • 没有统一的文档编写规范,导致文档越来越乱,无法维护和阅读
  • 开发中的接口增删改等变动,需要较大的沟通成本
  • 对于一个新需求,前端开发的接口调用和自测依赖后台开发完毕
  • 将接口的风险后置,耽误项目时间

解决方法

  • 接口文档服务器 -- 解决接口文档编辑和维护的问题
  • mock 数据 -- 解决前端开发依赖真实后台接口的问题

接口文档服务器

功能

接口编辑功能

  • 类型1:根据接口描述语法书写接口,并保存为文本文件,然后使用生成工具生成在线接文档(HTML)
    -- 也有一些类似 Markdown 的接口文档编辑器,参见:[There Are Four API Design Editors To Choose From Now][There Are Four API Design Editors To Choose From Now]。
    接口书写转换为接口文档.png

  • 类型2:提供在线的接口编辑平台,进行可交互的接口编辑
    接口文档服务器.png

接口查看功能

  • 提供友好的接口文档查看功能

用法

  • 后台开发人员进行接口文档编写
    -- 定义接口路径、接口上传字段、接口返回字段、字段含义、字段类型、字段取值
  • 前端开发人员查看接口文档

优点

  • 统一管理和维护接口文档
    -- 提供了接口导入、接口模块化、接口版本化、可视化编辑等功能
  • 接口文档规范,可读性强,减少前后端接口沟通成本

前端 mock 方法回顾

前端开发过程中,使用 mock 数据来模拟接口的返回,对开发的代码进行业务逻辑测试。解决开发过程中对后台接口的依赖。

硬编码数据

将 mock 数据写在代码中。

示例

// $.ajax({
//   url: ‘https://cntchen.github.io/userInfo’,
//   type: 'GET',
//   success: function(dt) {
    var dt = {
      "isSuccess": true,
      "errMsg": "This is error.",
      "data": {
        "userName": "Cntchen",
        "about": "FE"
      },
    };
    if (dt.isSuccess) {
      render(dt.data);
    } else {
      console.log(dt.errMsg);
    }
//   },
//   fail: function() {}
// });

优点

  • 可以快速修改测试数据

痛点

  • 无法模拟异步的网络请求,无法测试网络异常
  • 肮代码,联调前需要做较多改动,增加最终上真实环境的切换成本
    -- 添加网络请求,修改接口、添加错误控制逻辑
  • 接口文档变化需要手动更新

请求拦截 & mock 数据

hijack(劫持)接口的网络请求,将请求的返回替换为代码中的 mock 数据。

实例

jquery-mockjax

The jQuery Mockjax Plugin provides a simple and extremely flexible interface for mocking or simulating ajax requests and responses

优点

  • 可以模拟异步的网络请求
  • 可以快速修改测试数据

痛点

  • 依赖特定的框架,如Jquery
  • 增加最终上真实环境的切换成本
  • 接口文档变换需要手动更新

本地 mock 服务器

将 mock 数据保存为本地文件。在前端调试的构建流中,用 node 开本地 mock 服务器,请求接口指向本地 mock 服务器,本地 mock 服务器 response mock 文件。

mock 文件

.mock
├── userInfo.json
├── userStars.json
├── blogs.json
└── following.json

接口调用

https://github.com/CntChen/userInfo --> localhost:port/userInfo

优点

  • 对代码改动较小,联调测试只需要改动接口 url
  • 可以快速修改测试数据

痛点

  • json 文件非常多
  • 接口文档变化需要手动更新

代理服务器

  • 使用 charlesfiddler 作为代理服务器
  • 使用代理服务器的 map(映射)& rewrite(重写) 功能

map local

  • 接口请求的返回映射为本地 mock 数据
    https://github.com/CntChen/userInfo --> localPath/userInfo

map local.png

  • 编辑映射规则
    map rule.png

map remote

  • 接口请求的返回映射为另一个远程接口的调用
    map remote.png

rewrite

  • 修改接口调用的 request 或 response,添加/删除/修改 HTTP request line/response line/headers/body
    rewrite data.png

  • 解决跨域问题
    使用 map 后,接口调用的 response 不带 CORS headers,跨域请求在浏览器端会报错。需要重写接口返回的 header,添加 CORS 的字段。
    rewrite cors.png

优点

  • 前端直接请求真实接口,无需修改代码
  • 可以修改接口返回数据

痛点

  • 需要处理跨域问题
  • 一个变更需要代理服务器进行多处改动,配置效率低下
  • 不支持 HTTP method 的区分
    -- CORS 的 preflight 请求(OPTION)也会返回数据
  • 需要有远程接口或本地 mock 文件,与使用本地 mock 文件相同的痛点

mock 平台

接口文档服务器

使用接口文档服务器来定义接口数据结构

接口服务器.jpg

mock服务器

mock 服务器根据接口文档自动生成 mock 数据,实现了接口文档即API

mock服务器.jpg

优点

  • 接口文档自动生成和更新 mock 数据
  • 前端代码联调时改动小

缺点

  • 可能存在跨域问题

业界实践

公司实践

没有找到公司级别的框架,除了阿里的 RAP。可能原因:

  • 非关键性、开创性技术,没有太多研究价值
  • 许多大公司是小团队作战,没有统一的 mock 平台
  • 已经有一些稳定的接口,并不存在后台接口没有开发完成的问题
    -- 而我们想探究的问题是:前后端同时开发时的 mock

github 开源库

  • faker.js
    随机生成固定字段的 mock 数据,如emaildateimages等,支持国际化。

  • blueprint

A powerful high-level API design language for web APIs.

一种使用类markdown语法的接口编写语言,使用[json-schema][json-schema]和[mson][mson]作为接口字段描述。有完善的工具链进行接口文件 Edit,Test,Mock,Parse,Converter等。

Swagger是一种 Rest API 的简单但强大的表示方式,标准的,语言无关,这种表示方式不但人可读,而且机器可读。可以作为 Rest API 的交互式文档,也可以作为 Rest API 的形式化的接口描述,生成客户端和服务端的代码。 --[Swagger:Rest API的描述语言][Swagger:Rest API的描述语言]

定义了一套接口文档编写语法,然后可以自动生成接口文档。相关项目: Swagger Editor ,用于编写 API 文档。Swagger UI restful 接口文档在线自动生成与功能测试软件。点击查看Swagger-UI在线示例

WireMock is a simulator for HTTP-based APIs. Some might consider it a service virtualization tool or a mock server. It supports testing of edge cases and failure modes that the real API won't reliably produce.

商业化方案

  • apiary
    商业化方案,blueprint开源项目的创造者。界面化,提供mock功能,生成各编程语言的调用代码(跟 postman 的 generate code snippets类似)。

其他实践

API Evangelist(API 布道者)

总结

对于前后端分离开发方式,已经有比较成熟的 mock 平台,主要解决了2个问题:

  • 接口文档的编辑和维护
  • 接口 mock 数据的自动生成和更新

后记

预研时间比较有限,有一些新的 mock 模式或优秀的 mock 平台没有覆盖到,欢迎补充。
笔者所在公司选用的平台是 RAP,后续会整理一篇 RAP 实践方面的文章。
问题来了:你开发中的 mock 方式是什么?

References

  • 图解基于node.js实现前后端分离

http://yalishizhude.github.io/2016/04/19/front-back-separation/
[图解基于node.js实现前后端分离]:http://yalishizhude.github.io/2016/04/19/front-back-separation/

  • TestDouble(介绍 mock 相关的概念)

http://martinfowler.com/bliki/TestDouble.html
[mock 相关的概念]:http://martinfowler.com/bliki/TestDouble.html

  • There Are Four API Design Editors To Choose From Now

https://apievangelist.com/2014/11/21/there-are-four-api-design-editors-to-choose-from-now/
[There Are Four API Design Editors To Choose From Now]:https://apievangelist.com/2014/11/21/there-are-four-api-design-editors-to-choose-from-now/

  • 联调之痛--契约测试

http://www.ituring.com.cn/article/42460
[联调之痛]:http://www.ituring.com.cn/article/42460

  • Swagger:Rest API的描述语言

https://zhuanlan.zhihu.com/p/21353795
[Swagger:Rest API的描述语言]:https://zhuanlan.zhihu.com/p/21353795

  • Swagger - 前后端分离后的契约

http://www.cnblogs.com/whitewolf/p/4686154.html
[Swagger - 前后端分离后的契约]:http://www.cnblogs.com/whitewolf/p/4686154.html

  • Swagger UI教程 API 文档神器 搭配Node使用

http://www.jianshu.com/p/d6626e6bd72c#
[Swagger UI教程 API 文档神器 搭配Node使用]:http://www.jianshu.com/p/d6626e6bd72c#

END


读书笔记(持续更新)

背景

现在信息太碎片化了,还是要回归书本。
这是我读书的记录,督促自己持续学习。

读过的书

《Linux私房菜》

2016.05 ~ 2017.05.04
读了好久好久,效率非常低,这是需要反省的地方。
这本书让我收获很多,在工作中解决了许多实际问题,也非常推荐软件工程师阅读,前端工程师也合适。

《CSS权威指南》

2017.05.4 ~ 未知的未来(2017.08.25)
提高效率看书。
更新: 效率还是比较低,110 天。这本书非常不错,对 CSS 的一些理念有了基本了解。
现在大量使用 flex 布局,以后遇到问题 google 文章应该可以搞定。

《HTTP 权威指南》

2017.08.26 ~ 未知的未来(2018.01.17)
网络是前端工程师拓展外延和了解安全/缓存/web存储的重要一块。系统学习非常有必要。
上一本书看太慢了,这本是大块头,继续坚持。
更新: 坚持不够彻底. 看书遗忘没有关系, 曾经搞懂的东西, 以后再看一下书就可以搞懂.

<<设计模式 -- 可复用面向对象软件的基础>>

2018.01.17 ~ 未知的未来
设计模式的书应该是永远不会过时, 以前看过大话设计模式, 现在已经忘光光了. 是时候读书读书啦.

EOF

前端工程师的 docker 入门

前端工程师的 docker 入门

docker - the open-source application container engine

前言: 本篇文章2016年10月就记录了,但是搞丢了,最近重新找回,刚好又要搞 docker 了,重新整理完善.

背景

docker 是比较热门的技术,但是作为一名前端工程师,跟 docker 有工作交集的机会比较少。刚好公司有一个项目,需要自己搭建环境,所以试下 docker.这篇文章是我的学习笔记.

机器: Ubuntu16.10

安装

参考资料 docker 安装手册

获得最新的 docker 安装包

$ curl -sSL https://get.docker.com/ | sh

shell 会提示输入 root 的密码,然后开始执行安装过程.

曲折:安装失败.

Err:1 https://apt.dockerproject.org/repo ubuntu-xenial/main amd64 docker-engine amd64 1.12.1-0~xenia

这是因为国外的源不稳定,可以尝试墙内的安装方法

$ curl -sSL https://get.daocloud.io/docker | sh // 国内的源

查看 docker 版本

$ sudo docker version

是否安装成功

$ sudo docker run hello-world

成功标志,以下代码的最后一行:

Unable to find image 'hello-world:latest'
locallylatest: Pulling from library/hello-worldc04b14da8d14:
Pull completeDigest: sha256:0256e8a36e2070f7bf2d0b0763dbabdd67798512411de4cdcf9431a1feb60fd9Status:
Downloaded newer image for hello-world:latest
Hello from Docker!This message shows that your installation appears to be working correctly.

入门

参考资料:docker 入门教程

搜索 docker 镜像

$ docker search node

曲折:命令执行出错.

Warning: failed to get default registry endpoint from daemon
(Cannot connect to the Docker daemon. Is the docker daemon running on this host?).
Using system default: https://index.docker.io/v1/Cannot connect to the Docker daemon.
Is the docker daemon running on this host?

出错原因:需要 root 账户执行.
解决方法把当前用户执行权限添加到相应的 docker 用户组里面

$ sudo groupadd docker// groupadd: group 'docker' already exists
// 添加当前用户到docker用户组里,注意这里的yongboy为ubuntu server登录用户名
$ sudo gpasswd -a yongboy docker// Adding user chenhanjie to group docker
// 重启Docker后台监护进程
$ sudo service docker restart# 重启之后,尝试一下,是否生效
$ docker version
// 若还未生效,则系统重启,则生效m,命令:sudo reboot
// from:http://www.wujianjun.org/2016/04/06/docker-install-issue/
// 重启docker服务
$ sudo service docker restart

搜索 docker

$ docker search node
NAME                      DESCRIPTION                                     STARS     OFFICIAL   AUTOMATED
node                      Node.js is a JavaScript-based platform for...   3763      [OK]
nodered/node-red-docker   Node-RED Docker images.                         53                   [OK]
strongloop/node           StrongLoop, Node.js, and tools.                 37                   [OK]
kkarczmarczyk/node-yarn   Node docker image with yarn package manage...   21                   [OK]
bitnami/node              Bitnami Node.js Docker Image                    18                   [OK]
calico/node                                                               14                   [OK]
siomiz/node-opencv        _/node + node-opencv                            10                   [OK]
dahlb/alpine-node         small node for gitlab ci runner                 8                    [OK]
  • docker image 的命名规则
    在docker的镜像索引网站上面,镜像都是按照用户名/镜像名的方式来存储的。有一组比较特殊的镜像,比如 ubuntu 这类基础镜像,经过官方的验证,值得信任,可以直接用镜像名来检索到。

下载镜像

$ sudo docker pull node

下载镜像可能会非常缓慢,可以使用国内源:docker下使用daocloud/阿里云镜像加速 .

常用命令

使用 image 创建 container 并进入交互模式

$ docker run -i -t node
> var a = 1
undefined
> console.log(a)
1
undefined
  • -i:表示以“交互模式”运行容器,同步容器的 stdin/stdout 到宿主终端
  • -t:表示容器启动后进入其命令行
    这两个参数经常一起使用.

端口映射

$ docker run -i -t -p 2280:80 -p 28080:8080 rap_v7
  • -p 28080:8080:参数类似端口映射的功能
    容器内启动服务,然后接口映射到宿主端口.

后台启动

$ docker run -i -t -d node 
1ef49add06ccbb3fbfd47b292707781babc2a72fa15f42148db2ef4017470421
  • -d:表示 deamon ,以后台启动这个 container

安装新程序

  • 启动容器
$ docker run -i -t ubuntu
root@5b248d962f59:/# ls
bin   dev  home  lib64  mnt  proc  run   srv  tmp  var
boot  etc  lib   media  opt  root  sbin  sys  usr
root@5b248d962f59:/# cowsay
bash: cowsay: command not found
  • 安装新程序
root@5b248d962f59:/# apt update && apt install cowsay

执行新程序.

root@5b248d962f59:/# whereis cowsay
cowsay: /usr/games/cowsay /usr/share/cowsay /usr/share/man/man6/cowsay.6.gz
root@5b248d962f59:/# /usr/games/cowsay 'hello'
 _______
< hello >
 -------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||

保存对容器的修改

使用 docker commit 命令来创建新的 docker image.

  • 查看当前 container
$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
2fcf9d176a0f        ubuntu              "/bin/bash"         10 minutes ago      Up 10 minutes                           romantic_dubinsky
  • 从 container 生成 image
$ docker commit -m 'add cowsay' 2fcf9d176a0f ubuntu-with-cowsay
sha256:ce53d099e30cd453c416561e3919dcb7eaa0b38ebe4fd22b2f9f2ebe0f669ebc

运行新的镜像

  • 查看新 commit 的 image
$ docker images
REPOSITORY           TAG                 IMAGE ID            CREATED             SIZE
ubuntu-with-cowsay   latest              ce53d099e30c        3 minutes ago       211.3 MB
rap_v7               latest              de72241bf273        8 months ago        1.541 GB
  • 运行新 image
$ docker run -i -t ubuntu-with-cowsay
root@f08dc90c206a:/# /usr/games/cowsay 'hello CntChen'
 _______________
< hello CntChen >
 ---------------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||
root@f08dc90c206a:/# 

可视化管理

kitematic

使用 kitematic 对 docker image 进行可视化管理。
linux 版本需要自行构建,主要参考:Initial Linux installer
kitematic 使用 Electron 作为跨平台框架,安装 Electron 过程比较麻烦,反正最后我是安装上了,囧!有机会单独写文章记录安装流程。

// clone
$ git clone https://github.com/docker/kitematic.git
$ cd kitematic$ npm install
// 构建 $ sudo grunk release
// 安装
$ cd dist
$ sudo dpkg -i Kitematic_0.12.1_amd64.deb

安装后报错:
kitematic

原因好像是:

基于虚拟机,用于mac与windows平台,linux不需要虚拟机直接运行docker

但是,突然国庆长假回来,就好了!!!

然后愉快地使用kitematic。界面非常好看。

shipyard

没有完成安装,因为 kitematic 已经可以使用了,如果需要应对多个 host 和 container 的场景,这个工具可能会更好.
shipyard 安装参考 .

References

  • docker 安装手册

http://www.docker.org.cn/book/install/supported-platform-17.html

  • 墙内的安装方法

http://get.daocloud.io/#install-docke

  • docker 入门教程

http://www.docker.org.cn/book/docker/what-is-docker-16.html

  • Docker学习笔记之一,搭建一个JAVA Tomcat运行环境

http://www.blogjava.net/yongboy/archive/2013/12/12/407498.html

  • docker下使用daocloud/阿里云镜像加速

http://chuansong.me/n/341282551745

  • Initial Linux installer

docker/kitematic#1677

  • kitematic

https://github.com/docker/kitematic

EOF

IE9 跨域请求兼容

IE9 跨域请求兼容

Chrome:  You will die!
IE9:     Not today!

背景

搭建公司官网的框架时采用了 vuejs, 使用 history router mode 来做 SEO 优化, 使用 fetch 做网络请求, fetch 用 whatwg-fetch 做 polyfill. 根据百度浏览器市场份额统计, 2017年全年 IE9 的占有率达到 9.50%, 并且 vue 框架也是兼容到 IE9, 所以项目要求兼容到 IE9.

但是 fetch polyfill 并不兼容 IE9, 这篇文章追溯问题原因并提出解决方法.

问题: 访问拒绝

在 IE9 下打开页面, 发现 fetch 请求报了Unhandled promise rejectionError: 拒绝访问:

  • IE9
    访问拒绝

  • IE11 开 IE9 调试模式
    访问拒绝

怀疑是 fetch 的兼容问题, 查看一下版本:

$npm list whatwg-fetch     
project
└── [email protected] 

查看了一下whatwg-fetch 兼容性: 只支持到 IE10. 然后看到 whatwg-fetchv0.11 可以兼容 IE9, 那就降级一下吧:

$ npm uninstall whatwg-fetch
removed 1 package in 4.851s

$ npm install [email protected]
+ [email protected]
added 1 package in 5.96s

再试一下, 发现还是一样的问题.

问题原因: IE9 XMLHttpRequest 不支持 CORS

fetch 的 polyfill 采用了 XMLHttpRequest 实现, 但是在 IE9 下面, XMLHttpRequest 是不支持跨域请求的. IE10 的 XMLHttpRequest 支持跨域, 而 IE8, IE9 需要使用 XDomainRequest 来实现跨域.

那就用 XDomainRequest 实现异步请求, 代码:

function fetchIe9(url, options = {}) => {
  if (window.XDomainRequest) {
    // https://developer.mozilla.org/en-US/docs/Web/API/XDomainRequest
    // only support GET and POST method
    // request and response content type should be JSON
    // without response status code
    return new Promise((resolve, reject) => {
      const method = options.method || 'GET';
      const timeout = options.timeout || 30000;
      let data = options.body || options.params || {};
      if (data instanceof Object) {
        data = JSON.stringify(data);
      }

      const XDR = new XDomainRequest();
      XDR.open(method, url);
      XDR.timeout = timeout;
      XDR.onload = () => {
        try {
          const json = JSON.parse(XDR.responseText);
          return resolve(json.data);
        } catch (e) {
          reject(e);
        }
        return reject({});
      };
      XDR.ontimeout = () => reject('XDomainRequest timeout');
      XDR.onerror = () => reject('XDomainRequest error');
      XDR.send(data);
    });
  } else {
    // native fetch or polyfill fetch(XMLHttpRequest)
    // fetch...
  }
}

需要注意的是:

  • XDomainRequest 只支持 GET 和 POST mehtod
  • XDomainRequest 不支持带 cookie
  • XDomainRequest 不能设置 responseType, 通信双方需要约定数据格式
  • XDomainRequest 的响应没有 response status code

题外话: whatwg-fetch 一直采用 XMLHttpRequest 来做 polyfill, whatwg-fetch1.0+ 不支持 IE9, 并不是因为没有采用 XDomainRequest, 而是因为 IE9 的状态码不符合 fetch 规范, 而 polyfill 的目标是 polyfill 规范, 而不是做兼容.

问题: 请求异常终止和挂起

写好了代码, 在 IE9 中, 网络请求非常诡异, 经常不行: 请求只持续了不到 1ms, 并且接收数据为 0B, 没有状态码; 但是在少数时候是可以成功请求并获取数据的.

  • IE9
    异常终止

  • IE11 开 E9 调试模式
    此时 IE11 的 IE9 调试模式是可以的, 看来模拟器还是模拟不到位.
    没有异常终止

查了好久, 终于看到一篇文章: Internet Explorer Aborting AJAX Requests : FIXED

IE timing out the request even though data is being transmitted.

主要的原因大概是 IE9 会将一个正在传输的请求 timeout 掉.

解决办法是:

  • 添加 onprogress 事件回调, 告知 IE9 这个请求是活动中的, 不要 timeout 掉.
  • 将请求的发送放到主线程之外, 保证 XDomainRequest 已经完全初始化好.

最终代码

function fetchIe9(url, options = {}) => {
  if (window.XDomainRequest) {
    // https://developer.mozilla.org/en-US/docs/Web/API/XDomainRequest
    // only support GET and POST method
    // request and response content type should be JSON
    // without response status code
    return new Promise((resolve, reject) => {
      const method = options.method || 'GET';
      const timeout = options.timeout || 30000;
      let data = options.body || options.params || {};
      if (data instanceof Object) {
        data = JSON.stringify(data);
      }

      const XDR = new XDomainRequest();
      XDR.open(method, url);
      XDR.timeout = timeout;
      XDR.onload = () => {
        try {
          const json = JSON.parse(XDR.responseText);
          return resolve(json.data);
        } catch (e) {
          reject(e);
        }
        return reject({});
      };
      // fix random aborting: https://cypressnorth.com/programming/internet-explorer-aborting-ajax-requests-fixed/
      XDR.onprogress = () => {};
      XDR.ontimeout = () => reject('XDomainRequest timeout');
      XDR.onerror = () => reject('XDomainRequest error');
      setTimeout(() => {
        XDR.send(data);
      }, 0);
    });
  } else {
    // native fetch or polyfill fetch(XMLHttpRequest)
    // fetch...
  }
}

结论

  • IE9 发起跨域请求要使用 XDomainRequest, 因为 IE9 下的 XMLHttpRequest 不支持跨域调用.
  • XDomainRequest 只支持 GET 和 POST method, 并且没有 response status code, 可以说是不完善的 HTTP 异步请求对象.
  • XDomainRequest 不支持指定 responseType, 使用时建议请求和返回数据格式约定为 JSON.
  • whatwg-fetch1.0+ 不支持 IE9, 是因为 IE9 的状态码不符合 fetch 规范, 而 polyfill 的目标是 polyfill 规范, 而不是做兼容.

References

  • XDomainRequest

https://developer.mozilla.org/en-US/docs/Web/API/XDomainRequest

  • XMLHttpRequest

https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest

  • XDomainRequest – Restrictions, Limitations and Workarounds

https://blogs.msdn.microsoft.com/ieinternals/2010/05/13/xdomainrequest-restrictions-limitations-and-workarounds/

  • Internet Explorer Aborting AJAX Requests : FIXED

https://cypressnorth.com/programming/internet-explorer-aborting-ajax-requests-fixed/

EOF

Ubuntu16.10 迁移到 SSD

tags: Linux OS GRUB 硬盘分区

背景

2016年双十一入手了一块500G的 SSD(Solid State Drive,固态硬盘),打算安装到自己的笔记本上。笔记本的 HDD(Hard Disk Drive,机械硬盘)已经跑了 Ubuntu16.10 + Win10 双系统。光驱位的硬盘支架也装好了,一直虚位以待。工作忙一直拖到了2017年。

公司的 PC 机器也是 Ubuntu16.10,并且安装的软件比较齐全,所以计划将 PC 的 Ubuntu16.10 迁移到 SSD 上,然后在笔记本上运行。

开工准备

  • Ubuntu16.10,PC 上安装,各项迁移步骤运行的环境并作为要迁移到 SSD 的系统。
  • Gparted Partition Editor,图形化的分区工具。
  • 外接硬盘盒,通过 USB 线将 SSD 连接到 PC 上。

基础知识

该章节是计算机启动和系统加载的一些概念,有助于加深对迁移原理的理解,注重实践的话可以直接跳过。

总结不一定准确,仅作为个人理解。干货可以看这篇文章:uefi-boot-how-does-that-actually-work-then

BIOS vs UEFI

BIOS(Basic Input/Output System)和 UEFI(Unified Extensible Firmware Interface )是不同的计算机启动固件(Fireware),需要硬件(通常为主板)支持,相互代替的,其中 UEFI 是比较新的方式。

  • BIOS
    经典的启动固件,会调用磁盘的 MBR,然后由 MBR 中的 loader 继续加载操作系统。

  • UEFI
    UEFI 用来代替 BIOS,并克服 BIOS 的缺点,大多数的 UEFI 固件会提供兼容 BIOS 的启动方式。

  • 区别
    可以看这篇文章:[UEFI是什么?与BIOS的区别在哪里?][UEFI是什么?与BIOS的区别在哪里]

MBR vs GPT

MBR 与 GPT 用于存储硬盘的分区信息,是不同的硬盘分区表类型

  • MBR
    MBR 表示 MBR 分区表,MBR 分区表在硬盘开头处存放了特殊的启动分区,称为 MBR(Master Boot Record,主启动记录),包含 Boot Loader 和硬盘逻辑分区。MBR 支持最大约2T的硬盘,最多能划分4个主分区,更多分区需要使用拓展分区实现。
    MBR在行文中可以表示 MBR 分区表主启动记录两个意思,注意甄别。)

  • GPT
    GPT 表示 GUID(Globally Unique Identifier) 分区表,是 UEFI 规范的一部分,用于替换 MBR 的分区方式。GPT 没有分区数和分区大小限制。

  • 区别
    可以看这篇文章:[What’s the Difference Between GPT and MBR When Partitioning a Drive][What’s the Difference Between GPT and MBR When Partitioning a Drive]

File System

File System(文件系统)是存储媒介中文件存储的组织方式。
不同的文件系统类型有不同的速度,灵活性,安全性和占用空间。不同操作系统只支持特定的文件系统类型。
常见的文件系统类型有 FAT16,FAT32,NTFS,EXT3,EXT4,HFS 等。

磁盘发展史

Wikipedia 上有许多关于磁盘的资料,在磁盘分区上,我猜测的发展脉络是这样的:

  1. 磁盘跟内存一样直接物理寻址去访问数据;
  2. 为了方便,建立数据 Index,有了 File System;
  3. 需要多个分区,搞出了 Partition Tabel。

小结

  • BIOS/UEFI 跟 MBR/GPT 是不同层级的,BIOS/UEFI 是 Fireware,MBR/GPT 是分区表。
  • 推荐的使用方式: BIOS + MBR 或 UEFI + GPT:

If you want to do a ‘BIOS compatibility’ type installation, you probably want to install to an MBR formatted disk.
If you want to do a UEFI native installation, you probably want to install to a GPT formatted disk.

  • 理论上来说是可以组合使用的:

Of course, to make life complicated, many firmwares can boot BIOS-style from a GPT formatted disk.
UEFI firmwares are in fact technically required to be able to boot UEFI-style from an MBR formatted disk.

  • Windows 通常会要求 UEFI 的启动方式使用 GPT,不然不给继续安装。

SSD 分区

硬盘状态

使用外接硬盘盒,将 SSD 连接到 PC 机上,先查看硬盘状态:

$ sudo fdisk -l
Disk /dev/sda: 465.8 GiB, 500107862016 bytes, 976773168 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes
Disklabel type: dos
Disk identifier: 0xb2708ce0

Device     Boot     Start       End   Sectors   Size Id Type
/dev/sda1  *         2048    411647    409600   200M  7 HPFS/NTFS/exFAT
/dev/sda2          411648 210126847 209715200   100G  7 HPFS/NTFS/exFAT
/dev/sda3       210128894 913704959 703576066 335.5G  f W95 Ext'd (LBA)
/dev/sda5       210128896 703989759 493860864 235.5G 83 Linux
/dev/sda6       703991808 704966655    974848   476M 83 Linux
/dev/sda7       704968704 764067839  59099136  28.2G 83 Linux
/dev/sda8       764069888 771973119   7903232   3.8G 82 Linux swap / Solaris

Partition 3 does not start on physical sector boundary.

Disk /dev/sdb: 489.1 GiB, 525112713216 bytes, 1025610768 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 33553920 bytes

其中/dev/sda为 PC 上的硬盘,装有 Ubuntu16.10 + Win7;/dev/sdb为 SSD,当前 SSD 为空盘。

分区策略

  • 笔记本是 2011 年的机器,主板启动引导好像不支持 UEFI,是用 BIOS。

  • 考虑 SSD 的拓展性,分区表选择 GPL,选用的引导方式为 BIOS + GPT

  • 此时安装 GRUB 引导对分区划分有要求,具体参考接下文的*《GRUB 引导》*章节。

  • 先上分区结果:

    (注:前文出现的/dev/sdb1/dev/sdf1和后面可能出现的/dev/sd#1都为同一个分区,因为多次插拔了 SSD ,所以标识一直按字母序递增)

  • 不建立/swap分区了,因为 Ubuntu17.04也要移除 swap 分区

  • /dev/sdf1分区,建立 GRUB 引导所需分区,大小为 1M,分区文件类型为unformatted,分区 flag 为bios_grub

  • /dev/sdf2分区,Linux /boot分区,大小 1G。

  • /dev/sdf3分区,Linux /分区,大小 50G。

  • /dev/sdf4分区,Linux /home分区,大小 300G。

分区操作

分区操作在 Gparted 软件中完成,命令行fdiskparted也可以操作,但是我不熟悉。

  • 建立分区表
    SSD 是一个空磁盘,此时并没有分区表,所以要先建立分区表。分区表的格式选用 GPT:
  1. 打开 Gparted,点击 Device --> Created Partition Table
  2. 选择partition tabel typegpt,然后点击Apply

  • 建立 GRUB 所需分区
  1. 分区大小为1M,分区类型为unformatted

  2. 在新建的分区上点击右键,选择managerFlags,然后选中bios_grub选项。

  • 建立 Linux 系统分区
    根据上文*<<分区策略>>*章节,依次建立其他分区,分区的文件格式选择ext4
    分区结果:
$ sudo fdisk -l /dev/sdh
Device         Start       End   Sectors  Size Type
/dev/sdh1       2048      4095      2048    1M BIOS boot
/dev/sdh2       4096   2101247   2097152    1G Linux filesystem
/dev/sdh3    2101248 106958847 104857600   50G Linux filesystem
/dev/sdh4  106958848 736104447 629145600  300G Linux filesystem

GRUB 引导

GRUB 是什么

GRUB(Grand Unified Boot loader)是硬盘中的软件,引导器(loader)的一种。目前主流版本是 GRUB2,可以看 [GRUB2 中文介绍][GRUB2 中文介绍]。

GRUB 用于从多操作系统的计算机中选择一个系统来启动,或从系统分区中选择特殊的内核配置。

provides a user the choice to boot one of multiple operating systems installed on a computer or select a specific kernel configuration available on a particular operating system's partitions. -- GRUB

示例:

如图:第一个选项和最后一个选项是选择不同的操作系统;第一个选项和第二个选项是选择不同的内核配置。

GRUB 位置

其启动代码(boot.img)直接安装在 MBR 中,然后执行 GRUB 内核镜像(core.img),最后从/boot/grub中读取配置和其他功能代码。
BIOS 引导方式中,MBR 分区表和 GPT 分区表的 GRUB 引导文件所放分区不同

如图,GRUB 的执行顺序为 boot.img --> core.img --> /boot/grub/

  • 在 MBR 分区表中,boot.imgcore.img 都在 MBR 中。MBR 虽然只占用一个扇区(512Byte),但是其所在的磁道是空闲的,不会用于分区,可以放下 core.img

Some MBR code loads additional code for a boot manager from the first track of the disk, which it assumes to be "free" space that is not allocated to any disk partition, and executes it. -- MBR

  • 在 GPT 分区表中,MBR 为 protected MBR(为兼容 MBR,在硬盘起始位置保留的空间),后面并没有空间放core.img,需要建一个专门的分区来放,称为BIOS boot partition,该分区的文件类型为unformatted,flag 为BOIS_grub,该 flag 用于标识core.img所要安装到的分区。若果使用 UEFI 引导,GRUB 读取的是 ESP 分区中的数据,不需要 flag 为 BIOS_grub的分区。

建立 GRUB 引导

使用 grup-install 的教程来安装 GRUB 到 SSD 盘。

  • 挂载 /boot
    挂载 SSD 的/boot为 PC Ubuntu 的/mnt,因为我们需要将 GRUB 配置文件放入 SSD 的/boot/grub中。
$ sudo mount /dev/sdb2 /mnt
  • 安装 GRUB
    执行以下命令:
$ sudo grub-install --target=i386-pc --root-directory=/mnt --recheck --debug /dev/sdb

如果看到以下输出,应该就是成功了:

...
Installation finished. No error reported.

此时/mnt目录下,应该有一个./boot/grub的文件夹:

/mnt/boot/grub ⌚ 20:54:33
$ ls
fonts  grubenv  i386-pc  locale
  • 修复/grub位置
    查看下 PC Ubuntu 的/boot,/grub是直接放置在/boot下的:
/boot/grub ⌚ 13:30:33
$ ls
fonts  gfxblacklist.txt  grub.cfg  grubenv  i386-pc  locale  unicode.pf2

grub-install /dev/sdb安装的 GRUB 是/mnt/boot/grub,其中/mnt是 SSD /dev/sdb2分区,从 SSD 启动 Ubuntu 的话,/dev/sdb2会挂载为/boot,此时 GRUB 的位置是/boot/boot/grub。而当grub-install /dev/dsa安装 GRUB 到 PC Ubuntu 启动磁盘时,生成的/grub是在/boot/grubgrub-install的处理逻辑应该是先判断/boot路径是否存在,没有就新建。
所以,要将/mnt/boot/grub移动到/mnt/grub

$ sudo mv /mnt/boot/grub /mnt/grub

GRUB 引导修复类型

启动电脑后,当 GRUB 无法按照boot.img --> core.img --> /boot/grub/顺序执行时,会看到命令行界面,等待用户输入命令。此时可以通过输入 GRUB 内置的命令来修复 GRUB 引导。

boot.img是写在 MBR 中的,如果不能执行,直接跟 GRUB 引导方式说再见了,所以执行boot.img一般没问题。boot.img不能识别任何文件系统,core.img的位置是硬编码进boot.img的,所以执行boot.img一般没问题。因此,常见的引导问题集中在/boot/grub/,主要有两种,对应有两种引导修复模式:

  • GRUB Rescue 模式
    GRUB Rescue 模式是 GRUB 无法找到/boot分区,也就无法找到/boot/grub/。修复方法可以参考:grub rescue 模式下修复

  • GRUB Normal 模式
    GRUB Normal 模式是 GRUB 无法找到 GRUB 菜单grub.cfg,无法选择合适的内核或系统来启动。修复方法可以参考:Boot GNU/Linux from GRUB

数据复制

该步骤是把 PC 硬盘中几个 Linux 分区的数据拷贝到 SSD 上对应的分区。
注意:PC Ubuntu 和 SSD Ubuntu 都有//boot/home分区,阅读下文时注意辨别,我有时并没有写得很清晰。

操作方式

操作的套路是先将 SSD 的分区使用mount命令挂载为 PC 的/mnt,使用cp命令复制数据,再用umount命令移出这个分区;对下一个分区做同样操作。

  • 挂载和移出操作
// 挂载
$ sudo mount /dev/sdb2 /mnt
// 移出
$ sudo umount /mnt
  • 复制操作
    使用cp指令要加-r-f-a参数,-r表示递归复制,-f表示强制覆盖,-a表示保留原文件的属性(mode,ownership,tiemstamps等)
$ sudo cp -rf -a source destination

复制/boot分区

SSD Ubuntu 的/boot从 PC Ubuntu 上看为/dev/sdb2,将/dev/sdb2挂载为 PC Ubuntu 的/mnt。安装 GRUB 之后,/mnt已经有/grub这个文件夹和默认的lost+found文件夹。
使用cp将 PC 的/boot中其他文件复制到/mnt。结果类似:

/mnt/  13:56:06
$ ls | sort
abi-4.8.0-36-generic
config-4.8.0-36-generic
grub
initrd.img-4.8.0-36-generic
lost+found
memtest86+.bin
memtest86+.elf
memtest86+_multiboot.bin
System.map-4.8.0-36-generic
vmlinuz-4.8.0-36-generic

复制/分区

SSD Ubuntu 的/分区(根目录)比较特殊:一些子目录挂载了其他分区,并存在“伪目录”,不同子目录有特定的用途

所以复制/分区是有选择性的,不区分子目录进行复制,可能会提示“权限问题”、“无法访问”等错误。

  • 不需要复制的目录

  • /boot,/home/mnt挂载了其他分区

  • /media /cdrom 挂载可移除的媒体(cdrom 等)

  • /swap交换分区(不需要交换分区了)

  • 需要复制的目录
    主要参考: Linux操作系统备份之二

  • /bin 系统可执行文件

  • /etc 系统核心配置文件

  • /opt 用户程序文件

  • /root root用户主目录

  • /sbin 系统可执行文件

  • /usr 程序安装目录

  • /var 系统运行目录

  • 需要手动创建的目录
    /mnt中需要给 SSD 的/创建几个空目录

  • /dev 主要存放与设备(包括外设)有关的文件

  • /proc 正在运行的内核信息映射

  • /sys 硬件设备的驱动程序信息

这几个目录是 Linux 内核启动后由内核来挂载并存放信息的,不能从运行中的 PC Ubuntu 复制过去,但是需要建立空目录,不然内核启动后会报类似错误:

mount: mount point /dev does not exist

创建命令:

$ sudo mkdir dev proc sys
  • 操作策略
  • 每个目录单独执行复制命令,出错了好处理

复制/home分区

挂载 SSD Ubuntu/home到 PC Ubuntu /mnt,然后全盘复制

$ sudo mount /dev/sdb4 /mnt
$ sudo cp -rf -a /home/* /mnt

挂载/home/boot分区

SSD Ubuntu 的/home/boot需要挂载到/,挂载方法为:修改/ect/fstab

  • 挂载/dev/sda3为 PC Ubuntu /mnt
  • 使用blkid查看 SSD 各分区的 UUID
$ sudo blkid
...
/dev/sdb3 UUID="a5eb2b0c-2104-4afe-aa78-93396d3e0986" TYPE="ext4" PARTUUID="b2708ce0-07"
...
  • 修改 SSD Ubuntu 的fstab文件
$ sudo vim /mnt/etc/fatab

fstab文件大概是这样子的:

# /etc/fstab: static file system information.
#
# Use 'blkid' to print the universally unique identifier for a
# device; this may be used with UUID= as a more robust way to name devices
# that works even if disks are added and removed. See fstab(5).
#
# <file system> <mount point>   <type>  <options>       <dump>  <pass>
# / was on /dev/sda3 during installation
UUID=a5eb2b0c-2104-4afe-aa78-93396d3e0986 /               ext4    errors=remount-ro 0       1
#
# /boot was on /dev/sda2 during installation
UUID=8cba10c6-dff2-4300-a630-ab0e7a4782af /boot           ext4    defaults        0       2
#
# /home was on /dev/sda4 during installation
UUID=298ba5ad-d306-4b4a-aaa8-54312590dec6 /home           ext4    defaults        0       2

GRUB 引导修复

将 SSD 通过 USB 插入到笔记本,开机,选择从 USB 启动。此时应该会是看到类似下图的画面。

说明已经进入到 GRUB 引导程序中,但是没有 GRUB 启动选项,无法继续引导了。距离成功仅剩一步:修复 GRUB 引导。

指定内核启动

  • 指定/boot分区和/grub位置*(好像不需要这步,GRUB Rescue 才需要)*
// grub> root=hd0,gpt2
// grub> prefix=(hd0,gpt2)/grub
grub> set root=hd0,gpt2
grub> set prefix=(hd0,gpt2)/grub
  • 设置启动的 Linux 内核
grub> linux /vmlinuz-4.8.0-36-generic ro root=/dev/sda2
  • 设置虚拟内存
grub> initrd /initrd.img-4.8.0-36-generic
  • 启动 SSD Ubuntu
grub> boot

到这一步应该可以启动 SSD 的 Ubuntu,但是下次重新开机,又需要手动指定内核才能启动,通过在 SSD Ubuntu 中重建 GRUB 引导可以解决该问题。

重建 GRUB 引导

从 SSD 开启 Ubuntu 成功后,执行以下命令:

$ sudo update-grub
$ sudo grub-install /dev/dsa

以上命令更新了 GRUB 可引导的系统/内核列表:/boot/grub/grub.cf,并重新安装了 GRUB。可以参考:Grub2/Installing

笔记本下次开机,就能看到类似画面:

完成

将 SSD 放入笔记本内置硬盘位,将旧的 HDD 放到光驱位置,开机,完成!(撒花)!

结语

总共花了三天时间搞定这个事情,整理出文章花了N天,查看了很多资料,对计算机开机引导,硬盘分区和 GRUB 算是比较了解了。
现在笔记本有了 SSD + HDD,下一步可能会实践双硬盘的数据备份。
最后放上 HDD 凌乱的分区图,纪念这几年装机折腾的日子。折腾中总有收获。

Refenrences

  • UEFI是什么?与BIOS的区别在哪里

http://www.ihacksoft.com/uefi.html
[UEFI是什么?与BIOS的区别在哪里]:http://www.ihacksoft.com/uefi.html

  • Linux:系统启动引导过程

http://zhaodedong.leanote.com/post/Linux%EF%BC%9A%E7%B3%BB%E7%BB%9F%E5%90%AF%E5%8A%A8%E5%BC%95%E5%AF%BC%E8%BF%87%E7%A8%8B

  • What’s the Difference Between GPT and MBR When Partitioning a Drive

http://www.howtogeek.com/193669/whats-the-difference-between-gpt-and-mbr-when-partitioning-a-drive/
[What’s the Difference Between GPT and MBR When Partitioning a Drive]:http://www.howtogeek.com/193669/whats-the-difference-between-gpt-and-mbr-when-partitioning-a-drive/

  • 6 Stages of Linux Boot Process (Startup Sequence)

http://www.thegeekstuff.com/2011/02/Linux-boot-process/

  • GRUB2 中文介绍

https://my.oschina.net/guol/blog/37373
[GRUB2 中文介绍]:https://my.oschina.net/guol/blog/37373

END

Node 配置平台

已经实现, 分享 slides: 前端配置管理系统从0到1-public, 文件稍大, 慎点.

背景

Node 使用,从大处开始预研,然后从小点切入实践。
先搭建一个 Node 数据配置平台( content service)。

相关概念

要找一个开源平台,首先需要找对关键字。

  • config management
    配置管理,找到的多为运维相关的工具。
  • content service
    内容服务,一般指构建静态网站。
  • 后端平台
    这个可能性非常大。

References

  • 架构篇-cms的重构与演进

http://repractise.phodal.com/#架构篇-cms的重构与演进

  • 天猫App的动态化配置中心实践 (自建架构重点参考)

http://www.infoq.com/cn/articles/app-dynamic-configuration

https://www.one-tab.com/page/dIcbimsZRR-mGzXN0zZRTA
https://www.one-tab.com/page/HShl4-acSraNThECklqe8w

  • What is an Entity Relationship Diagram

https://www.lucidchart.com/pages/er-diagrams

EOF

页面可视化搭建工具前生今世

页面可视化搭建工具前生今世

背景

引子

页面可视化搭建, 是一个历久弥新的话题. 更广义上讲, 页面是 GUI 的一部分, GUI 的拖拉生成在各种开发工具上很常见, 如 Android Studio, Xcode, Visual Studio 等. 前端页面早在十几年前就能用 Dreamweaver, Frontpage 等工具可视化搭建出来.

Dreamweaver 操作页面示例:

dreamweaver_demo

但是现在已经很少人使用 Dreamweaver 了, 其主要原因是页面承载的内容已经和页面源码分离, 由后端接口返回再渲染到页面, 静态页面网站无法承载大量的动态内容.

Dreamweaver 死了, 但是页面可视化搭建工具依然广泛需要和使用, 所以这个话题依然值得探讨.

文章内容

  • 页面构成和页面组件化.
  • 页面可视化搭建工具的必要性.
  • 页面可视化搭建工具的区分维度.
  • 业界的实践实例.

页面

页面是 HTML / DOM

页面可视化搭建的操作对象是页面. 页面是一份 HTML 文档, 不管是静态页面还是动态渲染出来的页面, 在页面上看到的内容, 都是 HTML 文档的一部分.

对 HTML 文档的实例化和操作, 通过文档对象模型(DOM)来实现, 也可以说页面是一个 DOM. 本文没有严格区分 HTML 和 DOM 这两个概念, 以下行文都用 HTML 这个概念.

HTML 使用一种树形结构来表示页面, 树的每个节点为一个页面元素或文本节点, 一个页面元素可以包含多个页面元素节点或文本节点. 页面元素通常称为标签, 页面元素类型由 HTML 规范定义.

HTML 结构示例:

pic_htmltree

https://www.w3schools.com/js/js_htmldom.asp

页面是 HTML Tree + Data

从前端开发的角度, 可以认为页面是由 HTML TreeData 组成, HTML Tree 是页面元素的树形结构, Data 是页面元素的属性或文本节点. 下图中蓝色框所示的节点可以认为是数据.

pic_htmltree_data

为什么从前端开发角度会说页面是 HTML Tree + Data? 举一个常见场景来说明: 在开发新页面时, 我们是可以复制已有页面(好吧, 我就是这样的前端工程师), 然后只修改页面 HTML, 或者只修改数据, 或同时修改 HTML 和数据, 从而完成新页面的开发.

静态页面和动态逻辑页面

上一节说页面的由 HTML TreeData 组成, 讨论的是静态页面.

what_is_static_page

浏览器请求静态页面, 网络返回的 HTML 源码就是页面渲染完成后的 HTML. 静态页面的源码和页面渲染结果一致:

static_page_render_result

当下, 前端页面更多的是有动态逻辑的页面, 在页面中引入和使用动态脚本(Javascript)对页面进行修改和控制.

what_is_dynamic_page

浏览器请求动态逻辑页面, 网络返回的 HTML 源码与页面渲染完成后的 HTML 有差异. 动态逻辑页面的源码和渲染结果有差异:

dynamic_logic_page_render_result

页面组件化

页面渲染后是一棵 HTML 元素构成的树, 页面的可编辑粒度为 HTML 规范定义的 HTML 元素.
使用 Web Components 组合 HTML 元素, 实现了功能封装和可复用的页面组件. 在流行的前端框架中, 都提供了组件化的功能, 从前端框架的视角看, 页面是由组件树组成. 这些组件内部维护自身的 HTML 元素结构、样式和功能逻辑, 并通过组件的 props 获取外部传入的数据, 实现了功能封装和复用.

Vue 组件树示例:

vue_components

https://vuejs.org/v2/guide/#Composing-with-Components

并没有讨论 CSS

在以上的章节中, 我们并没有讨论决定页面样式的 CSS. 因为借助 Javascript 的动态逻辑, CSS 可以归入到 Data 的范围: 通过对页面元素 style attribute 的修改, 或将 CSS 属性动态添加到 <style> 标签中, 可以实现对页面元素样式的修改.

页面可视化搭建

有了对页面组成的认知基础,可以对页面可视化搭建有更多的讨论: 页面可视化搭建是什么? 为什么需要?

是什么

如前文所阐述, 动态逻辑页面分解为 HTML Tree, DataDynamic Logic. 前端开发工程师开发前端页面的过程, 本质上是用编程工具(IDE)对页面的 HTML Tree, DataDynamic Logic 进行增删和修改.

页面可视化搭建, 是用可视化交互的方式对页面的 HTML Tree, DataDynamic Logic 进行增删和修改, 从而实现页面的生成. 页面可视化搭建工具是实现页面可视化编辑的软件工具.

用页面可视化搭建工具来搭建页面与前端工程师在页面上搬砖, 都是搭建页面, 区别在于实现页面搭建的方式. 做个简单对比:

差异点 编程开发页面 可视化搭建页面
技能要求 需要编程基础 可以没有编程基础
操作方式 在代码编辑器中编写代码 在可视化搭建工具中拖拉/填表/编写代码

为什么需要

任何工具的存在都是更高效地解决问题. 页面可视化搭建工具, 用于解决页面生成的效率问题.
可能前端工程师会觉得最有效率的页面生成方式是打代码, 但有搭建页面需求的不只是前端工程师. 而可视化页面搭建工具, 恰恰是面向"就缺一个前端工程师"的人员, 用于提升他们生成页面的效率.
我们可以从一些使用场景来窥探页面可视化搭建工具的应用场合.

页面小白做 H5

页面小白不需要任何页面相关的知识, 不需要了解 HTML/JS/CSS 这些概念, 只要像使用 Word 一样在 H5 制作工具上操作, 就可以做出一个挺漂亮的页面. H5 制作工具很多, 其中 百度H5 做很好不错.

如: 小陈女票要生日了, 小陈为女票做了一个有创意的生日祝福页面:

baidu_h5

营销活动页面搭建

大多数互联网公司需要做许多的活动页面来承载运营业务. 运营活动页面的特点是: 页面功能大同小异、需求急、时间紧、下线快、研发性很比低. 前端工程师无法持续开发无穷无尽的活动页面, 需要采用活动页面可视化搭建工具, 由运营人员/产品人员直接生成活动页面. 研发人员的工作转变为提供满足活动页面业务需要的活动模板.

如: 抽奖活动页面的可视化搭建:

activity_demo

中后台系统开发

在公司内部, 需要做许多的中后台支持系统, 这些系统的管理端一般用 web 页面承载. 那么问题来了, 中后台系统的前端工程, 怎么保障可用性、可维护性和页面呈现一致性? 这些系统与后台逻辑强关联, 一般由后台开发人员开发; 后台开发人员写代码逻辑是没有问题的, 但是其前端开发能力相对较弱. 所以需要增强他们开发前端页面的能力, 前端开发能力由前端服务化提供.

前端服务化的第一种方式是提供一套组件库, 如 饿了么的 Element.
组件库一般由前端开发人员封装成模板工程, 模板工程提供公共样式和函数库, 并对编写的代码做校验和约束, 一定程度上降低了前端开发难度, 统一后台人员代码风格. 此时后台开发人员的开发方式为: 在代码中用组件拼凑页面, 然后写代码逻辑.

前端服务化的第二种方式, 是提供页面可视化组装系统, 这个系统输出组装后的前端工程源码. 这样的系统比提供组件库和模板工程的方式走得更远: 通过可视化生成模板工程, 后台开发人员不需要在代码中拼凑前端页面, 不需要关注前端组件, 只需要编写代码逻辑.
这种方式可以参考阿里的 ice.

阿里 ice 示例:

iceworks_demo

前端服务化的终极方式, 是直接提供一个开发的 IDE, 将动态逻辑的书写也在 IDE 中完成.
美团外卖前端可视化界面组装平台 —— 乐高, 前端服务化——页面搭建工具的死与生.

美团乐高示例:

lego_demo

前端服务化

更加广泛来说, 为页面小白/运营人员/产品人员提供的页面可视化生成工具, 也是赋予以上人员前端开发的能力. 所以页面可视化搭建, 本质上是前端服务化的一部分. 前端服务化总结, 可以看百度的 前端即服务-通向零成本开发之路.

页面可视化搭建工具区分维度

有了前文对页面的基础认知, 终于进入了本文的正题 -- 页面可视化搭建工具.
前面已经零星讨论过页面可视化搭建工具的定义, 再总结一下: 页面可视化搭建, 是指用可视化交互的方式(对比编写代码的方式), 实现页面的修改或生成; 页面可视化搭建工具, 增强了使用者的前端开发能力, 提升了使用者修改或生成页面的效率.

思考一个更具体的问题: 当我们讨论页面可视化搭建工具时, 怎么进行描述和讨论? 换个角度提问题: 可以从什么维度对页面可视化搭建工具进行描述和区分?

页面可视化搭建工具的区分维度包括:

  • 系统功能
  • 面向客群
  • 编辑自由度

下文会对页面可视化搭建工具的区分维度做介绍, 并会对每个区分维度提供示例(这些示例不会展开讨论, 且在不同维度下会多次使用同个示例).

系统功能

页面可视化搭建工具的系统功能是指该工具在解决特定页面可视化搭建问题上提供的核心能力.
页面是由HTML Tree, DataDynamic Logic 三部分组成, 一个页面可视化搭建工具提供的能力是编辑页面组成部分之一或多部分. 对基于组件的页面, 其可编辑单元为组件, 此时采用 Component Tree 概念取代 HTML Tree.

system_function_category

HTML Tree 编辑

这类页面搭建工具专注于可视化地编辑页面 HTML Tree 部分, 一般可以对页面做自由度较高的编辑.
其关键功能在于高自由度: 几乎可以编辑页面可见的所有元素, 能自由修改页面结构、页面元素样式和页面数据, 采用类似 Word, Photoshop 的可视化编辑方式.
这类工具一般只适用于生成逻辑比较简单的页面, 其中原因后续会讲.
常说的 H5 制作工具就是指这类工具.

如: 百度H5iH5

Component Tree 编辑

这类页面搭建工具针对组件化的页面, 主要实现 Component Tree 的可视化编辑. 其核心功能在于页面布局设计: 在 UI 组件列表中选择合适的组件, 通过拖拉的方式将组件嵌入到页面中, 生成带布局和样式的页面.

如: ice 阿里飞冰vue-layout

vue-layout 示例:

vue_layout_demo

https://jaweii.github.io/Vue-Layout/dist/#/

页面 Data 编辑

这类页面搭建工具专注于可视化地编辑页面的 Data 部分, 如图片URL、按钮文本、按钮跳转链接等.
这类搭建工具主要针对 HTML Tree 比较固定、能承载复杂业务逻辑的页面. HTML Tree 固定的常见方式是页面组件化, 只需修改页面组件的 Data 就能快速地生成页面.
其核心功能在于快速搭建承载业务逻辑的页面.
通常营销活动页面就采用这种方式来可视化搭建.

如: 阿里云凤蝶、开源的 pipeline

阿里云凤蝶示例:

yunfengdie_demo

Dynamic Logic 编辑

这类页面搭建工具支持在界面上输入逻辑代码, 实现页面 Dynamic Logic 编辑, 如后台接口请求逻辑, 业务判断逻辑等.
这些逻辑代码需要有合适的插入点, 一般在事件钩子中提供插入点, 如页面 onload、网络请求状态变更、按钮事件、数据变更等.
做到可以支持编辑 Dynamic Logic 是超牛逼的事情, 这类工具对页面的理解最深入, 对开发者的技术能力、前端架构能力和开发能力都要求很高.

如: 前端服务化——页面搭建工具的死与生

系统功能组合

还有其他系统功能的组合, 可以综合上面的典型类别来做讨论.

面向客群

页面可视化搭建工具的面向客群是指工具的使用客群. 不同的使用客群, 其对页面技术的认知程度、搭建页面的诉求有所不同, 所以可以从工具的面向客群来区分不同工具.

custom_category

前端小白

前端小白是不具有前端知识的人群, 他们对页面可视化搭建工具的诉求是交互性越高越好. 最适合他们的工具是像 Word, Powerpoint, Photoshop 等具有丰富交互功能, 且所见即所得的页面搭建工具.
同时他们也不关心页面最后用什么方式托管到互联网上, 页面编辑完成后要帮他们在公网上托管页面, 并提供页面链接, 方便前端小白将页面发给自己的女朋友.

如页面界的 Photoshop:

ih5_demo

https://www.ih5.cn

运营/产品

运营、产品人员没有开发人员页面开发、逻辑编程的能力, 他们的诉求是可以快速搭建活动、产品页面. 活动、产品页面是承载着业务逻辑的: 如包含领取优惠券功能、背景音乐播放功能、产品购买功能等. 运营、产品对页面可视化搭建的另一个诉求是“快速”: 一天好几个活动, 怎么快怎么来.
面向运营、产品的可视化搭建工具, 需要将页面的逻辑功能封装在页面区块内, 支持通过点击来选择区块, 然后在表单中编辑区块所需数据, 只对页面进行少量编辑就完成业务页面搭建.
如领取优惠券的页面, 运营、产品只要在表单中填入优惠券的 ID, 然后就快速生成领取该优惠券的页面, 不需要关心优惠券在页面上如何展示和被领取的具体逻辑.

如, 开源项目 pipeline:

demo

中后台开发人员

中后台开发人员具有逻辑编程能力, 但其前端开发能力比较弱. 中后台开发人员的诉求是, 在开发中后台系统的 Web 管理端时, 不需要进行重度的前端页面结构和样式开发, 可以专注在逻辑和数据处理上.
这要求页面可视化搭建工具提供页面搭建的区块, 对区块进行可视化组合来输出一个基本的前端页面; 并在页面搭建工具上提供业务逻辑编写的输入点, 或将基本前端页面源码导出到 IDE 中供中后台开发人员进行业务逻辑的开发.

如: ice 阿里飞冰

前端工程师

要啥页面可视化搭建工具, 抓起键盘就开始干.

just_do_it

编辑自由度

页面可视化搭建工具的编辑自由度, 是指页面可编辑单元的粒度. 前端页面的可编辑单元为 HTML 元素; 从前端页面组件化的角度, 页面可编辑单元为组件.
不同的编辑自由度的选择, 是可视化搭建工具在不同业务场景下编辑自由度与编辑效率的平衡.

degree_of_freedom_category

编辑自由度为 HTML 元素(左)与自由度为组件(右)的示例:

edit_free_degree

编辑自由度为 HTML 元素

编辑自由度为 HTML 元素的页面搭建工具有以下特点: 可编辑的元素丰富、页面结构灵活、可视化编辑效率较低、业务逻辑封装度较低.
这类工具的可编辑单元为 HTML 元素, 可以编辑元素的文本、样式和行为, 可编辑的元素较丰富; 并且可以组合各种 HTML 元素到页面中, 生成的页面结构灵活; 从生成页面的角度, 编辑出一个页面需要从基本的 HTML 元素开始搭建, 可视化编辑的工作量较大; 一个业务功能的实现, 通常需要渲染多个 HTML 元素, 而这类工具可以自由增删业务所需的 HTML 元素, 这导致无法固定地承载业务功能, 所以这类编辑工具生成的页面, 业务逻辑封装程度较低.

如: iH5vvveb

vvveb 示例:
vvveb_demo

http://www.vvveb.com/vvvebjs/editor.html

编辑自由度为前端框架组件

编辑自由度为前端框架组件的页面搭建工具有以下特点: 可编辑的元素依赖搭建工具提供的组件, 可视化编辑效率较高、业务逻辑封装度较高.
这类工具的可编辑单元为前端框架的组件, 这些组件需要开发并导入到页面可视化搭建工具中; 组件的渲染结果包含了多个 HTML 元素, 所以从生成页面的角度, 编辑出一个页面只需要组合组件, 可以较快速完成页面生成; 组件本身承载了特定的业务功能, 所以这类编辑器生成的页面, 业务逻辑封装程度较高.
对于嵌套的组件, 需要重点解决组件数据流和组件布局适配.

如: Vue-Layout

vue-layout 示例:

vue_layout_demo

不嵌套的前端框架组件

移动端的页面, 常用的布局策略是: 宽度铺满, 高度滚动. 如果前端框架组件都设置为铺满宽度, 页面展示时组件只需在浏览器垂直方向上顺序排列, 则组件组合时候不需要嵌套, 所有组件互为兄弟节点. 这种铺满宽度的组件, 非常适合搭建移动端页面的场景: 在承载页面逻辑的同时, 使得页面的编辑更加简单, 使用者只需要处理组件的顺序, 不需要处理组件的嵌套.

如: 阿里云凤蝶pipeline

pipeline 示例:
pipeline_demo

https://page-pipepline.github.io/pipeline-editor/dist/#/

理想的页面可视化搭建框架

页面可视化搭建工具, 需要对页面做一些约定和约束, 在可视化搭建时遵循工具约定和约束来编辑页面. 更全面讨论页面可视化搭建工具时, 不只是关注工具本身的功能, 还需要关注工具的依赖和约束, 如页面可视化搭建工具的组件化方式、模板组织方式、编辑功能实现方式等. 从工具开发的角度说, 页面可视化搭建工具是需要架构设计的, 不同工具的区分, 其实是不同的页面可视化搭建框架间的差异.

在互联网公司中, 广泛运用页面可视化搭建工具来支持运营活动页面的生成, 本章我们只探讨运营页面搭建工具的理想框架.

页面可视化搭建框架的核心是实现页面的可视化编辑. 运营页面搭建工具, 声明页面配置数据并提供配置表单, 通过对配置表单的数据填充, 实现基于模板的页面生成. 如图所示:

visual_edit

可视化编辑

配置数据

对页面的可编辑部分, 需要准确描述可编辑部分所需的配置数据; 配置数据是异构的, 不同页面、不同区块的配置数据各不相同. 所以需要对不同页面、不同区块定义各自配置数据的数据结构和字段类型.
理想的配置数据格式为 JSON, 因为其格式灵活, 前端友好; 理想的配置数据描述格式为 JSON Schema, 因为其支持表单动态生成和数据校验.

配置表单生成

采用 JSON Schema, 容易生成配置表单, 只要按照 JSON Schema 对 JSON 数据的描述, 可以动态渲染出配置表单. 并且可以采用 JSON Schema 对编辑后的数据做格式校验, 避免编辑错误.

如配置表单自动生成工具 json-editor:

json_editor_demo

组件化

组件是对 HTML 元素、元素布局和样式、业务逻辑的封装, 通过组件化的方式, 将页面的搭建转化为对组件的组合, 大大减低了运营页面生成的编辑工作量, 实现快速搭建承载业务逻辑的运营页面.

pipeline 的页面组件化:

component_demo

模板

模板是带有默认数据的页面; 对于组件化的页面, 模板是从组件库中选取部分组件, 并带有各个组件的默认数据.
采用模板生成页面, 只需对模板进行少量编辑即可实现页面快速生成.

与编辑系统解偶

编辑系统和组件解偶,组件只需要遵循编辑系统的组织约定, 其具体开发过程和承载的逻辑与编辑系统无关, 支持自由拓展页面组件.
编辑系统与模板采用的前端框架解偶, 在遵循编辑系统约定下, 可以选择不同的前端框架.

理想的运营页面可视化搭建框架

  • 采用 JSON Schema 声明配置数据, 配置表单自动生成.
  • 采用组件化和页面模板实现页面生成效率的提升.
  • 编辑系统与组件、前端框架、模板解耦.
  • 在遵循编辑系统约定下, 组件可以自由拓展, 前端框架可以自由选择.

页面可视化搭建工具举例

列举一些页面可视化搭工具, 并附带少量点评.

阿里云凤蝶

移动建站平台

  • 支持页面 Data 编辑, 面向运营、产品人员, 编辑自由度为无嵌套的组件.
  • 目前制作运营、活动页面功能上最好的工具.
  • 提供页面搭建的模板, 并支持自定义模板.
  • 配置表单基于 Schema 生成, 配置表单操作功能完善.

ice 阿里飞冰

飞冰 - 让前端开发简单而友好

  • 支持 Component Tree 编辑, 面向中后台开发人员, 编辑自由度为无嵌套的组件.
  • 使用"物料-区块", 非前端开发人员可以快速搭建出可用、符合规范的页面.
  • 页面以源码方式输出.
  • 前端服务化的一种方式.

百度H5

创意,绝不雷同

  • 支持 HTML Tree 编辑, 面向前端小白, 编辑自由度为 HTML 元素.
  • 做 H5 的好工具, 功能上很强大, 对动画的编辑功能做到细致.

美团外卖前端可视化界面组装平台 —— 乐高

  • 支持 Dynamic Logic 编辑, 面向中后台开发人员, 编辑自由度为可嵌套的组件.
  • 前端服务化的一种方式.
  • 在美团内部支持了许多业务页面, 没有公网服务, 了解该系统只能通过其介绍文章.

esview

Drag vue dynamic components to build your page,generate vue code.

开源项目, 模仿美团点评的乐高.

  • 完整的可视化页面搭建框架, 面向中后台开发人员.
  • 页面布局结果看起来比较乱, 自定义组件写法比较诡异; 没有融合业务逻辑, 不支持在框架中写页面的代码逻辑.

gaea-editor

Design websites in your browser

开源项目.

  • 支持 Component Tree 编辑, 面向中后台开发人员, 编辑自由度为可嵌套的组件.
  • 页面的拖拉生成, 实现得很完整.
  • 用于页面设计, 所以偏向页面元素的样式控制.
  • 技术文章对可视化搭建工具数据流有深刻理解: 可视化在线编辑器架构设计.

Vue-Layout

基于UI组件的Vue可视化布局、生成.vue代码的工具。

开源项目.

  • 支持 Component Tree 编辑, 面向中后台开发人员, 编辑自由度为可嵌套的组件.
  • 工具的使用体验效果不错.

gen

根据接口生成页面,减少重复性工作

  • 开源项目, 用起来感觉不错.
  • 系统中有好几个概念, 开始比较难上手.

其他

业界实践

列举一些业界在页面可视化搭工具上的实践, 并附带少量点评.

前端服务化——页面搭建工具的死与生

  • 支持 Dynamic Logic 的页面可视化搭建 IDE.
  • 讲解了页面可视化搭建框架支持 Dynamic Logic 的可行性和设计架构.
  • 作者在前端框架和 IDE 方面写了好几篇文章, 很深刻.

腾讯IMWeb: 积木系统,将运营系统做到极致

2015年的文章! 完全说到点上.

  • 简单易用的、可视化的可编辑页面.
  • 通用的、简便地组件接入机制.
  • 组件: 开发过程和系统无关, 逻辑和系统无关.

美团外卖前端可视化界面组装平台 —— 乐高

  • 把系统架构将得很清楚, 有借鉴意义.
  • 对页面组成做了分析, 阐述了可视化配置的原理.

前端即服务-通向零成本开发之路

百度的前端服务化实践, 都在这一篇.

可视化在线编辑器架构设计

  • 可视化在线编辑器属于前端开发引擎, 前端进入了前端工业时代.
  • 深入讨论了组件数据流.

百度外卖如何做到前端开发配置化

  • PPT 将原理和架构讲得很清楚.
  • 使用流程图很清晰.
  • 项目开源了 -- block, 试用起来功能比较简陋.

转转运营活动高效开发有哪些秘诀

基于组件的页面生成系统-魔方, 采用 npm 管理组件.

QQ会员: 如何保证H5页面高质量低成本快速生成

内部 ET 平台, 包含活动管理的其他功能.

vue-design 桌面端页面可视化构建程序

esview -- 这可能是目前最好的vue代码生成工具

总结

  • 页面由 HTML Tree, Data, Dynamic Login 组成.
  • 页面可视化搭建工具用于提升各类人员的页面搭建效率.
  • 页面可视化搭建其实是前端服务化的方式.
  • 页面可视化搭建工具需要平衡自由度和效率.
  • 组件和模板是页面可视化搭建框架的核心.

全文结束, 本文对页面可视化搭建思考和讨论可能还不够完整, 欢迎讨论和补充.

后记: 终于写完了, 历时估计一个月! 写这篇文章的初衷是给我造的页面可视化搭建框架 -- pipeline 写背景, 但思考的点比较多, 所以就独立写了一篇文章. Pipeline 基本对标阿里的云凤蝶, 已经开源, 相关文章还在撰写中. 赶紧点击 Demo 体验吧.

EOF

前端工程师的 dubbo 入门路径

tags: Spring Tomcat Maven Zookeeper Dubbo

背景

公司准备上 Node, 初步设想是 Node 使用 RPC 直接调用后台的微服务. 首先需要研究一下可行性. 从我的角度来说, 要做好基础知识的学习, 不能浅尝辄止.

需要学习的概念

下面对概念的描述并不一定准确,是我到目前为止的理解。

  • maven java 代码打包工具,匹配前端的 npm
  • spring java 的 servlet 开发框架
  • zookeeper 服务发现,使得服务使用者通过服务发现调用服务提供者的服务,避免直接调用,提升灵活性和实现负载均衡等
  • tomcat , Servlet/JSP container, java 动态服务容器
  • dubbo 阿里推出的一个 rpc 框架,基于 spring 实现.

maven

学习记录和代码:

https://github.com/CntChen/maven-test

spring

  • 概念
    spring 是 java 后台使用的框架, 需要搭建一个进行学习.

  • 介绍

https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/

  • 学习记录和代码:

https://github.com/CntChen/spring-rest-service-test

zookeeper

安装

$ docker pull zookeeper:3.4.10

启动

  • 后台启动并映射接口到 host
$ docker run --rm --name some-zookeeper -p 2181:2181 -d zookeeper:3.4.10
  • 查看接口绑定
$ netstat -lant | grep 2181
tcp6       0      0  ::1.2181                                      *.*                                           LISTEN     
tcp4       0      0  *.2181                 *.*                    LISTEN 

停止和删除 containner

$ docker stop `docker ps -f ancestor=zookeeper:3.4.10 -a -q`
$ docker rm `docker ps -f ancestor=zookeeper:3.4.10 -a -q`

测试

  • 进入 containner bash
$ docker exec -it `docker ps -f ancestor=zookeeper:3.4.10 -q` bash
  • 测试方法

http://zookeeper.apache.org/doc/trunk/zookeeperStarted.html

tomcat

基础知识

  • tomcat 部署 war

http://www.baeldung.com/tomcat-deploy-war

  • docker 部署 tomcat

http://dockone.io/article/285

安装

$ docker pull tomcat:9.0

启动

  • 启动容器
$ docker run -it --rm -p 8080:8080 tomcat:9.0
  • 查看

localhost:8080

使用 docker-compose

  • 启动
$ docker-compose -f docker-compose.yml up --build --force-recreate

解决无法打开server status manager app 问题.

https://stackoverflow.com/questions/38172756/apache-tomcat-8-5-3-manager-app-403-error/38650401#38650401

项目部署

学习记录和代码: https://github.com/CntChen/spring-rest-service-test/tree/war-tomcat

dubbo

文档

http://dubbo.io/

安装

使用dubbo 官方 demo, 并添加 zookeeper 注册中心.

学习记录和代码:https://github.com/CntChen/dubbo-demo-test

启动

  • 启动 zookeeper
$ docker run --rm --name some-zookeeper -p 2181:2181 -d zookeeper:3.4.10
  • 打包代码
$ mvn package
  • 启动 provider
$ java -jar dubbo-demo-provider/target/dubbo-demo-provider-2.5.6-jar-with-dependencies.jar
  • 启动 consumer
$ java dubbo-demo-consumer/target/dubbo-demo-consumer-2.5.6-jar-with-dependencies.jar

EOF

SQL 温习

tags: SQL MySQL Terminal Learning

背景

公司搭建的接口文档平台 rap 使用了 MySQL 存储数据,后台同事对数据库表做了修改,作为 rap 测试的我在操作 rap 后需要去数据库看数据。
所以学习了一下 SQL 的基本操作。

本文内容

  • Ubuntu 终端下连接远程 MySQL server。
  • 复习 SQL 教程的笔记。

安装 client

因为只需要连接远程数据库并修改数据,不需要本地提供 mysql 服务,所以只安装 mysql 客户端。

$ sudo apt install mysql-client

连接到远程服务器

$ mysql -h 10.xx.xxx.xxx -u root -p -P 3306

其中:
mysql: 终端中的 MySQL 命令
-h:指定连接的 server host
-u:指定连接的用户名
-p:指定连接的密码,如果没有在命令行中指定,会在该命令执行后询问用户输入,这样可以避免黑客通过查看命令历史获取到 server 的连接密码。
直接输入密码的话:

  • -pxxxx,不用加空格,可能跟 MySQL 命令解析命令行参数的规则有关。
  • --password=xxxx,完整参数。

-P:指定 server 的 port,默认为 3306,所以可以省略

然后输入数据库密码,就可以连接到远程 server,此时终端会等待用户输入 SQL 语句。

Welcome to the MySQL monitor.  Commands end with ; or \g.
...

mysql>

数据库查询

  • 注意:

  • 数据库语句后面需要加分号才会执行

  • 数据库的关键字不区分大小写

  • 显示数据库

mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| test_by_chenhanjie |
| bbb                |
| ccc                |
| ddd                |
+--------------------+
4 rows in set (0.00 sec)
  • 选择使用的数据库
    没有默认正在使用的数据库,所以需要先选择。
mysql> use test_by_chenhanjie;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A
  • 表查询
mysql> show tables;
+------------------------------+
| Tables_in_test_by_chenhanjie |
+------------------------------+
| persons                      |
| scores                       |
| sss                          |
| test                         |
+------------------------------+

数据库操作

  • 新建数据库
mysql> create database test_by_chenhanjie;
Query OK, 1 row affected (0.03 sec)
  • 新建表
mysql> create table test ( id int, score int, info text);

此处省略一万字。。。

参考资料

参考资料不是放在最后的吗,怎么出了个大标题?
因为一个一个讲命令没有太大意义,不如系统看下文档。我看的是 w3schools 的教程,顺便强迫自己看英文。

https://www.w3schools.com/sql/default.asp

应该关注的点

  • 数据库组成
    一个数据库服务(RDBMS)包含多个数据库(Database);一个数据库由多个表(Table)组成;一个表由多个记录(Record)组成,一个记录为一行(Row);一个记录有多个字段(Field),一个表中的同一个字段为一列(Row)。

  • SQL 语法
    SQL 语法的关键在于理解关键字和关键字的用法,关键字是基础,对一个 SQL 语句的理解是思维的挑战和提示。

  • DML 和 DDL
    DML 对数据库中的数据进行增删改查操作;DDL 对数据库和表进行操作,用于定义数据库

  • 数据类型
    数据类型是表中字段存储的数据类型,如INT TEXT等。

  • SQL 函数
    SQL 内置的函数可以用于数据的计算,如COUNT NOW等。

  • 数据筛选
    数据条件筛选是语句正确执行的关键。

  • 操作符
    包括算术操作符,位操作符,比较操作符,复合操作符(Compound Operators)和逻辑操作符。

  • 难点
    难点是学习中可能带来疑惑的地方,关键在于理解和实践。我总结的几个点:

  • 联合(Join)

  • 条件查询(Condition Query)

  • 别名(Aliases)

  • 主键(Primary Key)

  • 外键(Foreign Key)

  • 排序(Order)

  • 分组(Group)

  • 空(Null)

  • 操作符(Operator)

  • 索引(Index)
    索引可以提高查询速度,但是会减低修改(增删改)的效率,因为需要同时修改索引。

专业名词

SQL(Structured Query Language)
DML(Database Modify Language)
DDL (Database Define Language)
ANSI(American National Standards Institute)
RDBMS(Relational Database Management System)
ESC (escend)
DESC (descend)

后记

耗时两天,学到很多东西,欢迎批评指正。

EOF

浏览器的全屏功能小结

浏览器的全屏功能小结

背景

浏览器页面在视频播放, 图片浏览, 编辑文本等场景, 会遇到增大页面的可查看和可交互区域的需求.

对于这种场景, 一般做法是提供一个全屏按钮, 用户可以选择点击按钮触发全屏.

全屏场景-文本
全屏场景-视频

本文对实现浏览器全屏进行简单总结:

  • 伪全屏, 浏览器全屏和元素全屏
  • 全屏相关快捷键
  • 元素全屏的兼容性和浏览器差异

伪全屏, 浏览器全屏和元素全屏

在了解全屏功能之前, 先了解三个全屏相关概念: 伪全屏, 浏览器全屏元素全屏.

伪全屏

保持页面大小, 将页面中的次要的内容隐藏起来, 把页面空间让给需要突出给用户的内容.
伪全屏并没有改变页面在显示器中的展示面积, 只是优化了页面呈现的内容, 所以称为 伪全屏.

伪全屏示例

伪全屏是完全由代码控制实现, 不会有兼容性问题.

浏览器全屏

浏览器全屏的展示

浏览器全屏时浏览器铺满显示器窗口, 保留标签栏, 地址栏等浏览器组件.

如chrome浏览器全屏:
chrome浏览器全屏

浏览器全屏是系统窗口全屏

浏览器全屏通过浏览器菜单或浏览器快捷键触发. 浏览器全屏是操作系统的窗口全屏在浏览器上的实现, 其他桌面软件一般也支持窗口全屏.

  • IE11浏览器非全屏
    浏览器非全屏

  • IE11浏览器窗口全屏
    浏览器全屏

  • 资源管理器非全屏
    资源管理器非全屏

  • 资源管理器窗口全屏
    资源管理器全屏

浏览器全屏快捷键

  • mac: control + command + F 切换浏览器全屏
  • win: F11 切换浏览器全屏

元素全屏

元素全屏的展示

元素全屏的元素会铺满显示器全屏, 并将浏览器本身的窗口栏, 标签栏和地址栏都收起来.

  • 腾讯视频网页元素全屏
    腾讯视频全屏

  • 腾讯视频非全屏
    腾讯视频非全屏

元素全屏由代码控制

元素全屏是浏览器实现的页面全屏能力, 由 js 代码控制页面中的某个元素进行全屏展示. 相关文档可以看 Guide to the Fullscreen API.

比如: 腾讯视频播放时的全屏, 是对 <video /> 的祖先元素进行了全屏操作.

  • 腾讯视频video元素位置
    腾讯视频video元素

  • 全屏播放时的全屏元素为 video 的祖先元素
    腾讯视频video祖先元素全屏

元素全屏能力测试

元素全屏能力的浏览器兼容性可以查看官方文档: Fullscreen API.

浏览器元素全屏能力测试: 可以使用元素全屏库 fscreen 的测试页面: http://fscreen.rafrex.com/.

fscreen测试页面

元素全屏快捷键

  • mac/win Esc 退出元素全屏
  • 没有自带的触发元素全屏快捷键

各浏览器的全屏差异

测试了 mac 和 windows 系统下的 5 款浏览器.

  • chrome
  • firefox
  • Edge (windows)
  • IE11 (windows)
  • safari (mac)

Esc 快捷键

  • 浏览器全屏下, 按 Esc 不会退出全屏, 会触发 Esc 键盘事件. safari 会退出浏览器全屏并触发事件, 需要针对 safari 做 preventDefault.
  • 元素全屏下, 按 Esc 按键会退出元素全屏, 不会触发 Esc 的键盘事件.

触发元素全屏, 部分浏览器会出现提示

这些提示是浏览器行为, 无法通过 js 代码禁止.

  • chrome 会提示 "press Esc to exit full screen"
    chrome元素全屏提示

  • firefox 会提示 "rafrex.com is now full screen"
    firefox元素全屏提示

  • IE11会提示"Do you want to view rafrex.com in full screen?...", 可以选择"Allow once", "Always allow", "Deny once"
    IE11元素全屏提示

  • Edge 会提示"fscreen.rafrex.com switched to full screen(Esc to exit)."
    edge元素全屏提示

特定浏览器问题

firefox

触发元素全屏和退出元素全屏后, firefox 会出现1秒左右的显示器黑屏. fscreen 的测试代码, youtube 的视频全屏播放都存在这个问题.
firefox元素全屏1秒黑屏

该问题 mac 和 win 都存在. 相关讨论: Black screen for a second when going fullscreen on videos.

safari

mac safari 打开调试工具情况下, 触发浏览器全屏时, 调试工具会黑掉.

IE11

IE11 的元素全屏只能由用户操作触发, 不能由键盘事件触发. 相关讨论: IE11 fullscreen triggered from keyboard. 键盘事件触发元素全屏时, 会触发onfullscreenerror(MSFullscreenError) 事件.

QQ浏览器

QQ浏览器劫持全屏快捷键F11后, 第三次点击F11没有进入键盘事件回调, 直接触发浏览器全屏. 如果第三次点击F11前, 鼠标点击了页面元素, 则问题不会发生.

怀疑是浏览器的坑. 最简测试代码:

let isFullscreen = false;
window.addEventListener('keydown',
function(e) {
    console.log('test:', 'keydown', e.key, e.keyCode);
    if (e.keyCode === 122) {
        console.log('test:', 'F11 clicked');
        e.preventDefault();
        if (isFullscreen) {
            console.log('test:', 'exit fullscreen');
            document.webkitExitFullscreen();
        } else {
            console.log('test:', 'enter fullscreen');
            document.body.webkitRequestFullscreen();
        }
        isFullscreen = !isFullscreen;
    }
})
// win7, QQ浏览器10.2(1893), 内核模式: 使用智能内核模式

IE10

IE10 不支持元素全屏, 可以模拟发送键盘事件触发浏览器全屏. 没实试, 相关讨论: Internet Explorer full screen mode.

总结

  • 浏览器全屏只能由用户触发.
  • 元素全屏由代码触发使元素铺满显示器页面, 要考虑兼容性和浏览器行为差异.
  • 伪全屏不改变页面大小, 优化内容展示, 没有兼容性问题.

References

EOF

Node.js 应用场景预研

背景

公司大前端团队准备上 Node 了,激动人心.所以首先需要预研一下 Node 的应用场景和能力范围,探索 Node 生态跟业务需求结合起来的方式.

Node.js

Node 是基于 V8 JavaScript 引擎的 JavaScript 执行器. Node 采用事件驱动和非阻塞 I/O 模型达到轻量和高效,并拥有庞大的模块包生态 -- npm.

技术特点

  • 单线程 JS 执行器
  • 事件驱动
  • 非阻塞的异步 I/O
    IO操作: 数据库操作(连接数,机器性能等)、缓存服务、网络IO、文件读写等

应用特点

  • 轻量高效
  • 强大的生态
  • 可伸缩

业界观点

  • Node 在高并发轻 CPU 场景性能比 Java 和 PHP 好.

  • 一门编程语言的特性和性能会趋向一致,剩下的就看选择.

  • 阿里的很多 Node 服务,并不是不能用 Java 做,而是前端工程师在自己能力范围内把事情做了,而且做得蛮好的. -- Winter 在 TFC 致词,我听到的大意

  • 使用 Node 技术栈,小团队可以更快把事情做成.

  • 需要一名 Node工程师时,在2年 PHPer 和 2年 FE 间会毫不犹豫选择 PHPer. -- 说明写 Node 服务端, JS 语言不是重点,服务端开发的经验和能力才是

应用场景

基本功能: Web 服务器

Node 作为 Web 服务器,连接后台数据库/文件系统,接受网络请求,处理业务逻辑,提供静态资源和业务接口.

  • 适用于 IO 使用率较重,CPU 使用率较轻的场景
  • 可以快速开发出可用服务器

前后端分离

概念

在服务器(Java/PHP)与浏览器(JS)的中间架一个 Node 中间层.Node 中间层提供直出,接口转发,静态资源等,属于前端范畴,由前端维护.

特点

  • 不止是前后端物理层的分离,而是前后端职能的分离.MVC 模型中,由前端来接管 VC,后端专注于 M,职责更加分明,关注点分离.
  • 解决性能问题: 前端可以根据业务场景使用服务端直出和接口聚合等技术,减少客户端白屏时间.
  • 将前端公用业务和逻辑后移,减少多端的重复开发.
  • 复用前端模板,逻辑,路由.

注意区分

  • 许多公司有历史包袱,使用 Java 或 PHP 做了页面直出,所以他们的前后端分离,指的是借助 Node 隔离前后端的边界,分离前后端项目的源码/开发流程/部署方式
  • 对于没有直出的公司,后台 Java 接口 + 前端异步调用,已经算前后端分离,但是没有直出能力
  • 前后端分离后还要直出,需要 Node

直出

概念

在前后端接口分离(前后端解耦)情况下,在客户端访问页面时,服务器拉取接口数据,并将数据嵌入页面返回,称为直出.如果返回的是数据和模板,在前端渲染,称为数据直出;如果后端根据数据渲染出页面,直接返回页面,称为页面直出.

直出优点

  • 直接展示带数据的页面,减少首屏加载时间
  • 直出服务器与后台数据服务器传输高效,有效减少客户网络不稳定带来的等待
  • 有利于搜索引擎爬取 (SEO)
  • 静态页面生成

Node 直出优点

  • 完全的前后端分离
    这点前文已经有阐述.
  • 复杂页面直出
    对于复杂的页面,通常需要代码模块化和功能组件化,前端 MVVM 框架适合组件化直出的场景.
  • 同构
    借助前端 MVVM 框架,前端工程可以实现同构,同时支持服务端渲染和客户端加载.

接口聚合

Node 中间层代理后台接口,后台接口提供原子 RESTful 接口,前端根据需要组合使用.Node 调用内部接口使用 HTTPS/RPC 等.
参考: 接口聚合的简单研究

微服务

关注点在于 Node 的开发和部署方式

  • 应用程序分解为更小、完全独立的组件,具有可伸缩性和可用性
  • 基于 Docker 等技术自动化 Node 的持续集成和部署

数据持久化

关注 Node 层数据的落地,作为其他 Node 层功能的基础支撑.

业界实践

阿里

  • 超大规模超高性能业务开发
    • 页面渲染直出,天猫首页等
    • 内部运营平台生成活动页面(页面搭建平台), H5 和 RN 两个版本的模板
  • Node Web 框架 -- egg.js,阿里后端首选底层框架
  • alinode 提供 Node 日志和监控
  • 数据

腾讯

百度

美团

其他公司

  • 饿了么/知乎/大搜车等都在用

国外

What companies are using Node.js in production
node.js能开发大型网站吗 -- 国外使用情况

我司的实践点

现有条件

  • 我司没有历史包袱
    我们没有直出服务,没有 Node 服务,所以在提升性能,提升用户体验,降低开发成本上,基本可以无痛选择 Node 技术栈.

  • 我司没有积累

    • Node 服务器开发和运维并不简单,无线团队和运维团队并没有较好的技术积累.
    • 我司的业务场景需要考虑更多安全问题.
    • Node 服务层提供接口中转时,对后台接口的配置有一些要求.

为什么是 Node

  • 提升用户体验和服务器能力 -- 公司对外业务的需要
  • 人人都会配置前端页面 -- 公司运营/开发提升效率的需要
  • 这个事情后端没人管,前端来管;这个事情前端应该管,不需要动用后端 -- 团队间协作的需要
  • 提升前端开发效率,减少重复性劳动 -- 无线开发团队内部的需要

可实践工程

Node 服务层的能力是非常强大的,结合我司业务,可以实践的工程还是很多的.以下为头脑风暴结果,具体开展需要做可行性分析,需求评审,架构规划,功能划分,开发计划等等工作.

页面配置平台

  • 活动链接配置
    如: 对一个活动页面,生成一个 url 入口,外部服务统一调用该入口.配置平台设置映射规则:起止时间,省市区域,渠道等.活动的上下线和切换不需要开发改动发版本.
  • 活动页面生成
    简单可组合的页面搭建平台,生成活动页面.
  • 静态页面配置
    如:我们的帮助中心,每次文本改动需要前端开发,使用静态页面配置平台,可以有产品人员进行文本修改,然后由直接生成.

接口中间层

客户端对接口的访问,先经过 Node 接口中间层,由中间层代理接口服务.

  • 接口聚合
    如: 对于用户状态获取/商城等展示前需要请求多个接口的页面,可以使用接口聚合.后端只需要提供原子接口,而聚合由前端根据业务场景开发.
  • 大文件上传
    如: 用户申请验证,目前的趋势是使用拍照或活体,可能会上传视频,在不好的网络条件下,需要分片和断点续传,在 Node 层可以制定接口获取大文件,再内网提交给后端.甚至可以考虑使用 Web Socket.

服务端直出

微信商城和 App 的手机专区已经使用了服务端直出,可以保持关注和迭代.

其他场景

  • 图片服务,在不同的客户端环境中返回不同大小/格式的图片
  • 合同服务,在合同模板嵌入用户信息,渲染成图片返回前端

技术选型

服务端部署

  • 部署和运维
    • 云主机
    • 如何负载均衡和伸缩
    • nginx
    • cdn
  • Node 版本 Node v6.11.1 LTS / alinode(收费)
  • JavaScript ES6
  • 服务器框架 egg.js
  • 数据落地 mongoDB / mySQL / redis
  • 模板引擎 Jade 等,这里不是前端框架

开发注意点

  • 技术架构:各个服务的边界和交互方式
  • 理解异步
  • 多进程 cluster
  • 异常处理和重启
  • 数据库操作
  • 调试
  • 性能/GC/内存
  • 服务器开发的各技术点
  • 渐进式开发部署,灰度

Node 入门

References

EOF

OpenResty 替换 Nginx

背景

OpenRestyNginx 的一个 bundle, 添加了许多拓展模块来增强 Nginx 的功能, 其亮点是支持 Lua 脚本, 大大提升了 Nginx 的请求处理能力并降低了配置难度.

刚好有个需求需要 Lua 脚本才能支持, 所以在 Mac 下使用 OpenResty 替换 Nginx.

Bundle vs Fork

业界有蛮多对 Nginx 的拓展, 主要的拓展方式有 bundle 和 fork.

  • Bundle

Bundle 是基于 Nginx core, 默认提供一些 Nginx 的组件来增强 Nginx 的功能. 如 OpenResty. Bundle 有突出的好处是可以方便地升级 Nginx core, 保持和官方 Nginx 的同步.

  • Fork

Fork 是基于某个版本的 Nginx 做二次开发, 实现各种 Nginx 不支持的功能. 如淘宝的 Tengine. Fork 的好处是提供了 Nginx 及 Nginx 插件无法实现的功能, 但是无法较好保持与官方 Nginx 的同步.

安装和启动 OpenResty

参考官方安装教程, 使用 brew 进行安装:

$ brew install openresty/brew/openresty

如果 Nginx 在运行则先停止 Nginx:

$ ps aux | grep nginx
nobody           12129   0.0  0.0  2503864      8   ??  S     5:58PM   0:00.01 nginx: worker process  
root                51   0.0  0.0  2502840      8   ??  Ss   Thu06PM   0:00.07 nginx: master process /usr/local/opt/nginx/bin/nginx -g daemon off;

$ sudo kill -9 51

启动 OpenResty:

$ sudo openresty

替换 nginx 命令

安装完毕后查看安装路径:

$ which openresty
/usr/local/bin/openresty
$ ls -al /usr/local/bin/openresty
lrwxr-xr-x  1 cntchen  admin  42 Jan  3 09:27 /usr/local/bin/openresty -> ../Cellar/openresty/1.13.6.1/bin/openresty

查看 nginx 命令路径:

$ which nginx
/usr/local/bin/nginx
$ ls -al /usr/local/bin/nginx
lrwxr-xr-x  1 cntchen  admin  32 Oct 25 17:25 /usr/local/bin/nginx -> ../Cellar/nginx/1.12.2/bin/nginx

nginx 命令给 OpenResty 使用:

# 保留 nginx 服务命令
$ mv nginx nginx-origin
$ rm nginx
$ ln -s ../Cellar/openresty/1.13.6.1/bin/openresty nginx

注意: 如果使用 brew upgrate 对 OpenResty 或 Nginx 进行升级, 会生成指向新版本的默认软链, 所以升级后需要手动处理一下.

对 OpenResty 进行配置

OpenResty 配置路径 /usr/local/etc/openresty.

Nginx 配置路径 /usr/local/etc/nginx.

将现有的 nginx 配置复制到 openresty 目录. 然后 reload OpenResty:

$ nginx -s reload

访问页面查看是否生效.

References

  • OpenResty

https://openresty.org/en/

  • Nginx

http://nginx.org/en/

  • Tengine

http://tengine.taobao.org/index_cn.html

  • Supercharge your Nginx with Openresty and Lua

https://dchua.com/2017/01/01/supercharge-your-nginx-with-openresty-and-lua/

EOF

Web Worker 文献综述

Title Image

Web Worker 文献综述

Web Worker 作为浏览器多线程技术, 在页面内容不断丰富, 功能日趋复杂的当下, 成为缓解页面卡顿, 提升应用性能的可选方案.

但她的容颜, 隐藏在边缘试探的科普文章和不知深浅的兼容性背后; 对 JS 单线程面试题倒背如流的前端工程师, 对多线程开发有着天然的陌生感.

图片来源

背景

文献综述

文献综述(Literature Review)是学术研究领域一个常见概念, 写过毕业论文的同学应该还有印象. 它向读者介绍与主题有关的详细资料、动态、进展、展望以及对以上方面的评述.

近期笔者关注 Web Worker, 并落地到了大型复杂前端项目. 开源了 Worker 通信框架 alloy-worker, 正在写实践总结文章. 其间查阅了相关资料(50+文章, 10+技术演讲), 独立写成这篇综述性文章.

主要内容

发展历史

简介

前端同学对 Web Worker 应该不陌生, 即使没有动手实践过, 应该也在社区上看过相关文章. 在介绍和使用上, 官方文档是 MDN 的 Web Workers API. 其对 Web Worker 的表述是:

Web Workers makes it possible to run a script operation in a background thread separate from the main execution thread of a web application.

如下图所示, Web Worker 实现了多线程运行 JS 能力. 之前页面更新要先串行(Serial) 做 2 件事情; 使用 Worker 后, 2 件事情可并行(Parallel) 完成.

图片来源

可以直观地联想: 并行可能会提升执行效率; 运行任务拆分能减少页面卡顿. 后面应用场景章节将继续讨论.

技术规范

Web Worker 属于 HTML 规范, 规范文档见 Web Workers Working Draft, 有兴趣的同学可以读一读. 而它并不是很新的技术, 如下图所示: 2009 年就提出了草案.

图片来源

同年在 FireFox3.5 上率先实现, 可以在 using web workers: working smarter, not harder 中看到早期的实践. 2012年发布的 IE10 也实现了 Web Worker, 标志着主流浏览器上的全面支持. IE10 的 Web Worker 能力测试如下图所示:

图片来源

在预研 Worker 方案时, 开发人员会有兼容性顾虑. 这种顾虑的普遍存在, 主要由于业界 Worker 技术实践较少和社区推广不活跃. 单从发展历史看, Worker 从 2012 年起就广泛可用; 后面兼容性章节将继续讨论.

DedicatedWorker 和 SharedWorker

Web Worker 规范中包括: DedicatedWorkerSharedWorker; 规范并不包括 Service Worker, 本文也不会展开讨论.

图片来源

如上图所示, DedicatedWorker 简称 Worker, 其线程只能与一个页面渲染进程(Render Process)进行绑定和通信, 不能多 Tab 共享. DedicatedWorker 是最早实现并最广泛支持的 Web Worker 能力.

而 SharedWorker 可以在多个浏览器 Tab 中访问到同一个 Worker 实例, 实现多 Tab 共享数据, 共享 webSocket 连接等. 看起来很美好, 但 safari 放弃了 SharedWorker 支持, 因为 webkit 引擎的技术原因. 如下图所示, 只在 safari 5~6 中短暂支持过.

图片来源

社区也在讨论 是否继续支持 SharedWorker; 多 Tab 共享资源的需求建议在 Service Worker 上寻找方案.

相比之下, DedicatedWorker 有着更广的兼容性和更多业务落地实践, 本文后面讨论中的 Worker 都是特指 DedicatedWorker.

主线程和多线程

用户使用浏览器一般会打开多个页面(多 Tab), 现代浏览器使用单独的进程(Render Process)渲染每个页面, 以提升页面性能和稳定性, 并进行操作系统级别的内存隔离.

图片来源

主线程(Main Thread)

页面内, 内容渲染和用户交互主要由 Render Process 中的主线程进行管理. 主线程渲染页面每一帧(Frame), 如下图所示, 会包含 5 个步骤: JavaScript → Style → Layout → Paint → Composite, 如果 JS 的执行修改了 DOM, 可能还会暂停 JS, 插入并执行 Style 和 Layout.

图片来源

而我们熟知的 JS 单线程和 Event Loop, 是主线程的一部分. JS 单线程执行避免了多线程开发中的复杂场景(如竞态和死锁). 但单线程的主要困扰是: 主线程同步 JS 执行耗时过久时(浏览器理想帧间隔约 16ms), 会阻塞用户交互和页面渲染.

图片来源

如上图所示, 长耗时任务执行时, 页面将无法更新, 也无法响应用户的输入/点击/滚动等操作. 如果卡死太久, 浏览器可能会抛出卡顿的提示. 如下图所示.

  • Chrome81

  • IE11

多线程

Web Worker 会创建操作系统级别的线程.

The Worker interface spawns real OS-level threads. -- MDN

JS 多线程, 是有独立于主线程的 JS 运行环境. 如下图所示: Worker 线程有独立的内存空间, Message Queue, Event Loop, Call Stack 等, 线程间通过 postMessage 通信.

多个线程可以并发运行 JS. 熟悉 JS 异步编程的同学可能会说, setTimeout / Promise.all 不就是并发吗, 我写得可溜了.

JS 单线程中的"并发", 准确来说是 Concurrent. 如下图所示, 运行时只有一个函数调用栈, 通过 Event Loop 实现不同 Task 的上下文切换(Context Switch). 这些 Task 通过 BOM API 调起其他线程为主线程工作, 但回调函数代码逻辑依然由 JS 串行运行.

Web Worker 是 JS 多线程运行技术, 准确来说是 Parallel. 其与 Concurrent 的区别如下图所示: Parallel 有多个函数调用栈, 每个函数调用栈可以独立运行 Task, 互不干扰.

应用场景

讨论完主线程和多线程, 我们能更好地理解 Worker 多线程的应用场景:

  • 可以减少主线程卡顿.
  • 可能会带来性能提升.

减少卡顿

根据 Chrome 团队提出的用户感知性能模型 RAIL, 同步 JS 执行时间不能过长. 量化来说, 播放动画时建议小于 16ms, 用户操作响应建议小于 100ms, 页面打开到开始呈现内容建议小于 1000ms.

逻辑异步化

减少主线程卡顿的主要方法为异步化执行, 比如播放动画时, 将同步任务拆分为多个小于 16ms 的子任务, 然后在页面每一帧前通过 requestAnimationFrame 按计划执行一个子任务, 直到全部子任务执行完毕.

图片来源

拆分同步逻辑的异步方案对大部分场景有效果, 但并不是一劳永逸的银弹. 有以下几个问题:

  • 不是所有 JS 逻辑都可拆分. 比如数组排序, 树的递归查找, 图像处理算法等, 执行中需要维护当前状态, 且调用上非线性, 无法轻易地拆分为子任务.

  • 可以拆分的逻辑难以把控粒度. 如下图所示, 拆分的子任务在高性能机器(iphoneX)上可以控制在 16ms 内, 但在性能落后机器(iphone6)上就超过了 deadline. 16ms 的用户感知时间, 并不会因为用户手上机器的差别而变化, Google 给出的建议是再拆小到 3-4ms.

图片来源

  • 拆分的子任务并不稳定. 对同步 JS 逻辑的拆分, 需要根据业务场景寻找原子逻辑, 而原子逻辑会跟随业务变化, 每次改动业务都需要去 review 原子逻辑.

Worker 一步到位

Worker 的多线程能力, 使得同步 JS 任务的拆分一步到位: 从宏观上将整个同步 JS 任务异步化. 不需要再去苦苦寻找原子逻辑, 逻辑异步化的设计上也更加简单和可维护.

这给我们带来更多的想象空间. 如下图所示, 在浏览器主线程渲染周期内, 将可能阻塞页面渲染的 JS 运行任务(Jank Job)迁移到 Worker 线程中, 进而减少主线程的负担, 缩短渲染间隔, 减少页面卡顿.

性能提升

Worker 多线程并不会直接带来计算性能的提升, 能否提升与设备 CPU 核数和线程策略有关.

多线程与 CPU 核数

CPU 的单核(Single Core)和多核(Multi Core)离前端似乎有点远了. 但在页面上运用多线程技术时, 核数会影响线程创建策略.

进程是操作系统资源分配的基本单位,线程是操作系统调度 CPU 的基本单位. 操作系统对线程能占用的 CPU 计算资源有复杂的分配策略. 如下图所示:

  • 单核多线程通过时间切片交替执行.
  • 多核多线程可在不同核中真正并行.

Worker 线程策略

一台设备上相同任务在各线程中运行耗时是一样的. 如下图所示: 我们将主线程 JS 任务交给新建的 Worker 线程, 任务在 Worker 线程上运行并不会比原本主线程更快, 而线程新建消耗和通信开销使得渲染间隔可能变得更久.

图片来源

在单核机器上, 计算资源是内卷的, 新建的 Worker 线程并不能为页面争取到更多的计算资源. 在多核机器上, 新建的 Worker 线程和主线程都能做运算, 页面总计算资源增多, 但对单次任务来说, 在哪个线程上运行耗时是一样的.

真正带来性能提升的是多核多线程并发.

如多个没有依赖关系的同步任务, 在单线程上只能串行执行, 在多核多线程中可以并行执行. 如下图 alloy-worker图像处理 demo 所示, 在 iMac 上运行时创建了 6 条 Worker 线程, 图像处理总时间比主线程串行处理快了约 2000ms.

值得注意的是, 目前移动设备的核心数有限. 最新 iPhone Max Pro 上搭载的 A13 芯片 号称 6 核, 也只有 2 个高性能核芯(2.61G), 另外 4 个是低频率的能效核心(0.58G). 所以在创建多条 Worker 线程时, 建议区分场景和设备.

把主线程还给 UI

Worker 的应用场景, 本质上是从主线程中剥离逻辑, 让主线程专注于 UI 渲染. 这种架构设计并非 Web 技术上的独创.

Android 和 iOS 的原生开发中, 主线程负责 UI 工作; 前端领域热门的小程序, 实现原理上就是渲染和逻辑的完全分离.

本该如此.

Worker API

通信 API

如上图所示的 Worker 通信流程, Worker 通信 API 非常简单. 通俗中文教程可以参考 Web Worker 使用教程. 使用细节建议看官方文档.

双向通信示例代码如下图所示, 双向通信只需 7 行代码.

主要流程为:

  1. 主线程调用 new Worker(url) 创建 Worker 实例, url 为 Worker JS 资源 url.
  2. 主线程调用 postMessage 发送 hello, 在 onmesssage 中监听 Worker 线程消息.
  3. Worker 线程在 onmessage 中监听主线程消息, 收到主线程的 hello; 通过 postMessage 回复 world.
  4. 主线程在消息回调中收到 Worker 的 world 信息.

postMessage 会在接收线程创建一个 MessageEvent, 传递的数据添加到 event.data, 再触发该事件; MessageEvent 的回调函数进入 Message Queue, 成为待执行的宏任务. 因此 postMessage 顺序发送的信息, 在接收线程中会顺序执行回调函数. 而且我们无需担心实例化 Worker 过程中 postMessage 的信息丢失问题, 对此 Worker 内部机制已经处理.

Worker 事件驱动(postMessage/onmessage) 的通信 API 虽然简洁, 但大多数场景下通信需要等待响应(类似 HTTP 请求的 Request 和 Response), 并且多次同类型通信要匹配到各自的响应. 所以业务使用一般会封装原生 API, 如封装为 Promise 调用. 这也是笔者开发 alloy-worker 的原由之一.

运行环境

在 Worker 线程中运行 JS, 会创建独立于主线程的 JS 运行环境, 称之为 DedicatedWorkerGlobalScope. 开发者需关注 Worker 环境和主线程环境的异同, 以及 Worker 在不同浏览器上的差异.

Worker 环境和主线程环境的异同

Worker 是无 UI 的线程, 无法调用 UI 相关的 DOM/BOM API. Worker 具体支持的 API 可参考 MDN 的 functions and classes available to workers.

图片来源

上图展示了 Worker 线程与主线程的异同点. Worker 运行环境与主线程的共同点主要包括:

  • 包含完整的 JS 运行时, 支持 ECMAScript 规范定义的语言语法和内置对象.
  • 支持 XmlHttpRequest, 能独立发送网络请求与后台交互.
  • 包含只读的 Location, 指向 Worker 线程执行的 script url, 可通过 url 传递参数给 Worker 环境.
  • 包含只读的 Navigator, 用于获取浏览器信息, 如通过 Navigator.userAgent 识别浏览器.
  • 支持 setTimeout / setInterval 计时器, 可用于实现异步逻辑.
  • 支持 WebSocket 进行网络 I/O; 支持 IndexedDB 进行文件 I/O.

从共同点上看, Worker 线程其实很强大, 除了利用独立线程执行重度逻辑外, 其网络 I/O 和文件 I/O 能力给业务和技术方案带来很大的想象空间.

另一方面, Worker 线程运行环境和主线程的差异点有:

  • Worker 线程没有 DOM API, 无法新建和操作 DOM; 也无法访问到主线程的 DOM Element.
  • Worker 线程和主线程间内存独立, Worker 线程无法访问页面上的全局变量(window, document 等)和 JS 函数.
  • Worker 线程不能调用 alert() 或 confirm() 等 UI 相关的 BOM API.
  • Worker 线程被主线程控制, 主线程可以新建和销毁 Worker.
  • Worker 线程可以通过 self.close 自行销毁.

从差异点上看, Worker 线程无法染指 UI, 并受主线程控制, 适合默默干活.

Worker 在不同浏览器上的差异

各家浏览器实现 Worker 规范有差异, 对比主线程, 部分 API 功能不完备, 如:

  • IE10 发送的 AJAX 请求没有 referer, 请求可能被后台服务器拒绝.
  • Edge18 上字符编码/ Buffer 的实现有问题.

好在这种场景并不多. 并且可以在运行时通过错误监控发现问题, 并定位和修复(polyfill).

另一方面, 一些新增的 HTML 规范 API 只在较新的浏览器上实现, Worker 运行环境甚至主线程上没有, 使用 Worker 时需判断和兼容.

多线程同构代码

Worker 线程不支持 DOM, 这点和 Node.js 非常像. 我们在 Node.js 上做前后端同构的 SSR 时, 经常会遇到调用 BOM/DOM API 导致的报错. 如下图所示:

在开发 Worker 前端项目或迁移已有业务代码到 Worker 中时, 同构代码比例可能很高, 容易调到 BOM/DOM API. 可以通过构建变量区分代码逻辑, 或运行时动态判断所在线程, 实现同构代码在不同线程环境下运行.

通信速度

Worker 多线程虽然实现了 JS 任务的并行运行, 也带来额外的通信开销. 如下图所示, 从线程A 调用 postMessage 发送数据到线程B onmessage 接收到数据有时间差, 这段时间差称为通信消耗.

图片来源

提升的性能 = 并行提升的性能 – 通信消耗的性能. 在线程计算能力固定的情况下, 要通过多线程提升更多性能, 需要尽量减少通信消耗.

而且主线程 postMessage 会占用主线程同步执行, 占用时间与数据传输方式和数据规模相关. 要避免多线程通信导致的主线程卡顿, 需选择合适的传输方式, 并控制每个渲染周期内的数据传输规模.

数据传输方式

我们先来聊聊主线程和 Worker 线程的数据传输方式. 根据计算机进程模型, 主线程和 Worker 线程属于同一进程, 可以访问和操作进程的内存空间. 但为了降低多线程并发的逻辑复杂度, 部分传输方式直接隔离了线程间的内存, 相当于默认加了锁.

通信方式有 3 种: Structured Clone, Transfer Memory 和 Shared Array Buffer.

Structured Clone

Structured Clone 是 postMessage 默认的通信方式. 如下图所示, 复制一份线程A 的 JS Object 内存给到线程B, 线程B 能获取和操作新复制的内存.

Structured Clone 通过复制内存的方式简单有效地隔离不同线程内存, 避免冲突; 且传输的 Object 数据结构很灵活. 但复制过程中, 线程A 要同步执行 Object Serialization, 线程B 要同步执行 Object Deserialization; 如果 Object 规模过大, 会占用大量的线程时间.

Transfer Memory

Transfer Memory 意为转移内存, 它不需要 Serialization/Deserialization, 能大大减少传输过程占用的线程时间. 如下图所示 , 线程A 将指定内存的所有权和操作权转给线程B, 但转让后线程A 无法再访问这块内存.

Transfer Memory 以失去控制权来换取高效传输, 通过内存独占给多线程并发加锁. 但只能转让 ArrayBuffer 等大小规整的二进制(Raw Binary)数据; 对矩阵数据(如 RGB 图片)比较适用. 实践上也要考虑从 JS Object 生成二进制数据的运算成本.

Shared Array Buffers

Shared Array Buffer 是共享内存, 线程A 和线程B 可以同时访问和操作同一块内存空间. 数据都共享了, 也就没有传输什么事了.

但多个并行的线程共享内存, 会产生竞争问题(Race Conditions). 不像前 2 种传输方式默认加锁, Shared Array Buffers 把难题抛给开发者, 开发者可以用 Atomics 来维护这块共享的内存. 作为较新的传输方式, 浏览器兼容性可想而知, 目前只有 Chrome 68+ 支持.

传输方式小结

  • 全浏览器兼容的 Structured Clone 是较好的选择, 但要考虑数据传输规模, 下文我们会详细展开.
  • Transfer Memory 的兼容性也不错(IE11+), 但数据独占和数据类型的限制, 使得它是特定场景的最优解, 不是通用解;
  • Shared Array Buffers 当下糟糕的兼容性和线程锁的开发成本, 建议先暗中观察.

JSON.stringify 更快?

使用 Structured Clone 传输数据时, 有个阴影一直笼罩着我们: postMessage 前要不要对数据 JSON.stringify 一把, 听说那样更快?

2016 年的 High-performance Web Worker messages 进行了测试, 确实如此. 但是文章的测试结果也只能停留在 2016 年. 2019 年 Surma 进行新的测试: 如下图所示, 横轴上相同的数据规模, 直接 postMessage 的传输时间普遍比 JSON.stringify 更少.

图片来源

2020 年的当下, 不需要再使用 JSON.stringify. 其一是 Structured Clone 内置的 serialize/deserialize 比 JSON.stringify 性能更高; 其二是 JSON.stringify 只适合序列化基本数据类型, 而 Structured Clone 还支持复制其他内置数据类型(如 Map, Blob, RegExp 等, 虽然大部分应用场景只用到基本数据类型).

数据传输规模

我们再来聊聊 Structured Clone 的数据传输规模. Structured Clone 的 serialize/deserialize 执行耗时主要受数据对象复杂度影响, 这很好理解, 因为 serialize/deserialize 至少要以某种方式遍历对象. 数据对象的复杂度本身难以度量, 可以用序列化后的数据规模(size)作为参考.

2015 年的 How fast are web workers中等性能手机上进行了测试: postMessage 发送数组的通信速率为 80KB/ms, 相当于理想渲染周期(16ms)内发送 1300KB.

2019 年 Surma 对 postMessage 的数据传输能力进行了更深入研究, 具体见 Is postMessage slow. 高性能机器(macbook) 上的测试结果如下图所示:

图片来源

其中:

  • 测试数据为嵌套层数 1 到 6 层(payload depth, 图中纵坐标), 每层节点的子节点 1 到 6 个(payload breadth, 图中横坐标)的对象, 数据规模从 10B 到 10MB.
  • 在 macbook 上, 10MB 的数据传递耗时 47ms, 16ms 内可以传递 1MB 级别的数据.

低性能机器(nokia2) 上的测试结果如下图所示:

图片来源

其中:

  • 在 nokia2 上传输 10MB 的数据耗时 638ms, 16ms 内可以传递 10KB 级别的数据.
  • 高性能机器和低性能机器有超过 10 倍的传输效率差距.

不管用户侧的机器性能如何, 用户对流畅的感受是一致的: 前端同学的老朋友 16ms 和 100ms. Surma 兼顾低性能机型上 postMessage 容易造成主线程卡顿, 提出的数据传输规模建议是:

  • 如果 JS 代码里面不包括动画渲染(100ms), 数据传输规模应该保持在 100KB 以下;
  • 如果 JS 代码里面包括动画渲染(16ms), 数据传输规模应该保持在 10KB 以下.

笔者认为, Surma 给出的建议偏保守, 传输规模可以再大一些.

总之, 数据传输规模并没有最佳实践. 而是充分理解 Worker postMessage 的传输成本, 在实际应用中, 根据业务场景去评估和控制数据规模.

兼容性

兼容性是前端技术方案评估中需要关注的问题. 对 Web Worker 更是如此, 因为 Worker 的多线程能力, 要么业务场景完全用不上; 要么一用就是重度依赖的基础能力.

兼容性还不错

从前文 Worker 的历史和 兼容性视图 上看, Worker 的兼容性应该挺好的.

如上图所示, 主流浏览器在几年前就支持 Worker.

PC端:

  • IE10(2012/09)
  • Chrome4(2010/01)
  • Safari4(2009)
  • Firefox3.5(2009)

移动端:

  • iOS5(2012)
  • Android4.4(2013)

可用性评估指标

使用 Worker 并不是一锤子买卖, 我们不止关注浏览器 Worker 能力的有或没有; 也关注 Worker 能力是否完备可用. 为此笔者设计了以下几个指标来评估 Worker 可用性:

  • 是否有 Worker 能力: 通过浏览器是否有 window.Worker 来判断.
  • 能否实例化 Worker: 通过监控 new Worker() 是否报错来判断.
  • 能否跨线程通信: 通过测试双向通信来验证, 并设置超时.
  • 首次通信耗时: 页面开始加载 Worker 脚本到首次通讯完成的耗时. 该指标与 JS 资源加载时长, 同步逻辑执行耗时相关.

统计数据

有了可用性评估指标, 就可以给出量化的兼容性统计数据. 你将看到的, 是开放社区上唯一一份量化数据, 2019~2020 年某大型前端项目(亿级 MAU)的统计结果(By AlloyTeam alloy-worker).

其中:

  • 有 Worker 能力的终端超过 99.91%.
  • Worker 能力完全可用的终端达到 99.58%.
  • 而且 99.58% 到 99.91% 的差距大部分由于通信超时.

小结

可见当下浏览器已经较好地支持 Worker, 只要对 0.09% 的不支持浏览器做好回退策略(如展示一个 tip), Worker 可以放心地应用到前端业务中.

调试工具用法

前端工程师对 Worker 多线程开发方式比较陌生, 对开发中的 Worker 代码调试也是如此. 本章以 Chrome 和 IE10 为例简单介绍调试工具用法. 示例页面为 https://alloyteam.github.io/alloy-worker, 感兴趣的同学可以打开页面调试一把.

Chrome 调试

Chrome 已完善支持 Worker 代码调试, 开发者面板中的调试方式与主线程 JS 一致.

Console 调试

Console Panel 中可以查看页面全部的 JS 运行环境, 并通过下拉框切换调试的当前环境. 如下图所示, 其中 top 表示主线程的 JS 运行环境, alloyWorker--test 表示 Worker 线程的 JS 运行环境.

切换到 alloyWorker--test 后, 就可以在 Worker 运行环境中执行调试代码. 如下图所示, Worker 环境的全局对象为 self, 类型为 DedicatedWorkerGlobalScope.

断点调试

Worker 断点调试方式和主线程一致: 源码中添加 debugger 标识的代码位置会作为断点. 在 Sources Panel 查看页面源码时, 如下图所示, 左侧面板展示 Worker 线程的 alloy-worker.js资源; 运行到 Worker 线程断点时, 右侧的 Threads 提示所在的运行环境是名为 alloyWorker--test 的 Worker 线程.

性能调试

使用 Performance Panel 的录制功能即可. 如下图红框所示, Performance 中也记录了 Worker 线程的运行情况.

查看内存占用

Worker 的使用场景偏向数据和运算, 开发中适时回顾 Worker 线程的内存占用, 避免内存泄露干扰整个 Render Process. 如下图所示, 在 Memory Panel 中 alloyWorker-test 线程占用的内存为 1.2M.

IE10 调试

在比较极端的情况下, 我们需要到 IE10 这种老旧的浏览器上定位代码兼容性问题. 好在 IE10 也支持 Worker 源码调试. 可以参考微软官方文档, 具体步骤为:

  • F12 打开调试工具, 在 Script Panel 中, 开始是看不到 Worker 线程源码的, 点击 Start debugging, 就能看到 Worker 线程的 alloy-worker.js 源码.

  • 在 Worker 源码上打断点, 就能进行调试.

数据流调试

跨线程通信数据流是开发和调试中比较复杂的部分. 因为页面上可能有多个 Worker 实例; Worker 实例上有不同的数据类型(payload); 而且相同类型的通信可能会多次发起.

通过 onmessage 回调打 log 调试数据流时, 建议添加当前 Worker 实例名称, 通信类型, 通信负载等信息. 以 alloy-worker 调试模式的 log 为例:

如上图所示:

  • 每行信息包括: 线程名称, [时间戳, 会话 Id, 事务类型, 事务负载].
  • 绿色的向下箭头(⬇)表示 Worker 线程收到的信息.
  • 粉红的向上箭头(⬆)表示 Worker 线程发出的信息.

社区配套工具

现代化前端开发都采用模块化的方式组织代码, 使用 Web Worker 需将模块源码构建为单一资源(worker.js). 另一方面, Worker 原生的 postMessage/onmessage 通信 API 在使用上并不顺手, 复杂场景下往往需要进行通信封装和数据约定.

因此, 开源社区提供了相关的配套工具, 主解决 2 个关键问题:

  • Worker 代码打包. 将模块化的多个文件, 打包为单一 JS 资源.
  • Worker 通信封装. 封装多线程通信, 简化调用; 或约定通信负载的数据格式.

下面介绍社区的一些主要工具, star 数统计时间为 2020.06.

worker-loader (1.1k star)

Webpack 官方的 Worker loader. 负责将 Worker 源码打包为单个 chunk; chunk 可以是独立文件, 或 inline 的 Blob 资源.
输出内嵌 new Worker() 的 function, 通过调用该 function 实例化 Worker.

但 worker-loader 没有提供构建后的 Worker 资源 url, 上层业务进行定制有困难. 已有相关 issue 讨论该问题; worker-loader 也不对通信方式做额外处理.

worker-plugin (1.6k star)

GoogleChromeLabs 提供的 Webpack 构建 plugin.

作为 plugin, 支持 Worker 和 SharedWorker 的构建. 无需入侵源码, 通过解析源码中 new Workernew SharedWorker 语法, 自动完成 JS 资源的构建打包. 也提供 loader 功能: 打包资源并且返回资源 url, 这点比 worker-loader 有优势.

comlink (6.2k star)

也来自 GoogleChromeLabs 团队, 由 Surma 开发. 基于 ES6 的 Proxy 能力, 对 postMessage 进行 RPC
(Remote Procedure Call) 封装, 将跨线程的函数调用封装为 Promise 调用.

但它不涉及 Worker 资源构建打包, 需要其他配套工具. 且 Proxy 在部分浏览器中需要 polyfill, 可 polyfill 程度存疑.

workerize-loader (1.7k star)

目前社区比较完整, 且兼容性好的方案.

类似 worker-loader + comlink 的合体. 但不是基于 Proxy, 而在构建时根据源码 AST 提取出调用函数名称, 在另一线程内置同名函数; 封装跨线程函数为 RPC 调用.

与 workerize-loader 关联的另一个项目是 workerize (3.8k star). 支持手写文本函数, 内部封装为 RPC; 但手写文本函数实用性不强.

userWorker (1.8k star)

很有趣的项目, 将 Worker 封装为 React Hook. 基本原理是: 将传入 Hook 的函数处理为 BlobUrl 去实例化 Worker. 因为会把函数转为 BlobUrl 的字符串形式, 限制了函数不能有外部依赖, 函数体中也不能调用其他函数.

比较适合一次性使用的纯函数, 函数复杂度受限.

其他可参考项目

现有工具缺陷

现有的社区工具解决了 Worker 技术应用上的一些难点, 但目前还有些不足:

  • Web Worker 并不是 100% 可用的, 社区工具并没有给出回退方案.
  • 对大规模使用的场景, 代码的组织架构和构建方式并没较好的方案.
  • 部分工具在通信数据约定上缺乏强约束, 可能导致运行时意外的错误.
  • 支持 TypeScript 源码的较少, 编辑器中的函数提示也有障碍.

以上不足促使笔者开源了 alloy-worker, 面向事务的高可用 Web Worker 通信框架.
更加详细的工具讨论, 请查阅 alloy-worker 的业界方案对比.

业界实践回顾

实践场景

Web Worker 作为浏览器多线程技术, 在页面内容不断丰富, 功能日趋复杂的当下, 成为缓解页面卡顿, 提升应用性能的可选方案.

2010 年

2010 年, 文章 The Basics of Web Workers 列举的 Worker 可用场景如下:

2010 年的应用场景主要涉及数据处理, 文本处理, 图像/视频处理, 网络处理等.

当下

2018 年, 文章 Parallel programming in JavaScript using Web Workers 列举的 Worker 可用场景如下:

可见, 近年来 Worker 的场景比 2010 年更丰富, 拓展到了 Canvas drawing(离屏渲染方面), Virtual DOM diffing(前端框架方面), indexedDB(本地存储方面), Webassembly(编译型语言方面)等.

总的来说, Worker 对页面的计算任务/后台任务有用武之地. 接下来笔者将分享的一些具体 case, 并进行简析.

重度计算场景

石墨表格之 Web Worker 应用实战

2017 年的文章, 非常好的实践. 在线表格排序是 CPU 密集型场景, 复杂任务原子化和异步化后依然难以消除页面卡顿. 将排序迁移到 Worker 后, 对 2500 行数据的排序操作, Scripting 时间从 9984ms 减少到 3650ms .

Making TensorflowJS work faster with WebWorkers

2020 年的文章, 使用生动的图例说明 TF.js 在主线程运行造成的掉帧. 以实时摄像头视频的动作检测为例子, 通过 Worker 实现视频动画不卡顿(16ms内); 动作检测耗时 50ms, 但是不阻塞视频, 也有约 15FPS.

腾讯文档 Excel 函数实践

笔者撰写文章中, 近期发布.

前端框架场景

neo -- webworkers driven UI framework

2019 年开源的 Worker 驱动前端框架. 其将前端框架的拆分为 3 个 Worker: App Worker, Data Worker 和 Vdom Worker. 主线程只需要维护 DOM 和代理 DOM 事件到 App Worker 中; Data Worker 负责进行后台请求和托管数据 store; Vdom Worker 将模板字符串转换为虚拟节点, 并对每次变化生成增量去更新.

worker-dom

Google AMP 项目一部分. 在 Worker 中实现 DOM 操作 API 和 DOM 事件监听, 并将 DOM 变化应用到主线程真实 DOM 上. 官方 Demo 在 Worker 中直接引入 React 并实现 render!

Angular

Angular8 CLI 支持创建 Web Worker 指令, 并将耗 CPU 计算迁移到 Worker 中; 但是 Angular 本身并不能在 Worker 中运行. 官网 angular.io 也用 Worker 来提升搜索性能.

数据流场景

Off-main-thread React Redux with Performance

2019 年的文章. 将 Reduxaction 部分迁移到 Worker 中, 开源了项目 redux-in-worker.
做了 Worker Redux 的 benchmark: 和主线程相差不大(但是不卡了).

Off Main Thread Architecture with Vuex

2019 年的文章. 简单分析 UI 线程过载和 Worker 并发能力. 对 Vue 数据流框架 Vuex 进行分解, 发现 action 可以包含异步操作, 适合迁移到 Worker. 实现了 action 的封装函数和质数生成的 demo.

可视化场景

PROXX

PROXX 是 GoogleChromeLabs 开发的在线扫雷游戏, 其 Worker 能力由 Surma 开发的 Comlink 提供. Surma 特地开发了 Worker 版本和非 Worker 版本: 在高性能机型 Pixel3 和 MacBook 上, 两者差异不大; 但在低性能机型 Nokia2 上, 非 Worker 版本点击动作卡了 6.6s, Worker 版本点击回调需要 48ms.

图片风格处理

2013 年的文章. 使用 Worker 将图片处理为复古色调. 在当年先进的 12 核机器上, 使用 4 个 Worker 线程后, 处理时间从 150ms 减低到 80ms; 在当年的双核机器上, 处理时间从 900ms 减低到 500ms.

OpenCV directly in the browser (webassembly + webworker)

2020 的文章. 基于 OpenCV 项目, 将项目编译为 webassembly, 并且在 Worker 中动态加载 opencv.js, 实现了图片的灰度处理.

大型项目

OffscreenCanvas

Chrome69+ 支持, 能将主线程 Canvas 的绘制权 transfer 给 Worker 线程的 OffscreenCanvas, 在 Worker 中绘制后渲染直接到页面上; 也支持在 Worker 中新建 Canvas 绘制图形, 通过 imagebitmap transfer 到主线程展示.

hls.js

hls 是基于 JS 实现的 HTTP 实时流媒体播放库. 其使用 Worker 用于流数据的解复用(demuxer), 使用 Transfer Memory 来最小化传输的消耗.

pdf.js

判断浏览器是否支持 Worker 能力, 有 Worker 能力时将 pdf 文件解析( parsed and interpreted)全部放在 Worker 线程中; Worker 能力不完备则在主线程运行.

相关视频/分享 PPT

Web Workers -- I like the way you work it

2016年的分享 ppt, Pokedex.org 项目在 Web Worker 中进行 Virtual DOM 的更新, 显著提升快速滚动下的渲染效率.

The main thread is overworked & underpaid

Chrome Dev Summit 2019, 非常精彩的分享, 来自 google 的工程师 Surma. 演讲指出页面主线程工作量过大, 特别是发展**家有大量的低性能设备. 运算在 Worker 慢一点但页面不掉帧优于运算在主线程快一点但卡顿.

Is postMessage slow? - HTTP 203

同样来自 Surma 的技术访谈. 主要讨论 postMessage 的性能问题. 本文在通信速度部分大量引用 Surma 的研究.

Surma 在 Worker 领域写了多篇文章, 并开源了 Comlink.

前端項目上 Web Worker 實踐

2019 年的演讲, 笔者前同事, 曾在 Worker 实践上紧密合作. 演讲讨论 Web Worker 的使用场景; Worker 的注意点和适应多线程的代码改造; 以及实践中遇到的问题和解决方案.

Weaving Webs of Workers

2019 年的演讲, 来自 Netflix 的工程师. 总结使用 Web Worker 遇到的 4 大问题, 并通过引入社区多个配套工具逐一解决.

Web Workers: A graphical introduction

2018年的演讲, 讲多线程和 postMessage 数据传递部分图很漂亮. 将 Web Worker 应用在他开发的 Web 钢琴弹奏器.

What the heack is the event Loop anyway

2014年的演讲, 使用生动的图例介绍主线程 Event Loop.

实践建议

如上文所述, 社区已有许多 Worker 技术的应用实践. 如果你的业务也有使用 Worker 的需求, 以下是几个实践的建议.

也许你不需要 Worker

使用 Worker 是有成本的: Worker 线程会占用系统资源; 同构代码和异步通信会增加维护成本; 多线程编程会挑战前端仔的思维.

David 的文章指出, 迫切需要 Worker 的场景并不多, 开发者需要考虑投入效益比. 简单来说, 如果页面的某个操作会耗时, 同时不想让用户察觉(转菊花), 那就用 Worker 吧.

Worker 应该是常驻线程

虽然 Worker 规范提供了 terminate API 来结束 Worker 线程, 但线程的频繁新建会消耗资源. 大多数场景下, Worker 线程应该用作常驻的线程. 开发中优先复用常驻线程.

控制 Worker 线程数目

这也很好理解, Worker 线程在争取 CPU 计算资源时, 受限于 CPU 的核心数, 过多的线程并不能线性地提升性能, 而每个 Worker 线程会有约 1M 的固有内存消耗.

理解多线程开发方式

多线程开发的思维和方式, 是个比较大的话题. 开发者需要控制线程间的通信规模, 减少线程间数据和状态的依赖, 尝试去了解和控制 Worker 线程.

展望

本文试图梳理 2020 年当下 Web Worker 技术的现状和发展.

从现状上看, Worker 已经普遍可用, 业界也有业务和框架上的实践, 但在配套工具上仍有不足.

从发展趋势上看, Worker 的多线程能力有望成为复杂前端项目的标配, 在减少 UI 线程卡顿和压榨计算机性能上有收益. 但目前国内实践较少, 一方面是业务复杂程度未触及; 另一方面是社区缺少科普和实践分享.

前端多线程开发正当时. 笔者维护的 Worker 通信框架 alloy-worker 已经开源, 大型前端项目落地的文章正在路上. 鸡汤和勺子都给了, 加点老干妈, 真香!

References

  • alloy-worker

https://github.com/AlloyWorker/alloy-worker

  • Web Workers API

https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API

  • Remove shared workers?

whatwg/html#315

  • Using web Workers

https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers

  • Web Workers Working Draft

https://www.w3.org/TR/workers/

  • using web workers: working smarter, not harder (2009 年 firefox 上的实践)

https://hacks.mozilla.org/2009/07/working-smarter-not-harder/

  • Is postMessage slow? (数据通信实验设计)

https://dassur.ma/things/is-postmessage-slow/

  • 另眼看 Web Worker (讨论异步化编程)

https://www.ithome.com.tw/voice/132997

  • The Basics of Web Workers (2010, 谈到错误处理和安全限制)

https://www.html5rocks.com/en/tutorials/workers/basics/

  • Blink Workers (Blink 框架 Worker 实现介绍)

https://docs.google.com/document/d/1i3IA3TG00rpQ7MKlpNFYUF6EfLcV01_Cv3IYG_DjF7M/edit#heading=h.7smox3ra3f6n

  • Should you be using Web Workers (配图非常棒)

https://medium.com/@david.gilbertson/should-you-should-be-using-web-workers-hint-probably-not-9b6d26dc8c6a

  • How JavaScript works

https://blog.sessionstack.com/how-javascript-works-the-building-blocks-of-web-workers-5-cases-when-you-should-use-them-a547c0757f6a

  • Parallel programming in JavaScript using Web Workers

https://itnext.io/achieving-parallelism-in-javascript-using-web-workers-8f921f2d26db

  • So you want to use a Web Worker

https://povioremote.com/blog/so-you-want-to-use-a-web-worker/

EOF

Burn Again

Burn Again

prepare for the future

元素居中 done

  • Centering in CSS: A Complete Guide

https://css-tricks.com/centering-css-complete-guide/

flex done

  • Flex 布局教程:语法篇

http://www.ruanyifeng.com/blog/2015/07/flex-grammar.html

vue 原理

ES6

  • ES6 module done

https://zhuanlan.zhihu.com/p/33843378

原型链 done

<<高程>>第六章

浏览器渲染

repaint和reflow

性能优化 done

感知性能优化 https://share.weiyun.com/abf5d2140238dc8556ab5ff958b50795

dom 操作 API done

querySelectorAll

Strict Mode

Javascript 严格模式详解

Javascript 执行机制

这一次,彻底弄懂 JavaScript 执行机制
The Node.js Event Loop, Timers, and process.nextTick()

排序 done

八大排序算法 http://blog.csdn.net/hguisu/article/details/7776068

函数防抖(debounce)和截流(throttle)

https://mp.weixin.qq.com/s/Vkshf-nEDwo2ODUJhxgzVA

二叉树前序/中序/后序遍历

https://www.jianshu.com/p/456af5480cee

图的层次/深度优先/广度优先遍历

https://juejin.im/post/5a32688b5188254dd9366d6a

设计模式

闭包

Javascript strict mode

References

  • 阿里校招前端面经

https://lensh.github.io/2017/08/27/alibaba-interview/

  • 16年毕业的前端er在杭州求职ing

https://juejin.im/post/5a64541bf265da3e2d338862

  • 面试分享:一年经验初探阿里巴巴前端社招

jawil/blog#22

  • 技术面试九忌

http://mp.weixin.qq.com/s/luTi1_nCgkzB8XXGVeqizw

  • 你真的会写简历么

http://mp.weixin.qq.com/s?__biz=MzA4NTQwNDcyMA==&mid=402970472&idx=1&sn=b9738c66fb5750c2515d57357c01a83f&scene=21#wechat_redirect

  • 图解正向代理、反向代理、透明代理

http://blog.51cto.com/z00w00/1031287

  • 求职面试-HR会问你什么问题

http://blog.csdn.net/wwj_748/article/details/51226001

EOF

页面可视化搭建工具技术要点

页面可视化搭建工具技术要点

背景

页面可视化搭建工具, 是互联网公司中常见的运营工具, 实现了运营人员快速生成和发布页面, 提升页面上线效率; 且无需开发人员介入, 节省开发人力.

页面可视化搭建工具搭建出的页面示例:

但从零开始设计和开发出这种工具并不简单. 笔者维护的页面可视化搭建框架 pipeline, 提供了页面可视化搭建的核心功能, 免去从零实现页面可视化搭建工具的困难.

本文主要包含以下内容:

  • 活动页面重复开发的痛点.
  • 开发可视化搭建工具的技术要点.
  • 理想的运营页面可视化搭建工具.
  • 开源页面搭建框架 pipeline 介绍.

活动页面开发之痛

活动页面特点

前端业务中, 经常需要开发产品介绍页/营销页/活动页/图片展示页等页面. 这类需求有以下几个特点:

  • 页面类似: 页面布局和业务逻辑较固定.
  • 需求高频: 每周甚至每天有多个这种需求.
  • 迭代快速: 开发时间短, 上线时间紧.
  • 开发性价比低: 开发任务重复, 消耗各方的沟通时间和人力.

活动页面常规开发流程

活动页面常规开发流程图

image

流程

  1. 运营/产品提出页面需求.
  2. 走项目流程进入开发环节.
  3. 开发根据设计稿完成页面开发.
  4. 测试进行页面测试.
  5. 运维进行页面上线.
  6. 运营/产品进行页面验收.

痛点

  • 方多参与, 反复沟通, 串行流程.
  • 页面上线周期长, 无法快速响应活动需求.
  • 人力陷入重复工作泥潭, 忙碌而低效.

更优的流程

对于高频和重复的活动页面开发, 业界一般将页面做成配置化, 配置工作从开发人员交接给产品/运营等需求方; 开发和设计人员只需提供配置化页面支持. 更优的活动页面生成流程依靠页面可视化搭建系统来实现.
现.

更优的活动页面开发流程图

image

流程

  1. 运营/产品提出页面需求.
  2. 运营/产品在页面可视化搭建系统中选取合适的页面模板进行页面搭建.
  3. 页面自动化发布上线, 页面需求完成, 流程完结.
  4. 如果运营/产品没有找到合适的模板.
  5. 开发进行页面模板开发, 并将页面模板添加到页面可视化搭建系统中.
  6. 运营/产品继续流程2.

同时, 随着页面可视化搭建系统中的页面模板不断丰富, 新的页面需求对开发人员的依赖逐渐减低, 可由运营/产品直接完成.

页面可视化搭建工具

更优的活动页面开发流程依靠页面可视化搭建系统实现, 重点是要有页面可视化搭建工具提供技术支持. 页面可视化搭建工具通过填写配置数据表单, 拖拉页面组件等可视化的页面编辑方式, 实现页面的生成或修改.

但从零开始设计和开发出页面可视化搭建工具并不简单, 有几个需要了解和关注的技术点.

页面可视化搭建工具的技术要点

从技术角度, 设计和开发一个页面可视化搭建工具时, 需要考虑以下几个技术要点:

  • 页面组件化
  • 页面模板
  • 页面可视化编辑
  • 组件层级关系
  • 页面打包
  • 实时预览
  • 页面组件开发

image

页面组件化

组件化的优点

页面的基本单元是 HTML 元素, 但是 HTML 元素无法包含业务逻辑, 且由 HTML 元素直接组合出页面, 过于繁杂和低效.

image
图片来源: https://vuejs.org/images/components.png

页面较好的组织方式是组件化, 如上图所示. 组件是对 HTML 元素、元素布局和样式、业务逻辑的封装. 通过组件封装业务逻辑, 并通过组件属性(Props)向外暴露组件的配置字段. 采用页面组件化, 复杂的页面可视化搭建可以转化为2个较简单的操作:

  • 组件树组合, 页面可视化搭建变成页面组件的可视化组合.
  • 组件配置编辑, 将对页面内容的编辑转化为对组件的配置属性(props)修改.

页面前端框架

页面组件化需依靠前端框架来实现. 页面可视化搭建工具的架构方式对页面前端框架有限制: 需选择页面可视化搭建工具支持的前端框架. 如: 页面可视化搭建工具只支持基于 vue 的页面, 那页面组件化的前端框架只能选择 vue.

但是前端技术团队选用的前端框架, 一般已用于支持现有业务, 并沉淀了一定数量的技术组件和业务组件. 如果需要针对页面可视化搭建工具进行前端框架的切换, 成本将会很大.

所以理想的页面可视化搭建工具, 应该和页面的前端框架解偶, 如下图所示. 技术团队在某前端框架中沉淀的技术组件和业务组件, 可在页面可视化搭建工具的页面中复用.

image

技术难点1:页面可视化搭建工具与页面前端框架解偶.

当然, 前端业务已选用了某前端框架, 开发专门支持该前端框架的页面可视化搭建工具, 也是高效实现目标的选择.

页面模板

页面模板包含完整的业务逻辑, 有助于快速生成业务页面. 不同的页面模板适用于不同的业务功能, 从模板库中选择合适的页面模板并派生出默认业务页面, 再对默认页面进行可视化编辑, 从而生成目标业务页面.

image

云凤蝶的页面模板列表:
image
图片来源: https://www.yunfengdie.com/

模板带有页面的默认数据; 对于组件化的页面, 模板是从组件库中选取部分组件, 并带有各个组件的默认配置数据.

image

如上图所示, 页面组件库中有组件A, 组件B, 组件C, 组件D, ..., 组件X等. 页面模板一由组件库中的组件A, 组件B和组件C组成, 实现了一个完整的业务功能; 页面模板二由组件库中的组件A, 组件B和组件X组成, 完成另一个完整的业务功能.

页面编辑

页面由页面组件组合而成, 页面的编辑其实是对页面组件进行重新组合, 并编辑各页面组件的内容. 页面编辑包含2个部分: 编辑页面组件和编辑页面内容.

编辑页面组件

组件树

使用组件化的方式来组织页面, 页面可以认为是一棵组件树, 如下图所示, 树中的节点为页面组件, 页面组件可以包含子组件.

image

在代码编写上, 通过组件标签的组合来声明一棵组件树, 并在打包时生成页面资源, 在运行时加载页面资源渲染出页面.

react 和 vue 的组件树声明示例:
image

编辑页面组件的一个可行方式是: 动态地给页面源码添加组件, 然后重新打包生成页面. 如通过可视化的方式替换 Left组件NewLeft组件 后, 对源码的组件树声明做替换, 将 Left 标签替换为 NewLeft 标签.

image

image

动态组件

一些前端框架支持动态组件, 可以根据组件树声明动态渲染出组件, 而无需在构建前就定义好页面的组件树结构. 对动态组件页面实现可视化组件编辑时, 可以只编辑组件树声明文件, 然后将组件树声明传入提前打包好的页面中进行渲染. 采用动态组件可以避免重新打包的耗时, 快速生成新页面.

Vue 根据组件树声明动态地渲染组件示例如下图, vue 动态组件使用 compontent 关键字来声明, 并通过 is 属性来决定实例化的具体组件. 对于 react, 组件是一个 js 对象, 直接在 jsx 中按照组件名称返回对应组件就可以了.

image

编辑页面内容

组件化页面的页面内容编辑, 是对页面中各个组件的组件属性(Props)进行配置.

组件配置数据

一个组件包含组件属性(Props), 组件状态(State), 组件HTML模板(Template), 组件业务逻辑(Javascript), 组件样式布局(Style)等几个部分.

组件的配置数据通过组件暴露的 Props 注入到组件中, 在组件内部 Props 作为常量分发给 State, Template, Javascript, Style 等其他组件内容, 由组件内容渲染出视图.

image

组件差异化

组件是业务内容的呈现载体, 不同的业务内容, 封装在不同的业务组件中. 所以页面模板中的组件是差异化的, 差异点体现在组件的 Props, State, Template, Javascript, Style 等组件内容上. 在编辑不同组件内容时, 组件配置数据的数据结构是也是差异化的.

如下图示的页面包含3个组件: 头部组件, 间隔区组件和天气组件. 头部组件的配置数据为头部标题和头部图片等; 间隔去组件的配置数据为间隔提示文本等; 天气组件的配置数据为城市名称. 不同的组件需要不同的配置数据.

image

需要为各组件差异化的配置数据定义数据结构和字段类型, 理想的配置数据格式为 JSON, 因为其格式灵活, 支持数据嵌套, 且前端友好.

组件配置表单

页面可视化搭建工具的主要使用人员是运营/产品, 如果让运营/产品人员直接编辑文本格式的组件配置数据, 操作不友好并且容易出错. 需提供可视化的编辑方式 -- 使用 Form 表单来填入配置数据. Form 表单是页面中数据交互的基本形式, 非开发人员使用也没有技术门槛. 使用配置表单来填入配置数据有2个好处:

  • 配置表单交互功能完善, 容易使用.
  • 配置表单可以添加校验逻辑, 避免填入错误的配置数据.

image

如上图所示, 由于组件配置数据的差异化, 组件配置表单也是差异化的, 需为组件库中的每个组件提供相应的配置表单. 如果为每个组件都编写一个表单页面, 工作量较大; 对于复杂的配置项, 表单页面的编写工作量可能会大于页面组件的开发工作量. 需要重点考虑提供配置表单的方式.

技术难点2: 如何用最简单的方式生成配置数据编辑表单.

组件层级关系

组件树定义了组件间父子兄弟的层级关系, 父子组件通过数据流和事件进行关联: 数据从父组件的 State 传递到子组件的 Props; 子组件的变更触发 Event 通知父组件.

image

层级关系对数据流和布局的影响

页面可视化搭建工具编辑组件树时, 会修改组件数据流. 而不同组件的 Props 和 State 是异构的, 在编辑组件树时, 需要处理不同组件产生层级关系后对数据流的影响. 如下图, 父组件的 State 只包含子组件A的 Props, 将子组件B挂载为父组件的子组件, 父组件没有子组件B的 Props, 会导致无法渲染子组件B.

image

同理, 不同的组件有不同的样式布局, 编辑组件树时, 需要处理不同组件产生层级关系后带来的布局影响.

image
图片来源: https://alligator.io/react/using-this-props-children/

如下图, 一个父组件为行内组件, 给其添加一个块级组件作为子组件, 渲染后可能会导致行内组件被块级组件撑开.

image

所以设计页面可视化搭建工具的组件树编辑功能时, 需要重点关注组件树的层级关系, 解决组件间数据依赖和组件间布局适配问题. 页面可视化搭建工具需要制订组件嵌套的规则和约束, 通过组件嵌套规则来确保可视化编辑后的组件树正常渲染.

技术难点3: 如何组织页面组件的层级关系.

使用组件嵌套的搭建工具示例:
image
图片来源: https://github.com/jaweii/Vue-Layout

不嵌套的前端框架组件

可以想象, 组件的嵌套会加大页面可视化搭建工具的架构设计和开发难度.

我们注意到, 营销活动的主要平台是移动端, 移动端页面的常用的布局策略是: 宽度铺满, 高度滚动. 如果前端框架组件都设置为铺满宽度, 页面展示时组件只需在浏览器垂直方向上顺序排列, 则组件组合时不需要嵌套 -- 所有组件互为兄弟节点.

这种铺满页面宽度的组件, 非常适合搭建移动端页面的场景: 在承载页面逻辑的同时, 使得页面的编辑更加简单, 使用者只需处理组件的顺序, 无需处理组件的嵌套.

在移动端, 使用非嵌套组件层级规则的页面可视化搭建工具有: 阿里云凤蝶pipeline 等.

阿里云凤蝶图示:
image

可视化搭建PC端中后台系统页面的工具, 同样可以采用不嵌套组件层级规则, 如阿里的飞冰:

image

页面预览

页面实时预览是页面可视化搭建工具的必要部分, 使用人员可以在通过页面预览来查看和验证可视化编辑的效果.

页面预览示例:
image

用户的可视化编辑包括修改组件树和修改组件配置数据. 如下图, 用户修改页面后, 需要重新渲染页面组件, 得到新的预览页面.

image

实现页面预览有两种方式: 页面挂载后台渲染.

页面挂载

页面挂载指在编辑器前端页面的某个元素节点(div)上渲染出用户编辑的效果. 页面挂载流程图如下:

image

使用页面挂载的预览方式, 编辑器前端页面需要提供组件库组件渲染环境(组件库前端框架); 为实现前端渲染, 编辑器前端源码需引入组件库组件源码, 后续组件库更新, 编辑器需要同步更新. 页面挂载方式有以下特点:

  • 实现页面预览技术方案可行, 实时渲染速度快.
  • 为实现渲染, 编辑器的技术选型需和组件库前端框架一致.
  • 编辑器源码和组件库源码耦合.
  • 组件库组件渲染后的代码逻辑和样式布局, 可能会污染编辑器页面.

后台渲染

后台渲染指在后台进行用户编辑结果页面的渲染和生成, 编辑器前端页面通过 iframe 加载和展示结果页面. 后台渲染流程图如下:

image

使用后台渲染的预览方式, 编辑器前端页面并不需要渲染组件库的组件; 甚至不需要组件源码, 只需知道各个组件的描述信息. 后台渲染有以下特点:

  • 可以实现编辑器和组件库前端框架的分离.
  • 可以实现编辑器和组件库各组件的分离.
  • 可以避免预览页面的逻辑和样式污染编辑器环境.
  • 要求后台和组件库提供页面后台渲染能力, 并要求后台渲染速度快, 用户需要"实时"预览.

难点4: 如何实现组件库的快速后台渲染, 从而实现编辑器和组件库前端框架的分离.

页面构建

页面构建是组件化前端源码生成页面资源的必要环节: 在开发时需要进行开发构建来进行页面调试; 在可视化编辑后可能需要重新构建来生成预览页面; 在发布前需要进行生产构建.

在可视化搭建页面时需要“实时”预览, 要求页面页面构建效率高, 实现快速的构建和打包. 更进一步, 后台渲染其实和服务端渲染很像, 能否借鉴服务端渲染的技术思路.

自定义模板和组件开发

页面可视化搭建工具在业务中的落地, 需要根据不同的业务场景进行业务组件和页面模板的自定义开发. 这对页面可视化搭建工具提出3个要求:

  • 页面可视化搭建工具要支持业务现有的前端框架.

    避免后续组件和模板自定义开发时的工作量和割裂感. 我们是希望复用现有前端框架组件, 而不是用另一个前端框架重写一遍.

  • 组件和模板的编写方式需遵循较简单的编写约定, 避免开发人员难上手和写起来不舒服.

  • 自定义模板和组件和在开发模式下进行调试和测试.

    页面可视化搭建工具必然会对页面模板和页面组件的编码方式进行限定. 这要求就页面可视化工具在页面模板和页面组件上的约束较少, 减少对前端框架代码组织方式的入侵点.

理想的活动页面可视化搭建工具

页面可视化搭建工具, 需要对页面做一些约定和约束, 在可视化搭建时遵循工具约定和约束来编辑页面. 从页面可视化搭建工具的技术要点中, 可以归纳出活动页面可视化搭建工具的理想形态.

页面可视化搭建工具有不同的框架设计和实现方式, 不同的功能有不同的适用场景, 详细分类可以参考笔者以前的文章: 页面可视化搭建工具前生今世.

概述

运营页面搭建工具, 实现基于模板的页面生成; 将页面的逻辑功能封装在组件内, 声明页面配置数据并提供配置表单, 通过对配置表单的数据填充, 进行少量页面编辑就可以完成业务页面搭建.

image

不嵌套的组件

在编辑自由度的选择上, 选用不嵌套的组件.各组件铺满页面宽度, 在页面高度方向顺序排列.解决组件嵌套带来的数据流问题. 不嵌套的组件如下图各个红框框起来的部分所示.

image

配置表单自动生成

配置表单的作用是生成和约束 JSON 配置数据, 业界已有对 JSON 进行描述和自动生成表单的方案 -- JSON Schema. 按照 JSON Schema 规范对 JSON 数据进行描述, 可以动态渲染出配置表单; 且 JSON Schema 可以对编辑后的数据做格式校验, 避免编辑错误. 这比编写一个表单页面更加简单和高效.

image
图片来源: https://github.com/json-editor/json-editor

JSON Schema 的语法并不是很精简, 云凤蝶的 Schema 语法 等方案更简洁, 但是云凤蝶的语法没有开源的表单生成库支持, 在开源实践上还是 JSON Schema 最佳.

理想活动页面搭建工具特点

  • 采用组件化和页面模板实现页面生成效率的提升.
  • 采用不嵌套的组件层级简化数据流和样式布局.
  • 采用 JSON Schema 声明配置数据, 自动生成配置表单.
  • 采用后台渲染, 使编辑系统与组件前端框架解耦.
  • 在遵循编辑系统约定下, 组件可以自由拓展, 前端框架可以自由选择.

页面可视化搭建框架 Pipeline

简介

Pipeline 是一个开源的页面可视化搭建框架, 主要由笔者在维护. Pipeline 意为流水线, 期望 pipeline 像工厂流水线一样可以高效地组装活动页面.

所谓框架, 是它实现了页面可视化搭建的基本功能, 解决了页面可视化搭建的基本难点, 可以让开发者快速拥有页面搭建的能力, 并支持私有部署和二次开发.

项目信息:

功能 Demo

可视化编辑

demo

如动图所示, pipeline 的可视化编辑能力有:

  • 可视化修改页面全局配置, 如修改页面主题颜色.
  • 可视化修改页面组件内容, 如修改组件的图片和替换组件文本.
  • 实时预览页面编辑效果, 即刻获得搭建后的页面.
  • 页面支持用户交互.

组件编辑

dnd_demo

如动图所示, pipeline 的组件编辑能力有:

  • 动态增删页面组件.
  • 可视化的组件拖拽, 拖拽组件库组件插入到页面组件列表中.
  • 组件可以包含业务逻辑(网络请求和用户交互).

支持的前端框架

Pipeline 实现了编辑器和页面前端框架的分离, 可以支持不同的前端框架. 所谓支持的前端框架, 就是对某个前端框架按照 pipeline 的约束规则进行组件编辑方式和工程构建方式的改造, 使得前端框架页面可以在 pipeline 中可视化搭建.

目前已经支持 Vue, React, 和 Omi, 理论上可以支持任意前端框架.

image

框架特点

  • 开源页面可视化搭建框架.
  • 自定义页面可配置字段.
  • 组件动态增减, 组件拖拽.
  • 从页面模板快速生成业务页面.
  • 模板工程/编辑器/后台服务解偶.
  • 模板工程前端框架无关: 支持 vue 和 react 等.
  • 支持自由拓展页面组件, 不限制组件样式布局, 接口调用等.
  • 前端工程约束少, 不限制使用其他技术(Redux, SSR, UI库等).

与云凤蝶的对比

阿里云凤蝶 是目前市场上可见中最棒的页面可视化搭建服务, pipeline 的很多方面和云凤蝶相似, 做个简单对比:

云凤蝶 pipeline
商业化解决方案, 直接可用 开源系统, 基础的页面搭建框架, 需要自行部署
生成的页面, 上传的图片等只能托管在阿里, 也限定域名 资源落地和周边功能需要自行搭建, 但是可以100%掌控所有资源
配置表单功能比较完善 配置表单比较基础, 需要提升
使用自定义的组件配置约束规则 使用通用的 JSON Schema 规范
模板前端框架采用 Nunjucks 前端框架采用没有约束, 已经支持 vue 和 react 等, 业务迁移成本低
隐藏了模板的构建处理过程, 提供制定的 IDE 采用 webpack 构建, 模板开发与正常前端项目开发一致
不支持自定义页面级别的配置项 支持自定义页面级别的配置项

总的来说: 云凤蝶是完整的商业化页面可视化搭建系统, 适合偏业务运营的公司; pipeline 是开源的页面可视化搭建框架, 适合需要自建页面可视化搭建系统且有技术人员支持的公司.

下一步工作

  • 完善技术实现文档, 使用文档, 系统部署文档和模板开发文档.
  • 提供对更多前端框架的支持.
  • 提供更加丰富的可视化交互方式.

总结

本文讨论了活动页面开发的痛点, 总结出页可视化搭建工具的7个技术要点和4个技术难点, 并整理出理想的运营页面可视化搭建工具, 最后介绍页面可视化搭建框架 pipeline.

行文仓促, 对页面可视化搭建话题或开源项目 pipeline 感兴趣, 欢迎讨论.

References

  • pipeline

https://github.com/page-pipepline

  • 页面可视化搭建工具前生今世

https://github.com/CntChen/cntchen.github.io/issues /15

  • Vue 2.0学习笔记:组件数据传递

https://www.w3cplus.com/vue/component-data-and-props-part1.html

  • 阿里云凤蝶

https://i.yunfengdie.com/

EOF

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.