alangpierce / sucrase Goto Github PK
View Code? Open in Web Editor NEWSuper-fast alternative to Babel for when you can target modern JS runtimes
Home Page: https://sucrase.io
License: MIT License
Super-fast alternative to Babel for when you can target modern JS runtimes
Home Page: https://sucrase.io
License: MIT License
It would be interesting to see how spec compliant the transformed code is. https://github.com/kangax/compat-table has a set of tests, not sure how much work it would be to adapt them to run with sucrase.
Hey Alan, this is a really cool project. Thanks for your hard work!
As you know, a large part of Babel's appeal is its extensibility. Although sucrase avoids that bucket of worms (and for good reason), its users can still harness the power of plugins by using nebu after sucrase. Nebu is still very young, but I think these two projects will complement each other greatly. It uses magic-string (the same library that Bublé uses) to skip the AST-to-code generation phase in favor of simple string splits/joins (but with sourcemap support).
Cheers!
You said you've used magic-string before. Have you thought about using it in sucrase? (for sourcemap support)
Currently, class fields become assignments in the constructor. The number of lines of the class field declaration is preserved, but the constructor start might have newlines, causing duplicate newlines and more overall lines in the output than the input. In addition, this makes it impossible to set a breakpoint for bound handles methods e.g. in React components, since the method body moves to the constructor.
For example, this:
class Foo extends React.Component {
componentDidMount() {
console.log('Mounted!');
}
handleClick = () => {
console.log('Clicked!');
}
render() {
return <span onClick={this.handleClick} />;
}
}
Becomes this:
const _jsxFileName = "sample.js";
class Foo extends React.Component {constructor(...args) { super(...args); this.handleClick = () => {
console.log('Clicked!');
}; }
componentDidMount() {
console.log('Mounted!');
}
render() {
return React.createElement('span', { onClick: this.handleClick, __self: this, __source: {fileName: _jsxFileName, lineNumber: 10}} );
}
}
I think a better approach, which isn't quite as correct but helps the dev experience significantly, is to do something like this:
const _jsxFileName = "sample.js";
class Foo extends React.Component {constructor(...args) { super(...args); this.__setField1(); }
componentDidMount() {
console.log('Mounted!');
}
__setField1() {this.handleClick = () => {
console.log('Clicked!');
};}
render() {
return React.createElement('span', { onClick: this.handleClick, __self: this, __source: {fileName: _jsxFileName, lineNumber: 10}} );
}
}
That way breakpoints will work on the "Clicked!" line and every line below it without complicating source maps.
Design thoughts:
static __setStaticField1() { this.displayName = 'Foo'; }
with Foo.__setStaticField1()
after the class (which we already do).Sucrase chokes on the following input, where tsc
does not:
// src/foo.ts
export class Foo {
[name: string]: any;
}
$ sucrase src -d out --transforms typescript
src/foo.ts -> out/foo.js
Error: Error transforming src/foo.ts: Expected class context ID on computed name open bracket.
at getNameCode (/Users/alexr/.config/yarn/global/node_modules/sucrase/dist/util/getClassInfo.js:232:13)
at getClassInfo (/Users/alexr/.config/yarn/global/node_modules/sucrase/dist/util/getClassInfo.js:69:24)
at RootTransformer.processClass (/Users/alexr/.config/yarn/global/node_modules/sucrase/dist/transformers/RootTransformer.js:154:50)
at RootTransformer.processToken (/Users/alexr/.config/yarn/global/node_modules/sucrase/dist/transformers/RootTransformer.js:129:12)
at RootTransformer.processBalancedCode (/Users/alexr/.config/yarn/global/node_modules/sucrase/dist/transformers/RootTransformer.js:123:12)
at RootTransformer.transform (/Users/alexr/.config/yarn/global/node_modules/sucrase/dist/transformers/RootTransformer.js:78:10)
at transform (/Users/alexr/.config/yarn/global/node_modules/sucrase/dist/index.js:47:25)
at buildFile (/Users/alexr/.config/yarn/global/node_modules/sucrase/dist/cli.js:84:48)
at <anonymous>
Not sure if you're aware, but JSX is used for other projects too, such as virtual-dom, snabbdom, surplus and others. It would definitely be worthwhile making the element factory function configurable, rather than hardcoding it to React.createElement.
// src/foo.ts
export const Foo = {
bar<T>() {
return;
}
};
$ sucrase src -d out --transforms typescript
src/foo.ts -> out/foo.js
Error: Error transforming src/foo.ts: TODO
at tsStartParseObjPropValue (/Users/alexr/.config/yarn/global/node_modules/sucrase/dist/parser/plugins/typescript.js:1258:11)
at parseObjPropValue (/Users/alexr/.config/yarn/global/node_modules/sucrase/dist/parser/traverser/expression.js:833:46)
at parseObj (/Users/alexr/.config/yarn/global/node_modules/sucrase/dist/parser/traverser/expression.js:756:5)
at parseExprAtom (/Users/alexr/.config/yarn/global/node_modules/sucrase/dist/parser/traverser/expression.js:487:7)
at parseExprSubscripts (/Users/alexr/.config/yarn/global/node_modules/sucrase/dist/parser/traverser/expression.js:258:20)
at parseMaybeUnary (/Users/alexr/.config/yarn/global/node_modules/sucrase/dist/parser/traverser/expression.js:244:20)
at parseExprOps (/Users/alexr/.config/yarn/global/node_modules/sucrase/dist/parser/traverser/expression.js:184:20)
at parseMaybeConditional (/Users/alexr/.config/yarn/global/node_modules/sucrase/dist/parser/traverser/expression.js:157:20)
at baseParseMaybeAssign (/Users/alexr/.config/yarn/global/node_modules/sucrase/dist/parser/traverser/expression.js:141:20)
at tsParseMaybeAssign (/Users/alexr/.config/yarn/global/node_modules/sucrase/dist/parser/plugins/typescript.js:1306:49)
Curiously, Sucrase behavior is compatible with your sample's Babel stack.
[email protected] reports the following warning when using latest sucrase@^2.2.0:
"@sucrase/[email protected]" has incorrect peer dependency "sucrase@^1.12.1"
Maybe allowing both versions ^1.12.1 and ^2 in package.json would help. The range can be specified either as ^1.12.1 || ^2
, or as 1.12.1 - 2
, try it at the semver check:
"peerDependencies": {
"sucrase": "1.12.1 - 2"
},
"devDependencies": {
"@types/loader-utils": "^1.1.1",
"sucrase": "1.12.1 - 2"
},
It's forbidden in ES, but allowed in TS, so prettier inserts the comma, see prettier/prettier#3586 and prettier/prettier#3528.
Happens with latest [email protected].
ERROR in ./src/components/X.tsx
Module build failed (from ../node_modules/@sucrase/webpack-loader/dist/index.js):
SyntaxError: Error transforming ./src/components/X.tsx: A trailing comma is not permitted after the rest element (1:1032)
at raise (./node_modules/sucrase/dist/parser/traverser/base.js:24:15)
at unexpected (./node_modules/sucrase/dist/parser/traverser/util.js:63:25)
at parseObj (./node_modules/sucrase/dist/parser/traverser/expression.js:719:32)
at parseBindingAtom (./node_modules/sucrase/dist/parser/traverser/lval.js:63:32)
at parseMaybeDefault (./node_modules/sucrase/dist/parser/traverser/lval.js:131:5)
at parseAssignableListItem (./node_modules/sucrase/dist/parser/traverser/lval.js:115:3)
at parseBindingList (./node_modules/sucrase/dist/parser/traverser/lval.js:104:7)
at parseFunctionParams (./node_modules/sucrase/dist/parser/traverser/statement.js:579:30)
at parseParenAndDistinguishExpression (./node_modules/sucrase/dist/parser/traverser/expression.js:608:42)
at parseExprAtom (./node_modules/sucrase/dist/parser/traverser/expression.js:477:24)
at parseExprSubscripts (./node_modules/sucrase/dist/parser/traverser/expression.js:258:20)
at parseMaybeUnary (./node_modules/sucrase/dist/parser/traverser/expression.js:244:20)
at parseExprOps (./node_modules/sucrase/dist/parser/traverser/expression.js:184:20)
at parseMaybeConditional (./node_modules/sucrase/dist/parser/traverser/expression.js:157:20)
at baseParseMaybeAssign (./node_modules/sucrase/dist/parser/traverser/expression.js:141:20)
at tsParseMaybeAssign (./node_modules/sucrase/dist/parser/plugins/typescript.js:1306:49)
This code when compiled with the exports transform:
export function* foo() {}
crashes with this error:
Error transforming sample.tsx: Expected identifier for exported function name.
This code:
export const {a, b} = c;
crashes with this error:
Error transforming sample.tsx: Expected a regular identifier after export var/let/const.
What about typescript module resolution?
Something like as https://www.npmjs.com/package/tsconfig-paths
?
Sucrase ignores malformed JSX input such as
<div>
</vid>
whereas Babel would catch this as an error. This could lead to some weird parsing and hard to find human error whilst developing.
Is there any plan to implement a in memory transpiling cli, similar to babel-node ?
This input hits an infinite loop:
const x = <
At the start of my test file I have:
jest.mock("../util/active-tracker.js");
Inside the tests is:
const {
default: ActiveTracker,
} = await import("../util/active-tracker.js");
const mockTracker = ActiveTracker.mock.instances[0];
mockTracker.steal.mockClear();
jest runs into an exception on the last line because mock.instances
is empty.
My jest config:
"jest": {
"globals": {
"SNAPSHOT_INLINE_APHRODITE": true
},
"testRegex": ".test\\.jsx?$",
"setupTestFrameworkScriptFile": "./test-setup.js",
"transform": {
"^.+\\.jsx?$": "@sucrase/jest-plugin"
},
"collectCoverageFrom": [
"packages/**/*.js",
"!packages/**/dist/index.js",
"!<rootDir>/node_modules/",
"!packages/**/node_modules/"
]
},
ReactDOM.render(<div className={css(styles.header)}>
<h1>{foobar}</h1>
</div>, container);
compiles to
ReactDOM.render(React.createElement('div', { className: (0, _aphrodite.css)(styles.header), __self: this, __source: {fileName: _jsxFileName, lineNumber: 16}}
, React.createElement('h1', {__self: this, __source: {fileName: _jsxFileName, lineNumber: 17}}, _foobar2.default)
), container);
I'm curious about what __self
and __source
are used for.
Hope it support transform-runtime.
Example:
class A {
b = 1;
}
becomes this:
"use strict";class A {constructor() { this.b = 1; }
;
}
This behavior changed in Sucrase 3.2.0 ( #271 ), and it caused Sucrase 3.2.1 to be compiled in a way that broke the webpack build in the sucrase.io website. For some reason, it causes webpack 3.5.1 to silently stop about 15 seconds in with no error message. For now, it seems best to change the behavior to avoid ever emitting a lone semicolon.
sucrase/src/transformers/RootTransformer.ts
Lines 188 to 191 in 15debf7
Could we make those optional? There is partial implementation in V8 behind a flag.
Thanks 👍
I understand that sucrase-babylon
is a fork, but you still need to include the LICENSE
and AUTHORS
file.
Empty fragments and fragments without whitespace do not work
This code:
console.log('foo');
import './bar';
gets compiled to this code in Sucrase:
"use strict";
console.log('foo');
require('./bar');
This matches the tsc output, but disagrees with the Babel output:
"use strict";
require("./bar");
console.log('foo');
Babel is more correct in that imports are always evaluated first, regardless of their position in the file. Still, it's unclear what Sucrase should do here. Some thoughts:
My current thinking is that Sucrase won't implement the reordering, but there should be a place for this nuance to be documented. I've also had thoughts of making an ESLint config/rule/plugin that flags any possible issues like this, so this would certainly be a good candidate for that. But if anyone feels that the reordering transform would be particularly useful for them, feel free to comment here.
This code in the TypeScript transform should NOT import the ./a
module since it's not used (meaning "it's only used as a type" is vacuously true).
import a from './a';
function foo() {
let a;
a();
}
Instead, Sucrase gives this output:
"use strict"; function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var _a = require('./a'); var _a2 = _interopRequireDefault(_a);
function foo() {
let a;
a();
}
Sucrase should notice that the a()
usage refers to the shadowed a
declaration and not to the import, and thus not count it as a runtime usage of a
.
Mentioned by @Rich-Harris in #221.
Currently, Sucrase preserves line numbers, which gets many of the benefits of source maps (like reliable stack traces when using sucrase/register
), but ideally it would emit real source maps. If we just care about lines, I think it would be a trivial source map that maps each line to itself. Mapping tokens within each line (which I think is useful for debuggers) should also be doable, but would need to be more integrated into the code generator.
Example:
export const {a, b} = c;
https://sucrase.io/#code=export%20const%20%7Ba%2C%20b%7D%20%3D%20c%3B
It gives this error:
Error transforming sample.tsx: Expected a regular identifier after export var/let/const.
When attempting to test out the transpiling via the online tool, I found that it would throw an error when attempting to process class properties; Expected rhsEndIndex
on class field assignment.
import React, { Component } from 'react';
import PropTypes from 'prop-types';
export default class Paginate extends Component {
static propTypes = {
count: PropTypes.number.isRequired
}
render () {
const {
props: { count, size }
} = this;
return (
<section className='paginate'>
<div>{count} total row(s)</div>
</section>
);
}
}
I've had a few requests from others about how they can help out. I've been keeping a checklist of possible future tasks, and figured it would be good to make that public to make it easier for others to contribute.
Feel free to stop by and chat in the gitter room, which I just created. Generally my thought is that this can be a big checklist of possible things to do, and as work starts on them, they can be "promoted" to real issues with individual discussions/assignees/etc. I'm sure there's plenty of context to be shared about most of these issues, so feel free to chat if you want to take one of them on.
Currently, I'm focused on performance, and want to see how high I can get the N in "Sucrase is N times faster than Babel". But there's certainly lots of other stuff to do as well.
export let x; [x] = arr;
. Babel and TypeScript are both incomplete here, and ideally we just mimic one or the other.import
statements should be hoisted to the top of the file, but they aren't right now.import
and export
. Ideally we exactly copy either Babel or TypeScript, based on the config.export default
React class. We could add a filename arg to the Sucrase API to support this, although it's maybe obscure enough that the API change isn't worth it.noImplicitThis
in the TypeScript config. Implicit any
for this
has bit me a few times, but there are enough pre-existing errors that it's hard to enable.self-build
task, but we still use TypeScript for the real build.+
.When trying to run sucrase with the webpack-loader
and webpack-object-rest-spread-plugin
I get the following error:
ERROR in ./src/Main.jsx
Module parse failed: this.applyPluginsBailResult is not a function
You may need an appropriate loader to handle this file type.
TypeError: this.applyPluginsBailResult is not a function
Versions used:
webpack: 4.8.3
sucrase: 2.2.0
@sucrase/webpack-loader: 1.0.2,
@sucrase/webpack-object-rest-spread-plugin: 1.0.0
It'd be nice to have a "quiet" mode like -q
in the babel cli where the print statements during sucrase' compilation are not displayed in the terminal.
Given that part of the goal of this project is to have a small amount of highly-reliable code, seems like having really good coverage and enforcing it is a good way to showcase that and keep the project honest. Current stats from running in WebStorm show 82% coverage, so seems like there could be some improvement. Seems like https://coveralls.io/ might be the best way to do that.
We have an archaic build system that uses Nashorn (part of Java) to compile our JSX to to JS and then after that we run Closure Compiler on it. We initially started off using JSXTransformer and then migrated to babel for our jsx transformer. Babel is super slow (as you know). I'm trying to replace Babel with Sucrase as the jsx to js transformer and it was working fine up until 3.x. The code for this is at https://github.com/arv/sucrase-standalone.
Would you be interested in having an es5 stand alone build as part of the official sucrase package?
I was able to prototype a hacked-together variant of the sucrase-babylon parser and get it working in AssemblyScript. I ran it through a benchmark of compiling a ~100 line file over and over and counting the number of tokens, and it gave the correct answer both when run from js and when run from wasm. Here were the results:
# iterations JS time taken wasm time taken
1 10.065ms 0.192ms
10 19.173ms 1.004ms
100 72.770ms 9.074ms
1000 182.896ms 72.428ms
10000 594.523ms 899.885ms
100000 4180.687ms 8830.265ms
As expected, the wasm running time grows linearly and the JS running time grows sub-linearly at least in small numbers, since V8 has more and more time to optimize the code and wasm optimizations are all at compile time. The wasm code is 50x faster on a very small case, about equal for about 50,000 lines of code, and 2x slower in the extremes. So it looks like, at least for now, V8 can do a really good job at optimization when given long enough, enough to outperform the equivalent code compiled through AssemblyScript.
Sadly, this means webassembly isn't a slam dunk like I was hoping, but it still seems promising. Probably there's room for improvement in the use of webassembly (tricks that would be impossible to implement in plain JS and unreasonable for V8 to infer), and AssemblyScript and WebAssembly both may improve over time, so there's reason to believe webassembly will eventually win out for long-running programs after all. This test case may also be beneficial for V8 since it's processing exactly the same dataset, so the branches and call statistics will be the same every time. This also only handles about half of the sucrase codebase, and the other half (determining and performing the string replacements) may run better in wasm compared with JS.
My plan now is to get Sucrase into a state where it can both run in JS and be compiled by AssemblyScript to webassembly. That should at least make it a good test bed for this stuff.
Another good thing to prototype would be porting the parser to Rust and/or C++ and see how it performs as native code vs wasm, and see how that wasm compares with the wasm produced by AssemblyScript.
Sucrase looks promising for a faster dev experience. But what about support form HMR with react?
The popular solution is to use react-hot-loader but it's a babel plugin... Any existing solution to support this feature with Sucrase?
I was trying to use sucrase to transform a library that makes use of kebab-case props (e.g. aria-hidden
). Sucrase produces invalid JavaScript when trying to transform that property.
Example source:
<Foo aria-hidden />
Sucrase output:
"use strict";const _jsxFileName = "sample.tsx";React.createElement(Foo, { aria-hidden: true, __self: this, __source: {fileName: _jsxFileName, lineNumber: 1}} )
The invalid output is the unquoted object property since aria-hidden
is not a valid identifier.
For reference, here is the Babel output:
"use strict";
var _jsxFileName = "sample.tsx";
React.createElement(Foo, {
"aria-hidden": true,
__source: {
fileName: _jsxFileName,
lineNumber: 1
},
__self: void 0
});
It looks like babel-parser now supports new TypeScript 2.9 syntax and has some bug fixes, so it would be good to pull in those changes. It's not quite up-to-date with TS 3.0 syntax, but hopefully will be at some point.
_interopRequireWildcard
and _interopRequireDefault
are included regardless of whether an import
statement exists in the source. I would like to avoid this.
Also, I would like the option to exclude those functions entirely, because I will include them manually after concatenating sources.
Example module [email protected], see JedWatson/classnames#105.
In typescript, we use import * as cn from 'classnames'
to import from module without default export.
This gets transpiled using babel's _interopRequireDefault
to
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var _classnames = __webpack_require__(/*! classnames */ "../node_modules/classnames/index.js");
var _classnames2 = _interopRequireDefault(_classnames);
resulting in cn
equal to { default: classNames() }
instead of classNames()
function, and throwing Uncaught TypeError: cn is not a function
.
Is there any way to fix it and handle such exports correctly?
Line 7 in 15debf7
Currently Node (with --experimental-modules
) can decide to either load '.mjs' or '.js' depending on the mode (ie importing into ES or importing/requiring into cjs) however this only works if the "main" in package.json
does not specify an explicit extension.
{
"main": "dist/index"
}
To the best of my knowledge, omitting the extension should not have any side effects in earlier versions of Node, but it is worth verifying on your end if you are willing to consider making this minor change.
Thanks
transform('<a/>', {transforms: ['jsx']});
generates code that has __self
and __source
props. This is normally only enabled when you use preset-react with development to true.
Input code:
class A {
n?: number = 3;
}
Produces this code, which has a syntax error:
"use strict";
class A {constructor() { this.: = 3; }
}
This code:
export const f = () => {}
becomes this with the imports transform:
exports.f = () => {}
This is mostly correct, but stops JS from inferring the name as 'f'
from the syntactic position, as described in https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/name#Inferred_function_names . tsc has the same issue, but Babel transpiles in a way that respects the inferred name, so Sucrase should as well.
This TypeScript code crashes both Babel and Sucrase, but works in tsc:
const f: ({a}: {a: number}) => void = () => {};
A workaround is to simply avoid the destructure, like defining the type as (args: {a: number}) => void
, since the destructure doesn't do anything.
displayName works with variable assignment and AssignmentExpression when the LHS is an identifier expression but not when the LHS is a MemberExpression.
The following fails:
const x = <a b=<c/> d="e"/>;
See JSXAttributeValue at https://facebook.github.io/jsx/
It is possible to remove // @flow
and /* @flow */
pragmas when transform flow
is enabled. Such pragmas is a part of Flow and hence should be removed.
It's difficult, but it would be absolute awesome if Flow's suppress_comment
(.flowconfig) pragmas for current project are also removed as well.
This is more of a cosmetic issue, but certainly looks weird.
This code:
interface A {
// This is a comment.
}
Becomes this code:
"use strict";
//Thisisacomment.
https://sucrase.io/#code=interface%20A%20%7B%0A%20%20%2F%2F%20This%20is%20a%20comment.%0A%7D%0A
This happens because we remove all non-newline whitespace between tokens in some situations. It looks like it may be best to either keep all whitespace or remove all non-newline characters (assuming the only code between tokens is comments and that comments are always safe to remove).
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.