Giter Site home page Giter Site logo

ques's Introduction

Ques

An new architecture which deal with how to implement and use a component

Website: http://miniflycn.github.io/Ques/

[Warning] This project is under heavy development. Not ready for primetime.

演示

  • 编辑过程

i

清晰动画 >>

快速开始

  • 安装依赖

npm install

  • 运行学习环境

gulp learn

基本命令

  • 启动学习环境

gulp learn

打开浏览器,打开页面:http://localhost:3000/learn

  • 启动开发环境

gulp app

打开浏览器,打开页面:http://localhost/index.html

  • 构建

gulp

生成到dist文件夹,具体请参见gulpfile.js

  • 升级

gulp update

更新到miniflycn/Ques主干的最新版本。

用户

齐齐互动视频

QQ群

家校群

License

(The MIT License)

Copyright (c) 2014-2015 Daniel Yang [email protected]

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

ques's People

Contributors

as3long avatar leiming avatar miniflycn 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

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

ques's Issues

Ques第二阶段计划

  • 参数可配化 & 模板引擎替换成dom.tpl 1.14 - 1.15 done
  • qiqi项目数据完善,准备基准测试 1.16 目前由ouvenzhang负责,期望有成果 done
  • qiqi项目架构迁移 1.19 - 1.21 80%
  • 前端构建工具搭建(切换成爬虫式构建) 1.19 - 1.21 done
  • 验收项目上线 1.22 done
  • 数据验证 1.23 done
  • 更好的文档 done

模版引擎替换

目前的模版引擎难以实现components嵌套功能,建议实现一个类似polymer的模版引擎或参考mvvm实现。

HTML5将死

去年开始来有个热门职位叫“HTML5开发工程师”,移动浪潮炒起了iOS和Android开发工程师,也炒起了“HTML5开发工程师”。咳咳,当然在我们前端看来这是个很滑稽的职位……这不就是前端吗?但是随着React-native的春风,HTML5的部署灵活承载内容引流冲量方案试错快速迭代外部对接开源等优势可能就此不复存在。

HTML5替代方案

实际上腾讯内部也在探索一种替代HTML5的方案,并在性能下得以解决。在iOS手Q引入lua,通过更新脚本来动态运营一些重要的运营位(例如附近的群)。

但这一方案并没有对HTML5造成实际的威胁,由于不通用,其只能由特定的iOS开发人员开发。无法大规模推广利用。

React-native解决了什么?

  • 迭代速度,服务器更新脚本立刻能反应在界面上,不需要走App Store繁琐的审查逻辑,以及版本碎片化导致的痛楚
  • 性能,屏蔽WebView,用原生控件替代,彻底解决了被诟病的WebView创建速度问题DOM渲染性能问题Javascript单线程问题
  • 比Hybrid更容易在体验上向Native靠齐,Android 5.0开始将使用Material Design来规范设计体验,前端想达到这种体验的几乎难以登天,但React-native却至少存在可能性
  • 比Ajax更好的数据获取方式,React-native可通过TCP来获取数据,但Web只能通过HTTP1.1
  • 较平滑的学习曲线,可以很快上手并制作成品
  • 比Canvas UI更友好,比如什么无障碍化,链接跳转,Canvas UI基本做不了,但Native没什么难度

DIY组件&CGI预加载解决方案

DIY组件简介

在页面markup一个标签然后使得页面可以插入NodeJS插件来完成一些特殊功能。

CGI预加载体验

  • 页面插入<diy-preload>组件。
  • 在页面的对应目录(如src/pages/mypage)的*.db.js标记一个CGI需要预加载例如:
    DB.extend({ myCGI: DB.httpMethod({ url: '/cgi/xxx', // 表示需要预加载 preload: true }) });
  • 即完成预加载逻辑

Node 5.0 计划

被人问为啥不能在Node 5.0跑。我也想知道为什么Node 5.0不能向前兼容。好吧,我改改看吧

Refactor 計畫

本月準備使用TypeScript全面重購,重新設計配置文件,以符合完全個項目接入標準。

Q.js

Q.js

模仿Vuejs的伪MVVM库,下面是使用说明

一个简单例子

模版:

<a href="javascript:void(0)" q-text="msg"></a>

脚本:

var vm = new Q({
    el: '#demo',
    data: {
        msg: 'hello'
    }
});

则会展示:

<a href="javascript:void(0)">hello</a>

当使用.data方法修改data时候会触发节点数据修改:

// 得到msg数据的处理方法
vm.data('msg')
    // 设置值为"你好"
    .set('你好');

则会展示:

<a href="javascript:void(0)">你好</a>

基本概念

directive

告知libaray如何对节点进行操作,遵循Vuejs写法:

<element
  prefix-directiveId="[argument:] expression [| filters...]">
</element>

简单例子:

<div q-text="message"></div>

这里表示message对应的数据,用text指令进行操作,text指令是在该节点塞入文字。

自定义directive

举一个我们在todoMVC的例子:

<input q-todo-focus="editing" />

则表示editing对应的数据变化时执行todo-focus指令,看看我们todo-focus指令怎么写的:

directives: {
    'todo-focus': function (value, options) {
        // 如果editing的值为false,则不处理
        if (!value) {
            return;
        }
        // 为true则,对该节点focus()一下
        var el = options.node;
        setTimeout(function () {
            el.focus();
        }, 0);
    }
}

通用directive

目前只提供了极少的通用directive,未来可拓展

  • show - 显示与否
  • class - 是否添加class
  • value - 改变值
  • text - 插入文本
  • repeat - 重复节点
  • on - 事件绑定
  • model - 双向绑定(只支持input、textarea)

filter

如果设置了filter,则绑定的数据会经过filter才执行对应的directive,这是我们可以在塞入数据前做输出处理,或事件触发前做数据处理。

模版:

<input q-model="msg" q-on="keyup: showMsg | key enter" />

key是其中一个通用filter,基本实现是:

var keyCodes = {
        enter    : 13,
        tab      : 9,
        'delete' : 46,
        up       : 38,
        left     : 37,
        right    : 39,
        down     : 40,
        esc      : 27
    };

/**
 * A special filter that takes a handler function,
 * wraps it so it only gets triggered on specific
 * keypresses. v-on only.
 *
 * @param {String} key
 */
function key(handler, key) {
    if (!handler) return;
    var code = keyCodes[key];
    if (!code) {
        code = parseInt(key, 10);
    }
    return function (e) {
        if (e.keyCode === code) {
            return handler.call(this, e);
        }
    };
}

则,当keyup发生,keyCode为13(即enter)时候,才会触发showMsg方法。

method

特制on指令会调用的方法,例如:上面讲到的showMsg。

设置方法:

var vm = new Q({
    el: '#demo',
    data: {
        msg: 'hello'
    },
    methods: {
        showMsg: function () {
            alert(this.data('msg').get());
        }
    }
});

则那个input框会在初始化时自动设值为hello,当改变时候msg值也会改变,当按下回车键,则会触发showMsg方法打印值。

data

所有data操作都应该使用data,才能触发绑定,因为我们没有defineProperty的支持。

  • 生成一个data对象:
vm.data()
  • 生成一个data对象并将游标指向msg属性:
vm.data('msg')
  • 生成一个data对象,并在msg属性中寻找obj对象,并将游标指向obj对象:
vm.data('msg', obj)
  • 得到当前游标的值:
data.get()
  • 设置当前游标的值:
data.set(value)
  • 设置当前游标的子属性msg为value:
data.set('msg', value)
  • 没有改变属性,但是触发下属性改变:
data.touch()

throw errnoException(err, 'kill');

在 D2 与 master 分支,node v0.12.9 与 v4.2.2 后,运行任意 gulp 指令均报如下错误:

$ gulp learn
[21:21:30] Using gulpfile ~/IdeaProjects/D2/Ques/gulpfile.js
[21:21:30] Starting 'learn'...
app close
node.js:776
        throw errnoException(err, 'kill');
        ^

Error: kill ESRCH
    at exports._errnoException (util.js:874:11)
    at process.kill (node.js:776:15)
    at process.<anonymous> (/Users/leiming/IdeaProjects/D2/Ques/gulpfile.js:155:13)
    at emitOne (events.js:82:20)
    at process.emit (events.js:169:7)

Component extend方案

目标

  1. 任何Component的模版应当可以转成AST,所以模版extend相当于对AST进行更改,可使用cheerio来简化AST操作。
  2. 目前所有Component的Javscript脚本都是一个Q.js的配置,则应当可以对该配置进行extend
  3. css相当于允许一种添加新的css的方案,不允许对已有的css进行修正。

方案

  1. 重构代码,使得可得到任意Component的模版ASTQ.js配置css文件
  2. 标记component extend方式
  3. 允许一个种配置文件可做到下面的事情
    • 可操作AST
    • 可extend Q.js配置
    • 可添加css文件

解答一些问题

我前老大说,每种技术是要符合自己业务场景的,在不同的业务场景会催生不同的技术。

  • 为什么不使用React来做组件化?

React相对于手Q的业务来说太大了,这是什么意思呢?手Q三、四线城市用户较多,2.5G用户不在少数,以2.5G用户上网速度为10k/s来算,下载React-with-addon.min.js(gzip后~40k)是什么概念呢?也就是4s才能将基础库下载下来……也就是每次迭代这些悲惨的用户需要忍受4s以上白屏或者页面无法操作。那么腾讯的迭代频次是多少呢?每个项目每周2次吧= =

所以React-native这种将React直接打进APP是可取的,但在页面加载一个React还可能和业务代码一起迭代,在这个场景就是不可取的了。

  • 为什么要兼容IE6?

兼容IE6是因为之前项目的确要支持到IE6+,当然我们现在也是IE8+了,但是在IE6中长得丑是可以的,跑不起来就有点不好了。在QQ中,就算是1%的用户也是很大的量。

  • 为什么不用Vuejs

我是Vuejs的粉丝,否则也不会在接口设计上大量参考Vuejs。但是Vuejs不支持IE8,作者也不愿意支持。综合考虑,我们想做一个基于jQueryZepto的类Vuejs:

  1. jQueryZepto在我们的离线包体系中覆盖率在80%+(意思是80%的用户有这两个库之一,或者有这个库的缓存),所以用户不用再下载这两个库
  2. 基于这两个库可以将MVVM库写得比较小,但够能业务上够用
  3. 自己做MVVM库,我们可以保留最后支持后端输出页面的可能性(也就是可能很多奇怪的Hack,简单来说{{value}}这个语法和repeatif这两个指令是比较难后端输出并且可以绑定的)
  • 理念是什么?

using some simple markup to acquire fancy functionality

—— Polymer

总体来说就是基于线下编译的类Polymer兼容方案。Web Component非常好的观点是通过简单的Markup来引入一个复杂的节点,并包含其样式(CSS)结构(HTML)值(value)行为(methods or action)事件(event),也就是一个Custom Element。注意这里的意思是Custom Element应当包含CSS、HTML和Javascript。

而以前,包括BackboneAngularMV*,我认为都仅仅是Javascript层面的MV*

而React和Polymer就比较类似了,只是React是以Javascript为容器(即以Javascript为核心),而Polymer是以HTML为容器的。

很难说到底哪种方式好:

  1. React这种方式可以在只引入JSX等极少的概念就能很好的组织代码,反过来说代码分离是要有成本的,因为分离后依然需要有绑定关系,这种绑定关系可能利用directive或者class来绑定,这就增加了概念的引入
  2. 但是分离也有好处,代码的分离可以使得开发者聚焦,分工简单,比如可以引入重构工程师(当然大部分公司都没有, = =可是我们腾讯很多啊)。

个人感觉这是喜好问题,不是技术对错问题。

  • 除了这些技术上有什么好玩的东东?
  1. 我们是动态按需编译的,什么意思?就是通常用gruntgulpfis这种基于watch的机制的通病:只要代码一多,每次改点东东就要等很久。为什么?因为实际上他们每次修改文件是基于cache的全亮编译。而Ques是用户要浏览器请求什么就编译什么(用了Ques之后再也不用修改完代码就等几秒了)。
  2. 我们做了一个middleware-pipe使得connectexpress也可以使用gulp插件。
  3. 大量使用了语法解析器,例如:cheerio(解析HTML)、postcss(解析CSS)、esprima(解析Javascript),所以偷偷的做了很多事情,比如:把标准CSS根据配置转成兼容版、分析代码找到需要提前加载的CGI然后提前加载、分析代码找到依赖的Component然后提前定义……
  4. 动态引入,动态移除。也就是当component使用了才加载他的资源,当component移除了就移除他的资源(优化常见手段,按需加载)。
  5. 我们的构建是在本地起一个服务器,然后抓里面的资源再打包。为什么这么怪异?在大型项目中,我们可能会配置两套构建系统,开发系统生产系统。因为希望开发时候尽量快,生产的时候优化尽量好,实际操作中往往会发生,结果要维护两套系统了。所以我们有了别样的方式做拆分:可运行系统优化后系统。优化后系统可以写成一个较通用的模块,输入是可运行系统的输出,输出是页面优化后的结果。

我们的前端组件化体系——Ques

Ques是一套组件化系统,解决如何定义、嵌套、扩展、使用组件。
项目地址:https://github.com/miniflycn/Ques

传统开发模式的痛点

  • 无法方便的引用一个组件,需要分别引用其JavascriptTemplateCSS文件
  • 我们期望能以MV*的方式去写代码,结果发现只有JavascriptMV*
  • UI库打包成一坨(类似Bootstrap),但是实际上UI库伴随产品迭代在反复变更,每次打开网站,用户依然反复下载UI库
  • CSS没有命名空间导致两个组件容易冲突
  • 组件无法嵌套或者无法扩展,所以实际上组件根本无法复用
  • 组件无法复制后可用,在组件无法嵌套或无法扩展的情况下,连定制一个组件都困难连连
  • 每次性能优化都将代码弄的很恶心,不好维护
  • 可能没法支持IE6,例如React、Vuejs、Polymer这些方案IE6肯定不支持

UI组件

UI组件是用来专门做UI的组件,其特点为只有模版、样式文件。系统会根据用户在页面已使用的UI组件动态引用其依赖的资源。注意,UI组件的前缀必须是ui-

下面是一个简单的ui-button的例子:

定义
  • CSS文件
.ui-button {
    display: inline-block;
    padding: 6px 12px;
    margin-bottom: 0;
    font-size: 14px;
    font-weight: 400;
    line-height: 1.42857143;
    text-align: center;
    white-space: nowrap;
    vertical-align: middle;
    touch-action: manipulation;
    cursor: pointer;
    user-select: none;
    background-image: none;
    border: 1px solid transparent;
    border-radius: 4px;
    text-transform: none;
    -webkit-appearance: button;
    overflow: visible;
    margin: 0;
}

.ui-default {
    color:#333;
    background-color:#fff;
    border-color:#ccc
}
.ui-default.focus,.ui-default:focus {
    color:#333;
    background-color:#e6e6e6;
    border-color:#8c8c8c
}
.ui-default:hover {
    color:#333;
    background-color:#e6e6e6;
    border-color:#adadad
}

// 后面可添加更多样式的按钮
  • 模版文件
<button class="ui-button">
    <content></content>
</button>
效果
  • 在页面上直接引用:
<ui-button class="ui-default">DEFAULT</ui-button>
<ui-button class="ui-success">SUCCESS</ui-button>
<ui-button class="ui-info">INFO</ui-button>
<ui-button class="ui-warning">WARNING</ui-button>
<ui-button class="ui-danger">DANGER</ui-button>
  • 展示

这样我们就实现了一个ui-button组件,其可以在任意其他组件中嵌套使用。其依赖的资源会动态引用,也就是说,只有我们使用了ui-button他的模版和样式才会被引入。

备注
  • 由于我们使用了强大的cssnext,所以CSS吐出来的时候会根据配置转换成兼容版本,也就是说我们只需要按照标准去写CSS,系统会自动帮我们适配:
.ui-button {
    display: inline-block;
    padding: 6px 12px;
    margin-bottom: 0;
    font-size: 14px;
    font-weight: 400;
    line-height: 1.42857143;
    text-align: center;
    white-space: nowrap;
    vertical-align: middle;
    -ms-touch-action: manipulation;
        touch-action: manipulation;
    cursor: pointer;
    -webkit-user-select: none;
       -moz-user-select: none;
        -ms-user-select: none;
            user-select: none;
    background-image: none;
    border: 1px solid transparent;
    border-radius: 4px;
    text-transform: none;
    -webkit-appearance: button;
    overflow: visible;
    margin: 0;
}

.ui-default {
    color:#333;
    background-color:#fff;
    border-color:#ccc
}
.ui-default.focus,.ui-default:focus {
    color:#333;
    background-color:#e6e6e6;
    border-color:#8c8c8c
}
.ui-default:hover {
    color:#333;
    background-color:#e6e6e6;
    border-color:#adadad
}
  • 注意到我们引入了Shadow DOM中的<content>标签,<content>标签作为Component内部的插入点(或者可以理解成占位符),当外部引用该Component时可以从外部向内部插入节点,例如:
<ui-button class="ui-default">DEFAULT</ui-button>

则表示向Component的插入点插入DEFAULT这个文字节点。关于<content>标签我们后面还会提到其高级应用。

Component

Component是最常见的组件,其拥有模版、样式以及逻辑文件,使得这种Component更像一个自定义的元素(Custom Element)。体验上像引入一个<input>标签一样,我们可以设置她的值,绑定她的事件,调用她的函数。

下面是一个dialog组件的例子:

定义
  • CSS文件:
.$__mask {
    position: fixed;
    width: 100%;
    height: 100%;
    padding: 0px;
    margin: 0px;
    left: 0px;
    top: 0px;
    z-index: 999;
    background-color: rgba(0,0,0,.6) !important;
    display: none;
}

.$__mask.show {
    display: block;
}

.$__$ {
    position: fixed;
    top: 10%;
    opacity: .5;
    left: 50%;
    width: 490px;
    margin-left: -245px;
    z-index: 999;
    background: #fff;
    font-size: 14px;
    border-radius: 4px;
    overflow: hidden;
    transition: all 200ms ease-in-out;
}

.$__mask .$__$.show {
    top: 50%;
    opacity: 1;
}

.$__$ .header {
    height: 30px;
    line-height: 30px;
    text-indent: 12px;
    background: #039ae3;
    color: #fff;
    font-size: 14px;
}

.$__$ .body {
    padding: 30px 40px;
    position: relative;
    line-height: 24px;
    max-height: 500px;
    overflow-y: auto;
    overflow-x: hidden;
}

.$__$ .msg {
    margin-left: 66px;
    position: relative;
    top: 10px;
    word-break: break-all;
}

.$__$ .bottom {
    margin: 20px;
    text-align: right;
}

.icon-info-large {
    background: url(http://9.url.cn/edu/img/sprite/common.a8642.png) -41px -276px no-repeat;
    width: 36px;
    height: 36px;
    display: block;
    float: left;
    margin-top: 4px;
}
  • 模版文件:
<div class="$__mask" q-class="show: isShow">
    <div class="$__$">
        <div class="header">
            <content select="header *"></content>
        </div>
        <div class="body">
            <div class="icon-info-large"></div>
            <div class="msg">
                <content select="article *"></content>
            </div>
        </div>
        <div class="bottom">
            <ui-button class="ui-info" q-on="click: submit">确定</ui-button>
            <ui-button class="ui-default" q-on="click: cancel">取消</ui-button>
        </div>
    </div>
</div>
  • Javascript文件:
var $ = require('jquery');

module.exports = {
    data: {
        isShow: false
    },
    methods: {
        submit: function () {
            this.$emit('submit');
        },
        cancel: function () {
            this.$emit('cancel')
                .hide();
        },
        show: function () {
            this.$set('isShow', true);
        },
        hide: function () {
            this.$set('isShow', false);
        }
    },
    ready: function () {
        var dialog = $('.$__$', this.$el);
        this.$watch('isShow', function (val) {
            if (val) {
                setTimeout(function () {
                    dialog.addClass('show');
                }, 20);
            } else {
                dialog.removeClass(dialog, 'show');
            }
        }, false, true);
    }
}
效果
  • 在页面直接引入<dialog>
<dialog id="my-dialog">
    <header>
        欢迎使用Ques
    </header>
    <article>
        Hello World!
    </article>
</dialog>
  • 则可以在Controller中直接使用,例如拿到其实例,再调用其show方法,将其展示:
var Q = require('Q');

Q.get('#my-dialog')
    .show();
  • 展示

备注
  • 由于CSS没有命名空间,所以我们引入了两个$__$__$两个占位符来充当命名空间,系统会自动转换成当前Component的名字,所以CSS最终变成:
.dialog__mask {
    position: fixed;
    width: 100%;
    height: 100%;
    padding: 0px;
    margin: 0px;
    left: 0px;
    top: 0px;
    z-index: 999;
    background-color: #000000 !important;
    background-color: rgba(0,0,0,.6) !important;
    display: none;
}

.dialog__mask.show {
    display: block;
}

.dialog {
    position: fixed;
    top: 10%;
    opacity: .5;
    left: 50%;
    width: 490px;
    margin-left: -245px;
    z-index: 999;
    background: #fff;
    font-size: 14px;
    border-radius: 4px;
    overflow: hidden;
    -webkit-transition: all 200ms ease-in-out;
            transition: all 200ms ease-in-out;
}

.dialog__mask .dialog.show {
    top: 50%;
    opacity: 1;
}

.dialog .header {
    height: 30px;
    line-height: 30px;
    text-indent: 12px;
    background: #039ae3;
    color: #fff;
    font-size: 14px;
}

.dialog .body {
    padding: 30px 40px;
    position: relative;
    line-height: 24px;
    max-height: 500px;
    overflow-y: auto;
    overflow-x: hidden;
}

.dialog .msg {
    margin-left: 66px;
    position: relative;
    top: 10px;
    word-break: break-all;
}

.dialog .bottom {
    margin: 20px;
    text-align: right;
}

.icon-info-large {
    background: url(http://9.url.cn/edu/img/sprite/common.a8642.png) -41px -276px no-repeat;
    width: 36px;
    height: 36px;
    display: block;
    float: left;
    margin-top: 4px;
}

可以看见$__被转换成了dialog__,而$__$被转换成了dialog

  • 逻辑层我们使用了MVVM库Q.js,这里就不细说了。
  • 这里还用到<content>标签的高级功能,select属性。select属性是用来选择外部符合选择器的节点进行替换。例如:
<content select="header *"></content>

的意思是选择外部<header>标签内所有东西进行替换,所以“欢迎使用Ques”就被替换进去了。

第三方Component

第三方Component是一套兼容方案,使得业务方不依赖Q.js也能定义一个Component,但是由于系统无法控制第三方组件DOM的作用域,以及实现其扩展似乎没啥意思,所以第三方无法嵌套和扩展。总的来说第三方Component本质上就是系统给第三方一个容器,他在里面干什么,系统就不管了。第三方组件一定以third-为前缀。

下面是一个高亮代码third-code的例子:

定义
  • CSS文件:
.$__pre {
    width: 900px;
    margin: 10px;
}

/**  引入hightlight.js的css库  **/
@import "http://cdnjs.cloudflare.com/ajax/libs/highlight.js/8.6/styles/default.min.css";
  • 模版文件:
<pre class="$__pre">
    <code>
        <content></content>
    </code>
</pre>
  • Javascript文件:
module.exports = {
    bind: function () {
        var el = this.el,
            script = document.createElement('script');
        script.onload = function () {
            hljs.highlightBlock(el);
        }
        script.src = '//cdnjs.cloudflare.com/ajax/libs/highlight.js/8.6/highlight.min.js';
        document.body.appendChild(script);
    },
    unbind: function () {}
};
效果
  • 引入third-code
<third-code>
    &lt;ui-button class=&quot;ui-default&quot;&gt;DEFAULT&lt;/ui-button&gt;
    &lt;ui-button class=&quot;ui-success&quot;&gt;SUCCESS&lt;/ui-button&gt;
    &lt;ui-button class=&quot;ui-info&quot;&gt;INFO&lt;/ui-button&gt;
    &lt;ui-button class=&quot;ui-warning&quot;&gt;WARNING&lt;/ui-button&gt;
    &lt;ui-button class=&quot;ui-danger&quot;&gt;DANGER&lt;/ui-button&gt;
</third-code>
  • 效果:

备注
  • 第三方Component需要实现两个接口bindunbind,即在容器创建时需要绑定什么,当容器删除时需要解绑什么。this会带来必要的东西,例如容器、父级ViewModel等等。

组件嵌套

当组件可以嵌套,组件件可以拆成较小的颗粒,使得复用性大大提升。

下面我们是一个codeclick组件,其用途是高亮展示代码片段,点击代码弹出dialog,也就是说我们准备嵌套上面做出来的third-codedialog组件:

定义
  • CSS文件:
/** 可以给组件定义一些特殊样式,但为了简单我们什么也不做 **/
  • 模版文件:
<div>
    <third-code q-ref="code">
        <content></content>
    </third-code>
    <dialog q-ref="dialog">
        <header>欢迎使用Ques</header>
        <article>你点击了代码</article>
    </dialog>
</div>
  • Javascript文件:
var $ = require('jquery');

module.exports = {
    data: {},
    ready: function () {
        var code = this.$.code,
            dialog = this.$.dialog;
        // 点击代码,弹出dialog
        $(code.el).on('click', function () {
            dialog.show();
        });
    }
};
效果
  • 在页面上引用:
<codeclick>
    &lt;ui-button class=&quot;ui-default&quot;&gt;DEFAULT&lt;/ui-button&gt;
    &lt;ui-button class=&quot;ui-success&quot;&gt;SUCCESS&lt;/ui-button&gt;
    &lt;ui-button class=&quot;ui-info&quot;&gt;INFO&lt;/ui-button&gt;
    &lt;ui-button class=&quot;ui-warning&quot;&gt;WARNING&lt;/ui-button&gt;
    &lt;ui-button class=&quot;ui-danger&quot;&gt;DANGER&lt;/ui-button&gt;
</codeclick>
  • 展示:

备注
  • 我们看到<content>标签另一个神奇的用法是可传递,我们从third-code传递到codeclick,再传递到最外部。使得我们可以在最外部改third-code内部的节点。
  • 我们注意到q-ref本来是Q.js用于组件嵌套从母Component(为了和扩展中的父Component其分开来,这里称之为母Component)拿到子Component的引用,同样可以拿到第三方Component的引用。

组件扩展

组件可扩展,则差别不大的组件可以继承同一个父组件。

下面dialog组件扩展的例子,效果是弹出一个dialog,要求输入内容:

定义
  • CSS文件:
/** 同样为了简单我们什么也不做 **/
  • 模版文件:
<dialog extend>
    <header>
        <h2>欢迎使用Ques</h2>
    </header>
    <article>
        <p>请输入要设置的值</p>
        <ui-input value="" q-model="curVal" q-on="keyup: submit | key enter" q-focus="focus"></ui-input>
    </article>
</dialog>
  • Javascript文件:
var filters = require('filters');

module.exports = {
    methods: {
        submit: function () {
            if (!this.curVal) {
                this.$set('focus', true);
            } else {
                this.$emit('submit', this.curVal);
                this.$set('curVal', '');
                this.hide();
            }
        },
        show: function () {
            // call super.show
            this.constructor.super.options.methods.show.call(this);
            this.$set('focus', true);
        }
    },
    directives: {
        focus: function (val) {
            val && this.el.focus();
        }
    },
    filters: {
        key: filters.key
    }
};
效果
  • 在页面上引用inputval
<inputval id="my-dialog"></inputval>
  • 在Controller调用其show方法:
var Q = require('Q');

Q.get('#my-dialog').show();
  • 则页面弹出一个弹出,要求输入值:

备注
  • 这里我们引入extend属性,用于表示该组件继承哪个组件。
  • 我们还复写了dialogsubmitshow方法,并且可以调用其父Componnet的方法,如:
this.constructor.super.options.methods.show.call(this);

嵌套使用扩展组件

我们可以嵌套使用扩展后的组件。

下面是一个复杂的例子,一个按钮点击后弹出inputval,输入后alert出输入的内容。

定义
  • CSS文件
.$__anchor {
    padding: 13px 35px 17px;
    box-shadow: inset 0 -4px 0 #2a6496;
    border: 0;
    color: #fff;
    transition: all .2s ease-in-out;
    background-color: #337ab7;
    border-color: #2e6da4;
    display: block;
    margin-bottom: 0;
    font-size: 14px;
    font-weight: 400;
    line-height: 1.42857143;
    text-align: center;
    white-space: nowrap;
    vertical-align: middle;
    touch-action: manipulation;
    cursor: pointer;
    user-select: none;
    border-radius: 4px;
    text-decoration: none;
    margin: 0 auto;
}

.$__anchor:hover,
.$__anchor:active
.$__anchor:focus {
    background-color: #286090;
    border-color: #204d74;
}
  • 模版文件:
<div>
    <a href="javascript:void(0);" class="$__anchor" q-on="click: setMessage">
        <content></content>
    </a>
    <inputval q-ref="input"></inputval>
</div>
  • Javascript文件:
module.exports = {
    data: {},
    methods: {
        setMessage: function (e) {
            var self = this;
            this.$.input.$once('submit', function (value) {
                value && alert('输入了:' + value);
            });
            this.$.input.show();
        }
    }
};
效果
  • 在页面引用:
<clkme>请点击我</clkme>
  • 效果:

DIY组件

DIY组件允许页面通过Markup的方法引用NodeJS组件,这样我们可以使用该NodeJS组件分析我们的代码来做一些神奇的事情。

例如我们提供的diy-preload组件提供的CGI预加载方案,整个过程没有改变开发人员的编码体验,只是简简单单标记一下哪个CGI需要预加载,则系统会自动预加载CGI。

使用
  • 在页面引入diy-preload组件
<diy-preload></diy-preload>
  • 在页面对应的数据层配置文件,这里我们的规范是该文件名为db.*.js,的对应CGI设置成preload = true:
    var DB = require('db');

    DB.extend({
        ke: DB.httpMethod({
            url: 'http://ke.qq.com/cgi-bin/index_json',
            type: 'JSONP',
            preload: true
        })
    })

    module.exports = DB;
  • diy-preload组件会找到db.*.js,然后分析出什么CGI需要预加载,在<diy-preload>标签对应的位置插入预加载脚本。整个过程开发人员感知不到,体验上还是调取一个CGI,但是实际上在页面文档打开后CGI请求立刻发出,而不是等待Javascript加载完后才发出。

Ques核心**——CSS Namespace

Facebook's challenges are applicable to any very complex websites with many developers. Or any situation where CSS is bundled into multiple files and loaded asynchronously, and often loaded lazily.
——@vjeux
将Facebook换成Tencent同样适用。

同行们是怎么解决的?

  • Shadow DOM Style

Shadow DOM的样式是完全隔离的,这就意味着即使你在主文档中有一个针对全部 <h3> 标签的样式选择器,这个样式也不会不经你的允许便影响到 shadow DOM 的元素。

举个例子:

举个栗子

<body>  
  <style>
    button {
      font-size: 18px;
      font-family: '华文行楷';
    }
  </style>
  <button>我是一个普通的按钮</button>
  <div></div>

  <script>
    var host = document.querySelector('div');
    var root = host.createShadowRoot();
    root.innerHTML = '<style>button { font-size: 24px; color: blue; } </style>' +
                     '<button>我是一个影子按钮</button>'
  </script>
</body>

Nacespace

这就很好地为Web Component建立了CSS Namespace机制。

  • Facebook: CSS in JS

http://blog.vjeux.com/2014/javascript/react-css-in-js-nationjs.html

比较变态的想法,干脆直接不要用classname,直接用style,然后利用js来写每个元素的style……

例如,如果要写一个类似button:hover的样式,需要写成这样子:

var Button = React.createClass({
  styles: {
    container: {
      fontSize: '13px',
      backgroundColor: 'rgb(233, 234, 237)',
      border: '1px solid #cdced0',
      borderRadius: 2,
      boxShadow: '0 1px 1px rgba(0, 0, 0, 0.05)',
      padding: '0 8px',
      margin: 2,
      lineHeight: '23px'
    },
    depressed: {
      backgroundColor: '#4e69a2',
      borderColor: '#1A356E',
      color: '#FFF'
    },
  },
  propTypes: {
    isDepressed: React.PropTypes.bool,
    style: React.PropTypes.object,
  },
  render: function() {
    return (
      <button style={m(
        this.styles.container,
        // 如果压下按钮,mixin压下的style
        this.props.isDepressed && this.styles.depressed,
        this.props.style
      )}>{this.props.children}</button>
    );
  }
});

几乎等同于脱离了css,直接利用javascript来实现样式依赖、继承、混入、变量等问题……当然如果我们去看看React-nativecss-layout,就可以发现,如果想通过React打通客户端开发,style几乎成了必选方案。

我们的方案

我们期望用类似Web Component的方式去写Component的样式,但在低端浏览器根本就不支持Shadow DOM,所以,我们基于BEM来搭建了一种CSS Namespace的方案。

我们的Component由下面3个文件组成:

  • main.html 结构
  • main.js 逻辑
  • main.css 样式

可参考:https://github.com/miniflycn/Ques/tree/master/src/components/qtree

可以发现我们的css是这么写的:

.$__title {
    margin: 0 auto;
    font-size: 14px;
    cursor: default;
    padding-left: 10px;
    -webkit-user-select: none;
}
/** 太长忽略 **/

这里面有长得很奇怪的.$__前缀,该前缀是我们的占位符,构建系统会自动将其替换成Component名,例如,该Component为qtree,所以生成结果是:

.qtree__title {
    margin: 0 auto;
    font-size: 14px;
    cursor: default;
    padding-left: 10px;
    -webkit-user-select: none;
}
/** 太长忽略 **/

同样道理,在main.htmlmain.js中的对应选择器,在构建中也会自动替换成Component名。

这有什么好处呢?

  1. 基于路径的Namespace,路径没有冲突,那么在该项目中Namespace也不会冲突
  2. Component可以任意改名,或复制重构,不会产生任何影响,便于Component的重构和扩展
  3. Component相对隔离,不会对外部产生影响
  4. Component非绝对隔离,外部可以对其产生一定影响

准备利用AOP做上报分离,为之后产品自定义配置上报做准备

上报的核心问题

  1. 如何非侵入式数据上报
  2. 如何避免上报与逻辑耦合
  3. 更进一步如何将上报配置和业务代码分离
  4. 当上报配置可完全分离,产品自定义配置上报便成为可能。

基本设计实现方案

基于Q.js的事件机制,对事件机制可专门切成上报切面:

1

线下完成几个事情

  1. 节点产生事件,例如
  2. aspect声明:AOP('#componet-1', 'click', function (data) { doreport(); });
  3. 更具申明将上报函数粘到dosth上,在dosth前或后自动执行上报

需求

  • component目录结构变更,即:
    common-header -> components/common/header
  • class MD5化,即所有CSS classname都被MD5化,避免component名太长导致的css名太长
  • class 自动去冗余,即在当前架构下可以自动去除冗余,而不出现任何问题。Why?
    1. 模板被限制成DOM Base Template,模板变动可预期
    2. Javascript被限制在directive才能修改DOM结构,Javascript可预期

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.