Skip to content

Commit

Permalink
Optimize files size computing (#128)
Browse files Browse the repository at this point in the history
-  Iterate through each mapping rather than the source and only look at the source to determine the number of columns contributing to each file's mapped bytes.
- Add new error types `InvalidMappingLine` (when source map references a line outside source lines) and `InvalidMappingColumn` (when source map reference a column beyond source column at the particular line)
  • Loading branch information
Anish2 authored and nikolay-borzov committed Sep 22, 2019
1 parent 8b48640 commit c007f36
Show file tree
Hide file tree
Showing 5 changed files with 101 additions and 48 deletions.
29 changes: 29 additions & 0 deletions src/app-error.ts
Expand Up @@ -36,6 +36,19 @@ interface UnmappedBytesErrorContext {
unmappedBytes: number;
}

interface InvalidMappingLineErrorContext {
code: 'InvalidMappingLine';
generatedLine: number;
maxLine: number;
}

interface InvalidMappingColumnErrorContext {
code: 'InvalidMappingColumn';
generatedLine: number;
generatedColumn: number;
maxColumn: number;
}

interface CannotOpenTempFileErrorContext {
code: 'CannotOpenTempFile';
error: Buffer;
Expand All @@ -46,6 +59,8 @@ export type ErrorContext =
| CommonErrorContext
| OneSourceSourceMapErrorContext
| UnmappedBytesErrorContext
| InvalidMappingLineErrorContext
| InvalidMappingColumnErrorContext
| CannotOpenTempFileErrorContext;

export function getErrorMessage(context: ErrorContext): string {
Expand All @@ -71,6 +86,20 @@ See ${SOURCE_MAP_INFO_URL}`;
return `Unable to map ${unmappedBytes}/${totalBytes} bytes (${bytesString}%)`;
}

case 'InvalidMappingLine': {
const { generatedLine, maxLine } = context;

return `Your source map refers to generated line ${generatedLine}, but the source only contains ${maxLine} line(s).
Check that you are using the correct source map.`;
}

case 'InvalidMappingColumn': {
const { generatedLine, generatedColumn, maxColumn } = context;

return `Your source map refers to generated column ${generatedColumn} on line ${generatedLine}, but the source only contains ${maxColumn} column(s) on that line.
Check that you are using the correct source map.`;
}

case 'CannotSaveFile':
return 'Unable to save HTML to file';

Expand Down
94 changes: 46 additions & 48 deletions src/explore.ts
Expand Up @@ -21,7 +21,7 @@ export async function exploreBundle(

const sourceMapData = await loadSourceMap(code, map);

const sizes = computeGeneratedFileSizes(sourceMapData);
const sizes = computeFileSizeMapOptimized(sourceMapData);

const files = adjustSourcePaths(sizes.files, options);

Expand Down Expand Up @@ -86,65 +86,63 @@ async function loadSourceMap(codeFile: File, sourceMapFile?: File): Promise<Sour
}

/** Calculate the number of bytes contributed by each source file */
function computeGeneratedFileSizes(sourceMapData: SourceMapData): FileSizes {
const spans = computeSpans(sourceMapData);

function computeFileSizeMapOptimized(sourceMapData: SourceMapData): FileSizes {
const { consumer, codeFileContent } = sourceMapData;
const lines = codeFileContent.split('\n');
const files: FileSizeMap = {};
let unmappedBytes = 0;
let totalBytes = 0;

for (let i = 0; i < spans.length; i++) {
const { numChars, source } = spans[i];
let mappedBytes = 0;

consumer.computeColumnSpans();

consumer.eachMapping(({ source, generatedLine, generatedColumn, lastGeneratedColumn }) => {
// Lines are 1-based
const line = lines[generatedLine - 1];
if (line === null) {
throw new AppError({
code: 'InvalidMappingLine',
generatedLine: generatedLine,
maxLine: lines.length,
});
}

totalBytes += numChars;
// Columns are 0-based
if (generatedColumn >= line.length) {
throw new AppError({
code: 'InvalidMappingColumn',
generatedLine: generatedLine,
generatedColumn: generatedColumn,
maxColumn: line.length,
});
}

if (source === null) {
unmappedBytes += numChars;
let mappingLength = 0;
if (lastGeneratedColumn !== null) {
if (lastGeneratedColumn >= line.length) {
throw new AppError({
code: 'InvalidMappingColumn',
generatedLine: generatedLine,
generatedColumn: lastGeneratedColumn,
maxColumn: line.length,
});
}
mappingLength = lastGeneratedColumn - generatedColumn + 1;
} else {
files[source] = (files[source] || 0) + numChars;
mappingLength = line.length - generatedColumn;
}
}
files[source] = (files[source] || 0) + mappingLength;
mappedBytes += mappingLength;
});

// Don't count newlines as original version didn't count newlines
const totalBytes = codeFileContent.length - lines.length + 1;

return {
files,
unmappedBytes,
unmappedBytes: totalBytes - mappedBytes,
totalBytes,
};
}

interface Span {
source: string | null;
numChars: number;
}

function computeSpans(sourceMapData: SourceMapData): Span[] {
const { consumer, codeFileContent } = sourceMapData;

const lines = codeFileContent.split('\n');
const spans: Span[] = [];
let numChars = 0;

let lastSource: string | null | undefined = undefined; // not a string, not null

for (let line = 1; line <= lines.length; line++) {
const lineText = lines[line - 1];
const numCols = lineText.length;

for (let column = 0; column < numCols; column++, numChars++) {
const { source } = consumer.originalPositionFor({ line, column });

if (source !== lastSource) {
lastSource = source;
spans.push({ source, numChars: 1 });
} else {
spans[spans.length - 1].numChars += 1;
}
}
}

return spans;
}

export function adjustSourcePaths(fileSizeMap: FileSizeMap, options: ExploreOptions): FileSizeMap {
if (!options.noRoot) {
const prefix = getCommonPathPrefix(Object.keys(fileSizeMap));
Expand Down
9 changes: 9 additions & 0 deletions src/index.ts
Expand Up @@ -20,6 +20,8 @@ export type ErrorCode =
| 'NoSourceMap'
| 'OneSourceSourceMap'
| 'UnmappedBytes'
| 'InvalidMappingLine'
| 'InvalidMappingColumn'
| 'CannotSaveFile'
| 'CannotCreateTempFile'
| 'CannotOpenTempFile';
Expand Down Expand Up @@ -71,3 +73,10 @@ export interface ExploreErrorResult {
}

export type BundlesAndFileTokens = (Bundle | string)[] | Bundle | string;

// TODO: Remove when https://github.com/mozilla/source-map/pull/374 is merged
declare module 'source-map' {
export interface MappingItem {
lastGeneratedColumn: number | null;
}
}
10 changes: 10 additions & 0 deletions tests/__snapshots__/app-error.test.ts.snap
Expand Up @@ -12,6 +12,16 @@ exports['app-error getErrorMessage should create message for \'CannotSaveFile\'
Unable to save HTML to file
`

exports['app-error getErrorMessage should create message for \'InvalidMappingColumn\' 1'] = `
Your source map refers to generated column 81 on line 60, but the source only contains 80 column(s) on that line.
Check that you are using the correct source map.
`

exports['app-error getErrorMessage should create message for \'InvalidMappingLine\' 1'] = `
Your source map refers to generated line 60, but the source only contains 57 line(s).
Check that you are using the correct source map.
`

exports['app-error getErrorMessage should create message for \'NoBundles\' 1'] = `
No file(s) provided
`
Expand Down
7 changes: 7 additions & 0 deletions tests/app-error.test.ts
Expand Up @@ -9,6 +9,13 @@ describe('app-error', function() {
{ code: 'NoSourceMap' },
{ code: 'OneSourceSourceMap', filename: 'foo.min.js' },
{ code: 'UnmappedBytes', totalBytes: 100, unmappedBytes: 70 },
{ code: 'InvalidMappingLine', generatedLine: 60, maxLine: 57 },
{
code: 'InvalidMappingColumn',
generatedLine: 60,
generatedColumn: 81,
maxColumn: 80,
},
{ code: 'CannotSaveFile' },
{ code: 'CannotCreateTempFile' },
{
Expand Down

0 comments on commit c007f36

Please sign in to comment.