Skip to content

Commit

Permalink
Add basic support for source maps
Browse files Browse the repository at this point in the history
Progress toward #223

This is a first pass that's meant to be simple, flexible, and fairly
unopinionated. Notably, it does NOT yet compute mappings within each line, and
instead just maps each line to the corresponding line in the source. I'll
implement smarter mappings in the future, possibly as an option if it ends up
hurting performance.

A quick benchmark shows that enabling source maps increases total running time
by about 25%, even with this simple approach. Hopefully that can be optimized in
the future.

Another possible to-do item is for Sucrase to accept a source map which it then
modifies. However, this can hopefully be handled by other tools that compose
source maps.

There are also various tweaks that may make this more convenient, depending on
use case.
* Optionally include the original source code in `sourcesContent` in the source
  map.
* Optionally append a sourceMappingURL comment to the end of the source code.
* Optionally return the source map as a string instead of an object.
* Better control over the sourceRoot and file paths specified in the source map.

All of these use cases can be satisfied with just one or two lines of code after
running Sucrase, though, so none seem super critical.

Some docs of other tools emitting source maps, which I used for inspiration:
https://babeljs.io/docs/en/babel-core
https://www.typescriptlang.org/docs/handbook/compiler-options.html
  • Loading branch information
alangpierce committed Jun 17, 2018
1 parent 61613d3 commit b720e7e
Show file tree
Hide file tree
Showing 5 changed files with 71 additions and 5 deletions.
3 changes: 2 additions & 1 deletion package.json
Expand Up @@ -70,7 +70,8 @@
"commander": "^2.12.2",
"lines-and-columns": "^1.1.6",
"mz": "^2.7.0",
"pirates": "^3.0.2"
"pirates": "^3.0.2",
"source-map": "^0.7.3"
},
"engines": {
"node": ">=8"
Expand Down
22 changes: 22 additions & 0 deletions src/computeSourceMap.ts
@@ -0,0 +1,22 @@
import sourceMap, {RawSourceMap} from "source-map";

/**
* Generate a simple source map indicating that each line maps directly to the original line.
*/
export default function computeSourceMap(code: string, filePath: string): RawSourceMap {
const mapGenerator = new sourceMap.SourceMapGenerator({file: filePath});
let numLines = 0;
for (let i = 0; i < code.length; i++) {
if (code[i] === "\n") {
numLines++;
}
}
for (let line = 1; line <= numLines; line++) {
mapGenerator.addMapping({
source: filePath,
generated: {line, column: 0},
original: {line, column: 0},
});
}
return mapGenerator.toJSON();
}
35 changes: 32 additions & 3 deletions src/index.ts
@@ -1,4 +1,6 @@
import {RawSourceMap} from "source-map";
import CJSImportProcessor from "./CJSImportProcessor";
import computeSourceMap from "./computeSourceMap";
import identifyShadowedGlobals from "./identifyShadowedGlobals";
import NameManager from "./NameManager";
import {parse} from "./parser";
Expand All @@ -11,15 +13,37 @@ export type Transform = "jsx" | "typescript" | "flow" | "imports";

export interface Options {
transforms: Array<Transform>;
/**
* If specified, function name to use in place of React.createClass when compiling JSX.
*/
jsxPragma?: string;
/**
* If specified, function name to use in place of React.Fragment when compiling JSX.
*/
jsxFragmentPragma?: string;
/**
* If true, replicate the import behavior of TypeScript's esModuleInterop: false.
*/
enableLegacyTypeScriptModuleInterop?: boolean;
/**
* If true, replicate the import behavior Babel 5 and babel-plugin-add-module-exports.
*/
enableLegacyBabel5ModuleInterop?: boolean;
/**
* If true, we also return a RawSourceMap object alongside the code. Currently, source maps simply
* map each line to the original line without any mappings within lines, since Sucrase preserves
* line numbers. filePath must be specified if this option is enabled.
*/
computeSourceMap?: boolean;
/**
* File path to use in error messages, React display names, and source maps.
*/
filePath?: string;
}

export interface TransformResult {
code: string;
sourceMap?: RawSourceMap;
}

export interface SucraseContext {
Expand All @@ -43,9 +67,14 @@ export function transform(code: string, options: Options): TransformResult {
Boolean(options.enableLegacyBabel5ModuleInterop),
options,
);
return {
code: transformer.transform(),
};
let result: TransformResult = {code: transformer.transform()};
if (options.computeSourceMap) {
if (!options.filePath) {
throw new Error("filePath must be specified when generating a source map.");
}
result = {...result, sourceMap: computeSourceMap(result.code, options.filePath)};
}
return result;
} catch (e) {
if (options.filePath) {
e.message = `Error transforming ${options.filePath}: ${e.message}`;
Expand Down
12 changes: 11 additions & 1 deletion src/register.ts
@@ -1,10 +1,20 @@
// @ts-ignore: no types available.
import * as pirates from "pirates";

import {Options, transform} from "./index";

export function addHook(extension: string, options: Options): void {
pirates.addHook(
(code: string, filePath: string): string => transform(code, {...options, filePath}).code,
(code: string, filePath: string): string => {
const {code: transformedCode, sourceMap} = transform(code, {
...options,
computeSourceMap: true,
filePath,
});
const mapBase64 = Buffer.from(JSON.stringify(sourceMap)).toString("base64");
const suffix = `//# sourceMappingURL=data:application/json;charset=utf-8;base64,${mapBase64}`;
return `${transformedCode}\n${suffix}`;
},
{exts: [extension]},
);
}
Expand Down
4 changes: 4 additions & 0 deletions yarn.lock
Expand Up @@ -2014,6 +2014,10 @@ source-map@^0.5.0:
version "0.5.7"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"

source-map@^0.7.3:
version "0.7.3"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383"

spdx-correct@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.0.0.tgz#05a5b4d7153a195bc92c3c425b69f3b2a9524c82"
Expand Down

0 comments on commit b720e7e

Please sign in to comment.