Skip to content

Commit

Permalink
refactor: add module IDs and restructure metro config for atlas conve…
Browse files Browse the repository at this point in the history
…rsion (#43)

* refactor: add module IDs and restructure metro config for atlas conversion

* refactor: rename `options` to `serializeOptions` to avoid ambiguity

* chore: resolve linting issues
  • Loading branch information
byCedric authored Apr 28, 2024
1 parent c1b69ed commit 467466b
Show file tree
Hide file tree
Showing 4 changed files with 60 additions and 50 deletions.
17 changes: 6 additions & 11 deletions src/cli.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { ConfigT as MetroConfig } from 'metro-config';

import { MetroGraphSource } from './data/MetroGraphSource';
import { MetroGraphSource, convertMetroConfig } from './data/MetroGraphSource';
import { createAtlasMiddleware } from './utils/middleware';

/**
Expand Down Expand Up @@ -28,24 +28,19 @@ export function createExpoAtlasMiddleware(config: MetroConfig) {
const registerMetro = source.registerMetro.bind(source);

const metroCustomSerializer = config.serializer?.customSerializer ?? (() => {});
const metroExtensions = {
source: config.resolver?.sourceExts,
asset: config.resolver?.assetExts,
};
const metroConfig = convertMetroConfig(config);

// @ts-expect-error Should still be writable at this stage
config.serializer.customSerializer = (entryPoint, preModules, graph, options) => {
config.serializer.customSerializer = (entryPoint, preModules, graph, serializeOptions) => {
source.serializeGraph({
projectRoot,
entryPoint,
preModules,
graph,
options,
extensions: metroExtensions,
watchFolders: config.watchFolders,
serializeOptions,
metroConfig,
});

return metroCustomSerializer(entryPoint, preModules, graph, options);
return metroCustomSerializer(entryPoint, preModules, graph, serializeOptions);
};

return { source, middleware, registerMetro };
Expand Down
65 changes: 45 additions & 20 deletions src/data/MetroGraphSource.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type metro from 'metro';
import type DeltaBundler from 'metro/src/DeltaBundler';
import type MetroServer from 'metro/src/Server';
import type { MetroConfig } from 'metro-config';
import path from 'path';

import type { AtlasBundle, AtlasBundleDelta, AtlasModule, AtlasSource } from './types';
Expand All @@ -16,11 +17,14 @@ type ConvertGraphToAtlasOptions = {
entryPoint: string;
preModules: Readonly<MetroModule[]>;
graph: MetroGraph;
options: Readonly<metro.SerializerOptions>;
watchFolders?: Readonly<string[]>;
extensions?: {
source?: Readonly<string[]>;
asset?: Readonly<string[]>;
serializeOptions: Readonly<metro.SerializerOptions>;
/** Options passed-through from the Metro config */
metroConfig: {
watchFolders?: Readonly<string[]>;
resolver?: {
sourceExts?: Readonly<string[]>;
assetExts?: Readonly<string[]>;
};
};
};

Expand Down Expand Up @@ -129,6 +133,17 @@ class MetroDeltaListener {
}
}

/** Convert options from the Metro config, used during graph conversions to Atlas */
export function convertMetroConfig(config: MetroConfig): ConvertGraphToAtlasOptions['metroConfig'] {
return {
watchFolders: config.watchFolders,
resolver: {
sourceExts: config.resolver?.sourceExts,
assetExts: config.resolver?.assetExts,
},
};
}

/** Convert a Metro graph instance to a JSON-serializable entry */
export function convertGraph(options: ConvertGraphToAtlasOptions): AtlasBundle {
const serializeOptions = convertSerializeOptions(options);
Expand All @@ -153,7 +168,10 @@ export function convertGraph(options: ConvertGraphToAtlasOptions): AtlasBundle {

/** Find and collect all dependnecies related to the entrypoint within the graph */
export function collectEntryPointModules(
options: Pick<ConvertGraphToAtlasOptions, 'graph' | 'entryPoint' | 'extensions'>
options: Pick<
ConvertGraphToAtlasOptions,
'graph' | 'entryPoint' | 'serializeOptions' | 'metroConfig'
>
) {
const modules = new Map<string, AtlasModule>();

Expand All @@ -171,20 +189,28 @@ export function collectEntryPointModules(

/** Convert a Metro module to a JSON-serializable Atlas module */
export function convertModule(
options: Pick<ConvertGraphToAtlasOptions, 'graph' | 'extensions'>,
options: Pick<ConvertGraphToAtlasOptions, 'graph' | 'metroConfig' | 'serializeOptions'>,
module: MetroModule
): AtlasModule {
const { createModuleId } = options.serializeOptions;

return {
id: createModuleId(module.path),
path: module.path,
package: getPackageNameFromPath(module.path),
size: module.output.reduce((bytes, output) => bytes + Buffer.byteLength(output.data.code), 0),
imports: Array.from(module.dependencies.values()).map((module) => ({
id: createModuleId(module.absolutePath),
path: module.absolutePath,
package: getPackageNameFromPath(module.absolutePath),
})),
importedBy: Array.from(module.inverseDependencies)
.filter((path) => options.graph.dependencies.has(path))
.map((path) => ({ path, package: getPackageNameFromPath(path) })),
.map((path) => ({
id: createModuleId(path),
path,
package: getPackageNameFromPath(path),
})),
source: getModuleSourceContent(options, module),
output: module.output.map((output) => ({
type: output.type,
Expand All @@ -198,16 +224,16 @@ export function convertModule(
* If a file is an asset, it returns `[binary file]` instead.
*/
function getModuleSourceContent(
options: Pick<ConvertGraphToAtlasOptions, 'extensions'>,
options: Pick<ConvertGraphToAtlasOptions, 'metroConfig'>,
module: MetroModule
) {
const fileExtension = path.extname(module.path).replace('.', '');

if (options.extensions?.source?.includes(fileExtension)) {
if (options.metroConfig.resolver?.sourceExts?.includes(fileExtension)) {
return module.getSource().toString();
}

if (options.extensions?.asset?.includes(fileExtension)) {
if (options.metroConfig.resolver?.assetExts?.includes(fileExtension)) {
return '[binary file]';
}

Expand All @@ -231,11 +257,11 @@ export function convertTransformOptions(

/** Convert Metro serialize options to a JSON-serializable object */
export function convertSerializeOptions(
options: Pick<ConvertGraphToAtlasOptions, 'options'>
options: Pick<ConvertGraphToAtlasOptions, 'serializeOptions'>
): AtlasBundle['serializeOptions'] {
const serializeOptions: AtlasBundle['serializeOptions'] = { ...options.options };
const serializeOptions: AtlasBundle['serializeOptions'] = { ...options.serializeOptions };

// Delete all filters
// Delete all non-serializable functions
delete serializeOptions['processModuleFilter'];
delete serializeOptions['createModuleId'];
delete serializeOptions['getRunModuleStatement'];
Expand All @@ -245,12 +271,11 @@ export function convertSerializeOptions(
}

/** Get the shared root of `projectRoot` and `watchFolders`, used to make all paths within the bundle relative */
function getSharedRoot(options: Pick<ConvertGraphToAtlasOptions, 'projectRoot' | 'watchFolders'>) {
if (!options.watchFolders?.length) {
return options.projectRoot;
}

return findSharedRoot([options.projectRoot, ...options.watchFolders]) ?? options.projectRoot;
function getSharedRoot(options: Pick<ConvertGraphToAtlasOptions, 'projectRoot' | 'metroConfig'>) {
const { watchFolders } = options.metroConfig;
return !watchFolders?.length
? options.projectRoot
: findSharedRoot([options.projectRoot, ...watchFolders]) ?? options.projectRoot;
}

/** Determine if the module is a virtual module, like shims or canaries, which should be excluded from results */
Expand Down
4 changes: 3 additions & 1 deletion src/data/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ export type AtlasBundleDelta = {
};

export type AtlasModule = {
/** The internal module ID given by Metro */
id: number;
/** The absoluate path of this module */
path: string;
/** The name of the package this module belongs to, if from an external package */
Expand All @@ -63,4 +65,4 @@ export type AtlasModule = {
output?: MixedOutput[];
};

export type AtlasModuleRef = Pick<AtlasModule, 'path' | 'package'>;
export type AtlasModuleRef = Pick<AtlasModule, 'id' | 'path' | 'package'>;
24 changes: 6 additions & 18 deletions src/metro.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { type MetroConfig } from 'metro-config';

import { createAtlasFile, getAtlasPath, writeAtlasEntry } from './data/AtlasFileSource';
import { convertGraph } from './data/MetroGraphSource';
import { convertGraph, convertMetroConfig } from './data/MetroGraphSource';

type ExpoAtlasOptions = Partial<{
/** The output of the atlas file, defaults to `.expo/atlas.json` */
Expand Down Expand Up @@ -33,32 +33,20 @@ export function withExpoAtlas(config: MetroConfig, options: ExpoAtlasOptions = {
}

const atlasFile = options?.atlasFile ?? getAtlasPath(projectRoot);
const watchFolders = config.watchFolders;
const extensions = {
source: config.resolver?.sourceExts,
asset: config.resolver?.assetExts,
};
const metroConfig = convertMetroConfig(config);

// Note(cedric): we don't have to await this, Metro would never bundle before this is finisheds
// Note(cedric): we don't have to await this, Metro would never bundle before this is finishes
createAtlasFile(atlasFile);

// @ts-expect-error
config.serializer.customSerializer = (entryPoint, preModules, graph, options) => {
config.serializer.customSerializer = (entryPoint, preModules, graph, serializeOptions) => {
// Note(cedric): we don't have to await this, it has a built-in write queue
writeAtlasEntry(
atlasFile,
convertGraph({
projectRoot,
entryPoint,
preModules,
graph,
options,
extensions,
watchFolders,
})
convertGraph({ projectRoot, entryPoint, preModules, graph, serializeOptions, metroConfig })
);

return originalSerializer(entryPoint, preModules, graph, options);
return originalSerializer(entryPoint, preModules, graph, serializeOptions);
};

return config;
Expand Down

0 comments on commit 467466b

Please sign in to comment.