diff --git a/packages/metro-bundler/defaults.js b/packages/metro-bundler/defaults.js index 11939697..7f6c2e72 100644 --- a/packages/metro-bundler/defaults.js +++ b/packages/metro-bundler/defaults.js @@ -10,13 +10,15 @@ */ 'use strict'; -exports.assetExts = [ +exports.assetExts = [ 'bmp', 'gif', 'jpg', 'jpeg', 'png', 'psd', 'svg', 'webp', // Image formats 'm4v', 'mov', 'mp4', 'mpeg', 'mpg', 'webm', // Video formats 'aac', 'aiff', 'caf', 'm4a', 'mp3', 'wav', // Audio formats 'html', 'pdf', // Document formats ]; +exports.sourceExts = ['js', 'json']; + exports.moduleSystem = require.resolve('./src/Resolver/polyfills/require.js'); exports.platforms = ['ios', 'android', 'windows', 'web']; diff --git a/packages/metro-bundler/react-packager.js b/packages/metro-bundler/react-packager.js index 079b42ac..24d001c1 100644 --- a/packages/metro-bundler/react-packager.js +++ b/packages/metro-bundler/react-packager.js @@ -16,8 +16,8 @@ const Logger = require('./src/Logger'); const debug = require('debug'); const invariant = require('fbjs/lib/invariant'); -import type Server from './src/Server'; import type {PostProcessModules, PostMinifyProcess} from './src/Bundler'; +import type Server from './src/Server'; import type {GlobalTransformCache} from './src/lib/GlobalTransformCache'; import type {Reporter} from './src/lib/reporting'; import type {HasteImpl} from './src/node-haste/Module'; @@ -33,6 +33,7 @@ type Options = { postMinifyProcess?: PostMinifyProcess, projectRoots: Array, reporter?: Reporter, + +sourceExts: ?Array, watch?: boolean, }; diff --git a/packages/metro-bundler/rn-cli.config.js b/packages/metro-bundler/rn-cli.config.js index 3f6f4fd5..3b398c7d 100644 --- a/packages/metro-bundler/rn-cli.config.js +++ b/packages/metro-bundler/rn-cli.config.js @@ -22,6 +22,10 @@ module.exports = { return []; }, + getSourceExts() { + return []; + }, + getBlacklistRE() { return blacklist(); }, diff --git a/packages/metro-bundler/src/Bundler/__tests__/Bundler-test.js b/packages/metro-bundler/src/Bundler/__tests__/Bundler-test.js index 9100b766..dcea0d6e 100644 --- a/packages/metro-bundler/src/Bundler/__tests__/Bundler-test.js +++ b/packages/metro-bundler/src/Bundler/__tests__/Bundler-test.js @@ -44,6 +44,7 @@ var commonOptions = { extraNodeModules: {}, platforms: defaults.platforms, resetCache: false, + sourceExts: defaults.sourceExts, watch: false, }; diff --git a/packages/metro-bundler/src/Bundler/index.js b/packages/metro-bundler/src/Bundler/index.js index 03396550..628ffcbc 100644 --- a/packages/metro-bundler/src/Bundler/index.js +++ b/packages/metro-bundler/src/Bundler/index.js @@ -132,6 +132,7 @@ type Options = {| +providesModuleNodeModules?: Array, +reporter: Reporter, +resetCache: boolean, + +sourceExts: Array, +transformModulePath?: string, +transformTimeoutInterval: ?number, +watch: boolean, @@ -217,6 +218,7 @@ class Bundler { opts.providesModuleNodeModules || defaults.providesModuleNodeModules, reporter: opts.reporter, resetCache: opts.resetCache, + sourceExts: opts.sourceExts, transformCode: (module, code, transformCodeOptions) => this._transformer.transformFile( module.path, diff --git a/packages/metro-bundler/src/ModuleGraph/node-haste/node-haste.js b/packages/metro-bundler/src/ModuleGraph/node-haste/node-haste.js index fd3e48df..ac93bde1 100644 --- a/packages/metro-bundler/src/ModuleGraph/node-haste/node-haste.js +++ b/packages/metro-bundler/src/ModuleGraph/node-haste/node-haste.js @@ -34,6 +34,7 @@ const defaults = require('../../../defaults'); type ResolveOptions = {| assetExts: Extensions, extraNodeModules: {[id: string]: string}, + +sourceExts: Extensions, transformedFiles: {[path: Path]: TransformedCodeFile}, |}; @@ -72,6 +73,7 @@ exports.createResolveFn = function(options: ResolveOptions): ResolveFn { assetExts, extraNodeModules, transformedFiles, + sourceExts, } = options; const files = Object.keys(transformedFiles); function getTransformedFile(path) { @@ -93,7 +95,7 @@ exports.createResolveFn = function(options: ResolveOptions): ResolveFn { getTransformedFile, ); const hasteMap = new HasteMap({ - extensions: ['js', 'json'], + extensions: sourceExts, files, helpers, moduleCache, @@ -119,6 +121,7 @@ exports.createResolveFn = function(options: ResolveOptions): ResolveFn { platform, platforms, preferNativePlatform: true, + sourceExts, }); } diff --git a/packages/metro-bundler/src/Resolver/index.js b/packages/metro-bundler/src/Resolver/index.js index 11091396..ea2bd23c 100644 --- a/packages/metro-bundler/src/Resolver/index.js +++ b/packages/metro-bundler/src/Resolver/index.js @@ -46,6 +46,7 @@ type Options = {| +providesModuleNodeModules: Array, +reporter: Reporter, +resetCache: boolean, + +sourceExts: Array, +transformCode: TransformCode, +watch: boolean, |}; @@ -67,7 +68,6 @@ class Resolver { static async load(opts: Options): Promise { const depGraphOpts = Object.assign(Object.create(opts), { assetDependencies: ['react-native/Libraries/Image/AssetRegistry'], - extensions: ['js', 'json'], forceNodeFilesystemAPI: false, ignoreFilePath(filepath) { return filepath.indexOf('__tests__') !== -1 || diff --git a/packages/metro-bundler/src/Server/index.js b/packages/metro-bundler/src/Server/index.js index b450506c..4344cd28 100644 --- a/packages/metro-bundler/src/Server/index.js +++ b/packages/metro-bundler/src/Server/index.js @@ -74,6 +74,7 @@ type Options = { reporter: Reporter, resetCache?: boolean, silent?: boolean, + +sourceExts: ?Array, transformModulePath?: string, transformTimeoutInterval?: number, watch?: boolean, @@ -129,6 +130,7 @@ class Server { reporter: Reporter, resetCache: boolean, silent: boolean, + +sourceExts: Array, transformModulePath: void | string, transformTimeoutInterval: ?number, watch: boolean, @@ -166,6 +168,7 @@ class Server { reporter: options.reporter, resetCache: options.resetCache || false, silent: options.silent || false, + sourceExts: options.sourceExts || defaults.sourceExts, transformModulePath: options.transformModulePath, transformTimeoutInterval: options.transformTimeoutInterval, watch: options.watch || false, diff --git a/packages/metro-bundler/src/node-haste/DependencyGraph/ResolutionRequest.js b/packages/metro-bundler/src/node-haste/DependencyGraph/ResolutionRequest.js index 6e2a910c..170d8e05 100644 --- a/packages/metro-bundler/src/node-haste/DependencyGraph/ResolutionRequest.js +++ b/packages/metro-bundler/src/node-haste/DependencyGraph/ResolutionRequest.js @@ -71,6 +71,7 @@ type Options = {| +platform: ?string, +platforms: Set, +preferNativePlatform: boolean, + +sourceExts: Array, |}; /** @@ -528,22 +529,39 @@ class ResolutionRequest { let file; if (this._options.hasteFS.exists(potentialModulePath)) { file = potentialModulePath; - } else if (this._options.platform != null && - this._options.hasteFS.exists(potentialModulePath + '.' + this._options.platform + '.js')) { - file = potentialModulePath + '.' + this._options.platform + '.js'; - } else if (this._preferNativePlatform && - this._options.hasteFS.exists(potentialModulePath + '.native.js')) { - file = potentialModulePath + '.native.js'; - } else if (this._options.hasteFS.exists(potentialModulePath + '.js')) { - file = potentialModulePath + '.js'; - } else if (this._options.hasteFS.exists(potentialModulePath + '.json')) { - file = potentialModulePath + '.json'; } else { - throw new UnableToResolveError( - fromModule, - toModule, - `File ${potentialModulePath} doesn't exist`, - ); + const {platform, preferNativePlatform, hasteFS} = this._options; + for (let i = 0; i < this._options.sourceExts.length; i++) { + const ext = this._options.sourceExts[i]; + if (platform != null) { + const platformSpecificPath = `${potentialModulePath}.${platform}.${ext}`; + if (hasteFS.exists(platformSpecificPath)) { + file = platformSpecificPath; + break; + } + } + if (preferNativePlatform) { + const nativeSpecificPath = `${potentialModulePath}.native.${ext}`; + if (hasteFS.exists(nativeSpecificPath)) { + file = nativeSpecificPath; + break; + } + } + const genericPath = `${potentialModulePath}.${ext}`; + if (hasteFS.exists(genericPath)) { + file = genericPath; + break; + } + } + + if (file == null) { + throw new UnableToResolveError( + fromModule, + toModule, + `File ${potentialModulePath} doesn't exist`, + ); + } + } return this._options.moduleCache.getModule(file); diff --git a/packages/metro-bundler/src/node-haste/__tests__/DependencyGraph-test.js b/packages/metro-bundler/src/node-haste/__tests__/DependencyGraph-test.js index a90ed990..857849ec 100644 --- a/packages/metro-bundler/src/node-haste/__tests__/DependencyGraph-test.js +++ b/packages/metro-bundler/src/node-haste/__tests__/DependencyGraph-test.js @@ -40,7 +40,7 @@ describe('DependencyGraph', function() { let emptyTransformOptions; function getOrderedDependenciesAsJSON(dgraphPromise, entryPath, platform, recursive = true) { - return dgraphPromise + return Promise.resolve(dgraphPromise) .then(dgraph => dgraph.getDependencies({ entryPath, options: emptyTransformOptions, @@ -72,7 +72,6 @@ describe('DependencyGraph', function() { emptyTransformOptions = {transformer: {transform: {}}}; defaults = { assetExts: ['png', 'jpg'], - extensions: ['js', 'json'], forceNodeFilesystemAPI: true, providesModuleNodeModules: [ 'haste-fbjs', @@ -96,6 +95,7 @@ describe('DependencyGraph', function() { }, getTransformCacheKey: () => 'abcdef', reporter: require('../../lib/reporting').nullReporter, + sourceExts: ['js', 'json'], watch: true, }; }); @@ -5250,7 +5250,7 @@ describe('DependencyGraph', function() { var dgraph = DependencyGraph.load({ ...defaults, roots: [root], - extensions: ['jsx', 'coffee'], + sourceExts: ['jsx', 'coffee'], }); return dgraph @@ -5284,6 +5284,80 @@ describe('DependencyGraph', function() { ]); }); }); + + it('supports custom file extensions with relative paths', async () => { + const root = '/root'; + setMockFileSystem({ + 'root': { + 'index.jsx': [ + 'require("./a")', + ].join('\n'), + 'a.coffee': [ + ].join('\n'), + 'X.js': '', + }, + }); + + const dgraph = await DependencyGraph.load({ + ...defaults, + roots: [root], + sourceExts: ['jsx', 'coffee'], + }); + const files = await dgraph.matchFilesByPattern('.*'); + expect(files).toEqual([ + '/root/index.jsx', '/root/a.coffee', + ]); + + const deps = await getOrderedDependenciesAsJSON(dgraph, '/root/index.jsx'); + expect(deps).toEqual([ + { + dependencies: ['./a'], + id: '/root/index.jsx', + isAsset: false, + isJSON: false, + isPolyfill: false, + path: '/root/index.jsx', + resolution: undefined, + }, + { + dependencies: [], + id: '/root/a.coffee', + isAsset: false, + isJSON: false, + isPolyfill: false, + path: '/root/a.coffee', + resolution: undefined, + }, + ]); + }); + + it('does not include extensions that are not specified explicitely', async () => { + const root = '/root'; + setMockFileSystem({ + 'root': { + 'index.jsx': [ + 'require("./a")', + ].join('\n'), + 'a.coffee': [ + ].join('\n'), + 'X.js': '', + }, + }); + + const dgraph = await DependencyGraph.load({ + ...defaults, + roots: [root], + }); + const files = await dgraph.matchFilesByPattern('.*'); + expect(files).toEqual(['/root/X.js']); + + try { + await getOrderedDependenciesAsJSON(dgraph, '/root/index.jsx'); + throw Error('should not reach this line'); + } catch (error) { + expect(error.type).toEqual('UnableToResolveError'); + } + }); }); describe('Progress updates', () => { diff --git a/packages/metro-bundler/src/node-haste/index.js b/packages/metro-bundler/src/node-haste/index.js index 0633d23d..b950f2b1 100644 --- a/packages/metro-bundler/src/node-haste/index.js +++ b/packages/metro-bundler/src/node-haste/index.js @@ -51,7 +51,6 @@ import type {HasteFS} from './types'; type Options = {| +assetDependencies: Array, +assetExts: Array, - +extensions: Array, +extraNodeModules: ?{}, +forceNodeFilesystemAPI: boolean, +getTransformCacheKey: GetTransformCacheKey, @@ -65,6 +64,7 @@ type Options = {| +reporter: Reporter, +resetCache: boolean, +roots: Array, + +sourceExts: Array, +transformCode: TransformCode, +useWatchman: boolean, +watch: boolean, @@ -103,7 +103,7 @@ class DependencyGraph extends EventEmitter { static _createHaste(opts: Options): JestHasteMap { return new JestHasteMap({ - extensions: opts.extensions.concat(opts.assetExts), + extensions: opts.sourceExts.concat(opts.assetExts), forceNodeFilesystemAPI: opts.forceNodeFilesystemAPI, ignorePattern: {test: opts.ignoreFilePath}, maxWorkers: opts.maxWorkerCount, @@ -234,6 +234,7 @@ class DependencyGraph extends EventEmitter { platform, platforms: this._opts.platforms, preferNativePlatform: this._opts.preferNativePlatform, + sourceExts: this._opts.sourceExts, }); const response = new ResolutionResponse(options);