Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/metro-config/src/defaults/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ const getDefaultValues = (projectRoot: ?string): ConfigT => ({
customSerializer: null,
isThirdPartyModule: module =>
/(?:^|[/\\])node_modules[/\\]/.test(module.path),
unstable_inlineDependencyMap: false,
},

server: {
Expand Down
1 change: 1 addition & 0 deletions packages/metro-config/src/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ type SerializerConfigT = {
polyfillModuleNames: ReadonlyArray<string>,
processModuleFilter: (modules: Module<>) => boolean,
isThirdPartyModule: (module: Readonly<{path: string, ...}>) => boolean,
unstable_inlineDependencyMap: boolean,
};

type TransformerConfigT = {
Expand Down
3 changes: 2 additions & 1 deletion packages/metro-config/types/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
*
* @noformat
* @oncall react_native
* @generated SignedSource<<3f0c3f9093e9a6eadd3a8885218f8f45>>
* @generated SignedSource<<08960eb44a59d3d45155da2e2f9173b9>>
*
* This file was translated from Flow by scripts/generateTypeScriptDefinitions.js
* Original file: packages/metro-config/src/types.js
Expand Down Expand Up @@ -111,6 +111,7 @@ type SerializerConfigT = {
polyfillModuleNames: ReadonlyArray<string>;
processModuleFilter: (modules: Module) => boolean;
isThirdPartyModule: (module: Readonly<{path: string}>) => boolean;
unstable_inlineDependencyMap: boolean;
};
type TransformerConfigT = Omit<JsTransformerConfig, 'getTransformOptions' | 'transformVariants' | 'publicPath' | 'unstable_workerThreads'> & {
getTransformOptions: GetTransformOptions;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -549,3 +549,63 @@ test('does not add polyfills when `modulesOnly` is used', () => {
}
`);
});

test('inlines module ids when unstable_inlineDependencyMap is set', () => {
const NAME = 'DEP_MAP_RESERVED';
const ref = `${NAME}[0]`;
const fooWithRefs: Module<> = {
...fooModule,
output: [
{
type: 'js/module',
data: {
code: `__d(function(g,r,i,a,m,e,${NAME}){r(${ref})});`,
map: [],
lineCount: 1,
},
},
],
};

const bundle = baseJSBundle(
'/root/foo',
[polyfill],
{
dependencies: new Map([
['/root/foo', fooWithRefs],
['/root/bar', barModule],
]),
entryPoints: new Set(['/root/foo']),
transformOptions,
},
{
asyncRequireModulePath: '',
createModuleId: createModuleIdFactory(),
dev: false,
getRunModuleStatement,
globalPrefix: '',
includeAsyncPaths: false,
inlineSourceMap: false,
modulesOnly: false,
processModuleFilter: () => true,
projectRoot: '/root',
runBeforeMainModule: [],
runModule: true,
serverRoot: '/root',
shouldAddToIgnoreList: () => false,
sourceMapUrl: null,
sourceUrl: null,
getSourceUrl: null,
dependencyMapReservedName: NAME,
unstable_inlineDependencyMap: true,
},
);

// baseJSBundle pre-assigns sequential ids in graph order: foo=0, bar=1.
// foo's DEP_MAP_RESERVED[0] resolves to its first dependency (bar => 1),
// inlined and right-padded; the dependency-map array is dropped.
expect(bundle.modules).toEqual([
[0, `__d(function(g,r,i,a,m,e,${NAME}){r(${'1'.padEnd(ref.length)})},0);`],
[1, '__d(function() {/* code for bar */},1);'],
]);
});
2 changes: 2 additions & 0 deletions packages/metro/src/DeltaBundler/Serializers/baseJSBundle.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ export default function baseJSBundle(
projectRoot: options.projectRoot,
serverRoot: options.serverRoot,
sourceUrl: options.sourceUrl,
dependencyMapReservedName: options.dependencyMapReservedName,
unstable_inlineDependencyMap: options.unstable_inlineDependencyMap,
};

// Do not prepend polyfills or the require runtime when only modules are requested
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
import type {Dependency} from '../../../types';

import CountingSet from '../../../../lib/CountingSet';
import {wrapModule} from '../js';
import {inlineModuleIdReferences, wrapModule} from '../js';
import {wrap as raw} from 'jest-snapshot-serializer-raw';
import createModuleIdFactory from 'metro-config/private/defaults/createModuleIdFactory';
import nullthrows from 'nullthrows';
Expand Down Expand Up @@ -199,3 +199,146 @@ describe('wrapModule()', () => {
);
});
});

describe('wrapModule() with inlined module ids', () => {
const NAME = 'DEPENDENCY_MAP';
const ref = (i: number) => `${NAME}[${i}]`;
const baseInlineOptions = {
dev: false,
includeAsyncPaths: false,
projectRoot: '/root',
serverRoot: '/root',
sourceUrl: null,
dependencyMapReservedName: NAME,
unstable_inlineDependencyMap: true,
};

beforeEach(() => {
// foo=0 (self), bar=1, baz=2 with a fresh sequential id factory.
myModule.output[0].data.code = `__d(function(g,r,i,a,m,e,${NAME}){r(${ref(
0,
)});r(${ref(1)})});`;
});

test('inlines sync ids and drops the dependency-map array', () => {
expect(
wrapModule(myModule, {
...baseInlineOptions,
createModuleId: createModuleIdFactory(),
}),
).toBe(
`__d(function(g,r,i,a,m,e,${NAME}){r(${'1'.padEnd(
ref(0).length,
)});r(${'2'.padEnd(ref(1).length)})},0);`,
);
});

test('keeps a placeholder slot before the verbose name in dev', () => {
expect(
wrapModule(myModule, {
...baseInlineOptions,
dev: true,
createModuleId: createModuleIdFactory(),
}),
).toBe(
`__d(function(g,r,i,a,m,e,${NAME}){r(${'1'.padEnd(
ref(0).length,
)});r(${'2'.padEnd(ref(1).length)})},0,null,"foo.js");`,
);
});

test('passes a paths object (not the id array) for async dependencies', () => {
const dep = nullthrows(myModule.dependencies.get('bar'));
myModule.dependencies.set('bar', {
...dep,
data: {...dep.data, data: {...dep.data.data, asyncType: 'async'}},
});
expect(
wrapModule(myModule, {
...baseInlineOptions,
includeAsyncPaths: true,
sourceUrl: 'http://localhost/Main.bundle?param1=true',
createModuleId: createModuleIdFactory(),
}),
).toBe(
`__d(function(g,r,i,a,m,e,${NAME}){r(${'1'.padEnd(
ref(0).length,
)});r(${'2'.padEnd(ref(1).length)})},0,` +
`{"paths":{"1":"/../bar.bundle?param1=true&modulesOnly=true&runModule=false"}});`,
);
});

test('does not inline when the flag is off, even with a reserved name', () => {
expect(
wrapModule(myModule, {
...baseInlineOptions,
unstable_inlineDependencyMap: false,
createModuleId: createModuleIdFactory(),
}),
).toBe(
`__d(function(g,r,i,a,m,e,${NAME}){r(${ref(0)});r(${ref(1)})},0,[1,2]);`,
);
});
});

describe('inlineModuleIdReferences()', () => {
const NAME = 'DEP_MAP_RESERVED_NAME';
const ref = (i: number) => `${NAME}[${i}]`;

test('returns code unchanged when there are no dependencies', () => {
const code = ref(0);
expect(inlineModuleIdReferences(code, NAME, [])).toBe(code);
});

test('replaces dependency-map references with resolved ids', () => {
const code = `require(${ref(0)}); require(${ref(1)});`;
const inlined = inlineModuleIdReferences(code, NAME, [7, 42]);
expect(inlined).toBe(
`require(${'7'.padEnd(ref(0).length)}); ` +
`require(${'42'.padEnd(ref(1).length)});`,
);
expect(inlined.length).toBe(code.length);
});

test('right-pads ids to preserve byte offsets / source-map columns', () => {
const code = `x=${ref(0)};y=1;`;
const inlined = inlineModuleIdReferences(code, NAME, [3]);
expect(inlined.length).toBe(code.length);
expect(inlined).toBe(`x=${'3'.padEnd(ref(0).length)};y=1;`);
// Everything after the reference keeps its original column.
expect(inlined.indexOf('y=1;')).toBe(code.indexOf('y=1;'));
});

test('tolerates embedded tabs and spaces inside the reference', () => {
const code = `${NAME}\t[ 1 ]`;
const inlined = inlineModuleIdReferences(code, NAME, [0, 5]);
expect(inlined).toBe('5'.padEnd(code.length));
expect(inlined.length).toBe(code.length);
});

test('throws when a resolved id is wider than the available space', () => {
// Short reserved name so the resolved id cannot fit in the reference width.
expect(() => inlineModuleIdReferences('D[0]', 'D', [12345])).toThrow(
"Module ID doesn't fit in available space; add 1 more characters to " +
"'dependencyMapReservedName'.",
);
});

test('throws when the reserved name is absent but deps exist', () => {
expect(() =>
inlineModuleIdReferences('require(someOtherName[0]);', NAME, [1]),
).toThrow(
'Module has dependencies but does not use the preconfigured dependency ' +
"map name 'DEP_MAP_RESERVED_NAME'",
);
});

test('ignores a missing reserved name when told to', () => {
const code = 'require(someOtherName[0]);';
expect(
inlineModuleIdReferences(code, NAME, [1], {
ignoreMissingDependencyMapReference: true,
}),
).toBe(code);
});
});
Loading
Loading