marko
Version:
UI Components + streaming, async, high performance, HTML templating for Node.js and the browser.
176 lines (144 loc) • 4.63 kB
JavaScript
;exports.__esModule = true;exports.getKeyManager = getKeyManager;exports.hasAutoKey = hasAutoKey;exports.hasUserKey = hasUserKey;var _compiler = require("@marko/compiler");
var _babelUtils = require("@marko/compiler/babel-utils");
const KeyManagerLookup = new WeakMap();
/**
* @returns {KeyManager}
*/
function getKeyManager(path) {
const { hub } = path;
return (
KeyManagerLookup.get(hub) ||
KeyManagerLookup.set(hub, new KeyManager()).get(hub));
}
function hasAutoKey(path) {
const key = path.get("key").node;
return Boolean(key && key._isAutoKey);
}
function hasUserKey(path) {
return path.node._hasUserKey;
}
class KeyManager {
constructor() {
this._nextKey = 0;
}
nextKey() {
return Object.assign(_compiler.types.stringLiteral(String(this._nextKey++)), {
_isAutoKey: true
});
}
resolveKey(path) {
if ((0, _babelUtils.isLoopTag)(path)) {
// Record the first child key if found under a loop.
const firstChildTag = path.
get("body.body").
find((child) => child.isMarkoTag());
const firstChildKey = firstChildTag && getUserKey(firstChildTag);
if (firstChildKey) {
const keyValueIdentifier = path.scope.generateUidIdentifier("keyValue");
firstChildTag.set("key", keyValueIdentifier);
firstChildTag.insertBefore(
_compiler.types.variableDeclaration("const", [
_compiler.types.variableDeclarator(keyValueIdentifier, firstChildKey)]
)
);
path.set("keyValue", keyValueIdentifier);
path.get("body").scope.crawl();
}
return;
}
if ((0, _babelUtils.isTransparentTag)(path)) {
return;
}
if (getUserKey(path)) {
return;
}
const parentKeyScope = getParentKeyScope(path);
const autoKey = path.get("key").node || this.nextKey();
path.set(
"key",
parentKeyScope ?
_compiler.types.binaryExpression("+", autoKey, parentKeyScope) :
autoKey
);
}
}
function getParentKeyScope(path) {
const parentLoopTag = path.findParent(_babelUtils.isLoopTag);
return parentLoopTag && getKeyScope(parentLoopTag);
}
function getKeyScope(path) {
const existingKeyScope = path.get("keyScope").node;
if (existingKeyScope) {
return existingKeyScope;
}
const keyScopeIdentifier = path.scope.generateUidIdentifier("keyScope");
const firstChildKeyValue = path.get("keyValue").node;
if (firstChildKeyValue) {
const valuePath = path.
get("body").
scope.getOwnBinding(firstChildKeyValue.name).path;
const declarationPath = valuePath.parentPath;
declarationPath.pushContainer(
"declarations",
_compiler.types.variableDeclarator(
keyScopeIdentifier,
(0, _babelUtils.normalizeTemplateString)`[${firstChildKeyValue}]`
)
);
} else {
let keyValue;
if (path.get("name.value").node === "for") {
if (path.node.attributes.some((attr) => attr.name === "of")) {
keyValue = path.node.body.params[1];
} else {
keyValue = path.node.body.params[0];
}
}
if (!keyValue) {
const keyValueIdentifier = path.scope.generateUidIdentifier("keyValue");
path.insertBefore(
_compiler.types.variableDeclaration("let", [
_compiler.types.variableDeclarator(keyValueIdentifier, _compiler.types.numericLiteral(0))]
)
);
keyValue = _compiler.types.updateExpression("++", keyValueIdentifier);
}
const parentKeyScope = getParentKeyScope(path);
if (parentKeyScope) {
keyValue = _compiler.types.binaryExpression("+", keyValue, parentKeyScope);
}
const keyScopeDecl = _compiler.types.variableDeclaration("const", [
_compiler.types.variableDeclarator(
keyScopeIdentifier,
(0, _babelUtils.normalizeTemplateString)`[${keyValue}]`
)]
);
if (path.node.attributeTags.length) {
path.unshiftContainer("attributeTags", keyScopeDecl);
} else {
path.get("body").unshiftContainer("body", keyScopeDecl);
}
}
path.set("keyScope", keyScopeIdentifier);
return keyScopeIdentifier;
}
function getUserKey(path) {
if (hasAutoKey(path)) {
return undefined;
}
let key = path.get("key").node;
if (key === undefined) {
const keyAttr = path.
get("attributes").
find((attr) => attr.get("name").node === "key");
if (keyAttr) {
key = (0, _babelUtils.normalizeTemplateString)`@${keyAttr.get("value").node}`;
path.node._hasUserKey = true;
keyAttr.remove();
} else {
key = null;
}
path.set("key", key);
}
return key;
}