UNPKG

isv-page-json-to-xtpl

Version:

abs 将页面 json 转换成 html

438 lines (388 loc) 13.2 kB
var xtpl = require('xtpl'); var fs = require('fs'); var _ = require('lodash'); var path = require('path'); var util = require('./util'); var Promise = require('bluebird'); var debug = require('debug')('page-generator'); var env = process.env.NODE_ENV || 'production'; var air = require('./lib/air'); var seed = require('./lib/framework-seed'); var CACHE_DIR = '.cachefile'; // 缓存文件夹 var CACHE_SOURCE_NATIVE = 'source.native.js'; // native对应的source文件 var CACHE_SOURCE_ORIGIN = 'source.origin.js'; // index对应的source文件 var pageJsonToXtpl = module.exports = { type: '', airPath: 'http://tms-preview.taobao.com/preview', //airPath: 'http://tms-preview.daily.taobao.net/preview', assets: '', setType: function (type) { if (!type) return; pageJsonToXtpl.type = type; }, /** * 获取 solution 类型 * @param assets */ getSolutionType: function(assets){ var type = 'rx'; if (!assets) return type; if (typeof assets === 'string') { assets = JSON.parse(assets); } var names = Object.keys(assets); for (var i = 0; i < names.length; i++) { if (names[i] == 'pi-kissy'){ type = 'kissy'; } // else if (names[i] == 'weex') { // type = 'weex'; // } } return type; }, /** * 向下兼容,设置 type * @param assets * @returns {*} */ setTypeByAssets: function (assets) { return pageJsonToXtpl.type = pageJsonToXtpl.getSolutionType(assets); }, depMap: air.depMap, /** * 执行转换 * 二种场景:本地dev 与 搭建系统中的页面预览 * @param data * @param option */ run: function*(data, option) { /** * 第〇步:前置转换 */ // TODO: to test the PC version, manually set the option terminalType to `pc`. option = option || { //transformLayoutType: 'fiz', //terminalType: 'pc' }; // TODO: delete field `transformLayoutType` later. move this part before method `run` executed. if (option.transformLayoutType) { data = util.transformLayout(data, option.transformLayoutType); } /** * 第一步:生成 solution xtpl */ var solutionXtpl = ''; var solutionParams = yield pageJsonToXtpl.solutionParams(data); if (solutionParams) { solutionParams.terminalType = option.terminalType; solutionXtpl = yield pageJsonToXtpl.renderSolution(solutionParams); } debug('solution xtpl content:\n', solutionXtpl); /** * 第二步:生成 modules html */ //页面存在模块引用 if (data.layout) { data.modules = data.layout.modules; } if (pageJsonToXtpl.type === 'weex') { var code = ''; data.modules.map(function(item) { var cwd = process.cwd(); var cacheDir = path.join(cwd, 'mod', item.name, CACHE_DIR); var targetNativePath = path.join(cacheDir, CACHE_SOURCE_NATIVE); // native 源码 var targetOriginPath = path.join(cacheDir, CACHE_SOURCE_ORIGIN); // index 源码 var c = fs.readFileSync(targetOriginPath, 'utf8'); item.sourceCode = c; }); } var modules = data.modules; var modulesXtpl = ''; if (modules && modules.length) { modulesXtpl = yield pageJsonToXtpl.renderModulesNoAir(data, option); } debug('modules xtpl content:\n', modulesXtpl); /** * 第三步:使用 air 渲染页面 xtpl * @type {string} */ //替换 solution 的 body 变成模块html内容 var xtpl = solutionXtpl.replace(/\{\{block \("body"\) \}\}/g, modulesXtpl); var bucketDom = [0]; var bucketConf = data.bucket ? data.bucket[0] : []; bucketConf && _.map(bucketConf.items, function(item, index){ bucketDom.push(Number(item.weight) / 10 + bucketDom[index]); }); var lastRes = []; lastRes.push('{{set(bucket=$page.bucket)}}'); lastRes.push('{{#if($env.url.query.wh_bucket)}}'); lastRes.push('{{set(bucket=0+$env.url.query.wh_bucket)}}'); lastRes.push('{{/if}}'); var _spmB = lastRes.join(""); bucketConf && _.map(bucketConf.items, function(item, index){ var piece = `{{#if((bucket>=${bucketDom[index]})&&(bucket<${bucketDom[index + 1]}))}}{{set($page.spm[1]="${data.spmB}/${bucketConf.items[index].version}")}}{{/if}}`; _spmB += piece; }); var lastReturn = yield pageJsonToXtpl._airRender(_spmB + xtpl, data, { wh_bucket: data.wh_bucket, wh_ttid: option.wh_ttid, wh_block: option.wh_block, wh_chunk: option.wh_chunk, dev: option.dev }); if(lastReturn.success){ if(pageJsonToXtpl.type !== 'rx-bundle'){ lastReturn.content = lastReturn.content.replace('</body>','<script>var ___ABS=' + JSON.stringify(data) + '</script>'); } } return lastReturn; }, _airRender: function*(xtpl,data, option){ var airParams = yield air.params(xtpl, data, option); var queryParams = []; if(option.wh_ttid){ if(option.wh_ttid === 'native'){ queryParams.push('wh_ttid=native'); }else{ queryParams.push('wh_ttid=block'); } } //老的区块逻辑,以后需要去掉 if(option.wh_block){ queryParams.push('wh_block=true'); } //区块逻辑 if(option.wh_chunk){ queryParams.push('wh_chunk=true'); } if(option.wh_bucket){ queryParams.push('wh_bucket=' + option.wh_bucket); } var result = yield air.render(pageJsonToXtpl.airPath + '?' + queryParams.join('&'), airParams); if(result.success){ // 资源处理 分别对应到lib/{renderType}/assets_dule进行处理 var AssetsDule = require(path.join(__dirname, 'lib', pageJsonToXtpl.type, 'assets_dule')); if(AssetsDule && AssetsDule.afterAirRenderContentReplace){ result.content = yield AssetsDule.afterAirRenderContentReplace(result.content,airParams,option); } } return result; }, /** * 框架的 cdn 地址 * * @param framework ['kimi'] * @returns //g.alicdn.com/kissy/kimi/6.0.1/kimi-min.js,kissy/mmd/6.1.0/mmd-min.js,kg/m-base/2.0.3/index.js */ frameworkCdnPath: function (framework) { return util.frameworkCdnPath(framework, pageJsonToXtpl.assets).then(function (data) { return data; }).catch((err) => { console.log('#frameworkCdnPath():', err); }); }, /** * 渲染 xtpl 内容 * @param xtplPath * @param data * @returns {Promise} */ renderXtpl: function (xtplPath, data) { return new Promise(function (resolve, reject) { //本地 xtpl 渲染 xtpl.renderFile(xtplPath, _.defaultsDeep(data, { JSON: JSON, commands: { getItemById: function(scope, option){ var items = option.params[0]; var id = option.params[1]; return items.filter(function(item) { return item.id == id; })[0]; } } }), function (error, content) { if (error) { reject(error); return false; } resolve({content: content, data: data}); }); }); }, /** * 渲染可以自运行的模块内容,包含js、css、use * @param data * @param option * @returns {*} */ renderModules: function*(data, option) { var xtpl = '{{ assets("css") }}{{ assets("js") }}'; xtpl += yield pageJsonToXtpl.renderModulesNoAir(data, option); return yield pageJsonToXtpl._airRender(xtpl,data, {}); }, /** * 输出 layout xtpl 内容 * @return {Promise} */ renderSolution: function (solution) { // 渲染类型 kimi rx etc. var renderType = pageJsonToXtpl.type; // 资源处理 分别对应到lib/{renderType}/assets_dule进行处理 var AssetsDule = require(path.join(__dirname, 'lib', renderType, 'assets_dule')); // 模板 var xtplPath = path.join(__dirname, 'lib', renderType, util.fizXtplName('solution.xtpl',solution.terminalType)); //获取种子js return seed().then((seed)=>{ //获取框架js return pageJsonToXtpl.frameworkCdnPath(solution.assets, pageJsonToXtpl.assets).then((framework) => { var xtpl = fs.readFileSync(xtplPath).toString(); xtpl = AssetsDule.replaceSolutionAssets(xtpl, { isDev: !!solution.dev, framework: framework, seed: seed, terminalType: solution.terminalType, solutionAssets: solution.assets }); return xtpl; }) }); }, /** * 从页面json中获取solution信息 * @param data * @returns {*} */ solutionParams: function*(data) { //页面通用信息,比如使用的框架等 if (data.abs) { data.pageTemplate = data.abs; } var pageTemplate = data.pageTemplate; if (pageTemplate) { var solution = pageTemplate.solution; if (_.isArray(solution)) { solution = solution[0]; } else if (_.isObject(solution)) { solution = solution.default; } if (!solution) { console.log('缺少 solution 字段'); return false; } return solution; } return data.pageTemplate.solution; }, /** * 输出模块的 xtpl 内容,不使用 air 渲染 * abs 发布页面时使用 * @return {Promise} */ renderModulesNoAir: function (data, option) { //AB 处理 var bucket = data.layout && data.layout.bucket || []; bucket = data.bucket ? data.bucket : bucket; bucket = bucket || []; bucket = bucket[0] || {}; if(!bucket.id || !(bucket.items && bucket.items.length)){ return pageJsonToXtpl._renderModulesNoAir(data, option); } var bucketDom = [0]; bucket.items.forEach(function(item){ var lastDom = bucketDom[bucketDom.length -1]; bucketDom.push(parseInt(item.weight, 10)/10 + lastDom); }); var renderStack = []; bucket.items.forEach(function(item, index){ var modules = data.layout && data.layout.modules || data.modules; var newModules = []; // 去掉非当前的AB节点 modules.forEach(function(mod){ if(!pageJsonToXtpl._modIsBucket(mod.id, bucket.items)){ newModules.push(mod); return; } mod.id = Number(mod.id); item.instanceId = Number(item.instanceId); if(mod.id === item.instanceId){ mod.spmC = bucket.id; newModules.push(mod); return; } }); var newData = _.cloneDeep(data); newData.modules = newModules; if(newData.layout){ newData.layout.modules = newModules; } renderStack.push(pageJsonToXtpl._renderModulesNoAir(newData, option)); }); return Promise.all(renderStack).then(function(res){ var ret = ""; res.forEach(function(response, index){ ret += "{{#if((bucket>="+bucketDom[index]+")&&(bucket<"+bucketDom[index+1]+"))}}" + response + "{{/if}}"; }); return ret; }); }, _renderModulesNoAir: function(data, option){ if(!option) option = {}; data = _.cloneDeep(data); var modules = data.layout && data.layout.modules || data.modules; var layoutMap = data.layout && data.layout.map || {}; var offlineUrl = data.layout && data.layout.offlineUrl || ''; var xtplPath = path.join(__dirname, 'lib', pageJsonToXtpl.type, util.fizXtplName('mod.xtpl',option.terminalType)); var modsJson = []; _.forEach(modules, function (mod) { if(!mod.id){ mod.id = _.uniqueId(''); ///TODO 如果当前模块是AB 模块,则AB 两个模块的SPM 都是相同的 } mod.containerId = 'module_' + mod.id; mod.terminalType = option.terminalType; mod.spmC = mod.spmC || mod.id; modsJson.push(_.cloneDeep(mod)); //带有模块数据 线上环境才有 if(mod.sceneDataId){ mod.sceneData = `{{{$env.JSON.stringify($data['${mod.sceneDataId}'])}}}`; } }); modsJson = JSON.stringify(modsJson); //rx 不需要css var delCss = pageJsonToXtpl.type === 'rx' || pageJsonToXtpl.type === 'rx-bundle' || pageJsonToXtpl.type === 'weex'; //air 需要combo处理的入口文件 var require = air.requireCommand(modules,delCss); var offlineRedirect = ''; if(offlineUrl){ if(pageJsonToXtpl.type === 'rx-bundle'){ offlineRedirect = ';var offlineUrl = "'+offlineUrl+'";WindVane.call("Base", "openWindow", {url: offlineUrl}, function() {}, function() {});'; }else{ offlineRedirect = '{{redirect("'+offlineUrl+'")}}'; } } return pageJsonToXtpl.renderXtpl(xtplPath, { modules: modules, modsJson: modsJson, layoutMap: layoutMap, offlineRedirect: offlineRedirect }).then(function (result) { var content = result.content; //todo hack //xtemplate 存在bug,逃逸语法不支持 include() content = content.replace(/<\!--include\(/g, '{{include('); content = content.replace(/\)-->/g, ')}}'); return content + require; }); }, _modIsBucket: function(id, bucketConf){ var ret = false; bucketConf.forEach(function(conf){ if(String(conf.instanceId) === String(id)){ ret = true; } }); return ret; } };