Mirror: 馃帺 A tiny but capable push & pull stream library for TypeScript and Flow
at v4.0.12 4.2 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 cleanup(opts = {}) { 114 const filter = createFilter(opts.include, opts.exclude, { 115 resolve: false, 116 }); 117 118 return { 119 name: 'minifyBucklescript', 120 121 renderChunk(code, chunk) { 122 if (!filter(chunk.fileName)) { 123 return null; 124 } 125 126 return transform(code, { 127 plugins: [unwrapStatePlugin, curryGuaranteePlugin], 128 babelrc: false, 129 }); 130 }, 131 }; 132} 133 134export default cleanup;