Mirror: 馃帺 A tiny but capable push & pull stream library for TypeScript and Flow
at v5.0.0-rc.0 5.8 kB view raw
1import { transformSync as transform } from '@babel/core'; 2import { createFilter } from 'rollup-pluginutils'; 3 4function unwrapStatePlugin({ types: t }) { 5 return { 6 pre() { 7 this.props = new Map(); 8 this.test = (node) => 9 /state$/i.test(node.id.name) || 10 (node.init.properties.length === 1 && node.init.properties[0].key.name === 'contents'); 11 }, 12 visitor: { 13 VariableDeclarator(path) { 14 if ( 15 t.isIdentifier(path.node.id) && 16 t.isObjectExpression(path.node.init) && 17 path.node.init.properties.every( 18 (prop) => t.isObjectProperty(prop) && t.isIdentifier(prop.key) 19 ) && 20 this.test(path.node) 21 ) { 22 const id = path.node.id.name; 23 const properties = path.node.init.properties; 24 const propNames = new Set(properties.map((x) => x.key.name)); 25 const decl = properties.map((prop) => { 26 const key = `${id}$${prop.key.name}`; 27 return t.variableDeclarator(t.identifier(key), prop.value); 28 }); 29 30 this.props.set(id, propNames); 31 path.parentPath.replaceWithMultiple(t.variableDeclaration('let', decl)); 32 } 33 }, 34 MemberExpression(path) { 35 if ( 36 t.isIdentifier(path.node.object) && 37 this.props.has(path.node.object.name) && 38 t.isIdentifier(path.node.property) && 39 this.props.get(path.node.object.name).has(path.node.property.name) 40 ) { 41 const id = path.node.object.name; 42 const propName = path.node.property.name; 43 path.replaceWith(t.identifier(`${id}$${propName}`)); 44 } 45 }, 46 }, 47 }; 48} 49 50function curryGuaranteePlugin({ types: t }) { 51 const curryFnName = /^_(\d)$/; 52 const lengthId = t.identifier('length'); 53 const bindId = t.identifier('bind'); 54 55 return { 56 visitor: { 57 CallExpression(path) { 58 if (!t.isIdentifier(path.node.callee) || !curryFnName.test(path.node.callee.name)) { 59 return; 60 } 61 62 const callFn = path.node.arguments[0]; 63 const callArgs = path.node.arguments.slice(1); 64 65 // Check whether the value of the call is unused 66 if (t.isExpressionStatement(path.parent)) { 67 path.replaceWith(t.callExpression(callFn, callArgs)); 68 return; 69 } 70 71 // Check whether the callee is a local function definition whose arity matches 72 if (t.isIdentifier(callFn) && path.scope.hasBinding(callFn.name)) { 73 const callFnDefinition = path.scope.getBinding(callFn.name).path.node; 74 if ( 75 t.isFunctionDeclaration(callFnDefinition) && 76 callFnDefinition.params.length === callArgs.length 77 ) { 78 path.replaceWith(t.callExpression(callFn, callArgs)); 79 return; 80 } 81 } 82 83 // Special case since sources don't return any value 84 if ( 85 t.isIdentifier(callFn) && 86 callFn.name === 'source' && 87 t.isReturnStatement(path.parent) 88 ) { 89 path.replaceWith(t.callExpression(callFn, callArgs)); 90 return; 91 } 92 93 const arityLiteral = t.numericLiteral(callArgs.length); 94 const argIds = callArgs.map((init) => { 95 if (t.isIdentifier(init)) return init; 96 const id = path.scope.generateUidIdentifierBasedOnNode(path.node.id); 97 path.scope.push({ id, init }); 98 return id; 99 }); 100 101 path.replaceWith( 102 t.conditionalExpression( 103 t.binaryExpression('===', t.memberExpression(callFn, lengthId), arityLiteral), 104 t.callExpression(callFn, argIds), 105 t.callExpression(t.memberExpression(callFn, bindId), [t.nullLiteral()].concat(argIds)) 106 ) 107 ); 108 }, 109 }, 110 }; 111} 112 113function squashImplicitUnitReturn({ types: t }) { 114 return { 115 visitor: { 116 ReturnStatement(path) { 117 if ( 118 t.isCallExpression(path.node.argument) && 119 t.isIdentifier(path.node.argument.callee) && 120 (path.node.argument.callee.name === 'sink' || path.node.argument.callee.name === 'source') 121 ) { 122 path.replaceWithMultiple([ 123 t.expressionStatement(path.node.argument), 124 t.returnStatement(), 125 ]); 126 } 127 }, 128 Function: { 129 exit(functionPath) { 130 if (t.isIdentifier(functionPath.id) && functionPath.id.name === 'valFromOption') return; 131 132 let hasEmptyReturn = false; 133 let hasCallReturnOnly = true; 134 functionPath.traverse({ 135 Function(innerPath) { 136 innerPath.skip(); 137 }, 138 ReturnStatement: { 139 enter(path) { 140 if (path.node.argument === null) { 141 hasEmptyReturn = true; 142 } else if (!t.isCallExpression(path.node.argument)) { 143 hasCallReturnOnly = false; 144 } 145 }, 146 exit(path) { 147 if (hasEmptyReturn && hasCallReturnOnly && path.node.argument !== null) { 148 path.replaceWithMultiple([ 149 t.expressionStatement(path.node.argument), 150 t.returnStatement(), 151 ]); 152 } 153 }, 154 }, 155 }); 156 }, 157 }, 158 }, 159 }; 160} 161 162function cleanup(opts = {}) { 163 const filter = createFilter(opts.include, opts.exclude, { 164 resolve: false, 165 }); 166 167 return { 168 name: 'minifyBucklescript', 169 170 renderChunk(code, chunk) { 171 if (!filter(chunk.fileName)) { 172 return null; 173 } 174 175 return transform(code, { 176 plugins: [unwrapStatePlugin, curryGuaranteePlugin, squashImplicitUnitReturn], 177 babelrc: false, 178 }); 179 }, 180 }; 181} 182 183export default cleanup;