Skip to content

Commit

Permalink
Get rid of source-map-resolve and add Windows support
Browse files Browse the repository at this point in the history
  • Loading branch information
maxdavidson committed Jun 3, 2020
1 parent 66058ac commit ff874ab
Show file tree
Hide file tree
Showing 8 changed files with 100 additions and 133 deletions.
1 change: 1 addition & 0 deletions .gitattributes
@@ -0,0 +1 @@
* text=lf
7 changes: 6 additions & 1 deletion .travis.yml
@@ -1,9 +1,14 @@
language: node_js
os:
- linux
- windows
node_js:
- lts/dubnium
- lts/erbium
- stable
- node
cache: npm
git:
autocrlf: input
script:
- npm run typecheck
- npm run lint
Expand Down
9 changes: 1 addition & 8 deletions package.json
Expand Up @@ -40,8 +40,7 @@
"dist"
],
"dependencies": {
"@rollup/pluginutils": "^3.0.9",
"source-map-resolve": "^0.6.0"
"@rollup/pluginutils": "^3.0.9"
},
"devDependencies": {
"@rollup/plugin-typescript": "^4.1.1",
Expand All @@ -59,12 +58,6 @@
"typescript": "~3.9.3"
},
"peerDependencies": {
"@types/node": ">=10.0.0",
"rollup": ">=0.31.2"
},
"peerDependenciesMeta": {
"@types/node": {
"optional": true
}
}
}
4 changes: 3 additions & 1 deletion rollup.config.js
@@ -1,9 +1,11 @@
// @ts-check
import typescript from '@rollup/plugin-typescript';

import packageJson from './package.json';

export default /** @type {import('rollup').RollupOptions} */ ({
input: 'src/index.ts',
external: () => true,
external: Object.values(packageJson.dependencies),
plugins: [typescript()],
output: [
{
Expand Down
57 changes: 29 additions & 28 deletions src/__tests__/index.ts
@@ -1,7 +1,6 @@
/* eslint-disable @typescript-eslint/require-await, @typescript-eslint/no-non-null-assertion */
import fs from 'fs';
import path from 'path';
import util from 'util';
import ts from 'typescript';
import { rollup } from 'rollup';
import { describe, it, test, expect } from '@jest/globals';
Expand Down Expand Up @@ -30,26 +29,28 @@ async function rollupBundle({
}: ts.TranspileOutput & {
pluginOptions?: SourcemapsPluginOptions;
}) {
const load = async (path: string) => {
switch (path) {
case inputPath:
function load(path: string) {
switch (path.toLowerCase()) {
case inputPath.toLowerCase():
return inputText;
case outputPath:
case outputPath.toLowerCase():
return outputText;
case sourceMapPath:
case sourceMapPath.toLowerCase():
return sourceMapText!;
default:
throw new Error(`Unexpected path: ${path}`);
}
};
}

const { generate } = await rollup({
input: outputPath,
external: () => true,
plugins: [
{ name: 'skip-checks', resolveId: path => path },
sourcemaps({
readFile: util.callbackify(load),
async readFile(path: string) {
return load(path);
},
...pluginOptions,
}),
{ name: 'fake-fs', load },
Expand Down Expand Up @@ -82,8 +83,8 @@ it('ignores files with no source maps', async () => {
const { map } = await rollupBundle({ outputText, sourceMapText });

expect(map).toBeDefined();
expect(map!.sources).toStrictEqual([outputPath]);
expect(map!.sourcesContent).toStrictEqual([outputText]);
expect(map?.sources.map(path.normalize)).toStrictEqual([outputPath]);
expect(map?.sourcesContent).toStrictEqual([outputText]);
});

describe('detects files with source maps', () => {
Expand Down Expand Up @@ -123,8 +124,8 @@ describe('detects files with source maps', () => {
const { map } = await rollupBundle({ outputText, sourceMapText });

expect(map).toBeDefined();
expect(map!.sources).toStrictEqual([inputPath]);
expect(map!.sourcesContent).toStrictEqual([inputText]);
expect(map?.sources.map(path.normalize)).toStrictEqual([inputPath]);
expect(map?.sourcesContent).toStrictEqual([inputText]);
},
);
});
Expand All @@ -150,8 +151,8 @@ describe('ignores filtered files', () => {
});

expect(map).toBeDefined();
expect(map!.sources).toStrictEqual([outputPath]);
expect(map!.sourcesContent).toStrictEqual([outputText]);
expect(map?.sources.map(path.normalize)).toStrictEqual([outputPath]);
expect(map?.sourcesContent).toStrictEqual([outputText]);
});

test('excluded', async () => {
Expand All @@ -169,13 +170,13 @@ describe('ignores filtered files', () => {
outputText,
sourceMapText,
pluginOptions: {
exclude: [path.relative(process.cwd(), outputPath)],
exclude: [path.relative(process.cwd(), outputPath).split(path.sep).join(path.posix.sep)],
},
});

expect(map).toBeDefined();
expect(map!.sources).toStrictEqual([outputPath]);
expect(map!.sourcesContent).toStrictEqual([outputText]);
expect(map?.sources.map(path.normalize)).toStrictEqual([outputPath]);
expect(map?.sourcesContent).toStrictEqual([outputText]);
});
});

Expand All @@ -194,15 +195,15 @@ it('delegates failing file reads to the next plugin', async () => {
outputText,
sourceMapText,
pluginOptions: {
readFile(_path, cb) {
cb(new Error('Failed!'), '');
async readFile() {
throw new Error('Failed!');
},
},
});

expect(map).toBeDefined();
expect(map!.sources).toStrictEqual([outputPath]);
expect(map!.sourcesContent).toStrictEqual([outputText]);
expect(map?.sources.map(path.normalize)).toStrictEqual([outputPath]);
expect(map?.sourcesContent).toStrictEqual([outputText]);
});

it('handles failing source maps reads', async () => {
Expand All @@ -220,20 +221,20 @@ it('handles failing source maps reads', async () => {
outputText,
sourceMapText,
pluginOptions: {
readFile: util.callbackify(async (path: string) => {
switch (path) {
case inputPath:
async readFile(path: string) {
switch (path.toLowerCase()) {
case inputPath.toLowerCase():
return inputText;
case outputPath:
case outputPath.toLowerCase():
return outputText;
default:
throw new Error(`Unexpected path: ${path}`);
}
}),
},
},
});

expect(map).toBeDefined();
expect(map!.sources).toStrictEqual([outputPath]);
expect(map!.sourcesContent).toStrictEqual([outputText]);
expect(map?.sources.map(path.normalize)).toStrictEqual([outputPath]);
expect(map?.sourcesContent).toStrictEqual([outputText]);
});
64 changes: 0 additions & 64 deletions src/env.d.ts

This file was deleted.

59 changes: 28 additions & 31 deletions src/index.ts
@@ -1,28 +1,26 @@
import fs from 'fs';
import { promisify } from 'util';
import { Plugin, ExistingRawSourceMap } from 'rollup';
import pluginUtils, { CreateFilter } from '@rollup/pluginutils';
import sourceMapResolve from 'source-map-resolve';

const { createFilter } = pluginUtils;
const { resolveSourceMap, resolveSources } = sourceMapResolve;
import { getSourceMappingURL, tryParseDataUri, resolvePath, readFileAsString } from './utils';

const promisifiedResolveSourceMap = promisify(resolveSourceMap);
const promisifiedResolveSources = promisify(resolveSources);
const { createFilter } = pluginUtils;

export interface SourcemapsPluginOptions {
include?: Parameters<CreateFilter>[0];
exclude?: Parameters<CreateFilter>[1];
readFile?(path: string, callback: (error: Error | null, data: Buffer | string) => void): void;
readFile?(path: string): Promise<string>;
}

export default function sourcemaps({
include,
exclude,
readFile = fs.readFile,
readFile = readFileAsString,
}: SourcemapsPluginOptions = {}): Plugin {
const filter = createFilter(include, exclude);
const promisifiedReadFile = promisify(readFile);

async function loadFile(path: string, base?: string) {
return tryParseDataUri(path) ?? (await readFile(resolvePath(path, base)));
}

return {
name: 'sourcemaps',
Expand All @@ -34,38 +32,37 @@ export default function sourcemaps({

let code: string;
try {
code = (await promisifiedReadFile(id)).toString();
code = await loadFile(id);
} catch {
this.warn('Failed reading file');
return null;
}

let map: ExistingRawSourceMap;
try {
const result = await promisifiedResolveSourceMap(code, id, readFile);
const sourceMappingURL = getSourceMappingURL(code);

// The code contained no sourceMappingURL
if (result === null) {
return code;
}
// The code contained no sourceMappingURL
if (sourceMappingURL === undefined) {
return code;
}

map = result.map;
let map: ExistingRawSourceMap;
try {
const rawSourceMap = await loadFile(sourceMappingURL, id);
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
map = JSON.parse(rawSourceMap);
} catch {
this.warn('Failed resolving source map');
this.warn('Failed loading source map');
return code;
}

// Resolve sources if they're not included
if (map.sourcesContent === undefined) {
try {
const { sourcesContent } = await promisifiedResolveSources(map, id, readFile);
if (sourcesContent.every(item => typeof item === 'string')) {
map.sourcesContent = sourcesContent as string[];
}
} catch {
this.warn('Failed resolving sources for source map');
}
}
// Try resolving missing sources
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
map.sourcesContent = await Promise.all(
map.sources.map(
(source, index) => map.sourcesContent?.[index] ?? loadFile(source, id).catch(() => null),
),
);

return { code, map };
},
Expand Down
32 changes: 32 additions & 0 deletions src/utils.ts
@@ -0,0 +1,32 @@
import { promises as fs } from 'fs';
import url from 'url';

const innerRegex = /[#@] sourceMappingURL=([^\s'"]*)/;
const sourceMappingUrlRegex = RegExp(
`(?:/\\*(?:\\s*\r?\n(?://)?)?(?:${innerRegex.source})\\s*\\*/|//(?:${innerRegex.source}))\\s*`,
);

export function getSourceMappingURL(code: string) {
const match = sourceMappingUrlRegex.exec(code);
return match?.[1] ?? match?.[2];
}

export function tryParseDataUri(dataUri: string) {
if (!dataUri.startsWith('data:')) {
return undefined;
}

const body = dataUri.slice(dataUri.indexOf(',') + 1);

return Buffer.from(body, 'base64').toString('utf8');
}

export function resolvePath(path: string, base?: string) {
return url.fileURLToPath(
base === undefined ? url.pathToFileURL(path) : new URL(path, url.pathToFileURL(base)),
);
}

export function readFileAsString(path: string) {
return fs.readFile(path, 'utf8');
}

0 comments on commit ff874ab

Please sign in to comment.