Merge pull request #3323 from divmain/master
Source-map support for multiple input source files
This commit is contained in:
commit
4b2a660ee0
@ -44,4 +44,41 @@ name | type | default | description
|
||||
sourceMaps | boolean | `false` | Enable generating source maps
|
||||
sourceMapTarget | string | | The filename of the generated code that the source map will be associated with
|
||||
sourceRoot | string | | A root for all relative URLs in the source map
|
||||
sourceFileName | string | | The filename for the source code (i.e. the code in the `code` argument)
|
||||
sourceFileName | string | | The filename for the source code (i.e. the code in the `code` argument). This will only be used if `code` is a string.
|
||||
|
||||
## AST from Multiple Sources
|
||||
|
||||
In most cases, Babel does a 1:1 transformation of input-file to output-file. However,
|
||||
you may be dealing with AST constructed from multiple sources - JS files, templates, etc.
|
||||
If this is the case, and you want the sourcemaps to reflect the correct sources, you'll need
|
||||
to make some changes to your code.
|
||||
|
||||
First, each node with a `loc` property (which indicates that node's original placement in the
|
||||
source document) must also include a `loc.filename` property, set to the source filename.
|
||||
|
||||
Second, you should pass an object to `generate` as the `code` parameter. Keys
|
||||
should be the source filenames, and values should be the source content.
|
||||
|
||||
Here's an example of what that might look like:
|
||||
|
||||
```js
|
||||
import {parse} from 'babylon';
|
||||
import traverse from "babel-traverse";
|
||||
import generate from 'babel-generator';
|
||||
|
||||
const a = 'var a = 1;';
|
||||
const b = 'var b = 2;';
|
||||
const astA = parse(a, { filename: 'a.js' });
|
||||
const astB = parse(b, { filename: 'b.js' });
|
||||
const ast = {
|
||||
type: 'Program',
|
||||
body: [].concat(astA.body, ast2.body)
|
||||
};
|
||||
|
||||
const { code, map } = generate(ast, { /* options */ }, {
|
||||
'a.js': a,
|
||||
'b.js': b
|
||||
});
|
||||
|
||||
// Sourcemap will point to both a.js and b.js where appropriate.
|
||||
```
|
||||
|
||||
@ -71,7 +71,7 @@ export class CodeGenerator extends Printer {
|
||||
|
||||
static normalizeOptions(code, opts, tokens) {
|
||||
let style = " ";
|
||||
if (code) {
|
||||
if (code && typeof code === "string") {
|
||||
let indent = detectIndent(code).indent;
|
||||
if (indent && indent !== " ") style = indent;
|
||||
}
|
||||
|
||||
@ -17,7 +17,13 @@ export default class SourceMap {
|
||||
sourceRoot: opts.sourceRoot
|
||||
});
|
||||
|
||||
this.map.setSourceContent(opts.sourceFileName, code);
|
||||
if (typeof code === "string") {
|
||||
this.map.setSourceContent(opts.sourceFileName, code);
|
||||
} else if (typeof code === "object") {
|
||||
Object.keys(code).forEach((sourceFileName) => {
|
||||
this.map.setSourceContent(sourceFileName, code[sourceFileName]);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.map = null;
|
||||
}
|
||||
@ -68,7 +74,7 @@ export default class SourceMap {
|
||||
}
|
||||
|
||||
this.last = {
|
||||
source: this.opts.sourceFileName,
|
||||
source: loc.filename || this.opts.sourceFileName,
|
||||
generated: generated,
|
||||
original: original
|
||||
};
|
||||
|
||||
@ -17,6 +17,44 @@ suite("generation", function () {
|
||||
assert.ok(t.VISITOR_KEYS[type], type + " should not exist");
|
||||
});
|
||||
});
|
||||
|
||||
test("multiple sources", function () {
|
||||
var sources = {
|
||||
"a.js": "function hi (msg) { console.log(msg); }\n",
|
||||
"b.js": "hi('hello');\n"
|
||||
};
|
||||
var parsed = _.keys(sources).reduce(function (_parsed, filename) {
|
||||
_parsed[filename] = parse(sources[filename], { sourceFilename: filename });
|
||||
return _parsed;
|
||||
}, {});
|
||||
|
||||
var combinedAst = {
|
||||
"type": "File",
|
||||
"program": {
|
||||
"type": "Program",
|
||||
"sourceType": "module",
|
||||
"body": [].concat(parsed["a.js"].program.body, parsed["b.js"].program.body)
|
||||
}
|
||||
};
|
||||
|
||||
var generated = generate.default(combinedAst, { sourceMaps: true }, sources);
|
||||
|
||||
chai.expect(generated.map).to.deep.equal({
|
||||
version: 3,
|
||||
sources: [ 'a.js', 'b.js' ],
|
||||
names: [],
|
||||
mappings: 'AAAA,SAAS,EAAT,CAAa,GAAb,EAAkB;AAAE,UAAQ,GAAR,CAAY,GAAZ,EAAF;CAAlB;;GCAG,OAAH',
|
||||
sourcesContent: [
|
||||
'function hi (msg) { console.log(msg); }\n',
|
||||
'hi(\'hello\');\n'
|
||||
]
|
||||
}, "sourcemap was incorrectly generated");
|
||||
|
||||
chai.expect(generated.code).to.equal(
|
||||
"function hi(msg) {\n console.log(msg);\n}\n\nhi('hello');",
|
||||
"code was incorrectly generated"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
@ -37,6 +37,8 @@ Significant diversions are expected to occur in the future such as streaming, EB
|
||||
- **sourceType**: Indicate the mode the code should be parsed in. Can be
|
||||
either `"script"` or `"module"`.
|
||||
|
||||
- **sourceFilename**: Correlate output AST nodes with their source filename. Useful when generating code and source maps from the ASTs of multiple input files.
|
||||
|
||||
- **plugins**: Array containing the plugins that you want to enable.
|
||||
|
||||
### Example
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
|
||||
export const defaultOptions: {
|
||||
sourceType: string,
|
||||
sourceFilename: any,
|
||||
allowReturnOutsideFunction: boolean,
|
||||
allowImportExportEverywhere: boolean,
|
||||
allowSuperOutsideMethod: boolean,
|
||||
@ -11,6 +12,8 @@ export const defaultOptions: {
|
||||
} = {
|
||||
// Source type ("script" or "module") for different semantics
|
||||
sourceType: "script",
|
||||
// Source filename.
|
||||
sourceFilename: undefined,
|
||||
// When enabled, a return at the top level is not considered an
|
||||
// error.
|
||||
allowReturnOutsideFunction: false,
|
||||
|
||||
@ -14,6 +14,7 @@ export default class Parser extends Tokenizer {
|
||||
this.isReservedWord = reservedWords[6];
|
||||
this.input = input;
|
||||
this.plugins = this.loadPlugins(this.options.plugins);
|
||||
this.filename = options.sourceFilename;
|
||||
|
||||
// If enabled, skip leading hashbang line.
|
||||
if (this.state.pos === 0 && this.input[0] === "#" && this.input[1] === "!") {
|
||||
|
||||
@ -6,11 +6,12 @@ import { SourceLocation } from "../util/location";
|
||||
const pp = Parser.prototype;
|
||||
|
||||
class Node {
|
||||
constructor(pos?: number, loc?: SourceLocation) {
|
||||
constructor(pos?: number, loc?: SourceLocation, filename?: string) {
|
||||
this.type = "";
|
||||
this.start = pos;
|
||||
this.end = 0;
|
||||
this.loc = new SourceLocation(loc);
|
||||
if (filename) this.loc.filename = filename;
|
||||
}
|
||||
|
||||
type: string;
|
||||
@ -26,11 +27,11 @@ class Node {
|
||||
}
|
||||
|
||||
pp.startNode = function () {
|
||||
return new Node(this.state.start, this.state.startLoc);
|
||||
return new Node(this.state.start, this.state.startLoc, this.filename);
|
||||
};
|
||||
|
||||
pp.startNodeAt = function (pos, loc) {
|
||||
return new Node(pos, loc);
|
||||
return new Node(pos, loc, this.filename);
|
||||
};
|
||||
|
||||
function finishNodeAt(node, type, pos, loc) {
|
||||
|
||||
1
packages/babylon/test/fixtures/core/categorized/filename-specified/actual.js
vendored
Normal file
1
packages/babylon/test/fixtures/core/categorized/filename-specified/actual.js
vendored
Normal file
@ -0,0 +1 @@
|
||||
var node = "shouldHaveFilenameLocProp";
|
||||
109
packages/babylon/test/fixtures/core/categorized/filename-specified/expected.json
vendored
Normal file
109
packages/babylon/test/fixtures/core/categorized/filename-specified/expected.json
vendored
Normal file
@ -0,0 +1,109 @@
|
||||
{
|
||||
"type": "File",
|
||||
"start": 0,
|
||||
"end": 39,
|
||||
"loc": {
|
||||
"start": {
|
||||
"line": 1,
|
||||
"column": 0
|
||||
},
|
||||
"end": {
|
||||
"line": 1,
|
||||
"column": 39
|
||||
},
|
||||
"filename": "path/to/input-file.js"
|
||||
},
|
||||
"program": {
|
||||
"type": "Program",
|
||||
"start": 0,
|
||||
"end": 39,
|
||||
"loc": {
|
||||
"start": {
|
||||
"line": 1,
|
||||
"column": 0
|
||||
},
|
||||
"end": {
|
||||
"line": 1,
|
||||
"column": 39
|
||||
},
|
||||
"filename": "path/to/input-file.js"
|
||||
},
|
||||
"sourceType": "script",
|
||||
"body": [
|
||||
{
|
||||
"type": "VariableDeclaration",
|
||||
"start": 0,
|
||||
"end": 39,
|
||||
"loc": {
|
||||
"start": {
|
||||
"line": 1,
|
||||
"column": 0
|
||||
},
|
||||
"end": {
|
||||
"line": 1,
|
||||
"column": 39
|
||||
},
|
||||
"filename": "path/to/input-file.js"
|
||||
},
|
||||
"declarations": [
|
||||
{
|
||||
"type": "VariableDeclarator",
|
||||
"start": 4,
|
||||
"end": 38,
|
||||
"loc": {
|
||||
"start": {
|
||||
"line": 1,
|
||||
"column": 4
|
||||
},
|
||||
"end": {
|
||||
"line": 1,
|
||||
"column": 38
|
||||
},
|
||||
"filename": "path/to/input-file.js"
|
||||
},
|
||||
"id": {
|
||||
"type": "Identifier",
|
||||
"start": 4,
|
||||
"end": 8,
|
||||
"loc": {
|
||||
"start": {
|
||||
"line": 1,
|
||||
"column": 4
|
||||
},
|
||||
"end": {
|
||||
"line": 1,
|
||||
"column": 8
|
||||
},
|
||||
"filename": "path/to/input-file.js"
|
||||
},
|
||||
"name": "node"
|
||||
},
|
||||
"init": {
|
||||
"type": "StringLiteral",
|
||||
"start": 11,
|
||||
"end": 38,
|
||||
"loc": {
|
||||
"start": {
|
||||
"line": 1,
|
||||
"column": 11
|
||||
},
|
||||
"end": {
|
||||
"line": 1,
|
||||
"column": 38
|
||||
},
|
||||
"filename": "path/to/input-file.js"
|
||||
},
|
||||
"extra": {
|
||||
"rawValue": "shouldHaveFilenameLocProp",
|
||||
"raw": "\"shouldHaveFilenameLocProp\""
|
||||
},
|
||||
"value": "shouldHaveFilenameLocProp"
|
||||
}
|
||||
}
|
||||
],
|
||||
"kind": "var"
|
||||
}
|
||||
],
|
||||
"directives": []
|
||||
}
|
||||
}
|
||||
3
packages/babylon/test/fixtures/core/categorized/filename-specified/options.json
vendored
Normal file
3
packages/babylon/test/fixtures/core/categorized/filename-specified/options.json
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"sourceFilename": "path/to/input-file.js"
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user