From e01984c5a9b2a3b1a6be61411e616b4a10d81de3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20Asiel=20Guevara=20Casta=C3=B1eda?= Date: Tue, 9 Oct 2018 12:05:22 -0400 Subject: [PATCH 1/4] Added boolean attributes --- src/generators/bindings.ts | 63 +++++++++++++++++++++++--------------- src/index.ts | 2 +- tools/index.ts | 8 +++-- 3 files changed, 46 insertions(+), 27 deletions(-) diff --git a/src/generators/bindings.ts b/src/generators/bindings.ts index ecff7f9..82e14be 100644 --- a/src/generators/bindings.ts +++ b/src/generators/bindings.ts @@ -2,31 +2,46 @@ import { ctx } from '../utilities/context'; import { BlockAreas } from '../utilities/classes'; import { capitalize, filterParser } from '../utilities/tools'; +const isBooleanAttr = toMap(`allowfullscreen,async,autofocus,autoplay,checked,compact,controls, +declare,default,defaultchecked,defaultmuted,defaultselected,defer,disabled,enabled,formnovalidate, +hidden,indeterminate,inert,ismap,itemscope,loop,multiple,muted,nohref,noresize,noshade,novalidate, +nowrap,open,pauseonexit,readonly,required,reversed,scoped,seamless,selected,sortable,translate, +truespeed,typemustmatch,visible`); + export function genBind(variable: string, attr: string, expression: string, areas: BlockAreas, scope: string, type: string, classes: string) { - [scope] = scope.split(', '); - const globals = [variable, '_$ga']; - const bindFuncName = `bind${capitalize(attr)}${capitalize(variable)}`; - const isSelMulti = /select/.test(variable) && type === 'multiple'; - let params = areas.globals.length > 0 ? `, ${areas.globals.join(', ')}` : ''; - let bindExp = expression === null ? 'true' : `${ctx(filterParser(expression), scope, areas.globals.concat(globals))}`; - if (attr === 'class' || attr === 'style') { - bindExp = attr === 'style' ? - `_$bs(${bindExp})` : `(${classes ? `'${classes} ' + ` : ''}_$bc(${bindExp})).trim()`; - } - areas.variables.push(bindFuncName); + [scope] = scope.split(', '); + const globals = [variable, '_$ga']; + const bindFuncName = `bind${capitalize(attr)}${capitalize(variable)}`; + const isSelMulti = /select/.test(variable) && type === 'multiple'; + let params = areas.globals.length > 0 ? `, ${areas.globals.join(', ')}` : ''; + let bindExp = expression === null ? 'true' : `${ctx(filterParser(expression), scope, areas.globals.concat(globals))}`; + if (attr === 'class' || attr === 'style') { + bindExp = attr === 'style' ? + `_$bs(${bindExp})` : `(${classes ? `'${classes} ' + ` : ''}_$bc(${bindExp})).trim()`; + } + areas.variables.push(bindFuncName); areas.extras.push(`${bindFuncName} = (${scope}${params}) => (['${attr}', ${bindExp}]);`); let bindFunc = `${bindFuncName}(${scope}${params})`; - if (attr === 'value' && /input|select|textarea/.test(variable) && !/checkbox|radio/.test(type)) { - if (isSelMulti) { - areas.update.push(`_$bindMultiSelect(${variable}, ${bindFunc}[1]);`); - areas.unmount.push(`_$bindMultiSelect(${variable}, ${bindFunc}[1]);`); - } else { - areas.hydrate.push(`${variable}.value = _$toStr(${bindFunc}[1]);`); - } - } else if (attr === 'checked' && /input/.test(variable) && /checkbox|radio/.test(type)) { + if (attr === 'value' && /input|select|textarea/.test(variable) && !/checkbox|radio/.test(type)) { + if (isSelMulti) { + areas.update.push(`_$bindMultiSelect(${variable}, ${bindFunc}[1]);`); + areas.unmount.push(`_$bindMultiSelect(${variable}, ${bindFunc}[1]);`); + } else { + areas.hydrate.push(`${variable}.value = _$toStr(${bindFunc}[1]);`); + } + } else if (attr === 'checked' && /input/.test(variable) && /checkbox|radio/.test(type)) { areas.hydrate.push(`${variable}.checked = !!${bindFunc}[1];`); - } else { - areas.hydrate.push(`_$sa(${variable}, ${bindFunc});`); - } - !isSelMulti && areas.update.push(`_$bu(${variable}, ${bindFunc});`); -} \ No newline at end of file + } else if (isBooleanAttr(attr)) { + areas.update.push(`_$bba(${variable}, ${bindFunc});`); + areas.hydrate.push(`_$bba(${variable}, ${bindFunc});`); + } else { + areas.hydrate.push(`_$sa(${variable}, ${bindFunc});`); + } + !isSelMulti && !isBooleanAttr(attr) && areas.update.push(`_$bu(${variable}, ${bindFunc});`); +} + +function toMap(str: string) { + const map: Record = str + .split(',').reduce((map, val) => (map[val.trim()] = 1, map), {}); + return (val: string) => !!map[val]; +} diff --git a/src/index.ts b/src/index.ts index 7598a12..4814cbd 100644 --- a/src/index.ts +++ b/src/index.ts @@ -11,7 +11,7 @@ import { kebabToCamelCases, capitalize, camelToKebabCase } from './utilities/too const dest = `{ _$CompCtr, _$, _$d, _$a, _$add, _$remove, _$as, _$r, _$ce, _$cse, _$ct, _$bu, _$tu, _$nu, _$cm, _$sa, _$ga, _$al, _$ul, _$rl, _$bc, _$bs, _$f, _$e, _$is, _$ds, _$toStr, _$bindMultiSelect, _$gv, - _$setRef, _$noop, _$isType, _$isKey, _$bindGroup, _$cu, _$emptyElse, _$extends, _$updateMultiSelect }`; + _$setRef, _$noop, _$isType, _$isKey, _$bindGroup, _$cu, _$bba, _$emptyElse, _$extends, _$updateMultiSelect }`; const esDeps = `import ${dest} from 'trebor/tools';`; const cjsDeps = `const ${dest} = require('trebor/tools');`; const tools = readFileSync(join(__dirname, '../tools/index.js'), 'utf8'); diff --git a/tools/index.ts b/tools/index.ts index 57539e6..764c11f 100644 --- a/tools/index.ts +++ b/tools/index.ts @@ -508,8 +508,8 @@ export function _$ct(content?: string) { export function _$cm(content?: string) { return document.createComment(content || ''); } -export function _$sa(el: Element & { _value?: any }, attrOrBind: [string, any]) { - let [attr, value] = attrOrBind; +export function _$sa(el: Element & { _value?: any }, attrAndValue: [string, any]) { + let [attr, value] = attrAndValue; el.setAttribute(attr, _$toStr(value)); if (_$isValueAttr(attr) && !_$isStr(value)) el[PROP_MAP._] = value; } @@ -567,6 +567,10 @@ export function _$cu(block: { type: string } & ComponentTemplate, condition: Fun } return block; } +export function _$bba(el: Element, attrAndValue: [string, any]) { + let [attr, value, hasAttr] = attrAndValue.concat([el.hasAttribute(attrAndValue[0])]); + value == null || value === false ? hasAttr && el.removeAttribute(attr) : _$sa(el, [attr, '']); +} export function _$bu(el: (HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement) & { _value: any }, binding: [string, any]) { let [attr, value] = binding; let _value: string | boolean = attr === 'checked' ? !!value : _$toStr(value); From 0b5c6d372d3278c22774f186005503cb7b7169b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20Asiel=20Guevara=20Casta=C3=B1eda?= Date: Tue, 9 Oct 2018 12:07:27 -0400 Subject: [PATCH 2/4] Fixed some bugs on context assignment --- src/types/recast.d.ts | 6 +- src/utilities/context.ts | 869 ++++++++++++++++++++------------------- 2 files changed, 440 insertions(+), 435 deletions(-) diff --git a/src/types/recast.d.ts b/src/types/recast.d.ts index 0d23b04..53717b3 100644 --- a/src/types/recast.d.ts +++ b/src/types/recast.d.ts @@ -26,10 +26,13 @@ interface BlockStatementType extends NamedType { interface UnaryExpressionType extends NamedType { name: 'UnaryExpression' } +interface MemberExpressionType extends NamedType { + name: 'MemberExpression' +} interface UpdateExpressionType extends NamedType { name: 'UpdateExpression' } -interface AssignmentExpressionType extends NamedType { +interface AssignmentExpressionType extends NamedType { name: 'AssignmentExpression' } @@ -40,6 +43,7 @@ interface NamedTypes { IfStatement: IfStatementType; BlockStatement: BlockStatementType; UnaryExpression: UnaryExpressionType; + MemberExpression: MemberExpressionType; UpdateExpression: UpdateExpressionType; AssignmentExpression: AssignmentExpressionType; } diff --git a/src/utilities/context.ts b/src/utilities/context.ts index 92b6311..9b26a9b 100644 --- a/src/utilities/context.ts +++ b/src/utilities/context.ts @@ -4,545 +4,546 @@ import { Recast, Path, RecastAST } from '../types/recast'; import { builtin, browser, node as nodejs, amd } from 'globals'; import { analyze, Scope, Variable, Reference } from 'eslint-scope'; import { - Node, Expression, Identifier, AssignmentExpression, UpdateExpression, - UnaryExpression, BinaryExpression, BinaryOperator, Literal, UnaryOperator, - ExportDefaultDeclaration, BlockStatement, VariableDeclaration, ImportDeclaration, FunctionExpression + Node, Expression, Identifier, AssignmentExpression, UpdateExpression, + UnaryExpression, BinaryExpression, BinaryOperator, Literal, UnaryOperator, + ExportDefaultDeclaration, BlockStatement, VariableDeclaration, ImportDeclaration, FunctionExpression } from 'estree'; const { print, types, parse } = recast; const STATEMENT_TYPE = /(?:Statement|Declaration)$/; const LOOP_STATEMENT = /^(?:DoWhile|For|ForIn|ForOf|While)Statement$/; -const { Identifier, BlockStatement, Literal, UnaryExpression } = types.namedTypes; const { binaryExpression, literal, callExpression, memberExpression, identifier } = types.builders; +const { Identifier, BlockStatement, Literal, UnaryExpression, AssignmentExpression, IfStatement } = types.namedTypes; export function ctx(src: string, context: string, globals: string[] = []) { - if (src[0] === '{') src = `let _$o = ${src}`; - let ast = toAst(src); - visitExpressions(ast, context, globals.concat([context])); - return toCode(ast).code.replace('let _$o = ', ''); + if (src[0] === '{') src = `let _$o = ${src}`; + let ast = toAst(src); + visitExpressions(ast, context, globals.concat([context])); + return toCode(ast).code.replace('let _$o = ', ''); } export function toOptions(src: string) { - let ast = toAst(src, 'module'); - return visitOptions(ast); + let ast = toAst(src, 'module'); + return visitOptions(ast); } export function optimize(src: string) { - let ast = toAst(src, 'module'); - visitOptimize(ast); - return toCode(ast).code; -} - -function assignify(node: Node, ctx: string) { - if (isReplaceable(node)) { - if (types.namedTypes.AssignmentExpression.check(node)) { - let { operator, left, right } = node; - operator = operator.replace('=', '').trim(); - return settify(ctx, left, operator ? binaryExpression(operator, left, right) : right); - } else { - let { argument, operator } = node; - return settify(ctx, argument, binaryExpression(operator[0], argument, literal(1))); - } - } else { - return node; - } + let ast = toAst(src, 'module'); + visitOptimize(ast); + return toCode(ast).code; +} + +function assignify(node: Node, ctx: string, path: Path, globals: string[]) { + if (isReplaceable(node, path, globals)) { + if (AssignmentExpression.check(node)) { + let { operator, left, right } = node; + operator = operator.replace('=', '').trim(); + return settify(ctx, left, operator ? binaryExpression(operator, left, right) : right); + } else { + let { argument, operator } = node; + return settify(ctx, argument, binaryExpression(operator[0], argument, literal(1))); + } + } else { + return node; + } } function settify(ctx: string, left: Node, right: Expression) { - let callee = memberExpression(identifier(ctx), identifier('$set'), false); - let args = [literal(print(left).code), Identifier.check(right) ? toMember(right, ctx) : right]; - return callExpression(callee, args); + let callee = memberExpression(identifier(ctx), identifier('$set'), false); + let args = [literal(print(left).code), Identifier.check(right) ? toMember(right, ctx) : right]; + return callExpression(callee, args); } function visitExpressions(ast: RecastAST, ctx: string, globals: string[]) { - types.visit(ast, { - visitProperty(path) { - let { node } = path; - node.shorthand = false; - if (node.computed) { - if (canBeReplaced(node.key, path, globals)) { - replace('key', ctx, path); - } - this.traverse(path); - } - if (canBeReplaced(node.value, path, globals)) { - replace('value', ctx, path); - return false; - } - this.traverse(path); - }, - visitCallExpression(path) { - let { node } = path; - canBeReplaced(node.callee, path, globals) && replace('callee', ctx, path); - node.arguments.forEach((argument, i) => { - canBeReplaced(argument, path, globals) && replace('arguments', ctx, path, i); - }); - return this.traverse(path); - }, - visitSequenceExpression(path) { - let { node } = path; - node.expressions.forEach((exp, i) => { - canBeReplaced(exp, path, globals) && replace('expressions', ctx, path, i); - }); - return this.traverse(path); - }, - visitMemberExpression(path) { - let { node } = path; - if (canBeReplaced(node.object, path, globals)) { - replace('object', ctx, path); - if (node.computed && canBeReplaced(node.property, path, globals)) { - replace('property', ctx, path); - return false; - } - } - this.traverse(path); - }, - visitExpressionStatement(path) { - let { node } = path; - if (canBeReplaced(node.expression, path, globals)) { - replace('expression', ctx, path); - } - this.traverse(path); - }, - visitTemplateLiteral(path) { - let { node } = path; - node.expressions.forEach((expression, i) => { - canBeReplaced(expression, path, globals) && replace('expressions', ctx, path, i); - }); - this.traverse(path); - }, - visitArrayExpression(path) { - let { node } = path; - node.elements.forEach((element, i) => { - canBeReplaced(element, path, globals) && replace('elements', ctx, path, i); - }); - this.traverse(path); - }, - visitConditionalExpression(path) { - let { node } = path; - canBeReplaced(node.test, path, globals) && replace('test', ctx, path); - canBeReplaced(node.alternate, path, globals) && replace('alternate', ctx, path); - canBeReplaced(node.consequent, path, globals) && replace('consequent', ctx, path); - this.traverse(path); - }, - visitSpreadElement(path) { - this.visitUnaryExpression(path); - }, - visitUnaryExpression(path) { - let { node } = path; - canBeReplaced(node.argument, path, globals) && replace('argument', ctx, path); - this.traverse(path); - }, - visitBinaryExpression(path) { - this.visitLogicalExpression(path); - }, - visitLogicalExpression(path) { - let { node } = path; - canBeReplaced(node.left, path, globals) && replace('left', ctx, path); - canBeReplaced(node.right, path, globals) && replace('right', ctx, path); - this.traverse(path); - }, - visitUpdateExpression(path) { - this.visitAssignmentExpression(path); - }, - visitAssignmentExpression(path) { - let { node, parent } = path; - if (canBeReplaced('argument' in node ? node['argument'] : node.left, path, globals)) { - let newNode = assignify(node, ctx); - if ('expressions' in parent.node) { - let i = parent.node.expressions.indexOf(node); - node !== newNode && parent.get('expressions', i).replace(newNode); - } else { - node !== newNode && parent.get('expression').replace(newNode); - } - } - this.traverse(path); - } - }); - return ast; + types.visit(ast, { + visitProperty(path) { + let { node } = path; + node.shorthand = false; + if (node.computed) { + if (canBeReplaced(node.key, path, globals)) { + replace('key', ctx, path); + } + this.traverse(path); + } + if (canBeReplaced(node.value, path, globals)) { + replace('value', ctx, path); + return false; + } + this.traverse(path); + }, + visitCallExpression(path) { + let { node } = path; + canBeReplaced(node.callee, path, globals) && replace('callee', ctx, path); + node.arguments.forEach((argument, i) => { + canBeReplaced(argument, path, globals) && replace('arguments', ctx, path, i); + }); + return this.traverse(path); + }, + visitMemberExpression(path) { + let { node } = path; + if (canBeReplaced(node.object, path, globals)) { + replace('object', ctx, path); + if (node.computed && canBeReplaced(node.property, path, globals)) { + replace('property', ctx, path); + return false; + } + } + this.traverse(path); + }, + visitExpressionStatement(path) { + let { node } = path; + if (canBeReplaced(node.expression, path, globals)) { + replace('expression', ctx, path); + } + this.traverse(path); + }, + visitSequenceExpression(path) { + this.visitTemplateLiteral(path); + }, + visitTemplateLiteral(path) { + let { node } = path; + node.expressions.forEach((expression, i) => { + canBeReplaced(expression, path, globals) && replace('expressions', ctx, path, i); + }); + this.traverse(path); + }, + visitArrayExpression(path) { + let { node } = path; + node.elements.forEach((element, i) => { + canBeReplaced(element, path, globals) && replace('elements', ctx, path, i); + }); + this.traverse(path); + }, + visitConditionalExpression(path) { + let { node } = path; + canBeReplaced(node.test, path, globals) && replace('test', ctx, path); + canBeReplaced(node.alternate, path, globals) && replace('alternate', ctx, path); + canBeReplaced(node.consequent, path, globals) && replace('consequent', ctx, path); + this.traverse(path); + }, + visitSpreadElement(path) { + this.visitUnaryExpression(path); + }, + visitUnaryExpression(path) { + let { node } = path; + canBeReplaced(node.argument, path, globals) && replace('argument', ctx, path); + this.traverse(path); + }, + visitBinaryExpression(path) { + this.visitLogicalExpression(path); + }, + visitLogicalExpression(path) { + let { node } = path; + canBeReplaced(node.left, path, globals) && replace('left', ctx, path); + canBeReplaced(node.right, path, globals) && replace('right', ctx, path); + this.traverse(path); + }, + visitUpdateExpression(path) { + this.visitAssignmentExpression(path); + }, + visitAssignmentExpression(path) { + let { node, parent } = path; + let newNode = assignify(node, ctx, path, globals); + if ('expressions' in parent.node) { + let i = parent.node.expressions.indexOf(node); + node !== newNode && parent.get('expressions', i).replace(newNode); + } else { + node !== newNode && parent.get('expression').replace(newNode); + if (node === newNode && AssignmentExpression.check(node)) { + canBeReplaced(node.right, path, globals) && replace('right', ctx, path); + } + } + this.traverse(path); + } + }); + return ast; } function visitOptions(ast) { - let options = ''; - const imports: string[] = []; - types.visit(ast, { - visitImportDeclaration(path) { - return this.extract(path, node => { imports.push(print(node).code); }); - }, - visitExportDefaultDeclaration(path) { - return this.extract(path, ({ declaration }: ExportDefaultDeclaration) => { - options = print(declaration).code; - }); - }, - extract(path, extractor: (node: Node) => boolean) { - extractor(path.node); - path.prune(); - return false; - } - }); - let extras = print(ast).code; - return { imports, extras, options }; + let options = ''; + const imports: string[] = []; + types.visit(ast, { + visitImportDeclaration(path) { + return this.extract(path, node => { imports.push(print(node).code); }); + }, + visitExportDefaultDeclaration(path) { + return this.extract(path, ({ declaration }: ExportDefaultDeclaration) => { + options = print(declaration).code; + }); + }, + extract(path, extractor: (node: Node) => boolean) { + extractor(path.node); + path.prune(); + return false; + } + }); + let extras = print(ast).code; + return { imports, extras, options }; } function visitOptimize(ast: RecastAST) { - let iterations = 0; - let unused = findUnused(ast); - let isUnused = (node: Identifier) => !!unused.find(id => isSameIdentifier(id, node)); - while (iterations < 5) { - types.visit(ast, { - // Remove unused declarations - visitImportDeclaration(path) { - this.declarations(path, 'specifiers', 'local'); - }, - visitVariableDeclaration(path) { - this.declarations(path, 'declarations', 'id'); - }, - visitFunctionExpression(path) { - let { params } = path.node; - for (let i = params.length - 1; i >= 0; i--) { - isUnused(params[i]) ? path.get('params', i).prune() : (i = -1); - } - this.traverse(path); - }, - visitFunctionDeclaration(path) { - let { id } = path.node; - if (isUnused(id)) { - path.prune(); - return false; - } - this.visitFunctionExpression(path); - }, - // Remove empty block statements - visitSwitchStatement(path) { - let { node } = path; - return this.emptyStatement(path, !node.cases.length); - }, - visitWhileStatement(path) { - let { body } = path.node; - return this.emptyStatement(path, BlockStatement.check(body) && !(body).body.length); - }, - visitIfStatement(path) { - let { node } = path; - if (BlockStatement.check(node.alternate)) { - !(node.alternate).body.length && path.get('alternate').prune(); - } else if (types.namedTypes.IfStatement.check(node.alternate)) { - this.traverse(path); - } - let condition = BlockStatement.check(node.consequent) - && !(node.consequent).body.length && !node.alternate; - return this.emptyStatement(path, condition); - }, - visitTryStatement(path) { - let { node } = path; - if (BlockStatement.check(node.finalizer) && !node.finalizer.body.length) { - path.get('finalizer').prune(); - } - let condition = BlockStatement.check(node.block) && !node.block.body.length - && BlockStatement.check(node.handler.body) && !node.handler.body.body.length; - return this.emptyStatement(path, condition); - }, - // reduce expressions - visitUnaryExpression(path) { - let { node } = path; - if (Literal.check(node.argument) && node.operator !== 'void') { - path.replace(toLiteral(node)); - } - this.traverse(path); - }, - visitLogicalExpression(path) { - this.visitBinaryExpression(path); - }, - visitBinaryExpression(path) { - this.traverse(path); - let { node } = path; - if (Literal.check(node.left) && Literal.check(node.right)) { - path.replace(toLiteral(node)); - } - }, - // utils - declarations(path, list: string, prop: string) { - let node = path.node; - let items = node[list]; - for (let i = items.length - 1; i >= 0; i--) { - isUnused(items[i][prop]) && path.get(list, i).prune(); - } - !items.length && prop === 'local' && path.prune(); - this.traverse(path); - }, - emptyStatement(path, condition: boolean) { - if (condition) { - path.prune(); - return false; - } - this.traverse(path); - } - }); - unused = findUnused(ast); - iterations++; - if (!unused.length) { - iterations = 5; - } - } + let iterations = 0; + let unused = findUnused(ast); + let isUnused = (node: Identifier) => !!unused.find(id => isSameIdentifier(id, node)); + while (iterations < 5) { + types.visit(ast, { + // Remove unused declarations + visitImportDeclaration(path) { + this.declarations(path, 'specifiers', 'local'); + }, + visitVariableDeclaration(path) { + this.declarations(path, 'declarations', 'id'); + }, + visitFunctionExpression(path) { + let { params } = path.node; + for (let i = params.length - 1; i >= 0; i--) { + isUnused(params[i]) ? path.get('params', i).prune() : (i = -1); + } + this.traverse(path); + }, + visitFunctionDeclaration(path) { + let { id } = path.node; + if (isUnused(id)) { + path.prune(); + return false; + } + this.visitFunctionExpression(path); + }, + // Remove empty block statements + visitSwitchStatement(path) { + let { node } = path; + return this.emptyStatement(path, !node.cases.length); + }, + visitWhileStatement(path) { + let { body } = path.node; + return this.emptyStatement(path, BlockStatement.check(body) && !(body).body.length); + }, + visitIfStatement(path) { + let { node } = path; + if (BlockStatement.check(node.alternate)) { + !(node.alternate).body.length && path.get('alternate').prune(); + } else if (IfStatement.check(node.alternate)) { + this.traverse(path); + } + let condition = BlockStatement.check(node.consequent) + && !(node.consequent).body.length && !node.alternate; + return this.emptyStatement(path, condition); + }, + visitTryStatement(path) { + let { node } = path; + if (BlockStatement.check(node.finalizer) && !node.finalizer.body.length) { + path.get('finalizer').prune(); + } + let condition = BlockStatement.check(node.block) && !node.block.body.length + && BlockStatement.check(node.handler.body) && !node.handler.body.body.length; + return this.emptyStatement(path, condition); + }, + // reduce expressions + visitUnaryExpression(path) { + let { node } = path; + if (Literal.check(node.argument) && node.operator !== 'void') { + path.replace(toLiteral(node)); + } + this.traverse(path); + }, + visitLogicalExpression(path) { + this.visitBinaryExpression(path); + }, + visitBinaryExpression(path) { + this.traverse(path); + let { node } = path; + if (Literal.check(node.left) && Literal.check(node.right)) { + path.replace(toLiteral(node)); + } + }, + // utils + declarations(path, list: string, prop: string) { + let node = path.node; + let items = node[list]; + for (let i = items.length - 1; i >= 0; i--) { + isUnused(items[i][prop]) && path.get(list, i).prune(); + } + !items.length && prop === 'local' && path.prune(); + this.traverse(path); + }, + emptyStatement(path, condition: boolean) { + if (condition) { + path.prune(); + return false; + } + this.traverse(path); + } + }); + unused = findUnused(ast); + iterations++; + if (!unused.length) { + iterations = 5; + } + } } function toLiteral(node: Node) { - if (UnaryExpression.check(node)) { - let { operator, argument } = node; - return literal(evalUnary(operator, (argument).value)); - } else { - let { operator, left, right } = node; - return literal(evalBinary(operator, (left).value, (right).value)); - } + if (UnaryExpression.check(node)) { + let { operator, argument } = node; + return literal(evalUnary(operator, (argument).value)); + } else { + let { operator, left, right } = node; + return literal(evalBinary(operator, (left).value, (right).value)); + } } function evalUnary(operator: UnaryOperator, argument: any) { - let evaluate = `${/typeof|delete/.test(operator) ? `${operator} ` : operator}${toValue(argument)}`; - return eval(evaluate); + let evaluate = `${/typeof|delete/.test(operator) ? `${operator} ` : operator}${toValue(argument)}`; + return eval(evaluate); } function evalBinary(operator: BinaryOperator, left: any, right: any) { - let evaluate = `${toValue(left)}${operator}${toValue(right)}`; - return eval(evaluate); + let evaluate = `${toValue(left)}${operator}${toValue(right)}`; + return eval(evaluate); } function toValue(value: any) { - return typeof value === 'string' ? `'${value}'` : value; + return typeof value === 'string' ? `'${value}'` : value; } function findUnused({ program }: RecastAST) { - types.visit(program, { - visitNode(path) { - if (path.node.type !== 'Program') path.node['parent'] = path.parent.node; - this.traverse(path); - } - }); - let scopeManager = analyze(program, { sourceType: 'module', ecmaVersion: 10 }); - return collectUnuseds(scopeManager.acquire(program), []).reduce((result, variable) => { - if (variable.references.length) { - result.push(...variable.references.map(({ identifier: id, resolved }) => { - return id['parent'].type === 'CallExpression' ? resolved.defs[0].name : id; - })); - } else { - result.push(...variable.defs.map(d => d.name)); - } - return result; - }, []); + types.visit(program, { + visitNode(path) { + if (path.node.type !== 'Program') path.node['parent'] = path.parent.node; + this.traverse(path); + } + }); + let scopeManager = analyze(program, { sourceType: 'module', ecmaVersion: 10 }); + return collectUnuseds(scopeManager.acquire(program), []).reduce((result, variable) => { + if (variable.references.length) { + result.push(...variable.references.map(({ identifier: id, resolved }) => { + return id['parent'].type === 'CallExpression' ? resolved.defs[0].name : id; + })); + } else { + result.push(...variable.defs.map(d => d.name)); + } + return result; + }, []); } function isInLoop(node: Node) { - while (node && !types.namedTypes.Function.check(node)) { - if (LOOP_STATEMENT.test(node.type)) return true; - node = node['parent']; - } - return false; + while (node && !types.namedTypes.Function.check(node)) { + if (LOOP_STATEMENT.test(node.type)) return true; + node = node['parent']; + } + return false; } function getUpperFunction(node: Node) { - while (node) { - if (types.namedTypes.Function.check(node)) return node; - node = node['parent']; - } - return null; + while (node) { + if (types.namedTypes.Function.check(node)) return node; + node = node['parent']; + } + return null; } function getRhsNode(ref: Reference, prevRhsNode: Node) { - const id = ref.identifier; - const parent = id['parent']; - const granpa = parent.parent; - const refScope = ref.from.variableScope; - const varScope = ref.resolved['scope'].variableScope; - const canBeUsedLater = refScope !== varScope || isInLoop(id); - - if (prevRhsNode && isInside(id, prevRhsNode)) { - return prevRhsNode; - } - if (parent.type === 'AssignmentExpression' && granpa.type === 'ExpressionStatement' && - id === parent.left && !canBeUsedLater - ) { - return parent.right; - } - return null; + const id = ref.identifier; + const parent = id['parent']; + const granpa = parent.parent; + const refScope = ref.from.variableScope; + const varScope = ref.resolved['scope'].variableScope; + const canBeUsedLater = refScope !== varScope || isInLoop(id); + + if (prevRhsNode && isInside(id, prevRhsNode)) { + return prevRhsNode; + } + if (parent.type === 'AssignmentExpression' && granpa.type === 'ExpressionStatement' && + id === parent.left && !canBeUsedLater + ) { + return parent.right; + } + return null; } function isStorableFunction(funcNode: Identifier, rhsNode: Node) { - let node = funcNode; - let parent: Node = funcNode['parent']; - - while (parent && isInside(parent, rhsNode)) { - switch (parent.type) { - case 'SequenceExpression': - if (parent.expressions[parent.expressions.length - 1] !== node) { - return false; - } - break; - case 'CallExpression': - case 'NewExpression': - return parent.callee !== node; - case 'AssignmentExpression': - case 'TaggedTemplateExpression': - case 'YieldExpression': - return true; - default: - if (STATEMENT_TYPE.test(parent.type)) { - return true; - } - } - node = parent; - parent = parent['parent']; - } - return false; + let node = funcNode; + let parent: Node = funcNode['parent']; + + while (parent && isInside(parent, rhsNode)) { + switch (parent.type) { + case 'SequenceExpression': + if (parent.expressions[parent.expressions.length - 1] !== node) { + return false; + } + break; + case 'CallExpression': + case 'NewExpression': + return parent.callee !== node; + case 'AssignmentExpression': + case 'TaggedTemplateExpression': + case 'YieldExpression': + return true; + default: + if (STATEMENT_TYPE.test(parent.type)) { + return true; + } + } + node = parent; + parent = parent['parent']; + } + return false; } function isExported(variable: Variable) { - const definition = variable.defs[0]; - if (definition) { - let node: Node = definition.node; - if (node.type === 'VariableDeclarator') { - node = node['parent']; - } else if (definition.type === 'Parameter') { - return false; - } - return node['parent'].type.indexOf('Export') === 0; - } - return false; + const definition = variable.defs[0]; + if (definition) { + let node: Node = definition.node; + if (node.type === 'VariableDeclarator') { + node = node['parent']; + } else if (definition.type === 'Parameter') { + return false; + } + return node['parent'].type.indexOf('Export') === 0; + } + return false; } function isReadRef(ref: Reference) { - return ref.isRead(); + return ref.isRead(); } function isSelfReference(ref: Reference, nodes: Node[]) { - let scope = ref.from; - while (scope) { - if (nodes.indexOf(scope.block) >= 0) { - return true; - } - scope = scope.upper; - } - return false; + let scope = ref.from; + while (scope) { + if (nodes.indexOf(scope.block) >= 0) { + return true; + } + scope = scope.upper; + } + return false; } function isInside(inner: Identifier, outer: Node) { - return (inner.range[0] >= outer.range[0] && inner.range[1] <= outer.range[1]); + return (inner.range[0] >= outer.range[0] && inner.range[1] <= outer.range[1]); } function isInsideOfStorableFunction(id, rhsNode) { - const funcNode = getUpperFunction(id); - return (funcNode && isInside(funcNode, rhsNode) && isStorableFunction(funcNode, rhsNode)); + const funcNode = getUpperFunction(id); + return (funcNode && isInside(funcNode, rhsNode) && isStorableFunction(funcNode, rhsNode)); } function isReadForItself(ref: Reference, rhsNode: Node) { - const id = ref.identifier; - const parent = id['parent']; - const isExpStmt = parent.parent.type === 'ExpressionStatement'; - return ref.isRead() && ( - (parent.type === 'AssignmentExpression' && isExpStmt && parent.left === id) || - (parent.type === 'UpdateExpression' && isExpStmt) || - (rhsNode && isInside(id, rhsNode) && !isInsideOfStorableFunction(id, rhsNode)) - ); + const id = ref.identifier; + const parent = id['parent']; + const isExpStmt = parent.parent.type === 'ExpressionStatement'; + return ref.isRead() && ( + (parent.type === 'AssignmentExpression' && isExpStmt && parent.left === id) || + (parent.type === 'UpdateExpression' && isExpStmt) || + (rhsNode && isInside(id, rhsNode) && !isInsideOfStorableFunction(id, rhsNode)) + ); } function isForInRef(ref: Reference) { - let target: Node = ref.identifier['parent']; - if (target.type === 'VariableDeclarator') target = target['parent'].parent; - if (target.type !== 'ForInStatement') return false; - target = target.body.type === 'BlockStatement' ? target.body.body[0] : target.body; - if (!target) return false; - return target.type === 'ReturnStatement'; + let target: Node = ref.identifier['parent']; + if (target.type === 'VariableDeclarator') target = target['parent'].parent; + if (target.type !== 'ForInStatement') return false; + target = target.body.type === 'BlockStatement' ? target.body.body[0] : target.body; + if (!target) return false; + return target.type === 'ReturnStatement'; } function isUsedVar({ defs, references }: Variable) { - const fNodes = defs.filter(def => def.type === 'FunctionName') - .map(def => def.node), isFuncDef = fNodes.length > 0; - let rhsNode: Node = null; + const fNodes = defs.filter(def => def.type === 'FunctionName') + .map(def => def.node), isFuncDef = fNodes.length > 0; + let rhsNode: Node = null; - return references.some(ref => { - if (isForInRef(ref)) return true; - const forItself = isReadForItself(ref, rhsNode); - rhsNode = getRhsNode(ref, rhsNode); - return (isReadRef(ref) && !forItself && - !(isFuncDef && isSelfReference(ref, fNodes))); - }); + return references.some(ref => { + if (isForInRef(ref)) return true; + const forItself = isReadForItself(ref, rhsNode); + rhsNode = getRhsNode(ref, rhsNode); + return (isReadRef(ref) && !forItself && + !(isFuncDef && isSelfReference(ref, fNodes))); + }); } function collectUnuseds(scope: Scope, unusedVars: Variable[]) { - let i, l; - const { variables, childScopes } = scope; - for (i = 0, l = variables.length; i < l; ++i) { - const variable = variables[i]; - if (scope.type === 'class' && (scope.block).id === variable.identifiers[0]) { - continue; - } - if (scope.functionExpressionScope || variable['eslintUsed']) { - continue; - } - if (scope.type === 'function' && variable.name === 'arguments' && variable.identifiers.length === 0) { - continue; - } - const def = variable.defs[0]; - if (def && def.type === 'Parameter') { - const { type, kind } = def.node['parent']; - if ((type === 'Property' || type === 'MethodDefinition') && kind === 'set') { - continue; - } - } - !isUsedVar(variable) && !isExported(variable) && unusedVars.push(variable); - } - for (i = 0, l = childScopes.length; i < l; ++i) { - collectUnuseds(childScopes[i], unusedVars); - } - return unusedVars; + let i, l; + const { variables, childScopes } = scope; + for (i = 0, l = variables.length; i < l; ++i) { + const variable = variables[i]; + if (scope.type === 'class' && (scope.block).id === variable.identifiers[0]) { + continue; + } + if (scope.functionExpressionScope || variable['eslintUsed']) { + continue; + } + if (scope.type === 'function' && variable.name === 'arguments' && variable.identifiers.length === 0) { + continue; + } + const def = variable.defs[0]; + if (def && def.type === 'Parameter') { + const { type, kind } = def.node['parent']; + if ((type === 'Property' || type === 'MethodDefinition') && kind === 'set') { + continue; + } + } + !isUsedVar(variable) && !isExported(variable) && unusedVars.push(variable); + } + for (i = 0, l = childScopes.length; i < l; ++i) { + collectUnuseds(childScopes[i], unusedVars); + } + return unusedVars; } function toAst(src: string, sourceType: string = 'script') { - return parse(src, { - parser: { - parse(source: string, options) { - return parser(source, { ...options, ...{ ecmaVersion: 10, sourceType, range: true, loc: true } }); - } - } - }); + return parse(src, { + parser: { + parse(source: string, options) { + return parser(source, { ...options, ...{ ecmaVersion: 10, sourceType, range: true, loc: true } }); + } + } + }); } function toMember(node: Expression, ctx: string) { - return memberExpression(identifier(ctx), node, false); + return memberExpression(identifier(ctx), node, false); } function toCode(ast: RecastAST) { - return print(ast, { tabWidth: 2, quote: 'single' }); + return print(ast, { tabWidth: 2, quote: 'single' }); } function canBeReplaced(node: Node, path, globals) { - return Identifier.check(node) && !isDeclared(node, path) && !isGlobal(node, globals); + return Identifier.check(node) && !isDeclared(node, path) && !isGlobal(node, globals); } function replace(name: string, ctx: string, path, position?: number) { - if (typeof position === 'number') { - let node = path.get(name, position).value; - path.get(name, position).replace(toMember(node, ctx)); - } else { - let node = path.get(name).value; - path.get(name).replace(toMember(node, ctx)); - } + if (typeof position === 'number') { + let node = path.get(name, position).value; + path.get(name, position).replace(toMember(node, ctx)); + } else { + let node = path.get(name).value; + path.get(name).replace(toMember(node, ctx)); + } } function isDeclared(node: Identifier, { scope }: Path) { - return scope.declares(node.name); + return scope.declares(node.name); } function isGlobal(node: Identifier, globals = []) { - let { name } = node; - let isGlob = name in builtin || name in browser || name in nodejs || name in amd; - return isGlob || !!~globals.indexOf(name); + let { name } = node; + let isGlob = name in builtin || name in browser || name in nodejs || name in amd; + return isGlob || !!~globals.indexOf(name); } -function isReplaceable(node: Node) { - return Identifier.check(node) ? - true : types.namedTypes.AssignmentExpression.check(node) ? - isReplaceable((node).left) : types.namedTypes.UpdateExpression.check(node) ? - isReplaceable((node).argument) : isReplaceable((node).object); +function isReplaceable(node: Node, path: Path, globals: string[]) { + let checks = types.namedTypes; + return checks.AssignmentExpression.check(node) ? + isReplaceable((node).left, path, globals) : + checks.UpdateExpression.check(node) ? + isReplaceable((node).argument, path, globals) : + checks.MemberExpression.check(node) ? + isReplaceable((node).object, path, globals) : + canBeReplaced(node, path, globals); } function isSameIdentifier(a: Identifier, b: Identifier) { - return a.name === b.name && JSON.stringify(a.loc) === JSON.stringify(b.loc); + return a.name === b.name && JSON.stringify(a.loc) === JSON.stringify(b.loc); } From 28b70272a440654084d93b741e1198c58352242c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20Asiel=20Guevara=20Casta=C3=B1eda?= Date: Tue, 9 Oct 2018 12:30:06 -0400 Subject: [PATCH 3/4] Included checkboxes and radios to the new boolean code generation --- src/generators/bindings.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/generators/bindings.ts b/src/generators/bindings.ts index 82e14be..e1e467d 100644 --- a/src/generators/bindings.ts +++ b/src/generators/bindings.ts @@ -29,8 +29,6 @@ export function genBind(variable: string, attr: string, expression: string, area } else { areas.hydrate.push(`${variable}.value = _$toStr(${bindFunc}[1]);`); } - } else if (attr === 'checked' && /input/.test(variable) && /checkbox|radio/.test(type)) { - areas.hydrate.push(`${variable}.checked = !!${bindFunc}[1];`); } else if (isBooleanAttr(attr)) { areas.update.push(`_$bba(${variable}, ${bindFunc});`); areas.hydrate.push(`_$bba(${variable}, ${bindFunc});`); From 54c8c571e064fd4e8263e794c6a81e8a401039b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20Asiel=20Guevara=20Casta=C3=B1eda?= Date: Tue, 9 Oct 2018 12:30:31 -0400 Subject: [PATCH 4/4] Bump to version 0.2.3 --- CHANGELOG.md | 4 ++++ package-lock.json | 2 +- package.json | 2 +- spec/components/bind.umd.js | 30 +++++++++++++++++------------- spec/components/condition.umd.js | 2 +- spec/components/html.umd.js | 6 +++--- spec/components/init.umd.js | 2 +- spec/components/loop.umd.js | 6 +++--- 8 files changed, 31 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c4401e6..3f42e94 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +0.2.3 +- Added ability to add or remove boolean attributes. +- Fixed some bugs on context assignment with Sequence and Assignment expressions. + 0.2.2 - Added Sequence expression to context generation. - Fixed missing attributes generating elements in slots. diff --git a/package-lock.json b/package-lock.json index 2928845..cc5a254 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "trebor", - "version": "0.2.2", + "version": "0.2.3", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 6efec12..ac71aad 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "trebor", - "version": "0.2.2", + "version": "0.2.3", "description": "A node js module to make standalone web components.", "main": "./build/index.js", "bin": { diff --git a/spec/components/bind.umd.js b/spec/components/bind.umd.js index 8d94acb..6e0422c 100644 --- a/spec/components/bind.umd.js +++ b/spec/components/bind.umd.js @@ -125,7 +125,7 @@ function _$CompCtr(attrs, template, options, parent) { for (var key in data) { _loop_1(key); } - var tpl = template(self, opts.children); + var tpl = template(self); _$e(tpl, function(value, key) { _$def(self, key, { value: function(key) { @@ -456,8 +456,8 @@ function _$a(parent, child, sibling) { function _$ce(tagName) { return document.createElement(tagName || 'div'); } -function _$sa(el, attrOrBind) { - var attr = attrOrBind[0], value = attrOrBind[1]; +function _$sa(el, attrAndValue) { + var attr = attrAndValue[0], value = attrAndValue[1]; el.setAttribute(attr, _$toStr(value)); if (_$isValueAttr(attr) && !_$isStr(value)) el[PROP_MAP._] = value; @@ -474,6 +474,10 @@ function _$al(el, event, handler) { function _$rl(el, event, handler) { el.removeEventListener(event, handler, false); } +function _$bba(el, attrAndValue) { + var _a = attrAndValue.concat([el.hasAttribute(attrAndValue[0])]), attr = _a[0], value = _a[1], hasAttr = _a[2]; + value == null || value === false ? hasAttr && el.removeAttribute(attr) : _$sa(el, [attr, '']); +} function _$bu(el, binding) { var attr = binding[0], value = binding[1]; var _value = attr === 'checked' ? !!value : _$toStr(value); @@ -548,35 +552,35 @@ function _$tplBind(_$state) { _$al(input_2, 'change', handlerChangeEvent_1 = function(event) { changeEvent_1(_$state, event, input_2); }); - input_2.checked = !!bindCheckedInput_2(_$state)[1]; + _$bba(input_2, bindCheckedInput_2(_$state)); _$sa(input_2, ['id', 'checkbox_1']); _$sa(input_2, ['type', 'checkbox']); _$sa(input_2, ['value', 'Yes']); _$al(input_3, 'change', handlerChangeEvent_2 = function(event) { changeEvent_2(_$state, event, input_3); }); - input_3.checked = !!bindCheckedInput_3(_$state)[1]; + _$bba(input_3, bindCheckedInput_3(_$state)); _$sa(input_3, ['id', 'checkbox_2']); _$sa(input_3, ['type', 'checkbox']); _$sa(input_3, ['value', 'No']); _$al(input_4, 'change', handlerChangeEvent_3 = function(event) { changeEvent_3(_$state, event, input_4); }); - input_4.checked = !!bindCheckedInput_4(_$state)[1]; + _$bba(input_4, bindCheckedInput_4(_$state)); _$sa(input_4, ['id', 'radio_1']); _$sa(input_4, ['type', 'radio']); _$sa(input_4, ['value', 'radio 1']); _$al(input_5, 'change', handlerChangeEvent_4 = function(event) { changeEvent_4(_$state, event, input_5); }); - input_5.checked = !!bindCheckedInput_5(_$state)[1]; + _$bba(input_5, bindCheckedInput_5(_$state)); _$sa(input_5, ['id', 'radio_2']); _$sa(input_5, ['type', 'radio']); _$sa(input_5, ['value', 'radio 2']); _$al(input_6, 'change', handlerChangeEvent_5 = function(event) { changeEvent_5(_$state, event, input_6); }); - input_6.checked = !!bindCheckedInput_6(_$state)[1]; + _$bba(input_6, bindCheckedInput_6(_$state)); _$sa(input_6, ['id', 'radio_3']); _$sa(input_6, ['type', 'radio']); _$sa(input_6, ['value', 'radio 3']); @@ -591,11 +595,11 @@ function _$tplBind(_$state) { $update: function(_$state) { _$bu(input_1, bindValueInput_1(_$state)); - _$bu(input_2, bindCheckedInput_2(_$state)); - _$bu(input_3, bindCheckedInput_3(_$state)); - _$bu(input_4, bindCheckedInput_4(_$state)); - _$bu(input_5, bindCheckedInput_5(_$state)); - _$bu(input_6, bindCheckedInput_6(_$state)); + _$bba(input_2, bindCheckedInput_2(_$state)); + _$bba(input_3, bindCheckedInput_3(_$state)); + _$bba(input_4, bindCheckedInput_4(_$state)); + _$bba(input_5, bindCheckedInput_5(_$state)); + _$bba(input_6, bindCheckedInput_6(_$state)); }, $unmount: function() { diff --git a/spec/components/condition.umd.js b/spec/components/condition.umd.js index 88a7dfc..2dc8c2f 100644 --- a/spec/components/condition.umd.js +++ b/spec/components/condition.umd.js @@ -125,7 +125,7 @@ function _$CompCtr(attrs, template, options, parent) { for (var key in data) { _loop_1(key); } - var tpl = template(self, opts.children); + var tpl = template(self); _$e(tpl, function(value, key) { _$def(self, key, { value: function(key) { diff --git a/spec/components/html.umd.js b/spec/components/html.umd.js index b6d1885..7a6abe1 100644 --- a/spec/components/html.umd.js +++ b/spec/components/html.umd.js @@ -125,7 +125,7 @@ function _$CompCtr(attrs, template, options, parent) { for (var key in data) { _loop_1(key); } - var tpl = template(self, opts.children); + var tpl = template(self); _$e(tpl, function(value, key) { _$def(self, key, { value: function(key) { @@ -469,8 +469,8 @@ function _$ce(tagName) { function _$ct(content) { return document.createTextNode(content || ''); } -function _$sa(el, attrOrBind) { - var attr = attrOrBind[0], value = attrOrBind[1]; +function _$sa(el, attrAndValue) { + var attr = attrAndValue[0], value = attrAndValue[1]; el.setAttribute(attr, _$toStr(value)); if (_$isValueAttr(attr) && !_$isStr(value)) el[PROP_MAP._] = value; diff --git a/spec/components/init.umd.js b/spec/components/init.umd.js index 5f2fcad..ecf8127 100644 --- a/spec/components/init.umd.js +++ b/spec/components/init.umd.js @@ -125,7 +125,7 @@ function _$CompCtr(attrs, template, options, parent) { for (var key in data) { _loop_1(key); } - var tpl = template(self, opts.children); + var tpl = template(self); _$e(tpl, function(value, key) { _$def(self, key, { value: function(key) { diff --git a/spec/components/loop.umd.js b/spec/components/loop.umd.js index 982710b..85f8974 100644 --- a/spec/components/loop.umd.js +++ b/spec/components/loop.umd.js @@ -125,7 +125,7 @@ function _$CompCtr(attrs, template, options, parent) { for (var key in data) { _loop_1(key); } - var tpl = template(self, opts.children); + var tpl = template(self); _$e(tpl, function(value, key) { _$def(self, key, { value: function(key) { @@ -454,8 +454,8 @@ function _$ce(tagName) { function _$ct(content) { return document.createTextNode(content || ''); } -function _$sa(el, attrOrBind) { - var attr = attrOrBind[0], value = attrOrBind[1]; +function _$sa(el, attrAndValue) { + var attr = attrAndValue[0], value = attrAndValue[1]; el.setAttribute(attr, _$toStr(value)); if (_$isValueAttr(attr) && !_$isStr(value)) el[PROP_MAP._] = value;