okam-build
Version:
The build tool for Okam develop framework
278 lines (248 loc) • 7.89 kB
JavaScript
/**
* @file Transform quick app import tpl syntax
* @author sparklewhy@gmail.com
*/
;
const {file: fileUtil} = require('../../../../util/index');
const {parse: parseDom} = require('../../parser');
const {PLAIN_OBJECT_REGEXP} = require('../base/constant');
const {removeEmptyTextNode} = require('../base/helper');
const DATA_ATTR = ':data';
/**
* Get the imported template element based on the given template name.
*
* @inner
* @param {Object} tplFile the imported template file
* @param {string} name the template name to use
* @return {?Object}
*/
function getImportTemplateElement(tplFile, name) {
if (!name) {
return;
}
let ast = tplFile.ast;
if (!ast) {
ast = tplFile.ast = parseDom(tplFile.content.toString());
}
let children = ast.children;
for (let i = 0, len = children.length; i < len; i++) {
let node = children[i];
if (node.type === 'tag' && node.name === 'template') {
let {attribs: attrs} = node;
if (attrs && attrs.name === name) {
return node;
}
}
}
}
/**
* Find the import element
* Notice: currently, it does not support multiple import elements in template.
*
* @inner
* @param {Object} node the element node to use the import template
* @return {?Object}
*/
function findImportElement(node) {
let parent = node.parent;
if (!parent) {
return;
}
let children = parent.children;
let found;
children.some(item => {
if (item.type === 'tag' && item.name === 'import') {
found = item;
return true;
}
return false;
});
if (found) {
return found;
}
return findImportElement(parent);
}
/**
* Parse the used data information of the import template. If the data attribute
* name is not changed, it'll ignore.
*
* e.g.,
* input: a:b, c, ...obj
* output: {a: b}
*
* @inner
* @param {string} value the data value
* @return {?Object}
*/
function parseTplDataInfo(value) {
value = value.substring(1, value.length - 1);
let items = value.split(',');
let dataObjAttrs = {};
items.forEach(item => {
let colonIdx = item.indexOf(':');
if (colonIdx !== -1) {
let key = item.substring(0, colonIdx).trim();
let value = item.substr(colonIdx + 1).trim();
key === value || (dataObjAttrs[key] = value);
}
});
return Object.keys(dataObjAttrs).length ? dataObjAttrs : null;
}
/**
* Replace template variable using the new data variable name
*
* @inner
* @param {string} value the value to update
* @param {Object} data the data variable name map, key is the old variable name,
* value is the new variable name
* @return {string} return the updated value
*/
function replaceTemplateVariable(value, data) {
return value.replace(/\{\{(.+)\}\}/g, (match, varName) => {
varName = varName.trim();
let newVarName = data[varName];
if (newVarName) {
return `{{${newVarName}}}`;
}
return match;
});
}
/**
* Replace element attribute bind data variable using the new data variable name
*
* @inner
* @param {Object} attrs the attributes to update
* @param {Object} data the data variable name map, key is the old variable name,
* value is the new variable name
* @return {Array} return the updated attributes
*/
function replaceElementAttributeVariable(attrs, data) {
let newAttrs = Object.assign({}, attrs);
attrs && Object.keys(attrs).forEach(k => {
let value = attrs[k];
if (k.startsWith(':')) {
// replace okam data variable
let newDataName = data[value];
newDataName && (newAttrs[k] = newDataName);
}
else {
value = replaceTemplateVariable(value, data);
newAttrs[k] = value;
}
});
return newAttrs;
}
/**
* Update the inline template data variable name
*
* @inner
* @param {Object} element the element to update
* @param {Object} data the data variable name map, key is the old variable name,
* value is the new variable name
* @return {Array} return the updated element nodes
*/
function updateTemplateDataVariableName(element, data) {
let {children} = element;
let result = [];
children && children.forEach(item => {
let {type} = item.type;
let newItem = Object.assign({}, item);
if (type === 'tag') {
let {attribs: attrs, children} = item;
newItem.attribs = replaceElementAttributeVariable(attrs, data);
if (children) {
let newChildren = updateTemplateDataVariableName(item, data);
newChildren && (newItem.children = newChildren);
}
}
else if (type === 'text') {
let {data: textValue} = item;
textValue = replaceTemplateVariable(textValue, data);
newItem.data = textValue;
}
result.push(newItem);
});
return result;
}
/**
* Inline import template element, and update the inline template data variable
* e.g.,
* <view>
* <import src="a.tpl" />
* <tpl is="xx" data={{a: b}} />
* <view>others</view>
* </view>
*
* a.tpl template:
* <view>{{a}}</view>
*
* output:
* <view>
* <view>{{b}}</view>
* <view>others</view>
* </view>
*
* @param {Object} element the tpl element that uses the imported template
* @param {Object} tplOpts the template transformation options
* @param {Object=} opts the plugin options
*/
module.exports = function (element, tplOpts, opts) {
let {logger, file, getFileByFullPath} = tplOpts;
let importTplElement = findImportElement(element);
if (!importTplElement) {
logger.error(
file.path,
'cannot find the import template element'
);
return;
}
// Get the import template file
let {attribs: attrs} = importTplElement;
let tplPath = attrs && attrs.src;
if (!tplPath) {
logger.error(file.path, 'import element missing `src` information');
return;
}
let refTplFile = getFileByFullPath(
fileUtil.getFullPath(tplPath, file.fullPath)
);
if (!refTplFile) {
logger.error(file.path, 'imported template is not found', tplPath);
return;
}
// do not release the reference template file
refTplFile.release = false;
// inline the template
let refTplName = element.attribs && element.attribs.is;
let inlineTplElement = getImportTemplateElement(refTplFile, refTplName);
if (!inlineTplElement) {
logger.error(file.path, 'imported template name is not found:', refTplName);
return;
}
// update template data variable name
let toAddElements = inlineTplElement.children;
if (attrs.hasOwnProperty(DATA_ATTR)) {
let dataValue = attrs[DATA_ATTR];
if (typeof dataValue === 'string') {
dataValue = dataValue.trim();
if (PLAIN_OBJECT_REGEXP.test(dataValue)) {
let dataNames = parseTplDataInfo(dataValue);
toAddElements = dataNames && updateTemplateDataVariableName(
inlineTplElement, dataNames
);
}
}
}
// add remove flag, remove <import src="xxx" /> element
importTplElement.removed = true;
removeEmptyTextNode(importTplElement.prev);
// add remove flag, remove <tpl is="xx" data="xx" /> element
element.removed = true;
removeEmptyTextNode(element.prev, element.next);
// insert inline template elements
let children = element.parent.children;
let findInsertIdx = children.indexOf(element);
children.splice(findInsertIdx + 1, 0, ...toAddElements);
// notify visitor node structure change
this.nodeChange();
};