Skip to content

Commit

Permalink
Support custom transformer/resolver options in metro build and runB…
Browse files Browse the repository at this point in the history
…uild API

Summary:
Changelog:
* **[Feature]** Support custom transformer/resolver options in `metro build` and `runBuild` API

1. Adds the `--transform-option` and `--resolver-option` options to the `metro build` command.
2. Adds the `customTransformOptions` and `customResolverOptions` properties to the options object accepted by `Metro.runBuild`.

Previously, Metro only supported passing these options via the bundle URL and they had no counterpart in the API or CLI.

Reviewed By: huntie

Differential Revision: D43775385

fbshipit-source-id: dc87b4a170883b6efe694a72b218953b85798f92
  • Loading branch information
motiz88 authored and facebook-github-bot committed Mar 14, 2023
1 parent 5c5830b commit fcfecc9
Show file tree
Hide file tree
Showing 6 changed files with 132 additions and 1 deletion.
2 changes: 2 additions & 0 deletions docs/CLI.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ Generates a JavaScript bundle containing the specified entrypoint and its descen
| `source-map` | | Whether Metro should generate source maps | Boolean |
| `source-map-url` | | URL where the source map can be found | String |
| `legacy-bundler` | | Whether Metro should use the legacy bundler | Boolean |
| `resolver-option` | | [Custom resolver options](./Resolution.md#customresolveroptions-string-mixed) of the form `key=value` | Array |
| `transform-option` | | Custom transform options of the form `key=value` | Array |


## `serve`
Expand Down
66 changes: 66 additions & 0 deletions packages/metro/src/cli/__tests__/parseKeyValueParamArray-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/**
* Copyright (c) Meta Platforms, Inc. and 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
* @oncall react_native
*/

import parseKeyValueParamArray from '../parseKeyValueParamArray';

test('empty', () => {
expect(parseKeyValueParamArray([])).toEqual({});
});

test('result has nullish prototype', () => {
// eslint-disable-next-line no-proto
expect(parseKeyValueParamArray([]).__proto__).toBe(undefined);
});

test('single prop', () => {
expect(parseKeyValueParamArray(['foo=bar'])).toEqual({
foo: 'bar',
});
});

test('repeated prop, last one wins', () => {
expect(parseKeyValueParamArray(['foo=bar', 'foo=baz'])).toEqual({
foo: 'baz',
});
});

test('multiple props', () => {
expect(parseKeyValueParamArray(['foo=bar', 'baz=quux'])).toEqual({
foo: 'bar',
baz: 'quux',
});
});

test('"&" is not allowed', () => {
expect(() =>
parseKeyValueParamArray(['foo=bar&baz=quux']),
).toThrowErrorMatchingInlineSnapshot(
`"Parameter cannot include \\"&\\" but found: foo=bar&baz=quux"`,
);
});

test('"=" is required', () => {
expect(() =>
parseKeyValueParamArray(['foo', 'bar']),
).toThrowErrorMatchingInlineSnapshot(
`"Expected parameter to include \\"=\\" but found: foo"`,
);
});

test('multiple "=" characters', () => {
expect(parseKeyValueParamArray(['a=b=c'])).toEqual({a: 'b=c'});
});

test('performs URL decoding', () => {
expect(parseKeyValueParamArray(['a=b%20c'])).toEqual({a: 'b c'});
expect(parseKeyValueParamArray(['a=b%26c'])).toEqual({a: 'b&c'});
expect(parseKeyValueParamArray(['a%3Db=c'])).toEqual({'a=b': 'c'});
});
31 changes: 31 additions & 0 deletions packages/metro/src/cli/parseKeyValueParamArray.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/**
* Copyright (c) Meta Platforms, Inc. and 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
* @oncall react_native
*/

import querystring from 'querystring';

export default function coerceKeyValueArray(
keyValueArray: $ReadOnlyArray<string>,
): {
[key: string]: string,
__proto__: null,
} {
const result: {[key: string]: string, __proto__: null} = Object.create(null);
for (const item of keyValueArray) {
if (item.indexOf('=') === -1) {
throw new Error('Expected parameter to include "=" but found: ' + item);
}
if (item.indexOf('&') !== -1) {
throw new Error('Parameter cannot include "&" but found: ' + item);
}
Object.assign(result, querystring.parse(item));
}
return result;
}
25 changes: 24 additions & 1 deletion packages/metro/src/commands/build.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@
* @oncall react_native
*/

'use strict';
import parseKeyValueParamArray from '../cli/parseKeyValueParamArray';

import type {CustomTransformOptions} from 'metro-babel-transformer';
import type {CustomResolverOptions} from 'metro-resolver';
import type {RunBuildOptions} from '../index';
import typeof Yargs from 'yargs';
import type {ModuleObject} from 'yargs';
Expand All @@ -37,6 +39,8 @@ type Args = $ReadOnly<{
resetCache?: boolean,
sourceMap?: boolean,
sourceMapUrl?: string,
transformOption: CustomTransformOptions,
resolverOption: CustomResolverOptions,
}>;

module.exports = (): {
Expand Down Expand Up @@ -69,6 +73,23 @@ module.exports = (): {

yargs.option('config', {alias: 'c', type: 'string'});

yargs.option('transform-option', {
type: 'string',
array: true,
alias: 'transformer-option',
coerce: (parseKeyValueParamArray: $FlowFixMe),
describe:
'Custom transform options of the form key=value. URL-encoded. May be specified multiple times.',
});

yargs.option('resolver-option', {
type: 'string',
array: true,
coerce: (parseKeyValueParamArray: $FlowFixMe),
describe:
'Custom resolver options of the form key=value. URL-encoded. May be specified multiple times.',
});

// Deprecated
yargs.option('reset-cache', {type: 'boolean'});
},
Expand All @@ -83,6 +104,8 @@ module.exports = (): {
platform: argv.platform,
sourceMap: argv.sourceMap,
sourceMapUrl: argv.sourceMapUrl,
customResolverOptions: argv.resolverOption,
customTransformOptions: argv.transformOption,
};

// Inline require() to avoid circular dependency with ../index
Expand Down
7 changes: 7 additions & 0 deletions packages/metro/src/index.flow.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

'use strict';

import type {CustomResolverOptions} from 'metro-resolver';
import type {ReadOnlyGraph} from './DeltaBundler';
import type {ServerOptions} from './Server';
import type {OutputOptions, RequestOptions} from './shared/types.flow.js';
Expand Down Expand Up @@ -112,6 +113,8 @@ export type RunBuildOptions = {
platform?: string,
sourceMap?: boolean,
sourceMapUrl?: string,
customResolverOptions?: CustomResolverOptions,
customTransformOptions?: CustomTransformOptions,
};

type BuildCommandOptions = {} | null;
Expand Down Expand Up @@ -347,6 +350,8 @@ exports.runServer = async (
exports.runBuild = async (
config: ConfigT,
{
customResolverOptions,
customTransformOptions,
dev = false,
entry,
onBegin,
Expand Down Expand Up @@ -378,6 +383,8 @@ exports.runBuild = async (
sourceMapUrl: sourceMap === false ? undefined : sourceMapUrl,
createModuleIdFactory: config.serializer.createModuleIdFactory,
onProgress,
customResolverOptions,
customTransformOptions,
};

if (onBegin) {
Expand Down
2 changes: 2 additions & 0 deletions packages/metro/src/shared/types.flow.js
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,8 @@ export type RequestOptions = {
platform: string,
createModuleIdFactory?: () => (path: string) => number,
onProgress?: (transformedFileCount: number, totalFileCount: number) => void,
+customResolverOptions?: CustomResolverOptions,
+customTransformOptions?: CustomTransformOptions,
};

export type {MinifierOptions};

0 comments on commit fcfecc9

Please sign in to comment.