babel-plugin-nornj-in-jsx
Version:
Make the NornJ work gracefully in the JSX environment
374 lines (336 loc) • 11.8 kB
JavaScript
const { default: nj, expression: n } = require('nornj/dist/nornj.common');
const njUtils = require('nornj/tools/utils');
const { locInfo } = require('./utils');
function buildAttrs(types, tagName, attrs, quasis, expressions, lastAttrStr, newContextData) {
const attrNames = Object.keys(attrs);
const exTagConfig = nj.extensionConfig[tagName] || {};
const newContext = exTagConfig.newContext;
const isCtxObject = nj.isObject(newContext);
const getDataFromProps = newContext && newContext.getDataFromProps;
const dataFromPropsExcept = getDataFromProps && getDataFromProps.except;
if (isCtxObject) {
Object.keys(newContext).forEach(k => {
if (k !== 'getDataFromProps') {
newContextData[k] = newContext[k];
}
});
if (getDataFromProps && !newContextData.data) {
newContextData.data = {};
}
}
if (attrNames.length) {
attrNames.forEach((attrName, i) => {
const attr = attrs[attrName];
let attrStr = lastAttrStr + (i == 0 ? '<n-' + tagName : '');
if (attr.type != 'JSXSpreadAttribute') {
attrStr = attrStr + ' ' + attrName + '=';
let isGetDataFromProps = false;
if (getDataFromProps) {
isGetDataFromProps = !dataFromPropsExcept ? true : dataFromPropsExcept.indexOf(attrName) < 0;
}
if (isCtxObject && isGetDataFromProps) {
newContextData.data[attrName] = [attrName, attrName];
}
if (isCtxObject && !isGetDataFromProps && newContext[attrName] != null) {
newContextData[attrName] = attr.value.value;
lastAttrStr += i == 0 ? '<n-' + tagName : '';
} else if (isCtxObject && !isGetDataFromProps && newContext.data && newContext.data[attrName] != null) {
const attrDatas = newContextData.data[attrName];
newContextData.data[attrName] = [Array.isArray(attrDatas) ? attrDatas[0] : attrDatas, attr.value.value];
lastAttrStr += i == 0 ? '<n-' + tagName : '';
} else if (!attr.value) {
lastAttrStr = attrStr.substr(0, attrStr.length - 1);
} else if (attr.value.type == 'JSXExpressionContainer') {
const expr = attr.value.expression;
if (types.isTemplateLiteral(expr) && exTagConfig.useExpressionInProps) {
if (expr.quasis.length === 1) {
lastAttrStr = attrStr + '"{{' + expr.quasis[0].value.cooked + '}}"';
} else {
expr.quasis.forEach((q, i) => {
if (i == 0) {
attrStr += '"{{' + q.value.cooked;
quasis.push(
types.TemplateElement({
raw: attrStr,
cooked: attrStr
})
);
} else if (i == expr.quasis.length - 1) {
lastAttrStr = q.value.cooked + '}}"';
} else {
quasis.push(
types.TemplateElement({
raw: q.value.cooked,
cooked: q.value.cooked
})
);
}
if (i < expr.quasis.length - 1) {
expr.expressions[i].noExpression = true;
expressions.push(expr.expressions[i]);
}
});
}
} else {
quasis.push(
types.TemplateElement({
raw: attrStr,
cooked: attrStr
})
);
expressions.push(expr);
lastAttrStr = '';
}
} else {
quasis.push(
types.TemplateElement({
raw: attrStr,
cooked: attrStr
})
);
expressions.push(attr.value);
lastAttrStr = '';
}
} else {
quasis.push(
types.TemplateElement({
raw: attrStr + ' ',
cooked: attrStr + ' '
})
);
attr.argument.isSpread = true;
expressions.push(attr.argument);
lastAttrStr = '';
}
});
} else {
lastAttrStr += '<n-' + tagName;
}
return lastAttrStr;
}
function _expressionPrefix(expr) {
if (expr.isAccessor) {
return '#';
} else if (expr.isSpread) {
return '...';
} else {
return '';
}
}
function getElName(types, expr) {
if (types.isJSXMemberExpression(expr)) {
return types.memberExpression(getElName(types, expr.object), getElName(types, expr.property));
} else {
return types.identifier(expr.name);
}
}
function getDirectiveExpression(types, expr) {
if (types.isBinaryExpression(expr)) {
return [...getDirectiveExpression(types, expr.left), ...getDirectiveExpression(types, expr.right)];
} else {
return [expr.loc ? expr : expr.value];
}
}
function getDirectiveMemberExpression(types, expr) {
if (types.isMemberExpression(expr)) {
return [...getDirectiveMemberExpression(types, expr.object), ...getDirectiveMemberExpression(types, expr.property)];
} else {
return [expr];
}
}
const CTX_DATA = 'data';
const CTX_GET_DATA = 'getData';
function createRenderTmpl(babel, quasis, expressions, opts, path, taggedName) {
const types = babel.types;
const isTmplFnS = taggedName === 'njs';
const isTmplFn = taggedName === 'nj' || taggedName === 'html' || isTmplFnS;
const expressionParamIdentifiers = new Map();
let tmplStr = '';
let paramCount = 0;
quasis.forEach((q, i) => {
tmplStr += q.value.cooked;
if (i < quasis.length - 1) {
const expr = expressions[i];
const paramName = '_njParam' + paramCount;
tmplStr +=
(expr.noExpression ? '' : '{{') + _expressionPrefix(expr) + paramName + (expr.noExpression ? '' : '}}');
if (expr.paramIdentifierName != null) {
expressionParamIdentifiers.set(paramName, expr.paramIdentifierName);
}
paramCount++;
}
});
const tmplKey = `${njUtils.uniqueKey(tmplStr)}_${locInfo(path)}`;
if (taggedName && !['tag', 'directive'].includes(taggedName)) {
let taggedTmplConfig = {};
switch (taggedName) {
case 'n':
taggedTmplConfig = { isExpression: true };
break;
case 's':
taggedTmplConfig = { isCss: true };
break;
}
tmplStr = Object.assign({ quasis: quasis.map(q => q.value.cooked) }, taggedTmplConfig);
}
const hasAst = ['tag', 'directive', 'n'].includes(taggedName);
let tmplFns, tmplAst;
tmplFns = nj.precompile(
tmplStr,
!isTmplFnS ? (opts.outputH != null ? opts.outputH : true) : false,
nj.createTmplRule(
opts.delimiters != null
? opts.delimiters
: !isTmplFnS
? {
start: '{',
end: '}',
comment: ''
}
: {}
),
hasAst
);
if (hasAst) {
tmplAst = tmplFns.ast;
tmplFns = tmplFns.fns;
}
const paramIdentifiers = new Set();
if (tmplAst) {
switch (taggedName) {
case 'tag':
tmplAst.content[0] &&
nj.each(tmplAst.content[0].params, attr => {
attr.props && _getExpressionParams([attr.props[0].prop], paramIdentifiers);
});
break;
case 'directive':
tmplAst.content[0].paramsEx &&
nj.each(tmplAst.content[0].paramsEx.content, attr => {
attr.content && _getExpressionParams([attr.content[0].content[0].props[0].prop], paramIdentifiers);
});
break;
default:
_getExpressionParams([tmplAst.content[0].content[0].props[0].prop], paramIdentifiers);
break;
}
}
const tmplObj = _buildTmplFns(tmplFns, tmplKey);
const tmplParams = [];
expressions.forEach((e, i) => {
if (quasis[i].isCloseTagPrefix) {
return;
}
let block;
if (e.isAccessor) {
const arrowFnParams = [];
const newCtxDatakeys = Object.keys(e.newContextData);
let newDatas;
if (newCtxDatakeys.length) {
const properties = newCtxDatakeys.map(k => {
if (k !== CTX_DATA) {
return types.objectProperty(types.Identifier(k), types.Identifier(e.newContextData[k]));
} else {
newDatas = e.newContextData[k];
return types.objectProperty(types.Identifier(CTX_GET_DATA), types.Identifier(CTX_GET_DATA));
}
});
if (newDatas) {
properties.push(types.objectProperty(types.Identifier(CTX_DATA), types.Identifier(CTX_DATA)));
}
arrowFnParams.push(types.objectPattern(properties));
}
const arrowFnContent = [];
if (newDatas) {
const declarations = [];
Object.keys(newDatas).forEach(k => {
let varName, dataName;
if (Array.isArray(newDatas[k])) {
varName = newDatas[k][1];
dataName = newDatas[k][0];
} else {
dataName = varName = newDatas[k];
}
declarations.push(
types.variableDeclarator(
types.Identifier(varName),
types.callExpression(types.Identifier(CTX_GET_DATA), [
types.stringLiteral(dataName),
types.Identifier(CTX_DATA)
])
)
);
});
declarations.length && arrowFnContent.push(types.variableDeclaration('const', declarations));
}
arrowFnContent.push(types.returnStatement(e));
block = types.ArrowFunctionExpression(arrowFnParams, types.blockStatement(arrowFnContent));
} else {
block = e;
}
tmplParams.push(types.objectProperty(types.identifier('_njParam' + i), block));
});
if (paramIdentifiers.size) {
paramIdentifiers.forEach(paramIdentifier => {
if (path.scope.hasBinding(paramIdentifier)) {
tmplParams.push(types.objectProperty(types.identifier(paramIdentifier), types.identifier(paramIdentifier)));
}
});
}
if (expressionParamIdentifiers.size) {
expressionParamIdentifiers.forEach((v, k) => {
if (path.scope.hasBinding(v)) {
tmplParams.push(types.objectProperty(types.identifier(`set${k}`), types.identifier(v)));
}
});
}
const renderFnParams = [types.identifier(tmplObj)];
if (tmplParams.length) {
renderFnParams.push(types.objectExpression(tmplParams));
}
if (!isTmplFn) {
renderFnParams.push(types.thisExpression());
}
return types.CallExpression(
types.memberExpression(
types.identifier('nj'),
types.identifier(!isTmplFn ? 'renderH' : taggedName === 'njs' ? 'buildRender' : 'buildRenderH')
),
renderFnParams
);
}
function _buildTmplFns(fns, tmplKey) {
let ret = '{\n';
ret += ` _njTmplKey: '${tmplKey}',\n`;
nj.each(fns, (v, k, i, l) => {
if (k.indexOf('_') != 0) {
ret += ' ' + k + ': ' + v.toString() + (i < l - 1 ? ',' : '') + '\n';
}
});
return ret + '}';
}
function _getExpressionParams(paramsAst, paramIdentifiers) {
paramsAst.forEach(pAst => {
if (
!pAst.isBasicType &&
pAst.name !== 'this' &&
!(pAst.name && pAst.name.startsWith('_njParam')) /* && !n`${pAst}.name.startsWith('_njParam')` */
) {
paramIdentifiers.add(pAst.name);
}
if (pAst.filters) {
pAst.filters.forEach(filter => {
if (filter.params) {
_getExpressionParams(filter.params, paramIdentifiers);
}
});
}
});
}
module.exports = {
buildAttrs,
getElName,
getDirectiveExpression,
getDirectiveMemberExpression,
createRenderTmpl
};