packager: add support for relative files with custom extensions

Reviewed By: cpojer

Differential Revision: D4994139

fbshipit-source-id: 5e47c5bc6f8b2cd750f1ca0df940c23234c66600
This commit is contained in:
Jean Lauliac 2017-05-04 05:06:27 -07:00 committed by Facebook Github Bot
parent 264d67c424
commit 4a86f93982
15 changed files with 156 additions and 27 deletions

View File

@ -19,6 +19,7 @@ const outputBundle = require('./output/bundle');
const path = require('path'); const path = require('path');
const saveAssets = require('./saveAssets'); const saveAssets = require('./saveAssets');
const defaultAssetExts = require('../../packager/defaults').assetExts; const defaultAssetExts = require('../../packager/defaults').assetExts;
const defaultSourceExts = require('../../packager/defaults').sourceExts;
const defaultPlatforms = require('../../packager/defaults').platforms; const defaultPlatforms = require('../../packager/defaults').platforms;
const defaultProvidesModuleNodeModules = require('../../packager/defaults').providesModuleNodeModules; const defaultProvidesModuleNodeModules = require('../../packager/defaults').providesModuleNodeModules;
@ -64,6 +65,7 @@ function buildBundle(
var shouldClosePackager = false; var shouldClosePackager = false;
if (!packagerInstance) { if (!packagerInstance) {
const assetExts = (config.getAssetExts && config.getAssetExts()) || []; const assetExts = (config.getAssetExts && config.getAssetExts()) || [];
const sourceExts = (config.getSourceExts && config.getSourceExts()) || [];
const platforms = (config.getPlatforms && config.getPlatforms()) || []; const platforms = (config.getPlatforms && config.getPlatforms()) || [];
const transformModulePath = const transformModulePath =
@ -88,6 +90,7 @@ function buildBundle(
providesModuleNodeModules: providesModuleNodeModules, providesModuleNodeModules: providesModuleNodeModules,
resetCache: args.resetCache, resetCache: args.resetCache,
reporter: new TerminalReporter(), reporter: new TerminalReporter(),
sourceExts: defaultSourceExts.concat(sourceExts),
transformModulePath: transformModulePath, transformModulePath: transformModulePath,
watch: false, watch: false,
}; };

View File

@ -16,6 +16,7 @@ const connect = require('connect');
const copyToClipBoardMiddleware = require('./middleware/copyToClipBoardMiddleware'); const copyToClipBoardMiddleware = require('./middleware/copyToClipBoardMiddleware');
const cpuProfilerMiddleware = require('./middleware/cpuProfilerMiddleware'); const cpuProfilerMiddleware = require('./middleware/cpuProfilerMiddleware');
const defaultAssetExts = require('../../packager/defaults').assetExts; const defaultAssetExts = require('../../packager/defaults').assetExts;
const defaultSourceExts = require('../../packager/defaults').sourceExts;
const defaultPlatforms = require('../../packager/defaults').platforms; const defaultPlatforms = require('../../packager/defaults').platforms;
const defaultProvidesModuleNodeModules = require('../../packager/defaults').providesModuleNodeModules; const defaultProvidesModuleNodeModules = require('../../packager/defaults').providesModuleNodeModules;
const getDevToolsMiddleware = require('./middleware/getDevToolsMiddleware'); 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 // First we let require resolve it, so we can require packages in node_modules
// as expected. eg: require('my-package/reporter'); // as expected. eg: require('my-package/reporter');
LogReporter = require(args.customLogReporterPath); LogReporter = require(args.customLogReporterPath);
} catch(e) { } catch (e) {
// If that doesn't work, then we next try relative to the cwd, eg: // If that doesn't work, then we next try relative to the cwd, eg:
// require('./reporter'); // require('./reporter');
LogReporter = require(path.resolve(args.customLogReporterPath)); LogReporter = require(path.resolve(args.customLogReporterPath));
@ -116,6 +117,7 @@ function getPackagerServer(args, config) {
providesModuleNodeModules: providesModuleNodeModules, providesModuleNodeModules: providesModuleNodeModules,
reporter: new LogReporter(), reporter: new LogReporter(),
resetCache: args.resetCache, resetCache: args.resetCache,
sourceExts: defaultSourceExts.concat(args.sourceExts),
transformModulePath: transformModulePath, transformModulePath: transformModulePath,
verbose: args.verbose, verbose: args.verbose,
watch: !args.nonPersistent, watch: !args.nonPersistent,

View File

@ -67,9 +67,14 @@ module.exports = {
default: (config) => config.getProjectRoots(), default: (config) => config.getProjectRoots(),
}, { }, {
command: '--assetExts [list]', 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(','), parse: (val) => val.split(','),
default: (config) => config.getAssetExts(), 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]', command: '--platforms [list]',
description: 'Specify any additional platforms to be used by the packager', description: 'Specify any additional platforms to be used by the packager',

View File

@ -28,7 +28,7 @@ import type {HasteImpl} from '../../packager/src/node-haste/Module';
export type ConfigT = { export type ConfigT = {
extraNodeModules: {[id: string]: string}, 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'] * 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. * from here and use `require('./fonts/example.ttf')` inside your app.
*/ */
@ -54,6 +54,15 @@ export type ConfigT = {
* providesModule declarations. * providesModule declarations.
*/ */
getProvidesModuleNodeModules?: () => Array<string>, getProvidesModuleNodeModules?: () => Array<string>,
/**
* 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<string>,
/** /**
* Returns the path to a custom transformer. This can also be overridden * Returns the path to a custom transformer. This can also be overridden
* with the --transformer commandline argument. * with the --transformer commandline argument.
@ -90,6 +99,7 @@ const defaultConfig: ConfigT = {
getPlatforms: () => [], getPlatforms: () => [],
getProjectRoots: () => [process.cwd()], getProjectRoots: () => [process.cwd()],
getProvidesModuleNodeModules: () => providesModuleNodeModules.slice(), getProvidesModuleNodeModules: () => providesModuleNodeModules.slice(),
getSourceExts: () => [],
getTransformModulePath: () => path.resolve(__dirname, '../../packager/transformer'), getTransformModulePath: () => path.resolve(__dirname, '../../packager/transformer'),
getTransformOptions: async () => ({}), getTransformOptions: async () => ({}),
postMinifyProcess: x => x, postMinifyProcess: x => x,

View File

@ -17,6 +17,8 @@ exports.assetExts = [
'html', 'pdf', // Document formats 'html', 'pdf', // Document formats
]; ];
exports.sourceExts = ['js', 'json'];
exports.moduleSystem = require.resolve('./src/Resolver/polyfills/require.js'); exports.moduleSystem = require.resolve('./src/Resolver/polyfills/require.js');
exports.platforms = ['ios', 'android', 'windows', 'web']; exports.platforms = ['ios', 'android', 'windows', 'web'];

View File

@ -16,8 +16,8 @@ const Logger = require('./src/Logger');
const debug = require('debug'); const debug = require('debug');
const invariant = require('fbjs/lib/invariant'); const invariant = require('fbjs/lib/invariant');
import type Server from './src/Server';
import type {PostProcessModules, PostMinifyProcess} from './src/Bundler'; import type {PostProcessModules, PostMinifyProcess} from './src/Bundler';
import type Server from './src/Server';
import type {GlobalTransformCache} from './src/lib/GlobalTransformCache'; import type {GlobalTransformCache} from './src/lib/GlobalTransformCache';
import type {Reporter} from './src/lib/reporting'; import type {Reporter} from './src/lib/reporting';
import type {HasteImpl} from './src/node-haste/Module'; import type {HasteImpl} from './src/node-haste/Module';
@ -33,6 +33,7 @@ type Options = {
postMinifyProcess?: PostMinifyProcess, postMinifyProcess?: PostMinifyProcess,
projectRoots: Array<string>, projectRoots: Array<string>,
reporter?: Reporter, reporter?: Reporter,
+sourceExts: ?Array<string>,
watch?: boolean, watch?: boolean,
}; };

View File

@ -22,6 +22,10 @@ module.exports = {
return []; return [];
}, },
getSourceExts() {
return [];
},
getBlacklistRE() { getBlacklistRE() {
return blacklist(); return blacklist();
}, },

View File

@ -44,6 +44,7 @@ var commonOptions = {
extraNodeModules: {}, extraNodeModules: {},
platforms: defaults.platforms, platforms: defaults.platforms,
resetCache: false, resetCache: false,
sourceExts: defaults.sourceExts,
watch: false, watch: false,
}; };

View File

@ -132,6 +132,7 @@ type Options = {|
+providesModuleNodeModules?: Array<string>, +providesModuleNodeModules?: Array<string>,
+reporter: Reporter, +reporter: Reporter,
+resetCache: boolean, +resetCache: boolean,
+sourceExts: Array<string>,
+transformModulePath?: string, +transformModulePath?: string,
+transformTimeoutInterval: ?number, +transformTimeoutInterval: ?number,
+watch: boolean, +watch: boolean,
@ -217,6 +218,7 @@ class Bundler {
opts.providesModuleNodeModules || defaults.providesModuleNodeModules, opts.providesModuleNodeModules || defaults.providesModuleNodeModules,
reporter: opts.reporter, reporter: opts.reporter,
resetCache: opts.resetCache, resetCache: opts.resetCache,
sourceExts: opts.sourceExts,
transformCode: transformCode:
(module, code, transformCodeOptions) => this._transformer.transformFile( (module, code, transformCodeOptions) => this._transformer.transformFile(
module.path, module.path,

View File

@ -34,6 +34,7 @@ const defaults = require('../../../defaults');
type ResolveOptions = {| type ResolveOptions = {|
assetExts: Extensions, assetExts: Extensions,
extraNodeModules: {[id: string]: string}, extraNodeModules: {[id: string]: string},
+sourceExts: Extensions,
transformedFiles: {[path: Path]: TransformedCodeFile}, transformedFiles: {[path: Path]: TransformedCodeFile},
|}; |};
@ -72,6 +73,7 @@ exports.createResolveFn = function(options: ResolveOptions): ResolveFn {
assetExts, assetExts,
extraNodeModules, extraNodeModules,
transformedFiles, transformedFiles,
sourceExts,
} = options; } = options;
const files = Object.keys(transformedFiles); const files = Object.keys(transformedFiles);
function getTransformedFile(path) { function getTransformedFile(path) {
@ -93,7 +95,7 @@ exports.createResolveFn = function(options: ResolveOptions): ResolveFn {
getTransformedFile, getTransformedFile,
); );
const hasteMap = new HasteMap({ const hasteMap = new HasteMap({
extensions: ['js', 'json'], extensions: sourceExts,
files, files,
helpers, helpers,
moduleCache, moduleCache,
@ -119,6 +121,7 @@ exports.createResolveFn = function(options: ResolveOptions): ResolveFn {
platform, platform,
platforms, platforms,
preferNativePlatform: true, preferNativePlatform: true,
sourceExts,
}); });
} }

View File

@ -46,6 +46,7 @@ type Options = {|
+providesModuleNodeModules: Array<string>, +providesModuleNodeModules: Array<string>,
+reporter: Reporter, +reporter: Reporter,
+resetCache: boolean, +resetCache: boolean,
+sourceExts: Array<string>,
+transformCode: TransformCode, +transformCode: TransformCode,
+watch: boolean, +watch: boolean,
|}; |};
@ -67,7 +68,6 @@ class Resolver {
static async load(opts: Options): Promise<Resolver> { static async load(opts: Options): Promise<Resolver> {
const depGraphOpts = Object.assign(Object.create(opts), { const depGraphOpts = Object.assign(Object.create(opts), {
assetDependencies: ['react-native/Libraries/Image/AssetRegistry'], assetDependencies: ['react-native/Libraries/Image/AssetRegistry'],
extensions: ['js', 'json'],
forceNodeFilesystemAPI: false, forceNodeFilesystemAPI: false,
ignoreFilePath(filepath) { ignoreFilePath(filepath) {
return filepath.indexOf('__tests__') !== -1 || return filepath.indexOf('__tests__') !== -1 ||

View File

@ -74,6 +74,7 @@ type Options = {
reporter: Reporter, reporter: Reporter,
resetCache?: boolean, resetCache?: boolean,
silent?: boolean, silent?: boolean,
+sourceExts: ?Array<string>,
transformModulePath?: string, transformModulePath?: string,
transformTimeoutInterval?: number, transformTimeoutInterval?: number,
watch?: boolean, watch?: boolean,
@ -129,6 +130,7 @@ class Server {
reporter: Reporter, reporter: Reporter,
resetCache: boolean, resetCache: boolean,
silent: boolean, silent: boolean,
+sourceExts: Array<string>,
transformModulePath: void | string, transformModulePath: void | string,
transformTimeoutInterval: ?number, transformTimeoutInterval: ?number,
watch: boolean, watch: boolean,
@ -166,6 +168,7 @@ class Server {
reporter: options.reporter, reporter: options.reporter,
resetCache: options.resetCache || false, resetCache: options.resetCache || false,
silent: options.silent || false, silent: options.silent || false,
sourceExts: options.sourceExts || defaults.sourceExts,
transformModulePath: options.transformModulePath, transformModulePath: options.transformModulePath,
transformTimeoutInterval: options.transformTimeoutInterval, transformTimeoutInterval: options.transformTimeoutInterval,
watch: options.watch || false, watch: options.watch || false,

View File

@ -71,6 +71,7 @@ type Options<TModule, TPackage> = {|
+platform: ?string, +platform: ?string,
+platforms: Set<string>, +platforms: Set<string>,
+preferNativePlatform: boolean, +preferNativePlatform: boolean,
+sourceExts: Array<string>,
|}; |};
/** /**
@ -528,17 +529,32 @@ class ResolutionRequest<TModule: Moduleish, TPackage: Packageish> {
let file; let file;
if (this._options.hasteFS.exists(potentialModulePath)) { if (this._options.hasteFS.exists(potentialModulePath)) {
file = 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 { } else {
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( throw new UnableToResolveError(
fromModule, fromModule,
toModule, toModule,
@ -546,6 +562,8 @@ class ResolutionRequest<TModule: Moduleish, TPackage: Packageish> {
); );
} }
}
return this._options.moduleCache.getModule(file); return this._options.moduleCache.getModule(file);
} }

View File

@ -40,7 +40,7 @@ describe('DependencyGraph', function() {
let emptyTransformOptions; let emptyTransformOptions;
function getOrderedDependenciesAsJSON(dgraphPromise, entryPath, platform, recursive = true) { function getOrderedDependenciesAsJSON(dgraphPromise, entryPath, platform, recursive = true) {
return dgraphPromise return Promise.resolve(dgraphPromise)
.then(dgraph => dgraph.getDependencies({ .then(dgraph => dgraph.getDependencies({
entryPath, entryPath,
options: emptyTransformOptions, options: emptyTransformOptions,
@ -72,7 +72,6 @@ describe('DependencyGraph', function() {
emptyTransformOptions = {transformer: {transform: {}}}; emptyTransformOptions = {transformer: {transform: {}}};
defaults = { defaults = {
assetExts: ['png', 'jpg'], assetExts: ['png', 'jpg'],
extensions: ['js', 'json'],
forceNodeFilesystemAPI: true, forceNodeFilesystemAPI: true,
providesModuleNodeModules: [ providesModuleNodeModules: [
'haste-fbjs', 'haste-fbjs',
@ -96,6 +95,7 @@ describe('DependencyGraph', function() {
}, },
getTransformCacheKey: () => 'abcdef', getTransformCacheKey: () => 'abcdef',
reporter: require('../../lib/reporting').nullReporter, reporter: require('../../lib/reporting').nullReporter,
sourceExts: ['js', 'json'],
watch: true, watch: true,
}; };
}); });
@ -5250,7 +5250,7 @@ describe('DependencyGraph', function() {
var dgraph = DependencyGraph.load({ var dgraph = DependencyGraph.load({
...defaults, ...defaults,
roots: [root], roots: [root],
extensions: ['jsx', 'coffee'], sourceExts: ['jsx', 'coffee'],
}); });
return dgraph 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', () => { describe('Progress updates', () => {

View File

@ -51,7 +51,6 @@ import type {HasteFS} from './types';
type Options = {| type Options = {|
+assetDependencies: Array<string>, +assetDependencies: Array<string>,
+assetExts: Array<string>, +assetExts: Array<string>,
+extensions: Array<string>,
+extraNodeModules: ?{}, +extraNodeModules: ?{},
+forceNodeFilesystemAPI: boolean, +forceNodeFilesystemAPI: boolean,
+getTransformCacheKey: GetTransformCacheKey, +getTransformCacheKey: GetTransformCacheKey,
@ -65,6 +64,7 @@ type Options = {|
+reporter: Reporter, +reporter: Reporter,
+resetCache: boolean, +resetCache: boolean,
+roots: Array<string>, +roots: Array<string>,
+sourceExts: Array<string>,
+transformCode: TransformCode, +transformCode: TransformCode,
+useWatchman: boolean, +useWatchman: boolean,
+watch: boolean, +watch: boolean,
@ -103,7 +103,7 @@ class DependencyGraph extends EventEmitter {
static _createHaste(opts: Options): JestHasteMap { static _createHaste(opts: Options): JestHasteMap {
return new JestHasteMap({ return new JestHasteMap({
extensions: opts.extensions.concat(opts.assetExts), extensions: opts.sourceExts.concat(opts.assetExts),
forceNodeFilesystemAPI: opts.forceNodeFilesystemAPI, forceNodeFilesystemAPI: opts.forceNodeFilesystemAPI,
ignorePattern: {test: opts.ignoreFilePath}, ignorePattern: {test: opts.ignoreFilePath},
maxWorkers: opts.maxWorkerCount, maxWorkers: opts.maxWorkerCount,
@ -234,6 +234,7 @@ class DependencyGraph extends EventEmitter {
platform, platform,
platforms: this._opts.platforms, platforms: this._opts.platforms,
preferNativePlatform: this._opts.preferNativePlatform, preferNativePlatform: this._opts.preferNativePlatform,
sourceExts: this._opts.sourceExts,
}); });
const response = new ResolutionResponse(options); const response = new ResolutionResponse(options);