Skip to content

Commit 34148e6

Browse files
motiz88facebook-github-bot
authored andcommitted
Use yieldy version of sourceMapString in server
Summary: Changelog: * **[Performance]:** Make server more responsive during source map serialization. We have an existing (currently unused) non-blocking implementation of `sourceMapObject` that relies on explicitly chunking the source map encoding work. Here we reuse the same underlying machinery to make `sourceMapString` non-blocking, as a cheap way of reducing long main-thread pauses during `.map` requests. NOTE: `.map` requests do have a significant I/O component today, which isn't really addressed here. The I/O is best eliminated by passing the `excludeSource=true` parameter, typically in combination with `sourcePaths=url-server` (see D56952063, D56952065). Reviewed By: robhogan Differential Revision: D56957754 fbshipit-source-id: b18fd1d801d4d4542e39425fddd8cdb5ed8d6ce1
1 parent 788ff0d commit 34148e6

File tree

4 files changed

+151
-108
lines changed

4 files changed

+151
-108
lines changed

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

Lines changed: 115 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@ import type {Module} from '../../types.flow';
1515

1616
import CountingSet from '../../../lib/CountingSet';
1717

18-
const sourceMapString = require('../sourceMapString');
18+
const {
19+
sourceMapString,
20+
sourceMapStringNonBlocking,
21+
} = require('../sourceMapString');
1922

2023
const polyfill: Module<> = {
2124
path: '/root/pre.js',
@@ -77,102 +80,119 @@ const barModule: Module<> = {
7780
],
7881
};
7982

80-
it('should serialize a very simple bundle', () => {
81-
expect(
82-
JSON.parse(
83-
sourceMapString([polyfill, fooModule, barModule], {
84-
excludeSource: false,
85-
processModuleFilter: module => true,
86-
shouldAddToIgnoreList: module => false,
87-
getSourceUrl: null,
88-
}),
89-
),
90-
).toEqual({
91-
version: 3,
92-
sources: ['/root/pre.js', '/root/foo.js', '/root/bar.js'],
93-
sourcesContent: ['source pre', 'source foo', 'source bar'],
94-
x_facebook_sources: [null, [{names: ['<global>'], mappings: 'AAA'}], null],
95-
names: [],
96-
mappings: '',
97-
});
98-
});
83+
describe.each([sourceMapString, sourceMapStringNonBlocking])(
84+
'%p',
85+
sourceMapStringImpl => {
86+
test('should serialize a very simple bundle', async () => {
87+
expect(
88+
JSON.parse(
89+
await sourceMapStringImpl([polyfill, fooModule, barModule], {
90+
excludeSource: false,
91+
processModuleFilter: module => true,
92+
shouldAddToIgnoreList: module => false,
93+
getSourceUrl: null,
94+
}),
95+
),
96+
).toEqual({
97+
version: 3,
98+
sources: ['/root/pre.js', '/root/foo.js', '/root/bar.js'],
99+
sourcesContent: ['source pre', 'source foo', 'source bar'],
100+
x_facebook_sources: [
101+
null,
102+
[{names: ['<global>'], mappings: 'AAA'}],
103+
null,
104+
],
105+
names: [],
106+
mappings: '',
107+
});
108+
});
99109

100-
it('modules should appear in their original order', () => {
101-
expect(
102-
JSON.parse(
103-
sourceMapString([polyfill, barModule, fooModule], {
104-
excludeSource: false,
105-
processModuleFilter: module => true,
106-
shouldAddToIgnoreList: module => false,
107-
getSourceUrl: null,
108-
}),
109-
),
110-
).toEqual({
111-
version: 3,
112-
sources: ['/root/pre.js', '/root/bar.js', '/root/foo.js'],
113-
sourcesContent: ['source pre', 'source bar', 'source foo'],
114-
x_facebook_sources: [null, null, [{names: ['<global>'], mappings: 'AAA'}]],
115-
names: [],
116-
mappings: '',
117-
});
118-
});
110+
test('modules should appear in their original order', async () => {
111+
expect(
112+
JSON.parse(
113+
await sourceMapStringImpl([polyfill, barModule, fooModule], {
114+
excludeSource: false,
115+
processModuleFilter: module => true,
116+
shouldAddToIgnoreList: module => false,
117+
getSourceUrl: null,
118+
}),
119+
),
120+
).toEqual({
121+
version: 3,
122+
sources: ['/root/pre.js', '/root/bar.js', '/root/foo.js'],
123+
sourcesContent: ['source pre', 'source bar', 'source foo'],
124+
x_facebook_sources: [
125+
null,
126+
null,
127+
[{names: ['<global>'], mappings: 'AAA'}],
128+
],
129+
names: [],
130+
mappings: '',
131+
});
132+
});
119133

120-
it('should not include the source of an asset', () => {
121-
const assetModule: Module<> = {
122-
path: '/root/asset.jpg',
123-
dependencies: new Map(),
124-
inverseDependencies: new CountingSet(),
125-
getSource: () => {
126-
throw new Error('should not read the source of an asset');
127-
},
128-
output: [
129-
{
130-
type: 'js/module/asset',
131-
data: {
132-
code: '__d(function() {/* code for bar */});',
133-
lineCount: 1,
134-
map: [],
134+
test('should not include the source of an asset', async () => {
135+
const assetModule: Module<> = {
136+
path: '/root/asset.jpg',
137+
dependencies: new Map(),
138+
inverseDependencies: new CountingSet(),
139+
getSource: () => {
140+
throw new Error('should not read the source of an asset');
135141
},
136-
},
137-
],
138-
};
142+
output: [
143+
{
144+
type: 'js/module/asset',
145+
data: {
146+
code: '__d(function() {/* code for bar */});',
147+
lineCount: 1,
148+
map: [],
149+
},
150+
},
151+
],
152+
};
139153

140-
expect(
141-
JSON.parse(
142-
sourceMapString([fooModule, assetModule], {
143-
excludeSource: false,
144-
processModuleFilter: module => true,
145-
shouldAddToIgnoreList: module => false,
146-
getSourceUrl: null,
147-
}),
148-
),
149-
).toEqual({
150-
version: 3,
151-
sources: ['/root/foo.js', '/root/asset.jpg'],
152-
sourcesContent: ['source foo', ''],
153-
x_facebook_sources: [[{names: ['<global>'], mappings: 'AAA'}], null],
154-
names: [],
155-
mappings: '',
156-
});
157-
});
154+
expect(
155+
JSON.parse(
156+
await sourceMapStringImpl([fooModule, assetModule], {
157+
excludeSource: false,
158+
processModuleFilter: module => true,
159+
shouldAddToIgnoreList: module => false,
160+
getSourceUrl: null,
161+
}),
162+
),
163+
).toEqual({
164+
version: 3,
165+
sources: ['/root/foo.js', '/root/asset.jpg'],
166+
sourcesContent: ['source foo', ''],
167+
x_facebook_sources: [[{names: ['<global>'], mappings: 'AAA'}], null],
168+
names: [],
169+
mappings: '',
170+
});
171+
});
158172

159-
it('should emit x_google_ignoreList based on shouldAddToIgnoreList', () => {
160-
expect(
161-
JSON.parse(
162-
sourceMapString([polyfill, fooModule, barModule], {
163-
excludeSource: false,
164-
processModuleFilter: module => true,
165-
shouldAddToIgnoreList: module => true,
166-
getSourceUrl: null,
167-
}),
168-
),
169-
).toEqual({
170-
version: 3,
171-
sources: ['/root/pre.js', '/root/foo.js', '/root/bar.js'],
172-
sourcesContent: ['source pre', 'source foo', 'source bar'],
173-
x_facebook_sources: [null, [{names: ['<global>'], mappings: 'AAA'}], null],
174-
names: [],
175-
mappings: '',
176-
x_google_ignoreList: [0, 1, 2],
177-
});
178-
});
173+
test('should emit x_google_ignoreList based on shouldAddToIgnoreList', async () => {
174+
expect(
175+
JSON.parse(
176+
await sourceMapStringImpl([polyfill, fooModule, barModule], {
177+
excludeSource: false,
178+
processModuleFilter: module => true,
179+
shouldAddToIgnoreList: module => true,
180+
getSourceUrl: null,
181+
}),
182+
),
183+
).toEqual({
184+
version: 3,
185+
sources: ['/root/pre.js', '/root/foo.js', '/root/bar.js'],
186+
sourcesContent: ['source pre', 'source foo', 'source bar'],
187+
x_facebook_sources: [
188+
null,
189+
[{names: ['<global>'], mappings: 'AAA'}],
190+
null,
191+
],
192+
names: [],
193+
mappings: '',
194+
x_google_ignoreList: [0, 1, 2],
195+
});
196+
});
197+
},
198+
);

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

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,10 @@
1414
import type {Module} from '../types.flow';
1515
import type {SourceMapGeneratorOptions} from './sourceMapGenerator';
1616

17-
const {sourceMapGenerator} = require('./sourceMapGenerator');
17+
const {
18+
sourceMapGenerator,
19+
sourceMapGeneratorNonBlocking,
20+
} = require('./sourceMapGenerator');
1821

1922
function sourceMapString(
2023
modules: $ReadOnlyArray<Module<>>,
@@ -25,4 +28,17 @@ function sourceMapString(
2528
});
2629
}
2730

28-
module.exports = sourceMapString;
31+
async function sourceMapStringNonBlocking(
32+
modules: $ReadOnlyArray<Module<>>,
33+
options: SourceMapGeneratorOptions,
34+
): Promise<string> {
35+
const generator = await sourceMapGeneratorNonBlocking(modules, options);
36+
return generator.toString(undefined, {
37+
excludeSource: options.excludeSource,
38+
});
39+
}
40+
41+
module.exports = {
42+
sourceMapString,
43+
sourceMapStringNonBlocking,
44+
};

packages/metro/src/Server.js

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,9 @@ const {
5353
getExplodedSourceMap,
5454
} = require('./DeltaBundler/Serializers/getExplodedSourceMap');
5555
const getRamBundleInfo = require('./DeltaBundler/Serializers/getRamBundleInfo');
56-
const sourceMapString = require('./DeltaBundler/Serializers/sourceMapString');
56+
const {
57+
sourceMapStringNonBlocking,
58+
} = require('./DeltaBundler/Serializers/sourceMapString');
5759
const IncrementalBundler = require('./IncrementalBundler');
5860
const ResourceNotFoundError = require('./IncrementalBundler/ResourceNotFoundError');
5961
const bundleToString = require('./lib/bundleToString');
@@ -288,7 +290,7 @@ class Server {
288290
).code;
289291
}
290292
if (!bundleMap) {
291-
bundleMap = sourceMapString(
293+
bundleMap = await sourceMapStringNonBlocking(
292294
[...prepend, ...this._getSortedModules(graph)],
293295
{
294296
excludeSource: serializerOptions.excludeSource,
@@ -1151,14 +1153,17 @@ class Server {
11511153
prepend = [];
11521154
}
11531155

1154-
return sourceMapString([...prepend, ...this._getSortedModules(graph)], {
1155-
excludeSource: serializerOptions.excludeSource,
1156-
processModuleFilter: this._config.serializer.processModuleFilter,
1157-
shouldAddToIgnoreList: (module: Module<>) =>
1158-
this._shouldAddModuleToIgnoreList(module),
1159-
getSourceUrl: (module: Module<>) =>
1160-
this._getModuleSourceUrl(module, serializerOptions.sourcePaths),
1161-
});
1156+
return await sourceMapStringNonBlocking(
1157+
[...prepend, ...this._getSortedModules(graph)],
1158+
{
1159+
excludeSource: serializerOptions.excludeSource,
1160+
processModuleFilter: this._config.serializer.processModuleFilter,
1161+
shouldAddToIgnoreList: (module: Module<>) =>
1162+
this._shouldAddModuleToIgnoreList(module),
1163+
getSourceUrl: (module: Module<>) =>
1164+
this._getModuleSourceUrl(module, serializerOptions.sourcePaths),
1165+
},
1166+
);
11621167
},
11631168
finish({mres, result}) {
11641169
mres.setHeader('Content-Type', 'application/json');

packages/metro/src/lib/getAppendScripts.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@ import type {Dependency} from '../DeltaBundler/types.flow';
1616
import CountingSet from './CountingSet';
1717

1818
const getInlineSourceMappingURL = require('../DeltaBundler/Serializers/helpers/getInlineSourceMappingURL');
19-
const sourceMapString = require('../DeltaBundler/Serializers/sourceMapString');
19+
const {
20+
sourceMapString,
21+
} = require('../DeltaBundler/Serializers/sourceMapString');
2022
const countLines = require('./countLines');
2123
const nullthrows = require('nullthrows');
2224

0 commit comments

Comments
 (0)