1 | const fs = require('fs');
|
2 | const _ = require('lodash');
|
3 | const acorn = require('acorn');
|
4 | const walk = require('acorn/dist/walk');
|
5 |
|
6 | module.exports = {
|
7 | parseBundle
|
8 | };
|
9 |
|
10 | function parseBundle(bundlePath) {
|
11 | const content = fs.readFileSync(bundlePath, 'utf8');
|
12 | const ast = acorn.parse(content, {
|
13 | sourceType: 'script',
|
14 |
|
15 |
|
16 |
|
17 | ecmaVersion: 2050
|
18 | });
|
19 |
|
20 | const walkState = {
|
21 | locations: null
|
22 | };
|
23 |
|
24 | walk.recursive(
|
25 | ast,
|
26 | walkState,
|
27 | {
|
28 | CallExpression(node, state, c) {
|
29 | if (state.sizes) return;
|
30 |
|
31 | const args = node.arguments;
|
32 |
|
33 |
|
34 |
|
35 |
|
36 |
|
37 | if (
|
38 | node.callee.type === 'Identifier' &&
|
39 | args.length >= 2 &&
|
40 | isArgumentContainsChunkIds(args[0]) &&
|
41 | isArgumentContainsModulesList(args[1])
|
42 | ) {
|
43 | state.locations = getModulesLocationFromFunctionArgument(args[1]);
|
44 | return;
|
45 | }
|
46 |
|
47 |
|
48 |
|
49 |
|
50 |
|
51 | if (
|
52 | node.callee.type === 'Identifier' &&
|
53 | (args.length === 2 || args.length === 3) &&
|
54 | isArgumentContainsChunkIds(args[0]) &&
|
55 | isArgumentArrayConcatContainingChunks(args[1])
|
56 | ) {
|
57 | state.locations = getModulesLocationFromArrayConcat(args[1]);
|
58 | return;
|
59 | }
|
60 |
|
61 |
|
62 |
|
63 |
|
64 | if (
|
65 | node.callee.type === 'FunctionExpression' &&
|
66 | !node.callee.id &&
|
67 | args.length === 1 &&
|
68 | isArgumentContainsModulesList(args[0])
|
69 | ) {
|
70 | state.locations = getModulesLocationFromFunctionArgument(args[0]);
|
71 | return;
|
72 | }
|
73 |
|
74 |
|
75 |
|
76 | _.each(args, arg => c(arg, state));
|
77 | }
|
78 | }
|
79 | );
|
80 |
|
81 | if (!walkState.locations) {
|
82 | return null;
|
83 | }
|
84 |
|
85 | return {
|
86 | src: content,
|
87 | modules: _.mapValues(walkState.locations,
|
88 | loc => content.slice(loc.start, loc.end)
|
89 | )
|
90 | };
|
91 | }
|
92 |
|
93 | function isArgumentContainsChunkIds(arg) {
|
94 |
|
95 | return (arg.type === 'ArrayExpression' && _.every(arg.elements, isModuleId));
|
96 | }
|
97 |
|
98 | function isArgumentContainsModulesList(arg) {
|
99 | if (arg.type === 'ObjectExpression') {
|
100 | return _(arg.properties)
|
101 | .map('value')
|
102 | .every(isModuleWrapper);
|
103 | }
|
104 |
|
105 | if (arg.type === 'ArrayExpression') {
|
106 |
|
107 |
|
108 | return _.every(arg.elements, elem =>
|
109 |
|
110 | !elem ||
|
111 | isModuleWrapper(elem)
|
112 | );
|
113 | }
|
114 |
|
115 | return false;
|
116 | }
|
117 |
|
118 | function isArgumentArrayConcatContainingChunks(arg) {
|
119 | if (
|
120 | arg.type === 'CallExpression' &&
|
121 | arg.callee.type === 'MemberExpression' &&
|
122 |
|
123 | arg.callee.object.type === 'CallExpression' &&
|
124 | arg.callee.object.callee.type === 'Identifier' &&
|
125 | arg.callee.object.callee.name === 'Array' &&
|
126 | arg.callee.object.arguments.length === 1 &&
|
127 | isNumericId(arg.callee.object.arguments[0]) &&
|
128 |
|
129 | arg.callee.property.type === 'Identifier' &&
|
130 | arg.callee.property.name === 'concat' &&
|
131 |
|
132 | arg.arguments.length === 1 &&
|
133 | arg.arguments[0].type === 'ArrayExpression'
|
134 | ) {
|
135 |
|
136 |
|
137 |
|
138 | return true;
|
139 | }
|
140 |
|
141 | return false;
|
142 | }
|
143 |
|
144 | function isModuleWrapper(node) {
|
145 | return (
|
146 |
|
147 | ((node.type === 'FunctionExpression' || node.type === 'ArrowFunctionExpression') && !node.id) ||
|
148 |
|
149 | isModuleId(node) ||
|
150 |
|
151 | (node.type === 'ArrayExpression' && node.elements.length > 1 && isModuleId(node.elements[0]))
|
152 | );
|
153 | }
|
154 |
|
155 | function isModuleId(node) {
|
156 | return (node.type === 'Literal' && (isNumericId(node) || typeof node.value === 'string'));
|
157 | }
|
158 |
|
159 | function isNumericId(node) {
|
160 | return (node.type === 'Literal' && Number.isInteger(node.value) && node.value >= 0);
|
161 | }
|
162 |
|
163 | function getModulesLocationFromFunctionArgument(arg) {
|
164 | if (arg.type === 'ObjectExpression') {
|
165 | const modulesNodes = arg.properties;
|
166 |
|
167 | return _.transform(modulesNodes, (result, moduleNode) => {
|
168 | const moduleId = moduleNode.key.name || moduleNode.key.value;
|
169 |
|
170 | result[moduleId] = getModuleLocation(moduleNode.value);
|
171 | }, {});
|
172 | }
|
173 |
|
174 | if (arg.type === 'ArrayExpression') {
|
175 | const modulesNodes = arg.elements;
|
176 |
|
177 | return _.transform(modulesNodes, (result, moduleNode, i) => {
|
178 | if (!moduleNode) return;
|
179 |
|
180 | result[i] = getModuleLocation(moduleNode);
|
181 | }, {});
|
182 | }
|
183 |
|
184 | return {};
|
185 | }
|
186 |
|
187 | function getModulesLocationFromArrayConcat(arg) {
|
188 |
|
189 |
|
190 |
|
191 |
|
192 | const minId = arg.callee.object.arguments[0].value;
|
193 |
|
194 | const modulesNodes = arg.arguments[0].elements;
|
195 |
|
196 | return _.transform(modulesNodes, (result, moduleNode, i) => {
|
197 | if (!moduleNode) return;
|
198 |
|
199 | result[i + minId] = getModuleLocation(moduleNode);
|
200 | }, {});
|
201 | }
|
202 |
|
203 | function getModuleLocation(node) {
|
204 | return _.pick(node, 'start', 'end');
|
205 | }
|