diff --git a/local-cli/bundle/buildBundle.js b/local-cli/bundle/buildBundle.js index 3aa739ab3..97be14d79 100644 --- a/local-cli/bundle/buildBundle.js +++ b/local-cli/bundle/buildBundle.js @@ -19,6 +19,7 @@ const outputBundle = require('./output/bundle'); const path = require('path'); const saveAssets = require('./saveAssets'); const defaultAssetExts = require('../../packager/defaults').assetExts; +const defaultSourceExts = require('../../packager/defaults').sourceExts; const defaultPlatforms = require('../../packager/defaults').platforms; const defaultProvidesModuleNodeModules = require('../../packager/defaults').providesModuleNodeModules; @@ -64,6 +65,7 @@ function buildBundle( var shouldClosePackager = false; if (!packagerInstance) { const assetExts = (config.getAssetExts && config.getAssetExts()) || []; + const sourceExts = (config.getSourceExts && config.getSourceExts()) || []; const platforms = (config.getPlatforms && config.getPlatforms()) || []; const transformModulePath = @@ -88,6 +90,7 @@ function buildBundle( providesModuleNodeModules: providesModuleNodeModules, resetCache: args.resetCache, reporter: new TerminalReporter(), + sourceExts: defaultSourceExts.concat(sourceExts), transformModulePath: transformModulePath, watch: false, }; diff --git a/local-cli/server/runServer.js b/local-cli/server/runServer.js index 870bf0b99..ca3146210 100644 --- a/local-cli/server/runServer.js +++ b/local-cli/server/runServer.js @@ -16,6 +16,7 @@ const connect = require('connect'); const copyToClipBoardMiddleware = require('./middleware/copyToClipBoardMiddleware'); const cpuProfilerMiddleware = require('./middleware/cpuProfilerMiddleware'); const defaultAssetExts = require('../../packager/defaults').assetExts; +const defaultSourceExts = require('../../packager/defaults').sourceExts; const defaultPlatforms = require('../../packager/defaults').platforms; const defaultProvidesModuleNodeModules = require('../../packager/defaults').providesModuleNodeModules; const getDevToolsMiddleware = require('./middleware/getDevToolsMiddleware'); @@ -93,7 +94,7 @@ function getPackagerServer(args, config) { // First we let require resolve it, so we can require packages in node_modules // as expected. eg: require('my-package/reporter'); LogReporter = require(args.customLogReporterPath); - } catch(e) { + } catch (e) { // If that doesn't work, then we next try relative to the cwd, eg: // require('./reporter'); LogReporter = require(path.resolve(args.customLogReporterPath)); @@ -116,6 +117,7 @@ function getPackagerServer(args, config) { providesModuleNodeModules: providesModuleNodeModules, reporter: new LogReporter(), resetCache: args.resetCache, + sourceExts: defaultSourceExts.concat(args.sourceExts), transformModulePath: transformModulePath, verbose: args.verbose, watch: !args.nonPersistent, diff --git a/local-cli/server/server.js b/local-cli/server/server.js index 4825fc9a0..110db66ee 100644 --- a/local-cli/server/server.js +++ b/local-cli/server/server.js @@ -67,9 +67,14 @@ module.exports = { default: (config) => config.getProjectRoots(), }, { command: '--assetExts [list]', - description: 'Specify any additional asset extentions to be used by the packager', + description: 'Specify any additional asset extensions to be used by the packager', parse: (val) => val.split(','), default: (config) => config.getAssetExts(), + }, { + command: '--sourceExts [list]', + description: 'Specify any additional source extensions to be used by the packager', + parse: (val) => val.split(','), + default: (config) => config.getSourceExts(), }, { command: '--platforms [list]', description: 'Specify any additional platforms to be used by the packager', diff --git a/local-cli/util/Config.js b/local-cli/util/Config.js index 68b91eb60..987046bae 100644 --- a/local-cli/util/Config.js +++ b/local-cli/util/Config.js @@ -28,7 +28,7 @@ import type {HasteImpl} from '../../packager/src/node-haste/Module'; export type ConfigT = { extraNodeModules: {[id: string]: string}, /** - * Specify any additional asset extentions to be used by the packager. + * Specify any additional asset file extensions to be used by the packager. * For example, if you want to include a .ttf file, you would return ['ttf'] * from here and use `require('./fonts/example.ttf')` inside your app. */ @@ -54,6 +54,15 @@ export type ConfigT = { * providesModule declarations. */ getProvidesModuleNodeModules?: () => Array, + + /** + * Specify any additional source file extensions to be used by the packager. + * For example, if you want to include a .ts file, you would return ['ts'] + * from here and use `require('./module/example')` to require the file with + * path 'module/example.ts' inside your app. + */ + getSourceExts: () => Array, + /** * Returns the path to a custom transformer. This can also be overridden * with the --transformer commandline argument. @@ -90,6 +99,7 @@ const defaultConfig: ConfigT = { getPlatforms: () => [], getProjectRoots: () => [process.cwd()], getProvidesModuleNodeModules: () => providesModuleNodeModules.slice(), + getSourceExts: () => [], getTransformModulePath: () => path.resolve(__dirname, '../../packager/transformer'), getTransformOptions: async () => ({}), postMinifyProcess: x => x, diff --git a/packager/defaults.js b/packager/defaults.js index 119396977..7f6c2e721 100644 --- a/packager/defaults.js +++ b/packager/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/packager/react-packager.js b/packager/react-packager.js index 079b42ac8..24d001c1d 100644 --- a/packager/react-packager.js +++ b/packager/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/packager/rn-cli.config.js b/packager/rn-cli.config.js index 3f6f4fd54..3b398c7d1 100644 --- a/packager/rn-cli.config.js +++ b/packager/rn-cli.config.js @@ -22,6 +22,10 @@ module.exports = { return []; }, + getSourceExts() { + return []; + }, + getBlacklistRE() { return blacklist(); }, diff --git a/packager/src/Bundler/__tests__/Bundler-test.js b/packager/src/Bundler/__tests__/Bundler-test.js index 9100b766c..dcea0d6e1 100644 --- a/packager/src/Bundler/__tests__/Bundler-test.js +++ b/packager/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/packager/src/Bundler/index.js b/packager/src/Bundler/index.js index 03396550c..628ffcbc0 100644 --- a/packager/src/Bundler/index.js +++ b/packager/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/packager/src/ModuleGraph/node-haste/node-haste.js b/packager/src/ModuleGraph/node-haste/node-haste.js index fd3e48df3..ac93bde1c 100644 --- a/packager/src/ModuleGraph/node-haste/node-haste.js +++ b/packager/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/packager/src/Resolver/index.js b/packager/src/Resolver/index.js index 110913966..ea2bd23cb 100644 --- a/packager/src/Resolver/index.js +++ b/packager/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/packager/src/Server/index.js b/packager/src/Server/index.js index b450506c0..4344cd287 100644 --- a/packager/src/Server/index.js +++ b/packager/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/packager/src/node-haste/DependencyGraph/ResolutionRequest.js b/packager/src/node-haste/DependencyGraph/ResolutionRequest.js index 6e2a910c3..170d8e05d 100644 --- a/packager/src/node-haste/DependencyGraph/ResolutionRequest.js +++ b/packager/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/packager/src/node-haste/__tests__/DependencyGraph-test.js b/packager/src/node-haste/__tests__/DependencyGraph-test.js index a90ed9907..857849ec0 100644 --- a/packager/src/node-haste/__tests__/DependencyGraph-test.js +++ b/packager/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/packager/src/node-haste/index.js b/packager/src/node-haste/index.js index 0633d23d5..b950f2b1a 100644 --- a/packager/src/node-haste/index.js +++ b/packager/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);