Skip to content

Commit a8fabc8

Browse files
Alexandre Kirszenbergfacebook-github-bot
Alexandre Kirszenberg
authored andcommitted
Extract options parsing from Server logic
Reviewed By: rafeca Differential Revision: D10297284 fbshipit-source-id: 71caa0fcaeb3ac5a7b04468663a47f475dbea3b5
1 parent 612d4d7 commit a8fabc8

File tree

3 files changed

+269
-98
lines changed

3 files changed

+269
-98
lines changed

packages/metro/src/Server.js

+10-98
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,7 @@ const debug = require('debug')('Metro:Server');
2828
const formatBundlingError = require('./lib/formatBundlingError');
2929
const getPrependedScripts = require('./lib/getPrependedScripts');
3030
const mime = require('mime-types');
31-
const nullthrows = require('nullthrows');
32-
const parseCustomTransformOptions = require('./lib/parseCustomTransformOptions');
31+
const parseOptionsFromUrl = require('./lib/parseOptionsFromUrl');
3332
const parsePlatformFilePath = require('./node-haste/lib/parsePlatformFilePath');
3433
const path = require('path');
3534
const symbolicate = require('./Server/symbolicate/symbolicate');
@@ -49,6 +48,7 @@ import type {MetroSourceMap} from 'metro-source-map';
4948
import type {Symbolicate} from './Server/symbolicate/symbolicate';
5049
import type {AssetData} from './Assets';
5150
import type {TransformInputOptions} from './lib/transformHelpers';
51+
import type {DeltaOptions} from './lib/parseOptionsFromUrl';
5252

5353
const {
5454
Logger,
@@ -64,10 +64,6 @@ type GraphInfo = {|
6464

6565
export type OutputGraph = Graph<>;
6666

67-
type DeltaOptions = BundleOptions & {
68-
deltaBundleId: ?string,
69-
};
70-
7167
function debounceAndBatch(fn, delay) {
7268
let timeout;
7369
return () => {
@@ -567,12 +563,14 @@ class Server {
567563
req: IncomingMessage,
568564
mres: MultipartResponse,
569565
): {options: DeltaOptions, buildID: string} {
570-
const options = this._getOptionsFromUrl(
566+
const options = parseOptionsFromUrl(
571567
url.format({
572568
...url.parse(req.url),
573569
protocol: 'http',
574570
host: req.headers.host,
575571
}),
572+
this._config.projectRoot,
573+
new Set(this._config.resolver.platforms),
576574
);
577575

578576
const buildID = this.getNewBuildID();
@@ -931,7 +929,11 @@ class Server {
931929
}
932930

933931
async _sourceMapForURL(reqUrl: string): Promise<MetroSourceMap> {
934-
const options: DeltaOptions = this._getOptionsFromUrl(reqUrl);
932+
const options: DeltaOptions = parseOptionsFromUrl(
933+
reqUrl,
934+
this._config.projectRoot,
935+
new Set(this._config.resolver.platforms),
936+
);
935937

936938
const {graph, prepend} = await this._getGraphInfo(options, {
937939
rebuild: false,
@@ -967,96 +969,6 @@ class Server {
967969
});
968970
}
969971

970-
_getOptionsFromUrl(reqUrl: string): DeltaOptions {
971-
// `true` to parse the query param as an object.
972-
const urlObj = nullthrows(url.parse(reqUrl, true));
973-
const urlQuery = nullthrows(urlObj.query);
974-
975-
const pathname = urlObj.pathname ? decodeURIComponent(urlObj.pathname) : '';
976-
977-
let isMap = false;
978-
979-
// Backwards compatibility. Options used to be as added as '.' to the
980-
// entry module name. We can safely remove these options.
981-
const entryFile =
982-
pathname
983-
.replace(/^\//, '')
984-
.split('.')
985-
.filter(part => {
986-
if (part === 'map') {
987-
isMap = true;
988-
return false;
989-
}
990-
if (
991-
part === 'includeRequire' ||
992-
part === 'runModule' ||
993-
part === 'bundle' ||
994-
part === 'delta' ||
995-
part === 'assets'
996-
) {
997-
return false;
998-
}
999-
return true;
1000-
})
1001-
.join('.') + '.js';
1002-
1003-
const absoluteEntryFile = path.resolve(this._config.projectRoot, entryFile);
1004-
1005-
// try to get the platform from the url
1006-
const platform =
1007-
urlQuery.platform ||
1008-
parsePlatformFilePath(pathname, this._platforms).platform;
1009-
1010-
const deltaBundleId = urlQuery.deltaBundleId;
1011-
1012-
const dev = this._getBoolOptionFromQuery(urlQuery, 'dev', true);
1013-
const minify = this._getBoolOptionFromQuery(urlQuery, 'minify', false);
1014-
const excludeSource = this._getBoolOptionFromQuery(
1015-
urlQuery,
1016-
'excludeSource',
1017-
false,
1018-
);
1019-
const includeSource = this._getBoolOptionFromQuery(
1020-
urlQuery,
1021-
'inlineSourceMap',
1022-
false,
1023-
);
1024-
1025-
const customTransformOptions = parseCustomTransformOptions(urlObj);
1026-
1027-
return {
1028-
sourceMapUrl: url.format({
1029-
...urlObj,
1030-
pathname: pathname.replace(/\.(bundle|delta)$/, '.map'),
1031-
}),
1032-
bundleType: isMap ? 'map' : deltaBundleId ? 'delta' : 'bundle',
1033-
customTransformOptions,
1034-
entryFile: absoluteEntryFile,
1035-
deltaBundleId,
1036-
dev,
1037-
minify,
1038-
excludeSource,
1039-
hot: true,
1040-
runModule: this._getBoolOptionFromQuery(urlObj.query, 'runModule', true),
1041-
inlineSourceMap: includeSource,
1042-
platform,
1043-
onProgress: null,
1044-
};
1045-
}
1046-
1047-
_getBoolOptionFromQuery(
1048-
query: ?{},
1049-
opt: string,
1050-
defaultVal: boolean,
1051-
): boolean {
1052-
/* $FlowFixMe: `query` could be empty when it comes from an invalid URL */
1053-
if (query[opt] == null) {
1054-
return defaultVal;
1055-
}
1056-
1057-
return query[opt] === 'true' || query[opt] === '1';
1058-
}
1059-
1060972
getGraphs(): Map<string, Promise<GraphInfo>> {
1061973
return this._graphs;
1062974
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @emails oncall+javascript_foundation
8+
* @format
9+
* @flow strict-local
10+
*/
11+
12+
'use strict';
13+
14+
const parseOptionsFromUrl = require('../parseOptionsFromUrl');
15+
16+
jest.mock('../parseCustomTransformOptions', () => () => ({}));
17+
18+
describe('parseOptionsFromUrl', () => {
19+
it.each([['map'], ['delta'], ['bundle']])('detects %s requests', type => {
20+
expect(
21+
parseOptionsFromUrl(
22+
`http://localhost/my/bundle.${type}`,
23+
'/',
24+
new Set([]),
25+
),
26+
).toMatchObject({bundleType: type});
27+
});
28+
29+
it('resolves the entry file from the project root', () => {
30+
expect(
31+
parseOptionsFromUrl(
32+
'http://localhost/my/bundle.bundle.includeRequire.runModule.assets',
33+
'/static/bundles/',
34+
new Set([]),
35+
),
36+
).toMatchObject({entryFile: '/static/bundles/my/bundle.js'});
37+
});
38+
39+
it('removes extraneous options from the pathname', () => {
40+
expect(
41+
parseOptionsFromUrl(
42+
'http://localhost/my/bundle.bundle.includeRequire.runModule.assets',
43+
'/',
44+
new Set([]),
45+
),
46+
).toMatchObject({entryFile: '/my/bundle.js'});
47+
});
48+
49+
it('retrieves the platform from the query parameters', () => {
50+
expect(
51+
parseOptionsFromUrl(
52+
'http://localhost/my/bundle.bundle?platform=ios',
53+
'/',
54+
new Set([]),
55+
),
56+
).toMatchObject({platform: 'ios'});
57+
});
58+
59+
it('retrieves the platform from the pathname', () => {
60+
expect(
61+
parseOptionsFromUrl(
62+
'http://localhost/my/bundle.test.bundle',
63+
'/',
64+
new Set(['test']),
65+
),
66+
).toMatchObject({platform: 'test'});
67+
});
68+
69+
it('retrieves the delta bundle id from the url', () => {
70+
expect(
71+
parseOptionsFromUrl(
72+
'http://localhost/my/bundle.delta?deltaBundleId=XXX',
73+
'/',
74+
new Set([]),
75+
),
76+
).toMatchObject({deltaBundleId: 'XXX'});
77+
});
78+
79+
it('infers the source map url from the pathname', () => {
80+
expect(
81+
parseOptionsFromUrl(
82+
'http://localhost/my/bundle.bundle',
83+
'/',
84+
new Set([]),
85+
),
86+
).toMatchObject({sourceMapUrl: 'http://localhost/my/bundle.map'});
87+
88+
expect(
89+
parseOptionsFromUrl('http://localhost/my/bundle.delta', '/', new Set([])),
90+
).toMatchObject({sourceMapUrl: 'http://localhost/my/bundle.map'});
91+
});
92+
93+
it('always sets the `hot` option to `true`', () => {
94+
expect(
95+
parseOptionsFromUrl(
96+
'http://localhost/my/bundle.bundle',
97+
'/',
98+
new Set([]),
99+
),
100+
).toMatchObject({hot: true});
101+
});
102+
103+
describe.each([
104+
['dev', true],
105+
['minify', false],
106+
['excludeSource', false],
107+
['inlineSourceMap', false],
108+
['runModule', true],
109+
])('boolean option `%s`', (optionName, defaultValue) => {
110+
it(`defaults to \`${String(defaultValue)}\``, () => {
111+
expect(
112+
parseOptionsFromUrl(
113+
'http://localhost/my/bundle.bundle',
114+
'/',
115+
new Set([]),
116+
),
117+
).toMatchObject({[optionName]: defaultValue});
118+
});
119+
120+
it('is retrieved from the url', () => {
121+
expect(
122+
parseOptionsFromUrl(
123+
`http://localhost/my/bundle.bundle?${optionName}=true`,
124+
'/',
125+
new Set([]),
126+
),
127+
).toMatchObject({[optionName]: true});
128+
129+
expect(
130+
parseOptionsFromUrl(
131+
`http://localhost/my/bundle.bundle?${optionName}=false`,
132+
'/',
133+
new Set([]),
134+
),
135+
).toMatchObject({[optionName]: false});
136+
});
137+
});
138+
});

0 commit comments

Comments
 (0)