├── test └── ut │ ├── ext_map │ ├── widget │ │ ├── b.js │ │ ├── c.js │ │ └── a.js │ ├── server.conf │ ├── plugin │ │ ├── modifier.f_escape_xml.php │ │ ├── compiler.cdn.php │ │ ├── modifier.f_escape_path.php │ │ ├── modifier.f_escape_js.php │ │ ├── modifier.f_escape_data.php │ │ ├── modifier.f_escape_callback.php │ │ ├── modifier.f_escape_event.php │ │ ├── compiler.uri.php │ │ ├── compiler.body.php │ │ ├── compiler.widget_block.php │ │ ├── compiler.head.php │ │ ├── compiler.title.php │ │ ├── compiler.require.php │ │ ├── block.widget_inline.php │ │ ├── compiler.html.php │ │ ├── function.http_header.php │ │ ├── compiler.script.php │ │ ├── compiler.style.php │ │ ├── compiler.widget.php │ │ ├── README.md │ │ └── lib │ │ │ ├── FISResource.class.php │ │ │ └── FISPagelet.class.php │ ├── page │ │ └── index.tpl │ ├── fis-conf.js │ ├── webapp │ │ └── webapp.php │ └── static │ │ └── mod-store.js │ └── ext_map.js ├── .gitignore ├── README.md ├── package.json └── index.js /test/ut/ext_map/widget/b.js: -------------------------------------------------------------------------------- 1 | console.log('b'); -------------------------------------------------------------------------------- /test/ut/ext_map/widget/c.js: -------------------------------------------------------------------------------- 1 | console.log('c'); -------------------------------------------------------------------------------- /test/ut/ext_map/server.conf: -------------------------------------------------------------------------------- 1 | rewrite ^\/webapp\.php webapp/webapp.php -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | /.settings 3 | /.project 4 | 5 | tmp 6 | ~* 7 | *.db 8 | *.bak 9 | *.tmp 10 | *.swp 11 | .DS_Store 12 | -------------------------------------------------------------------------------- /test/ut/ext_map/widget/a.js: -------------------------------------------------------------------------------- 1 | console.log('a'); 2 | console.log('b'); 3 | console.log('c'); 4 | alert('a'); 5 | alert('b'); 6 | alert('cccc'); -------------------------------------------------------------------------------- /test/ut/ext_map/plugin/modifier.f_escape_xml.php: -------------------------------------------------------------------------------- 1 | ', '\'', '"'), 5 | array('&', '<', '>', ''', '"'), 6 | strval($str) 7 | ); 8 | } -------------------------------------------------------------------------------- /test/ut/ext_map.js: -------------------------------------------------------------------------------- 1 | var project_root = __dirname + '/ext_map', 2 | fisp = require('fis-plus'); 3 | fisp.cli.run([ 4 | 'fisp release -cmpd output -r ' + project_root + ' --no-color', 5 | 'fisp', 6 | 'release', 7 | '-cmpd', 8 | 'output', 9 | '-r', 10 | project_root, 11 | '--no-color' 12 | ]); 13 | -------------------------------------------------------------------------------- /test/ut/ext_map/page/index.tpl: -------------------------------------------------------------------------------- 1 | {%html framework="extmap:static/mod-store.js"%} 2 | {%head%} 3 | This is a test 4 | {%/head%} 5 | {%body%} 6 | {%script%} 7 | require.async('/widget/a.js'); 8 | require.async('/widget/b.js'); 9 | {%/script%} 10 | {%/body%} 11 | {%/html%} 12 | -------------------------------------------------------------------------------- /test/ut/ext_map/plugin/compiler.cdn.php: -------------------------------------------------------------------------------- 1 | '; 11 | return $strCode; 12 | } -------------------------------------------------------------------------------- /test/ut/ext_map/plugin/modifier.f_escape_path.php: -------------------------------------------------------------------------------- 1 | "\\\\", 6 | "'" => "\\x27", 7 | "\"" => "\\x22", 8 | "/" => "\\/", 9 | "\n" => "\\n", 10 | "\r" => "\\r" 11 | ); 12 | $patterns = array_keys($char_map); 13 | $targets = array_values($char_map); 14 | return str_replace($patterns, $targets, $str); 15 | } -------------------------------------------------------------------------------- /test/ut/ext_map/plugin/modifier.f_escape_data.php: -------------------------------------------------------------------------------- 1 | '<', 6 | '>' => '>', 7 | "'" => "\\'", 8 | '"' => '\\"', 9 | "\\" => "\\\\", 10 | "\n" => "\\n", 11 | "\r" => "\\r", 12 | "/" => "\\/" 13 | ); 14 | $patterns = array_keys($arr_js_char); 15 | $targets = array_values($arr_js_char); 16 | $ret = str_replace($patterns, $targets, $str); 17 | return $ret; 18 | } -------------------------------------------------------------------------------- /test/ut/ext_map/plugin/modifier.f_escape_callback.php: -------------------------------------------------------------------------------- 1 | ", 19 | "license": "MIT" 20 | } 21 | -------------------------------------------------------------------------------- /test/ut/ext_map/plugin/modifier.f_escape_event.php: -------------------------------------------------------------------------------- 1 | '&', 6 | '<' => '<', 7 | '>' => '>', 8 | "\\" => "\\\\", 9 | "'" => "\\'", 10 | '"' => "\\"", 11 | "\n" => "\\n", 12 | "\r" => "\\r", 13 | "/" => "\\/" 14 | ); 15 | $patterns = array_keys($char_map); 16 | $targets = array_values($char_map); 17 | return str_replace($patterns, $targets, $str); 18 | } -------------------------------------------------------------------------------- /test/ut/ext_map/plugin/compiler.uri.php: -------------------------------------------------------------------------------- 1 | smarty);'; 10 | $strCode .= '?>'; 11 | } 12 | return $strCode; 13 | } 14 | -------------------------------------------------------------------------------- /test/ut/ext_map/plugin/compiler.body.php: -------------------------------------------------------------------------------- 1 | $_value) { 6 | $strAttr .= ' ' . $_key . '=""'; 7 | } 8 | return ''; 9 | } 10 | 11 | function smarty_compiler_bodyclose($arrParams, $smarty){ 12 | $strCode = ''; 13 | $strCode .= ''; 18 | return $strCode; 19 | } -------------------------------------------------------------------------------- /test/ut/ext_map/plugin/compiler.widget_block.php: -------------------------------------------------------------------------------- 1 | '; 10 | return $strCode; 11 | } 12 | 13 | function smarty_compiler_widget_blockclose($arrParams, $smarty){ 14 | $strCode = ''; 15 | $strCode .= ''; 20 | return $strCode; 21 | } -------------------------------------------------------------------------------- /test/ut/ext_map/plugin/compiler.head.php: -------------------------------------------------------------------------------- 1 | $_value) { 6 | $strAttr .= ' ' . $_key . '=""'; 7 | } 8 | return ''; 9 | } 10 | 11 | function smarty_compiler_headclose($arrParams, $smarty){ 12 | $strResourceApiPath = preg_replace('/[\\/\\\\]+/', '/', dirname(__FILE__) . '/lib/FISPagelet.class.php'); 13 | $strCode = ''; 17 | $strCode .= ''; 18 | return $strCode; 19 | } -------------------------------------------------------------------------------- /test/ut/ext_map/plugin/compiler.title.php: -------------------------------------------------------------------------------- 1 | " . ''; 5 | } 6 | 7 | function smarty_compiler_titleclose($arrParams, $smarty){ 8 | $strResourceApiPath = preg_replace('/[\\/\\\\]+/', '/', dirname(__FILE__) . '/lib/FISPagelet.class.php'); 9 | $strCode = ''; 18 | return $strCode . ''; 19 | } 20 | -------------------------------------------------------------------------------- /test/ut/ext_map/plugin/compiler.require.php: -------------------------------------------------------------------------------- 1 | smarty,'.$async.');'; 20 | $strCode .= '?>'; 21 | } 22 | return $strCode; 23 | } 24 | -------------------------------------------------------------------------------- /test/ut/ext_map/plugin/block.widget_inline.php: -------------------------------------------------------------------------------- 1 | $value){ 7 | if($value instanceof Smarty_Variable){ 8 | $tpl_vars[$key] = $value; 9 | } else { 10 | $tpl_vars[$key] = new Smarty_Variable($value); 11 | } 12 | } 13 | } 14 | public static function pop(&$tpl_vars){ 15 | $tpl_vars = array_pop(self::$_vars); 16 | } 17 | } 18 | 19 | function smarty_block_widget_inline($params, $content, Smarty_Internal_Template $template, &$repeat){ 20 | if(!$repeat){//block 定义结束 21 | FISBlockFisWidget::pop($template->tpl_vars); 22 | return $content; 23 | }else{//block 定义开始 24 | FISBlockFisWidget::push($params, $template->tpl_vars); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /test/ut/ext_map/plugin/compiler.html.php: -------------------------------------------------------------------------------- 1 | smarty));'; 16 | } 17 | $strCode .= 'FISPagelet::init('.$strMode.');'; 18 | $strCode .= ' ?>'; 19 | foreach ($arrParams as $_key => $_value) { 20 | if (is_numeric($_key)) { 21 | $strAttr .= ' '; 22 | } else { 23 | $strAttr .= ' ' . $_key . '=""'; 24 | } 25 | } 26 | return $strCode . ""; 27 | } 28 | 29 | function smarty_compiler_htmlclose($arrParams, $smarty){ 30 | $strCode = 'registerFilter(\'output\', array(\'FISPagelet\', \'renderResponse\'));'; 32 | $strCode .= '?>'; 33 | $strCode .= ''; 34 | return $strCode; 35 | } 36 | -------------------------------------------------------------------------------- /test/ut/ext_map/plugin/function.http_header.php: -------------------------------------------------------------------------------- 1 | 'text/html', 26 | 'json' => 'application/json', 27 | 'javascript' => 'application/x-javascript', 28 | 'js' => 'application/x-javascript', 29 | 'xml' => 'text/xml', 30 | 'stream' => 'application/octet-stream' 31 | ); 32 | if (array_key_exists($type, $mimeTypes)) { 33 | $mime = $mimeTypes[$type]; 34 | } else { 35 | $mime = "text/plain"; 36 | } 37 | header("Content-Type:$mime; charset=$charset;"); 38 | } 39 | 40 | 41 | -------------------------------------------------------------------------------- /test/ut/ext_map/plugin/compiler.script.php: -------------------------------------------------------------------------------- 1 | '; 11 | return $strCode; 12 | } 13 | 14 | function smarty_compiler_scriptclose($params, $smarty){ 15 | $strResourceApiPath = preg_replace('/[\\/\\\\]+/', '/', dirname(__FILE__) . '/lib/FISPagelet.class.php'); 16 | $strCode = ''; 31 | return $strCode; 32 | } -------------------------------------------------------------------------------- /test/ut/ext_map/plugin/compiler.style.php: -------------------------------------------------------------------------------- 1 | '; 13 | return $strCode; 14 | } 15 | 16 | function smarty_compiler_styleclose($params, $smarty){ 17 | $strResourceApiPath = preg_replace('/[\\/\\\\]+/', '/', dirname(__FILE__) . '/lib/FISPagelet.class.php'); 18 | $strCode = ''; 33 | return $strCode; 34 | } -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * fis.baidu.com 3 | */ 4 | 5 | 'use strict' 6 | 7 | var allRet; 8 | 9 | //project root 10 | var root; 11 | var ns; 12 | 13 | var _ = fis.util; 14 | 15 | 16 | // create .data.php 17 | function genData(opt) { 18 | var ids = allRet.ids; 19 | var data = {}; 20 | _.map(ids, function(id, file) { 21 | //只对JS起效 22 | if (file.rExt == '.js') { 23 | data[id] = { 24 | 'uri': file.getUrl(opt.hash, opt.domain), 25 | 'content': file.getContent() 26 | }; 27 | } 28 | }); 29 | var dataFile = fis.file(root, '/webapp/' + (ns ? ns + '-' : '') + 'data.json'); 30 | dataFile.useHash = false; 31 | dataFile.setContent(JSON.stringify(data), null, opt.optimize ? null : 4); 32 | allRet.pkg[dataFile.subpath] = dataFile; 33 | } 34 | 35 | module.exports = function(ret, conf, settings, opt) { 36 | 37 | root = fis.project.getProjectPath(); 38 | ns = fis.config.get('namespace'); 39 | 40 | _.map(ret.map.res, function(id, info) { 41 | //提供给@wangcheng的统计用 42 | info['hash'] = fis.util.md5(id, 7); 43 | }); 44 | 45 | //处理自动打包插件传递的多维的打包数据(如国际化多国家map配置),生成md5及多维的map.json 46 | var retList = opt['retlist']; 47 | if(retList){ 48 | //给多维度的资源添加md5 49 | _.map(retList,function(dimen,_ret){ 50 | if(dimen!="default"){ //默认维度不处理 51 | _.map(_ret.res, function(id, info) { 52 | info['hash'] = fis.util.md5(id, 7); 53 | }); 54 | //生成多个维度的map.json 55 | var map = fis.file(root, (ns ? ns + '-' : '') + 'map-' + dimen +'.json'); 56 | //修正map文件放置目录,config下 57 | map.release = "/config" + map.subpath; 58 | map.setContent(JSON.stringify(_ret, null, opt.optimize ? null : 4)); 59 | ret.pkg[map.subpath] = map; 60 | } 61 | }); 62 | } 63 | 64 | 65 | 66 | //create map.json 67 | var map = fis.file(root, (ns ? ns + '-' : '') + 'map.json'); 68 | map.useHash = false; 69 | map.setContent(JSON.stringify(ret.map), null, opt.optimize ? null : 4); 70 | ret.pkg[map.subpath] = map; 71 | //create data.json 72 | if (settings['create'] && settings['create'].indexOf('data.json') != -1) { 73 | allRet = ret; 74 | genData(opt); 75 | } 76 | }; 77 | -------------------------------------------------------------------------------- /test/ut/ext_map/webapp/webapp.php: -------------------------------------------------------------------------------- 1 | array(), 10 | 'time'=> 0, 11 | 'error'=> '' 12 | );//资源对象 13 | 14 | $g_start = microtime(true); 15 | 16 | //如果请求data 17 | $diff = $_GET['diff']; 18 | 19 | if (!$diff) { 20 | $g_res_obj = array( 21 | 'data' => '{}', 22 | ); 23 | } else { 24 | $diffs = explode('|', $diff); 25 | $g_res_obj['data'] = Bd_Webapp_Reader::getData($diffs); 26 | Bd_Webapp_Reader::setHeader(); 27 | $g_end = microtime(true); 28 | $g_res_obj['time'] = round(($g_end - $g_start)*1000); 29 | 30 | echo json_encode($g_res_obj); 31 | } 32 | 33 | 34 | class Bd_Webapp_Reader{ 35 | public static $resMap = array(); 36 | public static function setHeader(){ 37 | //CORS header 38 | if(preg_match('/\([^)]*OS\s*[^)]*6/i',$_SERVER['HTTP_USER_AGENT'])){ 39 | header('Cache-Control:no-cache,no-store,must-revalidate'); 40 | } 41 | header("Content-type: text/javascript"); 42 | //Gzip压缩 ,不能同时使用 ob_gzhandler() 和 zlib.output_compression。 43 | if (Extension_Loaded('zlib') && !ini_get('zlib.output_compression')){ 44 | //解决PHP开启Gzip页面没有输出的故障 45 | if (strnatcasecmp(PHP_VERSION, '5.3') <= 0 && ini_get('output_buffering') == "4096"){ 46 | ini_set('output_buffering', 0); 47 | } 48 | //如果zlib扩展已经加载到PHP中,用php的内置压缩函数 49 | ob_start('ob_gzhandler'); 50 | } 51 | } 52 | /** 53 | * @static 54 | * @param $packageName 55 | * @return string 56 | */ 57 | private static function getPath($packageName){ 58 | $tokens = explode('_', $packageName, 2); 59 | $moduleName = $tokens[0]; 60 | return WP_DATA_PATH . '/' . $moduleName . '/' . $packageName; 61 | } 62 | 63 | /* 64 | *获取资源数据 65 | */ 66 | public static function getData($diff_list){ 67 | $data = array(); 68 | foreach ($diff_list as $id) { 69 | if ($pos = strpos($id, ':')) { 70 | $namespace = substr($id, 0, $pos); 71 | if (isset(self::$resMap[$namespace]) || self::register($namespace)) { 72 | $resMap = self::$resMap[$namespace]; 73 | $data[$id] = $resMap[$id]; 74 | } else { 75 | //error 76 | } 77 | } 78 | } 79 | 80 | return $data; 81 | } 82 | 83 | public static function register($namespace) { 84 | $path = WP_DATA_PATH . '/' . $namespace . '-data.json'; 85 | if (is_file($path)) { 86 | self::$resMap[$namespace] = json_decode(file_get_contents($path), true); 87 | return true; 88 | } 89 | return false; 90 | } 91 | } -------------------------------------------------------------------------------- /test/ut/ext_map/plugin/compiler.widget.php: -------------------------------------------------------------------------------- 1 | smarty);'; 44 | $strCode .= 'if(isset($_tpl_path)){'; 45 | if($bHasCall){ 46 | $strCode .= '$_smarty_tpl->getSubTemplate($_tpl_path, $_smarty_tpl->cache_id, $_smarty_tpl->compile_id, $_smarty_tpl->caching, $_smarty_tpl->cache_lifetime, ' . $strFuncParams . ', Smarty::SCOPE_LOCAL);'; 47 | $strCode .= 'if(is_callable('. $strTplFuncName . ')){'; 48 | $strCode .= $strCallTplFunc; 49 | $strCode .= '}else{'; 50 | $strCode .= 'trigger_error(\'missing function define "\'.' . $strTplFuncName . '.\'" in tpl "\'.$_tpl_path.\'"\', E_USER_ERROR);'; 51 | $strCode .= '}'; 52 | } else { 53 | $strCode .= 'echo $_smarty_tpl->getSubTemplate($_tpl_path, $_smarty_tpl->cache_id, $_smarty_tpl->compile_id, $_smarty_tpl->caching, $_smarty_tpl->cache_lifetime, ' . $strFuncParams . ', Smarty::SCOPE_LOCAL);'; 54 | } 55 | $strCode .= '}else{'; 56 | $strCode .= 'trigger_error(\'unable to locale resource "\'.' . $strName . '.\'"\', E_USER_ERROR);'; 57 | $strCode .= '}'; 58 | $strCode .= 'FISPagelet::load('.$strName.', $_smarty_tpl->smarty);'; 59 | $strCode .= '}'; 60 | $strCode .= 'FISPagelet::end();'; 61 | } else { 62 | trigger_error('undefined widget name in file "' . $smarty->_current_file . '"', E_USER_ERROR); 63 | } 64 | 65 | if($bHasCall){ 66 | $strCode .= '}'; 67 | } 68 | 69 | $strCode .= '?>'; 70 | return $strCode; 71 | } 72 | 73 | function getWidgetStrCode($path, $arrParams){ 74 | $strFuncParams = getFuncParams($arrParams); 75 | $path = trim($path,"\""); 76 | $fn = '"smarty_template_function_fis_' . strtr(substr($path, 0, strrpos($path, '/')), '/', '_') . '"'; 77 | $strCode = 'getSubTemplate("' . $path . '", $_smarty_tpl->cache_id, $_smarty_tpl->compile_id, null, null, '. $strFuncParams.', Smarty::SCOPE_LOCAL);'; 82 | $strCode .= 'if(is_callable(' . $fn .')){'; 83 | $strCode .= 'return call_user_func('. $fn . ',$_smarty_tpl,' . $strFuncParams . ');'; 84 | $strCode .= '}else{'; 85 | $strCode .= 'echo $fis_widget_output;'; 86 | $strCode .= '}'; 87 | $strCode .= '}?>'; 88 | return $strCode; 89 | } 90 | 91 | function getFuncParams($arrParams){ 92 | $arrFuncParams = array(); 93 | foreach ($arrParams as $_key => $_value) { 94 | if (is_int($_key)) { 95 | $arrFuncParams[] = "$_key=>$_value"; 96 | } else { 97 | $arrFuncParams[] = "'$_key'=>$_value"; 98 | } 99 | } 100 | $strFuncParams = 'array(' . implode(',', $arrFuncParams) . ')'; 101 | return $strFuncParams; 102 | } -------------------------------------------------------------------------------- /test/ut/ext_map/plugin/README.md: -------------------------------------------------------------------------------- 1 | #### Quickling 2 | 3 | 基于fis支持quickling的smarty插件 4 | 5 | #### Overview 6 | 7 | 插件支持两种渲染模式, 8 | 9 | + 正常渲染模式 `noscript`,不需要前端库进行控制 10 | + Pipeline渲染模式 `bigpipe`,需要前端库控制渲染 11 | 12 | ----- 13 | 14 | #####`noscript` Render##### 15 | 16 | ######before###### 17 | 18 | ```html 19 | {%html%} 20 | {%head%} 21 | This is a test 22 | {%/head%} 23 | {%body%} 24 | {%style%} 25 | body { 26 | background-color: #EEEEEE; 27 | } 28 | {%/style%} 29 | {%script%} 30 | console.log("foo, foo, foo"); 31 | {%/script%} 32 | {%widget_block pagelet_id="pager"%} 33 | {%widget name="widget/a.tpl"%} 34 |
35 | //balabala 36 |
37 | {%/widget_block%} 38 | {%/body%} 39 | {%/html%} 40 | ``` 41 | 42 | #####after##### 43 | 44 | ```html 45 | 46 | 47 | This is a test 48 | 53 | 54 | 55 |
56 |
57 | I'm `widget/a.tpl` 58 |
59 |
60 | //balabala.... 61 |
62 |
63 | 68 | 69 | 70 | ``` 71 | 72 | ------- 73 | 74 | ####`bigpipe` render == `pipeline`#### 75 | 76 | #####before##### 77 | 78 | ```html 79 | {%html mode="bigpipe"%} 80 | {%head%} 81 | This is a test 82 | {%/head%} 83 | {%body%} 84 | {%style%} 85 | body { 86 | background-color: #EEEEEE; 87 | } 88 | {%/style%} 89 | {%script%} 90 | console.log("foo, foo, foo"); 91 | {%/script%} 92 | {%widget_block pagelet_id="pager"%} 93 | {%widget name="widget/a.tpl"%} 94 |
95 | //balabala 96 |
97 | {%/widget_block%} 98 | {%/body%} 99 | {%/html%} 100 | ``` 101 | 102 | #####after##### 103 | 104 | ```html 105 | 106 | 107 | This is a test 108 | 113 | 114 | 115 |
116 |
117 | 118 | 119 | 124 | 129 | 133 | 140 | 144 | 147 | ``` 148 | 149 | 如上源码(before)和运行后的代码(after); 150 | 151 | #### Detail 152 | 153 | 在smarty里面为了控制输出,使用插件代替几个html标签。 154 | 155 | | 标签 | 插件 | | 156 | |:--------------:|:-----------------------------|:-----------------------------------------| 157 | | body | compiler.body.php | 确定JS的位置 | 158 | | html | compiler.html.php | 初始化数据 | 159 | | head | compiler.head.php | 确定css安放位置 | 160 | | script | compiler.script.php | 收集内联脚本 | 161 | | style | compiler.style.php | 收集内联样式 | 162 | | title | compiler.title.php | 获取title,以便异步请求切换页面 | 163 | 164 | 在`正常模式`渲染下,js和css加载的位置,css在head关闭标签前;js在body关闭标签前。 165 | 166 | 在`pipeline`渲染下,js和css的链接给前端加载器,前端负责加载。包括html内容,前端负责渲染。 167 | 168 | + lib/FISResource.class.php 收集静态资源,算法演示 169 | + lib/FISPagelet.class.php 初始化系统,输出模式控制 170 | 171 | 172 | #### 关于部署迁移 173 | 174 | OK, 各个插件的功能都已经说过了。现在说一下部署的问题 175 | 176 | ----- 177 | 178 | **纯粹的FIS 2.0项目** 179 | 180 | 把所有插件放到smarty的 plugin_dir下。 181 | 182 | ----- 183 | 184 | **和fis 1.0共存?** 185 | 186 | 把所有插件放到smarty的 plugin_dir下,并删除`compiler.widget_block.php` 187 | 188 | **2.0里面有一个插件和1.0有冲突,那就是`compiler.widget_block.php`,请删除2.0里面的.** 189 | -------------------------------------------------------------------------------- /test/ut/ext_map/static/mod-store.js: -------------------------------------------------------------------------------- 1 | /** 2 | * file: mod-store.js 3 | * ver: 1.0.0 4 | * auth: zhangjiachen@baidu.com 5 | * modify: fansekey@gmail.com 6 | * update: 2013-11-13 7 | */ 8 | var require, define; 9 | 10 | (function(self) { 11 | var head = document.getElementsByTagName('head')[0], 12 | loadingMap = {}, 13 | factoryMap = {}, 14 | modulesMap = {}, 15 | scriptsMap = {}, 16 | resMap = {}, 17 | pkgMap = {}; 18 | 19 | function checkLocalStorage() { 20 | try { 21 | //测试local 22 | localStorage.setItem('fis_test', 'good'); 23 | localStorage.removeItem('fis_test'); 24 | return true; 25 | } catch (e) { 26 | return false; 27 | } 28 | } 29 | function loadByXHR(id, url, callback) { 30 | var store = localStorage 31 | , content 32 | , has 33 | , diff = [] 34 | , script = '' 35 | //保存包内组件内容 36 | , scripts = []; 37 | 38 | var res = resMap[id] || {}; 39 | var pkg = res.pkg; 40 | var url = '/webapp.php'; 41 | 42 | if (pkg) { 43 | if ((has = store.getItem(pkg))) { 44 | has = has.split('|'); 45 | for (var i = 0, len = has.length; i < len; i++) { 46 | var oldUrl = store.getItem(has[i]); 47 | if (oldUrl) { 48 | //组件内容修改了 49 | if (oldUrl != resMap[has[i]]['url']) { 50 | diff[i] = has[i]; 51 | store.removeItem(oldUrl); 52 | } else { 53 | scripts[i] = store.getItem(oldUrl); 54 | } 55 | } else { 56 | diff[i] = has[i]; 57 | } 58 | } 59 | } else { 60 | has = pkgMap[pkg]['has']; 61 | diff = has; 62 | } 63 | store.setItem(pkg, pkgMap[pkg]['has'].join('|')); 64 | } else { 65 | if ((script = store.getItem(res.url))) { 66 | scripts.push(script); 67 | } else { 68 | diff.push(id); 69 | } 70 | } 71 | 72 | url = url + '?diff=' + diff.join('|'); 73 | 74 | function _load(url, scripts, diff, cb) { 75 | var xhr = new window.XMLHttpRequest; 76 | xhr.onreadystatechange = function() { 77 | if (xhr.readyState == 4 ) { 78 | if (xhr.status == 200) { 79 | var obj = eval('(' + xhr.responseText + ')'); 80 | if (!obj) { 81 | return; 82 | } 83 | var data = obj['data']; 84 | for (var resId in data) { 85 | if (data.hasOwnProperty(resId)) { 86 | store.setItem(data[resId]['uri'], data[resId]['content']); 87 | store.setItem(resId, data[resId]['uri']); 88 | scripts.push(data[resId]['content']); 89 | } 90 | } 91 | cb(scripts.join('')); 92 | } else { 93 | throw new Error('A unkown error occurred.'); 94 | } 95 | } 96 | }; 97 | xhr.open('get', url); 98 | xhr.send(null); 99 | } 100 | 101 | if (diff.length == 0) { 102 | callback(scripts.join('')); 103 | } else { 104 | _load(url, scripts, diff, callback); 105 | } 106 | } 107 | 108 | 109 | 110 | function createScript(url, onerror) { 111 | if (url in scriptsMap) return; 112 | scriptsMap[url] = true; 113 | 114 | var script = document.createElement('script'); 115 | if (onerror) { 116 | var tid = setTimeout(onerror, require.timeout); 117 | 118 | script.onerror = function() { 119 | clearTimeout(tid); 120 | onerror(); 121 | }; 122 | 123 | script.onreadystatechange = function() { 124 | if (this.readyState == 'complete') { 125 | clearTimeout(tid); 126 | } 127 | } 128 | } 129 | script.type = 'text/javascript'; 130 | script.src = url; 131 | head.appendChild(script); 132 | return script; 133 | } 134 | 135 | 136 | function loadScript(id, callback, onerror) { 137 | var queue = loadingMap[id] || (loadingMap[id] = []); 138 | queue.push(callback); 139 | 140 | // 141 | // resource map query 142 | // 143 | var res = resMap[id] || {}; 144 | var pkg = res.pkg; 145 | var url; 146 | 147 | if (pkg) { 148 | url = pkgMap[pkg].url; 149 | } else { 150 | url = res.url || id; 151 | } 152 | 153 | if (!window.XMLHttpRequest || !checkLocalStorage()) { 154 | createScript(url, onerror && function() { 155 | onerror(id); 156 | }); 157 | } else { 158 | if (! (url in scriptsMap)) { 159 | scriptsMap[url] = true; 160 | loadByXHR(id, url, function(content) { 161 | script = document.createElement('script'); 162 | script.type = 'text/javascript'; 163 | script.innerHTML = content; 164 | head.appendChild(script); 165 | }); 166 | } 167 | } 168 | } 169 | 170 | define = function(id, factory) { 171 | factoryMap[id] = factory; 172 | 173 | var queue = loadingMap[id]; 174 | if (queue) { 175 | for(var i = queue.length - 1; i >= 0; --i) { 176 | queue[i](); 177 | } 178 | delete loadingMap[id]; 179 | } 180 | }; 181 | 182 | require = function(id) { 183 | id = require.alias(id); 184 | 185 | var mod = modulesMap[id]; 186 | if (mod) { 187 | return mod.exports; 188 | } 189 | 190 | // 191 | // init module 192 | // 193 | var factory = factoryMap[id]; 194 | if (!factory) { 195 | throw Error('Cannot find module `' + id + '`'); 196 | } 197 | 198 | mod = modulesMap[id] = { 199 | exports: {} 200 | }; 201 | 202 | // 203 | // factory: function OR value 204 | // 205 | var ret = (typeof factory == 'function') 206 | ? factory.apply(mod, [require, mod.exports, mod]) 207 | : factory; 208 | 209 | if (ret) { 210 | mod.exports = ret; 211 | } 212 | return mod.exports; 213 | }; 214 | 215 | require.async = function(names, onload, onerror) { 216 | if (typeof names == 'string') { 217 | names = [names]; 218 | } 219 | 220 | for(var i = names.length - 1; i >= 0; --i) { 221 | names[i] = require.alias(names[i]); 222 | } 223 | 224 | var needMap = {}; 225 | var needNum = 0; 226 | 227 | function findNeed(depArr) { 228 | for(var i = depArr.length - 1; i >= 0; --i) { 229 | // 230 | // skip loading or loaded 231 | // 232 | var dep = depArr[i]; 233 | if (dep in factoryMap || dep in needMap) { 234 | continue; 235 | } 236 | 237 | needMap[dep] = true; 238 | needNum++; 239 | loadScript(dep, updateNeed, onerror); 240 | 241 | var child = resMap[dep]; 242 | if (child && 'deps' in child) { 243 | findNeed(child.deps); 244 | } 245 | } 246 | } 247 | 248 | function updateNeed() { 249 | if (0 == needNum--) { 250 | var i, n, args = []; 251 | for(i = 0, n = names.length; i < n; ++i) { 252 | args[i] = require(names[i]); 253 | } 254 | onload && onload.apply(self, args); 255 | } 256 | } 257 | 258 | findNeed(names); 259 | updateNeed(); 260 | }; 261 | 262 | require.resourceMap = function(obj) { 263 | var k, col; 264 | 265 | // merge `res` & `pkg` fields 266 | col = obj.res; 267 | for(k in col) { 268 | if (col.hasOwnProperty(k)) { 269 | resMap[k] = col[k]; 270 | } 271 | } 272 | 273 | col = obj.pkg; 274 | for(k in col) { 275 | if (col.hasOwnProperty(k)) { 276 | pkgMap[k] = col[k]; 277 | } 278 | } 279 | }; 280 | 281 | require.loadJs = function(url) { 282 | createScript(url); 283 | }; 284 | 285 | require.loadCss = function(cfg) { 286 | if (cfg.content) { 287 | var sty = document.createElement('style'); 288 | sty.type = 'text/css'; 289 | 290 | if (sty.styleSheet) { // IE 291 | sty.styleSheet.cssText = cfg.content; 292 | } else { 293 | sty.innerHTML = cfg.content; 294 | } 295 | head.appendChild(sty); 296 | } 297 | else if (cfg.url) { 298 | var link = document.createElement('link'); 299 | link.href = cfg.url; 300 | link.rel = 'stylesheet'; 301 | link.type = 'text/css'; 302 | head.appendChild(link); 303 | } 304 | }; 305 | 306 | 307 | require.alias = function(id) {return id}; 308 | 309 | require.timeout = 5000; 310 | 311 | define.amd = { 312 | 'jQuery': true, 313 | 'version': '1.0.0' 314 | }; 315 | 316 | })(this); 317 | -------------------------------------------------------------------------------- /test/ut/ext_map/plugin/lib/FISResource.class.php: -------------------------------------------------------------------------------- 1 | $val) { 59 | foreach ($val as $id => $info) { 60 | unset(self::$arrLoaded[$id]); 61 | unset(self::$arrAsyncDeleted[$id]); 62 | } 63 | } 64 | $ret['async'] = self::getResourceMap(self::$arrWidgetRequireAsync); 65 | } 66 | 67 | foreach (self::$arrWidgetStatic as $key => $val) { 68 | foreach ($val as $uri) { 69 | foreach (array_keys(self::$arrLoaded, $uri) as $id) { 70 | unset(self::$arrLoaded[$id]); 71 | unset(self::$arrAsyncDeleted[$id]); 72 | } 73 | } 74 | } 75 | //}}} 76 | if (self::$arrWidgetStatic['js']) { 77 | $ret['js'] = self::$arrWidgetStatic['js']; 78 | } 79 | if (self::$arrWidgetStatic['css']) { 80 | $ret['css'] = self::$arrWidgetStatic['css']; 81 | } 82 | if (self::$arrWidgetScript) { 83 | $ret['script'] = self::$arrWidgetScript; 84 | } 85 | if (self::$arrWidgetStyle) { 86 | $ret['style'] = self::$arrWidgetStyle; 87 | } 88 | return $ret; 89 | } 90 | 91 | public static function addStatic($uri, $type) { 92 | if (self::$isInnerWidget) { 93 | self::$arrWidgetStatic[$type][] = $uri; 94 | } else { 95 | self::$arrStaticCollection[$type][] = $uri; 96 | } 97 | } 98 | 99 | public static function addAsync($id, $info, $type) { 100 | if (self::$isInnerWidget) { 101 | self::$arrWidgetRequireAsync[$type][$id] = $info; 102 | } else { 103 | self::$arrRequireAsyncCollection[$type][$id] = $info; 104 | } 105 | } 106 | 107 | public static function delAsync($id, $type) { 108 | if (self::$isInnerWidget) { 109 | unset(self::$arrWidgetRequireAsync[$type][$id]); 110 | } else { 111 | unset(self::$arrRequireAsyncCollection[$type][$id]); 112 | } 113 | } 114 | 115 | public static function getAsync($id, $type) { 116 | if (self::$isInnerWidget) { 117 | return self::$arrWidgetRequireAsync[$type][$id]; 118 | } else { 119 | return self::$arrRequireAsyncCollection[$type][$id]; 120 | } 121 | } 122 | 123 | //设置framewok mod.js 124 | public static function setFramework($strFramework) { 125 | self::$framework = $strFramework; 126 | } 127 | 128 | public static function getFramework() { 129 | return self::$framework; 130 | } 131 | 132 | public static function addScriptPool($code) { 133 | if (!self::$isInnerWidget) { 134 | self::$arrScriptPool[] = $code; 135 | } else { 136 | self::$arrWidgetScript[] = $code; 137 | } 138 | } 139 | 140 | public static function addStylePool($code) { 141 | if (!self::$isInnerWidget) { 142 | self::$arrStylePool[] = $code; 143 | } else { 144 | self::$arrWidgetStyle[] = $code; 145 | } 146 | } 147 | 148 | public static function getArrStaticCollection() { 149 | //内嵌脚本 150 | if (self::$arrScriptPool) { 151 | self::$arrStaticCollection['script'] = self::$arrScriptPool; 152 | } 153 | 154 | if (self::$arrStylePool) { 155 | self::$arrStaticCollection['style'] = self::$arrStylePool; 156 | } 157 | 158 | //异步脚本 159 | if (self::$arrRequireAsyncCollection) { 160 | self::$arrStaticCollection['async'] = self::getResourceMap(self::$arrRequireAsyncCollection); 161 | } 162 | unset(self::$arrStaticCollection['tpl']); 163 | return self::$arrStaticCollection; 164 | } 165 | 166 | //获取异步js资源集合,变为json格式的resourcemap 167 | public static function getResourceMap($arr, $cdn = '') { 168 | $ret = ''; 169 | $arrResourceMap = array(); 170 | if (isset($arr['res'])) { 171 | foreach ($arr['res'] as $id => $arrRes) { 172 | $deps = array(); 173 | if (!empty($arrRes['deps'])) { 174 | foreach ($arrRes['deps'] as $strName) { 175 | if (preg_match('/\.js$/i', $strName)) { 176 | $deps[] = $strName; 177 | } 178 | } 179 | } 180 | 181 | $arrResourceMap['res'][$id] = array( 182 | 'url' => $cdn . $arrRes['uri'], 183 | ); 184 | 185 | if (!empty($arrRes['pkg'])) { 186 | $arrResourceMap['res'][$id]['pkg'] = $arrRes['pkg']; 187 | //如果包含到了某一个包,则模块的url是多余的 188 | if (!isset($_GET['fis_debug'])) { 189 | //@TODO 190 | //unset($arrResourceMap['res'][$id]['url']); 191 | } 192 | } 193 | 194 | if (!empty($deps)) { 195 | $arrResourceMap['res'][$id]['deps'] = $deps; 196 | } 197 | } 198 | } 199 | if (isset($arr['pkg'])) { 200 | foreach ($arr['pkg'] as $id => $arrRes) { 201 | $arrResourceMap['pkg'][$id] = array( 202 | 'url' => $cdn . $arrRes['uri'], 203 | 'has' => $arrRes['has'] 204 | ); 205 | } 206 | } 207 | if (!empty($arrResourceMap)) { 208 | $ret = $arrResourceMap; 209 | } 210 | return $ret; 211 | } 212 | 213 | //获取命名空间的map.json 214 | public static function register($strNamespace, $smarty){ 215 | if($strNamespace === '__global__'){ 216 | $strMapName = 'map'; 217 | } else { 218 | $strMapName = $strNamespace . '-map'; 219 | } 220 | $arrConfigDir = $smarty->getConfigDir(); 221 | foreach ($arrConfigDir as $strDir) { 222 | $strPath = preg_replace('/[\\/\\\\]+/', '/', $strDir . '/' . $strMapName); 223 | if(is_file($strPath . '.php')){ 224 | self::$arrMap[$strNamespace] = require($strPath . '.php'); 225 | return true; 226 | } else if (is_file($strPath . '.json')) { 227 | self::$arrMap[$strNamespace] = json_decode(file_get_contents($strPath . '.json'), true); 228 | return true; 229 | } 230 | } 231 | return false; 232 | } 233 | 234 | public static function getUri($strName, $smarty) { 235 | $intPos = strpos($strName, ':'); 236 | if($intPos === false){ 237 | $strNamespace = '__global__'; 238 | } else { 239 | $strNamespace = substr($strName, 0, $intPos); 240 | } 241 | if(isset(self::$arrMap[$strNamespace]) || self::register($strNamespace, $smarty)) { 242 | $arrMap = &self::$arrMap[$strNamespace]; 243 | $arrRes = &$arrMap['res'][$strName]; 244 | if (isset($arrRes)) { 245 | return $arrRes['uri']; 246 | } 247 | } 248 | } 249 | 250 | /** 251 | * 分析组件依赖 252 | * @param array $arrRes 组件信息 253 | * @param Object $smarty smarty对象 254 | * @param bool $async 是否异步 255 | */ 256 | private static function loadDeps($arrRes, $smarty, $async) { 257 | if(isset($arrRes['deps'])){ 258 | foreach ($arrRes['deps'] as $strDep) { 259 | self::load($strDep, $smarty, $async); 260 | } 261 | } 262 | 263 | //require.async 264 | if (isset($arrRes['extras']) && isset($arrRes['extras']['async'])) { 265 | foreach ($arrRes['extras']['async'] as $uri) { 266 | self::load($uri, $smarty, true); 267 | } 268 | } 269 | } 270 | 271 | /** 272 | * 已经分析到的组件在后续被同步使用时在异步组里删除。 273 | * @param $strName 274 | * @return bool 275 | */ 276 | private static function delAsyncDeps($strName) { 277 | if (isset(self::$arrAsyncDeleted[$strName])) { 278 | return true; 279 | } else { 280 | self::$arrAsyncDeleted[$strName] = true; 281 | $arrRes = self::getAsync($strName, 'res'); 282 | if ($arrRes['pkg']) { 283 | $arrPkg = self::getAsync($arrRes['pkg'], 'pkg'); 284 | if ($arrPkg) { 285 | self::addStatic($arrPkg['uri'], 'js'); 286 | self::delAsync($arrRes['pkg'], 'pkg'); 287 | foreach ($arrPkg['has'] as $strHas) { 288 | self::$arrLoaded[$strHas] = $arrPkg['uri']; 289 | if (self::getAsync($strHas, 'res')) { 290 | self::delAsyncDeps($strHas); 291 | } 292 | } 293 | } else { 294 | self::delAsync($strName, 'res'); 295 | } 296 | } else { 297 | //已经分析过的并且在其他文件里同步加载的组件,重新收集在同步输出组 298 | $res = self::getAsync($strName, 'res'); 299 | self::addStatic($res['uri'], 'js'); 300 | self::$arrLoaded[$strName] = $res['uri']; 301 | self::delAsync($strName, 'res'); 302 | } 303 | if ($arrRes['deps']) { 304 | foreach ($arrRes['deps'] as $strDep) { 305 | //if (isset(self::$arrRequireAsyncCollection['res'][$strDep])) { 306 | if (self::getAsync($strDep, 'res')) { 307 | self::delAsyncDeps($strDep); 308 | } 309 | } 310 | } 311 | } 312 | } 313 | 314 | /** 315 | * 加载组件以及组件依赖 316 | * @param $strName id 317 | * @param $smarty smarty对象 318 | * @param bool $async 是否为异步组件(only JS) 319 | * @return mixed 320 | */ 321 | public static function load($strName, $smarty, $async = false){ 322 | if(isset(self::$arrLoaded[$strName])) { 323 | //同步组件优先级比异步组件高 324 | if (!$async && self::getAsync($strName, 'res')) { 325 | self::delAsyncDeps($strName); 326 | } 327 | return self::$arrLoaded[$strName]; 328 | } else { 329 | $intPos = strpos($strName, ':'); 330 | if($intPos === false){ 331 | $strNamespace = '__global__'; 332 | } else { 333 | $strNamespace = substr($strName, 0, $intPos); 334 | } 335 | if(isset(self::$arrMap[$strNamespace]) || self::register($strNamespace, $smarty)){ 336 | $arrMap = &self::$arrMap[$strNamespace]; 337 | $arrRes = &$arrMap['res'][$strName]; 338 | $arrPkg = null; 339 | $arrPkgHas = array(); 340 | if(isset($arrRes)) { 341 | if(!array_key_exists('fis_debug', $_GET) && isset($arrRes['pkg'])){ 342 | $arrPkg = &$arrMap['pkg'][$arrRes['pkg']]; 343 | $strURI = $arrPkg['uri']; 344 | foreach ($arrPkg['has'] as $strResId) { 345 | self::$arrLoaded[$strResId] = $strURI; 346 | } 347 | foreach ($arrPkg['has'] as $strResId) { 348 | $arrHasRes = &$arrMap['res'][$strResId]; 349 | if ($arrHasRes) { 350 | $arrPkgHas[$strResId] = $arrHasRes; 351 | self::loadDeps($arrHasRes, $smarty, $async); 352 | } 353 | } 354 | } else { 355 | $strURI = $arrRes['uri']; 356 | self::$arrLoaded[$strName] = $strURI; 357 | self::loadDeps($arrRes, $smarty, $async); 358 | } 359 | 360 | if ($async && $arrRes['type'] === 'js') { 361 | if ($arrPkg) { 362 | self::addAsync($arrRes['pkg'], $arrPkg, 'pkg'); 363 | foreach ($arrPkgHas as $id => $val) { 364 | self::addAsync($id, $val, 'res'); 365 | } 366 | } else { 367 | self::addAsync($strName, $arrRes, 'res'); 368 | } 369 | } else { 370 | self::addStatic($strURI, $arrRes['type']); 371 | } 372 | return $strURI; 373 | } else { 374 | self::triggerError($strName, 'undefined resource "' . $strName . '"', E_USER_NOTICE); 375 | } 376 | } else { 377 | self::triggerError($strName, 'missing map file of "' . $strNamespace . '"', E_USER_NOTICE); 378 | } 379 | } 380 | self::triggerError($strName, 'unknown resource "' . $strName . '" load error', E_USER_NOTICE); 381 | } 382 | 383 | /** 384 | * 用户代码自定义js组件,其没有对应的文件 385 | * 只有有后缀的组件找不到时进行报错 386 | * @param $strName 组件ID 387 | * @param $strMessage 错误信息 388 | * @param $errorLevel 错误level 389 | */ 390 | private static function triggerError($strName, $strMessage, $errorLevel) { 391 | $arrExt = array( 392 | 'js', 393 | 'css', 394 | 'tpl', 395 | 'html', 396 | 'xhtml', 397 | ); 398 | if (preg_match('/\.('.implode('|', $arrExt).')$/', $strName)) { 399 | trigger_error(date('Y-m-d H:i:s') . ' ' . $strName . ' ' . $strMessage, $errorLevel); 400 | } 401 | } 402 | 403 | } 404 | -------------------------------------------------------------------------------- /test/ut/ext_map/plugin/lib/FISPagelet.class.php: -------------------------------------------------------------------------------- 1 | '; 77 | const JS_SCRIPT_HOOK = ''; 78 | 79 | const MODE_NOSCRIPT = 0; 80 | const MODE_QUICKLING = 1; 81 | const MODE_BIGPIPE = 2; 82 | 83 | /** 84 | * 收集widget内部使用的静态资源 85 | * array( 86 | * 0: array(), 1: array(), 2: array() 87 | * ) 88 | * @var array 89 | */ 90 | static protected $inner_widget = array( 91 | array(), 92 | array(), 93 | array() 94 | ); 95 | 96 | /** 97 | * 记录pagelet_id 98 | * @var string 99 | */ 100 | static private $_pagelet_id = null; 101 | 102 | static private $_session_id = 0; 103 | static private $_context = array(); 104 | static private $_contextMap = array(); 105 | static private $_pagelets = array(); 106 | static private $_title = ''; 107 | static private $_pagelet_group = array(); 108 | /** 109 | * 解析模式 110 | * @var number 111 | */ 112 | static protected $mode = null; 113 | 114 | static protected $default_mode = null; 115 | 116 | /** 117 | * 某一个widget使用那种模式渲染 118 | * @var number 119 | */ 120 | static protected $widget_mode; 121 | 122 | static protected $filter; 123 | 124 | static public $cp; 125 | static public $arrEmbeded = array(); 126 | 127 | static public $cdn; 128 | 129 | /** 130 | * 设置渲染模式及其需要渲染的widget 131 | * @param $default_mode string 设置默认渲染模式 132 | */ 133 | static public function init($default_mode) { 134 | if (is_string($default_mode) 135 | && in_array( 136 | self::_parseMode($default_mode), 137 | array(self::MODE_BIGPIPE, self::MODE_NOSCRIPT)) 138 | ) { 139 | self::$default_mode = self::_parseMode($default_mode); 140 | } else { 141 | self::$default_mode = self::MODE_NOSCRIPT; 142 | } 143 | 144 | $is_ajax = isset($_SERVER['HTTP_X_REQUESTED_WITH']) 145 | && (strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest'); 146 | if ($is_ajax) { 147 | self::setMode(self::MODE_QUICKLING); 148 | } else { 149 | self::setMode(self::$default_mode); 150 | } 151 | self::setFilter($_GET['pagelets']); 152 | } 153 | 154 | static public function setMode($mode){ 155 | if (self::$mode === null) { 156 | self::$mode = isset($mode) ? intval($mode) : 1; 157 | } 158 | } 159 | 160 | static public function setFilter($ids) { 161 | if (!is_array($ids)) { 162 | $ids = array($ids); 163 | } 164 | foreach ($ids as $id) { 165 | self::$filter[$id] = true; 166 | } 167 | } 168 | 169 | static public function setTitle($title) { 170 | self::$_title = $title; 171 | } 172 | 173 | static public function getUri($strName, $smarty) { 174 | return FISResource::getUri($strName, $smarty); 175 | } 176 | 177 | static public function addScript($code) { 178 | if(self::$_context['hit'] || self::$mode == self::$default_mode){ 179 | FISResource::addScriptPool($code); 180 | } 181 | } 182 | 183 | static public function addStyle($code) { 184 | if(self::$_context['hit'] || self::$mode == self::$default_mode){ 185 | FISResource::addStylePool($code); 186 | } 187 | } 188 | 189 | public static function cssHook() { 190 | return self::CSS_LINKS_HOOK; 191 | } 192 | 193 | public static function jsHook() { 194 | return self::JS_SCRIPT_HOOK; 195 | } 196 | 197 | static function load($str_name, $smarty, $async = false) { 198 | if(self::$_context['hit'] || self::$mode == self::$default_mode){ 199 | FISResource::load($str_name, $smarty, $async); 200 | } 201 | } 202 | 203 | static private function _parseMode($str_mode) { 204 | $str_mode = strtoupper($str_mode); 205 | $mode = self::$mode; 206 | switch($str_mode) { 207 | case 'BIGPIPE': 208 | $mode = self::MODE_BIGPIPE; 209 | break; 210 | case 'QUICKLING': 211 | $mode = self::MODE_QUICKLING; 212 | break; 213 | case 'NOSCRIPT': 214 | $mode = self::MODE_NOSCRIPT; 215 | break; 216 | } 217 | return $mode; 218 | } 219 | /** 220 | * WIDGET START 221 | * 解析参数,收集widget所用到的静态资源 222 | * @param $id 223 | * @param $mode 224 | * @param $group 225 | * @return bool 226 | */ 227 | static public function start($id, $mode = null, $group = null) { 228 | $has_parent = !empty(self::$_context); 229 | 230 | //这个是用来判断是否widget是async加载的。 231 | $special_flag = false; 232 | if ($mode !== null) { 233 | $special_flag = true; 234 | } 235 | 236 | if ($mode) { 237 | self::$widget_mode = self::_parseMode($mode); 238 | } else { 239 | self::$widget_mode = self::$mode; 240 | } 241 | self::$_pagelet_id = $id; 242 | $parent_id = $has_parent ? self::$_context['id'] : ''; 243 | $qk_flag = self::$mode == self::MODE_QUICKLING ? '_qk_' : ''; 244 | $id = empty($id) ? '__elm_' . $parent_id . '_' . $qk_flag . self::$_session_id ++ : $id; 245 | 246 | //widget是否命中,默认命中 247 | $hit = true; 248 | 249 | switch(self::$widget_mode) { 250 | case self::MODE_NOSCRIPT: 251 | if (self::$_pagelet_id) { 252 | echo '
'; 253 | } 254 | break; 255 | case self::MODE_QUICKLING: 256 | $hit = self::$filter[$id]; 257 | case self::MODE_BIGPIPE: 258 | $context = array( 'id' => $id, 'async' => false); 259 | //widget调用时mode='quickling',so,打出异步加载代码 260 | if ($special_flag && !$hit) { 261 | if (!$group) { 262 | echo ''; 265 | } else { 266 | if (isset(self::$_pagelet_group[$group])) { 267 | self::$_pagelet_group[$group][] = $id; 268 | } else { 269 | self::$_pagelet_group[$group] = array($id); 270 | echo ""; 271 | } 272 | } 273 | $context['async'] = true; 274 | } 275 | 276 | $parent = self::$_context; 277 | if(!empty($parent)) { 278 | $parent_id = $parent['id']; 279 | self::$_contextMap[$parent_id] = $parent; 280 | $context['parent_id'] = $parent_id; 281 | if($parent['hit'] && !$special_flag) { 282 | $hit = true; 283 | } else if($hit && self::$mode === self::MODE_QUICKLING){ 284 | unset($context['parent_id']); 285 | } 286 | } 287 | $context['hit'] = $hit; 288 | 289 | 290 | self::$_context = $context; 291 | 292 | if (empty($parent) && $hit) { 293 | FISResource::widgetStart(); 294 | } else if (!empty($parent) && !$parent['hit'] && $hit) { 295 | FISResource::widgetStart(); 296 | } 297 | echo '
'; 298 | ob_start(); 299 | break; 300 | } 301 | return $hit; 302 | } 303 | 304 | /** 305 | * WIDGET END 306 | * 收集html,收集静态资源 307 | */ 308 | static public function end() { 309 | $ret = true; 310 | if (self::$widget_mode !== self::MODE_NOSCRIPT) { 311 | $html = ob_get_clean(); 312 | $pagelet = self::$_context; 313 | //end 314 | if (isset($pagelet['parent_id'])) { 315 | $parent = self::$_contextMap[$pagelet['parent_id']]; 316 | if (!$parent['hit'] && $pagelet['hit']) { 317 | self::$inner_widget[self::$widget_mode][] = FISResource::widgetEnd(); 318 | 319 | } 320 | } else { 321 | if ($pagelet['hit']) { 322 | self::$inner_widget[self::$widget_mode][] = FISResource::widgetEnd(); 323 | } 324 | } 325 | 326 | if($pagelet['hit'] && !$pagelet['async']){ 327 | unset($pagelet['hit']); 328 | unset($pagelet['async']); 329 | $pagelet['html'] = $html; 330 | self::$_pagelets[] = &$pagelet; 331 | unset($pagelet); 332 | } else { 333 | $ret = false; 334 | } 335 | $parent_id = self::$_context['parent_id']; 336 | if(isset($parent_id)){ 337 | self::$_context = self::$_contextMap[$parent_id]; 338 | unset(self::$_contextMap[$parent_id]); 339 | } else { 340 | self::$_context = null; 341 | } 342 | self::$widget_mode = self::$mode; 343 | echo '
'; 344 | } else { 345 | if (self::$_pagelet_id) { 346 | echo '
'; 347 | } 348 | } 349 | 350 | return $ret; 351 | } 352 | 353 | /** 354 | * 设置cdn 355 | */ 356 | static public function setCdn($cdn) { 357 | $cdn = trim($cdn); 358 | self::$cdn = $cdn; 359 | } 360 | 361 | static public function getCdn() { 362 | return self::$cdn; 363 | } 364 | 365 | 366 | /** 367 | * 渲染静态资源 368 | * @param $html 369 | * @param $arr 370 | * @param bool $clean_hook 371 | * @return mixed 372 | */ 373 | static public function renderStatic($html, $arr, $clean_hook = false) { 374 | if (!empty($arr)) { 375 | $code = ''; 376 | $resource_map = $arr['async']; 377 | $loadModJs = (FISResource::getFramework() && ($arr['js'] || $resource_map)); 378 | if ($loadModJs) { 379 | foreach ($arr['js'] as $js) { 380 | $code .= ''; 381 | if ($js == FISResource::getFramework()) { 382 | if ($resource_map) { 383 | $code .= ''; 386 | } 387 | } 388 | } 389 | } 390 | 391 | if (!empty($arr['script'])) { 392 | $code .= ''; 397 | } 398 | $html = str_replace(self::JS_SCRIPT_HOOK, $code . self::JS_SCRIPT_HOOK, $html); 399 | $code = ''; 400 | if (!empty($arr['css'])) { 401 | $code = ''; 404 | } 405 | if (!empty($arr['style'])) { 406 | $code .= ''; 411 | } 412 | //替换 413 | $html = str_replace(self::CSS_LINKS_HOOK, $code . self::CSS_LINKS_HOOK, $html); 414 | } 415 | if ($clean_hook) { 416 | $html = str_replace(array(self::CSS_LINKS_HOOK, self::JS_SCRIPT_HOOK), '', $html); 417 | } 418 | return $html; 419 | } 420 | 421 | /** 422 | * @param $html string html页面内容 423 | * @return mixed 424 | */ 425 | static public function insertPageletGroup($html) { 426 | if (empty(self::$_pagelet_group)) { 427 | return $html; 428 | } 429 | $search = array(); 430 | $replace = array(); 431 | foreach (self::$_pagelet_group as $group => $ids) { 432 | $search[] = ''; 433 | $replace[] = ''; 436 | } 437 | return str_replace($search, $replace, $html); 438 | } 439 | 440 | static public function display($html) { 441 | $html = self::insertPageletGroup($html); 442 | $pagelets = self::$_pagelets; 443 | $mode = self::$mode; 444 | $res = array( 445 | 'js' => array(), 446 | 'css' => array(), 447 | 'script' => array(), 448 | 'style' => array(), 449 | 'async' => array( 450 | 'res' => array(), 451 | 'pkg' => array() 452 | ) 453 | ); 454 | 455 | //{{{ 456 | foreach (self::$inner_widget[$mode] as $item) { 457 | foreach ($res as $key => $val) { 458 | if (isset($item[$key]) && is_array($item[$key])) { 459 | if ($key != 'async') { 460 | $arr = array_merge($res[$key], $item[$key]); 461 | $arr = array_merge(array_unique($arr)); 462 | } else { 463 | $arr = array( 464 | 'res' => array_merge($res['async']['res'], (array)$item['async']['res']), 465 | 'pkg' => array_merge($res['async']['pkg'], (array)$item['async']['pkg']) 466 | ); 467 | } 468 | //合并收集 469 | $res[$key] = $arr; 470 | } 471 | } 472 | } 473 | //if empty, unset it! 474 | foreach ($res as $key => $val) { 475 | if (empty($val)) { 476 | unset($res[$key]); 477 | } 478 | } 479 | //}}} 480 | 481 | //add cdn 482 | foreach ((array)$res['js'] as $key => $js) { 483 | $res['js'][$key] = self::getCdn() . $js; 484 | } 485 | 486 | foreach ((array)$res['css'] as $key => $css) { 487 | $res['css'][$key] = self::getCdn() . $css; 488 | } 489 | 490 | //tpl信息没有必要打到页面 491 | switch($mode) { 492 | case self::MODE_NOSCRIPT: 493 | //渲染widget以外静态文件 494 | $all_static = FISResource::getArrStaticCollection(); 495 | $html = self::renderStatic( 496 | $html, 497 | $all_static, 498 | true 499 | ); 500 | break; 501 | case self::MODE_QUICKLING: 502 | header('Content-Type: text/json;charset: utf-8'); 503 | if ($res['script']) { 504 | $res['script'] = convertToUtf8(implode("\n", $res['script'])); 505 | } 506 | if ($res['style']) { 507 | $res['style'] = convertToUtf8(implode("\n", $res['style'])); 508 | } 509 | foreach ($pagelets as &$pagelet) { 510 | $pagelet['html'] = convertToUtf8(self::insertPageletGroup($pagelet['html'])); 511 | } 512 | unset($pagelet); 513 | $title = convertToUtf8(self::$_title); 514 | $html = json_encode(array( 515 | 'title' => $title, 516 | 'pagelets' => $pagelets, 517 | 'resource_map' => $res 518 | )); 519 | break; 520 | case self::MODE_BIGPIPE: 521 | $external = FISResource::getArrStaticCollection(); 522 | $page_script = $external['script']; 523 | unset($external['script']); 524 | $html = self::renderStatic( 525 | $html, 526 | $external, 527 | true 528 | ); 529 | $html .= "\n"; 530 | $html .= ''; 535 | $html .= "\n"; 536 | 537 | if ($res['script']) { 538 | $res['script'] = convertToUtf8(implode("\n", $res['script'])); 539 | } 540 | if ($res['style']) { 541 | $res['style'] = convertToUtf8(implode("\n", $res['style'])); 542 | } 543 | $html .= "\n"; 544 | foreach($pagelets as $index => $pagelet){ 545 | $id = '__cnt_' . $index; 546 | $html .= ''; 555 | $html .= "\n"; 556 | $html .= ''; 563 | $html .= "\n"; 564 | } 565 | $html .= ''; 576 | break; 577 | } 578 | 579 | return $html; 580 | } 581 | 582 | //smarty output filter 583 | static function renderResponse($content, $smarty) { 584 | return self::display($content); 585 | } 586 | } 587 | --------------------------------------------------------------------------------