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, }; }