isv-page-json-to-xtpl
Version:
abs 将页面 json 转换成 html
438 lines (388 loc) • 13.2 kB
JavaScript
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;
}
};