tree shaking 简单理解就是在代码打包时将项目代码中没有用到的代码剔除掉,比如在一个文件中申明了一个工具函数,但是并没有调用它,把这样的代码剔除掉,以减少代码打包体积。
acron
acron
A tiny, fast JavaScript parser, written completely in JavaScript。
这是用js写的js语言的解析器。用它可以将js代码进行词法分析,语法分析,进而得到ast,处理ast得到我们想要的结果。 这里就用acron的能力,进行js的语法分析。
解决思路
整体思路是,读取一个文件中的代码,调用acron进行语法分析,剔除没有调用过的函数,结果再输出到一个新的文件中。
需要剔除的源代码
源代码 test.js
1 2 3 4 5 6 7 8
| const add = (a, b) => a + b;
const subtract = (a, b) => a - b;
const num1 = 9; const num2 = 100;
const result = add(num1, num2);
|
文件结构
整个项目的文件结构是
1 2 3 4 5
| project src |--- index.js // 入口文件 |--- test.js // 源代码 |--- visitor.js // 处理ast的代码
|
文件内容
处理ast的代码 visitor.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111
| class Visitor { visitVariableDeclaration(node) { let str = ''; str += node.kind + ' '; str += this.visitNodes(node.declarations); return str + '\n'; } visitVariableDeclarator(node, kind) { let str = ''; str += kind ? kind + ' ' : str; str += this.visitNode(node.id) + ' '; str += '= '; str += this.visitNode(node.init); return str + ';' + '\n'; } visitIdentifier(node) { let str = ''; str += node.name; return str; } visitArrowFunctionExpression(node) { let str = ''; str += '('; node.params.forEach((param, index) => { str += this.visitNode(param); str += (index === node.params.length - 1) ? '' : ',' }) str += ')'; str += '=>'; str += this.visitNode(node.body); return str + '\n'; } visitLiteral(node) { let str = ''; str += node.raw; return str; } visitBinaryExpression(node) { let str = ''; str += this.visitNode(node.left); str += node.operator; str += this.visitNode(node.right); return str + '\n'; }
visitCallExpression(node) { let str = ''; str += this.visitNode(node.callee); str += '('; node.arguments.forEach((param, index) => { str += this.visitNode(param); str += (index === node.arguments.length - 1) ? '':','; }) str += ')'; return str; } visitNodes(nodes) { let str = '' nodes.forEach(node => { str += this.visitNode(node); }); return str; } visitNode(node) { let str = ''; switch (node.type) { case 'VariableDeclaration': str += this.visitVariableDeclaration(node); break; case 'VariableDeclarator': str += this.visitVariableDeclarator(node); break; case 'Identifier': str += this.visitIdentifier(node); break; case 'ArrowFunctionExpression': str += this.visitArrowFunctionExpression(node); break; case 'BinaryExpression': str += this.visitBinaryExpression(node); break; case 'Literal': str += this.visitLiteral(node); break; case 'CallExpression': str += this.visitCallExpression(node); break; default: break; } return str; } run(body) { let str = ''; str += this.visitNodes(body); return str; } }
module.exports = Visitor;
|
入口文件 index.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| const fs = require('fs'); const acron = require('acorn'); const Visitor = require('./visitor'); const visitor = new Visitor();
const args = process.argv[2];
const buffer = fs.readFileSync(args).toString();
const body = acron.parse(buffer).body;
const decls = new Map(); const calledDecls = []; let code = [];
body.forEach(node => { if (node.type === 'VariableDeclaration') { const kind = node.kind; for (const decl of node.declarations) { decls.set(visitor.visitNode(decl.id), visitor.visitVariableDeclarator(decl, kind)); if (decl.init.type === 'CallExpression') { calledDecls.push(visitor.visitIdentifier(decl.init.callee)); const args = decl.init.arguments; for (const arg of args) { if (arg.type === 'Identifier') { calledDecls.push(visitor.visitNode(arg)); } } } } } if (node.type === 'Identifier') { calledDecls.push(node.name); } code.push(visitor.run([node])); });
code = calledDecls.map(c => { return decls.get(c); }).join('');
fs.writeFileSync(__dirname + '/test.shaked.js', code);
|
执行项目
在命令行中执行
1
| node src/index.js src/test.js
|
得到test.shaked.js 文件
1 2 3 4
| const add = (a, b)=>a+b;
const num1 = 9; const num2 = 100;
|
至此,一个简单的tree shaking就做完了。