UNPKG

18.3 kBJavaScriptView Raw
1'use strict';
2
3var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
4
5function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
6
7/**
8 * Copyright 2015-present Desmond Yao
9 *
10 * Licensed under the Apache License, Version 2.0 (the "License");
11 * you may not use this file except in compliance with the License.
12 * You may obtain a copy of the License at
13 *
14 * http://www.apache.org/licenses/LICENSE-2.0
15 *
16 * Unless required by applicable law or agreed to in writing, software
17 * distributed under the License is distributed on an "AS IS" BASIS,
18 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19 * See the License for the specific language governing permissions and
20 * limitations under the License.
21 *
22 * Created by desmond on 4/16/17.
23 *
24 */
25
26var babylon = require('babylon');
27var traverse = require('babel-traverse').default;
28var path = require('path');
29var minimatch = require('minimatch');
30var Util = require('./utils');
31var fs = require('fs');
32var assetPathUtil = require('./assetPathUtils');
33
34var MODULE_SPLITER = '\n';
35
36var Parser = function () {
37 function Parser(codeBlob, config) {
38 _classCallCheck(this, Parser);
39
40 this._codeBlob = codeBlob;
41 this._config = config;
42 this._useCustomSplit = typeof config.customEntries !== 'undefined';
43 this._modules = {};
44
45 this._polyfills = []; // polyfill codes range, always append on start.
46 this._moduleCalls = []; // module call codes range, always append on end.
47
48 this._base = new Set(); // store module id of base modules
49 this._customEntries = [];
50 this._bundles = []; // store split codes
51 }
52
53 _createClass(Parser, [{
54 key: 'splitBundle',
55 value: function splitBundle() {
56 var outputDir = this._config.outputDir;
57 Util.ensureFolder(outputDir);
58 var bundleAST = babylon.parse(this._codeBlob, {
59 sourceType: 'script',
60 plugins: ['jsx', 'flow']
61 });
62 this._parseAST(bundleAST);
63 this._doSplit();
64
65 this._bundles.forEach(function (subBundle) {
66 console.log('====== Split ' + subBundle.name + ' ======');
67 var code = subBundle.codes.join(MODULE_SPLITER);
68 var subBundlePath = path.resolve(outputDir, subBundle.name);
69 Util.ensureFolder(subBundlePath);
70
71 var codePath = path.resolve(subBundlePath, 'index.bundle');
72 fs.writeFileSync(codePath, code);
73 console.log('[Code] Write code to ' + codePath);
74 if (subBundle.assetRenames) {
75 subBundle.assetRenames.forEach(function (item) {
76 var assetNewDir = path.dirname(item.newPath);
77 Util.mkdirsSync(assetNewDir);
78 console.log('[Resource] Move resource ' + item.originPath + ' to ' + item.newPath);
79 fs.createReadStream(item.originPath).pipe(fs.createWriteStream(item.newPath));
80 });
81 }
82 console.log('====== Split ' + subBundle.name + ' done! ======');
83 });
84 }
85 }, {
86 key: '_parseAST',
87 value: function _parseAST(bundleAST) {
88 var _this = this;
89
90 var program = bundleAST.program;
91 var body = program.body;
92 var customBase = [];
93 var customEntry = [];
94 var reactEntryModule = undefined;
95 var moduleCount = 0;
96 body.forEach(function (node) {
97 if (Util.isEmptyStmt(node)) {
98 return;
99 }
100
101 var start = node.start,
102 end = node.end;
103
104
105 if (Util.isPolyfillCall(node, _this._config.dev)) {
106 // push polyfill codes to base.
107 _this._polyfills.push({ start: start, end: end });
108 } else if (Util.isModuleCall(node)) {
109 _this._moduleCalls.push({ start: start, end: end });
110 } else if (Util.isModuleDeclaration(node)) {
111 moduleCount++;
112 var args = node.expression.arguments;
113 var _moduleId = parseInt(args[1].value);
114 var moduleName = args[3].value;
115 var _module = {
116 id: _moduleId,
117 name: moduleName,
118 dependencies: _this._getModuleDependency(args[0].body),
119 code: { start: start, end: end },
120 idCodeRange: {
121 start: args[1].start - node.start,
122 end: args[1].end - node.start
123 }
124 };
125
126 if (Util.isAssetModule(moduleName)) {
127 _module.isAsset = true;
128 _module.assetConfig = Object.assign({}, Util.getAssetConfig(node), { moduleId: _moduleId });
129 console.log('Get asset module ' + moduleName, _module.assetConfig);
130 }
131
132 if (!reactEntryModule && Util.isReactNativeEntry(moduleName)) {
133 // get react native entry, then init base set.
134 reactEntryModule = _moduleId;
135 }
136
137 if (_this._isBaseEntryModule(_module)) {
138 console.log('Get base entry module: ' + moduleName);
139 _this._baseEntryIndexModule = _moduleId;
140 } else if (_this._isCustomBaseModule(_module)) {
141 console.log('Get custom base ' + moduleName);
142 customBase.push(_moduleId);
143 } else if (_this._useCustomSplit) {
144 var entry = _this._isCustomEntryModule(_module);
145 if (!!entry) {
146 console.log('Get custom entry ' + moduleName);
147 customEntry.push({
148 id: _moduleId,
149 name: entry.name
150 });
151 }
152 }
153
154 _this._modules[_moduleId] = _module;
155 console.log('Module ' + moduleName + '(' + _moduleId + ') dependency:' + JSON.stringify(_module.dependencies));
156 } else {
157 console.log(require('util').inspect(node, false, null));
158 console.log('Cannot parse node!', _this._codeBlob.substring(node.start, node.end));
159 }
160 });
161
162 // generate react-native based module firstly.
163 if (reactEntryModule) {
164 this._genBaseModules(reactEntryModule);
165 } else {
166 console.warn('Cannot find react-native entry module! You should require(\'react-native\') at some entry.');
167 }
168
169 // append custom base modules.
170 customBase.forEach(function (base) {
171 _this._genBaseModules(base);
172 });
173
174 if (typeof this._baseEntryIndexModule !== 'undefined') {
175 (function () {
176 var module = _this._modules[_this._baseEntryIndexModule];
177 var dependency = module.dependencies;
178
179 var _loop = function _loop(i) {
180 if (!!customEntry.find(function (item) {
181 return item.id === dependency[i];
182 })) {
183 dependency.splice(i, 1);
184 }
185 };
186
187 for (var i = dependency.length - 1; i >= 0; i--) {
188 _loop(i);
189 }
190 _this._genBaseModules(_this._baseEntryIndexModule);
191 })();
192 }
193
194 if (!!customEntry) {
195 // after gen base module, generate custom entry sets.
196 customEntry.forEach(function (entry) {
197 _this._genCustomEntryModules(entry.name, entry.id);
198 });
199 }
200
201 // console.log('Get polyfills', this._polyfills);
202 console.log('Total modules :' + moduleCount);
203 console.log('Base modules size: ' + this._base.size);
204 }
205 }, {
206 key: '_genBaseModules',
207 value: function _genBaseModules(moduleId) {
208 var _this2 = this;
209
210 this._base.add(moduleId);
211 var module = this._modules[moduleId];
212 var queue = module.dependencies;
213
214 if (!queue) {
215 return;
216 }
217 var added = 0;
218 while (queue.length > 0) {
219 var tmp = queue.shift();
220
221 if (this._base.has(tmp)) {
222 continue;
223 }
224
225 if (this._modules[tmp].dependencies && this._modules[tmp].dependencies.length > 0) {
226 this._modules[tmp].dependencies.forEach(function (dep) {
227 if (!_this2._base.has(dep)) {
228 queue.push(dep);
229 }
230 });
231 }
232 added++;
233 this._base.add(tmp);
234 }
235 console.log('Module ' + module.name + ' added to base (' + added + ' more dependency added too)');
236 }
237 }, {
238 key: '_genCustomEntryModules',
239 value: function _genCustomEntryModules(name, moduleId) {
240 var _this3 = this;
241
242 var set = new Set();
243 set.add(moduleId);
244
245 var module = this._modules[moduleId];
246 var queue = module.dependencies;
247
248 if (!queue) {
249 return;
250 }
251 var added = 0;
252 while (queue.length > 0) {
253 var tmp = queue.shift();
254
255 if (set.has(tmp) || this._base.has(tmp)) {
256 continue;
257 }
258
259 var dependency = this._modules[tmp].dependencies;
260 if (dependency && dependency.length > 0) {
261 dependency.forEach(function (dep) {
262 if (!_this3._base.has(dep) && !set.has(dep)) {
263 queue.push(dep);
264 }
265 });
266 }
267 added++;
268 set.add(tmp);
269 }
270 this._customEntries.push({
271 moduleId: moduleId,
272 name: name,
273 moduleSet: set
274 });
275 console.log('Module ' + module.name + ' added to bundle ' + name + '. (' + added + ' more dependency added too)');
276 }
277 }, {
278 key: '_getModuleDependency',
279 value: function _getModuleDependency(bodyNode) {
280 if (bodyNode.type === 'BlockStatement') {
281 var _start = bodyNode.start,
282 _end = bodyNode.end;
283
284 return Util.getModuleDependency(this._codeBlob, _start, _end);
285 }
286 return [];
287 }
288 }, {
289 key: '_isBaseEntryModule',
290 value: function _isBaseEntryModule(module) {
291 var baseIndex = this._config.baseEntry.index;
292 var indexGlob = path.join(this._config.packageName, baseIndex + '.tmp');
293 // base index entry.
294 return minimatch(module.name, indexGlob);
295 }
296 }, {
297 key: '_isCustomEntryModule',
298 value: function _isCustomEntryModule(module) {
299 var _this4 = this;
300
301 return this._config.customEntries.find(function (entry) {
302 var pathGlob = path.join(_this4._config.packageName, entry.index);
303 return minimatch(module.name, pathGlob);
304 });
305 }
306 }, {
307 key: '_isCustomBaseModule',
308 value: function _isCustomBaseModule(module) {
309 var _this5 = this;
310
311 if (this._config.baseEntry.includes && this._config.baseEntry.includes.length > 0) {
312 var includes = this._config.baseEntry.includes;
313 var match = includes.find(function (glob) {
314 var pathGlob = path.join(_this5._config.packageName, glob);
315 return minimatch(module.name, pathGlob);
316 });
317 return typeof match !== 'undefined';
318 }
319 return false;
320 }
321 }, {
322 key: '_getAssetRenames',
323 value: function _getAssetRenames(asset, bundle) {
324 var _this6 = this;
325
326 var assetRenames = [];
327 if (this._config.platform === 'android') {
328 console.log('Get asset renames', asset);
329 assetPathUtil.getAssetPathInDrawableFolder(asset).forEach(function (relativePath) {
330 assetRenames.push({
331 originPath: path.resolve(_this6._config.bundleDir, relativePath),
332 relativePath: relativePath,
333 newPath: path.resolve(_this6._config.outputDir, bundle, relativePath)
334 });
335 });
336 } else {
337 console.log('Get ios asset renames', asset);
338 asset.scales.forEach(function (scale) {
339 var relativePath = _this6._getAssetDestPathIOS(asset, scale);
340 var originPath = path.resolve(_this6._config.bundleDir, relativePath);
341 if (Util.ensureFolder(originPath)) {
342 assetRenames.push({
343 originPath: originPath,
344 relativePath: relativePath,
345 newPath: path.resolve(_this6._config.outputDir, bundle, relativePath)
346 });
347 }
348 });
349 }
350
351 return assetRenames;
352 }
353 }, {
354 key: '_getAssetDestPathIOS',
355 value: function _getAssetDestPathIOS(asset, scale) {
356 var suffix = scale === 1 ? '' : '@' + scale + 'x';
357 var fileName = asset.name + suffix + '.' + asset.type;
358 return path.join(asset.httpServerLocation.substr(1), fileName);
359 }
360 }, {
361 key: '_doSplit',
362 value: function _doSplit() {
363 var _this7 = this;
364
365 this._splitBase();
366
367 if (this._useCustomSplit) {
368 this._customEntries.forEach(function (entry) {
369 _this7._splitCustomEntry(entry);
370 });
371 console.log('Use custom split');
372 } else {
373 this._splitNonBaseModules();
374 }
375 }
376 }, {
377 key: '_splitBase',
378 value: function _splitBase() {
379 var _this8 = this;
380
381 var bundleName = 'base';
382 var dev = this._config.dev;
383 var codes = [];
384 var assetRenames = [];
385 // append codes to base
386 this._polyfills.forEach(function (range, index) {
387 var code = _this8._codeBlob.substring(range.start, range.end);
388 if (index === 1) {
389 var requireAST = babylon.parse(code);
390 var conditionNode = void 0;
391 traverse(requireAST, {
392 enter: function enter(path) {
393 if (Util.isRequirePolyfillCondition(path.node, dev)) {
394 conditionNode = path.node;
395 }
396 },
397 exit: function exit(path) {}
398 });
399 if (conditionNode) {
400 code = code.substring(0, conditionNode.start) + code.substring(conditionNode.end);
401 }
402 }
403 codes.push(code);
404 });
405 this._base.forEach(function (moduleId) {
406 var module = _this8._modules[moduleId];
407 var code = _this8._codeBlob.substring(module.code.start, module.code.end);
408 code = code.substring(0, module.idCodeRange.start) + '\"' + module.name + '\"' + code.substring(module.idCodeRange.end);
409 if (module.isAsset && !!module.assetConfig) {
410 assetRenames = _this8._getAssetRenames(module.assetConfig, bundleName);
411 code = _this8._addBundleToAsset(module, bundleName, code);
412 } else if (moduleId === _this8._baseEntryIndexModule) {
413 (function () {
414 var dependencies = Util.getModuleDependencyCodeRange(code, 0, code.length);
415
416 var _loop2 = function _loop2(i) {
417 if (_this8._customEntries.find(function (entry) {
418 return parseInt(entry.moduleId) === parseInt(dependencies[i].module);
419 })) {
420 code = code.replace(dependencies[i].code, '');
421 }
422 };
423
424 for (var i = dependencies.length - 1; i >= 0; i--) {
425 _loop2(i);
426 }
427 })();
428 }
429 code = Util.replaceModuleIdWithName(code, _this8._modules);
430 codes.push(code);
431 });
432 this._moduleCalls.forEach(function (moduleCall) {
433 var code = _this8._codeBlob.substring(moduleCall.start, moduleCall.end);
434 code = Util.replaceModuleIdWithName(code, _this8._modules);
435 codes.push(code);
436 });
437 this._bundles.push({
438 name: bundleName,
439 codes: codes,
440 assetRenames: assetRenames
441 });
442 }
443 }, {
444 key: '_splitCustomEntry',
445 value: function _splitCustomEntry(entry) {
446 var _this9 = this;
447
448 var bundleName = entry.name;
449 var codes = [];
450 var assetRenames = [];
451 entry.moduleSet.forEach(function (moduleId) {
452 var module = _this9._modules[moduleId];
453 var code = _this9._codeBlob.substring(module.code.start, module.code.end);
454 code = code.substring(0, module.idCodeRange.start) + '\"' + module.name + '\"' + code.substring(module.idCodeRange.end);
455 if (module.isAsset && module.assetConfig) {
456 assetRenames = assetRenames.concat(_this9._getAssetRenames(module.assetConfig, bundleName));
457 code = _this9._addBundleToAsset(module, bundleName, code);
458 }
459 code = Util.replaceModuleIdWithName(code, _this9._modules);
460 codes.push(code);
461 });
462 var entryModuleName = this._modules[entry.moduleId].name;
463 codes.push('\nrequire(\"' + entryModuleName + '\");');
464 this._bundles.push({
465 name: bundleName,
466 codes: codes,
467 assetRenames: assetRenames
468 });
469 }
470 }, {
471 key: '_splitNonBaseModules',
472 value: function _splitNonBaseModules() {
473 var bundleName = 'business';
474 var codes = [];
475 var assetRenames = [];
476 for (var _moduleId2 in this._modules) {
477 var moduleIdInt = parseInt(_moduleId2);
478
479 if (this._modules.hasOwnProperty(_moduleId2) && !this._base.has(moduleIdInt)) {
480 var _module2 = this._modules[moduleIdInt];
481 var _code = this._codeBlob.substring(_module2.code.start, _module2.code.end);
482 _code = _code.substring(0, _module2.idCodeRange.start) + '\"' + _module2.name + '\"' + _code.substring(_module2.idCodeRange.end);
483 if (_module2.isAsset && _module2.assetConfig) {
484 assetRenames = this._getAssetRenames(_module2.assetConfig, bundleName);
485 _code = this._addBundleToAsset(_module2, bundleName, _code);
486 }
487 _code = Util.replaceModuleIdWithName(_code, this._modules);
488 codes.push(_code);
489 }
490 }
491 this._bundles.push({
492 name: bundleName,
493 codes: codes,
494 assetRenames: assetRenames
495 });
496 }
497 }, {
498 key: '_addBundleToAsset',
499 value: function _addBundleToAsset(module, bundleName, code) {
500 var asset = module.assetConfig;
501 var startInner = asset.code.start - module.code.start;
502 var endInner = asset.code.end - module.code.start;
503 return code.substring(0, startInner) + JSON.stringify({
504 httpServerLocation: asset.httpServerLocation,
505 width: asset.width,
506 height: asset.height,
507 scales: asset.scales,
508 hash: asset.hash,
509 name: asset.name,
510 type: asset.type,
511 bundle: bundleName
512 }) + code.substring(endInner);
513 }
514 }]);
515
516 return Parser;
517}();
518
519module.exports = Parser;
\No newline at end of file