diff options
author | 2022-03-05 19:15:25 -0800 | |
---|---|---|
committer | 2022-03-05 19:33:25 -0800 | |
commit | 093807391a9563ad36c2b04a286da23d09fad835 (patch) | |
tree | 5485bda684d4f04b6a5af9a12f509fd47e906be1 | |
parent | 18c923596a5b6480fa5a841bd0a6adfdb365b831 (diff) | |
download | bun-093807391a9563ad36c2b04a286da23d09fad835.tar.gz bun-093807391a9563ad36c2b04a286da23d09fad835.tar.zst bun-093807391a9563ad36c2b04a286da23d09fad835.zip |
[JS Parser] dot property shorthand for JSX
This is a non-standard backwards-compatible feature that I suspect other tooling will soon adopt (and expect to help other tooling adopt it)
```jsx
var hello = {hi: 'yo'};
export const Foo = () => <Bar {hello.hi} />
```
Desugars into:
```jsx
var hello = {hi: 'yo'};
export const Foo = () => <Bar hi={hello.hi} />
```
This works with defines and macros too.
```jsx
export const Foo = () => <Bar {process.env.NODE_ENV} />
```
```jsx
export const Foo = () => <Bar NODE_ENV="development" />
```
-rw-r--r-- | README.md | 2 | ||||
-rw-r--r-- | integration/bunjs-only-snippets/transpiler.test.js | 53 | ||||
-rw-r--r-- | src/js_parser/js_parser.zig | 37 |
3 files changed, 82 insertions, 10 deletions
@@ -261,7 +261,7 @@ bun is a project with incredibly large scope, and it’s early days. | [Hash components for Fast Refresh](https://github.com/Jarred-Sumner/bun/issues/18) | JSX Transpiler | | Source Maps | JavaScript | | Source Maps | CSS | -| JavaScript Minifier | JavaScript | +| JavaScript Minifier | JS Transpiler | | CSS Minifier | CSS | | CSS Parser (it only bundles) | CSS | | Tree-shaking | JavaScript | diff --git a/integration/bunjs-only-snippets/transpiler.test.js b/integration/bunjs-only-snippets/transpiler.test.js index 5553c1cc6..1162c65a7 100644 --- a/integration/bunjs-only-snippets/transpiler.test.js +++ b/integration/bunjs-only-snippets/transpiler.test.js @@ -50,6 +50,9 @@ describe("Bun.Transpiler", () => { it("JSX", () => { var bun = new Bun.Transpiler({ loader: "jsx", + define: { + "process.env.NODE_ENV": JSON.stringify("development"), + }, }); expect(bun.transformSync("export var foo = <div foo />")).toBe( `export var foo = jsx("div", { @@ -69,12 +72,62 @@ describe("Bun.Transpiler", () => { }, undefined, false, undefined, this); ` ); + expect(bun.transformSync("export var hi = <div {foo} />")).toBe( `export var hi = jsx("div", { foo }, undefined, false, undefined, this); ` ); + expect(bun.transformSync("export var hi = <div {foo.bar.baz} />")).toBe( + `export var hi = jsx("div", { + baz: foo.bar.baz +}, undefined, false, undefined, this); +` + ); + expect(bun.transformSync("export var hi = <div {foo?.bar?.baz} />")).toBe( + `export var hi = jsx("div", { + baz: foo?.bar?.baz +}, undefined, false, undefined, this); +` + ); + expect( + bun.transformSync("export var hi = <div {foo['baz'].bar?.baz} />") + ).toBe( + `export var hi = jsx("div", { + baz: foo["baz"].bar?.baz +}, undefined, false, undefined, this); +` + ); + + // cursed + expect( + bun.transformSync( + "export var hi = <div {foo[{name: () => true}.name].hi} />" + ) + ).toBe( + `export var hi = jsx("div", { + hi: foo[{ name: () => true }.name].hi +}, undefined, false, undefined, this); +` + ); + expect( + bun.transformSync("export var hi = <Foo {process.env.NODE_ENV} />") + ).toBe( + `export var hi = jsx(Foo, { + NODE_ENV: "development" +}, undefined, false, undefined, this); +` + ); + + expect( + bun.transformSync("export var hi = <div {foo['baz'].bar?.baz} />") + ).toBe( + `export var hi = jsx("div", { + baz: foo["baz"].bar?.baz +}, undefined, false, undefined, this); +` + ); try { bun.transformSync("export var hi = <div {foo}={foo}= />"); throw new Error("Expected error"); diff --git a/src/js_parser/js_parser.zig b/src/js_parser/js_parser.zig index b299b4498..afe429e9e 100644 --- a/src/js_parser/js_parser.zig +++ b/src/js_parser/js_parser.zig @@ -11356,16 +11356,35 @@ fn NewParser_( // -> // <div foo={foo} /> T.t_identifier => { - const name = p.lexer.identifier; - if (strings.eqlComptime(name, "let")) { - p.lexer.addError(p.source, p.lexer.loc(), "\"let\" is not allowed with JSX prop punning") catch unreachable; - } - const key = p.e(E.String{ .utf8 = name }, p.lexer.loc()); - const ref = p.storeNameInRef(name) catch unreachable; - const value = p.e(E.Identifier{ .ref = ref }, key.loc); - try p.lexer.next(); + // we need to figure out what the key they mean is + // to do that, we must determine the key name + const expr = try p.parseExpr(Level.lowest); + + const key = brk: { + switch (expr.data) { + .e_import_identifier => |ident| { + break :brk p.e(E.String{ .utf8 = p.loadNameFromRef(ident.ref) }, expr.loc); + }, + .e_identifier => |ident| { + break :brk p.e(E.String{ .utf8 = p.loadNameFromRef(ident.ref) }, expr.loc); + }, + .e_dot => |dot| { + break :brk p.e(E.String{ .utf8 = dot.name }, dot.name_loc); + }, + .e_index => |index| { + if (index.index.data == .e_string) { + break :brk index.index; + } + }, + else => {}, + } + + // If we get here, it's invalid + try p.log.addError(p.source, expr.loc, "Invalid JSX prop shorthand, must be identifier, dot or string"); + return error.SyntaxError; + }; - try props.append(G.Property{ .value = value, .key = key, .kind = .normal }); + try props.append(G.Property{ .value = expr, .key = key, .kind = .normal }); }, // This implements // <div {"foo"} /> |