Vue SSR从入门到Case Study

最近两个项目同时开发,使用了Vue2的SSR,这样后端渲染页面首屏可以加快页面呈现,增加SEO和用户体验,但是项目上线后却发现了严重的性能问题,于是在三天内两次重大调整,最后只能放弃Vue SSR,本文从Vue SSR实现开始,逐渐复盘整个事件。

两周前就预告了要写一篇Vue SSR的文章,但是没想到上周四上线之后,周六放量之后发现性能问题,这周一到周三,做了两次重大调整,最终还是放弃了SSR,并且做了这次事件复盘。

技术选型

调研Vue已经很久了,随着Vue2正式发布,使用Vue来做项目又燃起了希望,不是为了一时的技术理想和情怀(了解我的人都知道,我不是这样的人),主要是出于下面几方面考虑:

  1. 用artTemplate+Sass+JS做的components方案已经做了很久了,沉淀了很多组件,随着Node服务开始上线,一直想在此基础上做同构,而公司Node框架Yog2的view层选择偏向于Smarty模板的Swig,修改比较麻烦
  2. 既然改不了,那么要换不如直接选择新的components方案,这次最强烈需求是:组件化和支持SSR,而Vue2之后支持SSR
  3. 这次两个项目同时进行,而且仅仅给两周的开发时间,组件化有效提高工作效率,可以把通用的组件抽象出来,多个页面之间业务组件复用率也很高,而且业务组件在后续的运营活动也可以直接复用
  4. 手百产品形态复杂,页面即在手百内使用又有手百外使用,手百内页面被多个Webview隔开,不适合SPA形式,而手百外适合SPA形式,所以一套代码需要适配两种情况,Vue 可以适应这两种方式
  5. Vue的SPA形式可以方便进行PWA和Hybrid改造(继续关注本公众号Hybrid系列)

所以,最后决定:上Vue!技术栈:Vue2+Yog2

再介绍下两个项目:

  • 项目A是老项目进行重构,产品需求要跟功能全部保留,架构跑通使用的是Vue2.1,所以A项目代码相对复杂,一直没有使用Vue2.2
  • 项目B是新项目,开始使用Vue2.1,上线后发现已经有Vue2.2,于是升级Vue2.2,并且把项目目录结构调整一番,Webpack config等都可配

Vue SSR入门到上线

先看下Vue SSR的实现流程图:

简单解释一下:

  1. app.js是Server和Client公用的
  2. webpack会根据server-entry.js和client-entry.js打包出来两个文件:server-bundle和client-bundle
  3. server-bundle用于后端渲染(2.1是js文件,2.2变成json,引入更加方便)

但是这张图没有说明在调用API接口方面,前后端是怎么公用代码的。前端走的是Ajax请求,后端走的是http请求(百度内部是RAL接口服务管理),结合上图补充完整的代码执行流程图如下:

Hybrid APP开发:模板本地化

上次文章介绍了Hybrid开发中常用到的Web和NA的通信方案:JSBridge,通过比较之后,最终推荐安卓和iOS通用的scheme协议,可以保证APP内和外都可以使用,今天开始正式介绍整个Hybrid架构内容。就像本系列「开篇语」提到的,这里的Hybrid是「狭义的Hybrid」,而不是所有的NA套个webapp就是Hybrid。

好的架构不是随波逐流,应该经得起考研

Hybrid技术体系是一套很多技术组成的完整知识架构。拿手百Hybrid方案,包括规范/约定、开发、联调、服务支撑等整个开发流程,而随着PWA之类「新」技术的产生又在考研技术架构面向未来的设计能力,所以手百Hybrid还在不断完善,注意是不断完善,不是推到重来!手百Hybrid技术整体架构如下:


接下来系列会围绕本架构图中重要的部分依次展开,全面的介绍Hybrid开发知识,本文介绍模板本地化开发方案。

所谓「模板本地化」,就是将Web页面内置到APP内,随版发布上线,然后通过云端接口实现更新,再直白一点:将H5网站的页面预先放到客户端发版,然后云端更新新版本。

这个方案好处在于:

  • 本地化模板提高页面打开速度,减少用户等待时间
  • 模板可更新,版本控制更方便可控,收敛快
  • Web页面和NA内置代码实现一套代码,减少开发成本
  • 上手成本小,开发就是实际开发Web(H5)页面,通过构建工具,生成Web页面和NA模板包不同的代码
  • 标准H5代码,迁移成本小,通过Node和构建工具,可以做到H5版本前后同构,将来还可以不改代码的前提下适配PWA

本文提到的JSBridge调起协议是统一使用hybrid://开头,只是提供思路和介绍整体Hybrid模板包架构和用到的技术,不涉及到具体代码,但是文章保证干货和诚意都满满😄。

模板本地化实现方案

本文会介绍两种模板本地化方案,分别是:依赖客户端拦截器(proxy)的方案一;完全无域名限制的方案二,两个方案的实现流程如下:

在腾讯工作是怎样一种感受

最近消息是之前待过的腾讯OMG事业群换老大了,结合之前后台有朋友留言说多谈谈工作经历和趣闻,今天谈谈腾讯的见闻和观感

背景:本人12~13年在腾讯北京OMG部门工作过,当时在银科大厦,正值腾讯微博发力和腾讯视频起步阶段,当时微信已经是明星产品,本文见闻和观感基于本人经历。

本文思绪有些乱,偏意识流。。

员工关怀

敢说腾讯员工关怀做的是工作过公司当中最好的,没有之一,先列几个印象深刻的事情。

  1. 北京当时刚刚开张的image(爱马哥)印象店,里面各种体验和玩偶,公司也会组织一些玩偶涂鸦活动,做的好的直接在image店展示。
  2. 公司前台和保安都是外包公司(其实多数这样),但是和员工关系很融洽。每层前台都可以领药、口罩、雨伞,可以让妹子给洗眼镜,洗完擦干净双手送上,个人感觉比一些公司扔个超声波清洗机,但是没几个人会用
  3. 如果公司搞活动会一个一个办公区搞,办理一些行政人事的事情也不会跨办公区,甚至不需要从银科跑到第三极,这个如果在百度就会有体会,除了大厦和科技园很多事情办理不了。。
  4. 「班车文化」:这是腾讯最引以为豪的事情之一,据说深圳班车数量和深圳公交数量有的一比,如果不坐班车貌似还有80块钱每月的补助(如果没发可能作为团建费用了)。这里的班车是送员工回家的真班车,不是地铁摆渡车哦~
  5. 加班有专门合作的餐厅,晚上八点保安会推车发加班餐;加班自己报备,可以变现和调休,节假日三倍工资
  6. 还有个很有意思的事情是快递要过安检

Hybrid 开发:JsBridge - Web和客户端双向通信

Hybrid开发中,web页面往往会跟native进行交互,而JSBridge就是web页面和native进行通信的桥梁,通过JSBridge可以实现web调用native的方法,native可以通过webview.loadUrl之类的方法,将javascript:xxx代码放在页面执行,这有点类似在浏览器地址栏直接输入:javascript:xxx

本文较长,先把目录列出来:

  • JSBridge多种形式
    • js Interface 直接注入到window对象
    • 改写浏览器原有对象:alert/console/prompt
    • URL scheme
  • 唤起APP技术
    • intent
    • localserver
    • scheme:deeplink/applink/Universal link
    • smart app banner
  • JSBridge安全
  • JSBridge的最佳实践
    • 协议规范
    • 回调函数
    • 预留升级/统计能力
    • 简单JSBridge调用封装

JSBridge多种形式

web和native进行通信,方法有很多,接下来一一列举一下JSBridge的多种形式,及其利弊。

JavaScriptInterface

JSInterface是安卓4.2-官方推荐的解决方案,原理是通过WebView提供的addJavascriptInterface方法给浏览器window注入一个命名空间,然后给Web增加一些可以操作Java的反射。

// Android java代码
mWebView.addJavascriptInterface(new Class(), 'android');  

public class Class(){
  @JavascriptInterface
  public void method(){

  }
} 
// js 代码
window.android.method();

JSInterface在4.2之前的版本都可以,但是存在严重的安全隐患,容易被利用提权,从而调用各种Java的类和权限,甚至页面可以挂马。在我们实际产品(手机百度)开始阶段,用过这个方法,不过现在已经不使用了。

怎么进行一场好的面试:面试官篇

上次写了一篇文章「怎样准备一场面试:候选人篇」,今天的文章是姊妹篇:「面试官篇」,本文会结合自身做面试官和候选人的体验,简单说下我心目中的合格面试官是怎样的。

端正态度

面试官最大的任务是帮助团队挑选符合条件的候选人。所以,面试官找的是将来一起合作共事的人,应该尽最大努力去发现候选人的优点,引导候选人正常发挥。

面试官也是公司和团队的门面,面试整个过程应该体现出职业化的一面,给候选人留下好的印象,即使候选人不是合适人选,但是给候选人留个好印象,对于面试官个人和团队都有深远的意义。(谁还没个牛逼的朋友。

不要盛气凌人!不要盛气凌人!不要盛气凌人!

不知道为什么,越大的公司越不喜欢给面试官做个基本培训,很多面试官技术不错,但是职业化做的不一定很好
有个朋友吐槽某厂高P面试,大冬天天打飞的跑南方湿冷城市,面试官迟到几个小时不说,晚上7点多到了,夹着个surface支起来就看电脑,头都不抬,前后面试不超过半个小时,技术问题都没几个。。好吧,尊重人是再高的Level做到的基本素质,该朋友对此厂一生黑。。

准备工作

面试官收到面试通知的时候,应该清楚的知道时间、地点,并且了解候选人的情况,了解团队要求的级别和工作范围是什么。如果是交叉面试或者跨团队面试,了解JD之外,还要跟Team Leader沟通下,至少知道TL心中的合适人选是样子的。

根据JD要求,结合候选人的简历,初步准备问题,有必要可以提前准备笔试题。

怎样准备一场面试:候选人篇

本文不是面经,只是通过一些细节增加面试的成功率,帮助候选人找到合适的团队和公司。面霸跳过此文

  • 首先端正态度,认真对待每一次面试,不要浪费你和别人的时间;
  • 其次,不要觉得低人一等,面试是双向选择的,自信很重要。

仔细阅读JD

JD=job description,是指职位描述。往往很多候选人和发布JD的HR/Team Leader 忽略了JD的作用,好的JD传达出来很多信息:

  • 这是一个怎样的团队
  • 技术栈是怎样的
  • 需要什么样的人加入
  • 候选人加入团队做什么

如果喜欢做JD的项目,认可团队,技术栈也没问题,就可以投递简历;JD中提到的技术栈(要求)和项目也是面试的重点,可以针对性的做准备。

前端资源动态渲染模式介绍之seed篇

前面两篇文章(前端资源动态渲染模式介绍之概览篇前端资源动态渲染模式介绍之combo篇)介绍了combo模式,今天重点介绍下seed模式,seed模式是一种利用js动态解析页面模块依赖,而且结合localstorage和combo 服务,实现的一种速度更快的加载方式。

seed模式特点

  • 结合打包工具,实现页面依赖管理,seedjs不需要维护整站(整个项目)的resourcemap,combo需要使用后台语言维护map.json
  • 结合localstorage,将模块缓存到ls,方便全站(单域名)下公用,下载过模块避免二次请求
  • 对于更新的,没有下载过的模块,拼成combo url,一次加载,避免多次请求

如何实现seed模式

要实现seed模式,要修改模块定义和引入函数,例如AMD中的definerequire,结合打包工具,实现静态资源依赖表resourcemap(例如fis中的map.json)的字段自定义

define函数进行改造

首先对define函数进行改造,增加参数传入md5:define(id, factory, md5) ,使其将factory源码和版本号存入localstorage,文件的md5值,可以结合打包工具实现,fis中的file对象有个方法是file.getHash() 可以获取md5值,这个值还需要存入resourcemap,用于比较缓存中的version和下发的resourcemap是否一致,如果不一致则需要重新拉最新版本。

localstorage中缓存的内容是factory的源码,加上version(hash):

//代码示例如下
//找到resourcemap中的id
var map = resouceMap[id];
//拼缓存的数据
var content = {
    version: map.hash,
    //code是factory的内容
    code: factory.toString()
}
localStorage[id] = JSON.stringify(content)

resourcemap的改造

这个很简单,增加个字段:hash

字段
id 唯一id
uri 线上cdn完整url
hash 文件md5
deps 依赖的模块

对文件进行改造

为了防止每个页面都是用全局的resourcemap(fis的map.json),对于单个页面文件需要输出自己的依赖关系表resourcemap,减少页面的大小,这部分工作也是通过打包工具实现的。

打包工具将每个页面的依赖关系遍历出来,然后输出到每个页面,例如fis3的做法是:http://fis.baidu.com/fis3/docs/lv3.html#%E9%9D%99%E6%80%81%E8%B5%84%E6%BA%90%E6%98%A0%E5%B0%84%E8%A1%A8

require函数的改造

require函数是获取模块依赖关系,没有的则加载模块,优先加载依赖的模块,等依赖模块加载完毕后,再遍历向上加载,保证模块代码执行的时候,该模块依赖的模块都已经加载完毕。

前端资源动态渲染模式介绍之combo篇

今天继续介绍前端资源动态渲染模式中的combo模式,combo模式是利用静态服务器的combo服务,结合静态分析页面使用的js或者css文件,然后动态输出combo url地址的方式。

静态资源combo服务

公司静态集群使用的是nginx服务,nginx有个concat模块可以将url进行打包。使用它之后,需要合并输出的静态资源需要在??两个问号后面加,逗号隔开,例如:

http://baidu.com??style1.css,style2.css
http://box.bdimg.com/life/js??script1.js,script2.js

当然这种合并的文件数也是有限的,如果超过默认或者设置的最大文件数,服务就会报错,可以通过修改nginx.conf的配置进行修改:

location /static/ {
    concat on;
    concat_max_files 20;
}

combo渲染模式

前文介绍过基本原理,现在就拿smarty模板的{% require name="life:js/demo" %}说下具体的代码实现步骤,其他语言参考即可,我们还有个node版本的,node我们采用了yog2框架,其实是一样的,如果是直接php,其实就是个引入的函数而已(从第二步开始)

  1. 执行{% require name="life:js/demo" %},进入smarty的扩展语法require标签的实现
  2. 实际执行的是 Resource.class的load函数:Resource::load('life:js/demo')
  3. load函数根据传入的id(life:js/demo),读取life-map.json(fis生成的静态资源表),根据id找到类似下面的内容:
    "life:js/demo":{
    "uri":"http://s.box.bdimg.com/lf/js/demo_defb566.js",
    "type":"js",
    "deps":["common:bdbox/template"],
    "rUri":"/static/lf/js/demo_defb566.js",
    "hash": "defb566"
    }
    

JavaScript sdk(jssdk)设计指南

今天看到一篇《javascript sdk设计指南》,内容篇幅比较多,很多实际是问题的枚举,但是信息量太大,所以我结合之前做微博开放平台和运营活动平台的经验,说下jssdk的设计和一些核心问题的解决方案。

一个jssdk一般是指提供给第三方人员使用的一段js,通过这个js实现一些平台化产品提供的服务,比如微博的jssdk。整个jssdk的设计有一下几个核心问题:

  • 代码如何被使用页面接入
  • 如何实现跨域通信
  • 如何实现优雅api的设计
  • 公共资源的使用
  • 代码组件化

先说第一个问题

代码如何被使用页面接入

这个问题涉及到几个小问题需要讨论:

  • 命名空间
  • 样式冲突
  • 版本维护
  • appid等参数的传入

命名空间

在「命名空间」部分,需要做到不污染环境,保护好自己,即不要对本来的页面造成命名的破坏,只是用一个命名空间,又要考虑到第三方页面的复杂性,防止跟错综复杂的命名空间冲突。

要做到这点,需要我们在命名空间命名的时候多注意下,尽量不要使用业内通用的命名方法,比如驼峰,名字尽量起的怪一些,偏一些,一般,要么使用_开头(甚至多个),要么使用项目代号这些不太被别人想到的名字,嗯,我记得有人命名空间用av,很好呀!

还有一种方式是动态的命名空间,在url中带上namespace=xxx,本节结束后面会统一给出示例

样式冲突

除了js命名空间问题,如果jssdk带有UI组件,那么还需要考虑css的样式冲突问题,这里不用多说,记住以下几点:

  • 一些复杂的widget可以使用iframe方式引入
  • 不使用id
  • 使用带前缀的class命名,前面用一个class最好包裹
  • 自己做reset!
  • 跟js相关的class要有特殊的约定(比如_J-xxx )或者使用data-id代替

其实利用sass、less这些预编译语言很容易

例如下面的代码:

$name: avUI;

.#{$name}__dialog{
    @include reset();
    .#{$name}__dialog__header{
        color: white;
    }
}

版本维护

版本维护的目的是保证代码最新,功能最全,而不用每次做了升级,通知所有使用的第三方开发者把自己页面的代码挨个更换。所以这里版本维护不应该暴漏给使用者,比如在url使用版本号,到了2.0版本,通知使用方替换,这是不合理的,总有些公司或者人不配合的。最好的方法是设计的时候就要考虑到这个问题。

一般有两种比较好的方式:

  • 小拖大,动拖静:即第三方引入的js是一个动态的,或者没有缓存没有cdn的,然后由它带出后面的cdn
  • 隔段时间动态创建script

推荐使用「小拖大,动拖静」,后面介绍组件化也要使用这个方式来按需加载代码

小拖大,动拖静

核心代码示例

(function(){
    .....
    var url = '最新版本cdn的地址';
    load(url);
}())

隔段时间动态创建script

代码示例:

(function () {
    var s = document.createElement('script');
    s.type = 'text/javascript';
    var t = +new Date;
    t -= %864E5;
    s.src = 'http://xxx.com/sdk.js?t='+t;
    var x = document.getElementsByTagName('script')[0];
    x.parentNode.insertBefore(s, x);
  })();

前端资源动态渲染模式介绍之概览篇

在手百我们使用了一种动态管理静态资源的方式,在开发中通常打包工具在打包构建的时候根据页面依赖将所有的js或者css打成一个app.js或者app.css,这样的打包方式我们称之为「静态打包」,因为只能在项目发布构建的过程中一次性将页面用到的js或者css打包成一个文件,上完线之后就不能在拆分。

这种「静态打包」的方式有个弊端:如果在多个view的页面,是不能够充分利用不同页面之间的公用代码(模块)。于是加强版就是在线统计一个网站(项目)模块依赖和公用的代码,然后设置一个阈值,利用统计的数据,分析出多个页面高频使用的公共代码,再下次上线的时候,将这部分代码打包在一起。这种方式百度内部也有类似的解决方案,但是这种方案也没有充分发挥出细粒度模块的作用,而且实现相对来说比较麻烦,需要长期数据统计支持。

今天我介绍下手百中我们实现的一种动态管理静态资源的方式,我将它称之为「前端资源动态渲染模式」,简称「渲染模式」。这种方式是基于手百现有业务出发的,解决了手百不同平台(安卓、ios、winphone)的代码差异,而且充分利用缓存、combo服务,做到可配方便调试的目的。

前端资源动态渲染模式特点

代码永远只写一份,不需要编写特殊逻辑

我们实现的渲染模式,对于代码开发者来说,不需要做特殊的逻辑编写,只需要按照正常编码要求来写代码,剩下的就交给打包工具和上线流程,对于初学者不需要任何的学习门槛,而且支持本地静态资源和外部静态资源混用。

能够根据不同平台,不同后端业务逻辑,动态打包不同代码

后端业务逻辑不通,则需要的js或者css文件不通,之前的做法是一个js里面包含了所有的代码逻辑,现在只需要如下代码:

{% if($isiOS) %}
    {% require name="life:ios/invoke.js" %}
{%else%}
    {% require name="life:android/invoke.js" %}
{% endif%}

这个是动态逻辑解析后的执行,所以编译阶段的静态打包是完全解决不了这个问题的。

结合combo服务或者localstorage做缓存优化

利用静态资源的combo服务和浏览器的localstorage存储,我们可以把渲染模式进一步优化,最简单的方式是combo渲染模式,动态分析完页面依赖之后,将url拼接成combo url一次输出