diff --git a/packages/relay-compiler/bin/RelayCompilerBin.js b/packages/relay-compiler/bin/RelayCompilerBin.js index c7e3e2ece5e28..8d471bcab968c 100644 --- a/packages/relay-compiler/bin/RelayCompilerBin.js +++ b/packages/relay-compiler/bin/RelayCompilerBin.js @@ -84,6 +84,13 @@ const options = { type: 'boolean', default: false, }, + generateModuleNameFunction: { + describe: + 'A function (or path to a module exporting this function) which will generate a module name for a given file path.', + demandOption: false, + type: 'string', + array: false, + }, persistFunction: { describe: 'An async function (or path to a module exporting this function) which will persist the query text and return the id.', diff --git a/packages/relay-compiler/bin/RelayCompilerMain.js b/packages/relay-compiler/bin/RelayCompilerMain.js index 64957daa8e939..184b2a64b5864 100644 --- a/packages/relay-compiler/bin/RelayCompilerMain.js +++ b/packages/relay-compiler/bin/RelayCompilerMain.js @@ -44,6 +44,7 @@ import type { PluginInitializer, PluginInterface, } from '../language/RelayLanguagePluginInterface'; +import getModuleName from '../util/getModuleName'; export type Config = {| schema: string, @@ -60,6 +61,7 @@ export type Config = {| noFutureProofEnums: boolean, eagerESModules?: boolean, language: string | PluginInitializer, + generateModuleNameFunction?: ?string | ?((filePath: string) => string), persistFunction?: ?string | ?((text: string) => Promise), repersist: boolean, artifactDirectory?: ?string, @@ -166,6 +168,38 @@ function getLanguagePlugin( } } +function getGenerateModuleNameFunction( + config: Config, +): (filePath: string) => string { + const configValue = config.generateModuleNameFunction; + if (configValue == null) { + return getModuleName; + } else if (typeof configValue === 'string') { + try { + // eslint-disable-next-line no-eval + const generateModuleNameFunction = eval('require')( + path.resolve(process.cwd(), configValue), + ); + if (generateModuleNameFunction.default) { + return generateModuleNameFunction.default; + } + return generateModuleNameFunction; + } catch (err) { + const e = new Error( + `Unable to load generateModuleNameFunction ${configValue}: ${err.message}`, + ); + e.stack = err.stack; + throw e; + } + } else if (typeof configValue === 'function') { + return configValue; + } else { + throw new Error( + 'Expected generateModuleNameFunction to be a path string or a function.', + ); + } +} + function getPersistQueryFunction( config: Config, ): ?(text: string) => Promise { @@ -285,6 +319,7 @@ function getCodegenRunner(config: Config): CodegenRunner { const languagePlugin = getLanguagePlugin(config.language, { eagerESModules: config.eagerESModules === true, }); + const generateModuleNameFunction = getGenerateModuleNameFunction(config); const persistQueryFunction = getPersistQueryFunction(config); const inputExtensions = config.extensions || languagePlugin.inputExtensions; const outputExtension = languagePlugin.outputExtension; @@ -292,6 +327,7 @@ function getCodegenRunner(config: Config): CodegenRunner { const sourceWriterName = outputExtension; const sourceModuleParser = RelaySourceModuleParser( languagePlugin.findGraphQLTags, + generateModuleNameFunction, languagePlugin.getFileFilter, ); const providedArtifactDirectory = config.artifactDirectory; @@ -508,6 +544,7 @@ function hasWatchmanRootFile(testPath: string): boolean { module.exports = { getCodegenRunner, getLanguagePlugin, + getGenerateModuleNameFunction, getWatchConfig, hasWatchmanRootFile, main, diff --git a/packages/relay-compiler/bin/__fixtures__/name-generator-module.js b/packages/relay-compiler/bin/__fixtures__/name-generator-module.js new file mode 100644 index 0000000000000..d089251143729 --- /dev/null +++ b/packages/relay-compiler/bin/__fixtures__/name-generator-module.js @@ -0,0 +1,16 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + * @format + * @emails oncall+relay + */ + +// flowlint ambiguous-object-type:error + +'use strict'; + +module.exports = (filePath: string): string => filePath.toUpperCase(); diff --git a/packages/relay-compiler/bin/__tests__/RelayCompilerMain-test.js b/packages/relay-compiler/bin/__tests__/RelayCompilerMain-test.js index 6857a9235dbc7..5fa4354be4d9c 100644 --- a/packages/relay-compiler/bin/__tests__/RelayCompilerMain-test.js +++ b/packages/relay-compiler/bin/__tests__/RelayCompilerMain-test.js @@ -13,6 +13,7 @@ const RelayCompilerMain = require('../RelayCompilerMain'); const RelayFileWriter = require('../../codegen/RelayFileWriter'); const RelayLanguagePluginJavaScript = require('../../language/javascript/RelayLanguagePluginJavaScript'); +const getModuleName = require('../../util/getModuleName'); const path = require('path'); @@ -23,6 +24,7 @@ const {testSchemaPath} = require('relay-test-utils-internal'); const { getCodegenRunner, getLanguagePlugin, + getGenerateModuleNameFunction, getWatchConfig, main, } = RelayCompilerMain; @@ -334,6 +336,37 @@ describe('RelayCompilerMain', () => { }); }); + describe('getGenerateModuleNameFunction', () => { + it('uses the builtin module name generator if not configured', () => { + expect(config.generateModuleNameFunction).toBeUndefined(); + + expect(getGenerateModuleNameFunction(config)).toBe(getModuleName); + }); + + it('loads a module name generator from a local module', () => { + config = { + ...config, + generateModuleNameFunction: path.join( + __dirname, + '..', + '__fixtures__', + 'name-generator-module.js', + ), + }; + const generateModuleName = getGenerateModuleNameFunction(config); + expect(generateModuleName('foo')).toEqual('FOO'); + }); + + it('accepts a module name generator function', () => { + const generate = jest.fn(); + config = { + ...config, + generateModuleNameFunction: generate, + }; + expect(getGenerateModuleNameFunction(config)).toBe(generate); + }); + }); + describe('concerning the codegen runner', () => { const options = { schema: testSchemaPath, diff --git a/packages/relay-compiler/core/JSModuleParser.js b/packages/relay-compiler/core/JSModuleParser.js index c40464d839598..5ff15b58f1033 100644 --- a/packages/relay-compiler/core/JSModuleParser.js +++ b/packages/relay-compiler/core/JSModuleParser.js @@ -14,11 +14,13 @@ const FindGraphQLTags = require('../language/javascript/FindGraphQLTags'); const RelaySourceModuleParser = require('./RelaySourceModuleParser'); +const getModuleName = require('../util/getModuleName'); import type {SourceModuleParser} from './RelaySourceModuleParser'; const JSModuleParser: SourceModuleParser = RelaySourceModuleParser( FindGraphQLTags.find, + getModuleName, ); module.exports = JSModuleParser; diff --git a/packages/relay-compiler/core/RelayFindGraphQLTags.js b/packages/relay-compiler/core/RelayFindGraphQLTags.js index 9f83282c234b0..b30c0a2cc66bd 100644 --- a/packages/relay-compiler/core/RelayFindGraphQLTags.js +++ b/packages/relay-compiler/core/RelayFindGraphQLTags.js @@ -14,7 +14,6 @@ const RelayCompilerCache = require('../util/RelayCompilerCache'); -const getModuleName = require('../util/getModuleName'); const graphql = require('graphql'); const path = require('path'); const util = require('util'); @@ -29,6 +28,7 @@ const cache = new RelayCompilerCache('RelayFindGraphQLTags', 'v1'); function memoizedFind( tagFinder: GraphQLTagFinder, + generateModuleName: (filePath: string) => string, text: string, baseDir: string, file: File, @@ -40,17 +40,24 @@ function memoizedFind( ); return cache.getOrCompute( file.hash, - find.bind(null, tagFinder, text, path.join(baseDir, file.relPath)), + find.bind( + null, + tagFinder, + generateModuleName, + text, + path.join(baseDir, file.relPath), + ), ); } function find( tagFinder: GraphQLTagFinder, + generateModuleName: (filePath: string) => string, text: string, absPath: string, ): $ReadOnlyArray { const tags = tagFinder(text, absPath); - const moduleName = getModuleName(absPath); + const moduleName = generateModuleName(absPath); tags.forEach(tag => validateTemplate(tag, moduleName, absPath)); return tags.map(tag => tag.template); } diff --git a/packages/relay-compiler/core/RelaySourceModuleParser.js b/packages/relay-compiler/core/RelaySourceModuleParser.js index 7afe73e2cf90a..596d001711109 100644 --- a/packages/relay-compiler/core/RelaySourceModuleParser.js +++ b/packages/relay-compiler/core/RelaySourceModuleParser.js @@ -46,6 +46,7 @@ const parseGraphQL = Profiler.instrument(GraphQL.parse, 'GraphQL.parse'); module.exports = ( tagFinder: GraphQLTagFinder, + generateModuleName: (filePath: string) => string, getFileFilter?: GetFileFilter, ): SourceModuleParser => { const memoizedTagFinder = memoizedFind.bind(null, tagFinder); @@ -80,18 +81,20 @@ module.exports = ( const astDefinitions = []; const sources = []; - memoizedTagFinder(text, baseDir, file).forEach(template => { - const source = new GraphQL.Source(template, file.relPath); - const ast = parseGraphQL(source); - invariant( - ast.definitions.length, - 'RelaySourceModuleParser: Expected GraphQL text to contain at least one ' + - 'definition (fragment, mutation, query, subscription), got `%s`.', - template, - ); - sources.push(source.body); - astDefinitions.push(...ast.definitions); - }); + memoizedTagFinder(generateModuleName, text, baseDir, file).forEach( + template => { + const source = new GraphQL.Source(template, file.relPath); + const ast = parseGraphQL(source); + invariant( + ast.definitions.length, + 'RelaySourceModuleParser: Expected GraphQL text to contain at least one ' + + 'definition (fragment, mutation, query, subscription), got `%s`.', + template, + ); + sources.push(source.body); + astDefinitions.push(...ast.definitions); + }, + ); return { document: { diff --git a/packages/relay-compiler/core/__tests__/RelayFindGraphQLTags-test.js b/packages/relay-compiler/core/__tests__/RelayFindGraphQLTags-test.js index c341093f186ac..8dc9e30914edc 100644 --- a/packages/relay-compiler/core/__tests__/RelayFindGraphQLTags-test.js +++ b/packages/relay-compiler/core/__tests__/RelayFindGraphQLTags-test.js @@ -14,11 +14,17 @@ 'use strict'; const FindGraphQLTags = require('../../language/javascript/FindGraphQLTags'); +const getModuleName = require('../../util/getModuleName'); const RelayFindGraphQLTags = require('../RelayFindGraphQLTags'); describe('RelayFindGraphQLTags', () => { function find(text, absPath: string = '/path/to/FindGraphQLTags.js') { - return RelayFindGraphQLTags.find(FindGraphQLTags.find, text, absPath); + return RelayFindGraphQLTags.find( + FindGraphQLTags.find, + getModuleName, + text, + absPath, + ); } describe('query parsing', () => { diff --git a/packages/relay-compiler/index.js b/packages/relay-compiler/index.js index 3960446ac4e5b..c382fda6ba421 100644 --- a/packages/relay-compiler/index.js +++ b/packages/relay-compiler/index.js @@ -61,7 +61,10 @@ const getSchemaInstance = require('./runner/getSchemaInstance'); const md5 = require('./util/md5'); const writeRelayGeneratedFile = require('./codegen/writeRelayGeneratedFile'); -const {main} = require('./bin/RelayCompilerMain'); +const { + main, + getGenerateModuleNameFunction, +} = require('./bin/RelayCompilerMain'); const {SourceControlMercurial} = require('./codegen/SourceControl'); const { getReaderSourceDefinitionName, @@ -175,6 +178,7 @@ module.exports = { transformASTSchema: ASTConvert.transformASTSchema, getReaderSourceDefinitionName, + getGenerateModuleNameFunction, writeRelayGeneratedFile,