Skip to content

Commit fb2742c

Browse files
vzaidmanfacebook-github-bot
authored andcommitted
replaced deprecated url.parse with URL in parseOptionsFromUrl
Summary: Gets rid of the deprecated [`url.parse`](https://nodejs.org/api/url.html#urlparseurlstring-parsequerystring-slashesdenotehost) in favor of `URL` in `parseOptionsFromUrl`. Changelog: [Fixed] Replaced the deprecated `url.parse` with `URL` and correctly encode and decode source urls and source map urls Differential Revision: D78890065
1 parent 7d0de18 commit fb2742c

12 files changed

Lines changed: 448 additions & 158 deletions

packages/metro/src/DeltaBundler/Serializers/__tests__/baseJSBundle-test.js

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,23 @@ const barModule: Module<> = {
7979
getSource: () => Buffer.from('bar-source'),
8080
};
8181

82+
const nonAsciiModule: Module<> = {
83+
path: '/root/%30.бундл.Øಚ😁AA',
84+
dependencies: new Map(),
85+
inverseDependencies: new CountingSet(),
86+
output: [
87+
{
88+
type: 'js/module',
89+
data: {
90+
code: '__d(function() {/* code for ascii file with non ascii characters: %30.бундл.Øಚ😁AA */});',
91+
map: [],
92+
lineCount: 1,
93+
},
94+
},
95+
],
96+
getSource: () => Buffer.from('bar-source'),
97+
};
98+
8299
const getRunModuleStatement = (moduleId: number | string) =>
83100
`require(${JSON.stringify(moduleId)});`;
84101

@@ -144,6 +161,52 @@ test('should generate a very simple bundle', () => {
144161
`);
145162
});
146163

164+
test('should generate a bundle with correct ascii characters parsing', () => {
165+
expect(
166+
baseJSBundle(
167+
'/root/',
168+
[polyfill],
169+
{
170+
dependencies: new Map([['/root/%30.бундл.Øಚ😁AA', nonAsciiModule]]),
171+
entryPoints: new Set(['/root/%30.бундл.Øಚ😁AA']),
172+
transformOptions,
173+
},
174+
{
175+
asyncRequireModulePath: '',
176+
// $FlowFixMe[incompatible-call] createModuleId assumes numeric IDs - is this too strict?
177+
createModuleId: filePath => path.basename(filePath),
178+
dev: true,
179+
getRunModuleStatement,
180+
includeAsyncPaths: false,
181+
inlineSourceMap: false,
182+
modulesOnly: false,
183+
processModuleFilter: () => true,
184+
projectRoot: '/root',
185+
runBeforeMainModule: [],
186+
runModule: true,
187+
serverRoot: '/root',
188+
shouldAddToIgnoreList: () => false,
189+
// expecting to receive these as already encoded URIs
190+
sourceMapUrl: encodeURI('http://localhost/bundle.%30.бундл.Øಚ😁AA.map'),
191+
sourceUrl: encodeURI('http://localhost/bundle.%30.бундл.Øಚ😁AA.js'),
192+
getSourceUrl: null,
193+
},
194+
),
195+
).toMatchInlineSnapshot(`
196+
Object {
197+
"modules": Array [
198+
Array [
199+
"%30.бундл.Øಚ😁AA",
200+
"__d(function() {/* code for ascii file with non ascii characters: %30.бундл.Øಚ😁AA */},\\"%30.бундл.Øಚ😁AA\\",[],\\"%30.бундл.Øಚ😁AA\\");",
201+
],
202+
],
203+
"post": "//# sourceMappingURL=http://localhost/bundle.%2530.%D0%B1%D1%83%D0%BD%D0%B4%D0%BB.%C3%98%E0%B2%9A%F0%9F%98%81AA.map
204+
//# sourceURL=http://localhost/bundle.%2530.%D0%B1%D1%83%D0%BD%D0%B4%D0%BB.%C3%98%E0%B2%9A%F0%9F%98%81AA.js",
205+
"pre": "__d(function() {/* code for polyfill */});",
206+
}
207+
`);
208+
});
209+
147210
test('should add runBeforeMainModule statements if found in the graph', () => {
148211
expect(
149212
baseJSBundle(
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and 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+
* @flow strict-local
8+
* @format
9+
* @oncall react_native
10+
*/
11+
12+
'use strict';
13+
14+
import type {Module, ReadOnlyGraph, TransformInputOptions} from '../../types';
15+
16+
import CountingSet from '../../../lib/CountingSet';
17+
18+
const hmrJSBundle = require('../hmrJSBundle');
19+
20+
const fooModule: Module<> = {
21+
path: '/root/foo',
22+
dependencies: new Map([
23+
[
24+
'./bar',
25+
{
26+
absolutePath: '/root/bar',
27+
data: {
28+
data: {asyncType: null, isESMImport: false, locs: [], key: './bar'},
29+
name: './bar',
30+
},
31+
},
32+
],
33+
]),
34+
inverseDependencies: new CountingSet(),
35+
output: [
36+
{
37+
type: 'js/module',
38+
data: {
39+
code: '__d(function() {/* code for foo */});',
40+
map: [],
41+
lineCount: 1,
42+
},
43+
},
44+
],
45+
getSource: () => Buffer.from('foo-source'),
46+
};
47+
48+
const barModule: Module<> = {
49+
path: '/root/bar',
50+
dependencies: new Map(),
51+
inverseDependencies: new CountingSet(['/root/foo']),
52+
output: [
53+
{
54+
type: 'js/module',
55+
data: {
56+
code: '__d(function() {/* code for bar */});',
57+
map: [],
58+
lineCount: 1,
59+
},
60+
},
61+
],
62+
getSource: () => Buffer.from('bar-source'),
63+
};
64+
65+
const nonAsciiModule: Module<> = {
66+
path: '/root/%30.бундл.Øಚ😁AA',
67+
dependencies: new Map(),
68+
inverseDependencies: new CountingSet(),
69+
output: [
70+
{
71+
type: 'js/module',
72+
data: {
73+
code: '__d(function() {/* code for ascii file with non ascii characters: %30.бундл.Øಚ😁AA */});',
74+
map: [],
75+
lineCount: 1,
76+
},
77+
},
78+
],
79+
getSource: () => Buffer.from('bar-source'),
80+
};
81+
82+
const transformOptions: TransformInputOptions = {
83+
customTransformOptions: {},
84+
dev: true,
85+
hot: true,
86+
minify: true,
87+
platform: 'web',
88+
type: 'module',
89+
unstable_transformProfile: 'default',
90+
};
91+
92+
const graph: ReadOnlyGraph<> = {
93+
entryPoints: new Set(['root/foo']),
94+
dependencies: new Map([
95+
['root/foo', fooModule],
96+
['root/bar', barModule],
97+
]),
98+
transformOptions,
99+
};
100+
101+
const options = {
102+
clientUrl: new URL('http://localhost/root/foo/bundle.js'),
103+
createModuleId: (s: string) =>
104+
s.includes('foo') ? (s.includes('bar') ? 2 : 1) : 0,
105+
includeAsyncPaths: false,
106+
projectRoot: '/root/bar',
107+
serverRoot: '/root/bar',
108+
};
109+
110+
test('should generate a simple hot reload bundle from a change', () => {
111+
expect(
112+
hmrJSBundle(
113+
{
114+
added: new Map([['root/foo', fooModule]]),
115+
modified: new Map([['root/bar', barModule]]),
116+
deleted: new Set(),
117+
reset: false,
118+
},
119+
graph,
120+
options,
121+
),
122+
).toMatchInlineSnapshot(`
123+
Object {
124+
"added": Array [
125+
Object {
126+
"module": Array [
127+
1,
128+
"__d(function() {/* code for foo */},1,[0],\\"../foo\\",{});
129+
//# sourceMappingURL=http://localhost/foo.map
130+
//# sourceURL=http://localhost/foo.bundle
131+
",
132+
],
133+
"sourceMappingURL": "http://localhost/foo.map",
134+
"sourceURL": "http://localhost/foo.bundle",
135+
},
136+
],
137+
"deleted": Array [],
138+
"modified": Array [
139+
Object {
140+
"module": Array [
141+
0,
142+
"__d(function() {/* code for bar */},0,[],\\"\\",{});
143+
//# sourceMappingURL=http://localhost/bar.map
144+
//# sourceURL=http://localhost/bar.bundle
145+
",
146+
],
147+
"sourceMappingURL": "http://localhost/bar.map",
148+
"sourceURL": "http://localhost/bar.bundle",
149+
},
150+
],
151+
}
152+
`);
153+
});
154+
155+
test('should turn non ascii filesystem characters into proper encoded urls for source url and source map url', () => {
156+
expect(
157+
hmrJSBundle(
158+
{
159+
added: new Map(),
160+
modified: new Map([['root/%30.бундл.Øಚ😁AA', nonAsciiModule]]),
161+
deleted: new Set(),
162+
reset: false,
163+
},
164+
graph,
165+
options,
166+
),
167+
).toMatchInlineSnapshot(`
168+
Object {
169+
"added": Array [],
170+
"deleted": Array [],
171+
"modified": Array [
172+
Object {
173+
"module": Array [
174+
0,
175+
"__d(function() {/* code for ascii file with non ascii characters: %30.бундл.Øಚ😁AA */},0,[],\\"../%30.бундл.Øಚ😁AA\\",{});
176+
//# sourceMappingURL=http://localhost/%2530.%D0%B1%D1%83%D0%BD%D0%B4%D0%BB.map
177+
//# sourceURL=http://localhost/%2530.%D0%B1%D1%83%D0%BD%D0%B4%D0%BB.bundle
178+
",
179+
],
180+
"sourceMappingURL": "http://localhost/%2530.%D0%B1%D1%83%D0%BD%D0%B4%D0%BB.map",
181+
"sourceURL": "http://localhost/%2530.%D0%B1%D1%83%D0%BD%D0%B4%D0%BB.bundle",
182+
},
183+
],
184+
}
185+
`);
186+
});

packages/metro/src/DeltaBundler/Serializers/hmrJSBundle.js

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,16 @@
1111

1212
'use strict';
1313

14-
import type {EntryPointURL} from '../../HmrServer';
1514
import type {DeltaResult, Module, ReadOnlyGraph} from '../types';
1615
import type {HmrModule} from 'metro-runtime/src/modules/types';
1716

1817
const {isJsModule, wrapModule} = require('./helpers/js');
1918
const jscSafeUrl = require('jsc-safe-url');
2019
const {addParamsToDefineCall} = require('metro-transform-plugins');
2120
const path = require('path');
22-
const url = require('url');
2321

2422
type Options = $ReadOnly<{
25-
clientUrl: EntryPointURL,
23+
clientUrl: URL,
2624
createModuleId: string => number,
2725
includeAsyncPaths: boolean,
2826
projectRoot: string,
@@ -39,28 +37,30 @@ function generateModules(
3937

4038
for (const module of sourceModules) {
4139
if (isJsModule(module)) {
42-
// Construct a bundle URL for this specific module only
43-
const getURL = (extension: 'bundle' | 'map') => {
44-
const moduleUrl = url.parse(url.format(options.clientUrl), true);
45-
// the legacy url object is parsed with both "search" and "query" fields.
46-
// for the "query" field to be used when formatting the object bach to string, the "search" field must be empty.
47-
// https://nodejs.org/api/url.html#urlformaturlobject:~:text=If%20the%20urlObject.search%20property%20is%20undefined
48-
moduleUrl.search = '';
49-
moduleUrl.pathname = path.relative(
50-
options.serverRoot ?? options.projectRoot,
51-
path.join(
52-
path.dirname(module.path),
53-
path.basename(module.path, path.extname(module.path)) +
54-
'.' +
55-
extension,
40+
const getPathname = (extension: 'bundle' | 'map') => {
41+
// encoding a file path as a URL path so it could be dencoded back to a file path upon receiving
42+
return encodeURI(
43+
path.relative(
44+
options.serverRoot ?? options.projectRoot,
45+
path.join(
46+
path.dirname(module.path),
47+
path.basename(module.path, path.extname(module.path)) +
48+
'.' +
49+
extension,
50+
),
5651
),
5752
);
58-
delete moduleUrl.query.excludeSource;
59-
return url.format(moduleUrl);
6053
};
6154

62-
const sourceMappingURL = getURL('map');
63-
const sourceURL = jscSafeUrl.toJscSafeUrl(getURL('bundle'));
55+
const clientUrl = new URL(options.clientUrl);
56+
clientUrl.searchParams.delete('excludeSource');
57+
58+
clientUrl.pathname = getPathname('map');
59+
const sourceMappingURL = clientUrl.toString();
60+
61+
clientUrl.pathname = getPathname('bundle');
62+
const sourceURL = jscSafeUrl.toJscSafeUrl(clientUrl.toString());
63+
6464
const code =
6565
prepareModule(module, graph, options) +
6666
`\n//# sourceMappingURL=${sourceMappingURL}\n` +
@@ -84,7 +84,7 @@ function prepareModule(
8484
): string {
8585
const code = wrapModule(module, {
8686
...options,
87-
sourceUrl: url.format(options.clientUrl),
87+
sourceUrl: options.clientUrl.toString(),
8888
dev: true,
8989
});
9090

0 commit comments

Comments
 (0)