FIS-PLUS

fis-plus 是扩展自FIS的前端集成解决方案。其提供 后端框架、前端框架、自动化工具、辅助开发工具等开发套件

FIS-PLUS 解决方案

Welcome to the fis-plus wiki!

FIS-PLUS 是基于 FIS,应用于后端是 PHP,模板是 Smarty 的场景。现在被大多数百度产品使用。

fis-plus@0.8.2

  • fisp install 调整为 fisp init
  • fisp install 被调整用作组件支持,可以方便安装组件
  • 支持 node v0.12.0

feature

~

asciicast

快速入门

安装

fis-plus 的 自动化 / 辅助开发工具 被发布为一套 npm 包,它对环境的要求是:

  • 操作系统:任何能安装 nodejs 的操作系统
  • node版本:>= v0.8.0
  • jre版本:>= v1.5.0 【如果不需要本地调试服务器,可以忽略java环境要求】
  • php-cgi版本:>= v5.0.0 【如果不需要本地调试服务器,可以忽略php-cgi环境要求】

如果是百度 OCEAN 开发机器 ,直接执行以下脚本即可,所有 node & fis 的环境都被安装了;

bash -c "$( curl http://fedev.baidu.com/~fis/install.sh -k )" && source ~/.bashrc

安装 nodejs

npm

npm 是 nodejs 的包管理工具。安装 nodejs 后,npm 就自动一起安装了。

  • 用nodejs写的模块都发布在npm上。npm 网站
  • 用户需要使用npm install命令来安装nodejs模块。更多npm使用,执行 npm -h 来查看
  • 由于npm经常被墙,安装fis的时候会出现速度过慢,或者安装不上的问题
  • 可以通过 npm的 --registry 参数指定仓库。指定国内的npm镜像来解决npm被墙的问题。

例如:

npm install <some npm module> -g --registry=镜像

安装 fis-plus

nodejs 安装好后,命令行执行

npm install -g fis-plus

安装好 fis-plus 之后,执行 fisp -v,如果能看到以下信息,则表明安装成功。 如果安装过程中遇到什么问题,可以到 https://github.com/fex-team/fis-plus/issues?state=open 提问题,或者 QQ 询问。

安装 lights

lights 是 fis 提供的包管理工具,托管了 fis 所有资源。是使用 fis 的时候,必不可少的利器。

npm install -g lights

安装 Java

安装 php-cgi

  • mac 安装

    mac 下安装 php-cgi 有多种方法,这里只介绍比较简单的两个方法;

    • 用brew安装
    • 直接下载安装 XAMPP

      用 brew 安装

      如果安装了 xcode,那么推荐使用 brew 来安装 php。详细使用方法见 官网,这里只说明如何装 php-cgi;

      $ brew install php55 --with-cgi
      

      如果安装提示没有 php55,请用 brew tap homebrew/homebrew-php 后再安装

      如上,方法很简单,等安装成功后即可使用;

      直接下载安装 XAMPP

      XAMPP 官网 下载 Mac 版本,双击安装;等安装成功后需要把 XAMPP 的 bin 目录设置到 环境变量里面;

    • 使用 zsh

      $ echo 'export PATH=/Applications/XAMPP/bin:$PATH' >> ~/.zshrc
      $ source ~/.zshrc
      
    • 使用 bash

      $ echo 'export PATH=/Applications/XAMPP/bin:$PATH' >> ~/.bashrc
      $ source ~/.bashrc
      
  • windows 安装

    windows 安装方法很多,下面介绍最简单的一种。

    直接安装 xampp

    地址:[http://www.apachefriends.org/zh_cn/index.html]

    • 下载xampp并安装,将xampp/php路径加入环境变量中。
    • 就安装了php和php-cgi。
    • cmd命令行输入php-cgi -v ,就看到php-cgi版本号。php-cgi就装好啦
  • linux 安装

安装 JAVA 和 php-cgi 是由于 fis-plus 内置了 jetty 服务框架来解析 php 脚本,如果你自己有本地 Web 服务,可以用你自己的, 详见

示例

以下示例都是在命令行下操作的,如果你是 windows 用户,请打命令的时候忽略命令前的 $,而且请打开cmd 来执行这些操作

已经准备好了一个 fis-plus 的前端项目,只需要经过以下四步,就可以完整运行这个项目,并看到结果。

  • 初始化本地模拟环境
  • 下载Demo
  • 发布
  • 预览

初始化本地模拟环境

为了前后端开发分离,来并行开发,fis-plus 提供了一套本地环境模拟的工具,安装并初始化它后就能方便的模拟线上环境了。

$ fisp server init

### 下载 Demo

FIS 的所有示例及其组件都用包管理工具 lights 进行管理,使用 lights 安装 demo。

$ lights install pc-demo

其实 fisp 已经集成了 lights 的客户端。

和上面等值的用法;

$ fisp init pc-demo
fisp <= 0.7.5

$ fisp install pc-demo

如果下载失败,可直接从 GitHub 下载,https://github.com/fex-team/fis-plus-pc-demo

发布

$ cd pc-demo
$ fisp release -r common
$ fisp release -r home

预览

$ fisp server start #启动服务器

启动服务的时候,启动成功后会自动打开浏览器,访问首页,这时候你应该打开 demo 首页,并和下图是一致的。

示例解说

自此,一个前端项目已经运行起来了。你可以看一下 pc-demo 的源码,其中包含两个模块

  • common
  • home

模块 这个词会贯穿整个文档,以及整个 fis-plus 的使用。为什么会有模块这种东西? 当前端代码很多时,不得不面临分组件,分页面。为了发布迭代方便,不得不把它们分为不同的子系统。比如用户信息、首页、详情页等等。

模块 就是一个子系统,而在 fis 项目中用 namespacefis-conf.js来区分。每一个模块会有一个配置文件fis-conf.js,还会取名不同的namespace。这主要是为了区分模块之前的静态资源。

继续进入 home 模块,可以看看有几个目录及其文件

.
├── fis-conf.js
├── page
├── server.conf
├── static
├── test
└── widget

无疑,这就是使用 fis-plus 需要遵循的目录规范,为什么要有目录规范,可能在网上可以找到很多答案,这里就不再赘述。

说一下这几个目录所代表的意思;

  • page 页面模板
  • widget 组件,模板组件,JS组件,CSS组件,会被组件化
  • static 这个目录下放一些不需要组件化的公共库,比如lazyload.js
  • test 放置一些测试数据,和page下的模板相对应,表明哪个模板用哪个数据文件进行渲染
  • server.conf 这是一个很有用的文件,它里面可以配置url转发,可以方便在本地模拟ajax请求等。

细心的你有可能发现了一个比较专业的词汇 组件化,组件化的细节比较繁多,准备新开一节说明

page 如何引入 widget

  • 如示例中 index.tpl 如何应用 widget/header/header.tpl ? 如果用过 smarty 的你可能会想到include,但在 FIS-PLUS 中引入 widget 有个跟include 类似的插件完成,widget

    {%widget name="home:widget/header/header.tpl"%}
    

    home:widget/header/header.tpl 这个是 FIS 中 静态资源 id

模板中如何使用 widget 目录下的静态资源

根据目录规范 widget 目录下的 js 将被进行 组件化封装 ,根据 同名依赖 原则;

  • 当使用某个widget下模板时,同名js,css将会被加载。
  • 当使用某个widget下的js时,其同名css会被加载。
加载 JS

由于 js 进行了组件化封装,比如通过 require 或者 require.async 函数来执行其中逻辑。

{%script%}
require('/widget/a.js');
{%/script%}

如上,在模板中使用 widget 下的 js,必须放到 {%script%} {%/script%} 之间,用它来代替 js 的内联用法。

加载 css

说完加载 js 的方法,css 如何引入呢?

  • 同名依赖,被依赖
  • 通过smarty的require插件
{%require name="home:widget/a.css"%}

静态资源 id

静态资源 id 会贯穿整个用户文档,跟使用密切相关,所以它很重要。现在需要弄清楚两件事情

  • 静态资源id是如何在fis-plus中计算的?
  • 静态资源id在那些情况下使用,那些情况下必须用静态资源id?
静态资源 id 是如何在 fis-plus 中计算的?

fis-plus 必须要指定模块的 namespace,所以静态资源 id 被标记为

namespace:< 资源相对于模块根目录的路径 >

比如:

  • home/static/a.js home目录为模块跟目录,homenamespace,则静态资源id就为 home:static/a.js
    • namespace可以为任意值,可以不跟模块根目录相同。
静态资源 id 在那些情况下使用,那些情况下必须用静态资源 id?
  • 使用widgetrequirehtmlsmarty插件时,必须指定资源的id
  • requirerequire.asyncJavaScript函数,可以使用id

本地开发

本地开发主要是一些fis-plus指定的目录规范的讲解以及提供的js和css组件化开发和Smarty模板中提供的一些插件的使用等。

目录规范

规范是辅助用户开发的利器,按照规范进行开发,可以极大的提升开发效率。 在 FIS 中,定义了一套默认的模块化开发规范。

站点目录结构

业务功能模块化,针对常见的业务模型,抽象出以下层级关系:

  • 站点(site):一般指能独立提供服务,具有单独二级域名的产品线。如旅游产品线或者特大站点的子站点(lv.baidu.com)。
  • 模块(module):具有较清晰业务逻辑关系的功能业务集合,一般也叫系统子模块,多个子系统构成一个站点。
  • 页面(page): 具有独立URL的输出内容,多个页面一般可组成子系统。
  • 组件(widget):能独立提供功能且能够复用的独立资源,它可以是独立的JS、CSS或者是由JS、CSS和页面组成的页面碎片。
  • 静态资源(static):非组件资源目录,包括模板页面引用的静态资源和其他静态资源(favicon,crossdomain.xml等)。
  • 插件(plugin): 模板插件目录(可选,对于特殊需要的产品线,可以独立维护)。
  • 测试数据(test): 页面对应的测试数据目录。

FIS 规范定义了两类模块: common 模块 业务模块

  • common模块: 为其他业务模块提供规范、资源复用的通用模块。
  • 业务模块: 根据业务、URI等将站点进行划分的子系统站点模块。

站点整体目录结构示意:

|---site
|     |---common #通用子系统
|     |      |---config #smarty配置文件
|     |      |---page #模板页面文件目录,也包含用于继承的模板页面
|     |            └── layout.tpl
|     |      |---widget #组件的资源目录,包括模板组件,JS组件,CSS组件等
|     |      |     └── menu   #widget模板组件
|     |      |     |    └── menu.tpl
|     |      |     |    └── menu.js
|     |      |     |    └── menu.css
|     |      |     └── ui   #UI组件
|     |      |          └── dialog  #JS组件
|     |      |          |    └──dialog.js
|     |      |          |    └──dialog.css
|     |      |          └── reset #CSS组件
|     |      |               └── reset.css
|     |      |---static #非组件静态资源目录,包括模板页面引用的静态资源和其他静态资源
|     |      |---plugin #模板插件目录(可选,对于特殊需要的产品线,可以独立维护)
|     |      |---fis-conf.js #fis配置文件
|     |---module1 #module1子系统
|     |      |---test
|     |      |---config
|     |      |---page
|     |            └── index.tpl
|     |      |---widget
|     |      |---static
|     |      |     └── index #index.tpl模板对应的静态资源
|     |      |          └── index.js
|     |      |          └── index.css
|     |      |---fis-conf.js #fis配置文件

        ......

为什么 FIS 要按照上述模块化方式定义规范呢?

  • 模块化是一种处理复杂系统分解成为更好的可管理模块的方式,它可以把系统代码划分为一系列职责单一,高度解耦且可替换的模块,系统中某一部分的变化将如何影响其它部分就会变得显而易见,系统的可维护性更加简单易得。

  • 想了解的更多,可以查看 FIS 模块化

路径说明

  1. 页面 (page):存放在 模块根目录 /page 下,url 访问路径为 / 模块名 /page/ 页面名,例如 path_to_user_module/page/view.tpl,访问 url 为:/user/page/view。页面静态资源存储的位置为:

     tpl :path_to_module/page/页面名.tpl
      js :path_to_module/page/页面名.js
     css :path_to_module/page/页面名.css
    
  2. css 组件 :一般来说,CSS 组件是最简单的组件,它的存储方式为:

     #widget目录下的css文件皆为css组件,建议存放在widget/ui目录下
     css :path_to_module/widget/ui/组件名/组件名.css
    
  3. js 组件 :支持 AMD 规范的 js 组件,js 组件存储的方式为:

     #widget目录下的js文件皆为js组件,建议存放在widget/ui目录下
     js :path_to_module/widget/ui/组件名/组件名.js
    
  4. 模板组件 :存放在 模块根目录 /widget 下,每个 widget 包含至少一个与 widget 目录 同名 的 tpl,同时可以有与 widget 同名 的 js、css 作为其静态资源。组件存储方式为:

     tpl :path_to_module/widget/组件名/组件名.tpl
      js :path_to_module/widget/组件名/组件名.js
     css :path_to_module/widget/组件名/组件名.css
    
  5. 配置文件 (fis-conf.js):fis 配置文件存放在模块根目录下 path_to_user_module/fis-conf.js ,smarty 配置文件存放在:

     conf:path_to_module/config/模块名/
    
  6. smarty 插件 :与 smarty 插件相关的都存放在 plugin 目录下,存储位置为:

     插件:path_to_module/plugin/
    
  7. 测试数据 (test):fis 开发环境允许在本地开发中设置测试数据进行调试,测试数据以页面模板为单位进行组织,其存储方式为:

     tpl:path_to_module/page/模块名/页面名.tpl
     data:path_to_module/test/page/页面名.json(或php)
    

组件化

模块的 widget 目录默认为组件目录,组件化按照代码的组织方式,分为以下三种:

  • CSS 组件:独立 css 代码片段。可以被其他 css,js,模板引用。

  • JS 组件:独立 js 代码片段,JS 组件可以封装 CSS 组件的代码。

  • 模板组件:涉及代码最多,有模板代码,JS 代码,css 代码和 HTML 代码。 建议,模板组件中的 js 仅被这个 widget 使用,保持 widget 的独立。

下面的文档会详细介绍组件的使用方式。

自定义特殊目录规范

假设有自定义特殊目录规范的需求,比如某些图片需要挪动到自定义的目录下时,需要通过如下方式设置, 切记

具体设置属性参考 roadmap.path 设置

fis.config.set('roadmap.path', [
    { // 规则1
        reg: '匹配文件正则或者glob',
        release: '文件想产出的路径'
    },
    { // 规则2
        reg: '匹配文件正则或者glob',
        release: '文件想产出的路径'
    }
].concat(fis.config.get('roadmap.path', [])));

三种语言能力

  • html、css、javascript扩展语言能力,请参见三种语言能力
  • Smarty提供的三种语言能力

Smarty 三种语言能力

在 fis-plus 中使用 Smarty 插件的方式也实现了 三种语言能力 requireuri 以及编译期的widget_inline

  • {%require%}

    • 解释:当前文件添加依赖,是一个Smarty运行时函数
    • 参数:namesrc
    • 例子:

        {%require name="common:static/lib/jquery.js"%}
        或
        {%require src="http://www.baidu.com/cdn/jquery.js"%} {%*外链*%}
      
  • {%uri%}

    • 解释:运行时动态定位某一个资源,是一个Smarty运行时函数
    • 参数:name
    • 例子:

        <script src="{%uri name="common:static/lib/jquery.js"%}" type="text/javascript"></script>
      
  • widget_inline
    • 解释:widget_inline是一个fis编译插件,可以认为是一个fisp编译时功能,意在内嵌一些widget模板,降低线上解析smarty带来的IO开销。
    • 例子:

模板开发

使用如下方式获取最新 Smarty 插件

git clone https://github.com/fex-team/fis-plus-smarty-plugin plugin 后进入 plugin 目录 删除.git 目录

点击下载

页面模板

在模块中,用户可直接访问浏览的页面称为页面模板,文件在 模块根目录 /page/ 下。FIS 提供提供了很多模板插件替换原生 html 标签,为页面模板开发提供使用。

  • 通过html 插件控制整体页面的输出,以及注册前端组件化框架。
  • 通过head 插件在模板解析运行时,控制加载同步静态资源使用。
  • 通过body 插件可在页面底部集中输出JS静态资源。

从 demo 中 common 模块的 layout.tpl,可以了解到如何通过后端框架进行开发,组织整个页面:

<!DOCTYPE html>
{%* 使用html插件替换普通html标签,同时注册JS组件化库 *%}
{%html framework="common:static/mod.js" class="expanded"%}
    {%* 使用head插件替换head标签,主要为控制加载同步静态资源使用 *%}
    {%head%}
    <meta charset="utf-8"/>
        <meta content="{%$description%}" name="description">
        <title>{%$title%}</title>
        {%block name="block_head_static"%}{%/block%}
    {%/head%}
    {%* 使用body插件替换body标签,主要为可控制加载JS资源 *%}
    {%body%}
    {%block name="content"%}{%/block%}
    {%/body%}
{%/html%}
  • 通过require 插件加载静态资源,便于静态资源管理。
  • 通过script 插件管理JS片段,集中在页面底部加载。
  • 通过widget 插件调用模板组件组织页面,处理对应的静态资源。

在 demo-home 模块中的 index.tpl,加载页面模板对应的静态资源,通过模板组件组织页面:

{%extends file="common/page/layout.tpl"%}
{%block name="block_head_static"%}
    <!--[if lt IE 9]>
        <script src="/lib/js/html5.js"></script>
    <![endif]-->
    {%* 模板中加载静态资源 *%}
    {%require name="home:static/lib/css/bootstrap.css"%}
    {%require name="home:static/lib/css/bootstrap-responsive.css"%}
    {%require name="home:static/lib/js/jquery-1.10.1.js"%}
{%/block%}
{%block name="content"%}
    <div id="wrapper">
        <div id="sidebar">
            {%* 通过widget插件加载模块化页面片段,name属性对应文件路径,模块名:文件目录路径 *%}
            {%widget
                name="common:widget/sidebar/sidebar.tpl"
                data=$docs
            %}
        </div>
        <div id="container">
            {%widget name="home:widget/slogan/slogan.tpl"%}
            {%foreach $docs as $index=>$doc%}
                {%widget
                    name="home:widget/section/section.tpl"
                    call="section"
                    data=$doc index=$index
                %}
            {%/foreach%}
        </div>
    </div>
    {%require name="home:static/index/index.css"%}
    {%* 通过script插件收集JS片段 *%}
    {%script%}var _bdhmProtocol = (("https:" == document.location.protocol) ? " https://" : " http://");
document.write(unescape("%3Cscript src='" + _bdhmProtocol + "hm.baidu.com/h.js%3F70b541fe48dd916f7163051b0ce5a0e3' type='text/javascript'%3E%3C/script%3E"));{%/script%}
{%/block%}

在模板框架中,对应的文件调用路径均为 modulename: 相对模块根目录的相对路径

//home为模块名,static/lib/css/bootstrap.css为绝对路径截取home模块根目录后的路径
{%require name="home:static/lib/css/bootstrap.css"%}
前端模板

在 FIS 中,集成了 百度前端模板。在编译过程中,会静态编译 baidu template,不需要线上编译,提高页面运行效率。

在 JS 代码中,通过 __inline 方式进行编译处理前端模板。同时规定以 tmpl 为后缀的文件为前端模板,使用方式:

//编译前
var demoTemplate = __inline('demo.tmpl');
//编译后
var demoTemplate = function (_template_object) {
       .......
};

在使用前端模板时需要注意的问题:

//在__inline前端模板前加载template对象
require("common:static/lib/template.js")
//或者通过全局加载的方式
{%require name="common:static/lib/template.js"%}

template 的安装方式

//在common的static/lib目录下执行下面命令
$ lights install template

模板组件化

模板组件是能独立提供功能且能够复用的页面片段,所在规范目录 模块根目录 /widget/ ,模板组件以模板、JS 组件、CSS 组件组成(默认必须有模板)。

widget 定义

只要在 widget 目录下的 Smarty 模板即为模板组件, 目录下有与模板同名的 JS、CSS 文件 FIS 会自动添加依赖关系处理,在模板渲染时进行同步加载

widget 调用

调用一个模板组件需要通过 widget 语法,如我们想调用 home 下的 section 模板组件,则语法为

//调用模板的路径为 modulename:模板在widget目录下路径
{%widget name="home:widget/section/section.tpl" %}

widget 函数调用方式

widget 插件可以直接调用某个 smarty 的 function 函数,使用方式为:

//在模板中定义function函数
{%function name="functionDemo"%}
       .............
{%/function%}
------------------------------------
//在模板中调用此函数
{%widget call="functionDemo" %}

Smarty 插件

FIS-PLUS 提供一套基于 smarty(version >= 3.1.13) 插件的后台运行框架,针对静态资源管理、模板模块化、pagelet 等功能。

html

  • 功能:代替\标签,设置页面运行的前端框架,以及控制整体页面输出。
  • 属性值:framework及html标签原生属性值
  • 是否必须:是
  • 用法:在模板中替换普通\标签
{%html framework="home:static/lib/mod.js"%}
    ....
{%/html%}

head

  • 功能:代替\标签,控制CSS资源加载输出。
  • 属性值:head标签原生属性值
  • 是否必须:是
  • 用法:在模板中替换普通\标签
{%html framework="home:static/lib/mod.js"%}
    {%head%}
        <meta charset="utf-8"/>
    {%/head%}
{%/html%}

head

body

  • 功能:代替\标签,控制JS资源加载输出。
  • 属性值:body标签原生属性值
  • 是否必须:是
  • 用法:在模板中替换普通\标签
{%html framework="home:static/lib/mod.js"%}
    {%head%}
        <meta charset="utf-8"/>
    {%/head%}
    {%body%}
        ....
    {%/body%}
{%/html%}

body

script

  • 功能:代替<script>标签,收集使用JS组件的代码块,控制输出至页面底部。
  • 属性值:无
  • 是否必须:在模板中使用异步JS组件的JS代码块,必须通过插件包裹
  • 用法:在模板中替换普通<script>标签
{%html framework="home:static/lib/mod.js"%}
    {%head%}
       <meta charset="utf-8"/>
       {*通过script插件收集加载组件化JS代码*}
       {%script%}
           require.async("home:static/ui/B/B.js");
       {%/script%}
    {%/head%}
    {%body%}
        ...
    {%/body%}
{%/html%}

script

require

  • 功能:通过静态资源管理框架加载静态资源。
  • 插件类型:compiler
  • 属性值:name(调用文件目录路径,与src属性二选一) | src(调用线上资源,与name属性二选一)
  • 是否必须:是
  • 用法:在模板中如果需要加载模块内某个静态资源,可以通过require插件加载,便于管理输出静态资源
{%html framework="home:static/lib/mod.js"%}
    {%head%}
       <meta charset="utf-8"/>
       {*通过script插件收集加载组件化JS代码*}
       {%script%}
            require.async("home:static/ui/B/B.js");
       {%/script%}
    {%/head%}
    {%body%}
        {%require name="home:static/index/index.css"%}
        ...
    {%/body%}
{%/html%}

require

widget

  • 功能:调用模板组件,渲染输出模板片段。
  • 插件类型:compiler
  • 属性值:name(调用文件目录路径)
  • 是否必须:是
  • 用法:在模板中调用某个模板组件
{%html framework="home:static/lib/mod.js"%}
    {%head%}
       <meta charset="utf-8"/>
       {*通过script插件收集加载组件化JS代码*}
       {%script%}
            require.async("home:static/ui/B/B.js");
       {%/script%}
    {%/head%}
    {%body%}
        {%require name="home:static/index/index.css"%}
        {%widget name="home:widget/A/A.tpl"%}
    {%/body%}
{%/html%}

widget

JS & CSS

JS 组件化

> 在前端开发中,JS资源占了很大一部分比例,在FIS中,我们将JS资源分为组件和非组件类。组件类JS资源可以通过前端组件化框架进行资源加载,同时会进行组件化包装。非组件类JS资源,用户可以通过同步script标签加载方式或通过require插件方式加载。

模块根目录 /widget/ 下的 JS 资源皆为组件化资源,可以通过 requirerequire.async 进行调用,则在编译处理过程中会进行 组件化封装

只有 widget 目录下的资源进行了组件化封装,可以在 js 中通过 requirerequire.async 调用。其他目录下的资源除非进行了组件化的封装,不然是无法用这两个函数进行调用的。

mod.js 前端组件化框架

mod.js 是一套的前端模块加载解决方案。与传统的模块加载相比,ModJS 会根据产品实际使用场景,自动选择一种相应的方案,使最终的实现非常轻量简洁。

作为 FIS-PLUS 前端组件化框架,mod.js 和 AMD (require.js) 或者 CMD (seajs) 等都是互斥的, 无法同时在同一个项目中使用

我们可以到 https://github.com/fex-team/mod 下载最新版本的 mod.js

同时在开发中需要使用 mod.js,则需要通过 模板插件语法 html进行注册。

组件化封装

modjs 使用 define 来定义一个模块( 类似 nodeJS 中的 module):

/**
 * @param {String} id 唯一标识
 * @param {Function} factory  factory提供了3个参数,require, exports, module ,用于模块的引用和导出。
 */

define (id, factory)
  • Example

      define('module/id', function (require, exports, module) {
          exports.run = function() {
              //....
          };
      });
    

在平常开发中,我们只需写 factory 中的代码即可,无需手动定义模块 。发布工具会自动将模块代码嵌入 factory 的闭包里。

在编译处理过程中会对 widget JS 文件进行组件化 define 封装,如;

如果不是 widget 目录下的资源也想让工具自动封装,请查询文档 roadmap.path isMod 属性。

  • JS 源码:

      //common/widget/menu/menu.js
      var $ = require('common:widget/jquery/jquery.js');
    
      exports.init = function() {
          $('.menu-ui ul li a').click(function(event) {
              var self = this;
              $('.menu-ui ul li a.active').removeClass('active');
              $(self).addClass('active');
              event.preventDefault();
          });
      };
    
  • 编译后代码

      define('common:widget/menu/menu.js', function(require, exports, module){
          var $ = require('common:widget/jquery/jquery.js');
          exports.init = function() {
              $('.menu-ui ul li a').click(function(event) {
                  var self = this;
                  $('.menu-ui ul li a.active').removeClass('active');
                  $(self).addClass('active');
                  event.preventDefault();
              });
          };
      });
    
  • 自动封装的 ID

    自动封装的 ID 是根据源码路径计算的,id = <namespace>:<file.subpath>

      // namespace = 'common'
    
      proj/widget/a.js
      // id = 'common:widget/a.js'
    
      proj/widget/foo/foo.js
      // id = 'common:widget/foo/foo.js'
    
组件化调用
  • modJS 的发布工具会保证你的程序在使用之前,所有依赖的模块都已加载。因此当我们需要一个模块时,只需提供一个模块名即可获取:

    require (id)

      //id可为相对路径,或FIS中组件调用路径 模块名:文件所在widget中路径
      require("common:widget/ui/a/a.js")
    

因为所需的模块都已预先加载,因此 require 可以立即返回该模块。

  • 考虑到有些模块无需在启动时载入,因此 modJS 提供了可以在运行时 异步加载模块的接口

    require.async (names, callback)

    names 可以是一个 id,或者是数组形式的 id 列表。

    当所有都加载都完成时,callback 被调用,names 对应的模块实例将依次传入。

    使用 require.async 获取的模块不会被发布工具安排在预加载中,因此在完成回调之前 require 将会抛出模块未定义错误。

      //id可为相对路径,或FIS-Plus中组件调用路径 模块名:文件所在widget中路径
      require.async(["common:widget/menu/menu.js"],function(menu){
            menu.init()
      })
    
  • 了解更多 mod.js

其他 JS

在非 widget 目录下的 JS 资源,皆为非组件化资源。用户可以通过 script 标签、require 插件 等方式进行调用.

模板组件静态资源

与模板组件同名的静态资源,FIS 会自动添加依赖关系,同时会对 JS、CSS 进行同步加载。

tpl :模板根目录/widget/widgetName/widgetName.tpl
js :模板根目录/widget/widgetName/widgetName.js
css :模板根目录/widget/widgetName/widgetName.css

CSS 组件化

在前端开发中,CSS 资源占了很大一部分比例,在 FIS 中,我们将 CSS 资源分为组件和非组件类。CSS 组件会绑定到同名 JS 组件、模板组件上,进行加载管理,用户不需要关心加载方式;非组件类 CSS 资源通过 link 标签、require 插件方式进行加载。

模块根目录 /widget 目录下的 CSS 资源,皆为组件。在模板组件中以及 JS 组件中对应同名的 CSS 组件会自动与模板组件、JS 组件添加依赖关系,进行加载管理,用户不需要显示进行调用加载。

其他 CSS

在非 widget 目录下的 CSS 资源,皆为非组件化资源。用户可以通过 link 标签、require 插件 等方式进行调用.

模板组件静态资源

与模板组件同名的静态资源,FIS 会自动添加依赖关系,同时会对 JS、CSS 进行同步加载

tpl :模板根目录/widget/widgetName/widgetName.tpl
js :模板根目录/widget/widgetName/widgetName.js
css :模板根目录/widget/widgetName/widgetName.css
Less 资源

在 FIS 中默认配置了对 less 资源处理插件,less 文件编译处理后变为 css 文件。

编译优化工具

模板 XSS 修复工具

  • 功能:对Smarty模板进行XSS校验修复。
  • 默认开启:true(已默认配置)
  • 配置:
fis.config.set('modules.optimizer.tpl', 'smarty-xss');

fis.config.set('settings.optimizer.smarty-xss', {
    'escapeMap' : {
        'js' : 'f_escape_js',  //配置js_escape指定js转义插件的名称
        'html' : 'f_escape_xml', //配置html_escape指定对于xml转义插件的名称
        'data' : 'f_escape_data', //配置data_escape指定data转义插件的名称
        'path' : 'f_escape_path', //配置path_escape指定url、path转义插件的名称
        'event' : 'f_escape_event', //配置event_escape指定event转义插件的名称
        'no_escape' : 'escape:none' //不需要添加转义的变量标志
    },
    'leftDelimiter' : '{%', //smarty左定界符
    'rightDelimiter' : '%}', //smarty右定界符
    'xssSafeVars' :[          //配置白名单
        'fis_safe'      //支持正则
    ]
});

图片合并工具

  • 功能:对
  • 环境要求:依赖native插件,node-images 环境需要符合个插件的要求。(OS X、Windows提供了二进制包)
  • 默认开启:true
  • 使用文档:用户文档
  • 配置:
fis.config.set('settings.spriter.csssprites', {
    //图之间的边距
    margin: 10
});

模板压缩工具

  • 功能:对Smarty模板进行压缩。
  • 默认开启:true(已默认配置)
  • 配置:
fis.config.set('modules.optimizer.tpl', 'html-compress');

JS 压缩工具

  • 功能:通过uglify-js对JS文件进行压缩。
  • 默认开启:true(已默认配置)
  • 配置:
fis.config.set('modules.optimizer.js', 'uglify-js');

CSS 压缩工具

  • 功能:通过clean-css对CSS文件进行压缩。
  • 默认开启:true(已默认配置)
  • 配置:
fis.config.set('modules.optimizer.css', 'clean-css');

打包合并

FIS-Plus 提供静态资源管理系统,支持通过运行时计算静态资源使用情况来加载对应的静态资源。为了达到合理的高性能的加载方式,用户可以将静态资源进行合并。

配置

用户可以在配置文件中,通过配置将静态资源进行合并处理,对 demo-home 模块增加打包配置:

//fis-conf.js
fis.config.set('pack', {
        //打包所有static目录下的JS文件
        'pkg/aio.js' : /^\/static\/(.*\.js)$/i,
        'pkg/widget.js' : [
            /^\/widget\/ui\/(.*\.js)$/i,
            '/widget/menu/menu.js'
        ],
        //打包所有的css文件
        //将内容输出为static/pkg/aio.css文件
        'pkg/aio.css' : '**.css'
});

打包配置支持正则、字符串以及通配符,用户可灵活根据自己需求进行资源打包,同时注意以下打包细节:

  • 每个包只对应js或css类型,请勿将其他非同类文件配置合并至包内,特别是使用通配符配置时,请注意目录下是否有其他非同类型文件
  • 使用正则配置时需要添加定界符,不然将会达不到用户配置预期

输出结果:使用命令 fis release --pack --md5 --dest ./output 编译项目,然后到 output 目录下查看产出的 home-map.json 内容.fis 内置的 打包原理 与传统的打包概念不同,fis 的打包实际上是在建立一个资源表,并将其描述并产出为一份 map.json 文件,用户应该围绕着这份描述文件来设计前后端运行框架,从而实现运行时判断打包输出策略的架构。

打包策略

  • 每个资源包之间不会有重复资源,合并处理包时会根据配置节点顺序,将资源合并至资源包中。
  • 根据资源包配置的匹配规则,进行资源顺序排序。
  • 每个资源包中的静态资源,会根据文件之间的依赖关系进行自动排序。

基本打包原则

  • 根据静态资源复用率情况,可将复用率较高的可合并在一起,复用率较低的可合并一起,同时可根据使用情况对静态资源不进行打包。
  • 在common模块中,可以根据其他业务模块使用common资源情况进行打包,将复用高的资源合并打包,将个别模块使用的资源独立打包。
  • 需要独立加载的JS组件资源,可不合并在包中,运行时会自动按需加载。
  • 对匹配成功的资源会合并进资源包中,同时其依赖的资源会记录在资源表中,如果需要加载资源包时,会先加载依赖的文件。

自动打包

手工维护资源打包工作存在未能及时排除废弃资源、需持续维护、成本大等缺陷。针对此问题,FIS 提供静态资源自动合并服务自动生成资源打包配置,根据网站页面 pv 以及页面静态资源使用情况, 自动计算静态资源合并方案,减少人工管理静态资源成本和风险

自动打包使用方式与现有一致,接入简单。提供简版和高端版两个版本:

  • 简版:只需安装一个插件便可使用自动打包。在编译过程中扫描模块代码分析资源依赖,提供页面级别的打包配置,支持重点页面权重等配置
  • 高端版:基于线上资源统计计算整站最优打包方案,支持国际化等复杂使用场景。

具体使用可以进入官网查看 此文档

配置

模块配置

每个模块都有对应的模块名,同时需要在配置文件中申明,配置如下。

fis.config.set('namespace', 'common');

模块编译输出时默认都为 utf8 编码,如果需要做修改,可以进行如下配置:

fis.config.set('project.charset', 'gbk');

对静态资源进行 MD5 戳编译时,默认长度都为 7,当然你也可以进行修改:

fis.config.set('project.md5Length', 8);

Smarty 配置

在 fis-config.js 配置文件中,可在 setting 节点下配置 smarty 节点,修改定界符。

fis.config.set('settings.smarty', {
    'left_delimiter'  : '<{',
    'right_delimiter' : '}>'
})

本地调试环境 smarty 定界符修改

在模块根目录下放置 smarty.conf 将其内容设置为:

left_delimiter="<{"
right_delimiter="}>"

smarty.conf 文件为一个 ini 文件,需要保证 php parse_ini_file 能够解析它

假设有多个模块,只需要在 common 模块根目录下放置 smarty.conf

domain 配置

设置静态资源域名,domain 的值如果不是特殊需要,请不要以 "/" 结尾。

//fis-conf.js
//用法一
fis.config.set('roadmap.domain', 'http://s1.example.com, http://s2.example.com');

//用法二
fis.config.set('roadmap.domain', {
    //widget目录下的所有css文件使用 http://css1.example.com 作为域名
    'widget/**.css' : 'http://css1.example.com',
    //所有的js文件使用 http://js1.example.com 或者  http://js2.example.com 作为域名
    '**.js' : ['http://js1.example.com', 'http://js2.example.com']
});

产出目录

FIS 根据目录规范默认设置了文件的产出路径:

└── config
│    └── modulename-map.json    静态资源表
├── template
│    ├──home
│    │   └── page
│    │         └── index.tpl      page级模板文件
│    │   └── widget
│    │         └── section
│    │           └── section.tpl   widget模板文件
├── static
│    └── home
│    │   ├── pkg
│    │   │   ├── demo.css  打包css文件
│    │   │   └── demo.js   打包js文件
│    │   ├── index
│    │   │   ├── index.css  page级css文件
│    │   │   └── index.js   page级js文件
│    │   └── widget             widget组件目录
│    │       └── section
│    │               └── section.css
├── plugin
├── test

用户根据产出后的目录,只需要将 config、template、static、plugin 目录上传至联调机器进行项目联调。

上传配置

用户通过配置 deploy 节点 进行文件上传,fis 支持使用 post 请求向 http 服务器发送文件,服务器端可以使用 php、java 等后端逻辑进行接收,fis-command-release 插件中提供了一个这样的 php 版示例,用户可以直接部署此文件于接收端服务器上。

注意: receiver.php 放到测试机后,请在浏览器中直接访问该文件,保证可以访问。 可以访问的情况下,页面会显示 I'm ready for that, you know.

根据产出目录以及联调机器,进行上传配置:

上传到 1 个测试机

//上传到一个 remote测试机
fis.config.set('deploy', {
    //使用fis release --dest static来使用这个配置
    remote: [{
        //如果配置了receiver,fis会把文件逐个post到接收端上
        receiver : 'http://www.example.com/path/to/receiver.php',
        //从产出的结果的static目录下找文件
        from : '/static',
        //上传目录从static下一级开始不包括static目录
        subOnly : true,
        //保存到远端机器的/home/fis/www/static目录下
        //这个参数会跟随post请求一起发送
        to : '/home/fis/www/',
        //某些后缀的文件不进行上传
        exclude : /.*\.(?:svn|cvs|tar|rar|psd).*/
    },
    {
        //如果配置了receiver,fis会把文件逐个post到接收端上
        receiver : 'http://www.example.com/path/to/receiver.php',
        //从产出的结果的config目录下找文件
        from : '/config',
        //保存到远端机器的/home/fis/www/config目录下
        //这个参数会跟随post请求一起发送
        to : '/home/fis/www/',
        //某些后缀的文件不进行上传
        exclude : /.*\.(?:svn|cvs|tar|rar|psd).*/
    },
    {
        //如果配置了receiver,fis会把文件逐个post到接收端上
        receiver : 'http://www.example.com/path/to/receiver.php',
        //从产出的结果的template目录下找文件
        from : '/config',
        //保存到远端机器的/home/fis/www/template目录下
        //这个参数会跟随post请求一起发送
        to : '/home/fis/www/',
        //某些后缀的文件不进行上传
        exclude : /.*\.(?:svn|cvs|tar|rar|psd).*/
    },
    {
        //如果配置了receiver,fis会把文件逐个post到接收端上
        receiver : 'http://www.example.com/path/to/receiver.php',
        //从产出的结果的plugin目录下找文件
        from : '/plugin',
        //保存到远端机器的/home/fis/www/plugin目录下
        //这个参数会跟随post请求一起发送
        to : '/home/fis/www/',
        //某些后缀的文件不进行上传
        exclude : /.*\.(?:svn|cvs|tar|rar|psd).*/
    }]
});

上传到多个测试机

//上传到remote,local,remote2三台机器
//fis-conf.js
fis.config.set('deploy', {
    //使用fis release --dest remote来使用这个配置
    remote : {
        //如果配置了receiverfis会把文件逐个post到接收端上
        receiver : 'http://www.example.com/path/to/receiver.php',
        //从产出的结果的static目录下找文件
        from : '/static',
        //保存到远端机器的/home/fis/www/static目录下
        //这个参数会跟随post请求一起发送
        to : '/home/fis/www/',
        //通配或正则过滤文件,表示只上传所有的js文件
        include : '**.js',
        //widget目录下的那些文件就不要发布了
        exclude : /\/widget\//i,
        //支持对文件进行字符串替换
        replace : {
            from : 'http://www.online.com',
            to : 'http://www.offline.com'
        }
    },
    //名字随便取的,没有特殊含义
    local : {
        //from参数省略,表示从发布后的根目录开始上传
        //发布到当前项目的上一级的output目录中
        to : '../output'
    },
    //也可以是一个数组
    remote2 : [
        {
            //将static目录上传到/home/fis/www/webroot下
            //上传文件路径为/home/fis/www/webroot/static/xxxx
            receiver : 'http://www.example.com/path/to/receiver.php',
            from : '/static',
            to : '/home/fis/www/webroot'
        },
        {
            //将template目录内的文件(不包括template一级)
            //上传到/home/fis/www/tpl下
            //上传文件路径为/home/fis/www/tpl/xxxx
            receiver : 'http://www.example.com/path/to/receiver.php',
            //from支持正则
            from : '/template',
            to : '/home/fis/www/tpl',
            //subOnly仅上传
            subOnly : true
        }
    ]
});

小贴士

  • --dest 参数 支持使用逗号(,)分割多个发布配置,比如上面的例子,我们可以使用 fis release --dest remote,plugin 命令在一次编译中同时发布多个目标。

  • subOnly 参数 默认上传 from 整个目录到测试机。添加 subOnly 参数仅上传 from 目录下文件。

  • replace 替换多个字符串 需要 replace 替换多个字符串,可以使用正则的方式。例如:

replace : {
    from : /www\.a\.com|www\.b\.com/,
    to : function(m){
        if(m === 'www.a.com') return 'www.x.com';
        if(m === 'www.b.com') return 'www.y.com';
    }
}

上传常见问题

  1. 上传到一半突然中断, 怎么办?

    A: 请关闭测试机的服务器的防火墙,然后重新上传。至于服务器防火墙怎么关闭,不同服务器不一样,所以还是查文档或者 google 一下吧

上线部署

上传测试机

fis 通过简单的上传配置,把编译后文件上传到测试机, 方便联调。

发布上线

提供基础的 build.sh 编译脚本,用于上线时候自动编译项目。

#!/bin/bash

MOD_NAME="output"
TAR="$MOD_NAME.tar.gz"

# add path
export PATH=/home/fis/npm/bin:$PATH
#show fis-plus version
fisp --version --no-color

#通过fis-plus release 命令进行模块编译 开启optimize、md5、打包功能,同时需开启-u 独立缓存编译方式,产出到同目录下output中
fisp release -uompd output --no-color
#进入output目录
cd output
#删除产出的test目录
rm -rf test

#将output目录进行打包
tar zcf $TAR ./*
mv $TAR ../

cd ..
rm -rf output

mkdir output

mv $TAR output/

echo "build end"

联调测试

为了做到前后端分离,fisp 提供了一个 web 服务,提供了一套本地渲染 Smarty 的 APP 套件,支持 URL 转发、测试数据模拟等功能;

本地 Web 服务

本地 Web 服务是通过 fisp server 命令操作的;它是一个完整的 Web 服务,可以解析 PHP;

  • 开启服务

    fisp server start
    
  • 关闭服务

    fisp server stop
    
  • 重启服务

    fisp server restart
    
  • 查看服务器信息

    fisp server info
    
  • 打开服务器根目录

    fisp server open
    

如果你在本地不想用 fisp 提供的 Web 服务,可以用其他服务替代,只是可能需要你自己开发一套 APP 套件或者直接把后端代码部署到你自己的机器上,方便开发。

url 转发

如果用户没有配置 URL 转发,是按照如下方式去匹配到一个页面的;

比如我有代码路径

fis-conf.js
page/index.tpl
page/home.tpl

假设项目的 namespace = 'work'

那么 release 之后,可以通过如下方式来访问上面提到的两个页面;

  • page/index.tpl => http://127.0.0.1:8080/work/page/index
  • page/home.tpl => http://127.0.0.1:8080/work/page/home

如果配置了 url 转发,请按照 url 转发配置规则访问这些页面;具体配置参考 本地 URL 模拟转发

本地测试数据

FIS 提供了本地调试数据功能,本地预览模板时会根据选择的测试数据进行展现,默认的 smarty 版本为 3.1.13。

浏览器书签


//新建浏览器书签,网址为以下内容
javascript:void function(){var d=new Date();d.setFullYear(d.getFullYear()+1);document.cookie='FIS_DEBUG=YlwtSmt;path=/;expires='+d.toGMTString()+'';document.cookie='FIS_DEBUG_EDIT=1;path=/;expires='+d.toGMTString()+'';document.cookie='LITE_DEBUG=model;expires='+new(Date)(+new(Date)+1000).toGMTString();location.reload();}();

当模块进行编译发布后,在预览的时候点击书签,进入数据管理页面,修改数据后再进行渲染: fisp test

修改保存的数据会临时保存在发布目录中,如果需要保存在 test 目录下还需要单独粘贴复制。

测试数据

测试数据存放在模块 test 目录下,模板文件和测试数据文件对应关系如下:

模板: photo/page/index.tpl

对应数据文件:

0. photo/test/page/index.php (php格式)
0. photo/test/page/index.json (json格式)

也支持多份数据 (php 格式为例):

0. photo/test/page/index/index_1.php
0. photo/test/page/index/index_2.php
...

文件名: index_\d+.php

本地 URL 模拟转发

FIS 的 rewrite 模块。用于本地浏览时,url 的转发,覆盖 fis 默认的 url 规则,模拟线上 url 规则,ajax 请求,文件转发等。

基础配置

在模块目录下通过配置文件 server.conf 文件进行配置,配置方式是:

rewrite 和 redirect 开头的会被翻译成一条匹配规则,自上而下的匹配。所有非 rewrite 和 redirect 开头的会被当做注释处理。

rewrite : 匹配规则后转发到一个文件
template : 匹配规则后转发到一个模板文件,但url不改变,只针对模板
redirect : 匹配规则后重定向到另一个url

rewrite ^\/news\?.*tn\=[a-zA-Z0-9]+.* app/data/news.php
template ^\/(.*)\?.*  /home/page/index.tpl
redirect ^\/index\?.* /home/page/index

配置文件说明

  1. 配置文件每一行为一条规则。
  2. 格式为:匹配类型 (空格) 匹配url正则 (空格) 命中正则后的目的文件路径或者url。 rewrite、redirect和template是fis默认的三种匹配类型。
  3. rewrite : 匹配规则后转发到一个文件,同时url修改为访问文件的url路径。 目的文件路径的根目录(root)是fis本地调试目录(可以输入命令 $ fis server open 打开噢),配置文件从根目录之后写即可。 其中,转发到php文件,php会执行。转发到其他文件,会返回文件内容。例如:可以用rewrite模拟ajax请求,返回json数据~
  4. rewrite的目的文件,默认需要经过fis release之后投送到fis调试目录。直接转发到本地文件,该文件可能没有被fis处理,可能会出现诡异问题噢~
  5. template : 为了完全模拟线上url,可通过template配置对应的url规则对应相应的模板进行访问,访问url不变。
  6. redirect : 匹配规则后重定向到另一个url。

注意:

  1. server.conf专门配置转发规则,文件名不能改哦
  2. 项目很大,多模块时,一个模块配置server.conf就可以啦,比如在common模块配置全站的转发规则,否则会覆盖
  3. 所有非rewrite、template和redirect开头的会被当做注释处理。

线上调试

如果你在配置文件中进行静态资源打包配置,在编译后会产出资源表以及相对应的打包文件。在页面渲染加载时,FIS 后端框架会根据页面静态资源使用情况加载对应的资源包及静态资源。 如果在联调或者线上出现问题时,需要独立加载静态资源不想加载资源包进行问题定位怎么设置呢?

你只需要在 url 上加上 fis_debug 参数,即可获取静态资源的独立加载,而不是加载资源包。

如访问demo-home中的index:
http://localhost:8080/home/page/index?fis_debug

此时页面渲染加载的都是独立的静态资源。

Quickling 解决方案

Quickling 适用于网络高延迟、低带宽场景的解决方案。

  • 保证首屏及核心功能最快展现,使得展示核心功能所需要获取的数据、生成的html文档大小、资源加载量、渲染工作量最小化;
  • 提高服务端的渲染效率和并行度,保证功能不会受到慢数据模块的影响;
  • 支持page cache和用户缓存控制,可以避免大量的服务器端重复计算和客户端重复渲染

背景

Quickling 这个词诞生自 facebook Web 优化方案 ,它指的是页面的某一个块可以通过 Ajax 请求,包括这块使用到的静态资源,然后通过 JSON 方式返回给前端加载器,前端加载器先加载静态资源然后渲染块,这样得到一个可展示的页面局部,可以把它放到当前页的任何地方。

Quickling 解决方案 也使用相同的原理。得益于 FIS 2.0,我们很轻松就可以搞定整个逻辑的实现。

解决方案特性

介绍 Quickling 是如何工作。解决方案特性:

  • A 支持任意一个widget被异步请求,请求内容包括渲染好的HTML及静态资源
  • B 当widget指定为异步请求时,渲染引用此widget模版时不会渲染此widget,降低后端渲染模版压力。

使用场景

使用场景一栏,主要给大家展示一些案例,来引导理解整个解决方案。

案例一

项目 A 中使用方案提供的最初使用前端模板实现 webapp 一站式效果,但是前端渲染的形式,在展示的时候后端获取数据分为两步,展现页面时只能等数据拿到以后才能进行展现,而恰巧获取数据时比较慢,导致页面出现卡顿。那么我们用 Quickling 解决方案 如何解决这个问题呢?

答案很简单,展现页面的时候也分为两步走,第一次渲染的时候拿到比较重要那块的数据,并渲染对应的部分页面。再发起一次异步请求,请求剩下的部分页面。这样至少用户不会感觉到卡顿。是不是看着似曾相识,这个就好比纯的 WebApp 在渲染一个页面时,请求两次数据并渲染页面一样。但这个是后端模板层面支持的。

案例二

项目 B 主要服务于东南亚地区,这些国家的网络有个特点,那就是 ,有 IPHONE 并使用移动号的同学拿出手机访问一下某网站试试,就那种感觉。通过项目 B 同学的反馈以及统计数据显示,下载 HTML 的速度都慢的可怜。还有一个问题并发时下载资源之间抢带宽,阻塞页面的渲染。

问题

总结一下俩问题上面两个案例的问题

  • html太大,导致下载太慢
  • 资源抢带宽,阻塞页面渲染

那通过 Quickling 解决方案 如何解决问题呢。可以通过,

  • 整个页面多次渲染,第一次访问或者刷新时只渲染首屏,这样展示首屏的时候就减少了很多html。下载变快了
  • 拆分逻辑,把基础功能的css内联,增强功能的css在一定条件下触发请求,js进行异步加载。这样控制后页面渲染就变快了。

总结

案例一 案例二 中可以看到,Quickling 解决方案很好的解决了这些遇到的问题,而案例中说到的情况就是方案已知的适用场景,其他场景还有待发现。

使用方法

很多同学到这里就有疑问了,如此复杂的请求方式,一个页面可以分块请求,是不是需要在开发的时候实现很多东西,维护起来很麻烦。答案是 否定 的。整个方案依托于 FIS 2.0 的前端架构思想,从目录结构到静态资源管理。只需要做很小的工作就瞬间享受到 Quickling 解决方案 带来的新特性。

首先得有一个后端模板是 Smarty 的项目,并且是使用 FIS 制定的目录规范以及用 FIS 编译。目录结构是这样的;

├── build.sh
├── config
├── fis-conf.js
├── page
├── static
├── test
└── widget

每个目录放些什么,就不一一说明了,见 FIS2.0 文档 。我们只关注 widgetpage

假设有一个 widget widget_A ,包括一个模板文件 widget_A.tpl 和一个 js 文件 widget_A.js 还有一个 css 文件 widget_A.css。有个页面 index.tpl 要使用这个 widget。

├── page
│   └── index.tpl
└── widget
    └── widget_A
        ├── widget_A.js
        ├── widget_A.css
        └── widget_A.tpl

网站展示时渲染 index.tpl ,widget_A 是页面中的一部分。

//index.tpl
{%widget name="demo:widget/widget_A/widget_A.tpl"%}

当页面被渲染时,widget_A 就展现在页面上了。

<html>
...
    <link href="widget_A.css" rel="stylesheet" type="text/css" />
....
    <div> 我是widget_A </div>
....
    <script src="widget_A.js" type="text/javascript"></script>
</html>

上面是正常的使用方式,就像 方案二 中说到的,如何让渲染 index.tpl 时不展示 widget_A 呢。

{%widget name="demo:widget/widget_A/widget_A.tpl" mode="quickling" pagelet_id="widget_A"%}

OK,改造完成。 加了 mode="quickling"pagelet_id="widget_A"这俩参数。 这时候渲染页面的结果是什么呢?

<html>
.....
<textarea class="fis_g_bigrender" style=“display:none”>BigPipe.asyncLoad({id: "widget_A"})</textarea>
<div id="widget_A"></div>
.....
</html>

如上代码,做了俩事情。

  1. 挖了个坑<div id="widget_A"></div>,异步请求回来的widget_A的html就放在这个坑了。
  2. 在textarea里面打了一个JS函数,这个思路来自bigrender,可以在页面滚动到那个部位才去拉取数据。

等页面渲染完后,开发的同学需要做什么,他只需要把 textarea 里面的代码根据自己的需求执行就成,比如滚轮滚那个地方,domready 后。。。这个自己决定。

说到这里我想你也知道如何使用了。

使用步骤

  • widget调用的时候设定这个widget的 渲染模式quicklingmode="quickling"
  • widget调用的时候设定pagelet_id, pagelet_id="widget_A"
  • 运行时,取出class="fis_g_bigrender"中包含的代码,运行它
  • 页面引入前端加载器BigPipe.js
  • 项目中使用方案提供的smarty 插件

相关资源

优点和缺点

有了使用场景而且也知道如何使用,那现在开始总结一下它到底有哪些优点,事物都是双面的当然也有缺点。这栏总结一下整个方案的优缺点。按照一贯的做法,先说优点。

优点

  • 灵活 页面widget可以灵活请求
  • 可维护性高 FIS用户项目都是组件化的,维护肯定是最好的
  • 使用简单 只需要关注那些页面部分想后展示、具体展示的时机
  • 能解决特定问题 案例一和案例二已经说明了这一点。

缺点

  • 增加了请求 一个页面渲染,如果某一个widget显然模式是“quickling”,那么渲染页面就会多一次请求
  • 增加了服务器负担

性能本来就是一个折中,方案有优缺点,就看具体场景需要了。

国际化解决方案

国际化解决方案适应于国际化多语言支持的解决方案.

  • 解决当语言增多时出现的文件成倍增长的问题
  • 支持动态加载,运行时分析
  • 解决本地编译效率低下、国际化目录规范复杂开发成本高等问题

背景

国际化是指产品、应用或者内容的设计和开发可以使得不同语言、文化、地区的目标受众容易接受和实现本地化这个单词一般被缩写成 i18n

国际化过程由如下基本的必要属性:

  • 设计和开发方式必须要能够容易的实现本地化和通用部署,包括使用 unicode 字符,确保对老的字符编码做了处理等等

  • 提供一些只有在某些情况下才会使用的本地化特性。比如在 HTML 的 DTD 中支持双向的文本展示特性,或者标示出语言,或者在 CSS 中提供支持文字垂直方向展示等等

  • 能够支持一些本地区域的语言和文化展现,如加入一些预定义的本地化数据或者从当地图书馆导出的用户属性,比如时间和日期的格式化展现,数字格式化,排序方式,用户名展示方式等等

  • 从源码中区分本地化的元素和内容,比如本地化备选展现方式可以通过用户在界面上选择后再独立展现

产品国际化的基本流程

  • 产品调研和确定目标市场
  • 给出产品需要的功能需求文档
  • 研发人员调研目标市场的本地化特性,评审需求文档
  • 设计、开发
  • 联调、测试,翻译语言
  • 效果确认,语言确认
  • 上线,效果确认,语言确认
  • 前端开发调研点

前端开发调研点

语言特性

  • 是否有单复数
  • 字符的字节数
  • 人称属性,比如他它她
  • 性别属性,某些动词会因为性别属性不一样而表现不一样
  • 语言展示的方向:LTR,RTL,VERTICAL

时间日期

  • 时间的显示格式
  • 日期的显示格式
  • 是否有本土化的日期显示方式(如中文: 2012年12月30日,泰语佛历显示等)
  • 时区标示符,参考这里

其它格式化内容

  • 数字格式化显示
  • 货币符号
  • 字符行高,字体大小

本地优化特性

  • 虚拟键盘支持(阿语)
  • 输入法支持(越南语有输入法的支持)
  • RTL语言的自动识别

FIS 国际化方案

  • 自动抽取所有的词条
  • css RTL自动转换
  • 运行时展示不同语言

根据 gettext 的思路,一份代码在不同语言环境下展示不同语言。

使用

  1. 下载demo
  2. 编译发布项目
    • fisp release -r common
    • fisp release -r i18n
  3. 预览 http://127.0.0.1:8080

如何开发

按照上面说的使用方法,已经在本地模拟一个国际化项目。修改测试数据 i18n 字段,为 en_US、ja_JP 或其他分别看看效果。

<?php
$fis_data = array(
    'i18n' => 'en_US'
);
?>

进入模块 i18n,详细了解一下目录结构;

└── i18n
    ├── fis-conf.js
    ├── lang #语言目录
    │   ├── en_US.po
    │   └── ja_JP.po
    ├── page
    ├── server.conf
    ├── static
    ├── test
    └── widget

当这个模块 release 以后得到如下结果;

.
├── config
│   ├── i18n-map.json
│   └── lang
│       ├── i18n.en_US.json
│       └── i18n.ja_JP.json
├── server-conf
│   └── i18n.conf
├── static
│   └── i18n
│       ├── mod.js
│       └── widget
├── template
│   └── i18n
│       ├── page
│       └── widget
└── test
    └── i18n
        └── page

可以看到 config/lang 目录下是一些 JSON 翻译文件。翻译文件格式为;

{
    "原文": "译文",
    "原文1": "译文1"
}

这样当运行时调用了翻译函数,翻译函数读取对应的翻译文件展示译文。

翻译函数
  • JS中的语言
  • Smarty模板中的语言

js 中的语言:

var str = __('中文');

Smarty 模板中的语言:

{%'中文'|gettext%}

综述,开发过程;

  1. 功能开发完成后,通过fisp xgettext抽取所有的词条,产出<namespace>.en_US.po等语言文件
  2. 开发人员完善po文件
  3. 编译项目fisp release -r project
  4. fis在编译过程中,分析po文件,生成<namespace>.en_US.json语言翻译文件key=>value
  5. 运行时执行{%'xxxx'|gettext%}函数时,根据语言类型,读取对应的翻译文字并展示

更多资料

更多国际化资料 [http://fe.baidu.com/doc/i18n/]