源自babel的多包管理工具:Lerna
多包合作的烦恼
在开发需要多个密切协作的软件包时候,我们往往将独立的功能块进行划分,使得各个功能独立的模块分别完成,以减少相互影响,完成有效的多人合作。但是,在模块协作时,经常会遇到一些问题:
- 依赖处理繁琐。
- 依赖的模块,尚处在开发之中,通行的npm install、yarn等无法从安装源中获得。
- 被依赖的模块版本升级,模块其他版本需要手动管理相关的版本。
- 有循环依赖的风险
在开发需要多个密切协作的软件包时候,我们往往将独立的功能块进行划分,使得各个功能独立的模块分别完成,以减少相互影响,完成有效的多人合作。但是,在模块协作时,经常会遇到一些问题:
一直以来,Windows的命令行的体验都不是特别的友好。由于Windows以图形界面交互为主,同时微软在一段时间内对命令行程序发展并不积极,以及Windows系统底层与*nix系列的不一致。造成Windows下的命令行开发与图形开发体验相去甚远:一方面工具链的不完整,一方面终端字体不甚美观,甚至默认的终端色域相对于Mac都是精简的。
纷纷红紫已成尘,布谷声中夏令新。夹路桑麻行不尽,始知身是太平人。 ——宋.陆游 《初夏绝句》
我们在开发多Tab应用时候,常常会遇到多个Tab状态同步的问题。
想象如下场景:用户主界面,显示用户购物车内待结算的商品总数。此时,用户可能打开多个Tab。当用户添加新商品到购物车的时候,需要更新购物车的数量。
此时,当前页面需要向服务器发起请求,在得到添加成功响应的时候,可以更新用户界面。为了兼顾体验和可靠性,如果确信添加成功概率比较高的时候,也可以先更新界面,当多数返回错误的时候,可以给用户界面做状态回滚。为了下次展示方便,我们还会把这个数据写到LocalStorage里面。用户再次打开时候,可以优先从localStorage中取值。
当前页面解决了,那么如果同时打开多个Tab该如何解决呢?这里使用StorageEvent可能是一种代价较小的解决方案。
StorageEvent是什么呢?
这里有一个简单的示例可以展示这个API的用法。
const STORAGE_KEY = "cartlist"
const getStorage = () => {
try {
let rets = window.localStorage.getItem(STORAGE_KEY)
if (rets === null) {
return []
}
return JSON.parse(rets)
}
catch(e){
return []
}
}
const addCart = (value) => {
let rets = getStorage()
rets.push(value)
window.localStorage.setItem(STORAGE_KEY, JSON.stringify(rets))
return rets
}
const minusCart = (value) => {
let rets = getStorage()
let idx = rets.indexOf(value)
if (idx !== -1){
rets.splice(idx, 1)
window.localStorage.setItem(STORAGE_KEY, JSON.stringify(rets))
}
return rets
}
const render = () => {
let rets = getStorage()
if (rets.length){
$("#num").html(rets.length).show()
}
else {
$("#num").hide()
}
$(".list li").each((i,el) => {
if (rets.includes(i)){
$(el).find("a:nth-child(1)").css("visibility", "hidden")
$(el).find("a:nth-child(2)").css("visibility", "visible")
}
else {
$(el).find("a:nth-child(1)").css("visibility", "visible")
$(el).find("a:nth-child(2)").css("visibility", "hidden")
}
})
}
$(".list a").on("click", (e)=> {
let opIdx = $(e.target).parent().find("a").index(e.target)
let line = $(e.target).parent().parent()
let idx = $(".list li").index(line)
opIdx === 0 ? addCart(idx) : minusCart(idx)
render()
return false
})
window.addEventListener('storage', (e) => {
render()
})
render()
其中,下面这行代码是实现的关键:
window.addEventListener('storage', (e) => {
render()
})
当我们注释掉这个语句,我们的页面同步就不能运行了。
读者可以打开多个Tab并观察页面的变化https://jsbin.com/radekilosu/1/edit?html,css,js,output。
实际上,这个事件e上还带有很多信息,方便编程时,对于事件做精确的控制。
字段 | 含义 |
---|---|
key | 发生变化的storageKey |
newValue | 变换后新值 |
oldValue | 变换前原值 |
storageArea | 相关的变化对象 |
url | 触发变化的URL,如果是frameset内,则是触发帧的URL |
上述各值都是只读的。
还有一点没有解决掉,就是触发storage变化的本页面,不能接收这个值,这个一般情况下是没问题。当然,为了一致性,我们可以自行new一个事件,在发生时候主动触发它。
此时我们可以包装一个新的Storage对象:
var Storage = {
setItem : function(k,v){
localStorage.setItem(k,v);
},
removeItem : function(k){
localStorage.removeItem(k);
},
clear: function (){},
getItem: function(k){}
}
此时,我们再包装一个函数:
function dispatchMe(key, oldval, newval, url, storage){
var se = document.createEvent("StorageEvent");
se.initStorageEvent('storage', false, false, key, oldval, newval, url, storage);
window.dispatchEvent(se);
}
此时,我们只需要在setItem、removeItem、clear中获取对应的值,并手动调用一下dispatchMe,同时把和localStorage打交道的地方改为调用我们包装的Storage即可。
2019年5月20日,科技界最火爆的消息莫过于谷歌暂停支持华为部分业务。关于这个事件将产生的影响,大家可以从相关的科技媒体上找到详细的分析。比较一致的看法是:谷歌的这个做法,对于国内的华为用户,影响不大;对于海外的华为用户,尤其是在上游应用生态环境上,会有一定的影响。
这是由于,谷歌暂停合作的服务,都是在用户层面的服务GMS。用户层与安卓底层内核遵从不同的协议,因此不必开源,进而导致替换成本高企。基于现有生态,在海外市场构建应用生态环境难度非常大。
这篇文章将尝试从这则新闻涉及的开源协议来分析一下,这个做法背后相关的逻辑,并籍此,跟各位读者聊一聊主流的开源协议。
天街小雨润如酥,草色遥看近却无。最是一年春好处,绝胜烟柳满皇都。 —— 唐.韩愈 《早春呈水部张十八员外二首》
几个月之前,chunpu小编曾经在《震惊! 滑动验证码竟然能这样破解》一文中给大家展示了使用支持WebDriver标准的Firefox破解滑动验证码的示例,脑洞之大,构思之巧,笔法幽默,叹为观止,也极大的燃起了笔者对此的兴趣。
这篇文章就带大家使用目前较为流行的Puppeteer完成这件事情。
造物无言却有情,每于寒尽觉春生。千红万紫安排著,只待新雷第一声。 —— 清.张维屏 《新雷》
植根于Unix系统环境下的程序,很多都把贯彻Unix系统设计的哲学作为一种追求。Unix系统管道机制的发明者Douglas McIlroy把Unix哲学总结为三点:
随着Unix/Linux系统在服务器上影响力越发强大,以及各种跨平台解决方案的发展,这种哲学也被带到了各种平台上。若干年前,笔者第一次接触NodeJS和其包管理解决方案NPM时候,就感觉到其官方倡导的风格,和Unix系统哲学非常契合。近年来,随着NodeJS在服务端以及前端构建领域上的不断开拓,NodeJS的这种思想也正快速的渗透到这些领域。
其实,NodeJS的本身,也是开发命令行程序的一个重要利器。本文就将介绍几个常用的NodeJS相关命令行程序,之后介绍几个开发命令行中常用的组件,最后介绍发布npm包以及带scope的包的发布方法。
孤山寺北贾亭西,水面初平云脚低。几处早莺争暖树,谁家新燕啄春泥。乱花渐欲迷人眼,浅草才能没马蹄。最爱湖东行不足,绿杨阴里白沙堤。 ———— 唐.白居易《钱塘湖春行》
自从 Node8.5 以后, Node 开始支持引入 ES 模块。在新开的项目中,笔者尝试使用了这种方式。由于目前 NodeJS 对于 ES 模块尚属试验性支持,因此需要在启动时候加入参数:--experimental-modules
,完整的命令如:node --experimental-modules index.mjs
。这样程序就愉快地运行起来了。不过当笔者尝试加入一些新的依赖之后问题出现了:
白玉堂前一树梅,为谁零落为谁开。唯有春风最相惜,一年一度一归来。 宋.王安石《梅花》
仿佛一瞬间,2019 年的一月份就快过去了,然而迎接元旦的时刻似乎还历历在目。时间总是这么安静的流走,以它自己的节奏,不疾不徐。猛的发现,21 世纪也过去了将近 1/5 的时间了。
每一年,都会诞生各种各样的技术。有的经过实践的检验和逐步的完善,慢慢成为业界的标准或是最佳实践;有的则慢慢淡化直至完成使命,或者演化为其他技术。
在技术发展的上升阶段,总会有一些热点的技术,在一段时间被反复提起。它们诞生的初衷往往是一个很具象的痛点,然后在解决、完善之后,外延不断扩大,并最终成长为一个成熟的技术或者技术栈。
今天要说的 GraphQL 可能就是这样的技术。
随着前后端分离的开发模式不断的深入人心,越来越多的项目采用这种方式开发和部署。前端程序或是 App,多采用数据驱动的方式完成。数据可以采用数据接口的方式从服务端请求获得。主流的方式一般采用 REST API。
在 REST 风格的 API 中,接口数据被认为是一种“资源”,服务端提供 RESTful API,客户端通过 GET/POST/DELETE/PUT 等动作,通过 URI 对“资源”进行操作。
比如:GET /books/1
即是通过 GET 请求,访问 URI /books/1
,也即服务端提供的/books/:id
的 RESTful API,“获取”资源,得到如下返回:
{
"bookId": 1,
"title": "Black Hole Blues",
"author": {
"firstName": "Janna",
"lastName": "Levin"
},
"page": 260,
"press": "..."
}
这样的情景适应于现在的主流开发场景。不过也由一些痛点,下面列出几种:
举个例子,我们需要查询某本书作者的详细信息。
现有接口:GET /books/:bookId
返回形如下列格式:
{
"bookId": 1,
"title": "Black Hole Blues",
"authors": [
"authorId": 100
],
"page": 260,
"press": "..."
}
另有一个接口,可以获得作者信息:GET /authors/:userID
返回形如下列格式:
{
"userId": 100,
"name":{
"firstName": "Janna",
"lastName": "Levin"
},
"age": ***,
"gender": "***",
"photos":[2, 3, 5, 7, 11, 13, 17, 19, 23]
如果只有这两个个最简单的接口,咱们的需求还是可以完成的:首先,先通过GET /books/1
取得作者 ID,然后再通过GET /authors/100
就取到了作者的信息。
这里,会有两次相继的请求。后面的请求严格依赖前面的请求。这样可能会造成以下几个问题:
(1)在业务上,对于多个作者的情况,可能需要请求的次数更多(可以提供多 ID 查询接口变相解决)
(2)请求时间长。即便启用了Keep-Alive
也仅仅减少了重复建立链接的时间。
(3)如果第一次请求失败,需要对错误进行处理或重试。一方面会增大复杂度,另一方面极端的网络条件下,频繁的重试,对于服务端的压力是几何级的增长。
这样的情况,有的团队可能的做法是另外提供一个面对业务的整合接口,如: GET books/1/author/
,可以间接地解决问题。
这样做的问题在于,一个前端界面上的问题,直接透给了服务端。业务的变化,会造成这种接口的爆炸增长抑或成批的废弃,给维护造成负担。
photo
字段所示的照片集取得第一张照片显示......。为了达到这样的效果,一般的团队可能会为每种端开发一种特定 API,或者是统一把最冗余的数据接口提供出去。这样显然都不是最理想的。
这些痛点至少代表大家在开发中经常遇到的情况。GraphQL 也许可以成为解决这些问题的一个不错的选择。
那么,什么是 GraphQL 呢?
GraphQL 是一套关于数据查询和操作的语法标准,它极为适合用于 API 交互。它由 Facebook 在 2012 年开发并在内部使用。2015 年对外发布。2018 年 11 月 7 日,GraphQL 由 Facebook 转交由新成立的 GraphQL 基金会管理。
简单的说来,GraphQL 提供了一种机制,允许客户端定义返回的数据结构。当数据返回时候,精确的根据定义返回所需的数据结构。这种机制避免了大量冗余数据的返回,也使得合并多次请求成为可能。
这里是 GraphQL 官方网站。国内还有一份中文的镜像网站。
GraphQL 确切讲是一种语言标准,是与实现语言无关的。实际上,Facebook 和开源届,已经有了针对 GraphQL 的各种语言的实现。
为了对 GraphQL 有一个感性的认识,读者可以参考官方示例进行操作,完成一个 GraphQL 查询。
GraphQL 引入了类型系统、参数、查询与变更类型、标量类型、枚举类型、接口、联合类型、输入类型等概念。读者可以在这里学习相关概念的描述。这些概念至关重要。
接下来我们打交道的各种程序,无论是前端还是服务端,始终都回避不了一个名字:Apollo。Apollo 是一个通过社区力量帮助你在应用中使用 GraphQL 的一套工具,由 Meteor Development Group开发。本文选型的前端和服务端都最终依赖 Apollo 这个利器。下图是 Apollo 涵盖的领域:
我们以使用 Vue 的项目为例,看一下在前端如何使用 GraphQL。整合 Vue 与 GraphQL 依赖库:vue-apollo。vue-cli 3.x 已经支持工具化引入这个库。
我们以 vue-cli 3.x 为例。
graphql-test
为例vue create graphql-test
cd graphql-test
vue add apollo
此后将安装vue-cli-plugin-apollo
,用于配置请求。
为了简单地展示原理,我们暂时不安装样例代码。
此时,目录结构如下图:
默认地,vue-cli-plugin-apollo
已经在入口的main.js
为我们引入好了配置文件,并全局地,引入到 vue 组件中:
OK,一起来修改一下HelloWorld.vue
,如下:
<template>
<div>
<ApolloQuery :query="require('../graphql/hello.gql')">
<template slot-scope="{ result: { loading, error, data } }">
<div v-if="data">{{ data.hello }}</div>
<div class="book">
<div class="title">Book</div>
<ul v-if="data">
<li v-for="item in data.books">
<span>title: {{item.title}}</span> <span>author: {{item.author}}</span>
</li>
</ul>
</div>
</template>
</ApolloQuery>
</div>
</template>
<style scoped>
.title {
font-size: 14px;
font-weight: bold;
}
.book ul {
margin: 0;
padding: 0;
margin-left: 50%;
transform: translateX(-50%);
}
.book li {
margin: 0;
padding: 0;
list-style: none;
text-align: left;
display: flex;
align-content: space-between;
}
</style>
<script>
export default {
apollo: {},
};
</script>
hello.gql 的内容如下:
query Hello {
hello
books {
title
author
}
}
此时运行:npm run serve
。打开浏览器访问:http://localhost:8080
。
大家会看到,目前还没有网络返回。为了实现和服务器的交互,我们还需要把服务端建立起来。
这里需要说明一点,无论是前端还是服务端的响应代码不一定是 Javascript 的。前文提到的各种库列出了主流语言的实现。本文中,以服务端 Javascript 为例来说明。
特别地,如果使用上文提到的 vue-cli 3.0 生成服务端代码也是可以的。这里为了清晰,还是以手动建立为例。
graphql-server-test
为例vue create graphql-server-test
cd graphql-server-test
npm init -y
npm install --save apollo-server graphql
server.js
此时,刷新浏览器,可以看到响应。
对于 GraghQL,优势在于可以有效地降低请求数和请求载荷,提升效率。同时可以灵活地应对各种客户端需求以及多变的业务需求。当然也客观地增加了请求实现的复杂度,同时由于请求对象的个性化,不能充分利用 HTTP 的缓存,也是一个值得重视的问题。
这里是Nate Barbettini在 From Iterate Conference 2018 上的演讲以及问答视频,比较客观的比较了 RPC、REST 和 GraghQL 三种方式的优劣,有兴趣的同学可以了解。
本系列下一篇,将以一个完整的前后端示例,带读者亲自体验在实际项目中的 GraphQL。
东风夜放花千树。更吹落、星如雨。宝马雕车香满路。凤箫声动,玉壶光转,一夜鱼龙舞。
蛾儿雪柳黄金缕。笑语盈盈暗香去。众里寻他千百度。蓦然回首,那人却在,灯火阑珊处。 ——宋.辛弃疾《青玉案.元夕》
马上就要进入 9102 年的今天,仅仅借助于 CSS,Web 开发者就已经可以绘制出各种丰富多彩的图形了,而且方式还是多种多样的。比如下面的鹰嘴形,就可以用多种方式实现。
<span class="icon"></span> <span class="icon icon--blue"></span>
.icon{
display: inline-block;
width: 60px;
height: 100px;
margin: 100px 30px;
border-top: 30px solid #000;
border-top-left-radius: 55px 60px;
}
.icon--blue{
border-top-left-radius: 60px 70px;
border-color: blue;
}
.icon{
display: inline-block;
width: 60px;
height: 100px;
margin: 100px 30px;
border-bottom: 60px solid transparent;
border-left: 80px solid #000;
border-top-left-radius: 100%;
transform: rotateX(180deg) rotateZ(-90deg);
}
.icon--blue{
border-left-color: blue;
}
看上去很完美。不过这些我们创建的图形,还不能影响图形内部和周围的内容排布。举个例子来讲,我们可以简单地在页面上创建一个三角形,但是也许无法控制周围的内容围绕这个三角形。
自从 7102 年,iPhoneX 引入“刘海屏”之后,异形屏的全屏适配交互也成为了前端的又一个课题。随之而来的各种适配交互方案,也是层出不穷。下面是一种很讨巧的方案。
这种在排布内容的问题可以使用 CSS 标准的 CSS Shapes 解决。特别地,针对上文提到的“刘海屏”的适配交互方案,张鑫旭老师的这篇文章给出了解决方案的详细解析。