UNPKG

4.13 kBJavaScriptView Raw
1// @flow
2
3import type {FilePath, NamedBundle} from '@parcel/types';
4import type {FileSystem} from '@parcel/fs';
5import SourceMap from '@parcel/source-map';
6import nullthrows from 'nullthrows';
7import path from 'path';
8import {loadSourceMapUrl} from './';
9
10export type AssetStats = {|
11 filePath: string,
12 size: number,
13 originalSize: number,
14 time: number,
15|};
16
17export type BundleStats = {|
18 filePath: string,
19 size: number,
20 time: number,
21 assets: Array<AssetStats>,
22|};
23
24export type BuildMetrics = {|
25 bundles: Array<BundleStats>,
26|};
27
28async function getSourcemapSizes(
29 filePath: FilePath,
30 fs: FileSystem,
31 projectRoot: FilePath,
32): Promise<?Map<string, number>> {
33 let bundleContents = await fs.readFile(filePath, 'utf-8');
34 let mapUrlData = await loadSourceMapUrl(fs, filePath, bundleContents);
35 if (!mapUrlData) {
36 return null;
37 }
38
39 let rawMap = mapUrlData.map;
40 let sourceMap = new SourceMap();
41 sourceMap.addRawMappings(rawMap);
42 let parsedMapData = sourceMap.getMap();
43
44 if (parsedMapData.mappings.length > 2) {
45 let sources = parsedMapData.sources.map(s =>
46 path.normalize(path.join(projectRoot, s)),
47 );
48 let currLine = 1;
49 let currColumn = 0;
50 let currMappingIndex = 0;
51 let currMapping = parsedMapData.mappings[currMappingIndex];
52 let nextMapping = parsedMapData.mappings[currMappingIndex + 1];
53 let sourceSizes = new Array(sources.length).fill(0);
54 let unknownOrigin: number = 0;
55 for (let i = 0; i < bundleContents.length; i++) {
56 let character = bundleContents[i];
57
58 while (
59 nextMapping &&
60 nextMapping.generated.line === currLine &&
61 nextMapping.generated.column <= currColumn
62 ) {
63 currMappingIndex++;
64 currMapping = parsedMapData.mappings[currMappingIndex];
65 nextMapping = parsedMapData.mappings[currMappingIndex + 1];
66 }
67
68 let currentSource = currMapping.source;
69 let charSize = Buffer.byteLength(character, 'utf8');
70 if (
71 currentSource != null &&
72 currMapping.generated.line === currLine &&
73 currMapping.generated.column <= currColumn
74 ) {
75 sourceSizes[currentSource] += charSize;
76 } else {
77 unknownOrigin += charSize;
78 }
79
80 if (character === '\n') {
81 currColumn = 0;
82 currLine++;
83 } else {
84 currColumn++;
85 }
86 }
87
88 let sizeMap = new Map();
89 for (let i = 0; i < sourceSizes.length; i++) {
90 sizeMap.set(sources[i], sourceSizes[i]);
91 }
92
93 sizeMap.set('', unknownOrigin);
94
95 return sizeMap;
96 }
97}
98
99async function createBundleStats(
100 bundle: NamedBundle,
101 fs: FileSystem,
102 projectRoot: FilePath,
103) {
104 let filePath = bundle.filePath;
105 let sourcemapSizes = await getSourcemapSizes(filePath, fs, projectRoot);
106
107 let assets: Map<string, AssetStats> = new Map();
108 bundle.traverseAssets(asset => {
109 let filePath = path.normalize(asset.filePath);
110 assets.set(filePath, {
111 filePath,
112 size: asset.stats.size,
113 originalSize: asset.stats.size,
114 time: asset.stats.time,
115 });
116 });
117
118 let assetsReport: Array<AssetStats> = [];
119 if (sourcemapSizes && sourcemapSizes.size) {
120 assetsReport = Array.from(sourcemapSizes.keys()).map((filePath: string) => {
121 let foundSize = sourcemapSizes.get(filePath) || 0;
122 let stats = assets.get(filePath) || {
123 filePath,
124 size: foundSize,
125 originalSize: foundSize,
126 time: 0,
127 };
128
129 return {
130 ...stats,
131 size: foundSize,
132 };
133 });
134 } else {
135 assetsReport = Array.from(assets.values());
136 }
137
138 return {
139 filePath: nullthrows(bundle.filePath),
140 size: bundle.stats.size,
141 time: bundle.stats.time,
142 assets: assetsReport.sort((a, b) => b.size - a.size),
143 };
144}
145
146export default async function generateBuildMetrics(
147 bundles: Array<NamedBundle>,
148 fs: FileSystem,
149 projectRoot: FilePath,
150): Promise<BuildMetrics> {
151 bundles.sort((a, b) => b.stats.size - a.stats.size).filter(b => !!b.filePath);
152
153 return {
154 bundles: (
155 await Promise.all(bundles.map(b => createBundleStats(b, fs, projectRoot)))
156 ).filter(e => !!e),
157 };
158}