From 9d09efdd53017877328a9fbaef2fd2cfd31352fc Mon Sep 17 00:00:00 2001 From: David Aurelio Date: Tue, 8 Mar 2016 09:50:14 -0800 Subject: [PATCH] transform before extracting dependencies MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary:Make packager transform files before extracting their dependencies. This allows us to extract dependencies added by transforms (and to avoid including them manually). It also allows for better optimization and to get rid of the “whole program optimization” step: This diff utilizes the new worker introduced in D2976677 / d94a567 – that means that minified builds inline the following variables: - `__DEV__` → `false` - `process.env.NODE_ENV` → `'production'` - `Platform.OS` / `React.Platform.OS` → `'android'` / `'ios'` and eliminates branches of conditionals with constant conditions. Dependency extraction happens only after that step, which means that production bundles don’t include any modules that are not used. Fixes #4185 Reviewed By: martinbigio Differential Revision: D2977169 fb-gh-sync-id: e6ce8dd29d1b49aec49b309201141f5b2709da1d shipit-source-id: e6ce8dd29d1b49aec49b309201141f5b2709da1d --- local-cli/bundle/buildBundle.js | 1 - local-cli/bundle/output/bundle.js | 13 +- local-cli/dependencies/dependencies.js | 1 - local-cli/server/util/attachHMRServer.js | 2 + packager/package.json | 2 +- packager/react-packager/src/Bundler/Bundle.js | 143 +-- .../react-packager/src/Bundler/HMRBundle.js | 21 +- .../src/Bundler/__tests__/Bundle-test.js | 47 +- .../src/Bundler/__tests__/Bundler-test.js | 53 +- packager/react-packager/src/Bundler/index.js | 262 +++-- .../__tests__/Transformer-test.js | 79 +- .../JSTransformer/__tests__/worker-test.js | 109 --- .../react-packager/src/JSTransformer/index.js | 167 +--- .../src/JSTransformer/worker.js | 77 -- .../worker/__tests__/constant-folding-test.js | 24 +- .../__tests__/extract-dependencies-test.js | 26 +- .../worker/__tests__/minify-test.js | 90 +- .../worker/__tests__/worker-test.js | 65 +- .../JSTransformer/worker/constant-folding.js | 5 +- .../worker/extract-dependencies.js | 111 +-- .../src/JSTransformer/worker/index.js | 44 +- .../src/JSTransformer/worker/inline.js | 2 + .../src/JSTransformer/worker/minify.js | 69 +- .../src/Resolver/__tests__/Resolver-test.js | 911 +++--------------- packager/react-packager/src/Resolver/index.js | 91 +- packager/react-packager/src/Server/index.js | 15 +- .../react-packager/src/lib/ModuleTransport.js | 6 +- .../__tests__/dead-module-elimination-test.js | 115 --- .../dead-module-elimination.js | 148 --- .../whole-program-optimisations/index.js | 14 - packager/transformer.js | 10 +- 31 files changed, 622 insertions(+), 2101 deletions(-) delete mode 100644 packager/react-packager/src/JSTransformer/__tests__/worker-test.js delete mode 100644 packager/react-packager/src/JSTransformer/worker.js delete mode 100644 packager/react-packager/src/transforms/whole-program-optimisations/__tests__/dead-module-elimination-test.js delete mode 100644 packager/react-packager/src/transforms/whole-program-optimisations/dead-module-elimination.js delete mode 100644 packager/react-packager/src/transforms/whole-program-optimisations/index.js diff --git a/local-cli/bundle/buildBundle.js b/local-cli/bundle/buildBundle.js index 5b75ca3fb..10130e5a8 100644 --- a/local-cli/bundle/buildBundle.js +++ b/local-cli/bundle/buildBundle.js @@ -28,7 +28,6 @@ function buildBundle(args, config, output = outputBundle) { getTransformOptionsModulePath: config.getTransformOptionsModulePath, transformModulePath: args.transformer, verbose: args.verbose, - disableInternalTransforms: true, }; const requestOpts = { diff --git a/local-cli/bundle/output/bundle.js b/local-cli/bundle/output/bundle.js index 6f40e5bac..e4247982f 100644 --- a/local-cli/bundle/output/bundle.js +++ b/local-cli/bundle/output/bundle.js @@ -17,14 +17,10 @@ function buildBundle(packagerClient, requestOptions) { } function createCodeWithMap(bundle, dev) { - if (!dev) { - return bundle.getMinifiedSourceAndMap(dev); - } else { - return { - code: bundle.getSource({dev}), - map: JSON.stringify(bundle.getSourceMap({dev})), - }; - } + return { + code: bundle.getSource({dev}), + map: JSON.stringify(bundle.getSourceMap({dev})), + }; } function saveBundleAndMap(bundle, options, log) { @@ -53,7 +49,6 @@ function saveBundleAndMap(bundle, options, log) { } } - exports.build = buildBundle; exports.save = saveBundleAndMap; exports.formatName = 'bundle'; diff --git a/local-cli/dependencies/dependencies.js b/local-cli/dependencies/dependencies.js index 8ada423cf..adc1e8897 100644 --- a/local-cli/dependencies/dependencies.js +++ b/local-cli/dependencies/dependencies.js @@ -63,7 +63,6 @@ function _dependencies(argv, config, resolve, reject) { getTransformOptionsModulePath: config.getTransformOptionsModulePath, transformModulePath: args.transformer, verbose: config.verbose, - disableInternalTransforms: true, }; const relativePath = packageOpts.projectRoots.map(root => diff --git a/local-cli/server/util/attachHMRServer.js b/local-cli/server/util/attachHMRServer.js index 362fdadf5..353b2aad3 100644 --- a/local-cli/server/util/attachHMRServer.js +++ b/local-cli/server/util/attachHMRServer.js @@ -32,6 +32,7 @@ function attachHMRServer({httpServer, path, packagerServer}) { return packagerServer.getDependencies({ platform: platform, dev: true, + hot: true, entryFile: bundleEntry, }).then(response => { // for each dependency builds the object: @@ -143,6 +144,7 @@ function attachHMRServer({httpServer, path, packagerServer}) { return packagerServer.getDependencies({ platform: client.platform, dev: true, + hot: true, entryFile: filename, recursive: true, }).then(response => { diff --git a/packager/package.json b/packager/package.json index cf2306f0f..ab25c5cf2 100644 --- a/packager/package.json +++ b/packager/package.json @@ -1,5 +1,5 @@ { - "version": "0.1.4", + "version": "0.2.0", "name": "react-native-packager", "description": "Build native apps with React!", "repository": { diff --git a/packager/react-packager/src/Bundler/Bundle.js b/packager/react-packager/src/Bundler/Bundle.js index 9700af088..f42470272 100644 --- a/packager/react-packager/src/Bundler/Bundle.js +++ b/packager/react-packager/src/Bundler/Bundle.js @@ -11,53 +11,42 @@ const _ = require('underscore'); const base64VLQ = require('./base64-vlq'); const BundleBase = require('./BundleBase'); -const UglifyJS = require('uglify-js'); const ModuleTransport = require('../lib/ModuleTransport'); -const Activity = require('../Activity'); const crypto = require('crypto'); const SOURCEMAPPING_URL = '\n\/\/# sourceMappingURL='; -const minifyCode = code => - UglifyJS.minify(code, {fromString: true, ascii_only: true}).code; const getCode = x => x.code; -const getMinifiedCode = x => minifyCode(x.code); const getNameAndCode = ({name, code}) => ({name, code}); -const getNameAndMinifiedCode = - ({name, code}) => ({name, code: minifyCode(code)}); class Bundle extends BundleBase { - constructor(sourceMapUrl) { + constructor({sourceMapUrl, minify} = {}) { super(); this._sourceMap = false; this._sourceMapUrl = sourceMapUrl; this._shouldCombineSourceMaps = false; this._numPrependedModules = 0; this._numRequireCalls = 0; + this._minify = minify; } - addModule(resolver, response, module, transformed) { - return resolver.wrapModule( - response, + addModule(resolver, resolutionResponse, module, moduleTransport) { + return resolver.wrapModule({ + resolutionResponse, module, - transformed.code - ).then(({code, name}) => { - const moduleTransport = new ModuleTransport({ - code, - name, - map: transformed.map, - sourceCode: transformed.sourceCode, - sourcePath: transformed.sourcePath, - virtual: transformed.virtual, - }); - + name: moduleTransport.name, + code: moduleTransport.code, + map: moduleTransport.map, + meta: moduleTransport.meta, + minify: this._minify, + }).then(({code, map}) => { // If we get a map from the transformer we'll switch to a mode // were we're combining the source maps as opposed to - if (!this._shouldCombineSourceMaps && moduleTransport.map != null) { + if (!this._shouldCombineSourceMaps && map != null) { this._shouldCombineSourceMaps = true; } - super.addModule(moduleTransport); + super.addModule(new ModuleTransport({...moduleTransport, code, map})); }); } @@ -103,10 +92,6 @@ class Bundle extends BundleBase { options = options || {}; - if (options.minify) { - return this.getMinifiedSourceAndMap(options.dev).code; - } - let source = super.getSource(); if (options.inlineSourceMap) { @@ -118,84 +103,22 @@ class Bundle extends BundleBase { return source; } - getUnbundle({minify}) { - const allModules = super.getModules().slice(); + getUnbundle() { + const allModules = this.getModules().slice(); const prependedModules = this._numPrependedModules; const requireCalls = this._numRequireCalls; const modules = allModules .splice(prependedModules, allModules.length - requireCalls - prependedModules); - const startupCode = - allModules - .map(minify ? getMinifiedCode : getCode) - .join('\n'); + const startupCode = allModules.map(getCode).join('\n'); return { startupCode, - modules: - modules.map(minify ? getNameAndMinifiedCode : getNameAndCode) + modules: modules.map(getNameAndCode) }; } - getMinifiedSourceAndMap(dev) { - super.assertFinalized(); - - if (this._minifiedSourceAndMap) { - return this._minifiedSourceAndMap; - } - - let source = this.getSource(); - let map = this.getSourceMap(); - - if (!dev) { - const wpoActivity = Activity.startEvent('Whole Program Optimisations'); - const wpoResult = require('babel-core').transform(source, { - retainLines: true, - compact: true, - plugins: require('../transforms/whole-program-optimisations'), - inputSourceMap: map, - }); - Activity.endEvent(wpoActivity); - - source = wpoResult.code; - map = wpoResult.map; - } - - try { - const minifyActivity = Activity.startEvent('minify'); - this._minifiedSourceAndMap = UglifyJS.minify(source, { - fromString: true, - outSourceMap: this._sourceMapUrl, - inSourceMap: map, - output: {ascii_only: true}, - }); - Activity.endEvent(minifyActivity); - return this._minifiedSourceAndMap; - } catch(e) { - // Sometimes, when somebody is using a new syntax feature that we - // don't yet have transform for, the untransformed line is sent to - // uglify, and it chokes on it. This code tries to print the line - // and the module for easier debugging - let errorMessage = 'Error while minifying JS\n'; - if (e.line) { - errorMessage += 'Transformed code line: "' + - source.split('\n')[e.line - 1] + '"\n'; - } - if (e.pos) { - let fromIndex = source.lastIndexOf('__d(\'', e.pos); - if (fromIndex > -1) { - fromIndex += '__d(\''.length; - const toIndex = source.indexOf('\'', fromIndex); - errorMessage += 'Module name (best guess): ' + - source.substring(fromIndex, toIndex) + '\n'; - } - } - errorMessage += e.toString(); - throw new Error(errorMessage); - } - } - /** * I found a neat trick in the sourcemap spec that makes it easy * to concat sourcemaps. The `sections` field allows us to combine @@ -211,14 +134,17 @@ class Bundle extends BundleBase { }; let line = 0; - super.getModules().forEach(function(module) { + this.getModules().forEach(module => { let map = module.map; + if (module.virtual) { map = generateSourceMapForVirtualModule(module); } if (options.excludeSource) { - map = _.extend({}, map, {sourcesContent: []}); + if (map.sourcesContent && map.sourcesContent.length) { + map = _.extend({}, map, {sourcesContent: []}); + } } result.sections.push({ @@ -234,25 +160,20 @@ class Bundle extends BundleBase { getSourceMap(options) { super.assertFinalized(); - options = options || {}; - - if (options.minify) { - return this.getMinifiedSourceAndMap(options.dev).map; - } - if (this._shouldCombineSourceMaps) { return this._getCombinedSourceMaps(options); } const mappings = this._getMappings(); + const modules = this.getModules(); const map = { file: this._getSourceMapFile(), - sources: _.pluck(super.getModules(), 'sourcePath'), + sources: modules.map(module => module.sourcePath), version: 3, names: [], mappings: mappings, sourcesContent: options.excludeSource - ? [] : _.pluck(super.getModules(), 'sourceCode') + ? [] : modules.map(module => module.sourceCode) }; return map; } @@ -316,12 +237,10 @@ class Bundle extends BundleBase { } getJSModulePaths() { - return super.getModules().filter(function(module) { + return this.getModules() // Filter out non-js files. Like images etc. - return !module.virtual; - }).map(function(module) { - return module.sourcePath; - }); + .filter(module => !module.virtual) + .map(module => module.sourcePath); } getDebugInfo() { @@ -338,7 +257,7 @@ class Bundle extends BundleBase { '}', '', '

Module paths and transformed code:

', - super.getModules().map(function(m) { + this.getModules().map(function(m) { return '

Path:

' + m.sourcePath + '

Source:

' + '
'; @@ -354,15 +273,17 @@ class Bundle extends BundleBase { sourceMapUrl: this._sourceMapUrl, numPrependedModules: this._numPrependedModules, numRequireCalls: this._numRequireCalls, + shouldCombineSourceMaps: this._shouldCombineSourceMaps, }; } static fromJSON(json) { - const bundle = new Bundle(json.sourceMapUrl); + const bundle = new Bundle({sourceMapUrl: json.sourceMapUrl}); bundle._sourceMapUrl = json.sourceMapUrl; bundle._numPrependedModules = json.numPrependedModules; bundle._numRequireCalls = json.numRequireCalls; + bundle._shouldCombineSourceMaps = json.shouldCombineSourceMaps; BundleBase.fromJSON(bundle, json); diff --git a/packager/react-packager/src/Bundler/HMRBundle.js b/packager/react-packager/src/Bundler/HMRBundle.js index 728507788..d9725eea1 100644 --- a/packager/react-packager/src/Bundler/HMRBundle.js +++ b/packager/react-packager/src/Bundler/HMRBundle.js @@ -21,21 +21,14 @@ class HMRBundle extends BundleBase { this._sourceMappingURLs = []; } - addModule(resolver, response, module, transformed) { - return resolver.resolveRequires(response, + addModule(resolver, response, module, moduleTransport) { + return resolver.resolveRequires( + response, module, - transformed.code, - ).then(({name, code}) => { - const moduleTransport = new ModuleTransport({ - code, - name, - map: transformed.map, - sourceCode: transformed.sourceCode, - sourcePath: transformed.sourcePath, - virtual: transformed.virtual, - }); - - super.addModule(moduleTransport); + moduleTransport.code, + moduleTransport.meta.dependencyOffsets, + ).then(code => { + super.addModule(new ModuleTransport({...moduleTransport, code})); this._sourceMappingURLs.push(this._sourceMappingURLFn(moduleTransport.sourcePath)); this._sourceURLs.push(this._sourceURLFn(moduleTransport.sourcePath)); }); diff --git a/packager/react-packager/src/Bundler/__tests__/Bundle-test.js b/packager/react-packager/src/Bundler/__tests__/Bundle-test.js index 23fe5762d..87a5a432f 100644 --- a/packager/react-packager/src/Bundler/__tests__/Bundle-test.js +++ b/packager/react-packager/src/Bundler/__tests__/Bundle-test.js @@ -11,17 +11,15 @@ jest.autoMockOff(); const Bundle = require('../Bundle'); -const ModuleTransport = require('../../lib/ModuleTransport'); const Promise = require('Promise'); const SourceMapGenerator = require('source-map').SourceMapGenerator; -const UglifyJS = require('uglify-js'); const crypto = require('crypto'); describe('Bundle', () => { var bundle; beforeEach(() => { - bundle = new Bundle('test_url'); + bundle = new Bundle({sourceMapUrl: 'test_url'}); bundle.getSourceMap = jest.genMockFn().mockImpl(() => { return 'test-source-map'; }); @@ -108,34 +106,11 @@ describe('Bundle', () => { ].join('\n')); }); }); - - pit('should get minified source', () => { - const minified = { - code: 'minified', - map: 'map', - }; - - UglifyJS.minify = function() { - return minified; - }; - - return Promise.resolve().then(() => { - return addModule({ - bundle, - code: 'transformed foo;', - sourceCode: 'source foo', - sourcePath: 'foo path', - }); - }).then(() => { - bundle.finalize(); - expect(bundle.getMinifiedSourceAndMap({dev: true})).toBe(minified); - }); - }); }); describe('sourcemap bundle', () => { pit('should create sourcemap', () => { - const otherBundle = new Bundle('test_url'); + const otherBundle = new Bundle({sourceMapUrl: 'test_url'}); return Promise.resolve().then(() => { return addModule({ @@ -179,7 +154,7 @@ describe('Bundle', () => { }); pit('should combine sourcemaps', () => { - const otherBundle = new Bundle('test_url'); + const otherBundle = new Bundle({sourceMapUrl: 'test_url'}); return Promise.resolve().then(() => { return addModule({ @@ -269,7 +244,7 @@ describe('Bundle', () => { describe('getAssets()', () => { it('should save and return asset objects', () => { - var p = new Bundle('test_url'); + var p = new Bundle({sourceMapUrl: 'test_url'}); var asset1 = {}; var asset2 = {}; p.addAsset(asset1); @@ -281,7 +256,7 @@ describe('Bundle', () => { describe('getJSModulePaths()', () => { pit('should return module paths', () => { - var otherBundle = new Bundle('test_url'); + var otherBundle = new Bundle({sourceMapUrl: 'test_url'}); return Promise.resolve().then(() => { return addModule({ bundle: otherBundle, @@ -305,7 +280,7 @@ describe('Bundle', () => { describe('getEtag()', function() { it('should return an etag', function() { - var bundle = new Bundle('test_url'); + var bundle = new Bundle({sourceMapUrl: 'test_url'}); bundle.finalize({}); var eTag = crypto.createHash('md5').update(bundle.getSource()).digest('hex'); expect(bundle.getEtag()).toEqual(eTag); @@ -365,19 +340,17 @@ function genSourceMap(modules) { return sourceMapGen.toJSON(); } -function resolverFor(code) { +function resolverFor(code, map) { return { - wrapModule: (response, module, sourceCode) => Promise.resolve( - {name: 'name', code} - ), + wrapModule: () => Promise.resolve({code, map}), }; } function addModule({bundle, code, sourceCode, sourcePath, map, virtual}) { return bundle.addModule( - resolverFor(code), + resolverFor(code, map), null, null, - {sourceCode, sourcePath, map, virtual} + {code, sourceCode, sourcePath, map, virtual} ); } diff --git a/packager/react-packager/src/Bundler/__tests__/Bundler-test.js b/packager/react-packager/src/Bundler/__tests__/Bundler-test.js index 1c72d0a42..e108177e2 100644 --- a/packager/react-packager/src/Bundler/__tests__/Bundler-test.js +++ b/packager/react-packager/src/Bundler/__tests__/Bundler-test.js @@ -19,7 +19,6 @@ jest jest.mock('fs'); var Bundler = require('../'); -var JSTransformer = require('../../JSTransformer'); var Resolver = require('../../Resolver'); var sizeOf = require('image-size'); var fs = require('fs'); @@ -43,25 +42,29 @@ describe('Bundler', function() { isJSON() { return isJSON; }, isAsset() { return isAsset; }, isAsset_DEPRECATED() { return isAsset_DEPRECATED; }, + read: () => ({ + code: 'arbitrary', + source: 'arbitrary', + }), }; } var getDependencies; var getModuleSystemDependencies; - var wrapModule; var bundler; var assetServer; var modules; + var projectRoots; beforeEach(function() { getDependencies = jest.genMockFn(); getModuleSystemDependencies = jest.genMockFn(); - wrapModule = jest.genMockFn(); + projectRoots = ['/root']; + Resolver.mockImpl(function() { return { getDependencies: getDependencies, getModuleSystemDependencies: getModuleSystemDependencies, - wrapModule: wrapModule, }; }); @@ -80,7 +83,7 @@ describe('Bundler', function() { }; bundler = new Bundler({ - projectRoots: ['/root'], + projectRoots, assetServer: assetServer, }); @@ -109,34 +112,18 @@ describe('Bundler', function() { }), ]; - getDependencies.mockImpl(function() { - return Promise.resolve({ + getDependencies.mockImpl((main, options, transformOptions) => + Promise.resolve({ mainModuleId: 'foo', - dependencies: modules - }); - }); + dependencies: modules, + transformOptions, + }) + ); getModuleSystemDependencies.mockImpl(function() { return []; }); - JSTransformer.prototype.loadFileAndTransform - .mockImpl(function(path) { - return Promise.resolve({ - code: 'transformed ' + path, - map: 'sourcemap ' + path, - sourceCode: 'source ' + path, - sourcePath: path - }); - }); - - wrapModule.mockImpl(function(response, module, code) { - return module.getName().then(name => ({ - name, - code: 'lol ' + code + ' lol' - })); - }); - sizeOf.mockImpl(function(path, cb) { cb(null, { width: 50, height: 100 }); }); @@ -207,10 +194,20 @@ describe('Bundler', function() { }); pit('gets the list of dependencies from the resolver', function() { - return bundler.getDependencies('/root/foo.js', true).then(() => + const entryFile = '/root/foo.js'; + return bundler.getDependencies({entryFile, recursive: true}).then(() => expect(getDependencies).toBeCalledWith( '/root/foo.js', { dev: true, recursive: true }, + { minify: false, + dev: true, + transform: { + dev: true, + hot: false, + projectRoots, + } + }, + undefined, ) ); }); diff --git a/packager/react-packager/src/Bundler/index.js b/packager/react-packager/src/Bundler/index.js index 9da681d65..3c1c426df 100644 --- a/packager/react-packager/src/Bundler/index.js +++ b/packager/react-packager/src/Bundler/index.js @@ -26,7 +26,6 @@ const imageSize = require('image-size'); const version = require('../../../../package.json').version; const sizeOf = Promise.denodeify(imageSize); -const readFile = Promise.denodeify(fs.readFile); const noop = () => {}; @@ -82,10 +81,6 @@ const validateOpts = declareOpts({ type: 'number', required: false, }, - disableInternalTransforms: { - type: 'boolean', - default: false, - }, }); class Bundler { @@ -123,6 +118,10 @@ class Bundler { cacheKey: cacheKeyParts.join('$'), }); + this._transformer = new Transformer({ + transformModulePath: opts.transformModulePath, + }); + this._resolver = new Resolver({ projectRoots: opts.projectRoots, blacklistRE: opts.blacklistRE, @@ -132,14 +131,10 @@ class Bundler { fileWatcher: opts.fileWatcher, assetExts: opts.assetExts, cache: this._cache, - }); - - this._transformer = new Transformer({ - projectRoots: opts.projectRoots, - blacklistRE: opts.blacklistRE, - cache: this._cache, - transformModulePath: opts.transformModulePath, - disableInternalTransforms: opts.disableInternalTransforms, + transformCode: + (module, code, options) => + this._transformer.transformFile(module.path, code, options), + minifyCode: this._transformer.minify, }); this._projectRoots = opts.projectRoots; @@ -158,11 +153,11 @@ class Bundler { } bundle(options) { - const {dev, unbundle, platform} = options; + const {dev, minify, unbundle} = options; const moduleSystemDeps = - this._resolver.getModuleSystemDependencies({dev, unbundle, platform}); + this._resolver.getModuleSystemDependencies({dev, unbundle}); return this._bundle({ - bundle: new Bundle(options.sourceMapUrl), + bundle: new Bundle({minify, sourceMapUrl: options.sourceMapUrl}), moduleSystemDeps, ...options, }); @@ -230,7 +225,8 @@ class Bundler { entryFile, runModule: runMainModule, runBeforeMainModule, - dev: isDev, + dev, + minify, platform, moduleSystemDeps = [], hot, @@ -264,7 +260,8 @@ class Bundler { return this._buildBundle({ entryFile, - isDev, + dev, + minify, platform, bundle, hot, @@ -279,7 +276,7 @@ class Bundler { runModule: runMainModule, runBeforeMainModule, sourceMapUrl, - dev: isDev, + dev, platform, }) { const onModuleTransformed = ({module, transformed, response, bundle}) => { @@ -303,17 +300,19 @@ class Bundler { return this._buildBundle({ entryFile, - isDev, + dev, platform, onModuleTransformed, finalizeBundle, + minify: false, bundle: new PrepackBundle(sourceMapUrl), }); } _buildBundle({ entryFile, - isDev, + dev, + minify, platform, bundle, hot, @@ -323,54 +322,54 @@ class Bundler { finalizeBundle = noop, }) { const findEventId = Activity.startEvent('find dependencies'); + if (!resolutionResponse) { - resolutionResponse = this.getDependencies(entryFile, isDev, platform); + let onProgess; + if (process.stdout.isTTY) { + const bar = new ProgressBar( + 'transformed :current/:total (:percent)', + {complete: '=', incomplete: ' ', width: 40, total: 1}, + ); + onProgess = (_, total) => { + bar.total = total; + bar.tick(); + }; + } + + resolutionResponse = this.getDependencies( + {entryFile, dev, platform, hot, onProgess, minify}); } return Promise.resolve(resolutionResponse).then(response => { Activity.endEvent(findEventId); onResolutionResponse(response); - const transformEventId = Activity.startEvent('transform'); - const bar = process.stdout.isTTY - ? new ProgressBar('transforming [:bar] :percent :current/:total', { - complete: '=', - incomplete: ' ', - width: 40, - total: response.dependencies.length, - }) - : {tick() {}}; - const transformPromises = - response.dependencies.map(module => - this._transformModule({ - mainModuleName: response.mainModuleId, - bundle, + const toModuleTransport = module => + this._toModuleTransport({ + module, + bundle, + transformOptions: response.transformOptions, + }).then(transformed => { + onModuleTransformed({ module, - platform, - dev: isDev, - hot - }).then(transformed => { - bar.tick(); - onModuleTransformed({module, transformed, response, bundle}); - return {module, transformed}; - }) + response, + bundle, + transformed, + }); + return {module, transformed}; + }); + + return Promise.all(response.dependencies.map(toModuleTransport)) + .then(transformedModules => + Promise + .resolve(finalizeBundle({bundle, transformedModules, response})) + .then(() => bundle) ); - return Promise.all(transformPromises).then(transformedModules => { - Activity.endEvent(transformEventId); - return Promise - .resolve(finalizeBundle({bundle, transformedModules, response})) - .then(() => bundle); - }); }); } invalidateFile(filePath) { - if (this._transformOptionsModule) { - this._transformOptionsModule.onFileChange && - this._transformOptionsModule.onFileChange(); - } - - this._transformer.invalidateFile(filePath); + this._cache.invalidate(filePath); } getShallowDependencies(entryFile) { @@ -385,19 +384,35 @@ class Bundler { return this._resolver.getModuleForPath(entryFile); } - getDependencies(main, isDev, platform, recursive = true) { - return this._resolver.getDependencies( - main, - { - dev: isDev, + getDependencies({ + entryFile, + platform, + dev = true, + minify = !dev, + hot = false, + recursive = true, + onProgess, + }) { + return this.getTransformOptions( + entryFile, {dev, platform, hot, projectRoots: this._projectRoots} + ).then(transformSpecificOptions => { + const transformOptions = { + minify, + dev, platform, - recursive, - }, - ); + transform: transformSpecificOptions, + }; + return this._resolver.getDependencies( + entryFile, + {dev, platform, recursive}, + transformOptions, + onProgess, + ); + }); } getOrderedDependencyPaths({ entryFile, dev, platform }) { - return this.getDependencies(entryFile, dev, platform).then( + return this.getDependencies({entryFile, dev, platform}).then( ({ dependencies }) => { const ret = []; const promises = []; @@ -428,36 +443,32 @@ class Bundler { ); } - _transformModule({ - bundle, - module, - mainModuleName, - platform = null, - dev = true, - hot = false, - }) { + _toModuleTransport({module, bundle, transformOptions}) { + let moduleTransport; if (module.isAsset_DEPRECATED()) { - return this._generateAssetModule_DEPRECATED(bundle, module); + moduleTransport = this._generateAssetModule_DEPRECATED(bundle, module); } else if (module.isAsset()) { - return this._generateAssetModule(bundle, module, platform); - } else if (module.isJSON()) { - return generateJSONModule(module); - } else { - return this._getTransformOptions( - { - bundleEntry: mainModuleName, - platform: platform, - dev: dev, - modulePath: module.path, - }, - {hot}, - ).then(options => { - return this._transformer.loadFileAndTransform( - path.resolve(module.path), - options, - ); - }); + moduleTransport = this._generateAssetModule( + bundle, module, transformOptions.platform); } + + if (moduleTransport) { + return Promise.resolve(moduleTransport); + } + + return Promise.all([ + module.getName(), + module.read(transformOptions), + ]).then(( + [name, {code, dependencies, dependencyOffsets, map, source}] + ) => new ModuleTransport({ + name, + code, + map, + meta: {dependencies, dependencyOffsets}, + sourceCode: source, + sourcePath: module.path + })); } getGraphDebugInfo() { @@ -480,9 +491,10 @@ class Bundler { bundle.addAsset(img); - const code = 'module.exports = ' + JSON.stringify(img) + ';'; + const code = 'module.exports=' + JSON.stringify(img) + ';'; return new ModuleTransport({ + name: id, code: code, sourceCode: code, sourcePath: module.path, @@ -525,19 +537,31 @@ class Bundler { type: assetData.type, }; - const ASSET_TEMPLATE = 'module.exports = require("AssetRegistry").registerAsset(%json);'; - const code = ASSET_TEMPLATE.replace('%json', JSON.stringify(asset)); + const json = JSON.stringify(asset); + const code = + `module.exports = require('AssetRegistry').registerAsset(${json});`; + const dependencies = ['AssetRegistry']; + const dependencyOffsets = [code.indexOf('AssetRegistry') - 1]; - return {asset, code}; + return { + asset, + code, + meta: {dependencies, dependencyOffsets} + }; }); } _generateAssetModule(bundle, module, platform = null) { - return this._generateAssetObjAndCode(module, platform).then(({asset, code}) => { + return Promise.all([ + module.getName(), + this._generateAssetObjAndCode(module, platform), + ]).then(([name, {asset, code, meta}]) => { bundle.addAsset(asset); return new ModuleTransport({ - code: code, + name, + code, + meta, sourceCode: code, sourcePath: module.path, virtual: true, @@ -545,37 +569,15 @@ class Bundler { }); } - _getTransformOptions(config, options) { - const transformerOptions = this._transformOptionsModule - ? this._transformOptionsModule.get(Object.assign( - { - bundler: this, - platform: options.platform, - dev: options.dev, - }, - config, - )) - : Promise.resolve(null); - - return transformerOptions.then(overrides => { - return {...options, ...overrides}; - }); + getTransformOptions(mainModuleName, options) { + const extraOptions = this._transformOptionsModule + ? this._transformOptionsModule(mainModuleName, options, this) + : null; + return Promise.resolve(extraOptions) + .then(extraOptions => Object.assign(options, extraOptions)); } } -function generateJSONModule(module) { - return readFile(module.path).then(function(data) { - const code = 'module.exports = ' + data.toString('utf8') + ';'; - - return new ModuleTransport({ - code: code, - sourceCode: code, - sourcePath: module.path, - virtual: true, - }); - }); -} - function getPathRelativeToRoot(roots, absPath) { for (let i = 0; i < roots.length; i++) { const relPath = path.relative(roots[i], absPath); @@ -594,12 +596,4 @@ function verifyRootExists(root) { assert(fs.statSync(root).isDirectory(), 'Root has to be a valid directory'); } -class DummyCache { - get(filepath, field, loaderCb) { - return loaderCb(); - } - - end(){} - invalidate(filepath){} -} module.exports = Bundler; diff --git a/packager/react-packager/src/JSTransformer/__tests__/Transformer-test.js b/packager/react-packager/src/JSTransformer/__tests__/Transformer-test.js index cd73549c8..dc93a872f 100644 --- a/packager/react-packager/src/JSTransformer/__tests__/Transformer-test.js +++ b/packager/react-packager/src/JSTransformer/__tests__/Transformer-test.js @@ -12,61 +12,66 @@ jest .dontMock('../../lib/ModuleTransport') .dontMock('../'); -jest.mock('fs'); -jest.setMock('temp', {path: () => '/arbitrary/path'}); +const fs = {writeFileSync: jest.genMockFn()}; +const temp = {path: () => '/arbitrary/path'}; +const workerFarm = jest.genMockFn(); +jest.setMock('fs', fs); +jest.setMock('temp', temp); +jest.setMock('worker-farm', workerFarm); var Transformer = require('../'); -var fs = require('fs'); -var Cache; -var options; +const {any} = jasmine; describe('Transformer', function() { - var workers; + let options, workers, Cache; + const fileName = '/an/arbitrary/file.js'; + const transformModulePath = __filename; beforeEach(function() { Cache = jest.genMockFn(); Cache.prototype.get = jest.genMockFn().mockImpl((a, b, c) => c()); - workers = jest.genMockFn(); - jest.setMock('worker-farm', jest.genMockFn().mockImpl(function() { - return workers; - })); - - options = { - transformModulePath: '/foo/bar', - cache: new Cache({}), - }; + fs.writeFileSync.mockClear(); + options = {transformModulePath}; + workerFarm.mockClear(); + workerFarm.mockImpl((opts, path, methods) => { + const api = workers = {}; + methods.forEach(method => api[method] = jest.genMockFn()); + return api; + }); }); - pit('should loadFileAndTransform', function() { - workers.mockImpl(function(data, callback) { - callback(null, { code: 'transformed', map: 'sourceMap' }); - }); - fs.readFile.mockImpl(function(file, callback) { - callback(null, 'content'); + it('passes transform module path, file path, source code, and options to the worker farm when transforming', () => { + const transformOptions = {arbitrary: 'options'}; + const code = 'arbitrary(code)'; + new Transformer(options).transformFile(fileName, code, transformOptions); + expect(workers.transformAndExtractDependencies).toBeCalledWith( + transformModulePath, + fileName, + code, + transformOptions, + any(Function), + ); + }); + + pit('passes the data produced by the worker back', () => { + const transformer = new Transformer(options); + const result = { code: 'transformed', map: 'sourceMap' }; + workers.transformAndExtractDependencies.mockImpl(function(transformPath, filename, code, options, callback) { + callback(null, result); }); - return new Transformer(options).loadFileAndTransform('file') - .then(function(data) { - expect(data).toEqual({ - code: 'transformed', - map: 'sourceMap', - sourcePath: 'file', - sourceCode: 'content' - }); - }); + return transformer.transformFile(fileName, '', {}) + .then(data => expect(data).toBe(result)); }); pit('should add file info to parse errors', function() { + const transformer = new Transformer(options); var message = 'message'; var snippet = 'snippet'; - fs.readFile.mockImpl(function(file, callback) { - callback(null, 'var x;\nvar answer = 1 = x;'); - }); - - workers.mockImpl(function(data, callback) { + workers.transformAndExtractDependencies.mockImpl(function(transformPath, filename, code, options, callback) { var babelError = new SyntaxError(message); babelError.type = 'SyntaxError'; babelError.description = message; @@ -78,13 +83,13 @@ describe('Transformer', function() { callback(babelError); }); - return new Transformer(options).loadFileAndTransform('foo-file.js') + return transformer.transformFile(fileName, '', {}) .catch(function(error) { expect(error.type).toEqual('TransformError'); expect(error.message).toBe('SyntaxError ' + message); expect(error.lineNumber).toBe(2); expect(error.column).toBe(15); - expect(error.filename).toBe('foo-file.js'); + expect(error.filename).toBe(fileName); expect(error.description).toBe(message); expect(error.snippet).toBe(snippet); }); diff --git a/packager/react-packager/src/JSTransformer/__tests__/worker-test.js b/packager/react-packager/src/JSTransformer/__tests__/worker-test.js deleted file mode 100644 index f81907c2c..000000000 --- a/packager/react-packager/src/JSTransformer/__tests__/worker-test.js +++ /dev/null @@ -1,109 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ -'use strict'; - -jest.autoMockOff(); - -jest.mock('babel-core'); - -const worker = require('../worker'); -const babel = require('babel-core'); - -const code = 'code'; - -describe('Resolver', function() { - beforeEach(() => { - babel.transform.mockImpl((source, options) => source); - }); - - describe('when no external transform is provided', () => { - xit('should invoke internal transform if available', () => { - transform({ - sourceCode: 'code', - filename: 'test', - options: options({opts: {}, internalTransformsEnabled: true}), - }); - expect(babel.transform.mock.calls.length).toBe(1); - }); - - it('should not invoke internal transform if unavailable', () => { - transform({ - sourceCode: 'code', - filename: 'test', - options: options({opts: {}, internalTransformsEnabled: false}), - }); - expect(babel.transform.mock.calls.length).toBe(0); - }); - }); - - describe('when external transform is provided', () => { - xit('should invoke both transformers if internal is available', () => { - transform({ - sourceCode: code, - filename: 'test', - options: options({ - opts: { - externalTransformModulePath: require.resolve( - '../../../../transformer.js' - ), - }, - internalTransformsEnabled: true, - }), - }); - expect(babel.transform.mock.calls.length).toBe(2); - }); - - it('should invoke only external transformer if internal is not available', () => { - transform({ - sourceCode: 'code', - filename: 'test', - options: options({ - opts: { - externalTransformModulePath: require.resolve( - '../../../../transformer.js' - ), - }, - internalTransformsEnabled: false, - }), - }); - expect(babel.transform.mock.calls.length).toBe(1); - }); - - xit('should pipe errors through transform pipeline', () => { - const error = new Error('transform error'); - babel.transform.mockImpl((source, options) => { - throw error; - }); - - const callback = transform({ - sourceCode: 'code', - filename: 'test', - options: options({ - opts: { - externalTransformModulePath: require.resolve( - '../../../../transformer.js' - ), - }, - internalTransformsEnabled: true, - }), - }); - expect(callback.mock.calls[0][0]).toBe(error); - }); - }); -}); - -function transform(data) { - const callback = jest.genMockFunction(); - worker(data, callback); - return callback; -} - -function options({opts, internalTransformsEnabled}) { - return Object.assign(opts, {hot: internalTransformsEnabled}); -} diff --git a/packager/react-packager/src/JSTransformer/index.js b/packager/react-packager/src/JSTransformer/index.js index a25cd57d9..3ddd95bdf 100644 --- a/packager/react-packager/src/JSTransformer/index.js +++ b/packager/react-packager/src/JSTransformer/index.js @@ -8,18 +8,13 @@ */ 'use strict'; -const ModuleTransport = require('../lib/ModuleTransport'); const Promise = require('promise'); const declareOpts = require('../lib/declareOpts'); -const fs = require('fs'); const os = require('os'); -const temp = require('temp'); const util = require('util'); const workerFarm = require('worker-farm'); const debug = require('debug')('ReactNativePackager:JStransformer'); -const readFile = Promise.denodeify(fs.readFile); - // Avoid memory leaks caused in workers. This number seems to be a good enough number // to avoid any memory leak while not slowing down initial builds. // TODO(amasad): Once we get bundle splitting, we can drive this down a bit more. @@ -32,33 +27,14 @@ const DEFAULT_MAX_CALL_TIME = 301000; const MAX_RETRIES = 2; const validateOpts = declareOpts({ - projectRoots: { - type: 'array', - required: true, - }, - blacklistRE: { - type: 'object', // typeof regex is object - }, - polyfillModuleNames: { - type: 'array', - default: [], - }, transformModulePath: { type:'string', required: false, }, - cache: { - type: 'object', - required: true, - }, transformTimeoutInterval: { type: 'number', default: DEFAULT_MAX_CALL_TIME, }, - disableInternalTransforms: { - type: 'boolean', - default: false, - }, }); const maxConcurrentWorkers = ((cores, override) => { @@ -82,120 +58,67 @@ class Transformer { constructor(options) { const opts = this._opts = validateOpts(options); - this._cache = opts.cache; - this._transformModulePath = opts.transformModulePath; - this._projectRoots = opts.projectRoots; + const {transformModulePath} = opts; - if (opts.transformModulePath != null) { - let transformer; + if (transformModulePath) { + this._transformModulePath = require.resolve(transformModulePath); - if (opts.disableInternalTransforms) { - transformer = opts.transformModulePath; - } else { - transformer = this._workerWrapperPath = temp.path(); - fs.writeFileSync( - this._workerWrapperPath, - ` - module.exports = require(${JSON.stringify(require.resolve('./worker'))}); - require(${JSON.stringify(String(opts.transformModulePath))}); - ` - ); - } + this._workers = workerFarm( + { + autoStart: true, + maxConcurrentCallsPerWorker: 1, + maxConcurrentWorkers: maxConcurrentWorkers, + maxCallsPerWorker: MAX_CALLS_PER_WORKER, + maxCallTime: opts.transformTimeoutInterval, + maxRetries: MAX_RETRIES, + }, + require.resolve('./worker'), + ['minify', 'transformAndExtractDependencies'] + ); - this._workers = workerFarm({ - autoStart: true, - maxConcurrentCallsPerWorker: 1, - maxConcurrentWorkers: maxConcurrentWorkers, - maxCallsPerWorker: MAX_CALLS_PER_WORKER, - maxCallTime: opts.transformTimeoutInterval, - maxRetries: MAX_RETRIES, - }, transformer); - - this._transform = Promise.denodeify(this._workers); + this._transform = Promise.denodeify(this._workers.transformAndExtractDependencies); + this.minify = Promise.denodeify(this._workers.minify); } } kill() { this._workers && workerFarm.end(this._workers); - if (this._workerWrapperPath && - typeof this._workerWrapperPath === 'string') { - fs.unlink(this._workerWrapperPath, () => {}); // we don't care about potential errors here - } } - invalidateFile(filePath) { - this._cache.invalidate(filePath); - } - - loadFileAndTransform(filePath, options) { - if (this._transform == null) { + transformFile(fileName, code, options) { + if (!this._transform) { return Promise.reject(new Error('No transfrom module')); } + debug('transforming file', fileName); + return this + ._transform(this._transformModulePath, fileName, code, options) + .then(result => { + debug('done transforming file', fileName); + return result; + }) + .catch(error => { + if (error.type === 'TimeoutError') { + const timeoutErr = new Error( + `TimeoutError: transforming ${fileName} took longer than ` + + `${this._opts.transformTimeoutInterval / 1000} seconds.\n` + + `You can adjust timeout via the 'transformTimeoutInterval' option` + ); + timeoutErr.type = 'TimeoutError'; + throw timeoutErr; + } else if (error.type === 'ProcessTerminatedError') { + const uncaughtError = new Error( + 'Uncaught error in the transformer worker: ' + + this._opts.transformModulePath + ); + uncaughtError.type = 'ProcessTerminatedError'; + throw uncaughtError; + } - debug('transforming file', filePath); - - const optionsJSON = JSON.stringify(options); - - return this._cache.get( - filePath, - 'transformedSource-' + optionsJSON, - // TODO: use fastfs to avoid reading file from disk again - () => readFile(filePath).then( - buffer => { - const sourceCode = buffer.toString('utf8'); - - return this._transform({ - sourceCode, - filename: filePath, - options: { - ...options, - projectRoots: this._projectRoots, - externalTransformModulePath: this._transformModulePath, - }, - }).then(res => { - if (res.error) { - console.warn( - 'Error property on the result value from the transformer', - 'module is deprecated and will be removed in future versions.', - 'Please pass an error object as the first argument to the callback' - ); - throw formatError(res.error, filePath); - } - - debug('done transforming file', filePath); - - return new ModuleTransport({ - code: res.code, - map: res.map, - sourcePath: filePath, - sourceCode: sourceCode, - }); - }).catch(err => { - if (err.type === 'TimeoutError') { - const timeoutErr = new Error( - `TimeoutError: transforming ${filePath} took longer than ` + - `${this._opts.transformTimeoutInterval / 1000} seconds.\n` + - `You can adjust timeout via the 'transformTimeoutInterval' option` - ); - timeoutErr.type = 'TimeoutError'; - throw timeoutErr; - } else if (err.type === 'ProcessTerminatedError') { - const uncaughtError = new Error( - 'Uncaught error in the transformer worker: ' + - this._opts.transformModulePath - ); - uncaughtError.type = 'ProcessTerminatedError'; - throw uncaughtError; - } - - throw formatError(err, filePath); - }); - }) - ); + throw formatError(error, fileName); + }); } } - module.exports = Transformer; Transformer.TransformError = TransformError; diff --git a/packager/react-packager/src/JSTransformer/worker.js b/packager/react-packager/src/JSTransformer/worker.js deleted file mode 100644 index c8d388a41..000000000 --- a/packager/react-packager/src/JSTransformer/worker.js +++ /dev/null @@ -1,77 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ -'use strict'; - -var babel = require('babel-core'); -var makeInternalConfig = require('babel-preset-react-native/configs/internal'); - -// Runs internal transforms on the given sourceCode. Note that internal -// transforms should be run after the external ones to ensure that they run on -// Javascript code -function internalTransforms(sourceCode, filename, options) { - var internalBabelConfig = makeInternalConfig(options); - - if (!internalBabelConfig) { - return { - code: sourceCode, - filename: filename, - }; - } - - var result = babel.transform(sourceCode, Object.assign({ - filename: filename, - sourceFileName: filename, - }, internalBabelConfig)); - - return { - code: result.code, - filename: filename, - }; -} - -function onExternalTransformDone(data, callback, error, externalOutput) { - if (error) { - callback(error); - return; - } - - var result = internalTransforms( - externalOutput.code, - externalOutput.filename, - data.options - ); - - callback(null, result); -} - -module.exports = function(data, callback) { - try { - if (data.options.externalTransformModulePath) { - var externalTransformModule = require( - data.options.externalTransformModulePath - ); - externalTransformModule( - data, - onExternalTransformDone.bind(null, data, callback) - ); - } else { - onExternalTransformDone( - data, - callback, - null, - { - code: data.sourceCode, - filename: data.filename - } - ); - } - } catch (e) { - callback(e); - } -}; diff --git a/packager/react-packager/src/JSTransformer/worker/__tests__/constant-folding-test.js b/packager/react-packager/src/JSTransformer/worker/__tests__/constant-folding-test.js index a39a1e970..64ad751fe 100644 --- a/packager/react-packager/src/JSTransformer/worker/__tests__/constant-folding-test.js +++ b/packager/react-packager/src/JSTransformer/worker/__tests__/constant-folding-test.js @@ -16,6 +16,16 @@ function parse(code) { return babel.transform(code, {code: false, babelrc: false, compact: true}); } +const babelOptions = { + babelrc: false, + compact: true, + retainLines: false, +}; + +function normalize({code}) { + return babel.transform(code, babelOptions).code; +} + describe('constant expressions', () => { it('can optimize conditional expressions with constant conditions', () => { const code = ` @@ -29,7 +39,7 @@ describe('constant expressions', () => { 'foo'==='bar' ? b : c, f() ? g() : h() );`; - expect(constantFolding('arbitrary.js', parse(code)).code) + expect(normalize(constantFolding('arbitrary.js', parse(code)))) .toEqual(`a(true,true,2,true,{},{a:1},c,f()?g():h());`); }); @@ -39,7 +49,7 @@ describe('constant expressions', () => { var b = 'android' == 'android' ? ('production' != 'production' ? 'a' : 'A') : 'i';`; - expect(constantFolding('arbitrary.js', parse(code)).code) + expect(normalize(constantFolding('arbitrary.js', parse(code)))) .toEqual(`var a=1;var b='A';`); }); @@ -48,7 +58,7 @@ describe('constant expressions', () => { var a = true || 1; var b = 'android' == 'android' && 'production' != 'production' || null || "A";`; - expect(constantFolding('arbitrary.js', parse(code)).code) + expect(normalize(constantFolding('arbitrary.js', parse(code)))) .toEqual(`var a=true;var b="A";`); }); @@ -60,7 +70,7 @@ describe('constant expressions', () => { var d = null || z(); var e = !1 && z(); `; - expect(constantFolding('arbitrary.js', parse(code)).code) + expect(normalize(constantFolding('arbitrary.js', parse(code)))) .toEqual(`var a="truthy";var b=z();var c=null;var d=z();var e=false;`); }); @@ -70,7 +80,7 @@ describe('constant expressions', () => { var a = 1; } `; - expect(constantFolding('arbitrary.js', parse(code)).code) + expect(normalize(constantFolding('arbitrary.js', parse(code)))) .toEqual(``); }); @@ -86,7 +96,7 @@ describe('constant expressions', () => { var a = 'b'; } `; - expect(constantFolding('arbitrary.js', parse(code)).code) + expect(normalize(constantFolding('arbitrary.js', parse(code)))) .toEqual(`{var a=3;var b=a+4;}`); }); @@ -106,7 +116,7 @@ describe('constant expressions', () => { } } `; - expect(constantFolding('arbitrary.js', parse(code)).code) + expect(normalize(constantFolding('arbitrary.js', parse(code)))) .toEqual(`{{require('c');}}`); }); }); diff --git a/packager/react-packager/src/JSTransformer/worker/__tests__/extract-dependencies-test.js b/packager/react-packager/src/JSTransformer/worker/__tests__/extract-dependencies-test.js index a27def1dc..fba330eec 100644 --- a/packager/react-packager/src/JSTransformer/worker/__tests__/extract-dependencies-test.js +++ b/packager/react-packager/src/JSTransformer/worker/__tests__/extract-dependencies-test.js @@ -22,7 +22,7 @@ describe('Dependency extraction:', () => { } }); require - ('more');` + ('more');`; const {dependencies, dependencyOffsets} = extractDependencies(code); expect(dependencies) .toEqual(['foo/bar', 'React', 'Component', 'more']); @@ -34,11 +34,11 @@ describe('Dependency extraction:', () => { require('a'); foo.require('b'); bar. - require ( 'c').require('d')require('e')`; + require ( 'c').require('d');require('e')`; const {dependencies, dependencyOffsets} = extractDependencies(code); expect(dependencies).toEqual(['a', 'e']); - expect(dependencyOffsets).toEqual([15, 97]); + expect(dependencyOffsets).toEqual([15, 98]); }); it('does not extract require calls from strings', () => { @@ -51,7 +51,7 @@ describe('Dependency extraction:', () => { return require ( "Component" ); } }); - " \\" require('more')";` + " \\" require('more')";`; const {dependencies, dependencyOffsets} = extractDependencies(code); expect(dependencies).toEqual(['foo', 'Component']); @@ -85,12 +85,28 @@ describe('Dependency extraction:', () => { expect(dependencyOffsets).toEqual([]); }); + it('does not extract calls to require with non-static arguments', () => { + const code = `require('foo/' + bar)`; + + const {dependencies, dependencyOffsets} = extractDependencies(code); + expect(dependencies).toEqual([]); + expect(dependencyOffsets).toEqual([]); + }); + it('does not get confused by previous states', () => { // yes, this was a bug - const code = `require("a");/* a comment */ var a = /[a]/.test('a');` + const code = `require("a");/* a comment */ var a = /[a]/.test('a');`; const {dependencies, dependencyOffsets} = extractDependencies(code); expect(dependencies).toEqual(['a']); expect(dependencyOffsets).toEqual([8]); }); + + it('can handle regular expressions', () => { + const code = `require('a'); /["']/.test('foo'); require("b");`; + + const {dependencies, dependencyOffsets} = extractDependencies(code); + expect(dependencies).toEqual(['a', 'b']); + expect(dependencyOffsets).toEqual([8, 42]); + }); }); diff --git a/packager/react-packager/src/JSTransformer/worker/__tests__/minify-test.js b/packager/react-packager/src/JSTransformer/worker/__tests__/minify-test.js index e65594e7c..78450879e 100644 --- a/packager/react-packager/src/JSTransformer/worker/__tests__/minify-test.js +++ b/packager/react-packager/src/JSTransformer/worker/__tests__/minify-test.js @@ -21,91 +21,37 @@ const uglify = { jest.setMock('uglify-js', uglify); const minify = require('../minify'); -const {any} = jasmine; +const {objectContaining} = jasmine; describe('Minification:', () => { - const fileName = '/arbitrary/file.js'; - const DEPENDENCY_MARKER = '\u0002\ueffe\ue277\uead5'; + const filename = '/arbitrary/file.js'; + const code = 'arbitrary(code)'; let map; beforeEach(() => { uglify.minify.mockClear(); - map = {version: 3, sources: [fileName], mappings: ''}; + uglify.minify.mockReturnValue({code: '', map: '{}'}); + map = {version: 3, sources: ['?'], mappings: ''}; }); - it('passes the transformed code to `uglify.minify`, wrapped in an immediately invoked function expression', () => { - const code = 'arbitrary(code)'; - minify('', code, {}, [], []); - expect(uglify.minify).toBeCalledWith( - `(function(){${code}}());`, any(Object)); - }); - - it('uses the passed module locals as parameters of the IIFE', () => { - const moduleLocals = ['arbitrary', 'parameters']; - minify('', '', {}, [], moduleLocals); - expect(uglify.minify).toBeCalledWith( - `(function(${moduleLocals}){}());`, any(Object)); - }); - - it('passes the transformed source map to `uglify.minify`', () => { - minify('', '', map, [], []); - const [, options] = uglify.minify.mock.calls[0]; - expect(options.inSourceMap).toEqual(map); - }); - - it('passes the file name as `outSourceMap` to `uglify.minify` (uglify uses it for the `file` field on the source map)', () => { - minify(fileName, '', {}, [], []); - const [, options] = uglify.minify.mock.calls[0]; - expect(options.outSourceMap).toEqual(fileName); - }); - - it('inserts a marker for every dependency offset before minifing', () => { - const code = ` - var React = require('React'); - var Immutable = require('Immutable');`; - const dependencyOffsets = [27, 67]; - const expectedCode = - code.replace(/require\('/g, '$&' + DEPENDENCY_MARKER); - - minify('', code, {}, dependencyOffsets, []); - expect(uglify.minify).toBeCalledWith( - `(function(){${expectedCode}}());`, any(Object)); + it('passes file name, code, and source map to `uglify`', () => { + minify(filename, code, map); + expect(uglify.minify).toBeCalledWith(code, objectContaining({ + fromString: true, + inSourceMap: map, + outSourceMap: filename, + })); }); it('returns the code provided by uglify', () => { - const code = 'some(source) + code'; - uglify.minify.mockReturnValue({code: `!function(a,b,c){${code}}()`}); - - const result = minify('', '', {}, [], []); + uglify.minify.mockReturnValue({code, map: '{}'}); + const result = minify('', '', {}); expect(result.code).toBe(code); }); - it('extracts dependency offsets from the code provided by uglify', () => { - const code = ` - var a=r("${DEPENDENCY_MARKER}a-dependency"); - var b=r("\\x02\\ueffe\\ue277\\uead5b-dependency"); - var e=r(a()?'\\u0002\\ueffe\\ue277\\uead5c-dependency' - :'\x02\ueffe\ue277\uead5d-dependency');`; - uglify.minify.mockReturnValue({code: `!function(){${code}}());`}); - - const result = minify('', '', {}, [], []); - expect(result.dependencyOffsets).toEqual([15, 46, 81, 114]); - }); - - it('returns the source map object provided by uglify', () => { - uglify.minify.mockReturnValue({map, code: ''}); - const result = minify('', '', {}, [], []); - expect(result.map).toBe(map); - }); - - it('adds a `moduleLocals` object to the result that reflects the names of the minified module locals', () => { - const moduleLocals = ['arbitrary', 'parameters', 'here']; - uglify.minify.mockReturnValue({code: '(function(a,ll,d){}());'}); - const result = minify('', '', {}, [], moduleLocals); - expect(result.moduleLocals).toEqual({ - arbitrary: 'a', - parameters: 'll', - here: 'd', - }); + it('parses the source map object provided by uglify and sets the sources property', () => { + uglify.minify.mockReturnValue({map: JSON.stringify(map), code: ''}); + const result = minify(filename, '', {}); + expect(result.map).toEqual({...map, sources: [filename]}); }); }); diff --git a/packager/react-packager/src/JSTransformer/worker/__tests__/worker-test.js b/packager/react-packager/src/JSTransformer/worker/__tests__/worker-test.js index a6acb6689..b08035098 100644 --- a/packager/react-packager/src/JSTransformer/worker/__tests__/worker-test.js +++ b/packager/react-packager/src/JSTransformer/worker/__tests__/worker-test.js @@ -78,33 +78,6 @@ describe('code transformation worker:', () => { }); }); - it('puts an empty `moduleLocals` object on the result', done => { - transform.mockImplementation( - (_, callback) => callback(null, {code: 'arbitrary'})); - transformCode(transform, 'filename', 'code', {}, (_, data) => { - expect(data.moduleLocals).toEqual({}); - done(); - }); - }); - - it('if a `moduleLocals` array is passed, the `moduleLocals` object is a key mirror of its items', done => { - transform.mockImplementation( - (_, callback) => callback(null, {code: 'arbitrary'})); - const moduleLocals = - ['arbitrary', 'list', 'containing', 'variable', 'names']; - - transformCode(transform, 'filename', 'code', {moduleLocals}, (_, data) => { - expect(data.moduleLocals).toEqual({ - arbitrary: 'arbitrary', - list: 'list', - containing: 'containing', - variable: 'variable', - names: 'names', - }); - done(); - }); - }); - describe('dependency extraction:', () => { let code; @@ -155,21 +128,19 @@ describe('code transformation worker:', () => { }); describe('Minifications:', () => { - let constantFolding, extractDependencies, inline, minify, options; - let transformResult, dependencyData, moduleLocals; + let constantFolding, inline, options; + let transformResult, dependencyData; const filename = 'arbitrary/file.js'; - const foldedCode = 'arbitrary(folded(code));' - const foldedMap = {version: 3, sources: ['fold.js']} + const foldedCode = 'arbitrary(folded(code));'; + const foldedMap = {version: 3, sources: ['fold.js']}; beforeEach(() => { constantFolding = require('../constant-folding') .mockReturnValue({code: foldedCode, map: foldedMap}); extractDependencies = require('../extract-dependencies'); inline = require('../inline'); - minify = require('../minify').mockReturnValue({}); - moduleLocals = ['module', 'require', 'exports']; - options = {moduleLocals, minify: true}; + options = {minify: true}; dependencyData = { dependencies: ['a', 'b', 'c'], dependencyOffsets: [100, 120, 140] @@ -206,19 +177,6 @@ describe('code transformation worker:', () => { }); }); - it('passes the code obtained from `constant-folding` to `minify`', done => { - transformCode(transform, filename, 'code', options, () => { - expect(minify).toBeCalledWith( - filename, - foldedCode, - foldedMap, - dependencyData.dependencyOffsets, - moduleLocals - ); - done(); - }); - }); - it('uses the dependencies obtained from the optimized result', done => { transformCode(transform, filename, 'code', options, (_, result) => { expect(result.dependencies).toEqual(dependencyData.dependencies); @@ -226,17 +184,10 @@ describe('code transformation worker:', () => { }); }); - it('uses data produced by `minify` for the result', done => { - const minifyResult = { - code: 'minified(code)', - dependencyOffsets: [10, 30, 60], - map: {version: 3, sources: ['minified.js']}, - moduleLocals: {module: 'x', require: 'y', exports: 'z'}, - }; - minify.mockReturnValue(minifyResult); - + it('uses data produced by `constant-folding` for the result', done => { transformCode(transform, 'filename', 'code', options, (_, result) => { - expect(result).toEqual(objectContaining(minifyResult)) + expect(result) + .toEqual(objectContaining({code: foldedCode, map: foldedMap})); done(); }); }); diff --git a/packager/react-packager/src/JSTransformer/worker/constant-folding.js b/packager/react-packager/src/JSTransformer/worker/constant-folding.js index acf14f687..f896430f4 100644 --- a/packager/react-packager/src/JSTransformer/worker/constant-folding.js +++ b/packager/react-packager/src/JSTransformer/worker/constant-folding.js @@ -73,9 +73,12 @@ function constantFolding(filename, transformResult) { filename, plugins: [plugin], inputSourceMap: transformResult.map, + sourceMaps: true, + sourceFileName: filename, babelrc: false, compact: true, - }) + retainLines: true, + }); } module.exports = constantFolding; diff --git a/packager/react-packager/src/JSTransformer/worker/extract-dependencies.js b/packager/react-packager/src/JSTransformer/worker/extract-dependencies.js index 3ca221641..4e1bfb396 100644 --- a/packager/react-packager/src/JSTransformer/worker/extract-dependencies.js +++ b/packager/react-packager/src/JSTransformer/worker/extract-dependencies.js @@ -8,48 +8,8 @@ */ 'use strict'; -const SINGLE_QUOTE = "'".charCodeAt(0); -const DOUBLE_QUOTE = '"'.charCodeAt(0); -const BACKSLASH = '\\'.charCodeAt(0); -const SLASH = '/'.charCodeAt(0); -const NEWLINE = '\n'.charCodeAt(0); -const ASTERISK = '*'.charCodeAt(0); - -// dollar is the only regex special character valid in identifiers -const escapeRegExp = identifier => identifier.replace(/[$]/g, '\\$'); - -function binarySearch(indexes, index) { - var low = 0; - var high = indexes.length - 1; - var i = 0; - - if (indexes[low] === index) { - return low; - } - while (high - low > 1) { - var current = low + ((high - low) >>> 1); // right shift divides by 2 and floors - if (index === indexes[current]) { - return current; - } - if (index > indexes[current]) { - low = current; - } else { - high = current; - } - } - return low; -} - -function indexOfCharCode(string, needle, i) { - for (var charCode; (charCode = string.charCodeAt(i)); i++) { - if (charCode === needle) { - return i; - } - } - return -1; -} - -const reRequire = /(?:^|[^.\s])\s*\brequire\s*\(\s*(['"])(.*?)\1/g; +const babel = require('babel-core'); +const babylon = require('babylon'); /** * Extracts dependencies (module IDs imported with the `require` function) from @@ -66,61 +26,24 @@ const reRequire = /(?:^|[^.\s])\s*\brequire\s*\(\s*(['"])(.*?)\1/g; * The index points to the opening quote. */ function extractDependencies(code) { - const ranges = [0]; - // are we currently in a quoted string? -> SINGLE_QUOTE or DOUBLE_QUOTE, else undefined - var currentQuote; - // scan the code for string literals and comments. - for (var i = 0, charCode; (charCode = code.charCodeAt(i)); i++) { - if (charCode === BACKSLASH) { - i += 1; - continue; - } - - if (charCode === SLASH && currentQuote === undefined) { - var next = code.charCodeAt(i + 1); - var end = undefined; - if (next === SLASH) { - end = indexOfCharCode(code, NEWLINE, i + 2); - } else if (next === ASTERISK) { - end = code.indexOf('*/', i + 2) + 1; // assume valid JS input here - } - if (end === -1) { - // if the comment does not end, it goes to the end of the file - end += code.length; - } - if (end !== undefined) { - ranges.push(i, end); - i = end; - continue; - } - } - - var isQuoteStart = currentQuote === undefined && - (charCode === SINGLE_QUOTE || charCode === DOUBLE_QUOTE); - if (isQuoteStart || currentQuote === charCode) { - ranges.push(i); - currentQuote = currentQuote === charCode ? undefined : charCode; - } - } - ranges.push(i); - - // extract dependencies + const ast = babylon.parse(code); const dependencies = new Set(); const dependencyOffsets = []; - for (var match; (match = reRequire.exec(code)); ) { - // check whether the match is in a code range, and not inside of a string - // literal or a comment - if (binarySearch(ranges, match.index) % 2 === 0) { - dependencies.add(match[2]); - dependencyOffsets.push( - match[0].length - match[2].length - 2 + match.index); - } - } - return { - dependencyOffsets, - dependencies: Array.from(dependencies.values()), - }; + babel.traverse(ast, { + CallExpression(path) { + const node = path.node; + const callee = node.callee; + const arg = node.arguments[0]; + if (callee.type !== 'Identifier' || callee.name !== 'require' || !arg || arg.type !== 'StringLiteral') { + return; + } + dependencyOffsets.push(arg.start); + dependencies.add(arg.value); + } + }); + + return {dependencyOffsets, dependencies: Array.from(dependencies)}; } module.exports = extractDependencies; diff --git a/packager/react-packager/src/JSTransformer/worker/index.js b/packager/react-packager/src/JSTransformer/worker/index.js index bc6e93338..1b68feaed 100644 --- a/packager/react-packager/src/JSTransformer/worker/index.js +++ b/packager/react-packager/src/JSTransformer/worker/index.js @@ -13,12 +13,6 @@ const extractDependencies = require('./extract-dependencies'); const inline = require('./inline'); const minify = require('./minify'); -function keyMirrorFromArray(array) { - var keyMirror = {}; - array.forEach(key => keyMirror[key] = key); - return keyMirror; -} - function makeTransformParams(filename, sourceCode, options) { if (filename.endsWith('.json')) { sourceCode = 'module.exports=' + sourceCode; @@ -28,7 +22,6 @@ function makeTransformParams(filename, sourceCode, options) { function transformCode(transform, filename, sourceCode, options, callback) { const params = makeTransformParams(filename, sourceCode, options.transform); - const moduleLocals = options.moduleLocals || []; const isJson = filename.endsWith('.json'); transform(params, (error, transformed) => { @@ -52,28 +45,35 @@ function transformCode(transform, filename, sourceCode, options, callback) { code = code.replace(/^\w+\.exports=/, ''); } - const moduleLocals = options.moduleLocals || []; - const dependencyData = isJson || options.extern + const result = isJson || options.extern ? {dependencies: [], dependencyOffsets: []} : extractDependencies(code); - var result; - if (options.minify) { - result = minify( - filename, code, map, dependencyData.dependencyOffsets, moduleLocals); - result.dependencies = dependencyData.dependencies; - } else { - result = dependencyData; - result.code = code; - result.map = map; - result.moduleLocals = keyMirrorFromArray(moduleLocals); - } + result.code = code; + result.map = map; callback(null, result); }); } -module.exports = function(transform, filename, sourceCode, options, callback) { +exports.transformAndExtractDependencies = ( + transform, + filename, + sourceCode, + options, + callback +) => { transformCode(require(transform), filename, sourceCode, options || {}, callback); }; -module.exports.transformCode = transformCode; // for easier testing + +exports.minify = (filename, code, sourceMap, callback) => { + var result; + try { + result = minify(filename, code, sourceMap); + } catch (error) { + callback(error); + } + callback(null, result); +}; + +exports.transformCode = transformCode; // for easier testing diff --git a/packager/react-packager/src/JSTransformer/worker/inline.js b/packager/react-packager/src/JSTransformer/worker/inline.js index 2a419933a..a294bcefc 100644 --- a/packager/react-packager/src/JSTransformer/worker/inline.js +++ b/packager/react-packager/src/JSTransformer/worker/inline.js @@ -89,6 +89,8 @@ function inline(filename, transformResult, options) { filename, plugins: [[plugin, options]], inputSourceMap: transformResult.map, + sourceMaps: true, + sourceFileName: filename, code: false, babelrc: false, compact: true, diff --git a/packager/react-packager/src/JSTransformer/worker/minify.js b/packager/react-packager/src/JSTransformer/worker/minify.js index d34359187..089cfc787 100644 --- a/packager/react-packager/src/JSTransformer/worker/minify.js +++ b/packager/react-packager/src/JSTransformer/worker/minify.js @@ -10,63 +10,10 @@ const uglify = require('uglify-js'); -const MAGIC_MARKER = '\u0002\ueffe\ue277\uead5'; -const MAGIC_MARKER_SPLITTER = - /(?:\x02|\\u0002|\\x02)(?:\ueffe|\\ueffe)(?:\ue277|\\ue277)(?:\uead5|\\uead5)/; - -// IIFE = "immediately invoked function expression" -// we wrap modules in functions to allow the minifier to mangle local variables -function wrapCodeInIIFE(code, moduleLocals) { - return `(function(${moduleLocals.join(',')}){${code}}());`; -} - -function extractCodeFromIIFE(code) { - return code.substring(code.indexOf('{') + 1, code.lastIndexOf('}')); -} - -function extractModuleLocalsFromIIFE(code) { - return code.substring(code.indexOf('(', 1) + 1, code.indexOf(')')).split(','); -} - -function splitFirstElementAt(array, offset) { - const first = array.shift(); - array.unshift(first.slice(0, offset + 1), first.slice(offset + 1)); - return array; -} - -function insertMarkers(code, dependencyOffsets) { - return dependencyOffsets - .reduceRight(splitFirstElementAt, [code]) - .join(MAGIC_MARKER); -} - -function extractMarkers(codeWithMarkers) { - const dependencyOffsets = []; - const codeBits = codeWithMarkers.split(MAGIC_MARKER_SPLITTER); - var offset = 0; - for (var i = 0, max = codeBits.length - 1; i < max; i++) { - offset += codeBits[i].length; - dependencyOffsets.push(offset - 1); - } - - return {code: codeBits.join(''), dependencyOffsets}; -} - -function minify(filename, code, map, dependencyOffsets, moduleLocals) { - // before minifying, code is wrapped in an immediately invoked function - // expression, so that top level variables can be shortened safely - code = wrapCodeInIIFE( - // since we don't know where the strings specifying dependencies will be - // located in the minified code, we mark them with a special marker string - // and extract them afterwards. - // That way, post-processing code can use these positions - insertMarkers(code, dependencyOffsets), - moduleLocals - ); - +function minify(filename, code, sourceMap) { const minifyResult = uglify.minify(code, { fromString: true, - inSourceMap: map, + inSourceMap: sourceMap, outSourceMap: filename, output: { ascii_only: true, @@ -74,15 +21,9 @@ function minify(filename, code, map, dependencyOffsets, moduleLocals) { }, }); - const minifiedModuleLocals = extractModuleLocalsFromIIFE(minifyResult.code); - const codeWithMarkers = extractCodeFromIIFE(minifyResult.code); - const result = extractMarkers(codeWithMarkers); - result.map = minifyResult.map; - result.moduleLocals = {}; - moduleLocals.forEach( - (key, i) => result.moduleLocals[key] = minifiedModuleLocals[i]); - - return result; + minifyResult.map = JSON.parse(minifyResult.map); + minifyResult.map.sources = [filename]; + return minifyResult; } module.exports = minify; diff --git a/packager/react-packager/src/Resolver/__tests__/Resolver-test.js b/packager/react-packager/src/Resolver/__tests__/Resolver-test.js index b12ebd48d..df6f609e8 100644 --- a/packager/react-packager/src/Resolver/__tests__/Resolver-test.js +++ b/packager/react-packager/src/Resolver/__tests__/Resolver-test.js @@ -31,6 +31,7 @@ describe('Resolver', function() { this.getName = jest.genMockFn(); this.getDependencies = jest.genMockFn(); this.isPolyfill = jest.genMockFn().mockReturnValue(false); + this.isJSON = jest.genMockFn().mockReturnValue(false); }); Polyfill = jest.genMockFn().mockImpl(function() { var polyfill = new Module(); @@ -61,6 +62,10 @@ describe('Resolver', function() { finalize() { return Promise.resolve(this); } + + getResolvedDependencyPairs() { + return []; + } } function createModule(id, dependencies) { @@ -70,6 +75,12 @@ describe('Resolver', function() { return module; } + function createJsonModule(id) { + const module = createModule(id, []); + module.isJSON.mockReturnValue(true); + return module; + } + function createPolyfill(id, dependencies) { var polyfill = new Polyfill({}); polyfill.getName = jest.genMockFn().mockImpl(() => Promise.resolve(id)); @@ -79,6 +90,23 @@ describe('Resolver', function() { } describe('getDependencies', function() { + it('forwards transform options to the dependency graph', function() { + const transformOptions = {arbitrary: 'options'}; + const platform = 'ios'; + const entry = '/root/index.js'; + + DependencyGraph.prototype.getDependencies.mockImplementation( + () => Promise.reject()); + new Resolver({projectRoot: '/root', }) + .getDependencies(entry, {platform}, transformOptions); + expect(DependencyGraph.prototype.getDependencies).toBeCalledWith({ + entryPath: entry, + platform: platform, + transformOptions: transformOptions, + recursive: true, + }); + }); + pit('should get dependencies with polyfills', function() { var module = createModule('index'); var deps = [module]; @@ -242,388 +270,8 @@ describe('Resolver', function() { projectRoot: '/root', }); - var dependencies = ['x', 'y', 'z', 'a', 'b']; - /*eslint-disable */ var code = [ - // single line import - "import'x';", - "import 'x';", - "import 'x' ;", - "import Default from 'x';", - "import * as All from 'x';", - "import {} from 'x';", - "import { } from 'x';", - "import {Foo} from 'x';", - "import { Foo } from 'x';", - "import { Foo, } from 'x';", - "import {Foo as Bar} from 'x';", - "import { Foo as Bar } from 'x';", - "import { Foo as Bar, } from 'x';", - "import { Foo, Bar } from 'x';", - "import { Foo, Bar, } from 'x';", - "import { Foo as Bar, Baz } from 'x';", - "import { Foo as Bar, Baz, } from 'x';", - "import { Foo, Bar as Baz } from 'x';", - "import { Foo, Bar as Baz, } from 'x';", - "import { Foo as Bar, Baz as Qux } from 'x';", - "import { Foo as Bar, Baz as Qux, } from 'x';", - "import { Foo, Bar, Baz } from 'x';", - "import { Foo, Bar, Baz, } from 'x';", - "import { Foo as Bar, Baz, Qux } from 'x';", - "import { Foo as Bar, Baz, Qux, } from 'x';", - "import { Foo, Bar as Baz, Qux } from 'x';", - "import { Foo, Bar as Baz, Qux, } from 'x';", - "import { Foo, Bar, Baz as Qux } from 'x';", - "import { Foo, Bar, Baz as Qux, } from 'x';", - "import { Foo as Bar, Baz as Qux, Norf } from 'x';", - "import { Foo as Bar, Baz as Qux, Norf, } from 'x';", - "import { Foo as Bar, Baz, Qux as Norf } from 'x';", - "import { Foo as Bar, Baz, Qux as Norf, } from 'x';", - "import { Foo, Bar as Baz, Qux as Norf } from 'x';", - "import { Foo, Bar as Baz, Qux as Norf, } from 'x';", - "import { Foo as Bar, Baz as Qux, Norf as Enuf } from 'x';", - "import { Foo as Bar, Baz as Qux, Norf as Enuf, } from 'x';", - "import Default, * as All from 'x';", - "import Default, { } from 'x';", - "import Default, { Foo } from 'x';", - "import Default, { Foo, } from 'x';", - "import Default, { Foo as Bar } from 'x';", - "import Default, { Foo as Bar, } from 'x';", - "import Default, { Foo, Bar } from 'x';", - "import Default, { Foo, Bar, } from 'x';", - "import Default, { Foo as Bar, Baz } from 'x';", - "import Default, { Foo as Bar, Baz, } from 'x';", - "import Default, { Foo, Bar as Baz } from 'x';", - "import Default, { Foo, Bar as Baz, } from 'x';", - "import Default, { Foo as Bar, Baz as Qux } from 'x';", - "import Default, { Foo as Bar, Baz as Qux, } from 'x';", - "import Default, { Foo, Bar, Baz } from 'x';", - "import Default, { Foo, Bar, Baz, } from 'x';", - "import Default, { Foo as Bar, Baz, Qux } from 'x';", - "import Default, { Foo as Bar, Baz, Qux, } from 'x';", - "import Default, { Foo, Bar as Baz, Qux } from 'x';", - "import Default, { Foo, Bar as Baz, Qux, } from 'x';", - "import Default, { Foo, Bar, Baz as Qux } from 'x';", - "import Default, { Foo, Bar, Baz as Qux, } from 'x';", - "import Default, { Foo as Bar, Baz as Qux, Norf } from 'x';", - "import Default, { Foo as Bar, Baz as Qux, Norf, } from 'x';", - "import Default, { Foo as Bar, Baz, Qux as Norf } from 'x';", - "import Default, { Foo as Bar, Baz, Qux as Norf, } from 'x';", - "import Default, { Foo, Bar as Baz, Qux as Norf } from 'x';", - "import Default, { Foo, Bar as Baz, Qux as Norf, } from 'x';", - "import Default, { Foo as Bar, Baz as Qux, Norf as NoMore } from 'x';", - "import Default, { Foo as Bar, Baz as Qux, Norf as NoMore, } from 'x';", - "import Default , { } from 'x';", - 'import "x";', - 'import Default from "x";', - 'import * as All from "x";', - 'import { } from "x";', - 'import { Foo } from "x";', - 'import { Foo, } from "x";', - 'import { Foo as Bar } from "x";', - 'import { Foo as Bar, } from "x";', - 'import { Foo, Bar } from "x";', - 'import { Foo, Bar, } from "x";', - 'import { Foo as Bar, Baz } from "x";', - 'import { Foo as Bar, Baz, } from "x";', - 'import { Foo, Bar as Baz } from "x";', - 'import { Foo, Bar as Baz, } from "x";', - 'import { Foo as Bar, Baz as Qux } from "x";', - 'import { Foo as Bar, Baz as Qux, } from "x";', - 'import { Foo, Bar, Baz } from "x";', - 'import { Foo, Bar, Baz, } from "x";', - 'import { Foo as Bar, Baz, Qux } from "x";', - 'import { Foo as Bar, Baz, Qux, } from "x";', - 'import { Foo, Bar as Baz, Qux } from "x";', - 'import { Foo, Bar as Baz, Qux, } from "x";', - 'import { Foo, Bar, Baz as Qux } from "x";', - 'import { Foo, Bar, Baz as Qux, } from "x";', - 'import { Foo as Bar, Baz as Qux, Norf } from "x";', - 'import { Foo as Bar, Baz as Qux, Norf, } from "x";', - 'import { Foo as Bar, Baz, Qux as Norf } from "x";', - 'import { Foo as Bar, Baz, Qux as Norf, } from "x";', - 'import { Foo, Bar as Baz, Qux as Norf } from "x";', - 'import { Foo, Bar as Baz, Qux as Norf, } from "x";', - 'import { Foo as Bar, Baz as Qux, Norf as NoMore } from "x";', - 'import { Foo as Bar, Baz as Qux, Norf as NoMore, } from "x";', - 'import Default, * as All from "x";', - 'import Default, { } from "x";', - 'import Default, { Foo } from "x";', - 'import Default, { Foo, } from "x";', - 'import Default, { Foo as Bar } from "x";', - 'import Default, { Foo as Bar, } from "x";', - 'import Default, { Foo, Bar } from "x";', - 'import Default, { Foo, Bar, } from "x";', - 'import Default, { Foo as Bar, Baz } from "x";', - 'import Default, { Foo as Bar, Baz, } from "x";', - 'import Default, { Foo, Bar as Baz } from "x";', - 'import Default, { Foo, Bar as Baz, } from "x";', - 'import Default, { Foo as Bar, Baz as Qux } from "x";', - 'import Default, { Foo as Bar, Baz as Qux, } from "x";', - 'import Default, { Foo, Bar, Baz } from "x";', - 'import Default, { Foo, Bar, Baz, } from "x";', - 'import Default, { Foo as Bar, Baz, Qux } from "x";', - 'import Default, { Foo as Bar, Baz, Qux, } from "x";', - 'import Default, { Foo, Bar as Baz, Qux } from "x";', - 'import Default, { Foo, Bar as Baz, Qux, } from "x";', - 'import Default, { Foo, Bar, Baz as Qux } from "x";', - 'import Default, { Foo, Bar, Baz as Qux, } from "x";', - 'import Default, { Foo as Bar, Baz as Qux, Norf } from "x";', - 'import Default, { Foo as Bar, Baz as Qux, Norf, } from "x";', - 'import Default, { Foo as Bar, Baz, Qux as Norf } from "x";', - 'import Default, { Foo as Bar, Baz, Qux as Norf, } from "x";', - 'import Default, { Foo, Bar as Baz, Qux as Norf } from "x";', - 'import Default, { Foo, Bar as Baz, Qux as Norf, } from "x";', - 'import Default, { Foo as Bar, Baz as Qux, Norf as Enuf } from "x";', - 'import Default, { Foo as Bar, Baz as Qux, Norf as Enuf, } from "x";', - 'import Default from "y";', - 'import * as All from \'z\';', - // import with support for new lines - "import { Foo,\n Bar }\n from 'x';", - "import { \nFoo,\nBar,\n }\n from 'x';", - "import { Foo as Bar,\n Baz\n }\n from 'x';", - "import { \nFoo as Bar,\n Baz\n, }\n from 'x';", - "import { Foo,\n Bar as Baz\n }\n from 'x';", - "import { Foo,\n Bar as Baz,\n }\n from 'x';", - "import { Foo as Bar,\n Baz as Qux\n }\n from 'x';", - "import { Foo as Bar,\n Baz as Qux,\n }\n from 'x';", - "import { Foo,\n Bar,\n Baz }\n from 'x';", - "import { Foo,\n Bar,\n Baz,\n }\n from 'x';", - "import { Foo as Bar,\n Baz,\n Qux\n }\n from 'x';", - "import { Foo as Bar,\n Baz,\n Qux,\n }\n from 'x';", - "import { Foo,\n Bar as Baz,\n Qux\n }\n from 'x';", - "import { Foo,\n Bar as Baz,\n Qux,\n }\n from 'x';", - "import { Foo,\n Bar,\n Baz as Qux\n }\n from 'x';", - "import { Foo,\n Bar,\n Baz as Qux,\n }\n from 'x';", - "import { Foo as Bar,\n Baz as Qux,\n Norf\n }\n from 'x';", - "import { Foo as Bar,\n Baz as Qux,\n Norf,\n }\n from 'x';", - "import { Foo as Bar,\n Baz,\n Qux as Norf\n }\n from 'x';", - "import { Foo as Bar,\n Baz,\n Qux as Norf,\n }\n from 'x';", - "import { Foo,\n Bar as Baz,\n Qux as Norf\n }\n from 'x';", - "import { Foo,\n Bar as Baz,\n Qux as Norf,\n }\n from 'x';", - "import { Foo as Bar,\n Baz as Qux,\n Norf as Enuf\n }\n from 'x';", - "import { Foo as Bar,\n Baz as Qux,\n Norf as Enuf,\n }\n from 'x';", - "import Default,\n * as All from 'x';", - "import Default,\n { } from 'x';", - "import Default,\n { Foo\n }\n from 'x';", - "import Default,\n { Foo,\n }\n from 'x';", - "import Default,\n { Foo as Bar\n }\n from 'x';", - "import Default,\n { Foo as Bar,\n }\n from 'x';", - "import Default,\n { Foo,\n Bar\n } from\n 'x';", - "import Default,\n { Foo,\n Bar,\n } from\n 'x';", - "import Default,\n { Foo as Bar,\n Baz\n }\n from 'x';", - "import Default,\n { Foo as Bar,\n Baz,\n }\n from 'x';", - "import Default,\n { Foo,\n Bar as Baz\n }\n from 'x';", - "import Default,\n { Foo,\n Bar as Baz,\n }\n from 'x';", - "import Default,\n { Foo as Bar,\n Baz as Qux\n }\n from 'x';", - "import Default,\n { Foo as Bar,\n Baz as Qux,\n }\n from 'x';", - "import Default,\n { Foo,\n Bar,\n Baz\n }\n from 'x';", - "import Default,\n { Foo,\n Bar,\n Baz,\n }\n from 'x';", - "import Default,\n { Foo as Bar,\n Baz,\n Qux\n }\n from 'x';", - "import Default,\n { Foo as Bar,\n Baz,\n Qux,\n }\n from 'x';", - "import Default,\n { Foo,\n Bar as Baz,\n Qux\n }\n from 'x';", - "import Default,\n { Foo,\n Bar as Baz,\n Qux,\n }\n from 'x';", - "import Default,\n { Foo,\n Bar,\n Baz as Qux\n }\n from 'x';", - "import Default,\n { Foo,\n Bar,\n Baz as Qux,\n }\n from 'x';", - "import Default,\n { Foo as Bar,\n Baz as Qux,\n Norf\n }\n from 'x';", - "import Default,\n { Foo as Bar,\n Baz as Qux,\n Norf,\n }\n from 'x';", - "import Default,\n { Foo as Bar,\n Baz,\n Qux as Norf }\n from 'x';", - "import Default,\n { Foo as Bar,\n Baz,\n Qux as Norf, }\n from 'x';", - "import Default,\n { Foo, Bar as Baz,\n Qux as Norf }\n from 'x';", - "import Default,\n { Foo, Bar as Baz,\n Qux as Norf, }\n from 'x';", - "import Default,\n { Foo as Bar,\n Baz as Qux,\n Norf as NoMore\n }\n from 'x';", - "import Default,\n { Foo as Bar,\n Baz as Qux,\n Norf as NoMore,\n }\n from 'x';", - "import Default\n , { } from 'x';", - // single line export - "export'x';", - "export 'x';", - "export 'x' ;", - "export Default from 'x';", - "export * as All from 'x';", - "export {} from 'x';", - "export { } from 'x';", - "export {Foo} from 'x';", - "export { Foo } from 'x';", - "export { Foo, } from 'x';", - "export {Foo as Bar} from 'x';", - "export { Foo as Bar } from 'x';", - "export { Foo as Bar, } from 'x';", - "export { Foo, Bar } from 'x';", - "export { Foo, Bar, } from 'x';", - "export { Foo as Bar, Baz } from 'x';", - "export { Foo as Bar, Baz, } from 'x';", - "export { Foo, Bar as Baz } from 'x';", - "export { Foo, Bar as Baz, } from 'x';", - "export { Foo as Bar, Baz as Qux } from 'x';", - "export { Foo as Bar, Baz as Qux, } from 'x';", - "export { Foo, Bar, Baz } from 'x';", - "export { Foo, Bar, Baz, } from 'x';", - "export { Foo as Bar, Baz, Qux } from 'x';", - "export { Foo as Bar, Baz, Qux, } from 'x';", - "export { Foo, Bar as Baz, Qux } from 'x';", - "export { Foo, Bar as Baz, Qux, } from 'x';", - "export { Foo, Bar, Baz as Qux } from 'x';", - "export { Foo, Bar, Baz as Qux, } from 'x';", - "export { Foo as Bar, Baz as Qux, Norf } from 'x';", - "export { Foo as Bar, Baz as Qux, Norf, } from 'x';", - "export { Foo as Bar, Baz, Qux as Norf } from 'x';", - "export { Foo as Bar, Baz, Qux as Norf, } from 'x';", - "export { Foo, Bar as Baz, Qux as Norf } from 'x';", - "export { Foo, Bar as Baz, Qux as Norf, } from 'x';", - "export { Foo as Bar, Baz as Qux, Norf as Enuf } from 'x';", - "export { Foo as Bar, Baz as Qux, Norf as Enuf, } from 'x';", - "export Default, * as All from 'x';", - "export Default, { } from 'x';", - "export Default, { Foo } from 'x';", - "export Default, { Foo, } from 'x';", - "export Default, { Foo as Bar } from 'x';", - "export Default, { Foo as Bar, } from 'x';", - "export Default, { Foo, Bar } from 'x';", - "export Default, { Foo, Bar, } from 'x';", - "export Default, { Foo as Bar, Baz } from 'x';", - "export Default, { Foo as Bar, Baz, } from 'x';", - "export Default, { Foo, Bar as Baz } from 'x';", - "export Default, { Foo, Bar as Baz, } from 'x';", - "export Default, { Foo as Bar, Baz as Qux } from 'x';", - "export Default, { Foo as Bar, Baz as Qux, } from 'x';", - "export Default, { Foo, Bar, Baz } from 'x';", - "export Default, { Foo, Bar, Baz, } from 'x';", - "export Default, { Foo as Bar, Baz, Qux } from 'x';", - "export Default, { Foo as Bar, Baz, Qux, } from 'x';", - "export Default, { Foo, Bar as Baz, Qux } from 'x';", - "export Default, { Foo, Bar as Baz, Qux, } from 'x';", - "export Default, { Foo, Bar, Baz as Qux } from 'x';", - "export Default, { Foo, Bar, Baz as Qux, } from 'x';", - "export Default, { Foo as Bar, Baz as Qux, Norf } from 'x';", - "export Default, { Foo as Bar, Baz as Qux, Norf, } from 'x';", - "export Default, { Foo as Bar, Baz, Qux as Norf } from 'x';", - "export Default, { Foo as Bar, Baz, Qux as Norf, } from 'x';", - "export Default, { Foo, Bar as Baz, Qux as Norf } from 'x';", - "export Default, { Foo, Bar as Baz, Qux as Norf, } from 'x';", - "export Default, { Foo as Bar, Baz as Qux, Norf as NoMore } from 'x';", - "export Default, { Foo as Bar, Baz as Qux, Norf as NoMore, } from 'x';", - "export Default , { } from 'x';", - 'export "x";', - 'export Default from "x";', - 'export * as All from "x";', - 'export { } from "x";', - 'export { Foo } from "x";', - 'export { Foo, } from "x";', - 'export { Foo as Bar } from "x";', - 'export { Foo as Bar, } from "x";', - 'export { Foo, Bar } from "x";', - 'export { Foo, Bar, } from "x";', - 'export { Foo as Bar, Baz } from "x";', - 'export { Foo as Bar, Baz, } from "x";', - 'export { Foo, Bar as Baz } from "x";', - 'export { Foo, Bar as Baz, } from "x";', - 'export { Foo as Bar, Baz as Qux } from "x";', - 'export { Foo as Bar, Baz as Qux, } from "x";', - 'export { Foo, Bar, Baz } from "x";', - 'export { Foo, Bar, Baz, } from "x";', - 'export { Foo as Bar, Baz, Qux } from "x";', - 'export { Foo as Bar, Baz, Qux, } from "x";', - 'export { Foo, Bar as Baz, Qux } from "x";', - 'export { Foo, Bar as Baz, Qux, } from "x";', - 'export { Foo, Bar, Baz as Qux } from "x";', - 'export { Foo, Bar, Baz as Qux, } from "x";', - 'export { Foo as Bar, Baz as Qux, Norf } from "x";', - 'export { Foo as Bar, Baz as Qux, Norf, } from "x";', - 'export { Foo as Bar, Baz, Qux as Norf } from "x";', - 'export { Foo as Bar, Baz, Qux as Norf, } from "x";', - 'export { Foo, Bar as Baz, Qux as Norf } from "x";', - 'export { Foo, Bar as Baz, Qux as Norf, } from "x";', - 'export { Foo as Bar, Baz as Qux, Norf as NoMore } from "x";', - 'export { Foo as Bar, Baz as Qux, Norf as NoMore, } from "x";', - 'export Default, * as All from "x";', - 'export Default, { } from "x";', - 'export Default, { Foo } from "x";', - 'export Default, { Foo, } from "x";', - 'export Default, { Foo as Bar } from "x";', - 'export Default, { Foo as Bar, } from "x";', - 'export Default, { Foo, Bar } from "x";', - 'export Default, { Foo, Bar, } from "x";', - 'export Default, { Foo as Bar, Baz } from "x";', - 'export Default, { Foo as Bar, Baz, } from "x";', - 'export Default, { Foo, Bar as Baz } from "x";', - 'export Default, { Foo, Bar as Baz, } from "x";', - 'export Default, { Foo as Bar, Baz as Qux } from "x";', - 'export Default, { Foo as Bar, Baz as Qux, } from "x";', - 'export Default, { Foo, Bar, Baz } from "x";', - 'export Default, { Foo, Bar, Baz, } from "x";', - 'export Default, { Foo as Bar, Baz, Qux } from "x";', - 'export Default, { Foo as Bar, Baz, Qux, } from "x";', - 'export Default, { Foo, Bar as Baz, Qux } from "x";', - 'export Default, { Foo, Bar as Baz, Qux, } from "x";', - 'export Default, { Foo, Bar, Baz as Qux } from "x";', - 'export Default, { Foo, Bar, Baz as Qux, } from "x";', - 'export Default, { Foo as Bar, Baz as Qux, Norf } from "x";', - 'export Default, { Foo as Bar, Baz as Qux, Norf, } from "x";', - 'export Default, { Foo as Bar, Baz, Qux as Norf } from "x";', - 'export Default, { Foo as Bar, Baz, Qux as Norf, } from "x";', - 'export Default, { Foo, Bar as Baz, Qux as Norf } from "x";', - 'export Default, { Foo, Bar as Baz, Qux as Norf, } from "x";', - 'export Default, { Foo as Bar, Baz as Qux, Norf as Enuf } from "x";', - 'export Default, { Foo as Bar, Baz as Qux, Norf as Enuf, } from "x";', - 'export Default from "y";', - 'export * as All from \'z\';', - // export with support for new lines - "export { Foo,\n Bar }\n from 'x';", - "export { \nFoo,\nBar,\n }\n from 'x';", - "export { Foo as Bar,\n Baz\n }\n from 'x';", - "export { \nFoo as Bar,\n Baz\n, }\n from 'x';", - "export { Foo,\n Bar as Baz\n }\n from 'x';", - "export { Foo,\n Bar as Baz,\n }\n from 'x';", - "export { Foo as Bar,\n Baz as Qux\n }\n from 'x';", - "export { Foo as Bar,\n Baz as Qux,\n }\n from 'x';", - "export { Foo,\n Bar,\n Baz }\n from 'x';", - "export { Foo,\n Bar,\n Baz,\n }\n from 'x';", - "export { Foo as Bar,\n Baz,\n Qux\n }\n from 'x';", - "export { Foo as Bar,\n Baz,\n Qux,\n }\n from 'x';", - "export { Foo,\n Bar as Baz,\n Qux\n }\n from 'x';", - "export { Foo,\n Bar as Baz,\n Qux,\n }\n from 'x';", - "export { Foo,\n Bar,\n Baz as Qux\n }\n from 'x';", - "export { Foo,\n Bar,\n Baz as Qux,\n }\n from 'x';", - "export { Foo as Bar,\n Baz as Qux,\n Norf\n }\n from 'x';", - "export { Foo as Bar,\n Baz as Qux,\n Norf,\n }\n from 'x';", - "export { Foo as Bar,\n Baz,\n Qux as Norf\n }\n from 'x';", - "export { Foo as Bar,\n Baz,\n Qux as Norf,\n }\n from 'x';", - "export { Foo,\n Bar as Baz,\n Qux as Norf\n }\n from 'x';", - "export { Foo,\n Bar as Baz,\n Qux as Norf,\n }\n from 'x';", - "export { Foo as Bar,\n Baz as Qux,\n Norf as Enuf\n }\n from 'x';", - "export { Foo as Bar,\n Baz as Qux,\n Norf as Enuf,\n }\n from 'x';", - "export Default,\n * as All from 'x';", - "export Default,\n { } from 'x';", - "export Default,\n { Foo\n }\n from 'x';", - "export Default,\n { Foo,\n }\n from 'x';", - "export Default,\n { Foo as Bar\n }\n from 'x';", - "export Default,\n { Foo as Bar,\n }\n from 'x';", - "export Default,\n { Foo,\n Bar\n } from\n 'x';", - "export Default,\n { Foo,\n Bar,\n } from\n 'x';", - "export Default,\n { Foo as Bar,\n Baz\n }\n from 'x';", - "export Default,\n { Foo as Bar,\n Baz,\n }\n from 'x';", - "export Default,\n { Foo,\n Bar as Baz\n }\n from 'x';", - "export Default,\n { Foo,\n Bar as Baz,\n }\n from 'x';", - "export Default,\n { Foo as Bar,\n Baz as Qux\n }\n from 'x';", - "export Default,\n { Foo as Bar,\n Baz as Qux,\n }\n from 'x';", - "export Default,\n { Foo,\n Bar,\n Baz\n }\n from 'x';", - "export Default,\n { Foo,\n Bar,\n Baz,\n }\n from 'x';", - "export Default,\n { Foo as Bar,\n Baz,\n Qux\n }\n from 'x';", - "export Default,\n { Foo as Bar,\n Baz,\n Qux,\n }\n from 'x';", - "export Default,\n { Foo,\n Bar as Baz,\n Qux\n }\n from 'x';", - "export Default,\n { Foo,\n Bar as Baz,\n Qux,\n }\n from 'x';", - "export Default,\n { Foo,\n Bar,\n Baz as Qux\n }\n from 'x';", - "export Default,\n { Foo,\n Bar,\n Baz as Qux,\n }\n from 'x';", - "export Default,\n { Foo as Bar,\n Baz as Qux,\n Norf\n }\n from 'x';", - "export Default,\n { Foo as Bar,\n Baz as Qux,\n Norf,\n }\n from 'x';", - "export Default,\n { Foo as Bar,\n Baz,\n Qux as Norf }\n from 'x';", - "export Default,\n { Foo as Bar,\n Baz,\n Qux as Norf, }\n from 'x';", - "export Default,\n { Foo, Bar as Baz,\n Qux as Norf }\n from 'x';", - "export Default,\n { Foo, Bar as Baz,\n Qux as Norf, }\n from 'x';", - "export Default,\n { Foo as Bar,\n Baz as Qux,\n Norf as NoMore\n }\n from 'x';", - "export Default,\n { Foo as Bar,\n Baz as Qux,\n Norf as NoMore,\n }\n from 'x';", - "export Default\n , { } from 'x';", // require 'require("x")', 'require("y")', @@ -633,8 +281,16 @@ describe('Resolver', function() { ].join('\n'); /*eslint-disable */ - const module = createModule('test module', ['x', 'y']); + function *findDependencyOffsets() { + const re = /(['"']).*?\1/g; + let match; + while ((match = re.exec(code))) { + yield match.index; + } + } + const dependencyOffsets = Array.from(findDependencyOffsets()); + const module = createModule('test module', ['x', 'y']); const resolutionResponse = new ResolutionResponseMock({ dependencies: [module], mainModuleId: 'test module', @@ -647,393 +303,15 @@ describe('Resolver', function() { ]; } - return depResolver.wrapModule( + return depResolver.wrapModule({ resolutionResponse, - createModule('test module', ['x', 'y']), - code - ).then(processedCode => { - expect(processedCode.name).toEqual('test module'); - expect(processedCode.code).toEqual([ - '__d(\'test module\',function(global, require,' + - ' module, exports) { ' + - // single line import - "import'x';", - "import 'changed';", - "import 'changed' ;", - "import Default from 'changed';", - "import * as All from 'changed';", - "import {} from 'changed';", - "import { } from 'changed';", - "import {Foo} from 'changed';", - "import { Foo } from 'changed';", - "import { Foo, } from 'changed';", - "import {Foo as Bar} from 'changed';", - "import { Foo as Bar } from 'changed';", - "import { Foo as Bar, } from 'changed';", - "import { Foo, Bar } from 'changed';", - "import { Foo, Bar, } from 'changed';", - "import { Foo as Bar, Baz } from 'changed';", - "import { Foo as Bar, Baz, } from 'changed';", - "import { Foo, Bar as Baz } from 'changed';", - "import { Foo, Bar as Baz, } from 'changed';", - "import { Foo as Bar, Baz as Qux } from 'changed';", - "import { Foo as Bar, Baz as Qux, } from 'changed';", - "import { Foo, Bar, Baz } from 'changed';", - "import { Foo, Bar, Baz, } from 'changed';", - "import { Foo as Bar, Baz, Qux } from 'changed';", - "import { Foo as Bar, Baz, Qux, } from 'changed';", - "import { Foo, Bar as Baz, Qux } from 'changed';", - "import { Foo, Bar as Baz, Qux, } from 'changed';", - "import { Foo, Bar, Baz as Qux } from 'changed';", - "import { Foo, Bar, Baz as Qux, } from 'changed';", - "import { Foo as Bar, Baz as Qux, Norf } from 'changed';", - "import { Foo as Bar, Baz as Qux, Norf, } from 'changed';", - "import { Foo as Bar, Baz, Qux as Norf } from 'changed';", - "import { Foo as Bar, Baz, Qux as Norf, } from 'changed';", - "import { Foo, Bar as Baz, Qux as Norf } from 'changed';", - "import { Foo, Bar as Baz, Qux as Norf, } from 'changed';", - "import { Foo as Bar, Baz as Qux, Norf as Enuf } from 'changed';", - "import { Foo as Bar, Baz as Qux, Norf as Enuf, } from 'changed';", - "import Default, * as All from 'changed';", - "import Default, { } from 'changed';", - "import Default, { Foo } from 'changed';", - "import Default, { Foo, } from 'changed';", - "import Default, { Foo as Bar } from 'changed';", - "import Default, { Foo as Bar, } from 'changed';", - "import Default, { Foo, Bar } from 'changed';", - "import Default, { Foo, Bar, } from 'changed';", - "import Default, { Foo as Bar, Baz } from 'changed';", - "import Default, { Foo as Bar, Baz, } from 'changed';", - "import Default, { Foo, Bar as Baz } from 'changed';", - "import Default, { Foo, Bar as Baz, } from 'changed';", - "import Default, { Foo as Bar, Baz as Qux } from 'changed';", - "import Default, { Foo as Bar, Baz as Qux, } from 'changed';", - "import Default, { Foo, Bar, Baz } from 'changed';", - "import Default, { Foo, Bar, Baz, } from 'changed';", - "import Default, { Foo as Bar, Baz, Qux } from 'changed';", - "import Default, { Foo as Bar, Baz, Qux, } from 'changed';", - "import Default, { Foo, Bar as Baz, Qux } from 'changed';", - "import Default, { Foo, Bar as Baz, Qux, } from 'changed';", - "import Default, { Foo, Bar, Baz as Qux } from 'changed';", - "import Default, { Foo, Bar, Baz as Qux, } from 'changed';", - "import Default, { Foo as Bar, Baz as Qux, Norf } from 'changed';", - "import Default, { Foo as Bar, Baz as Qux, Norf, } from 'changed';", - "import Default, { Foo as Bar, Baz, Qux as Norf } from 'changed';", - "import Default, { Foo as Bar, Baz, Qux as Norf, } from 'changed';", - "import Default, { Foo, Bar as Baz, Qux as Norf } from 'changed';", - "import Default, { Foo, Bar as Baz, Qux as Norf, } from 'changed';", - "import Default, { Foo as Bar, Baz as Qux, Norf as NoMore } from 'changed';", - "import Default, { Foo as Bar, Baz as Qux, Norf as NoMore, } from 'changed';", - "import Default , { } from 'changed';", - 'import "changed";', - 'import Default from "changed";', - 'import * as All from "changed";', - 'import { } from "changed";', - 'import { Foo } from "changed";', - 'import { Foo, } from "changed";', - 'import { Foo as Bar } from "changed";', - 'import { Foo as Bar, } from "changed";', - 'import { Foo, Bar } from "changed";', - 'import { Foo, Bar, } from "changed";', - 'import { Foo as Bar, Baz } from "changed";', - 'import { Foo as Bar, Baz, } from "changed";', - 'import { Foo, Bar as Baz } from "changed";', - 'import { Foo, Bar as Baz, } from "changed";', - 'import { Foo as Bar, Baz as Qux } from "changed";', - 'import { Foo as Bar, Baz as Qux, } from "changed";', - 'import { Foo, Bar, Baz } from "changed";', - 'import { Foo, Bar, Baz, } from "changed";', - 'import { Foo as Bar, Baz, Qux } from "changed";', - 'import { Foo as Bar, Baz, Qux, } from "changed";', - 'import { Foo, Bar as Baz, Qux } from "changed";', - 'import { Foo, Bar as Baz, Qux, } from "changed";', - 'import { Foo, Bar, Baz as Qux } from "changed";', - 'import { Foo, Bar, Baz as Qux, } from "changed";', - 'import { Foo as Bar, Baz as Qux, Norf } from "changed";', - 'import { Foo as Bar, Baz as Qux, Norf, } from "changed";', - 'import { Foo as Bar, Baz, Qux as Norf } from "changed";', - 'import { Foo as Bar, Baz, Qux as Norf, } from "changed";', - 'import { Foo, Bar as Baz, Qux as Norf } from "changed";', - 'import { Foo, Bar as Baz, Qux as Norf, } from "changed";', - 'import { Foo as Bar, Baz as Qux, Norf as NoMore } from "changed";', - 'import { Foo as Bar, Baz as Qux, Norf as NoMore, } from "changed";', - 'import Default, * as All from "changed";', - 'import Default, { } from "changed";', - 'import Default, { Foo } from "changed";', - 'import Default, { Foo, } from "changed";', - 'import Default, { Foo as Bar } from "changed";', - 'import Default, { Foo as Bar, } from "changed";', - 'import Default, { Foo, Bar } from "changed";', - 'import Default, { Foo, Bar, } from "changed";', - 'import Default, { Foo as Bar, Baz } from "changed";', - 'import Default, { Foo as Bar, Baz, } from "changed";', - 'import Default, { Foo, Bar as Baz } from "changed";', - 'import Default, { Foo, Bar as Baz, } from "changed";', - 'import Default, { Foo as Bar, Baz as Qux } from "changed";', - 'import Default, { Foo as Bar, Baz as Qux, } from "changed";', - 'import Default, { Foo, Bar, Baz } from "changed";', - 'import Default, { Foo, Bar, Baz, } from "changed";', - 'import Default, { Foo as Bar, Baz, Qux } from "changed";', - 'import Default, { Foo as Bar, Baz, Qux, } from "changed";', - 'import Default, { Foo, Bar as Baz, Qux } from "changed";', - 'import Default, { Foo, Bar as Baz, Qux, } from "changed";', - 'import Default, { Foo, Bar, Baz as Qux } from "changed";', - 'import Default, { Foo, Bar, Baz as Qux, } from "changed";', - 'import Default, { Foo as Bar, Baz as Qux, Norf } from "changed";', - 'import Default, { Foo as Bar, Baz as Qux, Norf, } from "changed";', - 'import Default, { Foo as Bar, Baz, Qux as Norf } from "changed";', - 'import Default, { Foo as Bar, Baz, Qux as Norf, } from "changed";', - 'import Default, { Foo, Bar as Baz, Qux as Norf } from "changed";', - 'import Default, { Foo, Bar as Baz, Qux as Norf, } from "changed";', - 'import Default, { Foo as Bar, Baz as Qux, Norf as Enuf } from "changed";', - 'import Default, { Foo as Bar, Baz as Qux, Norf as Enuf, } from "changed";', - 'import Default from "Y";', - 'import * as All from \'z\';', - // import with support for new lines - "import { Foo,\n Bar }\n from 'changed';", - "import { \nFoo,\nBar,\n }\n from 'changed';", - "import { Foo as Bar,\n Baz\n }\n from 'changed';", - "import { \nFoo as Bar,\n Baz\n, }\n from 'changed';", - "import { Foo,\n Bar as Baz\n }\n from 'changed';", - "import { Foo,\n Bar as Baz,\n }\n from 'changed';", - "import { Foo as Bar,\n Baz as Qux\n }\n from 'changed';", - "import { Foo as Bar,\n Baz as Qux,\n }\n from 'changed';", - "import { Foo,\n Bar,\n Baz }\n from 'changed';", - "import { Foo,\n Bar,\n Baz,\n }\n from 'changed';", - "import { Foo as Bar,\n Baz,\n Qux\n }\n from 'changed';", - "import { Foo as Bar,\n Baz,\n Qux,\n }\n from 'changed';", - "import { Foo,\n Bar as Baz,\n Qux\n }\n from 'changed';", - "import { Foo,\n Bar as Baz,\n Qux,\n }\n from 'changed';", - "import { Foo,\n Bar,\n Baz as Qux\n }\n from 'changed';", - "import { Foo,\n Bar,\n Baz as Qux,\n }\n from 'changed';", - "import { Foo as Bar,\n Baz as Qux,\n Norf\n }\n from 'changed';", - "import { Foo as Bar,\n Baz as Qux,\n Norf,\n }\n from 'changed';", - "import { Foo as Bar,\n Baz,\n Qux as Norf\n }\n from 'changed';", - "import { Foo as Bar,\n Baz,\n Qux as Norf,\n }\n from 'changed';", - "import { Foo,\n Bar as Baz,\n Qux as Norf\n }\n from 'changed';", - "import { Foo,\n Bar as Baz,\n Qux as Norf,\n }\n from 'changed';", - "import { Foo as Bar,\n Baz as Qux,\n Norf as Enuf\n }\n from 'changed';", - "import { Foo as Bar,\n Baz as Qux,\n Norf as Enuf,\n }\n from 'changed';", - "import Default,\n * as All from 'changed';", - "import Default,\n { } from 'changed';", - "import Default,\n { Foo\n }\n from 'changed';", - "import Default,\n { Foo,\n }\n from 'changed';", - "import Default,\n { Foo as Bar\n }\n from 'changed';", - "import Default,\n { Foo as Bar,\n }\n from 'changed';", - "import Default,\n { Foo,\n Bar\n } from\n 'changed';", - "import Default,\n { Foo,\n Bar,\n } from\n 'changed';", - "import Default,\n { Foo as Bar,\n Baz\n }\n from 'changed';", - "import Default,\n { Foo as Bar,\n Baz,\n }\n from 'changed';", - "import Default,\n { Foo,\n Bar as Baz\n }\n from 'changed';", - "import Default,\n { Foo,\n Bar as Baz,\n }\n from 'changed';", - "import Default,\n { Foo as Bar,\n Baz as Qux\n }\n from 'changed';", - "import Default,\n { Foo as Bar,\n Baz as Qux,\n }\n from 'changed';", - "import Default,\n { Foo,\n Bar,\n Baz\n }\n from 'changed';", - "import Default,\n { Foo,\n Bar,\n Baz,\n }\n from 'changed';", - "import Default,\n { Foo as Bar,\n Baz,\n Qux\n }\n from 'changed';", - "import Default,\n { Foo as Bar,\n Baz,\n Qux,\n }\n from 'changed';", - "import Default,\n { Foo,\n Bar as Baz,\n Qux\n }\n from 'changed';", - "import Default,\n { Foo,\n Bar as Baz,\n Qux,\n }\n from 'changed';", - "import Default,\n { Foo,\n Bar,\n Baz as Qux\n }\n from 'changed';", - "import Default,\n { Foo,\n Bar,\n Baz as Qux,\n }\n from 'changed';", - "import Default,\n { Foo as Bar,\n Baz as Qux,\n Norf\n }\n from 'changed';", - "import Default,\n { Foo as Bar,\n Baz as Qux,\n Norf,\n }\n from 'changed';", - "import Default,\n { Foo as Bar,\n Baz,\n Qux as Norf }\n from 'changed';", - "import Default,\n { Foo as Bar,\n Baz,\n Qux as Norf, }\n from 'changed';", - "import Default,\n { Foo, Bar as Baz,\n Qux as Norf }\n from 'changed';", - "import Default,\n { Foo, Bar as Baz,\n Qux as Norf, }\n from 'changed';", - "import Default,\n { Foo as Bar,\n Baz as Qux,\n Norf as NoMore\n }\n from 'changed';", - "import Default,\n { Foo as Bar,\n Baz as Qux,\n Norf as NoMore,\n }\n from 'changed';", - "import Default\n , { } from 'changed';", - // single line export - "export'x';", - "export 'changed';", - "export 'changed' ;", - "export Default from 'changed';", - "export * as All from 'changed';", - "export {} from 'changed';", - "export { } from 'changed';", - "export {Foo} from 'changed';", - "export { Foo } from 'changed';", - "export { Foo, } from 'changed';", - "export {Foo as Bar} from 'changed';", - "export { Foo as Bar } from 'changed';", - "export { Foo as Bar, } from 'changed';", - "export { Foo, Bar } from 'changed';", - "export { Foo, Bar, } from 'changed';", - "export { Foo as Bar, Baz } from 'changed';", - "export { Foo as Bar, Baz, } from 'changed';", - "export { Foo, Bar as Baz } from 'changed';", - "export { Foo, Bar as Baz, } from 'changed';", - "export { Foo as Bar, Baz as Qux } from 'changed';", - "export { Foo as Bar, Baz as Qux, } from 'changed';", - "export { Foo, Bar, Baz } from 'changed';", - "export { Foo, Bar, Baz, } from 'changed';", - "export { Foo as Bar, Baz, Qux } from 'changed';", - "export { Foo as Bar, Baz, Qux, } from 'changed';", - "export { Foo, Bar as Baz, Qux } from 'changed';", - "export { Foo, Bar as Baz, Qux, } from 'changed';", - "export { Foo, Bar, Baz as Qux } from 'changed';", - "export { Foo, Bar, Baz as Qux, } from 'changed';", - "export { Foo as Bar, Baz as Qux, Norf } from 'changed';", - "export { Foo as Bar, Baz as Qux, Norf, } from 'changed';", - "export { Foo as Bar, Baz, Qux as Norf } from 'changed';", - "export { Foo as Bar, Baz, Qux as Norf, } from 'changed';", - "export { Foo, Bar as Baz, Qux as Norf } from 'changed';", - "export { Foo, Bar as Baz, Qux as Norf, } from 'changed';", - "export { Foo as Bar, Baz as Qux, Norf as Enuf } from 'changed';", - "export { Foo as Bar, Baz as Qux, Norf as Enuf, } from 'changed';", - "export Default, * as All from 'changed';", - "export Default, { } from 'changed';", - "export Default, { Foo } from 'changed';", - "export Default, { Foo, } from 'changed';", - "export Default, { Foo as Bar } from 'changed';", - "export Default, { Foo as Bar, } from 'changed';", - "export Default, { Foo, Bar } from 'changed';", - "export Default, { Foo, Bar, } from 'changed';", - "export Default, { Foo as Bar, Baz } from 'changed';", - "export Default, { Foo as Bar, Baz, } from 'changed';", - "export Default, { Foo, Bar as Baz } from 'changed';", - "export Default, { Foo, Bar as Baz, } from 'changed';", - "export Default, { Foo as Bar, Baz as Qux } from 'changed';", - "export Default, { Foo as Bar, Baz as Qux, } from 'changed';", - "export Default, { Foo, Bar, Baz } from 'changed';", - "export Default, { Foo, Bar, Baz, } from 'changed';", - "export Default, { Foo as Bar, Baz, Qux } from 'changed';", - "export Default, { Foo as Bar, Baz, Qux, } from 'changed';", - "export Default, { Foo, Bar as Baz, Qux } from 'changed';", - "export Default, { Foo, Bar as Baz, Qux, } from 'changed';", - "export Default, { Foo, Bar, Baz as Qux } from 'changed';", - "export Default, { Foo, Bar, Baz as Qux, } from 'changed';", - "export Default, { Foo as Bar, Baz as Qux, Norf } from 'changed';", - "export Default, { Foo as Bar, Baz as Qux, Norf, } from 'changed';", - "export Default, { Foo as Bar, Baz, Qux as Norf } from 'changed';", - "export Default, { Foo as Bar, Baz, Qux as Norf, } from 'changed';", - "export Default, { Foo, Bar as Baz, Qux as Norf } from 'changed';", - "export Default, { Foo, Bar as Baz, Qux as Norf, } from 'changed';", - "export Default, { Foo as Bar, Baz as Qux, Norf as NoMore } from 'changed';", - "export Default, { Foo as Bar, Baz as Qux, Norf as NoMore, } from 'changed';", - "export Default , { } from 'changed';", - 'export "changed";', - 'export Default from "changed";', - 'export * as All from "changed";', - 'export { } from "changed";', - 'export { Foo } from "changed";', - 'export { Foo, } from "changed";', - 'export { Foo as Bar } from "changed";', - 'export { Foo as Bar, } from "changed";', - 'export { Foo, Bar } from "changed";', - 'export { Foo, Bar, } from "changed";', - 'export { Foo as Bar, Baz } from "changed";', - 'export { Foo as Bar, Baz, } from "changed";', - 'export { Foo, Bar as Baz } from "changed";', - 'export { Foo, Bar as Baz, } from "changed";', - 'export { Foo as Bar, Baz as Qux } from "changed";', - 'export { Foo as Bar, Baz as Qux, } from "changed";', - 'export { Foo, Bar, Baz } from "changed";', - 'export { Foo, Bar, Baz, } from "changed";', - 'export { Foo as Bar, Baz, Qux } from "changed";', - 'export { Foo as Bar, Baz, Qux, } from "changed";', - 'export { Foo, Bar as Baz, Qux } from "changed";', - 'export { Foo, Bar as Baz, Qux, } from "changed";', - 'export { Foo, Bar, Baz as Qux } from "changed";', - 'export { Foo, Bar, Baz as Qux, } from "changed";', - 'export { Foo as Bar, Baz as Qux, Norf } from "changed";', - 'export { Foo as Bar, Baz as Qux, Norf, } from "changed";', - 'export { Foo as Bar, Baz, Qux as Norf } from "changed";', - 'export { Foo as Bar, Baz, Qux as Norf, } from "changed";', - 'export { Foo, Bar as Baz, Qux as Norf } from "changed";', - 'export { Foo, Bar as Baz, Qux as Norf, } from "changed";', - 'export { Foo as Bar, Baz as Qux, Norf as NoMore } from "changed";', - 'export { Foo as Bar, Baz as Qux, Norf as NoMore, } from "changed";', - 'export Default, * as All from "changed";', - 'export Default, { } from "changed";', - 'export Default, { Foo } from "changed";', - 'export Default, { Foo, } from "changed";', - 'export Default, { Foo as Bar } from "changed";', - 'export Default, { Foo as Bar, } from "changed";', - 'export Default, { Foo, Bar } from "changed";', - 'export Default, { Foo, Bar, } from "changed";', - 'export Default, { Foo as Bar, Baz } from "changed";', - 'export Default, { Foo as Bar, Baz, } from "changed";', - 'export Default, { Foo, Bar as Baz } from "changed";', - 'export Default, { Foo, Bar as Baz, } from "changed";', - 'export Default, { Foo as Bar, Baz as Qux } from "changed";', - 'export Default, { Foo as Bar, Baz as Qux, } from "changed";', - 'export Default, { Foo, Bar, Baz } from "changed";', - 'export Default, { Foo, Bar, Baz, } from "changed";', - 'export Default, { Foo as Bar, Baz, Qux } from "changed";', - 'export Default, { Foo as Bar, Baz, Qux, } from "changed";', - 'export Default, { Foo, Bar as Baz, Qux } from "changed";', - 'export Default, { Foo, Bar as Baz, Qux, } from "changed";', - 'export Default, { Foo, Bar, Baz as Qux } from "changed";', - 'export Default, { Foo, Bar, Baz as Qux, } from "changed";', - 'export Default, { Foo as Bar, Baz as Qux, Norf } from "changed";', - 'export Default, { Foo as Bar, Baz as Qux, Norf, } from "changed";', - 'export Default, { Foo as Bar, Baz, Qux as Norf } from "changed";', - 'export Default, { Foo as Bar, Baz, Qux as Norf, } from "changed";', - 'export Default, { Foo, Bar as Baz, Qux as Norf } from "changed";', - 'export Default, { Foo, Bar as Baz, Qux as Norf, } from "changed";', - 'export Default, { Foo as Bar, Baz as Qux, Norf as Enuf } from "changed";', - 'export Default, { Foo as Bar, Baz as Qux, Norf as Enuf, } from "changed";', - 'export Default from "Y";', - 'export * as All from \'z\';', - // export with support for new lines - "export { Foo,\n Bar }\n from 'changed';", - "export { \nFoo,\nBar,\n }\n from 'changed';", - "export { Foo as Bar,\n Baz\n }\n from 'changed';", - "export { \nFoo as Bar,\n Baz\n, }\n from 'changed';", - "export { Foo,\n Bar as Baz\n }\n from 'changed';", - "export { Foo,\n Bar as Baz,\n }\n from 'changed';", - "export { Foo as Bar,\n Baz as Qux\n }\n from 'changed';", - "export { Foo as Bar,\n Baz as Qux,\n }\n from 'changed';", - "export { Foo,\n Bar,\n Baz }\n from 'changed';", - "export { Foo,\n Bar,\n Baz,\n }\n from 'changed';", - "export { Foo as Bar,\n Baz,\n Qux\n }\n from 'changed';", - "export { Foo as Bar,\n Baz,\n Qux,\n }\n from 'changed';", - "export { Foo,\n Bar as Baz,\n Qux\n }\n from 'changed';", - "export { Foo,\n Bar as Baz,\n Qux,\n }\n from 'changed';", - "export { Foo,\n Bar,\n Baz as Qux\n }\n from 'changed';", - "export { Foo,\n Bar,\n Baz as Qux,\n }\n from 'changed';", - "export { Foo as Bar,\n Baz as Qux,\n Norf\n }\n from 'changed';", - "export { Foo as Bar,\n Baz as Qux,\n Norf,\n }\n from 'changed';", - "export { Foo as Bar,\n Baz,\n Qux as Norf\n }\n from 'changed';", - "export { Foo as Bar,\n Baz,\n Qux as Norf,\n }\n from 'changed';", - "export { Foo,\n Bar as Baz,\n Qux as Norf\n }\n from 'changed';", - "export { Foo,\n Bar as Baz,\n Qux as Norf,\n }\n from 'changed';", - "export { Foo as Bar,\n Baz as Qux,\n Norf as Enuf\n }\n from 'changed';", - "export { Foo as Bar,\n Baz as Qux,\n Norf as Enuf,\n }\n from 'changed';", - "export Default,\n * as All from 'changed';", - "export Default,\n { } from 'changed';", - "export Default,\n { Foo\n }\n from 'changed';", - "export Default,\n { Foo,\n }\n from 'changed';", - "export Default,\n { Foo as Bar\n }\n from 'changed';", - "export Default,\n { Foo as Bar,\n }\n from 'changed';", - "export Default,\n { Foo,\n Bar\n } from\n 'changed';", - "export Default,\n { Foo,\n Bar,\n } from\n 'changed';", - "export Default,\n { Foo as Bar,\n Baz\n }\n from 'changed';", - "export Default,\n { Foo as Bar,\n Baz,\n }\n from 'changed';", - "export Default,\n { Foo,\n Bar as Baz\n }\n from 'changed';", - "export Default,\n { Foo,\n Bar as Baz,\n }\n from 'changed';", - "export Default,\n { Foo as Bar,\n Baz as Qux\n }\n from 'changed';", - "export Default,\n { Foo as Bar,\n Baz as Qux,\n }\n from 'changed';", - "export Default,\n { Foo,\n Bar,\n Baz\n }\n from 'changed';", - "export Default,\n { Foo,\n Bar,\n Baz,\n }\n from 'changed';", - "export Default,\n { Foo as Bar,\n Baz,\n Qux\n }\n from 'changed';", - "export Default,\n { Foo as Bar,\n Baz,\n Qux,\n }\n from 'changed';", - "export Default,\n { Foo,\n Bar as Baz,\n Qux\n }\n from 'changed';", - "export Default,\n { Foo,\n Bar as Baz,\n Qux,\n }\n from 'changed';", - "export Default,\n { Foo,\n Bar,\n Baz as Qux\n }\n from 'changed';", - "export Default,\n { Foo,\n Bar,\n Baz as Qux,\n }\n from 'changed';", - "export Default,\n { Foo as Bar,\n Baz as Qux,\n Norf\n }\n from 'changed';", - "export Default,\n { Foo as Bar,\n Baz as Qux,\n Norf,\n }\n from 'changed';", - "export Default,\n { Foo as Bar,\n Baz,\n Qux as Norf }\n from 'changed';", - "export Default,\n { Foo as Bar,\n Baz,\n Qux as Norf, }\n from 'changed';", - "export Default,\n { Foo, Bar as Baz,\n Qux as Norf }\n from 'changed';", - "export Default,\n { Foo, Bar as Baz,\n Qux as Norf, }\n from 'changed';", - "export Default,\n { Foo as Bar,\n Baz as Qux,\n Norf as NoMore\n }\n from 'changed';", - "export Default,\n { Foo as Bar,\n Baz as Qux,\n Norf as NoMore,\n }\n from 'changed';", - "export Default\n , { } from 'changed';", + module: createModule('test module', ['x', 'y']), + name: 'test module', + code, + meta: {dependencyOffsets} + }).then(({code: processedCode}) => { + expect(processedCode).toEqual([ + '__d("test module", function(global, require, module, exports) {' + // require 'require("changed")', 'require("Y")', @@ -1045,6 +323,22 @@ describe('Resolver', function() { }); }); + pit('should pass through passed-in source maps', () => { + const module = createModule('test module'); + const resolutionResponse = new ResolutionResponseMock({ + dependencies: [module], + mainModuleId: 'test module', + }); + const inputMap = {version: 3, mappings: 'ARBITRARY'}; + return new Resolver({projectRoot: '/root'}).wrapModule({ + resolutionResponse, + module, + name: 'test module', + code: 'arbitrary(code)', + map: inputMap, + }).then(({map}) => expect(map).toBe(inputMap)); + }); + pit('should resolve polyfills', function () { const depResolver = new Resolver({ projectRoot: '/root', @@ -1053,17 +347,90 @@ describe('Resolver', function() { const code = [ 'global.fetch = () => 1;', ].join(''); - return depResolver.wrapModule( - null, - polyfill, + return depResolver.wrapModule({ + module: polyfill, code - ).then(processedCode => { - expect(processedCode.code).toEqual([ + }).then(({code: processedCode}) => { + expect(processedCode).toEqual([ '(function(global) {', 'global.fetch = () => 1;', "\n})(typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : this);", ].join('')); }); }); + + describe('JSON files:', () => { + const code = JSON.stringify({arbitrary: "data"}); + const id = 'arbitrary.json'; + let depResolver, module, resolutionResponse; + + beforeEach(() => { + depResolver = new Resolver({projectRoot: '/root'}); + module = createJsonModule(id); + resolutionResponse = new ResolutionResponseMock({ + dependencies: [module], + mainModuleId: id, + }); + }); + + pit('should prefix JSON files with `module.exports=`', () => { + return depResolver + .wrapModule({resolutionResponse, module, name: id, code}) + .then(({code: processedCode}) => + expect(processedCode).toEqual([ + `__d(${JSON.stringify(id)}, function(global, require, module, exports) {`, + `module.exports = ${code}\n});`, + ].join(''))); + }); + }); + + describe('minification:', () => { + const code ='arbitrary(code)'; + const id = 'arbitrary.js'; + let depResolver, minifyCode, module, resolutionResponse, sourceMap; + + beforeEach(() => { + minifyCode = jest.genMockFn().mockImpl((filename, code, map) => + Promise.resolve({code, map})); + depResolver = new Resolver({ + projectRoot: '/root', + minifyCode + }); + module = createModule(id); + module.path = '/arbitrary/path.js'; + resolutionResponse = new ResolutionResponseMock({ + dependencies: [module], + mainModuleId: id, + }); + sourceMap = {version: 3, sources: ['input'], mappings: 'whatever'}; + }); + + pit('should invoke the minifier with the wrapped code', () => { + const wrappedCode = `__d("${id}", function(global, require, module, exports) {${code}\n});` + return depResolver + .wrapModule({ + resolutionResponse, + module, + name: id, + code, + map: sourceMap, + minify: true + }).then(() => { + expect(minifyCode).toBeCalledWith(module.path, wrappedCode, sourceMap); + }); + }); + + pit('should use minified code', () => { + const minifiedCode = 'minified(code)'; + const minifiedMap = {version: 3, file: ['minified']}; + minifyCode.mockReturnValue(Promise.resolve({code: minifiedCode, map: minifiedMap})); + return depResolver + .wrapModule({resolutionResponse, module, name: id, code, minify: true}) + .then(({code, map}) => { + expect(code).toEqual(minifiedCode); + expect(map).toEqual(minifiedMap); + }); + }); + }); }); }); diff --git a/packager/react-packager/src/Resolver/index.js b/packager/react-packager/src/Resolver/index.js index d60e0fb2b..707402fac 100644 --- a/packager/react-packager/src/Resolver/index.js +++ b/packager/react-packager/src/Resolver/index.js @@ -12,7 +12,6 @@ const path = require('path'); const Activity = require('../Activity'); const DependencyGraph = require('node-haste'); -const replacePatterns = require('node-haste').replacePatterns; const declareOpts = require('../lib/declareOpts'); const Promise = require('promise'); @@ -48,6 +47,12 @@ const validateOpts = declareOpts({ type: 'object', required: true, }, + transformCode: { + type: 'function', + }, + minifyCode: { + type: 'function', + }, }); const getDependenciesValidateOpts = declareOpts({ @@ -97,8 +102,10 @@ class Resolver { fileWatcher: opts.fileWatcher, cache: opts.cache, shouldThrowOnUnresolvedErrors: (_, platform) => platform === 'ios', + transformCode: opts.transformCode, }); + this._minifyCode = opts.minifyCode; this._polyfillModuleNames = opts.polyfillModuleNames || []; this._depGraph.load().catch(err => { @@ -119,12 +126,14 @@ class Resolver { return this._depGraph.getModuleForPath(entryFile); } - getDependencies(entryPath, options) { + getDependencies(entryPath, options, transformOptions, onProgress) { const {platform, recursive} = getDependenciesValidateOpts(options); return this._depGraph.getDependencies({ entryPath, platform, + transformOptions, recursive, + onProgress, }).then(resolutionResponse => { this._getPolyfillDependencies().reverse().forEach( polyfill => resolutionResponse.prependDependency(polyfill) @@ -176,16 +185,14 @@ class Resolver { ); } - resolveRequires(resolutionResponse, module, code) { + resolveRequires(resolutionResponse, module, code, dependencyOffsets = []) { return Promise.resolve().then(() => { - if (module.isPolyfill()) { - return Promise.resolve({code}); - } - const resolvedDeps = Object.create(null); const resolvedDepsArr = []; return Promise.all( + // here, we build a map of all require strings (relative and absolute) + // to the canonical name of the module they reference resolutionResponse.getResolvedDependencyPairs(module).map( ([depName, depModule]) => { if (depModule) { @@ -197,59 +204,81 @@ class Resolver { } ) ).then(() => { - const relativizeCode = (codeMatch, pre, quot, depName, post) => { + const relativizeCode = (codeMatch, quot, depName) => { + // if we have a canonical name for the module imported here, + // we use it, so that require() is always called with the same + // id for every module. + // Example: + // -- in a/b.js: + // require('./c') => require('a/c'); + // -- in b/index.js: + // require('../a/c') => require('a/c'); const depId = resolvedDeps[depName]; if (depId) { - return pre + quot + depId + post; + return quot + depId + quot; } else { return codeMatch; } }; - code = code - .replace(replacePatterns.IMPORT_RE, relativizeCode) - .replace(replacePatterns.EXPORT_RE, relativizeCode) - .replace(replacePatterns.REQUIRE_RE, relativizeCode); + code = dependencyOffsets.reduceRight((codeBits, offset) => { + const first = codeBits.shift(); + codeBits.unshift( + first.slice(0, offset), + first.slice(offset).replace(/(['"])([^'"']*)\1/, relativizeCode), + ); + return codeBits; + }, [code]); - return module.getName().then(name => { - return {name, code}; - }); + return code.join(''); }); }); } - wrapModule(resolutionResponse, module, code) { - if (module.isPolyfill()) { - return Promise.resolve({ - code: definePolyfillCode(code), - }); + wrapModule({ + resolutionResponse, + module, + name, + map, + code, + meta = {}, + minify = false + }) { + if (module.isJSON()) { + code = `module.exports = ${code}`; } + const result = module.isPolyfill() + ? Promise.resolve({code: definePolyfillCode(code)}) + : this.resolveRequires( + resolutionResponse, + module, + code, + meta.dependencyOffsets + ).then(code => ({code: defineModuleCode(name, code), map})); - return this.resolveRequires(resolutionResponse, module, code).then( - ({name, code}) => { - return {name, code: defineModuleCode(name, code)}; - }); + return minify + ? result.then(({code, map}) => this._minifyCode(module.path, code, map)) + : result; } getDebugInfo() { return this._depGraph.getDebugInfo(); } - } function defineModuleCode(moduleName, code) { return [ `__d(`, - `'${moduleName}',`, - 'function(global, require, module, exports) {', - ` ${code}`, + `${JSON.stringify(moduleName)}, `, + `function(global, require, module, exports) {`, + `${code}`, '\n});', ].join(''); } -function definePolyfillCode(code) { +function definePolyfillCode(code,) { return [ - '(function(global) {', + `(function(global) {`, code, `\n})(typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : this);`, ].join(''); diff --git a/packager/react-packager/src/Server/index.js b/packager/react-packager/src/Server/index.js index 5949bd0c7..c1f20606e 100644 --- a/packager/react-packager/src/Server/index.js +++ b/packager/react-packager/src/Server/index.js @@ -73,10 +73,6 @@ const validateOpts = declareOpts({ type: 'string', required: false, }, - disableInternalTransforms: { - type: 'boolean', - default: false, - }, }); const bundleOpts = declareOpts({ @@ -146,6 +142,10 @@ const dependencyOpts = declareOpts({ type: 'boolean', default: true, }, + hot: { + type: 'boolean', + default: false, + }, }); class Server { @@ -259,12 +259,7 @@ class Server { } const opts = dependencyOpts(options); - return this._bundler.getDependencies( - opts.entryFile, - opts.dev, - opts.platform, - opts.recursive, - ); + return this._bundler.getDependencies(opts); }); } diff --git a/packager/react-packager/src/lib/ModuleTransport.js b/packager/react-packager/src/lib/ModuleTransport.js index afb660d38..8ba0aaea8 100644 --- a/packager/react-packager/src/lib/ModuleTransport.js +++ b/packager/react-packager/src/lib/ModuleTransport.js @@ -21,11 +21,7 @@ function ModuleTransport(data) { this.sourcePath = data.sourcePath; this.virtual = data.virtual; - - if (this.virtual && data.map) { - throw new Error('Virtual modules cannot have source maps'); - } - + this.meta = data.meta; this.map = data.map; Object.freeze(this); diff --git a/packager/react-packager/src/transforms/whole-program-optimisations/__tests__/dead-module-elimination-test.js b/packager/react-packager/src/transforms/whole-program-optimisations/__tests__/dead-module-elimination-test.js deleted file mode 100644 index 4d7d142c4..000000000 --- a/packager/react-packager/src/transforms/whole-program-optimisations/__tests__/dead-module-elimination-test.js +++ /dev/null @@ -1,115 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ -'use strict'; - -jest.autoMockOff(); - -var deadModuleElimintation = require('../dead-module-elimination'); -var babel = require('babel-core'); - -const compile = (code) => - babel.transform(code, { - plugins: [deadModuleElimintation], - }).code; - -const compare = (source, output) => { - const out = trim(compile(source)) - // workaround babel/source map bug - .replace(/^false;/, ''); - - expect(out).toEqual(trim(output)); -}; - - -const trim = (str) => - str.replace(/\s/g, ''); - -describe('dead-module-elimination', () => { - it('should inline __DEV__', () => { - compare( - `global.__DEV__ = false; - var foo = __DEV__;`, - `var foo = false;` - ); - }); - - it('should accept unary operators with literals', () => { - compare( - `global.__DEV__ = !1; - var foo = __DEV__;`, - `var foo = false;` - ); - }); - - it('should kill dead branches', () => { - compare( - `global.__DEV__ = false; - if (__DEV__) { - doSomething(); - }`, - `` - ); - }); - - it('should kill unreferenced modules', () => { - compare( - `__d('foo', function() {})`, - `` - ); - }); - - it('should kill unreferenced modules at multiple levels', () => { - compare( - `__d('bar', function() {}); - __d('foo', function() { require('bar'); });`, - `` - ); - }); - - it('should kill modules referenced only from dead branches', () => { - compare( - `global.__DEV__ = false; - __d('bar', function() {}); - if (__DEV__) { require('bar'); }`, - `` - ); - }); - - it('should replace logical expressions with the result', () => { - compare( - `global.__DEV__ = false; - __d('bar', function() {}); - __DEV__ && require('bar');`, - `false;` - ); - }); - - it('should keep if result branch', () => { - compare( - `global.__DEV__ = false; - __d('bar', function() {}); - if (__DEV__) { - killWithFire(); - } else { - require('bar'); - }`, - `__d('bar', function() {}); - require('bar');` - ); - }); - - it('should replace falsy ternaries with alternate expression', () => { - compare( - `global.__DEV__ = false; - __DEV__ ? foo() : bar(); - `, - `bar();` - ); - }); -}); diff --git a/packager/react-packager/src/transforms/whole-program-optimisations/dead-module-elimination.js b/packager/react-packager/src/transforms/whole-program-optimisations/dead-module-elimination.js deleted file mode 100644 index b5f33b4e0..000000000 --- a/packager/react-packager/src/transforms/whole-program-optimisations/dead-module-elimination.js +++ /dev/null @@ -1,148 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ -'use strict'; - -const t = require('babel-types'); - -var globals = Object.create(null); -var requires = Object.create(null); -var _requires; - -const hasDeadModules = modules => - Object.keys(modules).some(key => modules[key] === 0); - -function CallExpression(path) { - const { node } = path; - const fnName = node.callee.name; - - if (fnName === 'require' || fnName === '__d') { - var moduleName = node.arguments[0].value; - if (fnName === '__d' && _requires && !_requires[moduleName]) { - path.remove(); - } else if (fnName === '__d'){ - requires[moduleName] = requires[moduleName] || 0; - } else { - requires[moduleName] = (requires[moduleName] || 0) + 1; - } - } -} - -function IfStatement(path) { - const { node } = path; - - if (node.test.type === 'Identifier' && node.test.name in globals) { - if (globals[node.test.name]) { - if (node.consequent.type === 'BlockStatement') { - path.replaceWithMultiple(node.consequent.body); - } else { - path.replaceWith(node.consequent); - } - } else if (node.alternate) { - if (node.alternate.type === 'BlockStatement') { - path.replaceWithMultiple(node.alternate.body); - } else { - path.replaceWith(node.alternate); - } - } else { - path.remove(); - } - } - } - -module.exports = function () { - var firstPass = { - AssignmentExpression(path) { - const { node } = path; - - if ( - node.left.type === 'MemberExpression' && - node.left.object.name === 'global' && - node.left.property.type === 'Identifier' && - node.left.property.name === '__DEV__' - ) { - var value; - if (node.right.type === 'BooleanLiteral') { - value = node.right.value; - } else if ( - node.right.type === 'UnaryExpression' && - node.right.operator === '!' && - node.right.argument.type === 'NumericLiteral' - ) { - value = !node.right.argument.value; - } else { - return; - } - globals[node.left.property.name] = value; - - // workaround babel/source map bug - the minifier should strip it - path.replaceWith(t.booleanLiteral(value)); - - //path.remove(); - //scope.removeBinding(node.left.name); - } - }, - IfStatement, - ConditionalExpression: IfStatement, - Identifier(path) { - const { node } = path; - - var parent = path.parent; - if (parent.type === 'AssignmentExpression' && parent.left === node) { - return; - } - - if (node.name in globals) { - path.replaceWith(t.booleanLiteral(globals[node.name])); - } - }, - - CallExpression, - - LogicalExpression(path) { - const { node } = path; - - if (node.left.type === 'Identifier' && node.left.name in globals) { - const value = globals[node.left.name]; - - if (node.operator === '&&') { - if (value) { - path.replaceWith(node.right); - } else { - path.replaceWith(t.booleanLiteral(value)); - } - } else if (node.operator === '||') { - if (value) { - path.replaceWith(t.booleanLiteral(value)); - } else { - path.replaceWith(node.right); - } - } - } - } - }; - - var secondPass = { - CallExpression, - }; - - return { - visitor: { - Program(path) { - path.traverse(firstPass); - var counter = 0; - while (hasDeadModules(requires) && counter < 3) { - _requires = requires; - requires = {}; - path.traverse(secondPass); - counter++; - } - } - } - }; -}; diff --git a/packager/react-packager/src/transforms/whole-program-optimisations/index.js b/packager/react-packager/src/transforms/whole-program-optimisations/index.js deleted file mode 100644 index f802c0f77..000000000 --- a/packager/react-packager/src/transforms/whole-program-optimisations/index.js +++ /dev/null @@ -1,14 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ -'use strict'; - -// Return the list of plugins use for Whole Program Optimisations -module.exports = [ - require('./dead-module-elimination'), -]; diff --git a/packager/transformer.js b/packager/transformer.js index 071f4007f..18da16f28 100644 --- a/packager/transformer.js +++ b/packager/transformer.js @@ -61,7 +61,7 @@ const getBabelRC = (function() { } return babelRC; - } + }; })(); /** @@ -81,14 +81,16 @@ function buildBabelConfig(filename, options) { // Add extra plugins const extraPlugins = [externalHelpersPlugin]; - if (options.inlineRequires) { + var inlineRequires = options.inlineRequires; + var blacklist = inlineRequires && inlineRequires.blacklist; + if (inlineRequires && !(blacklist && filename in blacklist)) { extraPlugins.push(inlineRequiresPlugin); } config.plugins = extraPlugins.concat(config.plugins); if (options.hot) { - const hmrConfig = makeHMRConfig(options); + const hmrConfig = makeHMRConfig(options, filename); config = Object.assign({}, config, hmrConfig); } @@ -102,7 +104,9 @@ function transform(src, filename, options) { const result = babel.transform(src, babelConfig); return { + ast: result.ast, code: result.code, + map: result.map, filename: filename, }; }