├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── bin └── her ├── dist ├── js_helper │ ├── README.md │ ├── append.js │ ├── bigRender.js │ ├── lazy.js │ └── pageEmulator.js ├── main.js └── server │ ├── block.script.php │ ├── compiler.body.php │ ├── compiler.head.php │ ├── compiler.html.php │ ├── compiler.pagelet.php │ ├── compiler.title.php │ ├── function.require.php │ ├── function.widget.php │ └── lib │ ├── BigPipe.class.php │ ├── BigPipeResource.class.php │ ├── FirstController.class.php │ ├── NoScriptController.class.php │ ├── PageController.class.php │ ├── PageletContext.class.php │ ├── PageletEvent.class.php │ ├── SmartController.class.php │ └── TestController.class.php ├── examples └── her-website │ ├── container │ └── section │ │ └── inline.tpl │ ├── fis-conf.js │ ├── lib │ └── .gitignore │ ├── page │ ├── index.tpl │ ├── layout.tpl │ └── test.tpl │ ├── server.conf │ ├── smarty.conf │ ├── static │ ├── index │ │ └── index.less │ └── lib │ │ ├── css │ │ ├── bootstrap-responsive.css │ │ └── bootstrap.css │ │ ├── img │ │ ├── glyphicons-halflings-white.png │ │ └── glyphicons-halflings.png │ │ └── js │ │ ├── html5.js │ │ └── jquery-1.10.1.js │ ├── test │ └── page │ │ └── index.php │ └── widget │ ├── js-helper │ ├── bigRender.js │ ├── jquery-1.9.0.js │ ├── jquery_easing.js │ ├── lazy.js │ ├── pageEmulator.js │ └── widget.js │ ├── js │ └── jquery-1.10.1.js │ ├── nav │ ├── nav.css │ ├── nav.js │ └── nav.tpl │ ├── section │ ├── docs │ │ ├── how.tpl │ │ ├── img │ │ │ ├── fis.png │ │ │ ├── map.png │ │ │ ├── pack.gif │ │ │ └── uri.png │ │ ├── start.tpl │ │ ├── what.tpl │ │ └── why.tpl │ ├── section.css │ └── section.tpl │ ├── sidebar │ ├── sidebar.async.js │ ├── sidebar.css │ └── sidebar.tpl │ └── slogan │ ├── blacktocat.png │ ├── landscape.jpg │ ├── macbook.png │ ├── slogan.css │ └── slogan.tpl ├── gulpfile.js ├── index.js ├── package-lock.json ├── package.json ├── src ├── compiler │ ├── autoPackAnalyze.js │ ├── commonReg.js │ ├── cssInline.js │ ├── jsWrapper.js │ ├── outputHermap.js │ ├── pregQuote.js │ ├── requireAnalyze.js │ ├── tagFilter.js │ └── templateBuilder.js ├── config │ └── default.js ├── js_helper │ ├── README.md │ ├── append.js │ ├── bigRender.js │ ├── lazy.js │ └── pageEmulator.js ├── runtime │ ├── BigPipe.js │ ├── CSSLoader.js │ ├── Controller.js │ ├── EventEmitter.js │ ├── JSLoader.js │ ├── Pagelet.js │ ├── Requestor.js │ ├── Resource.js │ ├── main.js │ ├── runtimeAMD.js │ └── util │ │ ├── JSON.js │ │ ├── ajax.js │ │ ├── amd.js │ │ ├── appendToHead.js │ │ ├── bind.js │ │ ├── copyProperties.js │ │ ├── counter.js │ │ ├── derive.js │ │ ├── each.js │ │ ├── hasOwnProperty.js │ │ ├── inArray.js │ │ ├── isArray.js │ │ ├── isEmpty.js │ │ ├── isFunction.js │ │ ├── nextTick.js │ │ ├── queueCall.js │ │ ├── slice.js │ │ ├── toArray.js │ │ ├── type.js │ │ └── util.js └── server │ ├── block.script.php │ ├── compiler.body.php │ ├── compiler.head.php │ ├── compiler.html.php │ ├── compiler.pagelet.php │ ├── compiler.title.php │ ├── function.require.php │ ├── function.widget.php │ └── lib │ ├── BigPipe.class.php │ ├── BigPipeResource.class.php │ ├── FirstController.class.php │ ├── NoScriptController.class.php │ ├── PageController.class.php │ ├── PageletContext.class.php │ ├── PageletEvent.class.php │ ├── SmartController.class.php │ └── TestController.class.php ├── test ├── fisp-module │ ├── fis-conf.js │ ├── page │ │ ├── a.tpl │ │ ├── test.html │ │ └── test.tpl │ ├── static │ │ ├── async.js │ │ ├── lib.js │ │ ├── test.css │ │ ├── test.js │ │ └── test.less │ └── widget │ │ ├── fisp-widget.tpl │ │ ├── test-widget.css │ │ └── test-widget.tpl └── normal-module │ ├── fis-conf.js │ ├── page │ ├── a.tpl │ ├── inlinetest.tpl │ ├── test.html │ └── test.tpl │ ├── static │ ├── async.js │ ├── test.css │ ├── test.js │ ├── test_copy.css │ └── testless.less │ └── widget │ ├── fisp-widget.tpl │ ├── test-widget.css │ └── test-widget.tpl └── version.js /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | /.settings 3 | /.project 4 | /.tmp 5 | /node_modules 6 | /test/output 7 | /test/pc-demo 8 | tmp 9 | 10 | ~* 11 | *.tmp 12 | *.swp 13 | .DS_Store -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | /.settings 3 | /.project 4 | /.gitignore 5 | /.tmp 6 | /node_modules 7 | /test 8 | /tests 9 | tmp 10 | 11 | ~* 12 | *.cmd 13 | *.tmp 14 | *.swp 15 | .DS_Store -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 hao123-fe 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Her - High-performance Enhanced Rendering Hao123前端高性能渲染解决方案 2 | === 3 | [![npm](https://img.shields.io/npm/v/her.svg?style=flat-square)](https://www.npmjs.com/package/her) 4 | [![npm](https://img.shields.io/npm/dm/her.svg?style=flat-square)](https://www.npmjs.com/package/her) 5 | [![npm](https://img.shields.io/npm/l/her.svg?style=flat-square)](https://www.npmjs.com/package/her) 6 | 7 | Her (High-performance Enhanced Rendering) is a Pagelet and Bigpipe like implement, to provide High-performance Rendering in web pages, which inspried by Fackbook's [BigPipe: Pipelining web pages for high performance](https://www.facebook.com/notes/facebook-engineering/bigpipe-pipelining-web-pages-for-high-performance/389414033919). 8 | 9 | Her is made up of 3 parts, the build tool, the backend output controller and the frontend render controller. The build tool is based on [FIS](http://fis.baidu.com/). The backend output controllers are `FirstController`, `QuicklingController` and `NoScriptController`, in which `FirstController` provide 4 render modes (`server|lazy|default|none`). And the frontend render controller can load resources and render HTML snippet on demands. 10 | 11 | The current implement is for PHP + Smarty. And this repo is the code of the build tool. The backend and frontend runtime code are here - [her-runtime](https://github.com/hao123-fe/her-runtime). 12 | 13 | Hao123前端高性能渲染解决方案(Her)是一个为提升页面加载和渲染性能而设计的通用解决方案,实现了 Pagelet 和类 Bigpipe 输出渲染控制。 14 | 15 | Her 由编译工具、后端输出控制和前端渲染控制组成,目前提供了基于 PHP 和 Smarty 的实现。其中编译工具基于 [FIS](http://fis.baidu.com/) 实现,继承了 FIS 强大的前端构建能力;后端输出控制提供了`FirstController|QuicklingController|NoScriptController` 3种输出控制器,分别处理基础页请求、局部 Quickling 请求和 NoScript 请求,其中 `FirstController` 提供 `server|lazy|default|none` 4种输出模式,方便实现首屏优化、模块开关等;前端渲染控制实现了资源加载、Pagelet 按需渲染和动态打包(planning)。通过对页面进行细粒度分块,收集区块的 HTML 片段、JS、CSS 等资源,后端输出控制和前端按需渲染,极大的增强了前端性能优化的能力。其设计方案和架构图如下: 16 | ![Her 设计方案](https://gss2.bdstatic.com/5eR1dDebRNRTm2_p8IuM_a/res/her/her_runtime.jpg) 17 | ![Her 系统架构图](https://gss2.bdstatic.com/5eR1dDebRNRTm2_p8IuM_a/res/her/iframework.png) 18 | 19 | **本仓库为 Her 的构建工具代码,前后端运行时代码见 [her-runtime](https://github.com/hao123-fe/her-runtime)** 20 | 21 | ## [Docs](https://github.com/hao123-fe/her/wiki) / [Get Start](https://github.com/hao123-fe/her/wiki/1.Get-start) 22 | 23 | Her已经兼容fis-plus,请查看 [FISP模块迁移文档](https://github.com/hao123-dev/her-preprocessor-fispadaptor) 24 | 25 | ## 核心能力 ## 26 | 27 | Her 通过实现以下核心能力来解决前端性能优化: 28 | 29 | * **强大的自动化构建能力**。Her 集成了 FIS `资源定位、内容嵌入、依赖声明` 3种编译构建能力,满足了前端构建需求。 30 | 31 | * **核心运行时能力** 32 | * 通过 `Pagelet` Smarty 插件对页面分块。分块收集 HTML 片段及其依赖的 CSS、JS 资源,对页面模块进行细粒度编码,分解资源依赖和数据获取等 33 | * 后端输出控制器。后端输出控制提供了`FirstController|QuicklingController|NoScriptController` 3种输出控制器,分别处理基础页请求、局部 Quickling 请求和 NoScript 请求,其中 `FirstController` 为 `Pagelet` 提供了 `server|lazy|default|none` 4种输出模式,方便实现核心(首屏)模块优先输出、非核心模块延迟输出,模块开关等 34 | * 前端渲染控制器。实现了 `Pagelet` 按需加载、渲染,资源及其依赖加载、资源动态化打包(计划中)等 35 | 36 | * **定制优化方案的能力**,通过对 `Pagelet` 输出和渲染方式的简单配置编码,可以方便实现以下优化方案和业务方案 37 | * 延迟加载 lazyPagelets。对于非核心模块 `Pagelets` 后端可以使用 `lazy` 渲染模式,基础页请求的时候只输出占位标签,基础页渲染完成之后通过 `Quickling` 方式延迟加载 lazyPagelets,从而实现延迟加载 lazyPagelets,减少基础页 DOM 节点数,极大的优化页面渲染性能。 38 | * 延迟渲染 bigRender。对于不可见模块可以先不渲染,当用户滚动页面的时候再渲染相应模块。可以进一步提升性能,减少不可见模块的图片和数据接口请求等。 39 | * 局部刷新 Quickling。对于数据交互频繁的模块,可以通过 `BigPipe.fetch()` 实现局部刷新,可以实现同构的异步渲染逻辑,极大了降低了异步刷新的开发成本。 40 | 41 | ## 适用场景 ## 42 | Her 适用于采用 Smarty 作为后端模板的 PC 和 Mobile 页面。 43 | 44 | ## 案例 45 | [![hao123新首页](https://gss2.bdstatic.com/5eR1dDebRNRTm2_p8IuM_a/res/img/richanglogo168_24.png)](http://www.hao123.com/newindex) 46 | [![hao123游戏](https://gss2.bdstatic.com/5eR1dDebRNRTm2_p8IuM_a/res/her/game-logo.jpg)](https://game.hao123.com/) 47 | [![hao到家](https://gss2.bdstatic.com/5eR1dDebRNRTm2_p8IuM_a/resource/life/img/o2o/logo.1c06601.png)](http://life.hao123.com/) 48 | [![hao123新闻](https://gss2.bdstatic.com/5eR1dDebRNRTm2_p8IuM_a/resource/tuijian/img/logo.c877631.png)](http://tuijian.hao123.com/) 49 | 50 | ## 参考 ## 51 | [BigPipe: Pipelining web pages for high performance](https://www.facebook.com/notes/facebook-engineering/bigpipe-pipelining-web-pages-for-high-performance/389414033919) 52 | 53 | [FIS](http://fis.baidu.com/) 54 | -------------------------------------------------------------------------------- /bin/her: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | require('../index.js').cli.run(process.argv); 4 | 5 | // vim600: sw=4 ts=4 fdm=marker syn=javascript 6 | -------------------------------------------------------------------------------- /dist/js_helper/README.md: -------------------------------------------------------------------------------- 1 | # js_helper 2 | 基于 her 基本方法封装的一些插件 3 | 4 | ## append.js 5 | 6 | 结合 `BigPipe.fetch()` 实现无限追加,实现无限滚动加载等功能 7 | 8 | 其原理是利用 pagelet id 的动态特性,在前端对 pagelet id 做自增计数,同时预先插入与 id 对应的空的 pagelet 占位容器作为 quickling 渲染的容器,并将 id 通过 query 参数传到 smarty,将 smarty 端的 pagelet id 与前端对应上,即可实现 append 的效果。 9 | 10 | 支持方法: 11 | 12 | ```javascript 13 | // 初始化配置 14 | // pageletPrefix 为需要 append 的 pagelet id 前缀 15 | // conf 为对应的配置 16 | append.init( pageletPrefix, key, // pagelet 自增 id 参数名, 可同时作为分页参数 18 | wrapId // 父容器节点 id 19 | }> conf) 20 | 21 | // 调用 append 22 | // pageletPrefix 同上 23 | // 其中 url, cb 同 BigPipe.fetch(pagelets, url, cb) 24 | append( pageletPrefix, url, cb) 25 | 26 | ``` 27 | 28 | 使用实例: 29 | 30 | ```smarty 31 | {* 声明全局 $_p_id_ 变量, 即 pagelet 的自增 id, 通过 intval() 转换防止 xss *} 32 | {* 注意: 一定要放在全局, 即 {html} 标签之外或 {html} 内 {head} 和 {body} 外, 否则 quickling 会跳过 *} 33 | {block name="global_vars"} 34 | {$_p_id_ = intval($smarty.get._p_id_)} 35 | {/block} 36 | 37 | 38 | {* 在 content 之前完成初始化配置 *} 39 | 47 | 48 | {* 将 pagelet 防止父容器 #feed_wrap 中 *} 49 |
50 | 51 | {* 将 pagelet 的 id 设置为 "前缀_{自增id}" *} 52 | {pagelet id="p_feed_{$_p_id_}"} 53 | 54 | 这是 pagelet 的内容, 可以使用 $_p_id_ 进行相应的分页取数据操作…… 55 | 56 | {* 下面是结合 lazy 实现的自动加载, 结束条件是 $_p_id_ >= 3 *} 57 | {* 当然也可以不使用自动加载, 根据需求手动调用 append() *} 58 | {if $_p_id_ < 3} 59 | 60 | {* 在 pagelet 底部创建空 div 用于 lazy 的触发 hook, 然后在 js 中绑定 lazy 和 append() 回调 *} 61 |
62 | 72 | {/if} 73 | {/pagelet} 74 |
75 | ``` 76 | 77 | -------------------------------------------------------------------------------- /dist/js_helper/append.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 一个简单的 append 3 | * 可以结合 BigPipe.fetch 实现无限追加, 使用方法见 README.md 4 | * @file append.js 5 | * @author zhangwentao 6 | */ 7 | 8 | var config = {}; 9 | 10 | function initConf(key, conf) { 11 | config[key] = conf; 12 | } 13 | 14 | function getConf(key, defaultValue) { 15 | return config.hasOwnProperty(key) ? config[key] : defaultValue; 16 | } 17 | 18 | 19 | function append(prefix, url, cb) { 20 | var conf = getConf(prefix); 21 | 22 | if (conf) { 23 | var wrapId = conf.wrapId; 24 | var key = conf.key; 25 | 26 | 27 | if (!wrapId || !key) { 28 | throw new Error('wrapId or key missing'); 29 | } 30 | 31 | if (!conf.id) { 32 | conf.id = 0; 33 | } 34 | 35 | var id = ++conf.id; 36 | 37 | var pageletId = prefix + id; 38 | var paramStr = [key, id].join('='); 39 | 40 | var $wrap = document.getElementById(wrapId); 41 | 42 | if (!$wrap) { 43 | throw new Error('Wrap node does not exist ' + wrapId); 44 | } 45 | 46 | var $div = document.createElement('div'); 47 | $div.id = pageletId; 48 | 49 | $wrap.appendChild($div); 50 | 51 | url += url.indexOf('?') > -1 ? ('&' + paramStr) : ('?' + paramStr); 52 | 53 | /* globals BigPipe */ 54 | BigPipe.fetch([pageletId], url, cb); 55 | 56 | } else { 57 | throw new Error('There is no config for ' + prefix); 58 | } 59 | } 60 | 61 | append.init = initConf; 62 | 63 | module.exports = append; 64 | -------------------------------------------------------------------------------- /dist/js_helper/bigRender.js: -------------------------------------------------------------------------------- 1 | /* 2 | * File: bigRender.js 3 | * Path: commmon/js/bigRender.js 4 | * Author: HuangYi 5 | * Modifier: HuangYi 6 | * Modified: 2013-7-16 7 | * Description: 延迟渲染js 8 | */ 9 | var lazy = require("./lazy.js"); 10 | 11 | function add(pagelet) { 12 | 13 | var lazyKey, ele, cssText; 14 | try { 15 | // if(pagelet.parent){ 16 | // pagelet.parent.on("load",function(){ 17 | // bindBigRender(pagelet); 18 | // }); 19 | // }else{ 20 | bindBigRender(pagelet); 21 | //} 22 | return true; 23 | } catch (e) { 24 | setTimeout(function() { 25 | throw e; 26 | }, 0); 27 | return false; 28 | } 29 | } 30 | 31 | function bindBigRender(pagelet) { 32 | var ele = document.getElementById(pagelet.id); 33 | addClass(ele, "g-bigrender"); 34 | 35 | var lazyKey = lazy.add(ele, function() { 36 | 37 | pagelet.on("load", function() { 38 | removeClass(ele, "g-bigrender"); 39 | }); 40 | pagelet.load(); 41 | 42 | }); 43 | 44 | pagelet.on("unload", function() { 45 | lazy.remove(lazyKey); 46 | }); 47 | } 48 | 49 | function addClass(element, className) { 50 | if (!element) 51 | return; 52 | var elementClassName = element.className; 53 | if (elementClassName.length == 0) { 54 | element.className = className; 55 | return; 56 | } 57 | if (elementClassName == className || elementClassName.match(new RegExp("(^|\\s)" + className + "(\\s|$)"))) 58 | return; 59 | element.className = elementClassName + " " + className; 60 | } 61 | 62 | function removeClass(element, className) { 63 | if (!element) 64 | return; 65 | var elementClassName = element.className; 66 | if (elementClassName.length == 0) 67 | return; 68 | if (elementClassName == className) { 69 | element.className = ""; 70 | return; 71 | } 72 | if (elementClassName.match(new RegExp("(^|\\s)" + className + "(\\s|$)"))) 73 | element.className = elementClassName.replace((new RegExp("(^|\\s)" + className + "(\\s|$)")), " "); 74 | } 75 | 76 | 77 | module.exports = { 78 | add: add 79 | }; 80 | -------------------------------------------------------------------------------- /dist/js_helper/lazy.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 一个简单的lazy模块实现,基于getBoundingClientRect 3 | * 提供add和remove方法 4 | */ 5 | 6 | function getViewportSize() { 7 | if (window.innerHeight != null) { 8 | return { 9 | width: window.innerWidth, 10 | height: window.innerHeight 11 | }; 12 | } 13 | if (document.compatMode == 'CSS1Compat') { //标准模式 14 | return { 15 | width: document.documentElement.clientWidth, 16 | height: document.documentElement.clientHeight 17 | }; 18 | } 19 | return { 20 | width: document.body.clientWidth, 21 | height: document.body.clientHeight 22 | }; 23 | } 24 | 25 | function getElementOffset(element) { 26 | return { 27 | left: element.getBoundingClientRect().left, 28 | top: element.getBoundingClientRect().top 29 | }; 30 | } 31 | 32 | function addHandler(element, type, handler) { 33 | if (element.addEventListener) { 34 | element.addEventListener(type, handler, false); 35 | } else if (element.attachEvent) { 36 | element.attachEvent("on" + type, handler); 37 | } else { 38 | element["on" + type] = handler; 39 | } 40 | } 41 | 42 | var data = {}; 43 | var uniqueId = 0; 44 | var offsetBuffer = 50; 45 | 46 | function bind() { 47 | addHandler(window, 'resize', excute); 48 | addHandler(window, 'scroll', excute); 49 | } 50 | 51 | function excute() { 52 | var viewport = getViewportSize(); 53 | 54 | for (var i in data) { 55 | var item = data[i]; 56 | var ele = item[0]; 57 | var callback = item[1]; 58 | if (!ele || !ele.getBoundingClientRect) { 59 | return; 60 | } 61 | 62 | if (getElementOffset(ele).top < viewport.height + offsetBuffer) { 63 | callback && callback(); 64 | remove(i); 65 | } 66 | } 67 | } 68 | 69 | function add(element, callback) { 70 | data[uniqueId++] = [element, callback]; 71 | } 72 | 73 | function remove(id) { 74 | try { 75 | delete data[id]; 76 | return true; 77 | } catch (e) { 78 | //return false; 79 | throw new Error('Remove unknow lazy element by id:' + id); 80 | } 81 | } 82 | 83 | bind(); 84 | 85 | module.exports = { 86 | add: add, 87 | remove: remove 88 | }; 89 | -------------------------------------------------------------------------------- /dist/js_helper/pageEmulator.js: -------------------------------------------------------------------------------- 1 | var cacheMaxTime = 0, // 缓存时间 2 | appOptions = {}, // app页面管理的options 3 | layer; // 事件代理层 4 | 5 | var EventUtil = { 6 | addHandler: function(element, type, handler, flag) { 7 | if (element.addEventListener) { 8 | element.addEventListener(type, handler, flag || false); 9 | } else if (element.attachEvent) { 10 | element.attachEvent("on" + type, handler); 11 | } else { 12 | element["on" + type] = handler; 13 | } 14 | }, 15 | getEvent: function(event) { 16 | return event ? event : window.event; 17 | }, 18 | getTarget: function(event) { 19 | //return event.tagert || event.srcElement; 20 | return event.target || event.srcElement; 21 | }, 22 | preventDefault: function(event) { 23 | if (event.preventDefault) { 24 | event.preventDefault(); 25 | } else { 26 | event.returnValue = false; 27 | } 28 | }, 29 | stopPropagation: function(event) { 30 | if (event.stopPropagation) { 31 | event.stopPropagation(); 32 | } else { 33 | event.cancelBubble = true; 34 | } 35 | }, 36 | removeHandler: function(element, type, handler) { 37 | if (element.removeEventListener) { 38 | element.removeEventListener(type, handler, false); 39 | } else if (element.detachEvent) { 40 | element.detachEvent("on" + type, handler); 41 | } else { 42 | element["on" + type] = null; 43 | } 44 | } 45 | }; 46 | 47 | 48 | /** 49 | * 启动页面管理 50 | * @param {Object} options 初始化参数 51 | * @param {String} options["selector"] 全局代理元素的选择器匹配,写法同 document.querySeletor 函数 52 | * @param {Number} options["cacheMaxTime"] 页面缓存时间 53 | * @param {Function|RegExp} options["validate"] url验证方法, 54 | * @return {void} 55 | */ 56 | 57 | function start(options) { 58 | 59 | /** 60 | * 默认参数 { 61 | * selector : // 代理元素的选择器规则 62 | * cacheMaxTime: //缓存存活时间,默认5min 63 | * } 64 | */ 65 | var defaultOptions = { 66 | cacheMaxTime: 5 * 60 * 1000, 67 | layer: document 68 | }; 69 | 70 | appOptions = merge(defaultOptions, options); 71 | cacheMaxTime = appOptions.cacheMaxTime; 72 | layer = getLayer(appOptions.layer); 73 | bindEvent(); 74 | } 75 | 76 | /** 77 | * 事件绑定 78 | * @return {void} 79 | */ 80 | 81 | function bindEvent() { 82 | EventUtil.addHandler(layer, 'click', proxy, true); 83 | } 84 | 85 | function getLayer(ele) { 86 | if (typeof ele === "string") { 87 | return document.querySelector(ele); 88 | } else if (ele && ele.nodeType) { 89 | return ele; 90 | } else { 91 | return document.body 92 | } 93 | } 94 | 95 | /** 96 | * 简单merge两个对象 97 | * @param {object} _old 98 | * @param {object} _new 99 | * @returns {*} 100 | */ 101 | 102 | function merge(_old, _new) { 103 | for (var i in _new) { 104 | if (_new.hasOwnProperty(i) && _new[i] !== null) { 105 | _old[i] = _new[i]; 106 | } 107 | } 108 | return _old; 109 | } 110 | 111 | /** 112 | * 事件代理 113 | * @param {MouseEvent} 点击事件对象 114 | */ 115 | 116 | function proxy(e) { 117 | var element = EventUtil.getTarget(e), 118 | parent = element, 119 | selector = appOptions.selector; 120 | 121 | var url, urlAttr, pagelets; 122 | 123 | while (parent !== layer) { 124 | 125 | urlAttr = parent.tagName.toLowerCase() === "a" ? "href" : "data-href"; 126 | url = parent.getAttribute(urlAttr); 127 | pagelets = parent.getAttribute("data-pagelets"); 128 | 129 | if (url && pagelets) { 130 | pagelets = pagelets.split(','); 131 | 132 | if (pagelets.length) { 133 | 134 | EventUtil.stopPropagation(e); 135 | EventUtil.preventDefault(e); 136 | 137 | BigPipe.fetch(pagelets, url); 138 | } 139 | break; 140 | } else { 141 | parent = parent.parentNode; 142 | } 143 | } 144 | } 145 | 146 | module.exports = { 147 | start: start 148 | }; 149 | -------------------------------------------------------------------------------- /dist/server/block.script.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * @param array $params 8 | * @param string $content 9 | * @param Smarty $smarty 10 | * @param bool $repeat 11 | * @see BigPipe::currentContext 12 | * @see PageletContext->addRequire 13 | * @see PageletContext->addRequireAsync 14 | * @see PageletContext->addHook 15 | */ 16 | function smarty_block_script($params, $content, $smarty, &$repeat) 17 | { 18 | if (!$repeat && isset($content)) { 19 | $eventType = isset($params['on']) ? $params['on'] : "load"; 20 | $strict = (isset($params['strict']) && $params['strict'] == false) ? false : true; 21 | $context = BigPipe::currentContext(); 22 | 23 | if (isset($params["sync"])) { 24 | foreach ($params["sync"] as $resource) { 25 | BigPipeResource::registModule($resource); 26 | } 27 | $context->addRequire($eventType, $params["sync"]); 28 | } 29 | 30 | if (isset($params["async"])) { 31 | foreach ($params["async"] as $resource) { 32 | BigPipeResource::registModule($resource); 33 | } 34 | $context->addRequireAsync($eventType, $params["async"]); 35 | } 36 | 37 | $context->addHook($eventType, $content, $strict); 38 | } 39 | } 40 | 41 | -------------------------------------------------------------------------------- /dist/server/compiler.body.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * @param array $params 9 | * @param Smarty $smarty 10 | * @access public 11 | * @return string 编译后的php代码 12 | */ 13 | function smarty_compiler_body($params, $smarty){ 14 | return 15 | ''; 19 | } 20 | 21 | /** 22 | * smarty 编译插件 bodyclose 23 | * 24 | * 处理 {/body} 标签 25 | * 26 | * @param array $params 27 | * @param Smarty $smarty 28 | * @access public 29 | * @return string 编译后的php代码 30 | */ 31 | function smarty_compiler_bodyclose($params, $smarty){ 32 | return 33 | ''; 37 | } 38 | -------------------------------------------------------------------------------- /dist/server/compiler.head.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * @param array $params 9 | * @param Smarty $smarty 10 | * @access public 11 | * @return string 编译后的php代码 12 | */ 13 | function smarty_compiler_head($params, $smarty){ 14 | return 15 | ''; 19 | } 20 | 21 | /** 22 | * smarty 编译插件 headclose 23 | * 24 | * 处理 {/head} 标签 25 | * 26 | * @param array $params 27 | * @param Smarty $smarty 28 | * @access public 29 | * @return string 编译后的php代码 30 | */ 31 | function smarty_compiler_headclose($params, $smarty){ 32 | return 33 | ''; 37 | } 38 | -------------------------------------------------------------------------------- /dist/server/compiler.html.php: -------------------------------------------------------------------------------- 1 | smarty)){'. 23 | 'do{'. 24 | 'if(' . BigPipe::compileOpenTag(BigPipe::TAG_HTML, $params) . '){'. 25 | '?>'; 26 | } 27 | 28 | /** 29 | * smarty 编译插件 htmlclose 30 | * 31 | * 处理 {/html} 标签 32 | * 33 | * @param array $params 34 | * @param Smarty $smarty 35 | * @access public 36 | * @return string 编译后的php代码 37 | */ 38 | function smarty_compiler_htmlclose($params, $smarty){ 39 | return 40 | ''; 46 | } 47 | -------------------------------------------------------------------------------- /dist/server/compiler.pagelet.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * @param array $params 9 | * @param Smarty $smarty 10 | * @access public 11 | * @return string 编译后的php代码 12 | * @see BigPipe::compileOpenTag 13 | */ 14 | function smarty_compiler_pagelet($params, $smarty){ 15 | return 16 | ''; 20 | } 21 | 22 | /** 23 | * smarty 编译插件 pageletclose 24 | * 25 | * 处理 {/pagelet} 标签 26 | * 27 | * @param array $params 28 | * @param Smarty $smarty 29 | * @access public 30 | * @return string 编译后的php代码 31 | * @see BigPipe::compileCloseTag 32 | */ 33 | function smarty_compiler_pageletclose($params, $smarty){ 34 | return 35 | ''; 39 | } 40 | -------------------------------------------------------------------------------- /dist/server/compiler.title.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * @param array $params 9 | * @param Smarty $smarty 10 | * @access public 11 | * @return string 编译后的php代码 12 | * @see BigPipe::compileOpenTag 13 | */ 14 | function smarty_compiler_title($params, $smarty){ 15 | return 16 | ''; 20 | } 21 | 22 | /** 23 | * smarty 编译插件 titleclose 24 | * 25 | * 处理 {/title} 标签 26 | * 27 | * @param array $params 28 | * @param Smarty $smarty 29 | * @access public 30 | * @return string 编译后的php代码 31 | * @see BigPipe::compileCloseTag 32 | */ 33 | function smarty_compiler_titleclose($params, $smarty){ 34 | return 35 | ''; 39 | } 40 | -------------------------------------------------------------------------------- /dist/server/function.require.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * @param array $params 8 | * @param Smarty $smarty 9 | * @return void 10 | * @see BigPipeResource::registModule 11 | * @see BigPipe::currentContext 12 | * @see PageletContext->addRequire 13 | */ 14 | function smarty_function_require($params, $smarty){ 15 | $link = $params['name']; 16 | unset($params['name']); 17 | 18 | BigPipeResource::registModule($link); 19 | 20 | $context = BigPipe::currentContext(); 21 | $resource = BigPipeResource::getResourceByPath($link); 22 | 23 | switch ($resource["type"]) { 24 | case 'css': 25 | $on = isset($params['on']) ? $params['on'] : 'beforedisplay'; 26 | break; 27 | case 'js': 28 | $on = isset($params['on']) ? $params['on'] : 'load'; 29 | break; 30 | } 31 | 32 | $context->addRequire($on, $link); 33 | } 34 | -------------------------------------------------------------------------------- /dist/server/function.widget.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * @param array $params 8 | * @param Smarty $template 9 | * @return Smarty template funtion call 10 | * @see BigPipeResource::registModule 11 | * @see BigPipeResource::getTplByPath 12 | * @see BigPipe::currentContext 13 | * @see PageletContext->addRequire 14 | */ 15 | 16 | function smarty_function_widget($params, $template) 17 | { 18 | $name = $params['name']; 19 | $method = $params['method']; 20 | unset($params['name']); 21 | unset($params['method']); 22 | 23 | if(isset($params['call'])){ 24 | $call = $params['call']; 25 | unset($params['call']); 26 | } 27 | 28 | BigPipeResource::registModule($name); 29 | 30 | $tpl = BigPipeResource::getTplByPath($name); 31 | // Auto add widget css and less deps (no js) to currentContext. 32 | if(!empty($tpl["deps"])){ 33 | 34 | $deps = $tpl["deps"]; 35 | $context = BigPipe::currentContext(); 36 | 37 | foreach($deps as $dep){ 38 | BigPipeResource::registModule($dep); 39 | $depResource = BigPipeResource::getResourceByPath($dep); 40 | 41 | if($depResource["type"] === "css"){ 42 | $on = 'beforedisplay'; 43 | $context->addRequire($on, $dep); 44 | } 45 | } 46 | } 47 | 48 | $smarty=$template->smarty; 49 | $tplpath = $tpl["uri"]; 50 | 51 | // First try to call the mothed passed via the $call param, 52 | // in order to made it compatible for fisp. 53 | if(isset($call)){ 54 | $call = 'smarty_template_function_' . $call; 55 | if(!function_exists($call)) { 56 | try { 57 | $smarty->fetch($tplpath); 58 | } catch (Exception $e) { 59 | throw new Exception("\nNo tpl here, via call \n\"$name\" \n@ \"$tplpath\""); 60 | } 61 | } 62 | if(function_exists($call)) { 63 | return $call($template, $params); 64 | } 65 | } 66 | 67 | // If there is no method named $call, 68 | // try to call mothed passed via the $method param 69 | $fn='smarty_template_function_' . $method; 70 | if(!function_exists($fn)) { 71 | try { 72 | $smarty->fetch($tplpath); 73 | } catch (Exception $e) { 74 | throw new Exception("\nNo tpl here,via method \n\"$name\" \n@ \"$tplpath\""); 75 | } 76 | } 77 | 78 | if(function_exists($fn)) { 79 | return $fn($template, $params); 80 | } 81 | 82 | // If still no method named $method, 83 | // try to construct a method name with the tpl path, via md5(). 84 | // This is in order to support call method through dynamic tpl path. 85 | else 86 | { 87 | $methodName = preg_replace('/^_[a-fA-F0-9]{32}_/','',$method); 88 | 89 | if($method !== $methodName){ 90 | $method = '_' . md5($name) . '_' . $methodName; 91 | 92 | $fn='smarty_template_function_' . $method; 93 | 94 | if(function_exists($fn)){ 95 | return $fn($template, $params); 96 | } 97 | } 98 | throw new Exception("\nUndefined function \"$method\" \nin \"$name\" \n@ \"$tplpath\""); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /dist/server/lib/BigPipeResource.class.php: -------------------------------------------------------------------------------- 1 | 6 | */ 7 | class BigPipeResource 8 | { 9 | /** 10 | * 存储资源map 11 | */ 12 | private static $map = array( 13 | "res" => array(), 14 | "her" => array(), 15 | ); 16 | /** 17 | * 存储已经注册的module 18 | */ 19 | private static $registedMoudle = array(); 20 | /** 21 | * 存储已经处理过返回给前端的Resources 22 | */ 23 | public static $knownResources = array(); 24 | /** 25 | * 初始化资源map 26 | * 27 | * @param array $map 类名 28 | * @static 29 | * @access public 30 | * @return void 31 | */ 32 | public static function setupMap($map) 33 | { 34 | self::$map["res"] = self::$map["res"] + $map["res"]; 35 | self::$map["her"] = self::$map["her"] + $map["her"]; 36 | } 37 | 38 | /** 39 | * 注册一个module 将module的map存在$registedMoudle 40 | * 41 | * @param string $name 标准化资源路径 42 | * @static 43 | * @access public 44 | * @return void 45 | */ 46 | public static function registModule($name) 47 | { 48 | $intPos = strpos($name, ':'); 49 | 50 | if ($intPos === false) { 51 | return; 52 | } else { 53 | $femodule = substr($name, 0, $intPos); 54 | } 55 | 56 | $configPath = BIGPIPE_CONF_DIR; 57 | 58 | if (!in_array($femodule, self::$registedMoudle)) { 59 | 60 | if (defined("FE_HTTPS") && FE_HTTPS) { 61 | $femodulefix = $femodule . '-https'; 62 | } else { 63 | $femodulefix = $femodule; 64 | } 65 | $mapPath = $configPath . '/' . $femodulefix . '-map.json'; 66 | if (!file_exists($mapPath)) { 67 | throw new Exception("map file not exist, module: $femodule, path: $mapPath"); 68 | } 69 | 70 | $map = json_decode(file_get_contents($mapPath), true); 71 | if (!is_array($map)) { 72 | throw new Exception("map decode fail, module: $femodule"); 73 | } 74 | 75 | BigPipeResource::setupMap($map); 76 | self::$registedMoudle[] = $femodule; 77 | } 78 | } 79 | 80 | /** 81 | * 通过标准化路径获取tpl资源 82 | * 83 | * @param string $path 标准化资源路径 84 | * @static 85 | * @access public 86 | * @return resource 87 | */ 88 | public static function getTplByPath($path) 89 | { 90 | return self::$map["res"][$path]; 91 | } 92 | 93 | /** 94 | * 通过标准化路径获取资源 95 | * 96 | * @param string $path 标准化资源路径 97 | * @static 98 | * @access public 99 | * @return resource 100 | */ 101 | public static function getResourceByPath($path, $type = null) 102 | { 103 | 104 | $map = self::$map["her"]; 105 | $resource = self::getResource($map, $path, $type); 106 | if ($resource) { 107 | return $resource; 108 | } 109 | 110 | return false; 111 | } 112 | 113 | /** 114 | * 从给定 map 获取资源 115 | * 116 | * @param array $map 资源map 117 | * @param string $path 资源path 118 | * @param string $type 资源类型 119 | * @return resource 120 | */ 121 | public static function getResource($map, $path, $type) 122 | { 123 | foreach ($map as $id => $resource) { 124 | if ((!isset($type) || $type == $resource['type']) 125 | && in_array($path, $resource['defines'])) { 126 | $resource['id'] = $id; 127 | if (!isset($resource['requires'])) { 128 | $resource['requires'] = array(); 129 | } 130 | 131 | if (!isset($resource['requireAsyncs'])) { 132 | $resource['requireAsyncs'] = array(); 133 | } 134 | 135 | return $resource; 136 | } 137 | } 138 | return false; 139 | } 140 | 141 | /** 142 | * 通过路径数组获取资源数组 143 | * 144 | * @param string $pathArr 标准化资源路径数组 145 | * @static 146 | * @access public 147 | * @return resources 资源数组 148 | */ 149 | public static function pathToResource($pathArr, $type = null) 150 | { 151 | $resources = array(); 152 | 153 | foreach ($pathArr as $path) { 154 | $resource = self::getResourceByPath($path, $type); 155 | if ($resource) { 156 | $resources[$resource['id']] = $resource; 157 | } 158 | } 159 | return $resources; 160 | } 161 | 162 | /** 163 | * 通过资源数组获取依赖资源数组 164 | * 165 | * @param array $resources 资源数组 166 | * @param bool $asyncs 是否需要获取async依赖 167 | * @static 168 | * @access public 169 | * @return resources 依赖资源数组 170 | */ 171 | public static function getDependResource($resources, $asyncs = true) 172 | { 173 | $dependResources = array(); 174 | 175 | $depends = $resources; 176 | 177 | while (!empty($depends)) { 178 | 179 | $last = end($depends); 180 | array_pop($depends); 181 | 182 | $id = $last['id']; 183 | 184 | if (isset($dependResources[$id])) { 185 | continue; 186 | } 187 | $dependResources[$id] = $last; 188 | 189 | $lastDepends = self::getDepend($last, $asyncs); 190 | if (!empty($lastDepends)) { 191 | $depends = BigPipe::array_merge($depends, $lastDepends); 192 | } 193 | } 194 | 195 | return array_reverse($dependResources, true); 196 | } 197 | 198 | /** 199 | * 获取一个资源的依赖 200 | * 201 | * @param mixed $resource 资源数组 202 | * @param bool $asyncs 是否需要获取async依赖 203 | * @static 204 | * @access public 205 | * @return resources 依赖资源数组 206 | */ 207 | private static function getDepend($resource, $asyncs) 208 | { 209 | $requires = $resource['requires']; 210 | 211 | if ($asyncs) { 212 | $requires = array_merge($requires, $resource['requireAsyncs']); 213 | } 214 | 215 | if (count($requires) > 0) { 216 | return $dependResources = self::pathToResource($requires); 217 | } 218 | return array(); 219 | } 220 | 221 | } 222 | -------------------------------------------------------------------------------- /dist/server/lib/PageController.class.php: -------------------------------------------------------------------------------- 1 | 7 | * zhangwentao 8 | */ 9 | 10 | abstract class PageController 11 | { 12 | /** 13 | * 标签打开的动作类型 14 | * @see getActionKey 15 | */ 16 | const ACTION_OPEN = 1; 17 | /** 18 | * 标签关闭的动作类型 19 | * @see getActionKey 20 | */ 21 | const ACTION_CLOSE = 2; 22 | /** 23 | * 判断页面是否需要再次执行的动作类型 24 | * @see getActionKey 25 | */ 26 | const ACTION_MORE = 3; 27 | 28 | /** 29 | * 执行的动作链,子类应该设置该属性,以便 doAction 调用 30 | * 31 | * @var array 32 | * @access protected 33 | */ 34 | protected $actionChain = null; 35 | 36 | /** 37 | * 得到用于执行的动作链 key, 子类应该实现,以便从 actionChain 中查找用于执行的动作链 38 | * 39 | * @param PageletContext $context 当前 Pagelet 上下文 40 | * @param int $action 当前的动作类型 41 | * @abstract 42 | * @access protected 43 | * @return string 44 | */ 45 | abstract protected function getActionKey($context, $action); 46 | 47 | /** 48 | * 执行某个函数链 49 | * 50 | * @param string $key 函数链名 51 | * @param PageletContext $context 当前 Pagelet 上下文 52 | * @access private 53 | * @return bool 函数链的执行结果 54 | */ 55 | private function doAction($key, $context) 56 | { 57 | $ret = null; 58 | $actions = null; 59 | 60 | if (isset($this->actionChain[$key])) { 61 | $actions = $this->actionChain[$key]; 62 | if (is_string($actions)) { 63 | // $actions = array( 64 | // $actions 65 | // ); 66 | $actions = call_user_func(array( 67 | $this, 68 | $actions 69 | ), $context); 70 | } 71 | if (is_array($actions)) { 72 | foreach ($actions as $method) { 73 | if (is_string($method)) { 74 | $ret = call_user_func(array( 75 | $this, 76 | $method 77 | ), $context); 78 | } else { 79 | $ret = $method; 80 | } 81 | // 如果返回 false 直接退出返回 82 | if($ret === false){ 83 | break; 84 | } 85 | } 86 | } else { 87 | $ret = $actions; 88 | } 89 | } 90 | return $ret; 91 | } 92 | 93 | /** 94 | * 标签打开时调用,控制标签的执行 95 | * 96 | * @param PageletContext $context 当前 Pagelet 上下文 97 | * @final 98 | * @access public 99 | * @return bool 当前 Pagelet 是否需要执行 100 | */ 101 | public final function openTag($context) 102 | { 103 | return $this->doAction($this->getActionKey($context->type, self::ACTION_OPEN), $context); 104 | } 105 | 106 | /** 107 | * 标签关闭时调用 108 | * 109 | * @param PageletContext $context 110 | * @final 111 | * @access public 112 | * @return void 113 | */ 114 | public final function closeTag($context) 115 | { 116 | $this->doAction($this->getActionKey($context->type, self::ACTION_CLOSE), $context); 117 | } 118 | 119 | /** 120 | * 页面完成一次执行后调用,用于控制页面是否重复执行 121 | * 122 | * @final 123 | * @access public 124 | * @return bool 页面是否重复执行 125 | */ 126 | public final function hasMore() 127 | { 128 | return $this->doAction($this->getActionKey(BigPipe::TAG_NONE, self::ACTION_MORE), null); 129 | } 130 | 131 | /** 132 | * 输出打开标签 133 | * 134 | * @param PageletContext $context 当前 Pagelet 上下文 135 | * @access protected 136 | * @return void 137 | */ 138 | protected function outputOpenTag($context) 139 | { 140 | echo $context->getOpenHTML(); 141 | } 142 | 143 | /** 144 | * 输出闭合标签 145 | * 146 | * @param PageletContext $context 当前 Pagelet 上下文 147 | * @access protected 148 | * @return void 149 | */ 150 | protected function outputCloseTag($context) 151 | { 152 | echo $context->getCloseHTML(); 153 | } 154 | 155 | /** 156 | * 开始收集内容 157 | * 158 | * @param PageletContext $context 当前 Pagelet 上下文 159 | * @access protected 160 | * @return void 161 | */ 162 | protected function startCollect($context) 163 | { 164 | ob_start(); 165 | } 166 | 167 | /** 168 | * 将收集到的内容作为 pagelet 的 HTML 内容 169 | * 170 | * @param PageletContext $context 当前 Pagelet 上下文 171 | * @access protected 172 | * @return void 173 | */ 174 | protected function collectHTML($context) 175 | { 176 | $context->html = ob_get_clean(); 177 | } 178 | 179 | /** 180 | * collectScript 收集脚本 181 | * 182 | * @param PageletContext $context 当前 Pagelet 上下文 183 | * @access protected 184 | * @return void 185 | */ 186 | protected function collectScript($context) 187 | { 188 | $context->parent->addScript( 189 | ob_get_clean(), 190 | $context->getBigPipeConfig("on", "load"), 191 | $context->getBigPipeConfig("deps"), 192 | $context->getBigPipeConfig("asyncs") 193 | ); 194 | } 195 | 196 | } 197 | -------------------------------------------------------------------------------- /dist/server/lib/PageletEvent.class.php: -------------------------------------------------------------------------------- 1 | 7 | * zhangwentao 8 | */ 9 | 10 | class PageletEvent 11 | { 12 | /** 13 | * 事件的依赖资源 14 | * 15 | * @var mixed 16 | * @access public 17 | */ 18 | public $requires = null; 19 | 20 | /** 21 | * 事件的异步依赖资源 22 | * 23 | * @var mixed 24 | * @access public 25 | */ 26 | public $requireAsyncs = null; 27 | 28 | /** 29 | * 事件的钩子函数 30 | * 31 | * @var mixed 32 | * @access public 33 | */ 34 | public $hooks = null; 35 | 36 | /** 37 | * 构造新的 PageletEvent 对象 38 | * 39 | * @access public 40 | * @return void 41 | */ 42 | public function __construct() 43 | { 44 | $this->requires = array(); 45 | $this->requireAsyncs = array(); 46 | $this->hooks = array(); 47 | } 48 | 49 | /** 50 | * 添加依赖资源 51 | * 52 | * @param string $resourceName 依赖资源名 53 | * @access public 54 | * @return void 55 | */ 56 | public function addRequire($resourceName) 57 | { 58 | if (!in_array($resourceName, $this->requires)) { 59 | $this->requires[] = $resourceName; 60 | } 61 | } 62 | 63 | /** 64 | * 添加异步依赖资源 65 | * 66 | * @param string $resourceName 异步依赖资源名 67 | * @access public 68 | * @return void 69 | */ 70 | public function addRequireAsync($resourceName) 71 | { 72 | if (!in_array($resourceName, $this->requireAsyncs)) { 73 | $this->requireAsyncs[] = $resourceName; 74 | } 75 | } 76 | 77 | /** 78 | * 添加钩子函数 79 | * 80 | * @param string $scriptCode 钩子函数代码 81 | * @access public 82 | * @return void 83 | */ 84 | public function addHook($scriptCode, $strict) 85 | { 86 | if($strict){ 87 | $scriptCode = "'use strict';\n" . $scriptCode; 88 | } 89 | $this->hooks[] = $scriptCode; 90 | } 91 | } 92 | 93 | -------------------------------------------------------------------------------- /dist/server/lib/SmartController.class.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | 9 | BigPipe::loadClass("FirstController"); 10 | BigPipe::loadClass("BigPipeResource"); 11 | 12 | class SmartController extends FirstController 13 | { 14 | const STAT_COLLECT = 1; // 收集阶段 15 | const STAT_OUTPUT = 2; // 输出阶段 16 | 17 | private $state = self::STAT_COLLECT; //初始状态 18 | private $headInnerHTML = null; 19 | private $bodyInnerHTML = null; 20 | 21 | private $loadedResource = array(); 22 | 23 | protected $sessionId = 0; // 此次会话ID,用于自动生成不重复id,第一次默认为0 24 | protected $uniqIds = array(); // 不重复id种子 25 | 26 | /** 27 | * 构造函数 28 | * 29 | * @access public 30 | * @return void 31 | */ 32 | public function __construct($sessions, $ids) 33 | { 34 | $this->ids = $ids; 35 | $this->sessions = $sessions; 36 | 37 | $this->pagelets = array(); 38 | $this->cids = array(); 39 | $this->oids = $ids; 40 | 41 | $this->actionChain = array( 42 | //收集阶段 43 | 'collect_pagelet_open' => 'collect_pagelet_open', 44 | 'collect_pagelet_close' => 'collect_pagelet_close', 45 | // 'collect_pagelet_open' => array( 46 | // //TODO 'outputPageletOpenTag', 47 | // 'addPagelet', 48 | // 'outputSmartOpenTag', 49 | // 'startCollect', 50 | // true 51 | // ), 52 | // 'collect_pagelet_close' => array( 53 | // 'collectHTML', 54 | // // 'setupBigrender', 55 | // 'outputSmartCloseTag' 56 | // ), 57 | 'collect_more' => array( 58 | 'changeState', 59 | true, 60 | ), 61 | //输出阶段 62 | 'output_body_close' => array( 63 | 'outputPagelets', 64 | ), 65 | 'output_more' => false, 66 | 'default' => false, 67 | ); 68 | } 69 | 70 | /** 71 | * collect_pagelet_open 时的 actionChain 72 | * 73 | * @param PageletContext $context 74 | * @return mixed: actionChain 75 | */ 76 | protected function collect_pagelet_open($context) 77 | { 78 | if ($context->renderMode === BigPipe::RENDER_MODE_NONE) { 79 | return false; 80 | } 81 | 82 | $context->renderMode = BigPipe::RENDER_MODE_QUICKLING; 83 | 84 | $id = $context->getParam( 85 | "id", 86 | $this->sessionUniqId("__elm_"), 87 | PageletContext::FLG_APPEND_PARAM 88 | ); 89 | 90 | if (isset($context->parent)) { 91 | $parentId = $context->parent->getParam("id"); 92 | if (!empty($parentId) && in_array($parentId, $this->ids)) { 93 | $this->ids = array_merge($this->ids, array($id)); 94 | $this->cids[] = $id; 95 | } 96 | } 97 | if (in_array($id, $this->ids)) { 98 | $this->pagelets[] = $context; 99 | // var_dump($context->getParam("id"), $this->cids ); 100 | if (in_array($context->getParam("id"), $this->cids)) { 101 | return array( 102 | 'outputOpenTag', 103 | 'addPagelet', 104 | 'startCollect', 105 | true, 106 | ); 107 | } else { 108 | return array( 109 | // 'outputOpenTag', 110 | 'addPagelet', 111 | 'startCollect', 112 | true, 113 | ); 114 | } 115 | } else { 116 | // $context->renderMode = BigPipe::RENDER_MODE_NONE; 117 | // $context->opened = false; 118 | return false; 119 | } 120 | } 121 | 122 | /** 123 | * collect_pagelet_close 时的 actionChain 124 | * 125 | * @param PageletContext $context 126 | * @return mixed: actionChain 127 | */ 128 | protected function collect_pagelet_close($context) 129 | { 130 | 131 | // if($context->renderMode === BigPipe::RENDER_MODE_NONE) { 132 | if (!$context->opened) { 133 | return false; 134 | } 135 | 136 | if (in_array($context->getParam("id"), $this->cids)) { 137 | return array( 138 | 'collectHTML', 139 | 'outputCloseTag', 140 | ); 141 | } 142 | 143 | return array( 144 | 'collectHTML', 145 | ); 146 | } 147 | 148 | /** 149 | * 输出Quickling请求的pagelets 150 | * 151 | * @param PageletContext $context 152 | */ 153 | protected function outputPagelets($context) 154 | { 155 | $pagelets = array(); 156 | foreach ($this->pagelets as $pagelet) { 157 | $id = $pagelet->getParam("id"); 158 | if (in_array($id, $this->ids)) { 159 | $config = $this->outputPagelet($pagelet); 160 | 161 | if (isset($this->sessions[$id])) { 162 | $config["session"] = $this->sessions[$id]; 163 | } 164 | $pagelets[] = $config; 165 | } 166 | } 167 | 168 | // 输出之前 设置 Content-Type: application/json 169 | header('Content-Type: application/json;charset=UTF-8'); 170 | echo json_encode($pagelets); 171 | } 172 | /** 173 | * 按Quickling模式输出一个pagelet 174 | * 175 | * @param PageletContext $context 176 | */ 177 | protected function outputPagelet($pagelet) 178 | { 179 | $resourceMap = array(); 180 | $hooks = array(); 181 | $config = $this->getPageletConfig($pagelet, $html, $resourceMap, $hooks, true); 182 | $config['quickling'] = true; 183 | $outputMap = array(); 184 | //设置资源表 185 | if (!empty($resourceMap)) { 186 | $resourceMap = BigPipeResource::pathToResource($resourceMap); 187 | $resourceMap = BigPipeResource::getDependResource($resourceMap); 188 | 189 | $resourceMap = BigPipe::array_merge($resourceMap, $this->loadedResource); 190 | 191 | foreach ($resourceMap as $id => $resource) { 192 | 193 | if (isset(BigPipeResource::$knownResources[$id])) { 194 | continue; 195 | } 196 | 197 | $requires = $resource['requires']; 198 | unset($resource['requires']); 199 | unset($resource['requireAsyncs']); 200 | 201 | $requireIds = array(); 202 | if (!empty($requires)) { 203 | $requires = BigPipeResource::pathToResource($requires); 204 | $requireIds = array_keys($requires); 205 | } 206 | $resource['deps'] = $requireIds; 207 | $resource['mods'] = $resource['defines']; 208 | 209 | unset($resource['defines']); 210 | unset($resource['id']); 211 | unset($resource['content']); 212 | 213 | $outputMap[$id] = $resource; 214 | BigPipeResource::$knownResources[$id] = $resource; 215 | } 216 | } 217 | 218 | $config["resourceMap"] = $outputMap; 219 | 220 | return $config; 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /dist/server/lib/TestController.class.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | 9 | BigPipe::loadClass("FirstController"); 10 | BigPipe::loadClass("BigPipeResource"); 11 | 12 | class TestController extends FirstController 13 | { 14 | /** 15 | * 构造函数 16 | * 17 | * @param Array $ids 18 | * @param Array $parents 19 | * @return void 20 | */ 21 | public function __construct($ids, $parents) 22 | { 23 | $this->ids = $ids; 24 | $this->parents = $parents; 25 | $this->actionChain = array( 26 | //收集阶段 27 | 'collect_html_open' => array( 28 | 'outputOpenTag', 29 | true, 30 | ), 31 | 'collect_head_open' => array( 32 | 'startCollect', 33 | true, 34 | ), 35 | 'collect_head_close' => array( 36 | 'collectHeadHTML', 37 | ), 38 | 'collect_body_open' => array( 39 | 'startCollect', 40 | false, 41 | ), 42 | 'collect_pagelet_open' => 'collect_pagelet_open', 43 | 'collect_pagelet_close' => 'collect_pagelet_close', 44 | 'collect_body_close' => array( 45 | 'collectBodyHTML', 46 | ), 47 | 'collect_more' => array( 48 | 'changeState', 49 | true, 50 | ), 51 | //输出阶段 52 | 'output_head_open' => array( 53 | 'outputOpenTag', 54 | 'outputNoscriptFallback', 55 | 'outputHeadHTML', 56 | false, 57 | ), 58 | 'output_head_close' => array( 59 | //TODO 'outputLayoutStyle', 60 | 'outputLayoutStyle', 61 | 'outputCloseTag', 62 | ), 63 | 'output_body_open' => array( 64 | 'outputOpenTag', 65 | 'outputBodyHTML', 66 | false, 67 | ), 68 | 'output_body_close' => array( 69 | 'outputBigPipeLibrary', 70 | 'outputLazyPagelets', 71 | 'outputLoadedResource', 72 | 'outputLayoutPagelet', 73 | 'outputPagelets', 74 | 'outputCloseTag', 75 | ), 76 | 'output_html_close' => array( 77 | 'outputCloseTag', 78 | ), 79 | 'output_more' => false, 80 | 'default' => false, 81 | ); 82 | } 83 | 84 | /** 85 | * collect_pagelet_open 时的 actionChain 86 | * 87 | * @param PageletContext $context 88 | * @return actionChain 89 | */ 90 | protected function collect_pagelet_open($context) 91 | { 92 | // if no pagelet id, append unique id 93 | $id = $context->getParam("id", $this->sessionUniqId("__elm_"), PageletContext::FLG_APPEND_PARAM); 94 | 95 | if (isset($context->parent)) { 96 | $parentId = $context->parent->getParam("id"); 97 | if (!empty($parentId) && in_array($parentId, $this->ids)) { 98 | $this->ids = array_merge($this->ids, array($id)); 99 | } 100 | } 101 | 102 | // if only in parents, just output tag and empty pagelet 103 | if (!in_array($id, $this->ids)) { 104 | if (in_array($id, $this->parents)) { 105 | return array( 106 | 'outputOpenTag', 107 | 'addPagelet', 108 | ); 109 | } else { 110 | return false; 111 | } 112 | } 113 | 114 | switch ($context->renderMode) { 115 | case BigPipe::RENDER_MODE_NONE: 116 | $actionChain = false; 117 | break; 118 | default: 119 | $actionChain = array( 120 | 'outputOpenTag', 121 | 'addPagelet', 122 | 'startCollect', 123 | true, 124 | ); 125 | } 126 | return $actionChain; 127 | } 128 | /** 129 | * collect_pagelet_close 时的 actionChain 130 | * 131 | * @param PageletContext $context 132 | * @return actionChain 133 | */ 134 | protected function collect_pagelet_close($context) 135 | { 136 | $id = $context->getParam("id"); 137 | 138 | if (!in_array($id, $this->ids)) { 139 | if (in_array($id, $this->parents)) { 140 | return array( 141 | 'outputCloseTag', 142 | ); 143 | } else { 144 | return false; 145 | } 146 | } 147 | 148 | switch ($context->renderMode) { 149 | case BigPipe::RENDER_MODE_NONE: 150 | $actionChain = false; 151 | break; 152 | case BigPipe::RENDER_MODE_SERVER: 153 | $actionChain = array( 154 | 'collectHTML', 155 | // 'setupBigrender', 156 | 'renderPagelet', 157 | 'outputCloseTag', 158 | ); 159 | break; 160 | default: 161 | $actionChain = array( 162 | 'collectHTML', 163 | 'outputCloseTag', 164 | ); 165 | } 166 | return $actionChain; 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /examples/her-website/container/section/inline.tpl: -------------------------------------------------------------------------------- 1 | {pagelet id="sidebar"} 2 | {$nav_index = $smarty.get.nav|default:0} 3 | {* 通过widget插件加载模块化页面片段,name属性对应文件路径,模块名:文件目录路径 *} 4 | {widget 5 | name="home:widget/sidebar/sidebar.tpl" 6 | data=$docs 7 | } 8 | {require name="home:static/lib/js/jquery-1.10.1.js"} 9 | {script} 10 | {* $('html').toggleClass('expanded'); *} 11 | $('#sidebar').hover(function() { 12 | require.defer(['/widget/sidebar/sidebar.async.js'], function(sidebar){ 13 | sidebar.run(); 14 | }); 15 | }); 16 | {/script} 17 | {/pagelet} -------------------------------------------------------------------------------- /examples/her-website/fis-conf.js: -------------------------------------------------------------------------------- 1 | fis.config.merge({ 2 | namespace : 'home', 3 | pack : { 4 | 'static/pkg/aio.css?__inline' : [ 5 | 'static/lib/css/bootstrap.css', 6 | 'static/lib/css/bootstrap-responsive.css', 7 | 'widget/**.css' 8 | ], 9 | 'static/pkg/aio.js' : [ 10 | 'static/lib/js/jquery-1.10.1.js', 11 | 'widget/**.js' 12 | ] 13 | }, 14 | settings: { 15 | smarty: { 16 | left_delimiter: '{', 17 | right_delimiter: '}' 18 | } 19 | } 20 | }); 21 | 22 | fis.config.get('roadmap.path').unshift( 23 | { 24 | reg: /^\/lib\/server\/(.*)$/i, 25 | release: '/plugin/$1' 26 | }, 27 | { 28 | reg: /^\/lib\/main\.js$/i, 29 | release: '/static/${namespace}/lib/$1' 30 | }, 31 | { 32 | reg: /^\/lib\/js_helper\/(.*)$/i, 33 | isMod: true, 34 | release: '/static/${namespace}/js_helper/$1' 35 | } 36 | ); -------------------------------------------------------------------------------- /examples/her-website/lib/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore -------------------------------------------------------------------------------- /examples/her-website/page/index.tpl: -------------------------------------------------------------------------------- 1 | {extends file="./layout.tpl"} 2 | {block name="block_head_static"} 3 | {* 模板中加载静态资源 *} 4 | {require name="home:static/lib/css/bootstrap.css"} 5 | {require name="home:static/lib/css/bootstrap-responsive.css"} 6 | {/block} 7 | 8 | {block name="global_vars"} 9 | {$_p_id_ = intval($smarty.get._p_id_)} 10 | {/block} 11 | 12 | {block name="content"} 13 | {require name="home:static/index/index.less"} 14 | 15 | {* go test *} 18 | 19 | {* aaa *} 20 |
21 | {* inline 带有 pagelet 的 tpl *} 22 | 23 | 24 | {pagelet id="container" her-renderMode="server"} 25 | View on GitHub 26 | 27 | {pagelet her-renderMode="server" id="core"} 28 | {widget name="home:widget/slogan/slogan.tpl"} 29 | {widget 30 | name="home:widget/section/section.tpl" 31 | method="section" 32 | doc=$docs[0].doc index=$nav_index wiki=$docs[0].wiki 33 | } 34 | 35 | {script on="beforeload"} 36 | this.on('display', function() { 37 | console.log(this.id, 'display1'); 38 | }); 39 | 40 | {/script} 41 | {script on="display"} 42 | console.log(this.id, 'display'); 43 | {/script} 44 | {script} 45 | console.log(this.id, 'load'); 46 | {/script} 47 | {/pagelet} 48 | 49 | 57 | 58 |
59 | {pagelet id="p_feed_{$_p_id_}"} 60 | 61 | {foreach array_slice($docs, 1) as $doc} 62 | {widget 63 | name="home:widget/section/section.tpl" 64 | method="section" 65 | doc=$doc.doc index=$nav_index wiki=$doc.wiki 66 | } 67 | {/foreach} 68 | 69 | {if $_p_id_ < 3} 70 |
71 | 81 | {/if} 82 | 83 | {script} 84 | console.log(this.id, 'load'); 85 | {/script} 86 | {/pagelet} 87 |
88 | {/pagelet} 89 |
90 | {* 启用emulator监控页面点击实现局部刷新 *} 91 | {* require.defer会在DomReady之后执行 *} 92 | {* {script} 93 | 94 | require.defer(["home:widget/js-helper/pageEmulator.js"],function(emulator){ 95 | emulator.start(); 96 | }); 97 | {/script} *} 98 | 105 | {* {script} 106 | var _hmt = _hmt || []; 107 | (function() { 108 | var hm = document.createElement("script"); 109 | hm.src = "//hm.baidu.com/hm.js?ab6cd754962e109e24b0bcef3f05c34f"; 110 | var s = document.getElementsByTagName("script")[0]; 111 | s.parentNode.insertBefore(hm, s); 112 | })(); 113 | {/script} *} 114 | {/block} -------------------------------------------------------------------------------- /examples/her-website/page/layout.tpl: -------------------------------------------------------------------------------- 1 | {block name="global_vars"}{/block} 2 | 3 | {* 使用html插件替换普通html标签,同时注册JS组件化库 *} 4 | {html her="home:lib/main.js" her-config=['inlineCSS' => true]} 5 | {* 使用head插件替换head标签,主要为控制加载同步静态资源使用 *} 6 | {head} 7 | 8 | 9 | 10 | 11 | {$title} 12 | {block name="block_head_static"}{/block} 13 | {/head} 14 | {* 使用body插件替换body标签,主要为可控制加载JS资源 *} 15 | {body} 16 | {block name="content"}{/block} 17 | 26 | {/body} 27 | {/html} 28 | -------------------------------------------------------------------------------- /examples/her-website/page/test.tpl: -------------------------------------------------------------------------------- 1 | {* 2 | /home/page/test 3 | *} 4 | 5 | {extends file="./layout.tpl"} 6 | 7 | {block name="content"} 8 | {pagelet id="container" her-renderMode="server"} 9 | test 10 | {/pagelet} 11 | {/block} 12 | -------------------------------------------------------------------------------- /examples/her-website/server.conf: -------------------------------------------------------------------------------- 1 | #rewrite : 匹配规则后转发到一个文件 2 | #template : 匹配规则后转发到一个模板文件,但url不改变,只针对模板 3 | #redirect : 匹配规则后重定向到另一个url 4 | 5 | #rewrite ^\/news\?.*tn\=[a-zA-Z0-9]+.* app/data/news.php 6 | #template ^\/(.*)\?.* /home/page/index.tpl 7 | #redirect ^\/index\?.* /home/page/index 8 | 9 | template ^\/?$ home/page/index.tpl 10 | template ^\/?\?.* home/page/index.tpl -------------------------------------------------------------------------------- /examples/her-website/smarty.conf: -------------------------------------------------------------------------------- 1 | left_delimiter="{" 2 | right_delimiter="}" -------------------------------------------------------------------------------- /examples/her-website/static/index/index.less: -------------------------------------------------------------------------------- 1 | html { 2 | font-size: 62.5%; 3 | } 4 | 5 | body { 6 | font-family: 'Open Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; 7 | font-size: 15px; font-size: 1.5rem; 8 | font-weight: 300; 9 | line-height: 22px; 10 | background-color: #fff; 11 | /* margin-top: 60px; */ 12 | } 13 | 14 | p { 15 | margin: 20px 0; 16 | } 17 | 18 | a, a:hover { 19 | text-decoration: none; 20 | } 21 | 22 | #container { 23 | backface-visibility: hidden; 24 | // margin-left: 50px; 25 | position: relative; 26 | background: #fff; 27 | margin-bottom: 100px; 28 | } 29 | 30 | // .expanded #container { 31 | // margin-left: 150px; 32 | // } -------------------------------------------------------------------------------- /examples/her-website/static/lib/img/glyphicons-halflings-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hao123-fe/her/b3c9f53f6797738f417a6aec2219981a351e9c59/examples/her-website/static/lib/img/glyphicons-halflings-white.png -------------------------------------------------------------------------------- /examples/her-website/static/lib/img/glyphicons-halflings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hao123-fe/her/b3c9f53f6797738f417a6aec2219981a351e9c59/examples/her-website/static/lib/img/glyphicons-halflings.png -------------------------------------------------------------------------------- /examples/her-website/static/lib/js/html5.js: -------------------------------------------------------------------------------- 1 | /* 2 | HTML5 Shiv v3.6.2pre | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed 3 | Uncompressed source: https://github.com/aFarkas/html5shiv 4 | */ 5 | (function(l,f){function m(){var a=e.elements;return"string"==typeof a?a.split(" "):a}function i(a){var b=n[a[o]];b||(b={},h++,a[o]=h,n[h]=b);return b}function p(a,b,c){b||(b=f);if(g)return b.createElement(a);c||(c=i(b));b=c.cache[a]?c.cache[a].cloneNode():r.test(a)?(c.cache[a]=c.createElem(a)).cloneNode():c.createElem(a);return b.canHaveChildren&&!s.test(a)?c.frag.appendChild(b):b}function t(a,b){if(!b.cache)b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag(); 6 | a.createElement=function(c){return!e.shivMethods?b.createElem(c):p(c,a,b)};a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+m().join().replace(/\w+/g,function(a){b.createElem(a);b.frag.createElement(a);return'c("'+a+'")'})+");return n}")(e,b.frag)}function q(a){a||(a=f);var b=i(a);if(e.shivCSS&&!j&&!b.hasCSS){var c,d=a;c=d.createElement("p");d=d.getElementsByTagName("head")[0]||d.documentElement;c.innerHTML="x"; 7 | c=d.insertBefore(c.lastChild,d.firstChild);b.hasCSS=!!c}g||t(a,b);return a}var k=l.html5||{},s=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,r=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,j,o="_html5shiv",h=0,n={},g;(function(){try{var a=f.createElement("a");a.innerHTML="";j="hidden"in a;var b;if(!(b=1==a.childNodes.length)){f.createElement("a");var c=f.createDocumentFragment();b="undefined"==typeof c.cloneNode|| 8 | "undefined"==typeof c.createDocumentFragment||"undefined"==typeof c.createElement}g=b}catch(d){g=j=!0}})();var e={elements:k.elements||"abbr article aside audio bdi canvas data datalist details figcaption figure footer header hgroup main mark meter nav output progress section summary time video",version:"3.6.2pre",shivCSS:!1!==k.shivCSS,supportsUnknownElements:g,shivMethods:!1!==k.shivMethods,type:"default",shivDocument:q,createElement:p,createDocumentFragment:function(a,b){a||(a=f);if(g)return a.createDocumentFragment(); 9 | for(var b=b||i(a),c=b.frag.cloneNode(),d=0,e=m(),h=e.length;d 'Her - Hao123前端高性能渲染解决方案', 10 | 'description' => '专为前端性能优化设计的工程化解决方案,封装了前端性能优化核心解决方案,让前端性能优化更简单!', 11 | 'github' => $github, 12 | 'docs' => array( 13 | array( 14 | 'title' => 'What is Her', 15 | 'doc' => 'what', 16 | 'icon' => 'leaf', 17 | 'wiki' => $github_wiki . '#what-is-her-' 18 | ), 19 | array( 20 | 'title' => 'How to', 21 | 'doc' => 'how', 22 | 'icon' => 'eye-open', 23 | 'wiki' => $github_wiki . '#how-to-' 24 | ), 25 | array( 26 | 'title' => 'Why Her', 27 | 'doc' => 'why', 28 | 'icon' => 'fire', 29 | 'wiki' => $github_wiki . '#why-her-' 30 | ), 31 | // array( 32 | // 'title' => 'Get start', 33 | // 'doc' => 'start', 34 | // 'icon' => 'gift', 35 | // 'wiki' => $github_wiki . '/1.Get%20start' 36 | // ), 37 | ) 38 | ); -------------------------------------------------------------------------------- /examples/her-website/widget/js-helper/bigRender.js: -------------------------------------------------------------------------------- 1 | /* 2 | * File: bigRender.js 3 | * Path: commmon/js/bigRender.js 4 | * Author: HuangYi 5 | * Modifier: HuangYi 6 | * Modified: 2013-7-16 7 | * Description: 延迟渲染js 8 | */ 9 | var lazy = require("./lazy.js"); 10 | 11 | function add(pagelet) { 12 | 13 | var lazyKey, ele, cssText; 14 | try { 15 | if(false && pagelet.parent){ 16 | pagelet.parent.on("load",function(){ 17 | bindBigRender(pagelet); 18 | }); 19 | }else{ 20 | bindBigRender(pagelet); 21 | } 22 | return true; 23 | }catch (e) { 24 | setTimeout(function () { 25 | throw e; 26 | }, 0); 27 | return false; 28 | } 29 | } 30 | 31 | function bindBigRender(pagelet){ 32 | var ele = document.getElementById(pagelet.id); 33 | addClass(ele, "g-bigrender"); 34 | var lazyKey = lazy.add(ele, function () { 35 | 36 | pagelet.on("load", function () { 37 | removeClass(ele, "g-bigrender"); 38 | }); 39 | pagelet.load(); 40 | }); 41 | 42 | pagelet.on("unload", function () { 43 | lazy.remove(lazyKey); 44 | }); 45 | } 46 | 47 | function addClass(element, className) { 48 | if (!element) 49 | return; 50 | var elementClassName = element.className; 51 | if (elementClassName.length == 0) { 52 | element.className = className; 53 | return; 54 | } 55 | if (elementClassName == className || elementClassName.match(new RegExp("(^|\\s)" + className + "(\\s|$)"))) 56 | return; 57 | element.className = elementClassName + " " + className; 58 | } 59 | 60 | function removeClass(element, className) { 61 | if (!element) 62 | return; 63 | var elementClassName = element.className; 64 | if (elementClassName.length == 0) 65 | return; 66 | if (elementClassName == className) { 67 | element.className = ""; 68 | return; 69 | } 70 | if (elementClassName.match(new RegExp("(^|\\s)" + className + "(\\s|$)"))) 71 | element.className = elementClassName.replace((new RegExp("(^|\\s)" + className + "(\\s|$)")), " "); 72 | } 73 | 74 | 75 | module.exports = { 76 | add:add 77 | }; -------------------------------------------------------------------------------- /examples/her-website/widget/js-helper/lazy.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 工具函数:懒加载 3 | * 4 | * 加载的唯一标准是元素的位置有没有出现在视口内。 5 | * by hongwei 6 | * 2013.06.04 7 | */ 8 | 9 | var pageEvents = require("./pageEvents.js"), 10 | queue = require("./queue.js"), 11 | //保存每一条需要懒加载的信息,每条包括elem,callback,key。 12 | //elem:有src属性的元素,callback,自定义的处理加载的函数,key:每一次add的每一条信息都用唯一的key值标识 13 | _data = [], 14 | 15 | //标识add方法的每次添加 16 | _key = 0, 17 | viewport, 18 | disabled = false; 19 | 20 | var global = window; 21 | /** 22 | * 添加需要懒加载的元素 23 | * 24 | * @param {array|类array|单个dom元素} 可以是单个dom元素,也可以是一个有length的对象 25 | * @param {function} 自定义的回调函数,执行自定义加载图片,默认回调函数会把data-src属性值赋给src属性 26 | * @returns {number} 全局唯一的key,用作删除本次增加的参数 27 | */ 28 | 29 | function add(elems, callback) { 30 | if (!('length' in elems)) { 31 | elems = [elems]; 32 | } 33 | for (var i = 0, item, len = elems.length; i < len; i++) { 34 | item = { 35 | elem: elems[i], 36 | callback: callback || defaultCallback, 37 | key: _key, 38 | valid: true 39 | } 40 | _data.push(item); 41 | } 42 | if (!viewport) 43 | viewport = pageEvents.getViewport(); 44 | //TODO:调用计算添加元素的offsetTop的方法 45 | queue.call(getElementsOffsetTop); 46 | return _key++; 47 | } 48 | 49 | /** 50 | * 删除需要懒加载的元素 51 | * 52 | * @param {key} 添加时的返回值 53 | */ 54 | 55 | function remove(key) { 56 | var len = _data.length; 57 | while (len--) { 58 | if (_data[len].key === key) { 59 | _data[len].valid = false; 60 | } 61 | } 62 | } 63 | 64 | /*var STATE_UNINIT = 1, 65 | STATE_LOADING = 2, 66 | STATE_LOADED = 3, 67 | jqueryLoadState = STATE_UNINIT;*/ 68 | 69 | function getElementsOffsetTop() { 70 | var $ = require("jquery"); 71 | //if (jqueryLoadState == STATE_UNINIT) { 72 | // jqueryLoadState = STATE_LOADING; 73 | // requireLazy(["common.js.jquery"], function($) { 74 | // jqueryLoadState = STATE_LOADED; 75 | getElementsOffsetTop = getElementsOffsetTopViaJquery; 76 | queue.call(getElementsOffsetTop); 77 | // }); 78 | //} 79 | } 80 | 81 | function getElementsOffsetTopViaJquery() { 82 | var index, count, item; 83 | for (index = 0, count = _data.length; index < count; index++) { 84 | item = _data[index]; 85 | if (item.offsetTop === undefined && item.valid) { 86 | item.offsetTop = findValideTop(item.elem); 87 | } 88 | if (item.offsetTop !== undefined && checkItem(item)) { 89 | _data.splice(index, 1); 90 | index--; 91 | count--; 92 | } 93 | } 94 | } 95 | 96 | function checkItem(item) { 97 | if (!item.valid) 98 | return true; 99 | if (viewport.scrollTop + viewport.height + 50 > item.offsetTop) { 100 | queue.call(item.callback, global, item.elem); 101 | return true; 102 | } 103 | return false; 104 | } 105 | 106 | function defaultCallback(elem) { 107 | elem.setAttribute('src', elem.getAttribute('data-src')); 108 | } 109 | 110 | function findValideTop(elem) { 111 | var offsetTop = 0, 112 | offset; 113 | try { 114 | while (offsetTop <= 0) { 115 | if (!(elem && (offset = $(elem).offset()))) { 116 | return undefined; 117 | } 118 | offsetTop = offset.top; 119 | elem = elem.parentNode; 120 | } 121 | } catch (e) { 122 | return undefined; 123 | } 124 | return offsetTop; 125 | } 126 | 127 | function excute() { 128 | if (disabled) return; 129 | viewport = pageEvents.getViewport(); 130 | queue.call(getElementsOffsetTop); 131 | } 132 | 133 | function reset() { 134 | if (disabled) return; 135 | for (var i = 0, len = _data.length; i < len; i++) { 136 | _data[i].offsetTop = undefined; 137 | } 138 | queue.call(getElementsOffsetTop); 139 | } 140 | 141 | pageEvents.on('viewport.change', excute); 142 | pageEvents.on('page.endchange', reset); 143 | 144 | function disable() { 145 | disabled = true; 146 | } 147 | 148 | function enable() { 149 | disabled = false; 150 | } 151 | 152 | module.exports = { 153 | add: add, 154 | remove: remove, 155 | disable: disable, 156 | enable: enable 157 | }; 158 | 159 | /*华丽的分割线------------------------------*/ 160 | 161 | /*var viewPortChanged = false, 162 | excuteing = false; 163 | 164 | function checkViewPortChange() { 165 | if (viewPortChanged && !excuteing) { 166 | excuteing = true; 167 | viewPortChanged = false; 168 | excute(event.getViewport()); 169 | setTimeout(function () { 170 | excuteing = false; 171 | checkViewPortChange(); 172 | }, 20); 173 | } 174 | } 175 | 176 | function excute(e, data) { 177 | if (e) { 178 | _viewSize = e; 179 | } 180 | data = data || _data; 181 | var len = data.length, item; 182 | if (len <= 0) {return; } 183 | requireLazy(['common.js.jquery'], function ($) { 184 | var len = data.length, item; 185 | while (len--) { 186 | item = data[len]; 187 | if (item.offsetTop == undefined) { 188 | item.offsetTop = findValideTop(item.elem); 189 | } 190 | if (_viewSize.scrollTop > (item.offsetTop - _viewSize.height - 10)) { 191 | //顺序很重要,如果删除在callback之后 ,如果callback有错,导致删除不成功。 192 | data.splice(len, 1); 193 | item.callback(item.elem); 194 | } 195 | //如果是手动执行且未执行显示,需要把元素添加到_data,以便viewport.change发生时再次调用 196 | else if (!e) { 197 | _data.push(data[len]); 198 | } 199 | } 200 | 201 | }); 202 | } 203 | */ 204 | -------------------------------------------------------------------------------- /examples/her-website/widget/js-helper/pageEmulator.js: -------------------------------------------------------------------------------- 1 | var cacheMaxTime = 0, // 缓存时间 2 | appOptions = {}, // app页面管理的options 3 | layer; // 事件代理层 4 | 5 | var EventUtil = { 6 | addHandler: function (element, type, handler, flag) { 7 | if (element.addEventListener) { 8 | element.addEventListener(type, handler, flag || false); 9 | } else if (element.attachEvent) { 10 | element.attachEvent("on" + type, handler); 11 | } else { 12 | element["on" + type] = handler; 13 | } 14 | }, 15 | getEvent: function (event) { 16 | return event ? event : window.event; 17 | }, 18 | getTarget: function (event) { 19 | //return event.tagert || event.srcElement; 20 | return event.target || event.srcElement; 21 | }, 22 | preventDefault: function (event) { 23 | if (event.preventDefault) { 24 | event.preventDefault(); 25 | } else { 26 | event.returnValue = false; 27 | } 28 | }, 29 | stopPropagation: function (event) { 30 | if (event.stopPropagation) { 31 | event.stopPropagation(); 32 | } else { 33 | event.cancelBubble = true; 34 | } 35 | }, 36 | removeHandler: function (element, type, handler) { 37 | if (element.removeEventListener) { 38 | element.removeEventListener(type, handler, false); 39 | } else if (element.detachEvent) { 40 | element.detachEvent("on" + type, handler); 41 | } else { 42 | element["on" + type] = null; 43 | } 44 | } 45 | }; 46 | 47 | 48 | /** 49 | * 启动页面管理 50 | * @param {Object} options 初始化参数 51 | * @param {String} options["selector"] 全局代理元素的选择器匹配,写法同 document.querySeletor 函数 52 | * @param {Number} options["cacheMaxTime"] 页面缓存时间 53 | * @param {Function|RegExp} options["validate"] url验证方法, 54 | * @return {void} 55 | */ 56 | 57 | function start(options) { 58 | 59 | /** 60 | * 默认参数 { 61 | * selector : // 代理元素的选择器规则 62 | * cacheMaxTime: //缓存存活时间,默认5min 63 | * } 64 | */ 65 | var defaultOptions = { 66 | cacheMaxTime: 5 * 60 * 1000, 67 | layer : document 68 | }; 69 | 70 | appOptions = merge(defaultOptions, options); 71 | cacheMaxTime = appOptions.cacheMaxTime; 72 | layer = getLayer(appOptions.layer); 73 | bindEvent(); 74 | } 75 | 76 | /** 77 | * 事件绑定 78 | * @return {void} 79 | */ 80 | 81 | function bindEvent() { 82 | EventUtil.addHandler(layer,'click',proxy, true); 83 | } 84 | 85 | function getLayer(ele) { 86 | if(typeof ele === "string") { 87 | return document.querySelector(ele); 88 | } else if (ele && ele.nodeType) { 89 | return ele; 90 | } else { 91 | return document.body 92 | } 93 | } 94 | 95 | /** 96 | * 简单merge两个对象 97 | * @param {object} _old 98 | * @param {object} _new 99 | * @returns {*} 100 | */ 101 | 102 | function merge(_old, _new) { 103 | for (var i in _new) { 104 | if (_new.hasOwnProperty(i) && _new[i]!==null) { 105 | _old[i] = _new[i]; 106 | } 107 | } 108 | return _old; 109 | } 110 | 111 | /** 112 | * 事件代理 113 | * @param {MouseEvent} 点击事件对象 114 | */ 115 | 116 | function proxy(e) { 117 | var element = EventUtil.getTarget(e), 118 | parent = element, 119 | selector = appOptions.selector; 120 | 121 | var url, urlAttr, pagelets; 122 | 123 | while (parent !== layer) { 124 | 125 | urlAttr = parent.tagName.toLowerCase() === "a" ? "href" : "data-href"; 126 | url = parent.getAttribute(urlAttr); 127 | pagelets = parent.getAttribute("data-pagelets"); 128 | 129 | if(url && pagelets){ 130 | pagelets = pagelets.split(','); 131 | 132 | if (pagelets.length) { 133 | 134 | EventUtil.stopPropagation(e); 135 | EventUtil.preventDefault(e); 136 | 137 | BigPipe.fetch(pagelets, url); 138 | } 139 | break; 140 | } else { 141 | parent = parent.parentNode; 142 | } 143 | } 144 | } 145 | 146 | module.exports = { 147 | start : start 148 | }; 149 | -------------------------------------------------------------------------------- /examples/her-website/widget/js-helper/widget.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery UI Widget 1.8.20 3 | * 4 | * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) 5 | * Dual licensed under the MIT or GPL Version 2 licenses. 6 | * http://jquery.org/license 7 | * 8 | * http://docs.jquery.com/UI/Widget 9 | */ 10 | // @/require jquery.js; 11 | 12 | (function(a, d) { 13 | var c = a.cleanData; 14 | a.cleanData = function(f) { 15 | for (var g = 0, h; 16 | (h = f[g]) != null; g++) { 17 | try { 18 | a(h).triggerHandler("remove") 19 | } catch (j) {} 20 | } 21 | c(f) 22 | }; 23 | var b = "__widget__"; 24 | a.widget = function(f, i, e) { 25 | var h = f.split("."), 26 | g = h[0], 27 | k; 28 | f = h[1]; 29 | k = g + "-" + f; 30 | if (!e) { 31 | e = i; 32 | i = a.Widget 33 | } 34 | a.expr[":"][k] = function(l) { 35 | return !!a.data(l, b + f) 36 | }; 37 | a[g] = a[g] || {}; 38 | a[g][f] = function(l, m) { 39 | if (arguments.length) { 40 | this._createWidget(l, m) 41 | } 42 | }; 43 | var j = new i(); 44 | j.options = a.extend(true, {}, j.options); 45 | a[g][f].prototype = a.extend(true, j, { 46 | namespace: g, 47 | widgetName: f, 48 | widgetEventPrefix: a[g][f].prototype.widgetEventPrefix || f, 49 | widgetBaseClass: k 50 | }, e); 51 | a.widget.bridge(f, a[g][f]) 52 | }; 53 | a.widget.bridge = function(f, e) { 54 | a.fn[f] = function(i) { 55 | var g = typeof i === "string", 56 | h = Array.prototype.slice.call(arguments, 1), 57 | j = this; 58 | i = !g && h.length ? a.extend.apply(null, [true, i].concat(h)) : i; 59 | if (g && i.charAt(0) === "_") { 60 | return j 61 | } 62 | if (g) { 63 | this.each(function() { 64 | var k = a.data(this, b + f), 65 | l = k && a.isFunction(k[i]) ? k[i].apply(k, h) : k; 66 | if (l !== k && l !== d) { 67 | j = l; 68 | return false 69 | } 70 | }) 71 | } else { 72 | this.each(function() { 73 | var k = a.data(this, b + f); 74 | if (k) { 75 | k.option(i || {})._init() 76 | } else { 77 | a.data(this, b + f, new e(i, this)) 78 | } 79 | }) 80 | } 81 | return j 82 | } 83 | }; 84 | a.Widget = function(e, f) { 85 | if (arguments.length) { 86 | this._createWidget(e, f) 87 | } 88 | }; 89 | a.Widget.prototype = { 90 | widgetName: "widget", 91 | widgetEventPrefix: "", 92 | options: { 93 | disabled: false 94 | }, 95 | _createWidget: function(f, g) { 96 | a.data(g, b + this.widgetName, this); 97 | this.element = a(g); 98 | this.options = a.extend(true, {}, this.options, this._getCreateOptions(), f); 99 | var e = this; 100 | this.element.bind("remove." + this.widgetName, function() { 101 | e.destroy() 102 | }); 103 | this._create(); 104 | this._trigger("create"); 105 | this._init() 106 | }, 107 | _getCreateOptions: function() { 108 | return this.element.data(this.widgetName) 109 | }, 110 | _create: function() {}, 111 | _init: function() {}, 112 | destroy: function() { 113 | this.element.unbind("." + this.widgetName).removeData(b + this.widgetName); 114 | this.widget().unbind("." + this.widgetName).removeAttr("aria-disabled").removeClass(this.widgetBaseClass + "-disabled ui-state-disabled") 115 | }, 116 | widget: function() { 117 | return this.element 118 | }, 119 | option: function(f, g) { 120 | var e = f; 121 | if (arguments.length === 0) { 122 | return a.extend({}, this.options) 123 | } 124 | if (typeof f === "string") { 125 | if (g === d) { 126 | return this.options[f] 127 | } 128 | e = {}; 129 | e[f] = g 130 | } 131 | this._setOptions(e); 132 | return this 133 | }, 134 | _setOptions: function(f) { 135 | var e = this; 136 | a.each(f, function(g, h) { 137 | e._setOption(g, h) 138 | }); 139 | return this 140 | }, 141 | _setOption: function(e, f) { 142 | this.options[e] = f; 143 | if (e === "disabled") { 144 | this.widget()[f ? "addClass" : "removeClass"](this.widgetBaseClass + "-disabled ui-state-disabled").attr("aria-disabled", f) 145 | } 146 | return this 147 | }, 148 | enable: function() { 149 | return this._setOption("disabled", false) 150 | }, 151 | disable: function() { 152 | return this._setOption("disabled", true) 153 | }, 154 | _trigger: function(e, f, g) { 155 | var j, i, h = this.options[e]; 156 | g = g || {}; 157 | f = a.Event(f); 158 | f.type = (e === this.widgetEventPrefix ? e : this.widgetEventPrefix + e).toLowerCase(); 159 | f.target = this.element[0]; 160 | i = f.originalEvent; 161 | if (i) { 162 | for (j in i) { 163 | if (!(j in f)) { 164 | f[j] = i[j] 165 | } 166 | } 167 | } 168 | this.element.trigger(f, g); 169 | return !(a.isFunction(h) && h.call(this.element[0], f, g) === false || f.isDefaultPrevented()) 170 | } 171 | } 172 | })(jQuery); -------------------------------------------------------------------------------- /examples/her-website/widget/nav/nav.css: -------------------------------------------------------------------------------- 1 | // #nav { 2 | // margin-top: 80px; 3 | // } 4 | 5 | #nav ul { 6 | list-style: none; 7 | padding: 0; 8 | margin: 0; 9 | } 10 | 11 | #nav ul li { 12 | cursor: pointer; 13 | display: inline-block; 14 | line-height: 22px; 15 | filter: alpha(opacity=60); 16 | font-size: 16px; 17 | font-size: 1.6rem; 18 | font-style: normal; 19 | // font-weight: 100; 20 | opacity: .6; 21 | padding: 8px 0 8px 12px; 22 | text-transform: uppercase; 23 | width: 70%; 24 | } 25 | 26 | #nav ul li.active { 27 | filter: alpha(opacity=100); 28 | opacity: 1; 29 | } 30 | 31 | #nav ul li.last { 32 | padding-right: 0px; 33 | } 34 | 35 | #nav li span { 36 | display: inline-block; 37 | font-size: 14px; 38 | height: 0; 39 | opacity: 0; 40 | overflow: hidden; 41 | padding-left: 10px; 42 | width: 0; 43 | } 44 | 45 | #nav li i { 46 | position: relative; 47 | top: 2px; 48 | } 49 | 50 | .expanded #nav li { 51 | width: 90%; 52 | } 53 | .expanded #nav li span { 54 | display: inline-block; 55 | height: 14px; 56 | line-height: 14px; 57 | opacity: 1; 58 | overflow: visible; 59 | width: auto; 60 | } -------------------------------------------------------------------------------- /examples/her-website/widget/nav/nav.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hao123-fe/her/b3c9f53f6797738f417a6aec2219981a351e9c59/examples/her-website/widget/nav/nav.js -------------------------------------------------------------------------------- /examples/her-website/widget/nav/nav.tpl: -------------------------------------------------------------------------------- 1 | {define} 2 | 18 | {/define} -------------------------------------------------------------------------------- /examples/her-website/widget/section/docs/how.tpl: -------------------------------------------------------------------------------- 1 | {define} 2 | 3 |

How to ?

4 |

核心能力

5 | 6 |

Her 通过实现以下核心能力来解决前端性能优化:

7 | 8 |
    9 |
  • 强大的自动化构建能力。Her 集成了 FIS 资源定位、内容嵌入、依赖声明 3种编译构建能力,满足了前端构建需求。

  • 10 |
  • 11 |

    核心运行时能力

    12 | 13 |
      14 |
    • 通过 Pagelet Smarty 插件对页面分块。分块收集 HTML 片段及其依赖的 CSS、JS 资源,对页面模块进行细粒度编码,分解资源依赖和数据获取等
    • 15 |
    • 后端输出控制器。后端输出控制提供了FirstControllerQuicklingControllerNoScriptController 3种输出控制器,分别处理基础页请求、局部 Quickling 请求和 NoScript 请求,其中 FirstControllerPagelet 提供了 server|lazy|default|none 4种输出模式,方便实现核心(首屏)模块优先输出、非核心模块延迟输出,模块开关等
    • 16 |
    • 前端渲染控制器。实现了 Pagelet 按需加载、渲染,资源及其依赖加载、资源动态化打包(计划中)等
    • 17 |
    18 |
  • 19 |
  • 20 |

    定制优化方案的能力,通过对 Pagelet 输出和渲染方式的简单配置编码,可以方便实现以下优化方案和业务方案

    21 | 22 |
      23 |
    • 延迟加载 lazyPagelets。对于非核心模块 Pagelets 后端可以使用 lazy 渲染模式,基础页请求的时候只输出占位标签,基础页渲染完成之后通过 Quickling 方式延迟加载 lazyPagelets,从而实现延迟加载 lazyPagelets,减少基础页 DOM 节点数,极大的优化页面渲染性能。
    • 24 |
    • 延迟渲染 bigRender。对于不可见模块可以先不渲染,当用户滚动页面的时候再渲染相应模块。可以进一步提升性能,减少不可见模块的图片和数据接口请求等。
    • 25 |
    • 局部刷新 Quickling。对于数据交互频繁的模块,可以通过 BigPipe.fetch() 实现局部刷新,可以实现同构的异步渲染逻辑,极大了降低了异步刷新的开发成本。
    • 26 |
    27 |
  • 28 |
29 | 30 | {/define} -------------------------------------------------------------------------------- /examples/her-website/widget/section/docs/img/fis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hao123-fe/her/b3c9f53f6797738f417a6aec2219981a351e9c59/examples/her-website/widget/section/docs/img/fis.png -------------------------------------------------------------------------------- /examples/her-website/widget/section/docs/img/map.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hao123-fe/her/b3c9f53f6797738f417a6aec2219981a351e9c59/examples/her-website/widget/section/docs/img/map.png -------------------------------------------------------------------------------- /examples/her-website/widget/section/docs/img/pack.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hao123-fe/her/b3c9f53f6797738f417a6aec2219981a351e9c59/examples/her-website/widget/section/docs/img/pack.gif -------------------------------------------------------------------------------- /examples/her-website/widget/section/docs/img/uri.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hao123-fe/her/b3c9f53f6797738f417a6aec2219981a351e9c59/examples/her-website/widget/section/docs/img/uri.png -------------------------------------------------------------------------------- /examples/her-website/widget/section/docs/start.tpl: -------------------------------------------------------------------------------- 1 | {define} 2 | {/define} -------------------------------------------------------------------------------- /examples/her-website/widget/section/docs/what.tpl: -------------------------------------------------------------------------------- 1 | {define} 2 |

What is Her ?

3 | 4 |

Her 是基于 FIS 封装的解决方案,其设计目标是通过工程化的方法解决前端性能优化中的核心问题

5 | 6 |

Her 目前提供了基于 Smarty 的实现,适用于使用或计划使用 Smarty 作为模板语言且对页面前端性能优化有较高要求的(PC或mobile)页面和站点。

7 | 8 | {/define} -------------------------------------------------------------------------------- /examples/her-website/widget/section/docs/why.tpl: -------------------------------------------------------------------------------- 1 | {define} 2 |

Why Her ?

3 | 4 |

核心优势

5 | 6 |
    7 |
  • 8 |

    Her 集成了 FIS 的前端工程化能力,能够有效的

    9 | 10 |
    11 |

    解决前端开发中自动化工具、性能优化、模块化框架、开发规范、代码部署、开发流程等问题

    12 |
    13 |
  • 14 |
  • 15 |

    Her 封装了前端性能优化的核心解决方案,让前端性能优化更简单,如下:

    16 | 17 |
      18 |
    • 减少页面 DOM 节点数
    • 19 |
    • 核心(首屏)模块优先输出、渲染
    • 20 |
    • 非核心模块懒加载、延迟渲染
    • 21 |
    • 资源按需加载
    • 22 |
    • 优化资源打包方案
    • 23 |
    24 |
  • 25 |
26 | 27 | {/define} -------------------------------------------------------------------------------- /examples/her-website/widget/section/section.css: -------------------------------------------------------------------------------- 1 | .section { 2 | background-attachment: scroll; 3 | background-position: center top; 4 | padding: 80px 0 0; 5 | width: 800px; 6 | margin: 0 auto; 7 | } 8 | 9 | .section .title { 10 | position: relative; 11 | height: 45px; 12 | } 13 | 14 | .section .title h2 { 15 | font-size: 58px; 16 | font-size: 5.8rem; 17 | font-weight: 700; 18 | letter-spacing: -1px; 19 | line-height: 72px; 20 | position: absolute; 21 | background: #fff; 22 | padding-right: 50px; 23 | font-size: 67px; font-size: 6.7rem; 24 | } 25 | 26 | .section .title { 27 | border-bottom: 1px solid #151515; 28 | } 29 | 30 | .section h3 { 31 | padding: 40px 0 20px; 32 | } 33 | 34 | .section h4 { 35 | font-size: 23px; font-size: 2.3rem; 36 | font-weight: 500; 37 | line-height: 28px; 38 | margin: 0 0 20px; 39 | padding-bottom: 5px; 40 | } 41 | 42 | .section h5 { 43 | font-size: 19px; font-size: 1.9rem; 44 | font-weight: 500; 45 | line-height: 24px; 46 | margin: 0 0 20px; 47 | padding-bottom: 5px; 48 | } 49 | 50 | .section .desc { 51 | margin-top: 20px; 52 | } 53 | 54 | .section .content { 55 | margin-top: 75px; 56 | } 57 | 58 | .section li { 59 | line-height: 30px; 60 | } 61 | 62 | @media screen and (min-width: 1200px) { 63 | .section h2 { 64 | font-size: 67px; font-size: 6.7rem; 65 | } 66 | } 67 | 68 | @media screen and (max-width: 1200px) { 69 | .section { 70 | padding: 70px 5%; 71 | } 72 | .section h2 { 73 | font-size: 55px; font-size: 5.5rem; 74 | } 75 | } 76 | 77 | @media screen and (max-width: 979px) { 78 | .section { 79 | padding: 40px 5%; 80 | } 81 | .section h2 { 82 | font-size: 47px;font-size: 4.7rem; 83 | line-height: 58px; 84 | } 85 | } -------------------------------------------------------------------------------- /examples/her-website/widget/section/section.tpl: -------------------------------------------------------------------------------- 1 | {define method="section" index=0 doc=null wiki=null} 2 | 3 |
4 |
5 |
6 |

{$data.title}

7 |
8 |
9 | 10 | {widget name="home:widget/section/docs/`$doc`.tpl"} 11 | 12 | 了解更多 13 | 14 | 15 |
16 |
17 |
18 | {/define} 19 | -------------------------------------------------------------------------------- /examples/her-website/widget/sidebar/sidebar.async.js: -------------------------------------------------------------------------------- 1 | exports.run = function(){ 2 | $('html').toggleClass('expanded'); 3 | }; -------------------------------------------------------------------------------- /examples/her-website/widget/sidebar/sidebar.css: -------------------------------------------------------------------------------- 1 | #sidebar { 2 | background-color: #fff; 3 | // height: 120%; 4 | padding: 1px; 5 | position: fixed; 6 | _position: absolute; 7 | left: 10px; 8 | top: 10px; 9 | // width: 50px; 10 | z-index: 20; 11 | color: #F1F1F1; 12 | -moz-user-select: none; 13 | -webkit-user-select: none; 14 | -ms-user-select: none; 15 | } 16 | 17 | 18 | #sidebar .navigation { 19 | display: none; 20 | } 21 | 22 | .expanded #sidebar .navigation { 23 | display: block; 24 | } 25 | .btn-navbar { 26 | cursor: pointer; 27 | filter: alpha(opacity=40); 28 | float: left; 29 | // margin: 8px 5px; 30 | opacity: .4; 31 | padding: 13px 10px; 32 | } 33 | 34 | .btn-navbar .icon-bar { 35 | background-color: #666; 36 | border-radius: 1px 1px 1px 1px; 37 | box-shadow: none; 38 | display: block; 39 | height: 2px; 40 | width: 18px; 41 | } 42 | #sidebar a { 43 | display: block; 44 | color: #333; 45 | } 46 | 47 | #sidebar a:hover span{ 48 | text-decoration: underline; 49 | // color: #fff; 50 | } 51 | 52 | .expanded #sidebar { 53 | width: 150px; 54 | border-radius: 2px; 55 | padding: 0; 56 | box-shadow: 0 1px 3px 0 #eee; 57 | border: 1px solid #efefef; 58 | border-bottom: 1px solid #ddd; 59 | } -------------------------------------------------------------------------------- /examples/her-website/widget/sidebar/sidebar.tpl: -------------------------------------------------------------------------------- 1 | {define} 2 | 3 | 4 | 5 | 6 | 7 | {widget name="home:widget/nav/nav.tpl"} 8 | {/define} -------------------------------------------------------------------------------- /examples/her-website/widget/slogan/blacktocat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hao123-fe/her/b3c9f53f6797738f417a6aec2219981a351e9c59/examples/her-website/widget/slogan/blacktocat.png -------------------------------------------------------------------------------- /examples/her-website/widget/slogan/landscape.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hao123-fe/her/b3c9f53f6797738f417a6aec2219981a351e9c59/examples/her-website/widget/slogan/landscape.jpg -------------------------------------------------------------------------------- /examples/her-website/widget/slogan/macbook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hao123-fe/her/b3c9f53f6797738f417a6aec2219981a351e9c59/examples/her-website/widget/slogan/macbook.png -------------------------------------------------------------------------------- /examples/her-website/widget/slogan/slogan.css: -------------------------------------------------------------------------------- 1 | /** 2 | * slogan也使用了.section的样式 3 | * @require ../section/section.css 4 | */ 5 | 6 | #slogan { 7 | background-size: 100% 100%; 8 | margin-top: 80px; 9 | } 10 | 11 | #slogan .box { 12 | color: #666; 13 | font-weight: normal; 14 | line-height: 56px; 15 | text-align: center; 16 | position: relative; 17 | z-index: 10; 18 | } 19 | #slogan h1 { 20 | font-family: 'Open Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; 21 | letter-spacing: 0px; 22 | font-size: 96px; 23 | font-size: 9.6rem; 24 | margin-bottom: 0; 25 | font-weight: 100; 26 | letter-spacing: 5px; 27 | } 28 | #slogan h3 { 29 | font-family: 'Open Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; 30 | margin-left: 10px; 31 | font-weight: 200; 32 | } 33 | #slogan img { 34 | position: absolute; 35 | right: 0; 36 | top: 70px; 37 | z-index: 1; 38 | } 39 | 40 | #forkme_banner { 41 | display: block; 42 | position: fixed; 43 | top: 0; 44 | right: 20px; 45 | z-index: 10; 46 | padding: 10px 35px 10px 10px; 47 | color: #fff; 48 | background: url('blacktocat.png') #0090ff no-repeat 95% 50%; 49 | background-size: 20px 20px; 50 | font-weight: 200; 51 | box-shadow: 0 0 10px rgba(0,0,0,.5); 52 | border-bottom-left-radius: 2px; 53 | border-bottom-right-radius: 2px; 54 | } 55 | 56 | @media (max-width: 1024px) { 57 | #slogan img { 58 | display: none; 59 | } 60 | } -------------------------------------------------------------------------------- /examples/her-website/widget/slogan/slogan.tpl: -------------------------------------------------------------------------------- 1 | {define} 2 |
3 |
4 |

Her

5 |

Hao123前端高性能渲染解决方案

6 | Get Start 7 |
8 | {* *} 9 |
10 | {/define} -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var del = require('del'); 3 | var inline = require('gulp-inline-js'); 4 | 5 | var path = { 6 | libjs: { 7 | src: 'src/runtime/main.js', 8 | release: 'dist/' 9 | }, 10 | libplugin: { 11 | src: 'src/server/**/*', 12 | release: 'dist/server' 13 | }, 14 | jshelper: { 15 | src: 'src/js_helper/**/*', 16 | release: 'dist/js_helper' 17 | } 18 | }; 19 | 20 | //compile libjs 21 | gulp.task('libjs:compile', function () { 22 | return gulp.src(path.libjs.src) 23 | .pipe(inline()) 24 | .pipe(gulp.dest(path.libjs.release)); 25 | }); 26 | 27 | //copy runtime 28 | gulp.task('libplugin:copy', function () { 29 | gulp.src(path.libplugin.src) 30 | .pipe(gulp.dest(path.libplugin.release)); 31 | }); 32 | 33 | //copy jshelper 34 | gulp.task('js_helper:copy', function () { 35 | return gulp.src(path.jshelper.src) 36 | .pipe(gulp.dest(path.jshelper.release)); 37 | }); 38 | 39 | gulp.task('clean', del.bind(null, 'dist/**')); 40 | gulp.task('compile', ['libjs:compile', 'libplugin:copy', 'js_helper:copy']); 41 | 42 | gulp.task('default', ['clean'], function(){ 43 | gulp.start('compile'); 44 | }); 45 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | // var fis = module.exports = require('her-fis'); 2 | var fis = module.exports = require('fis'); 3 | 4 | fis.cli.name = 'her'; 5 | fis.cli.info = fis.util.readJSON(__dirname + '/package.json'); 6 | fis.cli.version = require('./version.js'); 7 | 8 | fis.require.prefixes = ['her', 'fis']; 9 | 10 | var defaultConfig = require('./src/config/default.js'); 11 | fis.config.merge(defaultConfig); 12 | // override Connector to update file name to fix cdn cache herid issue 13 | fis.config.merge({ 14 | project : { md5Connector : '.' } 15 | }); 16 | //alias 17 | Object.defineProperty(global, 'her', { 18 | enumerable: true, 19 | writable: false, 20 | value: fis 21 | }); 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "her", 3 | "version": "0.2.0", 4 | "description": "A High-performence Enhanced Rendering solution - Hao123前端高性能渲染解决方案", 5 | "keywords": [ 6 | "herJS", 7 | "fis", 8 | "bigpipe", 9 | "quickling", 10 | "bigrender", 11 | "lazyrender", 12 | "pagelet", 13 | "high-performence rendering" 14 | ], 15 | "homepage": "https://hao123-fe.github.io/her", 16 | "bugs": { 17 | "url": "https://github.com/hao123-fe/her/issues" 18 | }, 19 | "license": "MIT", 20 | "author": "hao123-fe ", 21 | "contributors": [ 22 | "zhangwentao ", 23 | "zhangyuanwei ", 24 | "ustbhuangyi ", 25 | "caoyu " 26 | ], 27 | "main": "index.js", 28 | "bin": { 29 | "her": "bin/her" 30 | }, 31 | "repository": { 32 | "type": "git", 33 | "url": "https://github.com/hao123-fe/her.git" 34 | }, 35 | "scripts": { 36 | "build": "gulp", 37 | "server:init": "bin/her server clean && bin/her server init", 38 | "server:start": "bin/her server start --type php -p 8088", 39 | "dev:release": "cd examples/her-website && ../../bin/her release -c", 40 | "dev:watch": "cd examples/her-website && ../../bin/her release -cw", 41 | "start": "npm run build && cp -r dist/* examples/her-website/lib/ && npm run dev:release && npm run server:init && npm run server:start && npm run dev:watch" 42 | }, 43 | "dependencies": { 44 | "fis": "^1.9.44", 45 | "fis-optimizer-html-compress": "^0.0.7", 46 | "fis-optimizer-smarty-xss": "^0.1.3", 47 | "fis-parser-less": "^0.1.2", 48 | "del": "^0.1.3", 49 | "gulp": "^3.6.0", 50 | "gulp-inline-js": "latest" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/compiler/autoPackAnalyze.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 自动打包方案分析脚本 3 | * @author caoyu (261179233@qq.com) 4 | */ 5 | var exports = module.exports = function (ret, conf, settings, opt) { 6 | 7 | if (fis.config.get('autopack')) { 8 | 9 | var res = ret.map.res; 10 | 11 | // 获取tpl依赖的js 12 | function getDepsOfPage(id, arr, type) { 13 | 14 | if (res[id].deps && res[id].deps.length) { 15 | // 遍历依赖的tpl/js/css 16 | for (var i = 0; i < res[id].deps.length; i++) { 17 | var itemId = res[id].deps[i]; 18 | if (res[itemId]) { 19 | if (res[itemId].type === 'tpl') { 20 | // 获取widget的依赖 21 | arr = getDepsOfPage(itemId, arr, type); 22 | } else if (res[itemId].type === type) { 23 | // 子集资源 index 24 | var itemIndex = arr.indexOf(itemId); 25 | // 父级资源 index 26 | var parentIndex = arr.indexOf(id) === -1 ? arr.length : arr.indexOf(id); 27 | 28 | if (itemIndex === -1) { 29 | arr.splice(parentIndex, 0, itemId); 30 | } 31 | // 获取资源的依赖 32 | arr = getDepsOfPage(itemId, arr, type); 33 | } 34 | } 35 | } 36 | } 37 | 38 | return arr; 39 | } 40 | 41 | function analyze(type) { 42 | // 第一步:以page为维度,创建page依赖的资源链,递归进行,去重,排序,不支持循环引用 43 | var pageOfJsMap = {}; 44 | fis.util.map(res, function (key, value) { 45 | if (value.extras && value.extras.isPage) { 46 | pageOfJsMap[key] = getDepsOfPage(key, [], type); 47 | } 48 | }); 49 | 50 | // 第二步:以第一步产出的js为维度,合并tpl,进行js去重 51 | var jsOfPageArrMap = {}; 52 | fis.util.map(pageOfJsMap, function (parentKey, parentValue) { 53 | fis.util.map(parentValue, function (subKey, subValue) { 54 | jsOfPageArrMap[subValue] = jsOfPageArrMap[subValue] || []; 55 | if (jsOfPageArrMap[subValue].indexOf(parentKey) === -1) { 56 | jsOfPageArrMap[subValue].push(parentKey); 57 | } 58 | }); 59 | }); 60 | 61 | // 给page数组排序,将相同的tpl数组顺序统一 62 | fis.util.map(jsOfPageArrMap, function (key, value) { 63 | value.sort(); 64 | }); 65 | 66 | // 第三步:以第二步产出的tpl array为维度,合并js,进行tpl&array去重 67 | var pageArrOfJsArrMap = {}; 68 | fis.util.map(jsOfPageArrMap, function (key, value) { 69 | var info = '/resource/pkg/aio-' + fis.util.md5(value.join(',')) + '.' + type; 70 | pageArrOfJsArrMap[info] = pageArrOfJsArrMap[info] || []; 71 | if (pageArrOfJsArrMap[info].indexOf(key) === -1) { 72 | pageArrOfJsArrMap[info].push((key.split(':'))[1]); 73 | } 74 | }); 75 | 76 | fis.config.merge({ 77 | pack: pageArrOfJsArrMap 78 | }); 79 | } 80 | 81 | analyze('css'); 82 | analyze('js'); 83 | } 84 | }; 85 | 86 | -------------------------------------------------------------------------------- /src/compiler/commonReg.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 一些通用的正则表达式库 3 | */ 4 | 5 | var stringRegStr = "(?:" + 6 | "\"(?:[^\\\\\"\\r\\n\\f]|\\\\[\\s\\S])*\"" + //匹配以"为界定符的字符禅 7 | "|" + 8 | "\'(?:[^\\\\\'\\r\\n\\f]|\\\\[\\s\\S])*\'" + //匹配以'为界定符的字符串 9 | ")", 10 | jscommentRegStr = "(?:" + 11 | "\\/\\/[^\\r\\n\\f]*" + // 匹配单行注释 12 | "|" + 13 | "\\/\\*[\\s\\S]+?\\*\\/" + //匹配多行注释 14 | ")", 15 | jsStringArrayRegStr = "(?:" + 16 | "\\[\\s*" + stringRegStr + "(?:\\s*,\\s*" + stringRegStr + ")*\\s*\\]" + //匹配非空字符串数组 17 | ")"; 18 | 19 | exports.stringRegStr = stringRegStr; 20 | exports.jscommentRegStr = jscommentRegStr; 21 | exports.jsStringArrayRegStr = jsStringArrayRegStr; 22 | -------------------------------------------------------------------------------- /src/compiler/cssInline.js: -------------------------------------------------------------------------------- 1 | /** 2 | * postpackager cssInline 3 | * 4 | * @author zhangwentao 5 | */ 6 | 7 | module.exports = function(ret, conf, settings, opt) { 8 | fis.util.map(ret.map.her, function (herId) { 9 | var herRes = ret.map.her[herId]; 10 | if (herRes.inline && herRes.file) { 11 | // console.log(herId); 12 | herRes.content = herRes.file.getContent(); 13 | delete herRes.inline; 14 | delete herRes.file; 15 | } 16 | }); 17 | }; -------------------------------------------------------------------------------- /src/compiler/jsWrapper.js: -------------------------------------------------------------------------------- 1 | /** 2 | * jsWrapper 以amd模式为js代码封装define,并分析文件中的require,放入depends中 3 | */ 4 | var commonReg = require("./commonReg.js"); 5 | 6 | //构造分析require("xxx")的正则表达式,同时优先分析字符串和注释。 7 | var requireRegStr = commonReg.stringRegStr + "|" + 8 | commonReg.jscommentRegStr + "|" + 9 | "([^\\$\\.]|^)(\\brequire\\s*\\(\\s*(" + 10 | commonReg.stringRegStr + ")\\s*\\))"; 11 | 12 | var PREFIX = "__", 13 | SUFFIX = "__"; 14 | 15 | var defaultDeps = ["global", "module", "exports", "require"]; 16 | 17 | module.exports = function (content, file, conf) { 18 | var reg, deps, md5deps, args; 19 | 20 | if (file.isMod || conf.wrapAll) { 21 | reg = new RegExp(requireRegStr, "g"); //分析require正则实例化 22 | deps = []; //require依赖的模块standard路径数组 23 | md5deps = []; //require依赖模块standard路径md5后的数组 24 | args; //define的factory回调函数的参数 25 | 26 | content = content.replace(reg, function (all, requirePrefix, requireStr, requireValueStr) { 27 | var info, dep, md5dep; 28 | 29 | //满足条件说明匹配到期望的require,注释和字符中的requirePrefix都为undefined 30 | if (requirePrefix !== undefined) { 31 | 32 | info = fis.util.stringQuote(requireValueStr); 33 | 34 | //把路径standard 35 | dep = fis.uri.getId(info.rest, file.dirname).id; 36 | 37 | //把standard路径再次md5,作为函数参数用 38 | md5dep = PREFIX + fis.util.md5(dep) + SUFFIX; 39 | 40 | //避免重复添加 41 | if (deps.indexOf(dep) < 0) { 42 | deps.push(dep); 43 | md5deps.push(md5dep); 44 | } 45 | 46 | //把reuqire注释并替换成md5后的函数参数 47 | return requirePrefix + "/*" + requireStr + "*/" + md5dep; 48 | } 49 | return all; 50 | }); 51 | 52 | args = defaultDeps.concat(md5deps); 53 | 54 | content = "define('" + file.getId() + "'," + JSON.stringify(defaultDeps.concat(deps)) + ",function(" + args.join(", ") + "){\n\n" + content + "\n\n});"; 55 | 56 | } 57 | 58 | return content; 59 | 60 | }; -------------------------------------------------------------------------------- /src/compiler/pregQuote.js: -------------------------------------------------------------------------------- 1 | function pregQuote(str, delimiter) { 2 | // http://kevin.vanzonneveld.net 3 | return (str + '').replace(new RegExp('[.\\\\+*?\\[\\^\\]$(){}=!<>|:\\' + (delimiter || '') + '-]', 'g'), '\\$&'); 4 | } 5 | module.exports = pregQuote; 6 | -------------------------------------------------------------------------------- /src/compiler/requireAnalyze.js: -------------------------------------------------------------------------------- 1 | /** 2 | * requireAnalyze 分析文件中的同步require和异步require 3 | */ 4 | var commonReg = require("./commonReg.js"); 5 | 6 | var stringRegStr = commonReg.stringRegStr, 7 | jscommentRegStr = commonReg.jscommentRegStr, 8 | jsStringArrayRegStr = commonReg.jsStringArrayRegStr, 9 | //构造分析require|require.async|require.defer的正则表达式 10 | requireRegStr = "((?:[^\\$\\.]|^)\\brequire(?:\\s*\\.\\s*(async|defer))?\\s*\\(\\s*)(" + 11 | stringRegStr + "|" + 12 | jsStringArrayRegStr + ")"; 13 | 14 | module.exports = function (content, file, conf) { 15 | 16 | var reg, requires, initial; 17 | 18 | if (file.isMod || conf.wrapAll) { 19 | //优先匹配字符串和注释 20 | reg = new RegExp(stringRegStr + "|" + 21 | jscommentRegStr + "|" + 22 | requireRegStr, "g"); 23 | //存储分析的同步require和异步require 24 | requires = { 25 | sync: {}, 26 | async: {} 27 | }; 28 | initial = false; 29 | 30 | if (file.extras == undefined) { 31 | file.extras = {}; 32 | initial = true; 33 | } 34 | file.extras.async = []; 35 | 36 | content = content.replace(reg, function (all, requirePrefix, requireType, requireValueStr) { 37 | var hasBrackets = false, 38 | requireValue, holder, info, quote; 39 | 40 | //满足条件说明匹配到期望的require,注释和字符中的requirePrefix都为undefined 41 | if (requirePrefix) { 42 | 43 | //判断是否有[],有则切割, 44 | requireValueStr = requireValueStr.trim().replace(/(^\[|\]$)/g, function (m, v) { 45 | if (v) { 46 | hasBrackets = true; 47 | } 48 | return ''; 49 | }); 50 | 51 | requireValue = requireValueStr.split(/\s*,\s*/); 52 | 53 | requireType = "require" + (requireType ? ("." + requireType) : ""); 54 | 55 | switch (requireType) { 56 | case "require": 57 | holder = requires.sync; 58 | break; 59 | case "require.async": 60 | case "require.defer": 61 | holder = requires.async; 62 | break; 63 | default: 64 | break; 65 | } 66 | 67 | requireValue.forEach(function (item, index, array) { 68 | //standard路径 69 | info = fis.uri.getId(item, file.dirname); 70 | holder[info.id] = true; 71 | 72 | }); 73 | 74 | //standard路径 75 | if (hasBrackets) {//Array 76 | all = requirePrefix + 77 | "[" + requireValue.map(function (path) { 78 | quote = fis.util.stringQuote(path).quote; 79 | return quote + fis.uri.getId(path, file.dirname).id + quote 80 | }).join(",") + "]"; 81 | } else { //String 82 | quote = fis.util.stringQuote(requireValueStr).quote; 83 | all = requirePrefix + quote + fis.uri.getId(requireValueStr, file.dirname).id + quote; 84 | } 85 | 86 | } 87 | return all; 88 | }); 89 | 90 | //处理同步require 91 | // for (var id in requires.sync) { 92 | // file.addRequire(id); 93 | // } 94 | 95 | 96 | //处理异步require 97 | for (var id in requires.async) { 98 | if (file.extras.async.indexOf(id) < 0) { 99 | file.extras.async.push(id); 100 | } 101 | } 102 | 103 | //如果没有异步依赖,删除多余的对象 104 | if (file.extras.async.length == 0) { 105 | delete file.extras.async; 106 | if (initial) { 107 | delete file.extras; 108 | } 109 | } 110 | } 111 | 112 | return content; 113 | 114 | }; -------------------------------------------------------------------------------- /src/config/default.js: -------------------------------------------------------------------------------- 1 | var templateBuilder = require("../compiler/templateBuilder.js"); 2 | var requireAnalyze = require("../compiler/requireAnalyze.js"); 3 | var jsWrapper = require("../compiler/jsWrapper.js"); 4 | var autoPackAnalyze = require("../compiler/autoPackAnalyze.js"); 5 | var outputHermap = require("../compiler/outputHermap.js"); 6 | var cssInline = require("../compiler/cssInline.js"); 7 | 8 | //copy fis-plus default configs 9 | module.exports = { 10 | //静态文件目录 11 | statics: "/static", 12 | templates: '/template', 13 | namespace: '', 14 | //fis server config 15 | server: { 16 | rewrite: true, 17 | libs: 'pc', 18 | clean: { 19 | exclude: "fisdata**,smarty**,rewrite**,index.php**,WEB-INF**" 20 | } 21 | }, 22 | modules: { 23 | parser: { 24 | less: "less" 25 | }, 26 | preprocessor: { 27 | //tpl: "extlang" 28 | tpl: [ 29 | templateBuilder.replaceScriptTag, 30 | templateBuilder.explandPath 31 | ] 32 | }, 33 | postprocessor: { 34 | tpl: [ 35 | templateBuilder.analyseSmartyRequire, 36 | templateBuilder.analyseScript, 37 | templateBuilder.defineWidget 38 | ], 39 | //TOOD her 主要处理点 40 | js: [ 41 | requireAnalyze, 42 | jsWrapper 43 | ] 44 | //css: explander.css 45 | }, 46 | optimizer: { 47 | tpl: 'smarty-xss,html-compress' 48 | //tpl: 'html-compress' 49 | }, 50 | prepackager: [ 51 | autoPackAnalyze, 52 | outputHermap 53 | ], 54 | packager: [], 55 | postpackager: [ cssInline ] 56 | }, 57 | // modules: { 58 | // parser: { 59 | // less: 'less' 60 | // }, 61 | // preprocessor: { 62 | // tpl: 'extlang' 63 | // }, 64 | // postprocessor: { 65 | // tpl: 'require-async', 66 | // js: 'jswrapper, require-async' 67 | // }, 68 | // optimizer: { 69 | // tpl: 'smarty-xss,html-compress' 70 | // }, 71 | // prepackager: 'widget-inline,js-i18n' 72 | // }, 73 | roadmap: { 74 | ext: { 75 | less: 'css', 76 | tmpl: 'js', 77 | po: 'json' 78 | }, 79 | path: [ 80 | // i18n 81 | { 82 | reg: '/fis_translate.tpl', 83 | release: '/template/${namespace}/widget/fis_translate.tpl' 84 | }, { 85 | reg: /\/lang\/([^\/]+)\.po/i, 86 | release: '/config/lang/${namespace}.$1.po' 87 | }, 88 | //i18n end 89 | { 90 | reg: /^\/widget\/(.*\.tpl)$/i, 91 | isMod: true, 92 | url: '${namespace}/widget/$1', 93 | release: '/template/${namespace}/widget/$1' 94 | }, { 95 | reg: /^\/widget\/(.*\.(js|css))$/i, 96 | isMod: true, 97 | release: '${statics}/${namespace}/widget/$1' 98 | }, { 99 | reg: /^\/page\/(.+\.tpl)$/i, 100 | isMod: true, 101 | release: '/template/${namespace}/page/$1', 102 | extras: { 103 | isPage: true 104 | } 105 | }, { 106 | reg: /\.tmpl$/i, 107 | release: false 108 | }, { 109 | reg: /^\/(static)\/(.*)/i, 110 | release: '${statics}/${namespace}/$2' 111 | }, { 112 | reg: /^\/(config|test)\/(.*)/i, 113 | isMod: false, 114 | release: '/$1/${namespace}/$2' 115 | }, { 116 | reg: /^\/(plugin|smarty\.conf$)|\.php$/i 117 | }, { 118 | reg: 'server.conf', 119 | release: '/server-conf/${namespace}.conf' 120 | }, { 121 | reg: "domain.conf", 122 | release: '/config/$&' 123 | }, { 124 | reg: "build.sh", 125 | release: false 126 | }, { 127 | reg: '${namespace}-map.json', 128 | release: '/config/${namespace}-map.json' 129 | }, { 130 | reg: '${namespace}-hermap.json', 131 | release: '/config/${namespace}-hermap.json' 132 | }, { 133 | reg: /^.+$/, 134 | release: '${statics}/${namespace}$&' 135 | } 136 | ] 137 | }, 138 | settings: { 139 | parser: { 140 | bdtmpl: { 141 | LEFT_DELIMITER: '<#', 142 | RIGHT_DELIMITER: '#>' 143 | } 144 | }, 145 | smarty: { 146 | left_delimiter: "{%", 147 | right_delimiter: "%}" 148 | } 149 | // ,postprocessor: { 150 | // jswrapper: { 151 | // type: 'amd' 152 | // } 153 | // } 154 | } 155 | }; 156 | -------------------------------------------------------------------------------- /src/js_helper/README.md: -------------------------------------------------------------------------------- 1 | # js_helper 2 | 基于 her 基本方法封装的一些插件 3 | 4 | ## append.js 5 | 6 | 结合 `BigPipe.fetch()` 实现无限追加,实现无限滚动加载等功能 7 | 8 | 其原理是利用 pagelet id 的动态特性,在前端对 pagelet id 做自增计数,同时预先插入与 id 对应的空的 pagelet 占位容器作为 quickling 渲染的容器,并将 id 通过 query 参数传到 smarty,将 smarty 端的 pagelet id 与前端对应上,即可实现 append 的效果。 9 | 10 | 支持方法: 11 | 12 | ```javascript 13 | // 初始化配置 14 | // pageletPrefix 为需要 append 的 pagelet id 前缀 15 | // conf 为对应的配置 16 | append.init( pageletPrefix, key, // pagelet 自增 id 参数名, 可同时作为分页参数 18 | wrapId // 父容器节点 id 19 | }> conf) 20 | 21 | // 调用 append 22 | // pageletPrefix 同上 23 | // 其中 url, cb 同 BigPipe.fetch(pagelets, url, cb) 24 | append( pageletPrefix, url, cb) 25 | 26 | ``` 27 | 28 | 使用实例: 29 | 30 | ```smarty 31 | {* 声明全局 $_p_id_ 变量, 即 pagelet 的自增 id, 通过 intval() 转换防止 xss *} 32 | {* 注意: 一定要放在全局, 即 {html} 标签之外或 {html} 内 {head} 和 {body} 外, 否则 quickling 会跳过 *} 33 | {block name="global_vars"} 34 | {$_p_id_ = intval($smarty.get._p_id_)} 35 | {/block} 36 | 37 | 38 | {* 在 content 之前完成初始化配置 *} 39 | 47 | 48 | {* 将 pagelet 防止父容器 #feed_wrap 中 *} 49 |
50 | 51 | {* 将 pagelet 的 id 设置为 "前缀_{自增id}" *} 52 | {pagelet id="p_feed_{$_p_id_}"} 53 | 54 | 这是 pagelet 的内容, 可以使用 $_p_id_ 进行相应的分页取数据操作…… 55 | 56 | {* 下面是结合 lazy 实现的自动加载, 结束条件是 $_p_id_ >= 3 *} 57 | {* 当然也可以不使用自动加载, 根据需求手动调用 append() *} 58 | {if $_p_id_ < 3} 59 | 60 | {* 在 pagelet 底部创建空 div 用于 lazy 的触发 hook, 然后在 js 中绑定 lazy 和 append() 回调 *} 61 |
62 | 72 | {/if} 73 | {/pagelet} 74 |
75 | ``` 76 | 77 | -------------------------------------------------------------------------------- /src/js_helper/append.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 一个简单的 append 3 | * 可以结合 BigPipe.fetch 实现无限追加, 使用方法见 README.md 4 | * @file append.js 5 | * @author zhangwentao 6 | */ 7 | 8 | var config = {}; 9 | 10 | function initConf(key, conf) { 11 | config[key] = conf; 12 | } 13 | 14 | function getConf(key, defaultValue) { 15 | return config.hasOwnProperty(key) ? config[key] : defaultValue; 16 | } 17 | 18 | 19 | function append(prefix, url, cb) { 20 | var conf = getConf(prefix); 21 | 22 | if (conf) { 23 | var wrapId = conf.wrapId; 24 | var key = conf.key; 25 | 26 | 27 | if (!wrapId || !key) { 28 | throw new Error('wrapId or key missing'); 29 | } 30 | 31 | if (!conf.id) { 32 | conf.id = 0; 33 | } 34 | 35 | var id = ++conf.id; 36 | 37 | var pageletId = prefix + id; 38 | var paramStr = [key, id].join('='); 39 | 40 | var $wrap = document.getElementById(wrapId); 41 | 42 | if (!$wrap) { 43 | throw new Error('Wrap node does not exist ' + wrapId); 44 | } 45 | 46 | var $div = document.createElement('div'); 47 | $div.id = pageletId; 48 | 49 | $wrap.appendChild($div); 50 | 51 | url += url.indexOf('?') > -1 ? ('&' + paramStr) : ('?' + paramStr); 52 | 53 | /* globals BigPipe */ 54 | BigPipe.fetch([pageletId], url, cb); 55 | 56 | } else { 57 | throw new Error('There is no config for ' + prefix); 58 | } 59 | } 60 | 61 | append.init = initConf; 62 | 63 | module.exports = append; 64 | -------------------------------------------------------------------------------- /src/js_helper/bigRender.js: -------------------------------------------------------------------------------- 1 | /* 2 | * File: bigRender.js 3 | * Path: commmon/js/bigRender.js 4 | * Author: HuangYi 5 | * Modifier: HuangYi 6 | * Modified: 2013-7-16 7 | * Description: 延迟渲染js 8 | */ 9 | var lazy = require("./lazy.js"); 10 | 11 | function add(pagelet) { 12 | 13 | var lazyKey, ele, cssText; 14 | try { 15 | // if(pagelet.parent){ 16 | // pagelet.parent.on("load",function(){ 17 | // bindBigRender(pagelet); 18 | // }); 19 | // }else{ 20 | bindBigRender(pagelet); 21 | //} 22 | return true; 23 | } catch (e) { 24 | setTimeout(function() { 25 | throw e; 26 | }, 0); 27 | return false; 28 | } 29 | } 30 | 31 | function bindBigRender(pagelet) { 32 | var ele = document.getElementById(pagelet.id); 33 | addClass(ele, "g-bigrender"); 34 | 35 | var lazyKey = lazy.add(ele, function() { 36 | 37 | pagelet.on("load", function() { 38 | removeClass(ele, "g-bigrender"); 39 | }); 40 | pagelet.load(); 41 | 42 | }); 43 | 44 | pagelet.on("unload", function() { 45 | lazy.remove(lazyKey); 46 | }); 47 | } 48 | 49 | function addClass(element, className) { 50 | if (!element) 51 | return; 52 | var elementClassName = element.className; 53 | if (elementClassName.length == 0) { 54 | element.className = className; 55 | return; 56 | } 57 | if (elementClassName == className || elementClassName.match(new RegExp("(^|\\s)" + className + "(\\s|$)"))) 58 | return; 59 | element.className = elementClassName + " " + className; 60 | } 61 | 62 | function removeClass(element, className) { 63 | if (!element) 64 | return; 65 | var elementClassName = element.className; 66 | if (elementClassName.length == 0) 67 | return; 68 | if (elementClassName == className) { 69 | element.className = ""; 70 | return; 71 | } 72 | if (elementClassName.match(new RegExp("(^|\\s)" + className + "(\\s|$)"))) 73 | element.className = elementClassName.replace((new RegExp("(^|\\s)" + className + "(\\s|$)")), " "); 74 | } 75 | 76 | 77 | module.exports = { 78 | add: add 79 | }; 80 | -------------------------------------------------------------------------------- /src/js_helper/lazy.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 一个简单的lazy模块实现,基于getBoundingClientRect 3 | * 提供add和remove方法 4 | */ 5 | 6 | function getViewportSize() { 7 | if (window.innerHeight != null) { 8 | return { 9 | width: window.innerWidth, 10 | height: window.innerHeight 11 | }; 12 | } 13 | if (document.compatMode == 'CSS1Compat') { //标准模式 14 | return { 15 | width: document.documentElement.clientWidth, 16 | height: document.documentElement.clientHeight 17 | }; 18 | } 19 | return { 20 | width: document.body.clientWidth, 21 | height: document.body.clientHeight 22 | }; 23 | } 24 | 25 | function getElementOffset(element) { 26 | return { 27 | left: element.getBoundingClientRect().left, 28 | top: element.getBoundingClientRect().top 29 | }; 30 | } 31 | 32 | function addHandler(element, type, handler) { 33 | if (element.addEventListener) { 34 | element.addEventListener(type, handler, false); 35 | } else if (element.attachEvent) { 36 | element.attachEvent("on" + type, handler); 37 | } else { 38 | element["on" + type] = handler; 39 | } 40 | } 41 | 42 | var data = {}; 43 | var uniqueId = 0; 44 | var offsetBuffer = 50; 45 | 46 | function bind() { 47 | addHandler(window, 'resize', excute); 48 | addHandler(window, 'scroll', excute); 49 | } 50 | 51 | function excute() { 52 | var viewport = getViewportSize(); 53 | 54 | for (var i in data) { 55 | var item = data[i]; 56 | var ele = item[0]; 57 | var callback = item[1]; 58 | if (!ele || !ele.getBoundingClientRect) { 59 | return; 60 | } 61 | 62 | if (getElementOffset(ele).top < viewport.height + offsetBuffer) { 63 | callback && callback(); 64 | remove(i); 65 | } 66 | } 67 | } 68 | 69 | function add(element, callback) { 70 | data[uniqueId++] = [element, callback]; 71 | } 72 | 73 | function remove(id) { 74 | try { 75 | delete data[id]; 76 | return true; 77 | } catch (e) { 78 | //return false; 79 | throw new Error('Remove unknow lazy element by id:' + id); 80 | } 81 | } 82 | 83 | bind(); 84 | 85 | module.exports = { 86 | add: add, 87 | remove: remove 88 | }; 89 | -------------------------------------------------------------------------------- /src/js_helper/pageEmulator.js: -------------------------------------------------------------------------------- 1 | var cacheMaxTime = 0, // 缓存时间 2 | appOptions = {}, // app页面管理的options 3 | layer; // 事件代理层 4 | 5 | var EventUtil = { 6 | addHandler: function(element, type, handler, flag) { 7 | if (element.addEventListener) { 8 | element.addEventListener(type, handler, flag || false); 9 | } else if (element.attachEvent) { 10 | element.attachEvent("on" + type, handler); 11 | } else { 12 | element["on" + type] = handler; 13 | } 14 | }, 15 | getEvent: function(event) { 16 | return event ? event : window.event; 17 | }, 18 | getTarget: function(event) { 19 | //return event.tagert || event.srcElement; 20 | return event.target || event.srcElement; 21 | }, 22 | preventDefault: function(event) { 23 | if (event.preventDefault) { 24 | event.preventDefault(); 25 | } else { 26 | event.returnValue = false; 27 | } 28 | }, 29 | stopPropagation: function(event) { 30 | if (event.stopPropagation) { 31 | event.stopPropagation(); 32 | } else { 33 | event.cancelBubble = true; 34 | } 35 | }, 36 | removeHandler: function(element, type, handler) { 37 | if (element.removeEventListener) { 38 | element.removeEventListener(type, handler, false); 39 | } else if (element.detachEvent) { 40 | element.detachEvent("on" + type, handler); 41 | } else { 42 | element["on" + type] = null; 43 | } 44 | } 45 | }; 46 | 47 | 48 | /** 49 | * 启动页面管理 50 | * @param {Object} options 初始化参数 51 | * @param {String} options["selector"] 全局代理元素的选择器匹配,写法同 document.querySeletor 函数 52 | * @param {Number} options["cacheMaxTime"] 页面缓存时间 53 | * @param {Function|RegExp} options["validate"] url验证方法, 54 | * @return {void} 55 | */ 56 | 57 | function start(options) { 58 | 59 | /** 60 | * 默认参数 { 61 | * selector : // 代理元素的选择器规则 62 | * cacheMaxTime: //缓存存活时间,默认5min 63 | * } 64 | */ 65 | var defaultOptions = { 66 | cacheMaxTime: 5 * 60 * 1000, 67 | layer: document 68 | }; 69 | 70 | appOptions = merge(defaultOptions, options); 71 | cacheMaxTime = appOptions.cacheMaxTime; 72 | layer = getLayer(appOptions.layer); 73 | bindEvent(); 74 | } 75 | 76 | /** 77 | * 事件绑定 78 | * @return {void} 79 | */ 80 | 81 | function bindEvent() { 82 | EventUtil.addHandler(layer, 'click', proxy, true); 83 | } 84 | 85 | function getLayer(ele) { 86 | if (typeof ele === "string") { 87 | return document.querySelector(ele); 88 | } else if (ele && ele.nodeType) { 89 | return ele; 90 | } else { 91 | return document.body 92 | } 93 | } 94 | 95 | /** 96 | * 简单merge两个对象 97 | * @param {object} _old 98 | * @param {object} _new 99 | * @returns {*} 100 | */ 101 | 102 | function merge(_old, _new) { 103 | for (var i in _new) { 104 | if (_new.hasOwnProperty(i) && _new[i] !== null) { 105 | _old[i] = _new[i]; 106 | } 107 | } 108 | return _old; 109 | } 110 | 111 | /** 112 | * 事件代理 113 | * @param {MouseEvent} 点击事件对象 114 | */ 115 | 116 | function proxy(e) { 117 | var element = EventUtil.getTarget(e), 118 | parent = element, 119 | selector = appOptions.selector; 120 | 121 | var url, urlAttr, pagelets; 122 | 123 | while (parent !== layer) { 124 | 125 | urlAttr = parent.tagName.toLowerCase() === "a" ? "href" : "data-href"; 126 | url = parent.getAttribute(urlAttr); 127 | pagelets = parent.getAttribute("data-pagelets"); 128 | 129 | if (url && pagelets) { 130 | pagelets = pagelets.split(','); 131 | 132 | if (pagelets.length) { 133 | 134 | EventUtil.stopPropagation(e); 135 | EventUtil.preventDefault(e); 136 | 137 | BigPipe.fetch(pagelets, url); 138 | } 139 | break; 140 | } else { 141 | parent = parent.parentNode; 142 | } 143 | } 144 | } 145 | 146 | module.exports = { 147 | start: start 148 | }; 149 | -------------------------------------------------------------------------------- /src/runtime/BigPipe.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Bigpipe提供获取pagelet 处理pagelet和资源的接口 3 | * @author zhangwentao(zhangwentao@baidu.com) 4 | */ 5 | 6 | __d("BigPipe", ["Resource", "Requestor", "Controller"], function(global, require, module, exports) { 7 | 8 | var Resource = require("Resource"); 9 | var Requestor = require("Requestor"); 10 | var Controller = require("Controller"); 11 | 12 | /** 13 | * @global 14 | * @namespace 15 | */ 16 | var BigPipe = { 17 | init: function() {}, 18 | /** 19 | * 页面调用函数,用于传输数据 20 | * 21 | * @param {Object} conf pagelet数据 22 | * @example 23 | * BigPipe.onPageletArrive({ 24 | * "id" : "", //pagelet 的 ID 25 | * "container": "__cnt_0_1", //pagelet HTML的容器ID 26 | * "children" : [ //子pagelet的 ID 列表 27 | * "header", 28 | * "__elm_0_1", 29 | * "__elm_0_2" 30 | * ]. 31 | * "deps":{ //事件的依赖资源 32 | * "beforeload": [ //"beforeload" 事件的依赖资源 33 | * "XbsgfuDYNN" 34 | * ], 35 | * "load": [ //"load" 事件的依赖资源 36 | * "qwlFYEkDet" 37 | * ] 38 | * }, 39 | * "hooks":{ //事件回调 40 | * "beforeload": [ //"beforeload" 的事件回调 41 | * "__cb_0_1", 42 | * "__cb_0_2" 43 | * ], 44 | * "load":[ //"load" 的事件回调 45 | * "__cb_0_3", 46 | * "__cb_0_4" 47 | * ] 48 | * } 49 | * }); 50 | */ 51 | onPageletArrive: function(conf) { 52 | Controller.pageletArrive(conf); 53 | }, 54 | //TODO 55 | /** 56 | * 页面调用函数,用于设置资源列表 57 | * 58 | * @param {Object} map 资源列表 59 | * @example 60 | * BigPipe.setResourceMap({ 61 | * "XbsgfuDYNN" : { //资源ID 62 | * "type" : "css", //资源类型 63 | * "src" : "http://s0.hao123img.com/v5/xx/XX/XX.Bigpipe", //资源地址 64 | * "deps" : [ //依赖资源 ID 列表 65 | * "rIffdDBQKC", 66 | * "qwlFYEkDet" 67 | * ], 68 | * "mods" : [ //资源所包含的模块列表 69 | * "common:Bigpipe/jquery.Bigpipe", 70 | * "common:Bigpipe/event.Bigpipe" 71 | * ] 72 | * }, 73 | * "rIffdDBQKC" : { 74 | * // 同上 75 | * } 76 | * }); 77 | */ 78 | setResourceMap : Controller.setResourceMap, //function(map) { 79 | //TODO 80 | //Resource.setResourceMap(map); 81 | //}, 82 | loadedResource : Resource.loadedResource, //function(resources){ 83 | //Resource.loadedResource(resources); 84 | //}, 85 | getResource : Resource.getResource, //function(id) { 86 | //TODO 87 | //return Resource.getResource(id); 88 | //}, 89 | getResourceByName : Resource.getResourceByName, //function(name){ 90 | //return Resource.getResourceByName(name); 91 | //}, 92 | hooks : Controller.hooks, 93 | fetch: function (pagelets, url, cb) { 94 | Requestor.fetch(pagelets, url, cb); 95 | } 96 | }; 97 | 98 | module.exports = BigPipe; 99 | 100 | }); 101 | -------------------------------------------------------------------------------- /src/runtime/CSSLoader.js: -------------------------------------------------------------------------------- 1 | __d("CSSLoader", ["EventEmitter"], function(global, require, module, exports) { 2 | var EventEmitter = require("EventEmitter"); 3 | /** 4 | * 声明全局变量 5 | * @param {Number} interval 检测拉取css的时间间隔 6 | * @param {Number} WAITTIME 等待超时时间 7 | * @param {Boolen} dusSupport 是否支持data url scheme 8 | * @param {Boolen} checked 是否已检测过data url scheme 9 | * @param {Object} loadedMap 已加载css的Map 10 | * @param {Array} styleSheetSet 加载styleSheet的集合 11 | * @param {Number} timeout 超时时间 12 | * @param {Object} pullMap 拉取css的Map 13 | */ 14 | 15 | var interval = 20; 16 | var WAITTIME = 15000; 17 | var dusSupport; 18 | var checked; 19 | var loadedMap = {}; 20 | var styleSheetSet = []; 21 | var timeout; 22 | var pullMap = {}; 23 | 24 | function checkDusSupport() { 25 | var link; 26 | if (checked) 27 | return; 28 | checked = true; 29 | link = document.createElement('link'); 30 | link.onload = function () { 31 | dusSupport = true; 32 | link.parentNode.removeChild(link); 33 | }; 34 | link.rel = 'stylesheet'; 35 | link.href = 'data:text/css;base64,'; 36 | appendToHead(link); 37 | } 38 | 39 | function checkCssLoaded() { 40 | var id, 41 | callbacks = [], 42 | contexts = [], 43 | signals = [], 44 | signal, 45 | style; 46 | if (+new Date >= timeout) { //超时 47 | for (id in pullMap) { 48 | signals.push(pullMap[id].signal); 49 | callbacks.push(pullMap[id].error); 50 | contexts.push(pullMap[id].context); 51 | } 52 | pullMap = {}; 53 | } else { 54 | for (id in pullMap) { 55 | signal = pullMap[id].signal; 56 | style = window.getComputedStyle ? getComputedStyle(signal, null) : signal.currentStyle; 57 | if (style && parseInt(style.height, 10) > 1) { 58 | callbacks.push(pullMap[id].load); 59 | contexts.push(pullMap[id].context); 60 | signals.push(signal); 61 | delete pullMap[id]; 62 | } 63 | } 64 | } 65 | //清理 66 | for (var i = 0; i < signals.length; i++) 67 | signals[i].parentNode.removeChild(signals[i]); 68 | if (!isEmpty(callbacks)) { 69 | for (i = 0; i < callbacks.length; i++) { 70 | callbacks[i].call(contexts[i]); 71 | } 72 | timeout = +new Date + WAITTIME; 73 | } 74 | return isEmpty(pullMap); 75 | } 76 | 77 | function pullCss(id, onload, onTimeout, context) { 78 | var meta, empty, timer; 79 | meta = document.createElement('meta'); 80 | meta.className = 'css_' + id; 81 | appendToHead(meta); 82 | 83 | empty = !isEmpty(pullMap); 84 | timeout = +new Date + WAITTIME; 85 | pullMap[id] = { 86 | signal: meta, 87 | load: onload, 88 | error: onTimeout, 89 | context: context 90 | }; 91 | if (!empty) { 92 | timer = setInterval(function () { 93 | if (checkCssLoaded()) { 94 | clearInterval(timer); 95 | } 96 | }, interval); 97 | } 98 | } 99 | 100 | function onload() { 101 | this.done("load"); 102 | } 103 | 104 | function onTimeout() { 105 | this.done("timeout"); 106 | } 107 | 108 | var EVENT_TYPES = [ 109 | "load", // CSS加载完成时派发的事件 110 | "timeout" // CSS 加载超时事件 111 | //TODO 错误处理? 112 | ]; 113 | 114 | var CSSLoader = derive(EventEmitter, 115 | /** 116 | * 构造一个CSS加载对象, 117 | * 118 | * @constructor 119 | * @extends EventEmitter 120 | * @alias CSSLoader 121 | * 122 | * @param {String} id CSS资源ID 123 | * @param {Object} config CSS资源信息 124 | */ 125 | function (__super, id, config) { 126 | __super(EVENT_TYPES); 127 | /** 128 | * CSS资源ID 129 | * @member {String} 130 | */ 131 | this.id = id; 132 | /** 133 | * CSS资源URL 134 | * @member {String} 135 | */ 136 | this.uri = config.src; 137 | 138 | }, 139 | /** 140 | * @alias CSSLoader.prototype 141 | */ 142 | { 143 | /** 144 | * 开始加载资源 145 | * @fires CSSLoader#load 146 | */ 147 | load: function () { 148 | var me = this, 149 | id = this.id, 150 | uri = this.uri, 151 | index, link; 152 | if (loadedMap[id]) 153 | throw new Error('CSS component ' + id + ' has already been requested.'); 154 | if (document.createStyleSheet) { 155 | for (var i = 0; i < styleSheetSet.length; i++) 156 | if (styleSheetSet[i].imports.length < 31) { 157 | index = i; 158 | break; 159 | } 160 | if (index === undefined) { 161 | styleSheetSet.push(document.createStyleSheet()); 162 | index = styleSheetSet.length - 1; 163 | } 164 | styleSheetSet[index].addImport(uri); 165 | loadedMap[id] = { 166 | styleSheet: styleSheetSet[index], 167 | uri: uri 168 | }; 169 | pullCss(id, onload, onTimeout, this); 170 | return; 171 | } 172 | link = document.createElement('link'); 173 | link.rel = 'stylesheet'; 174 | link.type = 'text/css'; 175 | link.href = uri; 176 | loadedMap[id] = { 177 | link: link 178 | }; 179 | if (dusSupport) { 180 | link.onload = function () { 181 | link.onload = link.onerror = null; 182 | //link.parentNode.removeChild(link); 183 | onload.call(me); 184 | }; 185 | link.onerror = function () { 186 | link.onload = link.onerror = null; 187 | //link.parentNode.removeChild(link); 188 | onTimeout.call(me); 189 | }; 190 | } else { 191 | pullCss(id, onload, onTimeout, this); 192 | if (dusSupport === undefined) 193 | checkDusSupport(); 194 | } 195 | appendToHead(link); 196 | }, 197 | unload: function () { 198 | // TODO 资源卸载?一期先不用 199 | } 200 | }); 201 | 202 | module.exports = CSSLoader; 203 | }); 204 | -------------------------------------------------------------------------------- /src/runtime/Controller.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Controller 处理pageletArrive 3 | * @author zhangwentao(zhangwentao@baidu.com) 4 | */ 5 | 6 | __d("Controller", ["Pagelet", "Resource"], function (global, require, module, exports) { 7 | 8 | var Pagelet = require("Pagelet"); 9 | var Resource = require("Resource"); 10 | 11 | var Controller = { 12 | pageletsArrive : function(pagelets){ 13 | each(pagelets, function(pagelet){ 14 | this.pageletArrive(pagelet); 15 | }, this); 16 | }, 17 | pageletArrive : function(conf){ 18 | var pagelet; 19 | 20 | if(Pagelet.hasPlagelet(conf.id)){ 21 | Pagelet.getPlagelet(conf.id).unload(); 22 | } 23 | 24 | pagelet = Pagelet.getPlagelet(conf.id); 25 | 26 | if(conf.quickling){ 27 | Resource.setResourceMap(conf.resourceMap); 28 | }else if(conf.html){ 29 | conf.html = document.getElementById(conf.html.container).firstChild.data; 30 | } 31 | 32 | var hooks = conf.hooks; 33 | if(hooks){ 34 | for(var i in hooks){ 35 | var type = hooks[i]; 36 | for(var j = 0; j < type.length; j++){ 37 | conf.quickling ? 38 | pagelet.on(i, new Function(type[j])) : 39 | pagelet.on(i, this.hooks[type[j]]); 40 | } 41 | } 42 | } 43 | 44 | pagelet.arrive(conf); 45 | }, 46 | hooks:{}, 47 | setResourceMap : Resource.setResourceMap 48 | }; 49 | 50 | module.exports = Controller; 51 | }); 52 | /* @cmd false */ 53 | -------------------------------------------------------------------------------- /src/runtime/JSLoader.js: -------------------------------------------------------------------------------- 1 | __d("JSLoader", ["EventEmitter"], function(global, require, module, exports) { 2 | var EventEmitter = require("EventEmitter"); 3 | var STAT_INITIALIZED = 1, 4 | STAT_LOADING = 2, 5 | STAT_LOADED = 3, 6 | STAT_ERROR = 4; 7 | 8 | var EVENT_TYPES = [ 9 | "load" // JS加载完成时派发的事件 10 | //TODO 错误处理? 11 | ]; 12 | 13 | var JSLoader = derive(EventEmitter, 14 | /** 15 | * 构造一个Js加载对象, 16 | * 17 | * @constructor 18 | * @extends EventEmitter 19 | * @alias JSLoader 20 | * 21 | * @param {String} id JS资源ID 22 | * @param {Object} config JS资源信息 23 | */ 24 | function (__super, id, config) { 25 | __super(EVENT_TYPES); 26 | /** 27 | * JS资源ID 28 | * @member {String} 29 | */ 30 | this.id = id; 31 | /** 32 | * JS资源URL 33 | * @member {String} 34 | */ 35 | this.url = config.src; 36 | /** 37 | * loader加载状态 38 | * @member {Number} 39 | */ 40 | this.state = STAT_INITIALIZED; 41 | }, 42 | /** 43 | * @alias JSLoader.prototype 44 | */ 45 | { 46 | /** 47 | * 开始加载资源 48 | * @fires JSLoader#load 49 | */ 50 | load: function () { 51 | //TODO 实现资源加载 52 | var me = this, 53 | script, 54 | onload; 55 | if (this.state >= STAT_LOADING) 56 | return; 57 | this.state = STAT_LOADING; 58 | 59 | script = document.createElement('script'); 60 | script.type = 'text/javascript'; 61 | script.src = me.url; 62 | script.async = true; 63 | 64 | script.onload = function () { 65 | callback(true); 66 | }; 67 | 68 | script.onerror = function () { 69 | callback(false); 70 | }; 71 | 72 | script.onreadystatechange = function () { 73 | if (this.readyState in { 74 | loaded: 1, 75 | complete: 1 76 | }) { 77 | callback(true); 78 | } 79 | }; 80 | 81 | appendToHead(script); 82 | /** 83 | * 资源加载完成回调 84 | * 85 | * @event JSLoader#load 86 | */ 87 | function callback(success) { 88 | if (me.state >= STAT_LOADED) { 89 | return; 90 | } 91 | me.state = success ? STAT_LOADED : STAT_ERROR; 92 | 93 | me.emit("load"); 94 | 95 | nextTick(function () { 96 | 97 | script.onload = script.onerror = script.onreadystatechange = null; 98 | script.parentNode && script.parentNode.removeChild(script); 99 | script = null; 100 | }); 101 | } 102 | }, 103 | 104 | unload: function () { 105 | // TODO 资源卸载?一期先不用 106 | } 107 | }); 108 | 109 | module.exports = JSLoader; 110 | }); 111 | -------------------------------------------------------------------------------- /src/runtime/Requestor.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Requestor 发起quickling请求,管理sessionid和pagelet缓存 3 | * @author zhangwentao(zhangwentao@baidu.com) 4 | */ 5 | 6 | __d("Requestor", ["Controller"], function (global, require, module, exports) { 7 | 8 | var Controller = require('Controller'); 9 | 10 | var requestor = { 11 | sessions: {}, 12 | /* 13 | cacheData : { 14 | url1 : { 15 | id1 : {}, // {child : id2} 16 | id2 : {} 17 | }, 18 | url2 : { 19 | id1 : {}, // {child : id2} 20 | id2 : {} 21 | } 22 | } 23 | */ 24 | cacheData: {}, 25 | fetch: function (pagelets, url, cb) { 26 | var cache, 27 | cached = [], 28 | nonCached = [], 29 | pagelet, 30 | i, 31 | length = pagelets.length, 32 | j, 33 | child; 34 | 35 | // if (cache = this.cacheData[url]) { 36 | // for (i = 0; i < length; i++) { 37 | // pagelet = pagelets[i]; 38 | // if (!cache[pagelet]) { 39 | // nonCached.push(pagelet); 40 | // } else { 41 | // //cached.push(cache[pagelet]); 42 | // findChild(cache[pagelet]); 43 | // } 44 | // } 45 | // } else { 46 | nonCached = pagelets; 47 | // } 48 | function pageletsArrive(data) { 49 | for (var i = 0; i < data.length; i++) { 50 | // BigPipe.onPageletArrive(data[i]); 51 | var conf = data[i]; 52 | if (conf.html.html) { 53 | conf.html = conf.html.html; 54 | } 55 | 56 | // conf.html = conf.html.html; 57 | // if(!this.cacheData[url]) this.cacheData[url] = {}; 58 | // if (!cache[conf.id]) { 59 | // cache[conf.id] = conf; 60 | // } 61 | // if(conf.session >= me.sessions[conf.id]){ 62 | Controller.pageletArrive(data[i]); 63 | // callback && callback(data[i]); 64 | // } 65 | } 66 | } 67 | 68 | this._fetch(nonCached, url, function (err, data, xhrStatus) { 69 | if (err) { 70 | cb && cb(err, data, xhrStatus); 71 | } else { 72 | // Controller.pageletsArrive(data); 73 | pageletsArrive(data); 74 | } 75 | }, cached); 76 | 77 | // function findChild(pagelet) { 78 | // var count; 79 | // var childObj; 80 | // count = pagelet.children && pagelet.children.length || 0; 81 | 82 | // cached.push(pagelet); 83 | 84 | // if (count) { 85 | // for (j = 0; j < count; j++) { 86 | // child = pagelet.children[j]; 87 | // childObj = cache[child]; 88 | 89 | // if (!childObj) { 90 | // nonCached.push(child); 91 | // } else { 92 | // findChild(childObj); 93 | // } 94 | // } 95 | // } 96 | // } 97 | }, 98 | _fetch: function (pagelets, url, callback, cached) { 99 | //var url; 100 | // var cache; 101 | 102 | // if (!this.cacheData[url]) { 103 | // this.cacheData[url] = {}; 104 | // } 105 | // cache = this.cacheData[url]; 106 | 107 | // if (!pagelets.length && cached.length) { 108 | // parseData(cached); 109 | // return; 110 | // } 111 | 112 | for (var i = 0; i < pagelets.length; i++) { 113 | if (this.sessions[pagelets[i]] === undefined) { 114 | this.sessions[pagelets[i]] = 0; 115 | } else { 116 | this.sessions[pagelets[i]]++; 117 | } 118 | pagelets[i] += '.' + this.sessions[pagelets[i]]; 119 | } 120 | url = url || ''; 121 | 122 | if (url.indexOf('?') > -1) { 123 | url += '&__quickling__=' + pagelets.join(','); 124 | } 125 | else { 126 | url += '?__quickling__=' + pagelets.join(','); 127 | } 128 | 129 | /* globals ajax */ 130 | ajax(url, function (err, text, xhrStatus) { 131 | if (err) { 132 | callback(err, text, xhrStatus); 133 | } else { 134 | var data; 135 | try { 136 | data = JSON.parse(text); 137 | } catch (e) { 138 | return callback(new Error('JSONParseError'), text, xhrStatus); 139 | } 140 | 141 | if (cached && cached.length) { 142 | data = data.concat(cached); 143 | } 144 | callback(false, data); 145 | } 146 | }); 147 | } 148 | }; 149 | 150 | module.exports = requestor; 151 | }); 152 | /* __wrapped__ */ 153 | /* @cmd false */ 154 | -------------------------------------------------------------------------------- /src/runtime/main.js: -------------------------------------------------------------------------------- 1 | __inline('runtimeAMD.js'); 2 | 3 | (function(global, window, document, undefined) { 4 | __inline('util/util.js'); 5 | __inline('BigPipe.js'); 6 | __inline('Controller.js'); 7 | __inline('CSSLoader.js'); 8 | __inline('EventEmitter.js'); 9 | __inline('JSLoader.js'); 10 | __inline('Pagelet.js'); 11 | __inline('Requestor.js'); 12 | __inline('Resource.js'); 13 | 14 | var BigPipe = require("BigPipe"); 15 | if (hasOwnProperty(global, "BigPipe")) { 16 | BigPipe.origBigPipe = global.BigPipe; 17 | } 18 | global.BigPipe = BigPipe; 19 | 20 | })(this, window, document); -------------------------------------------------------------------------------- /src/runtime/runtimeAMD.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 运行时AMD,提供define require require.defer require.async接口 3 | * @author zhangwentao(zhangwentao@baidu.com) 4 | */ 5 | 6 | (function(global){ 7 | 8 | var isReady = false, 9 | DOMContentLoaded, 10 | _lazy_modules = []; 11 | 12 | var _module_map = {}; 13 | 14 | //function define(id, dependencies, factory) { 15 | //var _module_map = {}; 16 | 17 | /** 18 | * 运行时 AMD define 实现 19 | * 20 | * @param {String} id 模块名 21 | * @param {Array} dependencies 依赖模块 22 | * @param {factory} factory 模块函数 23 | * @access public 24 | * @return void 25 | * @see require 26 | */ 27 | var define = function(id, dependencies, factory) { 28 | if (!factory) { 29 | factory = dependencies; 30 | dependencies = []; 31 | } 32 | _module_map[id] = { 33 | factory: factory, 34 | dependencies: dependencies 35 | }; 36 | }; 37 | 38 | 39 | define.amd = { 40 | jQuery : true 41 | }; 42 | /** 43 | * 运行时 AMD require 实现 44 | * 45 | * @param {String} id 需要 require 的模块 id 46 | * @access public 47 | * @return {Object} 模块的exports 48 | * @see define 49 | */ 50 | var require = function(id) { 51 | var module, exports, args, ret; 52 | 53 | if (!_module_map.hasOwnProperty(id)) { 54 | throw new Error('Requiring unknown module "' + id + '"'); 55 | } 56 | 57 | module = _module_map[id]; 58 | if (module.hasOwnProperty("exports")) { 59 | return module.exports; 60 | } 61 | 62 | module.exports = exports = {}; 63 | 64 | args = buildArguments(module.dependencies, require, module, exports); 65 | ret = module.factory.apply(undefined, args); 66 | 67 | if(ret !== undefined){ 68 | module.exports = ret; 69 | } 70 | return module.exports; 71 | }; 72 | 73 | require.async = function(names, callback){ 74 | var resource, modules; 75 | 76 | resource = getFakeResource(names); 77 | 78 | resource.on('resolve', function(){ 79 | modules = getModules(names); 80 | 81 | callback && callback.apply(null, modules); 82 | }); 83 | 84 | resource.load(); 85 | }; 86 | 87 | function getModules(names){ 88 | var i, 89 | count = names.length, 90 | name, 91 | modules = []; 92 | 93 | for(i = 0; i < count; i++){ 94 | name = names[i]; 95 | modules.push(require(name)); 96 | } 97 | 98 | return modules; 99 | } 100 | 101 | var __fakeId = 0; 102 | function getFakeResource(names){ 103 | var count, 104 | i, 105 | name, 106 | deps = [], 107 | resource, 108 | map = {}, 109 | id; 110 | 111 | count = names.length; 112 | 113 | for(i = 0; i < count; i++){ 114 | name = names[i]; 115 | deps.push(BigPipe.getResourceByName(name).id); 116 | } 117 | 118 | id = 'fake_res_' + __fakeId ++; 119 | map[id] = { 120 | deps : deps, 121 | type : 'js', 122 | mods : [] 123 | }; 124 | 125 | BigPipe.setResourceMap(map); 126 | resource = BigPipe.getResource(id); 127 | 128 | return resource; 129 | } 130 | 131 | require.defer = function(names, callback){ 132 | if(isReady){ 133 | require.async(names, callback); 134 | }else{ 135 | _lazy_modules.push([names, callback]); 136 | } 137 | }; 138 | /** 139 | * 根据 id 数组生成模块数组 140 | * 实现AMD规范 141 | * 142 | * @param {Array} deps 依赖的模块名列表 143 | * @param {Function} require require函数 144 | * @param {Object} module 模块 145 | * @param {Object} exports 模块的 exports 对象 146 | * @access public 147 | * @return {Array} 执行 require 后的模块数组 148 | */ 149 | function buildArguments(deps, require, module, exports) { 150 | var index, count, did, args; 151 | 152 | args = []; 153 | count = deps.length; 154 | for (index = 0; index < count; index++) { 155 | did = deps[index]; 156 | if (did === "global") { 157 | args.push(window); 158 | }else if (did === "require") { 159 | args.push(require); 160 | } else if (did === "module") { 161 | args.push(module); 162 | } else if (did === "exports") { 163 | args.push(exports); 164 | } else { 165 | args.push(require(did)); 166 | } 167 | } 168 | return args; 169 | } 170 | 171 | function onready(){ 172 | var i, 173 | len = _lazy_modules.length, 174 | mod, 175 | names, 176 | callback; 177 | 178 | if(len > 0){ 179 | for(i = 0; i < len; i++){ 180 | mod = _lazy_modules[i]; 181 | names = mod[0]; 182 | callback = mod[1]; 183 | require.async(names, callback); 184 | } 185 | _lazy_modules = []; 186 | } 187 | } 188 | 189 | function ready() { 190 | if (!isReady) { 191 | isReady = true; 192 | onready(); 193 | //requireLazy.onready && requireLazy.onready(); 194 | //loadAllLazyModules(); 195 | } 196 | } 197 | 198 | // Cleanup functions for the document ready method 199 | if (document.addEventListener) { 200 | DOMContentLoaded = function () { 201 | document.removeEventListener("DOMContentLoaded", DOMContentLoaded, false); 202 | ready(); 203 | }; 204 | 205 | } else if (document.attachEvent) { 206 | DOMContentLoaded = function () { 207 | // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). 208 | if (document.readyState === "complete") { 209 | document.detachEvent("onreadystatechange", DOMContentLoaded); 210 | ready(); 211 | } 212 | }; 213 | } 214 | 215 | function bindReady() { 216 | // Mozilla, Opera and webkit nightlies currently support this event 217 | if (document.addEventListener) { 218 | // Use the handy event callback 219 | document.addEventListener("DOMContentLoaded", DOMContentLoaded, false); 220 | 221 | // A fallback to window.onload, that will always work 222 | window.addEventListener("load", ready, false); 223 | 224 | // If IE event model is used 225 | } else if (document.attachEvent) { 226 | // ensure firing before onload, 227 | // maybe late but safe also for iframes 228 | document.attachEvent("onreadystatechange", DOMContentLoaded); 229 | 230 | // A fallback to window.onload, that will always work 231 | window.attachEvent("onload", ready); 232 | } 233 | } 234 | 235 | global.require = require; 236 | global.define = define; 237 | 238 | bindReady(); 239 | //global.require = require; 240 | })(window); 241 | 242 | -------------------------------------------------------------------------------- /src/runtime/util/ajax.js: -------------------------------------------------------------------------------- 1 | function ajax(url, cb, data) { 2 | var xhr = new (window.XMLHttpRequest || ActiveXObject)("Microsoft.XMLHTTP"); 3 | 4 | xhr.onreadystatechange = function () { 5 | if (xhr.readyState == 4) { 6 | if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304 || xhr.status === 1223 || xhr.status === 0) { 7 | cb(false, xhr.responseText, xhr.status); 8 | } else { 9 | cb(new Error('AjaxError'), xhr.statusText, xhr.status); 10 | } 11 | } 12 | }; 13 | xhr.open(data ? 'POST' : 'GET', url + '&t=' + ~~(Math.random() * 1e6), true); 14 | 15 | if (data) { 16 | xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); 17 | } 18 | xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); 19 | xhr.send(data); 20 | } 21 | -------------------------------------------------------------------------------- /src/runtime/util/amd.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 一个简单的 AMD define 实现 3 | * 4 | * @param {String} name 模块名 5 | * @param {Array} dependencies 依赖模块 6 | * @param {factory} factory 模块函数 7 | * @access public 8 | * @return void 9 | * @see require 10 | */ 11 | 12 | var _module_map = {}; 13 | 14 | function define(id, dependencies, factory) { 15 | _module_map[id] = { 16 | factory: factory, 17 | deps: dependencies 18 | }; 19 | } 20 | 21 | function require(id) { 22 | var module = _module_map[id]; 23 | if (!hasOwnProperty(_module_map, id)) 24 | throw new Error('Requiring unknown module "' + id + '"'); 25 | if (hasOwnProperty(module, "exports")) 26 | return module.exports; 27 | 28 | var exports; 29 | module.exports = exports = {}; 30 | 31 | var args = buildArguments(module.deps, require, module, exports); 32 | 33 | module.factory.apply(undefined, args); 34 | 35 | return module.exports; 36 | } 37 | 38 | /** 39 | * 根据 id 数组生成模块数组 40 | * 实现AMD规范 41 | * 42 | * @param {Array} deps 依赖的模块名列表 43 | * @param {Function} require require函数 44 | * @param {Object} module 模块 45 | * @param {Object} exports 模块的 exports 对象 46 | * @access public 47 | * @return {Array} 执行 require 后的模块数组 48 | */ 49 | function buildArguments(deps, require, module, exports) { 50 | var index, count, did, args; 51 | 52 | args = []; 53 | count = deps.length; 54 | for (index = 0; index < count; index++) { 55 | did = deps[index]; 56 | if (did === "require") { 57 | args.push(require); 58 | } else if (did === "module") { 59 | args.push(module); 60 | } else if (did === "exports") { 61 | args.push(exports); 62 | } else { 63 | args.push(require(did)); 64 | } 65 | } 66 | return args; 67 | } 68 | 69 | function __b(id, exports) { 70 | _module_map[id] = { 71 | exports: exports 72 | }; 73 | } 74 | 75 | function __d(id, dependencies, factory) { 76 | return define(id, ['global', 'require', 'module', 'exports'].concat(dependencies), factory); 77 | } 78 | 79 | __b("global", global); 80 | -------------------------------------------------------------------------------- /src/runtime/util/appendToHead.js: -------------------------------------------------------------------------------- 1 | function appendToHead(element) { 2 | var hardpoint, 3 | heads = document.getElementsByTagName('head'); 4 | hardpoint = heads.length && heads[0] || document.body; 5 | 6 | appendToHead = function (element) { 7 | hardpoint.appendChild(element); 8 | }; 9 | return appendToHead(element); 10 | } 11 | -------------------------------------------------------------------------------- /src/runtime/util/bind.js: -------------------------------------------------------------------------------- 1 | /** 2 | * bind 方法会创建一个新函数,称为绑定函数. 3 | * 当调用这个绑定函数时,绑定函数会以创建它时传入bind方法的第二个参数作为this, 4 | * 传入bind方法的第三个以及以后的参数加上绑定函数运行时本身的参数按照顺序作为原函数的参数来调用原函数. 5 | * 6 | * @param {Function} fn 摇绑定的函数 7 | * @param {Object} thisObj 调用函数时的 this 对象 8 | * @param {...*} [args] 执行函数时的参数 9 | * @access public 10 | * @return {Function} 绑定后的函数 11 | * 12 | * @example 13 | * 14 | * var obj = { 15 | * name: "it's me!" 16 | * }; 17 | * 18 | * var fn = bind(function(){ 19 | * console.log(this.name); 20 | * console.log(arguments); 21 | * }, obj, 1, 2, 3); 22 | * 23 | * fn(4, 5, 6); 24 | * // it's me 25 | * // [ 1, 2, 3, 4, 5, 6] 26 | */ 27 | function bind(fn, thisObj /*, args... */) { 28 | 29 | function _bind(fn, thisObj /*, args... */) { 30 | var savedArgs, //保存绑定的参数 31 | savedArgLen, //绑定参数的个数 32 | ret; //返回函数 33 | 34 | // 判断是否有绑定的参数 35 | savedArgLen = arguments.length - 2; 36 | if (savedArgLen > 0) { 37 | //有绑定参数,需要拼接调用参数 38 | savedArgs = slice(arguments, 2); 39 | ret = function () { 40 | var args = toArray(arguments), 41 | index = savedArgLen; 42 | //循环将保存的参数移入调用参数 43 | //这里不使用 Array.prototype.concat 也是为了避免内存浪费 44 | while (index--) { 45 | args.unshift(savedArgs[index]); 46 | } 47 | return fn.apply(thisObj, args); 48 | }; 49 | } else { 50 | // 没有绑定参数,直接调用,减少内存消耗 51 | ret = function () { 52 | return fn.apply(thisObj, arguments); 53 | }; 54 | } 55 | 56 | return ret; 57 | } 58 | 59 | //保存原生的 bind 函数 60 | var native_bind = Function.prototype.bind; 61 | //修改 bind 函数指针 62 | if (native_bind) { 63 | //如果原生支持 Function.prototype.bind 则使用原生函数来实现 64 | bind = function (fn, thisObj /*, args... */) { 65 | return native_bind.apply(fn, slice(arguments, 1)); 66 | }; 67 | } else { 68 | bind = _bind; 69 | } 70 | 71 | return bind.apply(this, arguments); 72 | } 73 | -------------------------------------------------------------------------------- /src/runtime/util/copyProperties.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 复制属性到对象 3 | * 4 | * @param {Object} to 目标对象 5 | * @param {...Object} from 多个参数 6 | * @access public 7 | * @return {Object} 目标对象 8 | * 9 | * @example 10 | * var objA = { 11 | * a : 1 12 | * }, 13 | * objB = { 14 | * b : 2 15 | * }; 16 | * 17 | * copyProperties(objA, objB, { 18 | * c : 3 19 | * }); 20 | * console.log(objA); 21 | * // { 22 | * // a : 1, 23 | * // b : 2, 24 | * // c : 3 25 | * // } 26 | */ 27 | function copyProperties(to /*, ...*/) { 28 | var index, count, item, key; 29 | 30 | to = to || {}; 31 | count = arguments.length; 32 | 33 | //遍历参数列表 34 | for (index = 1; index < count; index++) { 35 | item = arguments[index]; 36 | for (key in item) { 37 | //只复制自有属性 38 | if (hasOwnProperty(item, key)) { 39 | to[key] = item[key]; 40 | } 41 | } 42 | } 43 | 44 | return to; 45 | } 46 | -------------------------------------------------------------------------------- /src/runtime/util/counter.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 计数器,返回一个用于计数调用的函数,每调用该函数一次,内部的计数器加一,直到达到 total 所指定次数,则调用 callback 函数。如果传递了 timeout 参数,则在 timeout 毫秒后如果还未达到指定次数,则调用 callback 3 | * 4 | * @param {Number} total 计数次数 5 | * @param {Function} callback 计数完成或超时后的回调函数 6 | * @param {Number} timeout 超时时间 7 | * @access public 8 | * @return {Function} 计数函数,每调用一次,内部的计数器就会加一 9 | * 10 | * 11 | * @example 12 | * var add = counter(4, function(){ 13 | * console.log("add 4 times!"); 14 | * }); 15 | * 16 | * add(); 17 | * add(); 18 | * add(); 19 | * add(); // add 4 times! 20 | */ 21 | 22 | function counter(total, callback, timeout) { 23 | var running = true, //是否正在计数 24 | count = 0, //当前计数值 25 | timeoutId = null; //超时标记 26 | 27 | // 计数完成或者超时后的回调函数 28 | function done() { 29 | if (!running) { 30 | return; 31 | } 32 | 33 | running = false; 34 | if (timeoutId !== null) { 35 | clearTimeout(timeoutId); 36 | } 37 | 38 | //TODO 参数?错误处理? 39 | callback(); 40 | } 41 | 42 | //将 total 值转换为整数 43 | total = total | 0; 44 | 45 | //如果目标计数值小于0,则直接调用done 46 | if (total <= 0) { 47 | done(); 48 | } else if (timeout !== undefined) { 49 | timeoutId = setTimeout(done, timeout); 50 | } 51 | 52 | //返回计数器触发函数 53 | //该函数每执行一次,计数器加一 54 | //直到到达设定的 total 值或者超时 55 | return function () { 56 | if (running && ++count >= total) { 57 | done(); 58 | } 59 | }; 60 | } 61 | -------------------------------------------------------------------------------- /src/runtime/util/derive.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Js 派生实现 3 | * 4 | * @param {Function} parent 父类 5 | * @param {Function} [constructor] 子类构造函数 6 | * @param {Object} [proto] 子类原型 7 | * @access public 8 | * @return {Function} 新的类 9 | * 10 | * @example 11 | * 12 | * var ClassA = derive(Object, function(__super){ 13 | * console.log("I'm an instance of ClassA:", this instanceof ClassA); 14 | * }); 15 | * 16 | * var ClassB = derive(ClassA, function(__super){ 17 | * console.log("I'm an instance of ClassB:", this instanceof ClassB); 18 | * __super(); 19 | * }, { 20 | * test:function(){ 21 | * console.log("test method!"); 22 | * } 23 | * }); 24 | * 25 | * var b = new ClassB(); 26 | * //I'm an instance of ClassA: true 27 | * //I'm an instance of ClassA: true 28 | * b.test(); 29 | * //test method! 30 | */ 31 | function derive(parent, constructor, proto) { 32 | 33 | //如果没有传 constructor 参数 34 | if (typeof constructor === 'object') { 35 | proto = constructor; 36 | constructor = proto.constructor || function () { 37 | }; 38 | delete proto.constructor; 39 | } 40 | 41 | var tmp = function () { 42 | }, 43 | //子类构造函数 44 | subClass = function () { 45 | //有可能子类和父类初始化参数定义不同,所以将初始化延迟到子类构造函数中执行 46 | //构造一个 __super 函数,用于子类中调用父类构造函数 47 | var __super = bind(parent, this), 48 | args = slice(arguments); 49 | 50 | //将 __super 函数作为 constructor 的第一个参数 51 | args.unshift(__super); 52 | constructor.apply(this, args); 53 | 54 | //parent.apply(this, arguments); 55 | //constructor.apply(this, arguments); 56 | }, 57 | subClassPrototype; 58 | 59 | //原型链桥接 60 | tmp.prototype = parent.prototype; 61 | subClassPrototype = new tmp(); 62 | 63 | //复制属性到子类的原型链上 64 | copyProperties( 65 | subClassPrototype, 66 | constructor.prototype, 67 | proto || {}); 68 | 69 | subClassPrototype.constructor = constructor.prototype.constructor; 70 | subClass.prototype = subClassPrototype; 71 | return subClass; 72 | } 73 | 74 | -------------------------------------------------------------------------------- /src/runtime/util/each.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 遍历数组或对象 3 | * 4 | * @param {Object|Array} object 数组或对象 5 | * @param {Function} callback 回调函数 6 | * @param {Object} [thisObj=undefined] 回调函数的this对象 7 | * @access public 8 | * @return {Object|Array} 被遍历的对象 9 | * 10 | * @example 11 | * 12 | * var arr = [1, 2, 3, 4]; 13 | * each(arr, function(item, index, arr){ 14 | * console.log(index + ":" + item); 15 | * }); 16 | * // 0:1 17 | * // 1:2 18 | * // 2:3 19 | * // 3:4 20 | * 21 | * @example 22 | * 23 | * var arr = [1, 2, 3, 4]; 24 | * each(arr, function(item, index, arr){ 25 | * console.log(index + ":" + item); 26 | * if(item > 2){ 27 | * return false; 28 | * } 29 | * }); 30 | * // 0:1 31 | * // 1:2 32 | */ 33 | function each(object, callback, thisObj) { 34 | var name, i = 0, 35 | length = object.length, 36 | isObj = length === undefined || isFunction(object); 37 | 38 | if (isObj) { 39 | for (name in object) { 40 | if (callback.call(thisObj, object[name], name, object) === false) { 41 | break; 42 | } 43 | } 44 | } else { 45 | for (i = 0; i < length; i++) { 46 | if (callback.call(thisObj, object[i], i, object) === false) { 47 | break; 48 | } 49 | } 50 | } 51 | return object; 52 | } 53 | -------------------------------------------------------------------------------- /src/runtime/util/hasOwnProperty.js: -------------------------------------------------------------------------------- 1 | /** 2 | * hasOwnProperty 3 | * 4 | * @param obj $obj 5 | * @param key $key 6 | * @access public 7 | * @return void 8 | */ 9 | function hasOwnProperty(obj, key) { 10 | 11 | var native_hasOwnProperty = Object.prototype.hasOwnProperty; 12 | 13 | hasOwnProperty = function(obj, key) { 14 | return native_hasOwnProperty.call(obj, key); 15 | }; 16 | 17 | return hasOwnProperty(obj, key); 18 | } 19 | -------------------------------------------------------------------------------- /src/runtime/util/inArray.js: -------------------------------------------------------------------------------- 1 | /** 2 | * inArray 判断对象是否为数组 3 | * 4 | * @param {array} arr 数据源 5 | * @param {item} item 需要判断的数据项 6 | * @access public 7 | * @return {index} 该数据项在目标数组中的索引 8 | */ 9 | function inArray(arr, item) { 10 | //如果支持 Array.prototype.indexOf 方法则直接使用, 11 | var index = undefined; 12 | if (Array.prototype.indexOf) { 13 | return arr.indexOf(item) > -1 ? arr.indexOf(item) : -1; 14 | } else { 15 | each(arr, function (v, i, arr) { 16 | if (v === item) { 17 | index = i; 18 | return false; 19 | } 20 | }); 21 | } 22 | return index === undefined ? -1 : index; 23 | } 24 | -------------------------------------------------------------------------------- /src/runtime/util/isArray.js: -------------------------------------------------------------------------------- 1 | /** 2 | * isArray 判断对象是否为数组 3 | * 4 | * @param {Object} obj 需要被判断的对象 5 | * @access public 6 | * @return {Boolean} 该对象是否为 Array 7 | */ 8 | function isArray(obj) { 9 | //重设 isArray 函数指针,方便下次调用 10 | //如果支持 Array.isArray 方法则直接使用, 11 | //否则用 type 函数来判断 12 | isArray = Array.isArray || function (obj) { 13 | return type(obj) === 'array'; 14 | }; 15 | return isArray(obj); 16 | } 17 | -------------------------------------------------------------------------------- /src/runtime/util/isEmpty.js: -------------------------------------------------------------------------------- 1 | /** 2 | * isEmpty 判断是否是空对象,数组判断长度,对象判断自定义属性,其它取非 3 | * 4 | * @param {Object} obj 需要被判断的对象 5 | * @access public 6 | * @return {Boolean} 该对象是否为为空 7 | */ 8 | function isEmpty(obj) { 9 | if (isArray(obj)) { 10 | return obj.length === 0; 11 | } else if (typeof obj === 'object') { 12 | for (var i in obj) return false; 13 | return true; 14 | } else return !obj; 15 | } 16 | -------------------------------------------------------------------------------- /src/runtime/util/isFunction.js: -------------------------------------------------------------------------------- 1 | /** 2 | * isFunction 判断一个对象是否为一个函数 3 | * 4 | * @param {Object} obj 需要被鉴定的对象 5 | * @access public 6 | * @return {Boolean} 该对象是否为一个函数 7 | */ 8 | function isFunction(obj) { 9 | return type(obj) === 'function'; 10 | } 11 | -------------------------------------------------------------------------------- /src/runtime/util/nextTick.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 将函数延迟执行 3 | * 4 | * @param {Function} fn 希望被延迟执行的函数 5 | * @access public 6 | * @return {Number} 等待执行的任务数 7 | * 8 | * @example 9 | * 10 | * var fn = function(){ 11 | * console.log(2); 12 | * }; 13 | * 14 | * nextTick(fn); 15 | * console.log(1); 16 | * 17 | * // 1 18 | * // 2 19 | */ 20 | function nextTick(fn) { 21 | 22 | var callbacks = [], //等待调用的函数栈 23 | running = false; //当前是否正在运行中 24 | 25 | //调用所有在函数栈中的函数 26 | //如果在执行某函数时又有新的函数被添加进来, 27 | //该函数也会在本次调用的最后被执行 28 | function callAllCallbacks() { 29 | var count, index; 30 | 31 | count = callbacks.length; 32 | for (index = 0; index < count; index++) { 33 | //TODO 错误处理 34 | callbacks[index](); 35 | } 36 | //删除已经调用过的函数 37 | callbacks.splice(0, count); 38 | 39 | //判断是否还有函数需要执行 40 | //函数可能在 callAllCallbacks 调用的过程中被添加到 callbacks 数组 41 | //所以需要再次判断 42 | if (callbacks.length) { 43 | setTimeout(callAllCallbacks, 1); 44 | } else { 45 | running = false; 46 | } 47 | } 48 | 49 | //修改 nextTick 函数指针,方便下次调用 50 | nextTick = function (fn) { 51 | //将函数存放到待调用栈中 52 | callbacks.push(fn); 53 | 54 | //判断定时器是否启动 55 | //如果没有启动,则启动计时器 56 | //如果已经启动,则不需要做什么 57 | //本次添加的函数会在 callAllCallbacks 时被调用 58 | if (!running) { 59 | running = true; 60 | setTimeout(callAllCallbacks, 1); 61 | } 62 | return callbacks.length; 63 | }; 64 | 65 | return nextTick.apply(this, arguments); 66 | } 67 | -------------------------------------------------------------------------------- /src/runtime/util/queueCall.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 按照顺序调用函数,避免调用堆栈过长 3 | * 4 | * @param {Function} fn 需要调用的函数 5 | * @access public 6 | * @return {Number} 当前的队列大小,返回0表示函数已经被立即执行 7 | * 8 | * @example 9 | * 10 | * function fn1(){ 11 | * console.log("fn1 start"); 12 | * queueCall(fn2); 13 | * console.log("fn1 end"); 14 | * } 15 | * 16 | * function fn2(){ 17 | * console.log("fn2 start"); 18 | * queueCall(fn3); 19 | * console.log("fn2 end"); 20 | * } 21 | * function fn3(){ 22 | * console.log("fn3 start"); 23 | * console.log("fn3 end"); 24 | * } 25 | * 26 | * queueCall(fn1); 27 | * //fn1 start 28 | * //fn1 end 29 | * //fn2 start 30 | * //fn2 end 31 | * //fn3 start 32 | * //fn3 end 33 | */ 34 | function queueCall(fn) { 35 | var running = false, 36 | list = []; 37 | 38 | queueCall = function (fn) { 39 | var count, index; 40 | list.push(fn); 41 | if (!running) { 42 | running = true; 43 | while (true) { 44 | count = list.length; 45 | if (count <= 0) { 46 | break; 47 | } 48 | for (index = 0; index < count; index++) { 49 | //TODO 错误处理 50 | list[index](); 51 | } 52 | list.splice(0, count); 53 | } 54 | running = false; 55 | } 56 | 57 | return list.length; 58 | }; 59 | 60 | return queueCall(fn); 61 | } 62 | -------------------------------------------------------------------------------- /src/runtime/util/slice.js: -------------------------------------------------------------------------------- 1 | /** 2 | * slice 把数组中一部分的浅复制存入一个新的数组对象中,并返回这个新的数组。 3 | * 4 | * @param {Array} array 数组 5 | * @param {Number} start 开始索引 6 | * @param {Number} end 结束索引 7 | * @access public 8 | * @return {Array} 被截取后的数组 9 | */ 10 | function slice(array, start, end) { 11 | var _slice = Array.prototype.slice; 12 | //重写 slice 函数指针,便于下次调用. 13 | slice = function (array, start, end) { 14 | switch (arguments.length) { 15 | case 0: 16 | //TODO throw Error??? 17 | return []; 18 | case 1: 19 | return _slice.call(array); 20 | case 2: 21 | return _slice.call(array, start); 22 | case 3: 23 | default: 24 | return _slice.call(array, start, end); 25 | } 26 | }; 27 | return slice.apply(this, arguments); 28 | } 29 | -------------------------------------------------------------------------------- /src/runtime/util/toArray.js: -------------------------------------------------------------------------------- 1 | /** 2 | * toArray 将类数组对象转化为数组 3 | * 4 | * @param {Object} obj 需要被转化的类数组对象 5 | * @access public 6 | * @return {Array} 数组对象 7 | */ 8 | function toArray(obj) { 9 | return slice(obj); 10 | } 11 | -------------------------------------------------------------------------------- /src/runtime/util/type.js: -------------------------------------------------------------------------------- 1 | /** 2 | * type 判断对象类型函数 3 | * 从 jquery 中拷贝来的 4 | * 5 | * @param {Object} obj 被鉴定的对象 6 | * @access public 7 | * @return {String} 对象类型字符串 8 | */ 9 | function type(obj) { 10 | //var types = "Boolean Number String Function Array Date RegExp Object".split(" "), 11 | var types = ["Boolean", "Number", "String", "Function", "Array", "Date", "RegExp", "Object"], 12 | toString = Object.prototype.toString, 13 | class2type = {}, 14 | count, name; 15 | 16 | //构造 class2type 表 17 | //{ 18 | // "[object Object]" : "object", 19 | // "[object RegExp]" : "regexp", 20 | // "[object Date]" : "date", 21 | // "[object Array]" : "array", 22 | // "[object Function]" : "function", 23 | // "[object String]" : "string", 24 | // "[object Number]" : "number", 25 | // "[object Boolean]" : "boolean" 26 | //} 27 | count = types.length; 28 | while (count--) { 29 | name = types[count]; 30 | class2type["[object " + name + "]"] = name.toLowerCase(); 31 | } 32 | 33 | // 判断函数,初始化后再次调用会直接调用这个函数 34 | function _type(obj) { 35 | return obj == null ? 36 | String(obj) : 37 | class2type[toString.call(obj)] || "object"; 38 | } 39 | 40 | //修改 type 函数指针,以便多次调用开销 41 | type = _type; 42 | 43 | return type(obj); 44 | } 45 | 46 | 47 | var str = 'abcba'; 48 | var indexMap = {}; 49 | var tmpArr = []; 50 | for(var i = 0; i < str.length; i++) { 51 | var s = str[i]; 52 | var index = indexMap[s]; 53 | if (index === undefined) { 54 | index = tmpArr.push(s) - 1; 55 | indexMap[s] = index; 56 | } else { 57 | tmpArr[index] = ''; 58 | } 59 | } 60 | 61 | var ret = tmpArr.join(''); 62 | 63 | console.log(ret[0]); 64 | -------------------------------------------------------------------------------- /src/runtime/util/util.js: -------------------------------------------------------------------------------- 1 | __inline('ajax.js'); 2 | __inline('amd.js'); 3 | __inline('appendToHead.js'); 4 | __inline('bind.js'); 5 | __inline('copyProperties.js'); 6 | __inline('counter.js'); 7 | __inline('derive.js'); 8 | __inline('each.js'); 9 | __inline('hasOwnProperty.js'); 10 | __inline('inArray.js'); 11 | __inline('isArray.js'); 12 | __inline('isEmpty.js'); 13 | __inline('isFunction.js'); 14 | __inline('JSON.js'); 15 | __inline('nextTick.js'); 16 | __inline('queueCall.js'); 17 | __inline('slice.js'); 18 | __inline('toArray.js'); 19 | __inline('type.js'); 20 | -------------------------------------------------------------------------------- /src/server/block.script.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * @param array $params 8 | * @param string $content 9 | * @param Smarty $smarty 10 | * @param bool $repeat 11 | * @see BigPipe::currentContext 12 | * @see PageletContext->addRequire 13 | * @see PageletContext->addRequireAsync 14 | * @see PageletContext->addHook 15 | */ 16 | function smarty_block_script($params, $content, $smarty, &$repeat) 17 | { 18 | if (!$repeat && isset($content)) { 19 | $eventType = isset($params['on']) ? $params['on'] : "load"; 20 | $strict = (isset($params['strict']) && $params['strict'] == false) ? false : true; 21 | $context = BigPipe::currentContext(); 22 | 23 | if (isset($params["sync"])) { 24 | foreach ($params["sync"] as $resource) { 25 | BigPipeResource::registModule($resource); 26 | } 27 | $context->addRequire($eventType, $params["sync"]); 28 | } 29 | 30 | if (isset($params["async"])) { 31 | foreach ($params["async"] as $resource) { 32 | BigPipeResource::registModule($resource); 33 | } 34 | $context->addRequireAsync($eventType, $params["async"]); 35 | } 36 | 37 | $context->addHook($eventType, $content, $strict); 38 | } 39 | } 40 | 41 | -------------------------------------------------------------------------------- /src/server/compiler.body.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * @param array $params 9 | * @param Smarty $smarty 10 | * @access public 11 | * @return string 编译后的php代码 12 | */ 13 | function smarty_compiler_body($params, $smarty){ 14 | return 15 | ''; 19 | } 20 | 21 | /** 22 | * smarty 编译插件 bodyclose 23 | * 24 | * 处理 {/body} 标签 25 | * 26 | * @param array $params 27 | * @param Smarty $smarty 28 | * @access public 29 | * @return string 编译后的php代码 30 | */ 31 | function smarty_compiler_bodyclose($params, $smarty){ 32 | return 33 | ''; 37 | } 38 | -------------------------------------------------------------------------------- /src/server/compiler.head.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * @param array $params 9 | * @param Smarty $smarty 10 | * @access public 11 | * @return string 编译后的php代码 12 | */ 13 | function smarty_compiler_head($params, $smarty){ 14 | return 15 | ''; 19 | } 20 | 21 | /** 22 | * smarty 编译插件 headclose 23 | * 24 | * 处理 {/head} 标签 25 | * 26 | * @param array $params 27 | * @param Smarty $smarty 28 | * @access public 29 | * @return string 编译后的php代码 30 | */ 31 | function smarty_compiler_headclose($params, $smarty){ 32 | return 33 | ''; 37 | } 38 | -------------------------------------------------------------------------------- /src/server/compiler.html.php: -------------------------------------------------------------------------------- 1 | smarty)){'. 23 | 'do{'. 24 | 'if(' . BigPipe::compileOpenTag(BigPipe::TAG_HTML, $params) . '){'. 25 | '?>'; 26 | } 27 | 28 | /** 29 | * smarty 编译插件 htmlclose 30 | * 31 | * 处理 {/html} 标签 32 | * 33 | * @param array $params 34 | * @param Smarty $smarty 35 | * @access public 36 | * @return string 编译后的php代码 37 | */ 38 | function smarty_compiler_htmlclose($params, $smarty){ 39 | return 40 | ''; 46 | } 47 | -------------------------------------------------------------------------------- /src/server/compiler.pagelet.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * @param array $params 9 | * @param Smarty $smarty 10 | * @access public 11 | * @return string 编译后的php代码 12 | * @see BigPipe::compileOpenTag 13 | */ 14 | function smarty_compiler_pagelet($params, $smarty){ 15 | return 16 | ''; 20 | } 21 | 22 | /** 23 | * smarty 编译插件 pageletclose 24 | * 25 | * 处理 {/pagelet} 标签 26 | * 27 | * @param array $params 28 | * @param Smarty $smarty 29 | * @access public 30 | * @return string 编译后的php代码 31 | * @see BigPipe::compileCloseTag 32 | */ 33 | function smarty_compiler_pageletclose($params, $smarty){ 34 | return 35 | ''; 39 | } 40 | -------------------------------------------------------------------------------- /src/server/compiler.title.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * @param array $params 9 | * @param Smarty $smarty 10 | * @access public 11 | * @return string 编译后的php代码 12 | * @see BigPipe::compileOpenTag 13 | */ 14 | function smarty_compiler_title($params, $smarty){ 15 | return 16 | ''; 20 | } 21 | 22 | /** 23 | * smarty 编译插件 titleclose 24 | * 25 | * 处理 {/title} 标签 26 | * 27 | * @param array $params 28 | * @param Smarty $smarty 29 | * @access public 30 | * @return string 编译后的php代码 31 | * @see BigPipe::compileCloseTag 32 | */ 33 | function smarty_compiler_titleclose($params, $smarty){ 34 | return 35 | ''; 39 | } 40 | -------------------------------------------------------------------------------- /src/server/function.require.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * @param array $params 8 | * @param Smarty $smarty 9 | * @return void 10 | * @see BigPipeResource::registModule 11 | * @see BigPipe::currentContext 12 | * @see PageletContext->addRequire 13 | */ 14 | function smarty_function_require($params, $smarty){ 15 | $link = $params['name']; 16 | unset($params['name']); 17 | 18 | BigPipeResource::registModule($link); 19 | 20 | $context = BigPipe::currentContext(); 21 | $resource = BigPipeResource::getResourceByPath($link); 22 | 23 | switch ($resource["type"]) { 24 | case 'css': 25 | $on = isset($params['on']) ? $params['on'] : 'beforedisplay'; 26 | break; 27 | case 'js': 28 | $on = isset($params['on']) ? $params['on'] : 'load'; 29 | break; 30 | } 31 | 32 | $context->addRequire($on, $link); 33 | } 34 | -------------------------------------------------------------------------------- /src/server/function.widget.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * @param array $params 8 | * @param Smarty $template 9 | * @return Smarty template funtion call 10 | * @see BigPipeResource::registModule 11 | * @see BigPipeResource::getTplByPath 12 | * @see BigPipe::currentContext 13 | * @see PageletContext->addRequire 14 | */ 15 | 16 | function smarty_function_widget($params, $template) 17 | { 18 | $name = $params['name']; 19 | $method = $params['method']; 20 | unset($params['name']); 21 | unset($params['method']); 22 | 23 | if(isset($params['call'])){ 24 | $call = $params['call']; 25 | unset($params['call']); 26 | } 27 | 28 | BigPipeResource::registModule($name); 29 | 30 | $tpl = BigPipeResource::getTplByPath($name); 31 | // Auto add widget css and less deps (no js) to currentContext. 32 | if(!empty($tpl["deps"])){ 33 | 34 | $deps = $tpl["deps"]; 35 | $context = BigPipe::currentContext(); 36 | 37 | foreach($deps as $dep){ 38 | BigPipeResource::registModule($dep); 39 | $depResource = BigPipeResource::getResourceByPath($dep); 40 | 41 | if($depResource["type"] === "css"){ 42 | $on = 'beforedisplay'; 43 | $context->addRequire($on, $dep); 44 | } 45 | } 46 | } 47 | 48 | $smarty=$template->smarty; 49 | $tplpath = $tpl["uri"]; 50 | 51 | // First try to call the mothed passed via the $call param, 52 | // in order to made it compatible for fisp. 53 | if(isset($call)){ 54 | $call = 'smarty_template_function_' . $call; 55 | if(!function_exists($call)) { 56 | try { 57 | $smarty->fetch($tplpath); 58 | } catch (Exception $e) { 59 | throw new Exception("\nNo tpl here, via call \n\"$name\" \n@ \"$tplpath\""); 60 | } 61 | } 62 | if(function_exists($call)) { 63 | return $call($template, $params); 64 | } 65 | } 66 | 67 | // If there is no method named $call, 68 | // try to call mothed passed via the $method param 69 | $fn='smarty_template_function_' . $method; 70 | if(!function_exists($fn)) { 71 | try { 72 | $smarty->fetch($tplpath); 73 | } catch (Exception $e) { 74 | throw new Exception("\nNo tpl here,via method \n\"$name\" \n@ \"$tplpath\""); 75 | } 76 | } 77 | 78 | if(function_exists($fn)) { 79 | return $fn($template, $params); 80 | } 81 | 82 | // If still no method named $method, 83 | // try to construct a method name with the tpl path, via md5(). 84 | // This is in order to support call method through dynamic tpl path. 85 | else 86 | { 87 | $methodName = preg_replace('/^_[a-fA-F0-9]{32}_/','',$method); 88 | 89 | if($method !== $methodName){ 90 | $method = '_' . md5($name) . '_' . $methodName; 91 | 92 | $fn='smarty_template_function_' . $method; 93 | 94 | if(function_exists($fn)){ 95 | return $fn($template, $params); 96 | } 97 | } 98 | throw new Exception("\nUndefined function \"$method\" \nin \"$name\" \n@ \"$tplpath\""); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/server/lib/BigPipeResource.class.php: -------------------------------------------------------------------------------- 1 | 6 | */ 7 | class BigPipeResource 8 | { 9 | /** 10 | * 存储资源map 11 | */ 12 | private static $map = array( 13 | "res" => array(), 14 | "her" => array(), 15 | ); 16 | /** 17 | * 存储已经注册的module 18 | */ 19 | private static $registedMoudle = array(); 20 | /** 21 | * 存储已经处理过返回给前端的Resources 22 | */ 23 | public static $knownResources = array(); 24 | /** 25 | * 初始化资源map 26 | * 27 | * @param array $map 类名 28 | * @static 29 | * @access public 30 | * @return void 31 | */ 32 | public static function setupMap($map) 33 | { 34 | self::$map["res"] = self::$map["res"] + $map["res"]; 35 | self::$map["her"] = self::$map["her"] + $map["her"]; 36 | } 37 | 38 | /** 39 | * 注册一个module 将module的map存在$registedMoudle 40 | * 41 | * @param string $name 标准化资源路径 42 | * @static 43 | * @access public 44 | * @return void 45 | */ 46 | public static function registModule($name) 47 | { 48 | $intPos = strpos($name, ':'); 49 | 50 | if ($intPos === false) { 51 | return; 52 | } else { 53 | $femodule = substr($name, 0, $intPos); 54 | } 55 | 56 | $configPath = BIGPIPE_CONF_DIR; 57 | 58 | if (!in_array($femodule, self::$registedMoudle)) { 59 | 60 | if (defined("FE_HTTPS") && FE_HTTPS) { 61 | $femodulefix = $femodule . '-https'; 62 | } else { 63 | $femodulefix = $femodule; 64 | } 65 | $mapPath = $configPath . '/' . $femodulefix . '-map.json'; 66 | if (!file_exists($mapPath)) { 67 | throw new Exception("map file not exist, module: $femodule, path: $mapPath"); 68 | } 69 | 70 | $map = json_decode(file_get_contents($mapPath), true); 71 | if (!is_array($map)) { 72 | throw new Exception("map decode fail, module: $femodule"); 73 | } 74 | 75 | BigPipeResource::setupMap($map); 76 | self::$registedMoudle[] = $femodule; 77 | } 78 | } 79 | 80 | /** 81 | * 通过标准化路径获取tpl资源 82 | * 83 | * @param string $path 标准化资源路径 84 | * @static 85 | * @access public 86 | * @return resource 87 | */ 88 | public static function getTplByPath($path) 89 | { 90 | return self::$map["res"][$path]; 91 | } 92 | 93 | /** 94 | * 通过标准化路径获取资源 95 | * 96 | * @param string $path 标准化资源路径 97 | * @static 98 | * @access public 99 | * @return resource 100 | */ 101 | public static function getResourceByPath($path, $type = null) 102 | { 103 | 104 | $map = self::$map["her"]; 105 | $resource = self::getResource($map, $path, $type); 106 | if ($resource) { 107 | return $resource; 108 | } 109 | 110 | return false; 111 | } 112 | 113 | /** 114 | * 从给定 map 获取资源 115 | * 116 | * @param array $map 资源map 117 | * @param string $path 资源path 118 | * @param string $type 资源类型 119 | * @return resource 120 | */ 121 | public static function getResource($map, $path, $type) 122 | { 123 | foreach ($map as $id => $resource) { 124 | if ((!isset($type) || $type == $resource['type']) 125 | && in_array($path, $resource['defines'])) { 126 | $resource['id'] = $id; 127 | if (!isset($resource['requires'])) { 128 | $resource['requires'] = array(); 129 | } 130 | 131 | if (!isset($resource['requireAsyncs'])) { 132 | $resource['requireAsyncs'] = array(); 133 | } 134 | 135 | return $resource; 136 | } 137 | } 138 | return false; 139 | } 140 | 141 | /** 142 | * 通过路径数组获取资源数组 143 | * 144 | * @param string $pathArr 标准化资源路径数组 145 | * @static 146 | * @access public 147 | * @return resources 资源数组 148 | */ 149 | public static function pathToResource($pathArr, $type = null) 150 | { 151 | $resources = array(); 152 | 153 | foreach ($pathArr as $path) { 154 | $resource = self::getResourceByPath($path, $type); 155 | if ($resource) { 156 | $resources[$resource['id']] = $resource; 157 | } 158 | } 159 | return $resources; 160 | } 161 | 162 | /** 163 | * 通过资源数组获取依赖资源数组 164 | * 165 | * @param array $resources 资源数组 166 | * @param bool $asyncs 是否需要获取async依赖 167 | * @static 168 | * @access public 169 | * @return resources 依赖资源数组 170 | */ 171 | public static function getDependResource($resources, $asyncs = true) 172 | { 173 | $dependResources = array(); 174 | 175 | $depends = $resources; 176 | 177 | while (!empty($depends)) { 178 | 179 | $last = end($depends); 180 | array_pop($depends); 181 | 182 | $id = $last['id']; 183 | 184 | if (isset($dependResources[$id])) { 185 | continue; 186 | } 187 | $dependResources[$id] = $last; 188 | 189 | $lastDepends = self::getDepend($last, $asyncs); 190 | if (!empty($lastDepends)) { 191 | $depends = BigPipe::array_merge($depends, $lastDepends); 192 | } 193 | } 194 | 195 | return array_reverse($dependResources, true); 196 | } 197 | 198 | /** 199 | * 获取一个资源的依赖 200 | * 201 | * @param mixed $resource 资源数组 202 | * @param bool $asyncs 是否需要获取async依赖 203 | * @static 204 | * @access public 205 | * @return resources 依赖资源数组 206 | */ 207 | private static function getDepend($resource, $asyncs) 208 | { 209 | $requires = $resource['requires']; 210 | 211 | if ($asyncs) { 212 | $requires = array_merge($requires, $resource['requireAsyncs']); 213 | } 214 | 215 | if (count($requires) > 0) { 216 | return $dependResources = self::pathToResource($requires); 217 | } 218 | return array(); 219 | } 220 | 221 | } 222 | -------------------------------------------------------------------------------- /src/server/lib/PageController.class.php: -------------------------------------------------------------------------------- 1 | 7 | * zhangwentao 8 | */ 9 | 10 | abstract class PageController 11 | { 12 | /** 13 | * 标签打开的动作类型 14 | * @see getActionKey 15 | */ 16 | const ACTION_OPEN = 1; 17 | /** 18 | * 标签关闭的动作类型 19 | * @see getActionKey 20 | */ 21 | const ACTION_CLOSE = 2; 22 | /** 23 | * 判断页面是否需要再次执行的动作类型 24 | * @see getActionKey 25 | */ 26 | const ACTION_MORE = 3; 27 | 28 | /** 29 | * 执行的动作链,子类应该设置该属性,以便 doAction 调用 30 | * 31 | * @var array 32 | * @access protected 33 | */ 34 | protected $actionChain = null; 35 | 36 | /** 37 | * 得到用于执行的动作链 key, 子类应该实现,以便从 actionChain 中查找用于执行的动作链 38 | * 39 | * @param PageletContext $context 当前 Pagelet 上下文 40 | * @param int $action 当前的动作类型 41 | * @abstract 42 | * @access protected 43 | * @return string 44 | */ 45 | abstract protected function getActionKey($context, $action); 46 | 47 | /** 48 | * 执行某个函数链 49 | * 50 | * @param string $key 函数链名 51 | * @param PageletContext $context 当前 Pagelet 上下文 52 | * @access private 53 | * @return bool 函数链的执行结果 54 | */ 55 | private function doAction($key, $context) 56 | { 57 | $ret = null; 58 | $actions = null; 59 | 60 | if (isset($this->actionChain[$key])) { 61 | $actions = $this->actionChain[$key]; 62 | if (is_string($actions)) { 63 | // $actions = array( 64 | // $actions 65 | // ); 66 | $actions = call_user_func(array( 67 | $this, 68 | $actions 69 | ), $context); 70 | } 71 | if (is_array($actions)) { 72 | foreach ($actions as $method) { 73 | if (is_string($method)) { 74 | $ret = call_user_func(array( 75 | $this, 76 | $method 77 | ), $context); 78 | } else { 79 | $ret = $method; 80 | } 81 | // 如果返回 false 直接退出返回 82 | if($ret === false){ 83 | break; 84 | } 85 | } 86 | } else { 87 | $ret = $actions; 88 | } 89 | } 90 | return $ret; 91 | } 92 | 93 | /** 94 | * 标签打开时调用,控制标签的执行 95 | * 96 | * @param PageletContext $context 当前 Pagelet 上下文 97 | * @final 98 | * @access public 99 | * @return bool 当前 Pagelet 是否需要执行 100 | */ 101 | public final function openTag($context) 102 | { 103 | return $this->doAction($this->getActionKey($context->type, self::ACTION_OPEN), $context); 104 | } 105 | 106 | /** 107 | * 标签关闭时调用 108 | * 109 | * @param PageletContext $context 110 | * @final 111 | * @access public 112 | * @return void 113 | */ 114 | public final function closeTag($context) 115 | { 116 | $this->doAction($this->getActionKey($context->type, self::ACTION_CLOSE), $context); 117 | } 118 | 119 | /** 120 | * 页面完成一次执行后调用,用于控制页面是否重复执行 121 | * 122 | * @final 123 | * @access public 124 | * @return bool 页面是否重复执行 125 | */ 126 | public final function hasMore() 127 | { 128 | return $this->doAction($this->getActionKey(BigPipe::TAG_NONE, self::ACTION_MORE), null); 129 | } 130 | 131 | /** 132 | * 输出打开标签 133 | * 134 | * @param PageletContext $context 当前 Pagelet 上下文 135 | * @access protected 136 | * @return void 137 | */ 138 | protected function outputOpenTag($context) 139 | { 140 | echo $context->getOpenHTML(); 141 | } 142 | 143 | /** 144 | * 输出闭合标签 145 | * 146 | * @param PageletContext $context 当前 Pagelet 上下文 147 | * @access protected 148 | * @return void 149 | */ 150 | protected function outputCloseTag($context) 151 | { 152 | echo $context->getCloseHTML(); 153 | } 154 | 155 | /** 156 | * 开始收集内容 157 | * 158 | * @param PageletContext $context 当前 Pagelet 上下文 159 | * @access protected 160 | * @return void 161 | */ 162 | protected function startCollect($context) 163 | { 164 | ob_start(); 165 | } 166 | 167 | /** 168 | * 将收集到的内容作为 pagelet 的 HTML 内容 169 | * 170 | * @param PageletContext $context 当前 Pagelet 上下文 171 | * @access protected 172 | * @return void 173 | */ 174 | protected function collectHTML($context) 175 | { 176 | $context->html = ob_get_clean(); 177 | } 178 | 179 | /** 180 | * collectScript 收集脚本 181 | * 182 | * @param PageletContext $context 当前 Pagelet 上下文 183 | * @access protected 184 | * @return void 185 | */ 186 | protected function collectScript($context) 187 | { 188 | $context->parent->addScript( 189 | ob_get_clean(), 190 | $context->getBigPipeConfig("on", "load"), 191 | $context->getBigPipeConfig("deps"), 192 | $context->getBigPipeConfig("asyncs") 193 | ); 194 | } 195 | 196 | } 197 | -------------------------------------------------------------------------------- /src/server/lib/PageletEvent.class.php: -------------------------------------------------------------------------------- 1 | 7 | * zhangwentao 8 | */ 9 | 10 | class PageletEvent 11 | { 12 | /** 13 | * 事件的依赖资源 14 | * 15 | * @var mixed 16 | * @access public 17 | */ 18 | public $requires = null; 19 | 20 | /** 21 | * 事件的异步依赖资源 22 | * 23 | * @var mixed 24 | * @access public 25 | */ 26 | public $requireAsyncs = null; 27 | 28 | /** 29 | * 事件的钩子函数 30 | * 31 | * @var mixed 32 | * @access public 33 | */ 34 | public $hooks = null; 35 | 36 | /** 37 | * 构造新的 PageletEvent 对象 38 | * 39 | * @access public 40 | * @return void 41 | */ 42 | public function __construct() 43 | { 44 | $this->requires = array(); 45 | $this->requireAsyncs = array(); 46 | $this->hooks = array(); 47 | } 48 | 49 | /** 50 | * 添加依赖资源 51 | * 52 | * @param string $resourceName 依赖资源名 53 | * @access public 54 | * @return void 55 | */ 56 | public function addRequire($resourceName) 57 | { 58 | if (!in_array($resourceName, $this->requires)) { 59 | $this->requires[] = $resourceName; 60 | } 61 | } 62 | 63 | /** 64 | * 添加异步依赖资源 65 | * 66 | * @param string $resourceName 异步依赖资源名 67 | * @access public 68 | * @return void 69 | */ 70 | public function addRequireAsync($resourceName) 71 | { 72 | if (!in_array($resourceName, $this->requireAsyncs)) { 73 | $this->requireAsyncs[] = $resourceName; 74 | } 75 | } 76 | 77 | /** 78 | * 添加钩子函数 79 | * 80 | * @param string $scriptCode 钩子函数代码 81 | * @access public 82 | * @return void 83 | */ 84 | public function addHook($scriptCode, $strict) 85 | { 86 | if($strict){ 87 | $scriptCode = "'use strict';\n" . $scriptCode; 88 | } 89 | $this->hooks[] = $scriptCode; 90 | } 91 | } 92 | 93 | -------------------------------------------------------------------------------- /src/server/lib/TestController.class.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | 9 | BigPipe::loadClass("FirstController"); 10 | BigPipe::loadClass("BigPipeResource"); 11 | 12 | class TestController extends FirstController 13 | { 14 | /** 15 | * 构造函数 16 | * 17 | * @param Array $ids 18 | * @param Array $parents 19 | * @return void 20 | */ 21 | public function __construct($ids, $parents) 22 | { 23 | $this->ids = $ids; 24 | $this->parents = $parents; 25 | $this->actionChain = array( 26 | //收集阶段 27 | 'collect_html_open' => array( 28 | 'outputOpenTag', 29 | true, 30 | ), 31 | 'collect_head_open' => array( 32 | 'startCollect', 33 | true, 34 | ), 35 | 'collect_head_close' => array( 36 | 'collectHeadHTML', 37 | ), 38 | 'collect_body_open' => array( 39 | 'startCollect', 40 | false, 41 | ), 42 | 'collect_pagelet_open' => 'collect_pagelet_open', 43 | 'collect_pagelet_close' => 'collect_pagelet_close', 44 | 'collect_body_close' => array( 45 | 'collectBodyHTML', 46 | ), 47 | 'collect_more' => array( 48 | 'changeState', 49 | true, 50 | ), 51 | //输出阶段 52 | 'output_head_open' => array( 53 | 'outputOpenTag', 54 | 'outputNoscriptFallback', 55 | 'outputHeadHTML', 56 | false, 57 | ), 58 | 'output_head_close' => array( 59 | //TODO 'outputLayoutStyle', 60 | 'outputLayoutStyle', 61 | 'outputCloseTag', 62 | ), 63 | 'output_body_open' => array( 64 | 'outputOpenTag', 65 | 'outputBodyHTML', 66 | false, 67 | ), 68 | 'output_body_close' => array( 69 | 'outputBigPipeLibrary', 70 | 'outputLazyPagelets', 71 | 'outputLoadedResource', 72 | 'outputLayoutPagelet', 73 | 'outputPagelets', 74 | 'outputCloseTag', 75 | ), 76 | 'output_html_close' => array( 77 | 'outputCloseTag', 78 | ), 79 | 'output_more' => false, 80 | 'default' => false, 81 | ); 82 | } 83 | 84 | /** 85 | * collect_pagelet_open 时的 actionChain 86 | * 87 | * @param PageletContext $context 88 | * @return actionChain 89 | */ 90 | protected function collect_pagelet_open($context) 91 | { 92 | // if no pagelet id, append unique id 93 | $id = $context->getParam("id", $this->sessionUniqId("__elm_"), PageletContext::FLG_APPEND_PARAM); 94 | 95 | if (isset($context->parent)) { 96 | $parentId = $context->parent->getParam("id"); 97 | if (!empty($parentId) && in_array($parentId, $this->ids)) { 98 | $this->ids = array_merge($this->ids, array($id)); 99 | } 100 | } 101 | 102 | // if only in parents, just output tag and empty pagelet 103 | if (!in_array($id, $this->ids)) { 104 | if (in_array($id, $this->parents)) { 105 | return array( 106 | 'outputOpenTag', 107 | 'addPagelet', 108 | ); 109 | } else { 110 | return false; 111 | } 112 | } 113 | 114 | switch ($context->renderMode) { 115 | case BigPipe::RENDER_MODE_NONE: 116 | $actionChain = false; 117 | break; 118 | default: 119 | $actionChain = array( 120 | 'outputOpenTag', 121 | 'addPagelet', 122 | 'startCollect', 123 | true, 124 | ); 125 | } 126 | return $actionChain; 127 | } 128 | /** 129 | * collect_pagelet_close 时的 actionChain 130 | * 131 | * @param PageletContext $context 132 | * @return actionChain 133 | */ 134 | protected function collect_pagelet_close($context) 135 | { 136 | $id = $context->getParam("id"); 137 | 138 | if (!in_array($id, $this->ids)) { 139 | if (in_array($id, $this->parents)) { 140 | return array( 141 | 'outputCloseTag', 142 | ); 143 | } else { 144 | return false; 145 | } 146 | } 147 | 148 | switch ($context->renderMode) { 149 | case BigPipe::RENDER_MODE_NONE: 150 | $actionChain = false; 151 | break; 152 | case BigPipe::RENDER_MODE_SERVER: 153 | $actionChain = array( 154 | 'collectHTML', 155 | // 'setupBigrender', 156 | 'renderPagelet', 157 | 'outputCloseTag', 158 | ); 159 | break; 160 | default: 161 | $actionChain = array( 162 | 'collectHTML', 163 | 'outputCloseTag', 164 | ); 165 | } 166 | return $actionChain; 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /test/fisp-module/fis-conf.js: -------------------------------------------------------------------------------- 1 | fis.config.get('modules.preprocessor.tpl').unshift('fispadaptor') 2 | 3 | fis.config.merge({ 4 | namespace: 'fisp-test', 5 | deploy: { 6 | test: { 7 | //from参数省略,表示从发布后的根目录开始上传 8 | //发布到当前项目的上一级的output目录中 9 | to: '../output' 10 | } 11 | }, 12 | // pack : { 13 | // 'static/pkg/aio.css' : [ 14 | // 'static/lib/css/bootstrap.css', 15 | // 'static/lib/css/bootstrap-responsive.css', 16 | // 'widget/**.css' 17 | // ], 18 | // 'static/pkg/aio.js' : [ 19 | // 'static/lib/js/jquery-1.10.1.js', 20 | // 'widget/**.js' 21 | // ] 22 | // }, 23 | roadmap: { 24 | path: [{ 25 | reg: /^\/widget\/(.*\.tpl)$/i, 26 | isMod: true, 27 | url: '${namespace}/widget/$1', 28 | release: '/template/${namespace}/widget/$1' 29 | }] 30 | }, 31 | settings: { 32 | smarty: { 33 | left_delimiter: "{", 34 | right_delimiter: "}" 35 | } 36 | } 37 | 38 | }); 39 | -------------------------------------------------------------------------------- /test/fisp-module/page/a.tpl: -------------------------------------------------------------------------------- 1 | a.tpl -------------------------------------------------------------------------------- /test/fisp-module/page/test.html: -------------------------------------------------------------------------------- 1 | This is "test.html". -------------------------------------------------------------------------------- /test/fisp-module/page/test.tpl: -------------------------------------------------------------------------------- 1 | This is "test.tpl"; 2 | haha 3 | 4 | {html framework="../static/lib.js"} 5 | 6 | {widget name="../widget/test-widget.tpl"} 7 | 8 | {require name="../static/test.js"} 9 | {require name="/static/test.css"} 10 | 11 | {script} 12 | require.async("../static/async.js"); 13 | {/script} 14 | 15 | -------------------------------------------------------------------------------- /test/fisp-module/static/async.js: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /test/fisp-module/static/lib.js: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /test/fisp-module/static/test.css: -------------------------------------------------------------------------------- 1 | /* This is test.css */ 2 | html{ 3 | padding: 0; 4 | } -------------------------------------------------------------------------------- /test/fisp-module/static/test.js: -------------------------------------------------------------------------------- 1 | // This is test.js 2 | var a = "This is test.js"; -------------------------------------------------------------------------------- /test/fisp-module/static/test.less: -------------------------------------------------------------------------------- 1 | // This is test.less 2 | html{ 3 | margin:0; 4 | 5 | body{ 6 | margin:0 7 | } 8 | } -------------------------------------------------------------------------------- /test/fisp-module/widget/fisp-widget.tpl: -------------------------------------------------------------------------------- 1 | This is a fisp like widget, 2 | which has no "define" or "function" wrapper. -------------------------------------------------------------------------------- /test/fisp-module/widget/test-widget.css: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /test/fisp-module/widget/test-widget.tpl: -------------------------------------------------------------------------------- 1 | {require name="/static/test.js"} 2 | {require name="/static/test.css"} 3 | 4 | {script} 5 | require("/static/test.js"); 6 | {/script} -------------------------------------------------------------------------------- /test/normal-module/fis-conf.js: -------------------------------------------------------------------------------- 1 | fis.config.merge({ 2 | namespace: 'test', 3 | deploy: { 4 | test: { 5 | //from参数省略,表示从发布后的根目录开始上传 6 | //发布到当前项目的上一级的output目录中 7 | to: '../output' 8 | } 9 | }, 10 | pack : { 11 | 'static/pkg/test.css?__inline' : [ 12 | 'static/test.css' 13 | ], 14 | 'static/pkg/test_copy.css' : [ 15 | 'static/test_copy.css' 16 | ] 17 | }, 18 | roadmap: { 19 | path: [{ 20 | reg: /^\/widget\/(.*\.tpl)$/i, 21 | isMod: true, 22 | url: '${namespace}/widget/$1', 23 | release: '/template/${namespace}/widget/$1' 24 | }] 25 | }, 26 | settings: { 27 | smarty: { 28 | left_delimiter: "{", 29 | right_delimiter: "}" 30 | } 31 | } 32 | 33 | }); 34 | -------------------------------------------------------------------------------- /test/normal-module/page/a.tpl: -------------------------------------------------------------------------------- 1 | a.tpl -------------------------------------------------------------------------------- /test/normal-module/page/inlinetest.tpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /test/normal-module/page/test.html: -------------------------------------------------------------------------------- 1 | This is "test.html". -------------------------------------------------------------------------------- /test/normal-module/page/test.tpl: -------------------------------------------------------------------------------- 1 | This is "test.tpl"; 2 | haha 3 | 4 | {widget name="../widget/test-widget.tpl"} 5 | 6 | {require name="../static/test.js"} 7 | {require name="/static/test.css"} 8 | 9 | {script} 10 | require.async("../static/async.js"); 11 | {/script} 12 | 13 | -------------------------------------------------------------------------------- /test/normal-module/static/async.js: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /test/normal-module/static/test.css: -------------------------------------------------------------------------------- 1 | /* This is test.css */ 2 | html{ 3 | padding: 0; 4 | } -------------------------------------------------------------------------------- /test/normal-module/static/test.js: -------------------------------------------------------------------------------- 1 | // This is test.js 2 | var a = "This is test.js"; -------------------------------------------------------------------------------- /test/normal-module/static/test_copy.css: -------------------------------------------------------------------------------- 1 | /* This is test.css */ 2 | html{ 3 | padding: 0; 4 | } -------------------------------------------------------------------------------- /test/normal-module/static/testless.less: -------------------------------------------------------------------------------- 1 | // This is test.less 2 | html{ 3 | margin:0; 4 | 5 | body{ 6 | margin:0 7 | } 8 | } -------------------------------------------------------------------------------- /test/normal-module/widget/fisp-widget.tpl: -------------------------------------------------------------------------------- 1 | This is a fisp like widget, 2 | which has no "define" or "function" wrapper. -------------------------------------------------------------------------------- /test/normal-module/widget/test-widget.css: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /test/normal-module/widget/test-widget.tpl: -------------------------------------------------------------------------------- 1 | {define} 2 | {require name="/static/test.js"} 3 | {require name="/static/test.css"} 4 | {require name="test-widget.css"} 5 | 6 | {script} 7 | require("/static/test.js"); 8 | {/script} 9 | {/define} -------------------------------------------------------------------------------- /version.js: -------------------------------------------------------------------------------- 1 | ////output version info 2 | //module.exports = function () { 3 | // var content = [ 4 | // '', 5 | // ' v' + fis.cli.info.version, 6 | // '', 7 | // '0000'.bold.red + ' ' + '0000'.bold.red + ' ' + '00000000000000'.bold.yellow + ' ' + '0000000000000'.bold.green, 8 | // '0000'.bold.red + ' ' + '0000'.bold.red + ' ' + '00000000000000'.bold.yellow + ' ' + '00000000000000'.bold.green, 9 | // '0000'.bold.red + ' ' + '0000'.bold.red + ' ' + '0000'.bold.yellow + ' ' + '0000'.bold.green + ' ' + '0000'.bold.green, 10 | // '00000000000000'.bold.red + ' ' + '0000000000'.bold.yellow + ' ' + '00000000000000'.bold.green, 11 | // '00000000000000'.bold.red + ' ' + '0000000000'.bold.yellow + ' ' + '0000000000000'.bold.green, 12 | // '0000'.bold.red + ' ' + '0000'.bold.red + ' ' + '0000'.bold.yellow + ' ' + '0000'.bold.green + ' ' + '0000'.bold.green, 13 | // '0000'.bold.red + ' ' + '0000'.bold.red + ' ' + '00000000000000'.bold.yellow + ' ' + '0000'.bold.green + ' ' + '0000'.bold.green, 14 | // '0000'.bold.red + ' ' + '0000'.bold.red + ' ' + '00000000000000'.bold.yellow + ' ' + '0000'.bold.green + ' ' + '0000'.bold.green, 15 | // ].join('\n'); 16 | // console.log(content); 17 | //}; 18 | 19 | module.exports = function () { 20 | 21 | var content = [ 22 | '', 23 | ' v' + fis.cli.info.version, 24 | '', 25 | '`--.'.bold.red + ' ' + ':ss-'.bold.red + ' ' + './////////////.'.bold.yellow + ' ' + '`/ossssso+:-'.bold.green, 26 | '.yyo'.bold.red + ' ' + '+yy/'.bold.red + ' ' + '+oo+//+++++++/.'.bold.yellow + ' ' + ':dmdhyyyyhddy/`'.bold.green, 27 | '.yyo'.bold.red + ' ' + '/yys`'.bold.red + ' ' + '+oo.'.bold.yellow + ' ' + '.ymh:'.bold.green + ' ' + '`:ymmy'.bold.green, 28 | '-yyo'.bold.red + ' ' + '/yyy`'.bold.red + ' ' + '/oo+'.bold.yellow + ' ' + 'omd:'.bold.green + ' ' + '`mmm'.bold.green, 29 | 'oyys--::+oosyyy`'.bold.red + ' ' + '.ooo------..`'.bold.yellow + ' ' + 'omm/'.bold.green + ' ' + '`.-ommh'.bold.green, 30 | 'syyyyyyssoo+yyy.'.bold.red + ' ' + '`oooo+++++++-'.bold.yellow + ' ' + 'ommysssyhddh+.'.bold.green, 31 | 'yyyo-..``'.bold.red + ' ' + 'syy:'.bold.red + ' ' + '`ooo.```````'.bold.yellow + ' ' + 'ommdsssdmm+`'.bold.green, 32 | 'yyy:'.bold.red + ' ' + 'oyyo'.bold.red + ' ' + '`+oo-'.bold.yellow + ' ' + '/dmh.'.bold.green + ' ' + '`.hmd:'.bold.green, 33 | 'yyy`'.bold.red + ' ' + 'oyys'.bold.red + ' ' + '`/oo+---------.'.bold.yellow + ' ' + '`hmd.'.bold.green + ' ' + '.ymd/'.bold.green, 34 | 'yyy'.bold.red + ' ' + ':yys'.bold.red + ' ' + '`:++++++++++:-'.bold.yellow + ' ' + 'hmy`'.bold.green + ' ' + '`oh+`'.bold.green 35 | ].join('\n'); 36 | console.log(content); 37 | }; 38 | 39 | --------------------------------------------------------------------------------