marko
Version:
UI Components + streaming, async, high performance, HTML templating for Node.js and the browser.
260 lines (218 loc) • 6.92 kB
JavaScript
;var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");exports.__esModule = true;exports.buildEventHandlerArray = buildEventHandlerArray;exports.evaluateAttr = evaluateAttr;exports.getAttrs = getAttrs;var _compiler = require("@marko/compiler");
var _babelUtils = require("@marko/compiler/babel-utils");
var _classValue = _interopRequireDefault(require("marko/src/runtime/helpers/class-value"));
var _styleValue = _interopRequireDefault(require("marko/src/runtime/helpers/style-value"));
function getAttrs(path, preserveNames, isAttrTag) {
const { node } = path;
const {
extra,
attributes,
attributeTags,
body: { body, params }
} = node;
const attrsLen = attributes.length;
const childLen = body.length;
const properties = [];
const targetObjects = {};
const tagDef = (0, _babelUtils.getTagDef)(path);
const foundProperties = {};
const hasAttributeTags = !!attributeTags.length;
const isTagsAPI = findRootTag(path)?.node.extra?.featureType === "tags";
const renderBodyKey = isTagsAPI ? "content" : "renderBody";
for (let i = 0; i < attrsLen; i++) {
const { name, value } = attributes[i];
if (name) {
const attrDef = tagDef && tagDef.getAttribute(name);
let targetProperties = properties;
let targetProperty = name;
let preserveName = preserveNames;
if (attrDef) {
if (attrDef.targetProperty) {
const key = attrDef.targetProperty;
preserveName =
attrDef.preserveName !== false && attrDef.removeDashes !== true;
if (attrDef.dynamicAttribute) {
let targetObject = targetObjects[key];
if (!targetObject) {
properties.push(
_compiler.types.objectProperty(
_compiler.types.stringLiteral(key),
targetObject = targetObjects[key] = _compiler.types.objectExpression([])
)
);
}
targetProperties = targetObject.properties;
} else {
targetProperty = key;
}
} else if (
!preserveName && (
attrDef.preserveName === true || attrDef.removeDashes === false))
{
preserveName = true;
}
}
if (!preserveName) {
targetProperty = camelCase(targetProperty);
}
foundProperties[targetProperty] = true;
targetProperties.push(
_compiler.types.objectProperty(_compiler.types.stringLiteral(targetProperty), value)
);
} else {
mergeSpread(properties, value);
}
}
if (childLen && !hasAttributeTags) {
properties.push(
_compiler.types.objectProperty(
_compiler.types.stringLiteral(renderBodyKey),
_compiler.types.arrowFunctionExpression(
[_compiler.types.identifier("out"), ...params],
_compiler.types.blockStatement(body)
)
)
);
}
// Default parameters
tagDef &&
tagDef.forEachAttribute &&
tagDef.forEachAttribute((attr) => {
if (foundProperties[attr.name] || attr.dynamicAttribute) {
return;
}
if (attr.defaultValue !== undefined) {
properties.push(
_compiler.types.objectProperty(
_compiler.types.stringLiteral(attr.name),
_compiler.types.stringLiteral(attr.defaultValue + "")
)
);
} else if (attr.required) {
throw path.
get("name").
buildCodeFrameError(`The "${attr.name}" attribute is required.`);
}
});
let attrsObject =
properties.length === 0 ?
_compiler.types.nullLiteral() :
!hasAttributeTags &&
!isAttrTag &&
properties.length === 1 &&
_compiler.types.isSpreadElement(properties[0]) ?
properties[0].argument :
_compiler.types.objectExpression(properties);
if (hasAttributeTags) {
let attrTagBody = attributeTags;
if (body.length) {
attrTagBody = attrTagBody.concat(
_compiler.types.returnStatement(
_compiler.types.arrowFunctionExpression(
[_compiler.types.identifier("out"), ...params],
_compiler.types.blockStatement(body)
)
)
);
}
const attrTagFn = _compiler.types.arrowFunctionExpression(
[],
_compiler.types.blockStatement(attrTagBody)
);
attrsObject = _compiler.types.callExpression(
(0, _babelUtils.importNamed)(
path.hub.file,
"marko/src/runtime/helpers/attr-tag.js",
"i",
"marko_render_input"
),
properties.length === 0 ? [attrTagFn] : [attrTagFn, attrsObject]
);
}
return attrsObject;
}
function buildEventHandlerArray(path) {
const { handlers } = path.node;
if (!handlers) {
return [];
}
return [
_compiler.types.arrayExpression(
Object.entries(handlers).map(([eventName, { arguments: args, once }]) => {
const parts = [
_compiler.types.stringLiteral(eventName),
args[0],
_compiler.types.booleanLiteral(once)];
if (args.length > 1) {
parts.push(_compiler.types.arrayExpression(args.slice(1)));
}
return _compiler.types.arrayExpression(parts);
})
)];
}
function evaluateAttr(attr) {
const computed = (0, _babelUtils.computeNode)(attr.node.value);
if (computed) {
const { value } = computed;
switch (attr.node.name) {
case "class":
return {
value: (0, _classValue.default)(value)?.replace(/\s+/, " ").trim()
};
case "style":
return {
value: (0, _styleValue.default)(value)?.
replace(/\s+/, " ").
trim().
replace(/;$/, "")
};
}
if (value == null || value === false) {
return { value: undefined };
}
if (value === true) {
return { value: "" };
}
if (typeof value === "object") {
switch (value.toString) {
case Object.prototype.toString:
case Array.prototype.toString:
return { value: JSON.stringify(value) };
case RegExp.prototype.toString:
return { value: value.source };
}
}
return { value: value + "" };
}
}
function camelCase(string) {
return string.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
}
function findLastIndex(arr, check) {
for (let i = arr.length; i--;) {
if (check(arr[i])) {
return i;
}
}
return -1;
}
function mergeSpread(properties, value) {
if (_compiler.types.isObjectExpression(value)) {
for (const prop of value.properties) {
if (_compiler.types.isSpreadElement(prop)) {
mergeSpread(properties, prop.argument);
} else {
properties.push(prop);
}
}
} else {
properties.push(_compiler.types.spreadElement(value));
}
}
function findRootTag(tag) {
let cur = tag;
while ((0, _babelUtils.isAttributeTag)(cur)) {
cur = (0, _babelUtils.findParentTag)(cur);
}
return cur;
}