Skip to content

Commit 8c8dbaa

Browse files
Merge pull request #986 from DataDog/marcosaia/RUM-11727/support-metro-83
[RUM-11727] [METRO PLUGIN] Support Metro 0.83+ imports
2 parents 7668dfc + 99a91f2 commit 8c8dbaa

File tree

8 files changed

+565
-207
lines changed

8 files changed

+565
-207
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@
6161
"genversion": "3.0.2",
6262
"jest": "^29.7.0",
6363
"lerna": "8.1.6",
64-
"metro": "^0.82.0",
64+
"metro": "^0.83.1",
6565
"pod-install": "0.1.14",
6666
"prettier": "2.2.0",
6767
"react": "18.3.1",

packages/core/src/metro/__tests__/__utils__/serializerUtils.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,16 @@
44
* Copyright 2016-Present Datadog, Inc.
55
*/
66

7-
// eslint-disable-next-line import/no-extraneous-dependencies
8-
import countLines from 'metro/src/lib/countLines';
9-
107
import type {
118
MetroSerializer,
129
MixedOutput,
1310
Module,
1411
MetroVirtualModuleOutput
1512
} from '../../plugin/types/metroTypes';
16-
import { createCountingSet } from '../../plugin/utils';
13+
import {
14+
getCreateCountingSetFunction,
15+
getCountLinesFunction
16+
} from '../../plugin/utils';
1717

1818
export const mockSerializerArgsForEmptyModule = (): Parameters<MetroSerializer> => {
1919
return [
@@ -48,6 +48,8 @@ export const mockSerializerArgsForEmptyModule = (): Parameters<MetroSerializer>
4848
};
4949

5050
export const mockSerializerArgsForSourceMappingURLModule = (): Parameters<MetroSerializer> => {
51+
const countLines = getCountLinesFunction();
52+
const createCountingSet = getCreateCountingSetFunction();
5153
const mockedCode = '//# sourceMappingURL=index.android.bundle.map';
5254
return [
5355
'index.js',

packages/core/src/metro/__tests__/metro.test.ts

Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ const DEBUG_ID_CODE_SNIPPET =
2727
'var _datadogDebugIds,_datadogDebugIdMeta;void 0===_datadogDebugIds&&(_datadogDebugIds={});try{var stack=(new Error).stack;stack&&(_datadogDebugIds[stack]="__datadog_debug_id_placeholder__",_datadogDebugIdMeta="datadog-debug-id-__datadog_debug_id_placeholder__")}catch(e){}';
2828

2929
describe('Datadog Metro Plugin', () => {
30+
afterEach(() => {
31+
jest.resetModules();
32+
});
33+
3034
describe('Datadog Metro Serializer', () => {
3135
test('generates bundle and source map with UUID v5 Debug ID', async () => {
3236
const codeSnippetHash = createHash('md5');
@@ -404,4 +408,254 @@ describe('Datadog Metro Plugin', () => {
404408
expect(result.map).toBe('{"testMap":"test"}');
405409
});
406410
});
411+
412+
describe('Import Utils', () => {
413+
test('M getDefaultExport extracts the default export if it exists', () => {
414+
// GIVEN
415+
// eslint-disable-next-line global-require, @typescript-eslint/no-var-requires
416+
const getDefaultExport = require('../plugin/utils')
417+
.getDefaultExport;
418+
419+
const exampleModuleWithDefault = {
420+
default: 'default export',
421+
namedExport: 'named export'
422+
};
423+
424+
// WHEN
425+
const result = getDefaultExport(exampleModuleWithDefault);
426+
427+
// THEN
428+
expect(result).toBe('default export');
429+
});
430+
431+
test('M getDefaultExport returns the module as it is if default does not exist', () => {
432+
// GIVEN
433+
// eslint-disable-next-line global-require, @typescript-eslint/no-var-requires
434+
const getDefaultExport = require('../plugin/utils')
435+
.getDefaultExport;
436+
437+
const exampleModule1 = {
438+
namedExport: 'named export'
439+
};
440+
441+
const exampleModule2 = 'just a string';
442+
443+
// WHEN
444+
const result1 = getDefaultExport(exampleModule1);
445+
const result2 = getDefaultExport(exampleModule2);
446+
447+
// THEN
448+
expect(result1).toEqual({ namedExport: 'named export' });
449+
expect(result2).toBe('just a string');
450+
});
451+
452+
test('M getDefaultExport returns undefined if the module is null or undefined', () => {
453+
// GIVEN
454+
// eslint-disable-next-line global-require, @typescript-eslint/no-var-requires
455+
const getDefaultExport = require('../plugin/utils')
456+
.getDefaultExport;
457+
458+
const exampleModule1 = null;
459+
const exampleModule2 = undefined;
460+
461+
// WHEN
462+
const result1 = getDefaultExport(exampleModule1);
463+
const result2 = getDefaultExport(exampleModule2);
464+
465+
// THEN
466+
expect(result1).toBeUndefined();
467+
expect(result2).toBeUndefined();
468+
});
469+
470+
test('M createCountingSet correctly imports function from metro/src when metro/private is not available', () => {
471+
// GIVEN
472+
jest.isolateModules(() => {
473+
// GIVEN
474+
jest.doMock('metro/private/lib/CountingSet', () => {
475+
throw new Error('Module not found');
476+
});
477+
478+
jest.doMock(
479+
'metro/src/lib/CountingSet',
480+
() => ({
481+
default: class CountingSetMock {
482+
test: string = 'constructor_not_called';
483+
constructor() {
484+
this.test = 'constructor_called';
485+
}
486+
}
487+
}),
488+
{ virtual: true }
489+
);
490+
491+
// WHEN
492+
// eslint-disable-next-line global-require, @typescript-eslint/no-var-requires
493+
const utils = require('../plugin/utils');
494+
const createCountingSet = utils.getCreateCountingSetFunction();
495+
const result = createCountingSet();
496+
497+
// THEN
498+
expect(result).toHaveProperty('test');
499+
expect(result.test).toBe('constructor_called');
500+
});
501+
});
502+
503+
test('M sourcemapString correctly imports function from metro/src when metro/private is not available', () => {
504+
// GIVEN
505+
jest.isolateModules(() => {
506+
// GIVEN
507+
jest.doMock(
508+
'metro/private/DeltaBundler/Serializers/sourceMapString',
509+
() => {
510+
throw new Error('Module not found');
511+
}
512+
);
513+
514+
jest.doMock(
515+
'metro/src/DeltaBundler/Serializers/sourceMapString',
516+
() => ({
517+
default: (
518+
modules: unknown[],
519+
options: object
520+
): string =>
521+
`test-modules_length:${
522+
modules.length
523+
},options_keys:${Object.keys(options).length}`
524+
}),
525+
{ virtual: true }
526+
);
527+
528+
// WHEN
529+
// eslint-disable-next-line global-require, @typescript-eslint/no-var-requires
530+
const utils = require('../plugin/utils');
531+
const metroSourceMapString = utils.getSourceMapStringFunction();
532+
const result = metroSourceMapString([{}, {}, {}], {
533+
excludeSource: true,
534+
shouldAddToIgnoreList: () => true
535+
});
536+
537+
// THEN
538+
expect(result).toBe('test-modules_length:3,options_keys:2');
539+
});
540+
});
541+
542+
test('M sourcemapString returns the correct function when retrieved as named export', () => {
543+
// GIVEN
544+
jest.isolateModules(() => {
545+
// GIVEN
546+
jest.doMock(
547+
'metro/private/DeltaBundler/Serializers/sourceMapString',
548+
() => ({
549+
sourceMapString: (
550+
modules: unknown[],
551+
options: object
552+
): string =>
553+
`test-modules_length:${
554+
modules.length
555+
},options_keys:${Object.keys(options).length}`
556+
})
557+
);
558+
559+
// WHEN
560+
// eslint-disable-next-line global-require, @typescript-eslint/no-var-requires
561+
const utils = require('../plugin/utils');
562+
const metroSourceMapString = utils.getSourceMapStringFunction();
563+
const result = metroSourceMapString([{}, {}, {}], {
564+
excludeSource: true,
565+
shouldAddToIgnoreList: () => true
566+
});
567+
// THEN
568+
expect(result).toBe('test-modules_length:3,options_keys:2');
569+
});
570+
});
571+
572+
test('M sourcemapString returns the correct function when retrieved as default export', () => {
573+
// GIVEN
574+
jest.isolateModules(() => {
575+
// GIVEN
576+
jest.doMock(
577+
'metro/private/DeltaBundler/Serializers/sourceMapString',
578+
() => ({
579+
default: (
580+
modules: unknown[],
581+
options: object
582+
): string =>
583+
`test-modules_length:${
584+
modules.length
585+
},options_keys:${Object.keys(options).length}`
586+
})
587+
);
588+
589+
// WHEN
590+
// eslint-disable-next-line global-require, @typescript-eslint/no-var-requires
591+
const utils = require('../plugin/utils');
592+
const metroSourceMapString = utils.getSourceMapStringFunction();
593+
const result = metroSourceMapString([{}, {}, {}], {
594+
excludeSource: true,
595+
shouldAddToIgnoreList: () => true
596+
});
597+
// THEN
598+
expect(result).toBe('test-modules_length:3,options_keys:2');
599+
});
600+
});
601+
602+
test('M getBaseJSBundle correctly imports function from metro/src when metro/private is not available', () => {
603+
// GIVEN
604+
jest.isolateModules(() => {
605+
// GIVEN
606+
jest.doMock(
607+
'metro/private/DeltaBundler/Serializers/baseJSBundle',
608+
() => {
609+
throw new Error('Module not found');
610+
}
611+
);
612+
613+
jest.doMock(
614+
'metro/src/DeltaBundler/Serializers/baseJSBundle',
615+
() => () => ({
616+
test: 'ok'
617+
}),
618+
{ virtual: true }
619+
);
620+
621+
// WHEN
622+
// eslint-disable-next-line global-require, @typescript-eslint/no-var-requires
623+
const utils = require('../plugin/utils');
624+
const baseJSBundle = utils.getBaseJSBundleFunction();
625+
const result = baseJSBundle();
626+
627+
// THEN
628+
expect(result).toHaveProperty('test');
629+
expect(result.test).toBe('ok');
630+
});
631+
});
632+
633+
test('M getCountLines correctly imports function from metro/src when metro/private is not available', () => {
634+
// GIVEN
635+
jest.isolateModules(() => {
636+
// GIVEN
637+
jest.doMock('metro/private/lib/countLines', () => {
638+
throw new Error('Module not found');
639+
});
640+
641+
// Random int between 10 and 100 to ensure the mock is used
642+
const randomInt = Math.floor(Math.random() * 90) + 10;
643+
644+
jest.doMock(
645+
'metro/src/lib/countLines',
646+
() => (str: string): number => str.length + randomInt,
647+
{ virtual: true }
648+
);
649+
650+
// WHEN
651+
// eslint-disable-next-line global-require, @typescript-eslint/no-var-requires
652+
const utils = require('../plugin/utils');
653+
const getCountLines = utils.getCountLinesFunction();
654+
const result = getCountLines('test-string');
655+
656+
// THEN
657+
expect(result).toBe('test-string'.length + randomInt);
658+
});
659+
});
660+
});
407661
});

packages/core/src/metro/metro.d.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ declare type Bundle = {
2828
};
2929

3030
// https://github.com/facebook/metro/blob/a3d021a0d021b5706372059f472715c63019e044/packages/metro/src/DeltaBundler/Serializers/baseJSBundle.js#L25
31-
declare module 'metro/src/DeltaBundler/Serializers/baseJSBundle' {
31+
declare module 'metro/private/DeltaBundler/Serializers/baseJSBundle' {
3232
const baseJSBundle: (
3333
entryPoint: string,
3434
preModules: ReadonlyArray<Module>,
@@ -39,7 +39,7 @@ declare module 'metro/src/DeltaBundler/Serializers/baseJSBundle' {
3939
}
4040

4141
// https://github.com/facebook/metro/blob/a3d021a0d021b5706372059f472715c63019e044/packages/metro/src/lib/bundleToString.js#L22
42-
declare module 'metro/src/lib/bundleToString' {
42+
declare module 'metro/private/lib/bundleToString' {
4343
const bundleToString: (
4444
bundle: Bundle
4545
) => {
@@ -51,7 +51,7 @@ declare module 'metro/src/lib/bundleToString' {
5151
}
5252

5353
// https://github.com/facebook/metro/blob/a3d021a0d021b5706372059f472715c63019e044/packages/metro/src/DeltaBundler/Serializers/sourceMapString.js#L22
54-
declare module 'metro/src/DeltaBundler/Serializers/sourceMapString' {
54+
declare module 'metro/private/DeltaBundler/Serializers/sourceMapString' {
5555
import type { MixedOutput, Module } from 'metro';
5656

5757
const sourceMapString: (
@@ -67,7 +67,7 @@ declare module 'metro/src/DeltaBundler/Serializers/sourceMapString' {
6767
}
6868

6969
// https://github.com/facebook/metro/blob/a3d021a0d021b5706372059f472715c63019e044/packages/metro/src/lib/countLines.js#L16
70-
declare module 'metro/src/lib/countLines' {
70+
declare module 'metro/private/lib/countLines' {
7171
const countLines: (code: string) => number;
7272
export = countLines;
7373
}

packages/core/src/metro/plugin/debugIdHelper.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@
99

1010
import { createHash } from 'crypto';
1111
import { existsSync, mkdirSync, writeFileSync, unlinkSync } from 'fs';
12-
// eslint-disable-next-line import/no-extraneous-dependencies
13-
import countLines from 'metro/src/lib/countLines';
1412
import path from 'path';
1513

1614
import type {
@@ -20,7 +18,7 @@ import type {
2018
MetroBundleWithMap,
2119
DatadogDebugIdModule
2220
} from './types/metroTypes';
23-
import { createCountingSet } from './utils';
21+
import { getCreateCountingSetFunction, getCountLinesFunction } from './utils';
2422

2523
/**
2624
* Regex to match the Debug ID comment in the bundle.
@@ -63,6 +61,8 @@ const DEBUG_ID_METADATA_PREFIX = 'datadog-debug-id-';
6361
*/
6462
export const createDebugIdModule = (debugId: string): DatadogDebugIdModule => {
6563
let debugIdCode = createDebugIdSnippet(debugId);
64+
const countLines = getCountLinesFunction();
65+
const createCountingSet = getCreateCountingSetFunction();
6666

6767
return {
6868
setSource: (code: string) => {

0 commit comments

Comments
 (0)