Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix #334: support automatic JSX runtime #2349

Merged
merged 24 commits into from
Jul 28, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions internal/bundler/snapshots/snapshots_tsconfig.txt
Original file line number Diff line number Diff line change
Expand Up @@ -339,18 +339,18 @@ console.log(/* @__PURE__ */ jsxDEV(Fragment, {
/* @__PURE__ */ jsxDEV("div", {}, void 0, false, {
fileName: "Users/user/project/entry.tsx",
lineNumber: 2,
columnNumber: 18
columnNumber: 19
}, this),
/* @__PURE__ */ jsxDEV("div", {}, void 0, false, {
fileName: "Users/user/project/entry.tsx",
lineNumber: 2,
columnNumber: 24
columnNumber: 25
}, this)
]
}, void 0, true, {
fileName: "Users/user/project/entry.tsx",
lineNumber: 2,
columnNumber: 16
columnNumber: 17
}, this));
import {
Fragment,
Expand All @@ -366,18 +366,18 @@ console.log(/* @__PURE__ */ jsxDEV(Fragment, {
/* @__PURE__ */ jsxDEV("div", {}, void 0, false, {
fileName: "Users/user/project/entry.tsx",
lineNumber: 2,
columnNumber: 18
columnNumber: 19
}, this),
/* @__PURE__ */ jsxDEV("div", {}, void 0, false, {
fileName: "Users/user/project/entry.tsx",
lineNumber: 2,
columnNumber: 24
columnNumber: 25
}, this)
]
}, void 0, true, {
fileName: "Users/user/project/entry.tsx",
lineNumber: 2,
columnNumber: 16
columnNumber: 17
}, this));
import {
Fragment,
Expand Down
44 changes: 39 additions & 5 deletions internal/js_parser/js_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,11 @@ type parser struct {
tempRefCount int
topLevelTempRefCount int

// We need to scan over the source contents to recover the line and column offsets
jsxSourceLoc int
jsxSourceLine int
jsxSourceColumn int

exportsRef js_ast.Ref
requireRef js_ast.Ref
moduleRef js_ast.Ref
Expand Down Expand Up @@ -12115,6 +12120,38 @@ func (p *parser) visitExprInOut(expr js_ast.Expr, in exprIn) (js_ast.Expr, exprO

case *js_ast.EJSXElement:
propsLoc := expr.Loc

// Resolving the location index to a specific line and column in
// development mode is not too expensive because we seek from the
// previous JSX element. It amounts to at most a single additional
// scan over the source code. Note that this has to happen before
// we visit anything about this JSX element to make sure that we
// only ever need to scan forward, not backward.
var jsxSourceLine int
var jsxSourceColumn int
if p.options.jsx.Development && p.options.jsx.AutomaticRuntime {
for p.jsxSourceLoc < int(propsLoc.Start) {
r, size := utf8.DecodeRuneInString(p.source.Contents[p.jsxSourceLoc:])
p.jsxSourceLoc += size
if r == '\n' || r == '\r' || r == '\u2028' || r == '\u2029' {
if r == '\r' && p.jsxSourceLoc < len(p.source.Contents) && p.source.Contents[p.jsxSourceLoc] == '\n' {
p.jsxSourceLoc++ // Handle Windows-style CRLF newlines
}
p.jsxSourceLine++
p.jsxSourceColumn = 0
} else {
// Babel and TypeScript count columns in UTF-16 code units
if r < 0xFFFF {
p.jsxSourceColumn++
} else {
p.jsxSourceColumn += 2
}
}
}
jsxSourceLine = p.jsxSourceLine
jsxSourceColumn = p.jsxSourceColumn
}

if e.TagOrNil.Data != nil {
propsLoc = e.TagOrNil.Loc
e.TagOrNil = p.visitExpr(e.TagOrNil)
Expand Down Expand Up @@ -12306,9 +12343,6 @@ func (p *parser) visitExprInOut(expr js_ast.Expr, in exprIn) (js_ast.Expr, exprO
args = append(args, js_ast.Expr{Data: &js_ast.EBoolean{Value: isStaticChildren}})

// "__source"
// Resolving the location to a specific line and column could be expensive, but it
// only happens in development mode and is a documented tradeoff.
prettyLoc := p.tracker.MsgLocationOrNil(js_lexer.RangeOfIdentifier(p.source, expr.Loc))
args = append(args, js_ast.Expr{Data: &js_ast.EObject{
Properties: []js_ast.Property{
{
Expand All @@ -12319,12 +12353,12 @@ func (p *parser) visitExprInOut(expr js_ast.Expr, in exprIn) (js_ast.Expr, exprO
{
Kind: js_ast.PropertyNormal,
Key: js_ast.Expr{Data: &js_ast.EString{Value: helpers.StringToUTF16("lineNumber")}},
ValueOrNil: js_ast.Expr{Data: &js_ast.ENumber{Value: float64(prettyLoc.Line)}},
ValueOrNil: js_ast.Expr{Data: &js_ast.ENumber{Value: float64(jsxSourceLine + 1)}}, // 1-based lines
},
{
Kind: js_ast.PropertyNormal,
Key: js_ast.Expr{Data: &js_ast.EString{Value: helpers.StringToUTF16("columnNumber")}},
ValueOrNil: js_ast.Expr{Data: &js_ast.ENumber{Value: float64(prettyLoc.Column)}},
ValueOrNil: js_ast.Expr{Data: &js_ast.ENumber{Value: float64(jsxSourceColumn + 1)}}, // 1-based columns
},
},
}})
Expand Down
31 changes: 19 additions & 12 deletions internal/js_parser/js_parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4756,28 +4756,35 @@ func TestJSXAutomatic(t *testing.T) {

// Dev, without runtime imports
d := JSXAutomaticTestOptions{Development: true, OmitJSXRuntimeForTests: true}
expectPrintedJSXAutomatic(t, d, "<div>></div>", "/* @__PURE__ */ jsxDEV(\"div\", {\n children: \">\"\n}, void 0, false, {\n fileName: \"<stdin>\",\n lineNumber: 1,\n columnNumber: 0\n}, this);\n")
expectPrintedJSXAutomatic(t, d, "<div>{1}}</div>", "/* @__PURE__ */ jsxDEV(\"div\", {\n children: [\n 1,\n \"}\"\n ]\n}, void 0, true, {\n fileName: \"<stdin>\",\n lineNumber: 1,\n columnNumber: 0\n}, this);\n")
expectPrintedJSXAutomatic(t, d, "<div key={true} />", "/* @__PURE__ */ jsxDEV(\"div\", {}, true, false, {\n fileName: \"<stdin>\",\n lineNumber: 1,\n columnNumber: 0\n}, this);\n")
expectPrintedJSXAutomatic(t, d, "<div key=\"key\" />", "/* @__PURE__ */ jsxDEV(\"div\", {}, \"key\", false, {\n fileName: \"<stdin>\",\n lineNumber: 1,\n columnNumber: 0\n}, this);\n")
expectPrintedJSXAutomatic(t, d, "<div key=\"key\" {...props} />", "/* @__PURE__ */ jsxDEV(\"div\", {\n ...props\n}, \"key\", false, {\n fileName: \"<stdin>\",\n lineNumber: 1,\n columnNumber: 0\n}, this);\n")
expectPrintedJSXAutomatic(t, d, "<div>></div>", "/* @__PURE__ */ jsxDEV(\"div\", {\n children: \">\"\n}, void 0, false, {\n fileName: \"<stdin>\",\n lineNumber: 1,\n columnNumber: 1\n}, this);\n")
expectPrintedJSXAutomatic(t, d, "<div>{1}}</div>", "/* @__PURE__ */ jsxDEV(\"div\", {\n children: [\n 1,\n \"}\"\n ]\n}, void 0, true, {\n fileName: \"<stdin>\",\n lineNumber: 1,\n columnNumber: 1\n}, this);\n")
expectPrintedJSXAutomatic(t, d, "<div key={true} />", "/* @__PURE__ */ jsxDEV(\"div\", {}, true, false, {\n fileName: \"<stdin>\",\n lineNumber: 1,\n columnNumber: 1\n}, this);\n")
expectPrintedJSXAutomatic(t, d, "<div key=\"key\" />", "/* @__PURE__ */ jsxDEV(\"div\", {}, \"key\", false, {\n fileName: \"<stdin>\",\n lineNumber: 1,\n columnNumber: 1\n}, this);\n")
expectPrintedJSXAutomatic(t, d, "<div key=\"key\" {...props} />", "/* @__PURE__ */ jsxDEV(\"div\", {\n ...props\n}, \"key\", false, {\n fileName: \"<stdin>\",\n lineNumber: 1,\n columnNumber: 1\n}, this);\n")
expectPrintedJSXAutomatic(t, d, "<div {...props} key=\"key\" />", "/* @__PURE__ */ createElement(\"div\", {\n ...props,\n key: \"key\"\n});\n") // Falls back to createElement
expectPrintedJSXAutomatic(t, d, "<div>{...children}</div>", "/* @__PURE__ */ jsxDEV(\"div\", {\n children: [\n ...children\n ]\n}, void 0, true, {\n fileName: \"<stdin>\",\n lineNumber: 1,\n columnNumber: 0\n}, this);\n")
expectPrintedJSXAutomatic(t, d, "<div>\n {...children}\n <a/></div>", "/* @__PURE__ */ jsxDEV(\"div\", {\n children: [\n ...children,\n /* @__PURE__ */ jsxDEV(\"a\", {}, void 0, false, {\n fileName: \"<stdin>\",\n lineNumber: 3,\n columnNumber: 2\n }, this)\n ]\n}, void 0, true, {\n fileName: \"<stdin>\",\n lineNumber: 1,\n columnNumber: 0\n}, this);\n")
expectPrintedJSXAutomatic(t, d, "<>></>", "/* @__PURE__ */ jsxDEV(Fragment, {\n children: \">\"\n}, void 0, false, {\n fileName: \"<stdin>\",\n lineNumber: 1,\n columnNumber: 0\n}, this);\n")
expectPrintedJSXAutomatic(t, d, "<div>{...children}</div>", "/* @__PURE__ */ jsxDEV(\"div\", {\n children: [\n ...children\n ]\n}, void 0, true, {\n fileName: \"<stdin>\",\n lineNumber: 1,\n columnNumber: 1\n}, this);\n")
expectPrintedJSXAutomatic(t, d, "<div>\n {...children}\n <a/></div>", "/* @__PURE__ */ jsxDEV(\"div\", {\n children: [\n ...children,\n /* @__PURE__ */ jsxDEV(\"a\", {}, void 0, false, {\n fileName: \"<stdin>\",\n lineNumber: 3,\n columnNumber: 3\n }, this)\n ]\n}, void 0, true, {\n fileName: \"<stdin>\",\n lineNumber: 1,\n columnNumber: 1\n}, this);\n")
expectPrintedJSXAutomatic(t, d, "<>></>", "/* @__PURE__ */ jsxDEV(Fragment, {\n children: \">\"\n}, void 0, false, {\n fileName: \"<stdin>\",\n lineNumber: 1,\n columnNumber: 1\n}, this);\n")

expectParseErrorJSXAutomatic(t, d, "<a key/>", "<stdin>: ERROR: Please provide an explicit key value. Using \"key\" as a shorthand for \"key={true}\" is not allowed.\n<stdin>: NOTE: The property \"key\" was defined here:\n")
expectParseErrorJSXAutomatic(t, d, "<div __self={self} />", "<stdin>: ERROR: Duplicate \"__self\" prop found. Both __source and __self are set automatically by esbuild. These may have been set automatically by a plugin.\n<stdin>: NOTE: The property \"__self\" was defined here:\n")
expectParseErrorJSXAutomatic(t, d, "<div __source=\"/path/to/source.jsx\" />", "<stdin>: ERROR: Duplicate \"__source\" prop found. Both __source and __self are set automatically by esbuild. These may have been set automatically by a plugin.\n<stdin>: NOTE: The property \"__source\" was defined here:\n")

// Line/column offset tests. Unlike Babel, TypeScript sometimes points to a
// location other than the start of the element. I'm not sure if that's a bug
// or not, but it seems weird. So I decided to match Babel instead.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

expectPrintedJSXAutomatic(t, d, "\r\n<x/>", "/* @__PURE__ */ jsxDEV(\"x\", {}, void 0, false, {\n fileName: \"<stdin>\",\n lineNumber: 2,\n columnNumber: 1\n}, this);\n")
expectPrintedJSXAutomatic(t, d, "\n\r<x/>", "/* @__PURE__ */ jsxDEV(\"x\", {}, void 0, false, {\n fileName: \"<stdin>\",\n lineNumber: 3,\n columnNumber: 1\n}, this);\n")
expectPrintedJSXAutomatic(t, d, "let 𐀀 = <x>🍕🍕🍕<y/></x>", "let 𐀀 = /* @__PURE__ */ jsxDEV(\"x\", {\n children: [\n \"🍕🍕🍕\",\n /* @__PURE__ */ jsxDEV(\"y\", {}, void 0, false, {\n fileName: \"<stdin>\",\n lineNumber: 1,\n columnNumber: 19\n }, this)\n ]\n}, void 0, true, {\n fileName: \"<stdin>\",\n lineNumber: 1,\n columnNumber: 10\n}, this);\n")

// Dev, with runtime imports
dr := JSXAutomaticTestOptions{Development: true}
expectPrintedJSXAutomatic(t, dr, "<div/>", "/* @__PURE__ */ jsxDEV(\"div\", {}, void 0, false, {\n fileName: \"<stdin>\",\n lineNumber: 1,\n columnNumber: 0\n}, this);\nimport {\n jsxDEV\n} from \"react/jsx-dev-runtime\";\n")
expectPrintedJSXAutomatic(t, dr, "<>\n <a/>\n <b/>\n</>", "/* @__PURE__ */ jsxDEV(Fragment, {\n children: [\n /* @__PURE__ */ jsxDEV(\"a\", {}, void 0, false, {\n fileName: \"<stdin>\",\n lineNumber: 2,\n columnNumber: 2\n }, this),\n /* @__PURE__ */ jsxDEV(\"b\", {}, void 0, false, {\n fileName: \"<stdin>\",\n lineNumber: 3,\n columnNumber: 2\n }, this)\n ]\n}, void 0, true, {\n fileName: \"<stdin>\",\n lineNumber: 1,\n columnNumber: 0\n}, this);\nimport {\n Fragment,\n jsxDEV\n} from \"react/jsx-dev-runtime\";\n")
expectPrintedJSXAutomatic(t, dr, "<div/>", "/* @__PURE__ */ jsxDEV(\"div\", {}, void 0, false, {\n fileName: \"<stdin>\",\n lineNumber: 1,\n columnNumber: 1\n}, this);\nimport {\n jsxDEV\n} from \"react/jsx-dev-runtime\";\n")
expectPrintedJSXAutomatic(t, dr, "<>\n <a/>\n <b/>\n</>", "/* @__PURE__ */ jsxDEV(Fragment, {\n children: [\n /* @__PURE__ */ jsxDEV(\"a\", {}, void 0, false, {\n fileName: \"<stdin>\",\n lineNumber: 2,\n columnNumber: 3\n }, this),\n /* @__PURE__ */ jsxDEV(\"b\", {}, void 0, false, {\n fileName: \"<stdin>\",\n lineNumber: 3,\n columnNumber: 3\n }, this)\n ]\n}, void 0, true, {\n fileName: \"<stdin>\",\n lineNumber: 1,\n columnNumber: 1\n}, this);\nimport {\n Fragment,\n jsxDEV\n} from \"react/jsx-dev-runtime\";\n")

dri := JSXAutomaticTestOptions{Development: true, ImportSource: "preact"}
expectPrintedJSXAutomatic(t, dri, "<div/>", "/* @__PURE__ */ jsxDEV(\"div\", {}, void 0, false, {\n fileName: \"<stdin>\",\n lineNumber: 1,\n columnNumber: 0\n}, this);\nimport {\n jsxDEV\n} from \"preact/jsx-dev-runtime\";\n")
expectPrintedJSXAutomatic(t, dri, "<>\n <a/>\n <b/>\n</>", "/* @__PURE__ */ jsxDEV(Fragment, {\n children: [\n /* @__PURE__ */ jsxDEV(\"a\", {}, void 0, false, {\n fileName: \"<stdin>\",\n lineNumber: 2,\n columnNumber: 2\n }, this),\n /* @__PURE__ */ jsxDEV(\"b\", {}, void 0, false, {\n fileName: \"<stdin>\",\n lineNumber: 3,\n columnNumber: 2\n }, this)\n ]\n}, void 0, true, {\n fileName: \"<stdin>\",\n lineNumber: 1,\n columnNumber: 0\n}, this);\nimport {\n Fragment,\n jsxDEV\n} from \"preact/jsx-dev-runtime\";\n")
expectPrintedJSXAutomatic(t, dri, "<div/>", "/* @__PURE__ */ jsxDEV(\"div\", {}, void 0, false, {\n fileName: \"<stdin>\",\n lineNumber: 1,\n columnNumber: 1\n}, this);\nimport {\n jsxDEV\n} from \"preact/jsx-dev-runtime\";\n")
expectPrintedJSXAutomatic(t, dri, "<>\n <a/>\n <b/>\n</>", "/* @__PURE__ */ jsxDEV(Fragment, {\n children: [\n /* @__PURE__ */ jsxDEV(\"a\", {}, void 0, false, {\n fileName: \"<stdin>\",\n lineNumber: 2,\n columnNumber: 3\n }, this),\n /* @__PURE__ */ jsxDEV(\"b\", {}, void 0, false, {\n fileName: \"<stdin>\",\n lineNumber: 3,\n columnNumber: 3\n }, this)\n ]\n}, void 0, true, {\n fileName: \"<stdin>\",\n lineNumber: 1,\n columnNumber: 1\n}, this);\nimport {\n Fragment,\n jsxDEV\n} from \"preact/jsx-dev-runtime\";\n")

// JSX namespaced names
for _, colon := range []string{":", " :", ": ", " : "} {
Expand Down
4 changes: 2 additions & 2 deletions scripts/js-api-tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -3556,7 +3556,7 @@ let transformTests = {
},
loader: 'jsx',
})
assert.strictEqual(code5, `/* @__PURE__ */ jsxDEV(Fragment, {\n children: /* @__PURE__ */ jsxDEV("div", {}, void 0, false, {\n fileName: "<stdin>",\n lineNumber: 1,\n columnNumber: 2\n }, this)\n}, void 0, false, {\n fileName: "<stdin>",\n lineNumber: 1,\n columnNumber: 0\n}, this);\nimport {\n Fragment,\n jsxDEV\n} from "react/jsx-dev-runtime";\n`)
assert.strictEqual(code5, `/* @__PURE__ */ jsxDEV(Fragment, {\n children: /* @__PURE__ */ jsxDEV("div", {}, void 0, false, {\n fileName: "<stdin>",\n lineNumber: 1,\n columnNumber: 3\n }, this)\n}, void 0, false, {\n fileName: "<stdin>",\n lineNumber: 1,\n columnNumber: 1\n}, this);\nimport {\n Fragment,\n jsxDEV\n} from "react/jsx-dev-runtime";\n`)
},

// Note: tree shaking is disabled when the output format isn't IIFE
Expand Down Expand Up @@ -3921,7 +3921,7 @@ let transformTests = {

async jsxDevelopment({ esbuild }) {
const { code } = await esbuild.transform(`console.log(<div/>)`, { loader: 'jsx', jsxRuntime: 'automatic', jsxDevelopment: true })
assert.strictEqual(code, `console.log(/* @__PURE__ */ jsxDEV("div", {}, void 0, false, {\n fileName: "<stdin>",\n lineNumber: 1,\n columnNumber: 12\n}, this));\nimport {\n jsxDEV\n} from "react/jsx-dev-runtime";\n`)
assert.strictEqual(code, `console.log(/* @__PURE__ */ jsxDEV("div", {}, void 0, false, {\n fileName: "<stdin>",\n lineNumber: 1,\n columnNumber: 13\n}, this));\nimport {\n jsxDEV\n} from "react/jsx-dev-runtime";\n`)
},

async jsxImportSource({ esbuild }) {
Expand Down