Skip to content

Commit

Permalink
refactor: move bundle graph calculation to server and clean up compon…
Browse files Browse the repository at this point in the history
…ents (#27)

* feature: add new bundle graph

* refactor: simplify modules api endpoints

* refactor: add close and clear filters

* refactor: update graph pages

* fix: avoid jumping headers when adding/removing buttons

* chore: clean up legacy graphs

* chore: revert local package changes

* chore: bump new prerelease version

* fix: disable root tooltip and remove logging

* chore: bump new prerelease version
  • Loading branch information
byCedric authored Apr 5, 2024
1 parent 4862cc6 commit 93a3327
Show file tree
Hide file tree
Showing 15 changed files with 696 additions and 751 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"sideEffects": false,
"name": "expo-atlas",
"version": "0.0.17",
"version": "0.0.18-preview.2",
"description": "Inspect bundle stats from Metro",
"keywords": [
"expo",
Expand Down
Binary file modified webui/bun.lockb
Binary file not shown.
10 changes: 5 additions & 5 deletions webui/fixture/atlas-tabs-50.jsonl

Large diffs are not rendered by default.

74 changes: 0 additions & 74 deletions webui/src/app/api/stats/[entry]/folders/index+api.ts

This file was deleted.

74 changes: 74 additions & 0 deletions webui/src/app/api/stats/[entry]/modules/graph+api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { statsModuleFiltersFromUrlParams } from '~/components/forms/StatsModuleFilter';
import { getSource } from '~/utils/atlas';
import { globFilterModules } from '~/utils/search';
import { type TreemapNode, createModuleTree, finalizeModuleTree } from '~/utils/treemap';
import type { StatsEntry, StatsModule } from '~core/data/types';

export type ModuleGraphResponse = {
data: TreemapNode;
entry: {
platform: 'android' | 'ios' | 'web';
moduleSize: number;
moduleFiles: number;
};
filtered: {
moduleSize: number;
moduleFiles: number;
};
};

export async function GET(request: Request, params: Record<'entry', string>) {
let entry: StatsEntry;

try {
entry = await getSource().getEntry(params.entry);
} catch (error: any) {
return Response.json({ error: error.message }, { status: 406 });
}

const allModules = Array.from(entry.modules.values());
const modules = modulesMatchingFilters(request, entry, allModules);
const tree = createModuleTree(modules);

const response: ModuleGraphResponse = {
data: finalizeModuleTree(tree),
entry: {
platform: entry.platform as any,
moduleSize: allModules.reduce((size, module) => size + module.size, 0),
moduleFiles: entry.modules.size,
},
filtered: {
moduleSize: modules.reduce((size, module) => size + module.size, 0),
moduleFiles: modules.length,
},
};

return Response.json(response);
}

/**
* Get and filter the modules from the stats entry based on query parameters.
* - `modules=project,node_modules` to show only project code and/or node_modules
* - `include=<glob>` to only include specific glob patterns
* - `exclude=<glob>` to only exclude specific glob patterns
* - `path=<folder>` to only show modules in a specific folder
*/
function modulesMatchingFilters(
request: Request,
entry: StatsEntry,
modules: StatsModule[]
): StatsModule[] {
const searchParams = new URL(request.url).searchParams;

const folderRef = searchParams.get('path');
if (folderRef) {
modules = modules.filter((module) => module.path.startsWith(folderRef));
}

const filters = statsModuleFiltersFromUrlParams(searchParams);
if (!filters.modules.includes('node_modules')) {
modules = modules.filter((module) => !module.package);
}

return globFilterModules(modules, entry.projectRoot, filters);
}
67 changes: 36 additions & 31 deletions webui/src/app/api/stats/[entry]/modules/index+api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,18 @@ import { globFilterModules } from '~/utils/search';
import { type StatsEntry, type StatsModule } from '~core/data/types';

/** The partial module data, when listing all available modules from a stats entry */
export type ModuleMetadata = Omit<StatsModule, 'source' | 'output'> & {
source: undefined;
output: undefined;
};
export type PartialModule = Omit<StatsModule, 'source' | 'output'>;

export type EntryGraphData = {
metadata: {
export type ModuleListResponse = {
data: PartialModule[];
entry: {
platform: 'android' | 'ios' | 'web';
size: number;
modulesCount: number;
moduleSize: number;
moduleFiles: number;
};
data: {
size: number;
modulesCount: number;
modules: ModuleMetadata[];
filtered: {
moduleSize: number;
moduleFiles: number;
};
};

Expand All @@ -31,42 +28,50 @@ export async function GET(request: Request, params: Record<'entry', string>) {
return Response.json({ error: error.message }, { status: 406 });
}

const filteredModules = filterModules(request, entry);
const data: EntryGraphData = {
metadata: {
const allModules = Array.from(entry.modules.values());
const modules = modulesMatchingFilters(request, entry, allModules);

const response: ModuleListResponse = {
data: modules,
entry: {
platform: entry.platform as any,
size: Array.from(entry.modules.values()).reduce((size, module) => size + module.size, 0),
modulesCount: entry.modules.size,
moduleSize: allModules.reduce((size, module) => size + module.size, 0),
moduleFiles: entry.modules.size,
},
data: {
size: filteredModules.reduce((size, module) => size + module.size, 0),
modulesCount: filteredModules.length,
modules: filteredModules,
filtered: {
moduleSize: modules.reduce((size, module) => size + module.size, 0),
moduleFiles: modules.length,
},
};

return Response.json(data);
return Response.json(response);
}

/**
* Filter the node modules based on query parameters.
* Get and filter the modules from the stats entry based on query parameters.
* - `modules=project,node_modules` to show only project code and/or node_modules
* - `include=<glob>` to only include specific glob patterns
* - `exclude=<glob>` to only exclude specific glob patterns
* - `path=<folder>` to only show modules in a specific folder
*/
function filterModules(request: Request, stats: StatsEntry): ModuleMetadata[] {
const filters = statsModuleFiltersFromUrlParams(new URL(request.url).searchParams);
let modules = Array.from(stats.modules.values());
function modulesMatchingFilters(
request: Request,
entry: StatsEntry,
modules: StatsModule[]
): StatsModule[] {
const searchParams = new URL(request.url).searchParams;

const folderRef = searchParams.get('path');
if (folderRef) {
modules = modules.filter((module) => module.path.startsWith(folderRef));
}

const filters = statsModuleFiltersFromUrlParams(searchParams);
if (!filters.modules.includes('node_modules')) {
modules = modules.filter((module) => !module.package);
}

return globFilterModules(modules, stats.projectRoot, filters).map((module) => ({
...module,
source: undefined,
output: undefined,
}));
return globFilterModules(modules, entry.projectRoot, filters);
}

/**
Expand Down
Loading

0 comments on commit 93a3327

Please sign in to comment.