UNPKG

marko

Version:

UI Components + streaming, async, high performance, HTML templating for Node.js and the browser.

176 lines (144 loc) 4.63 kB
"use strict";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; }