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

Support JSX/TSX #4

Closed
evmar opened this issue Sep 29, 2015 · 53 comments
Closed

Support JSX/TSX #4

evmar opened this issue Sep 29, 2015 · 53 comments

Comments

@evmar
Copy link

evmar commented Sep 29, 2015

TypeScript added support for JSX:
microsoft/TypeScript#3203

Briefly, this adds support for HTML-like literals in code:

var foo = <div style="foo">blah</div>;

typescript-mode currently doesn't indent such literals properly, in contexts like this:

var foo = <div style="foo"
  attr2="bar">      <- should indent matching "style"
  <div>
    <div>              <- should indent due to opening tag above
      ...

Currently I use web-mode (which understands JSX) for typescript files, but that doesn't work with tide (which wants to work with typescript-mode, not web-mode).

@evmar
Copy link
Author

evmar commented Sep 29, 2015

See the linked web-mode bug for an alternative solution approach.

@ananthakumaran
Copy link
Collaborator

PR welcome to add support for indent

@yqrashawn
Copy link

Is it possible to use rjsx-delete-creates-full-tag in typescript-mode? Is it easy to implement? rjsx-mode has a function to get the rjsx-class at cursor point which needs to parse whole file in advance.

felipeochoa/rjsx-mode#65 (comment)

@felipeochoa
Copy link
Contributor

@yqrashawn (rjsx author here) No, the rjsx-class attribute is added while parsing the buffer, and rjsx cannot parse typescript, only javascript

@felipeochoa
Copy link
Contributor

Separate comment -- what would it take to get JSX support in typescript mode? Would be interested to hear if anyone has given any thoughts in regards to the following features:

  • Fontification
  • Indentation
  • Editing facilities (comment-dwim, rjsx-delete-creates-full-tag, etc.)

It looks like this mode does some sort parsing into "p-items" but not sure if that could be extended to handle JSX. I'm guessing some of the editing facilities are more likely to exist in tide than here, so happy to ignore that point for now.

@josteink
Copy link
Member

josteink commented Mar 8, 2019

Quite recently, emacs-devel has had some discussions about how to handle JSX better in core Emacs, and I've provided some minimal feedback w.r.t. JSX/React-syntax being used by other languages, not just JavaScript.

How it's solved in core may be relevant to how it's best solved in typescript-mode.

Thread for those interested: https://lists.gnu.org/archive/html/emacs-devel/2019-02/msg00430.html

@gaudecker
Copy link

Emacs 27 now supports JSX in js-mode, maybe this can be leveraged?

@lddubeau
Copy link
Collaborator

lddubeau commented Mar 6, 2020

I'm sure js-mode's JSX support can be helpful in some way.

However, the main obstacle to getting TSX support is that nobody who needs it is stepping up to implement it. I'm going to quote @josteink:

Until we have a "regular" TSX-user onboard who is willing to test and review TSX-related changes, I doubt typescript-mode will be able to support this.

@gaudecker
Copy link

@lddubeau there are 3 (including me) Emacs users at our company who can help with the testing. I have some experience with elisp, but have never implemented a major mode.

I will take a look at the code and see I can can help.

@tam5
Copy link
Collaborator

tam5 commented Mar 9, 2020

I currently use tsx (react) with web-mode plus tide which means plus typescript-mode.

As far as I can tell, I would consider tsx fully supported, especially the concerns posted by @evmar in 2015. If there is still something left to do, I'd suggest we close this issue and open a new one describing what is yet to be implemented.

@lddubeau
Copy link
Collaborator

lddubeau commented Mar 9, 2020

@tam5 I'm using web-mode to edit .vue files that contain HTML, CSS (or a variant) and TypeScript, but from my experience web-mode cannot apply to a buffer simultaneously with typescript-mode. typescript-mode has not been designed to cooperate with other major modes. It would be useful to have a wiki page describing how you get that working together.

web-mode plus tide presents no inherent problem on Emacs' side. (You need a special plugin for tsserver to not choke on the .vue files, however, and the plugins currently published for this purpose are all majorly buggy. Yes, I have the fix, not I have not published it yet. Busy...) But running tide does not entail running or even using parts of typescript-mode.

@YievCkim
Copy link

YievCkim commented Mar 9, 2020

@evmar There is a rjsx-mode in emacs which works fine. I don't use web-mode.
@tam5 I don't know tide, but what I would like is editing tsx the same manner as in rjsx-mode.
rjsx-mode is built on js2-mode and have nice features as syntax control.

eshamster added a commit to eshamster/react-devel that referenced this issue Apr 12, 2020
typescript-mode can't collectly indent attribute of element in
tsx.
Cf. emacs-typescript/typescript.el#4

On the other hand, js-mode (also rjsx-mode) can't collectly
indent some cases as the following.

The line including "isPlaying" can't be collectly indented.

type GameState = {
  cellsBuffer: RingBuffer<Cells>;
  isPlaying: boolean;
}

So, for a temproal solution, toggle each mode suitably.
@geoffp
Copy link

geoffp commented May 13, 2020

For day to day web development, what I'd put forward is that to be competitive with VSCode, we'd need:

  1. First-class JSX support, in our case, for .tsx files
  2. Ability to work within polymode so we can provide a first-class CSS experience in .vue files, Styled Components, or what have you.

Beyond that, I think all the advanced language comprehension stuff we'd want is taken care of by the TS language server. I use Tide for this, but I hear good things from people using lsp-mode and similar.

FWIW, it seems to be that the approach that rjsx-mode and js2-mode take, which is to build a whole AST into the major mode to get language comprehension, is obsoleted by language servers. Those modes are fantastic pieces of work, and I've used them for a long time, but taking advantage of all the work going into language servers seems much more sustainable.

@geoffp
Copy link

geoffp commented May 13, 2020

Also: the javascript-mode in Emacs 27 seems really good. It handles JSX almost perfectly, from what I can tell.

@lddubeau
Copy link
Collaborator

General remark: I think we're all on board with having support for tsx files, but the situation has not changed since I wrote the following in an earlier comment on this issue:

[...] the main obstacle to getting TSX support is that nobody who needs it is stepping up to implement it.


And now specific reactions to @geoffp's comments:

  1. Ability to work within polymode so we can provide a first-class CSS experience in .vue files, Styled Components, or what have you.

Every... single... time... I've tried using one of those "multiple major modes on the same buffer" modes, and I've tried many times, it has never worked right. I sank tons of my time, for nothing. It is easy to come up with a proof-of-concept recipe without realizing that, as the data to be edited becomes more complex, the whole thing falls apart. Been there, done that. (I've tried multiple different modes. mmm is probably the one I spent the most time with.)

This being said, if changes are needed for tide to work nicely with polymode or mmm or some other multi-major-mode thingy, we can make these changes, but, as usual, someone has to champion the changes.

(By the way, for those looking for something that already works for editing .vue files with TypeScript code, wed-mode works fine. It does not try to get multiple modes to work in the same buffer. Rather, it has built-in knowledge of HTML, CSS, JS, TS, etc.)

Beyond that, I think all the advanced language comprehension stuff we'd want is taken care of by the TS language server. I use Tide for this, but I hear good things from people using lsp-mode and similar.

Yes, lsp-mode is getting better and better. There is going to be a point where it is so good that there's no reason to use tide anymore.

FWIW, it seems to be that the approach that rjsx-mode and js2-mode take, which is to build a whole AST into the major mode to get language comprehension, is obsoleted by language servers. Those modes are fantastic pieces of work, and I've used them for a long time, but taking advantage of all work going into language servers seems much more sustainable.

I agree with this. I've commented many a time that to get absolutely perfect handling of highlighting and indentation in typescript-mode we'd need an AST. My hope was that we'd be able to call on a language server to provide the syntactic information.

@josteink
Copy link
Member

the main obstacle to getting TSX support is that nobody who needs it is stepping up to implement it.

Seconding that. We may have a chicken and egg problem: TSX-users are not using Emacs/typescript-mode because it has inadequate TSX-support, and since we don't have users with a burning passion for TSX, nobody steps up to improve the support.

Every... single... time... I've tried using one of those "multiple major modes on the same buffer" modes, and I've tried many times, it has never worked right.

I've tried a few times, but never understood how it was supposed to work. Granted I didn't try too hard, but I guess we can assume a fair amount of average users will suffer the same kind of issues we did.

someone has to champion the changes.

As Eric S. Raymond so famously said: Show me the code. That still applies today.

Yes, lsp-mode is getting better and better.

I find it usable in day to day work nowadays, but that wont solve or fix TSX-support :)

I agree with this. I've commented many a time that to get absolutely perfect handling of highlighting and indentation in typescript-mode we'd need an AST.

Still agreed. And still: Show me the code ;)

Also: the javascript-mode in Emacs 27 seems really good. It handles JSX almost perfectly, from what I can tell.

To put it a bit simplistic: Typescript and Javascript is mostly the same language except for a dash of : Types.

With Javascript being complex and getting more complex by the day, at what point do we find these duplicate major-modes to be more work than they are worth?

Has anyone seriously considered looking at current javascript-mode to see how much work it would be to add support for type-declaration and generic-parameters? As a long term strategy, I'm starting to wonder if that could be a option or not, but I've yet to look at the code myself.

@felipeochoa
Copy link
Contributor

Author of rjsx-mode here. I haven't used 27 yet, but from a couple of threads I was on, I got the sense that there was more to do on the JSX front still.

From what I saw of the js-mode process, parsing JSX with regexes was a monster effort when it was Javascript alone, and even then it relied on certain conventions (multiline JSX has to be enclosed in parens, with the root tag on its own line) to simplify things.

In TS, where there are generics, the situation is going to be even tougher. There were some attempts at extending js2/rjsx to support flow, but the conclusion was you'd basically have to modify most of the parsing functions to accommodate type declarations and assertions. I imagine doing that on a lot of enormous regexes will be even tougher.

Ultimately I think you need a proper parser with error recovery to handle TSX well.
I think web mode has a parser of sorts, but that gets confused somewhat often. It's what I use for tsx (with tide), but it's never felt entirely polished.

My dream was to build an incremental parser with automatic error recovery and grammar composition (see my dormant mole repo; lezer is another cool approach). I think even with tide/lsp, it is really nice to have a proper AST to build on top of. Unfortunately, I'm just another talking head with no code to show!

@shackra
Copy link

shackra commented May 15, 2020

please allow me to interject: what if we wait for https://github.com/ubolonton/emacs-tree-sitter to be somewhat usable and relay on it for parsing instead?

EDIT: silly me, I noticed that syntax highlight is currently available in emacs-tree-sitter emacs-tree-sitter/elisp-tree-sitter#36

@josteink
Copy link
Member

That looks amazing.

I also guess it would be possible to do an incremental rewrite of typescript-mode using that package, as opposed to the full rewrite we all fear (and which almost never work).

Are there any examples of any major-modes built using that package yet?

@shackra
Copy link

shackra commented May 15, 2020

Are there any examples of any major-modes built using that package yet?

no so far, the author is working on the documentation for syntax highlight still.

@jacksonrayhamilton
Copy link

Also: the javascript-mode in Emacs 27 seems really good. It handles JSX almost perfectly, from what I can tell.

To put it a bit simplistic: Typescript and Javascript is mostly the same language except for a dash of : Types.

With Javascript being complex and getting more complex by the day, at what point do we find these duplicate major-modes to be more work than they are worth?

Has anyone seriously considered looking at current javascript-mode to see how much work it would be to add support for type-declaration and generic-parameters? As a long term strategy, I'm starting to wonder if that could be a option or not, but I've yet to look at the code myself.

Emacs 27 JSX author here. Also, non-TypeScript user here. I too wondered why typescript-mode was a standalone mode, as opposed to being a derived mode of js-mode. After all, TypeScript is a superset of JavaScript, right? Wouldn’t it be awesome if you guys could inherit support for the latest language features that are happening in Emacs core?

From what I saw of the js-mode process, parsing JSX with regexes was a monster effort when it was Javascript alone, and even then it relied on certain conventions (multiline JSX has to be enclosed in parens, with the root tag on its own line) to simplify things.

In TS, where there are generics, the situation is going to be even tougher. There were some attempts at extending js2/rjsx to support flow, but the conclusion was you'd basically have to modify most of the parsing functions to accommodate type declarations and assertions. I imagine doing that on a lot of enormous regexes will be even tougher.

Indeed, parsing JSX was quite difficult. But it no longer relies on conventions like wrapping it in parens. You should be able to write a JSX expression just about anywhere in a buffer and it will font-lock and indent properly now.

And indeed, since the generics syntax in TypeScript looks similar to JSX, the current parsing logic in js-mode would need to be extended to determine if the angle brackets are being used in a TS context instead of a JSX context. Here were my initial thoughts on that; I suggested that js-mode could offer some hooks into its JSX parser for deriving modes—like typescript-mode, in theory—to tweak the logic. You’d bail out when discovering a generic in an array/object initialization, in an arrow/traditional function declaration/expression/call, in a class declaration/expression, or anywhere else they’re used.

@josteink
Copy link
Member

I too wondered why typescript-mode was a standalone mode, as opposed to being a derived mode of js-mode. After all, TypeScript is a superset of JavaScript, right? Wouldn’t it be awesome if you guys could inherit support for the latest language features that are happening in Emacs core?

Historical reasons, mostly. I guess this was started as a fork, when js-mode was far from as feature-rich as it is today.

Also, as the (somewhat dormant) maintainer of csharp-mode, a major-mode which derives from cc-mode, I have experiences with being (third party) derived modes having their own kinds of issues wrt following up and being compatible with different versions of upstream base-modes.

Here were my initial thoughts on that

Well now, that does look very familiar! 😄

Thanks for reaching out back then. While I've not done any work on this issue myself, I just wanted to at least try to let the developers in Emacs core to know about possible interesting extension-points, so that if someone on our end would be willing to step up to the task, they would have something to work with.

Maybe I at least succeeded in that?

@nahuel
Copy link
Contributor

nahuel commented Jan 17, 2021

Just in case this is useful for somebody, my workaround (when possible) is to not use TSX but instead:

const $ = React.createElement
const _ = null

const Title = (p: { label: string, children?: ReactNode }) => 
  $("div", { className : "mt-4 mb-8" },
    $("span", { className : "text-gray-400" }, 
      p.label, 
      $(OtherComponent, _, p.children),
   )

Now I find it better than JSX/TSX. Indenting and typing works like a charm. And with some font-lock love:

1

Comparison with TSX (js-mode):

2

@achempion
Copy link

I made typescript-mode support tsx syntax with a help from tree-sitter package, took one Sunday 😆

Here is my configuration

(use-package typescript-mode
  :ensure t
  :mode "\\.tsx?$")
(use-package emmet-mode :ensure t :hook typescript-mode)
(use-package tree-sitter :ensure t)
(use-package tree-sitter-langs :ensure t)
(use-package prettier-js :ensure t :hook (typescript-mode))

;; couldn't make it work with `use-package`, plain elisp instead
(require 'tree-sitter)
(require 'tree-sitter-langs)
(add-hook 'typescript-mode-hook #'tree-sitter-hl-mode)

I use it with lsp integration

@josteink
Copy link
Member

josteink commented May 24, 2021

@achempion That's great stuff!

If you could create a PR with that information included in the README (and possibly for plain users not using use-package, they are a thing) that could possibly close this issue once and for all after all these years 😃

@achempion
Copy link

@josteink will send it soon

@lynlevenick
Copy link

lynlevenick commented May 27, 2021

I ended up extending the above typescript-mode + tree-sitter configuration like so:

First, I defined a derived mode specifically for .tsx files (with no other changes)

(use-package typescript-mode
  :mode (rx ".ts" string-end)
  :init
  (define-derived-mode typescript-tsx-mode typescript-mode "typescript-tsx")
  (add-to-list 'auto-mode-alist (cons (rx ".tsx" string-end) #'typescript-tsx-mode)))

then I tweaked my tree-sitter configuration to take advantage of the new mode:

(use-package tree-sitter
  :hook (typescript-mode . tree-sitter-hl-mode)
  :config
  (setf (alist-get 'typescript-tsx-mode tree-sitter-major-mode-language-alist) 'tsx))

which uses the tsx grammar to provide better syntax highlighting for JSX, in particular this situation:

const SomeComponent: React.FC<{}> = () => {
  return (
    <div>
      {"any evaluated expression of only a string literal"}
    </div> // <-- without the tsx grammar, this line is highlighted as a string
  );
};

@achempion
Copy link

achempion commented May 27, 2021 via email

@lynlevenick
Copy link

Does my config above have this issue?

So far as I'm aware, yes: you configure typescript-mode for both .ts and .tsx files, but the latter has a grammar just for it in tree-sitter-langs. I had to do some extremely non-standard things to get tree-sitter running at all, so I can't exactly say what you've done definitely has this issue 😅

@achempion
Copy link

@lynlevenick I've tried your config and have this error

File mode specification error: (error Cannot find shared library for language: tsx)

@lynlevenick
Copy link

@achempion That's really strange! I can't help but say that it works for me 😅. I didn't include that I'm still using tree-sitter-langs, did you take that out? Maybe you could try to figure out what path it's trying to load, and why it's not finding it? This issue probably isn't the best place to work through it though.

@josemiguelo
Copy link

josemiguelo commented Jul 3, 2021

@lynlevenick's config didn't work for me. This is a slightly different setup that did the trick:

(use-package typescript-mode
  :ensure t
  :init
  (define-derived-mode typescript-tsx-mode typescript-mode "tsx")
  :config
  (setq typescript-indent-level 2)
  (add-hook 'typescript-mode #'subword-mode)
  (add-to-list 'auto-mode-alist '("\\.tsx?\\'" . typescript-tsx-mode)))

(use-package tree-sitter
  :ensure t
  :hook ((typescript-mode . tree-sitter-hl-mode)
	 (typescript-tsx-mode . tree-sitter-hl-mode)))

(use-package tree-sitter-langs
  :ensure t
  :after tree-sitter
  :config
  (tree-sitter-require 'tsx)
  (add-to-list 'tree-sitter-major-mode-language-alist '(typescript-tsx-mode . tsx)))

@SalTor
Copy link

SalTor commented Jul 16, 2021

Thanks @josemiguelo this resolved some frustrations I had doing TSX work. I'm glad to see development of treesitter in emacs which I had gotten used to in my vim setup

@cmatzenbach
Copy link

cmatzenbach commented Sep 27, 2021

@lynlevenick This worked BEAUTIFULLY for me. I'm honestly shocked, your setup creates a perfect tsx editing experience, easily on par with vscode's. All I had to do was add one line to reduce indentation from 4 spaces to 2. I can't believe how well it worked. Thank you so much for posting, even spacemacs doesn't have anything half as good as what you put together (spacemacs user for the past three years who just jumped back to his old emacs config to see if I could get a better experience for writing tsx components).

@josteink
Copy link
Member

Thanks for the suggested solution @josemiguelo!

That does IMO look simple and clean enough to be included mainline, since we are outsourcing all the "hard" work to tree-sitter.

I've therefore created a PR where I attempt to mainline your solution. Feedback welcome!

@xevrem
Copy link

xevrem commented Sep 28, 2021

can confirm that with some slight modification to @josemiguelo 's post, it can also works with an eglot config :D

(note that i use straight.el, with always ensure enabled w/ use-package )

(use-package typescript-mode
  :defer t
  :mode "\\.ts\\'")

(define-derived-mode typescript-tsx-mode typescript-mode "tsx")
(add-to-list 'auto-mode-alist '("\\.tsx\\'" . typescript-tsx-mode))

(use-package eglot
  :defer t
  :after (:all yasnippet jsonrpc flymake project xref eldoc)
  :hook 
  (typescript-mode . eglot-ensure))
 
 (use-package tree-sitter
  :straight (:type git :host github
                   :repo "emacs-tree-sitter/elisp-tree-sitter"
                   :branch "release")
  :defer t
  :after tree-sitter-langs
  :hook
  (eglot--managed-mode . (lambda ()
                 (tree-sitter-mode)
                 (tree-sitter-hl-mode))))

(use-package tree-sitter-langs
  :straight (:type git :host github
                   :repo "emacs-tree-sitter/tree-sitter-langs"
                   :branch "release")
  :config
  (tree-sitter-require 'tsx)
  (add-to-list 'tree-sitter-major-mode-language-alist '(typescript-tsx-mode . tsx)))

@angrybacon
Copy link

The configurations shared above don't seem to take care of indenting in my case, is that to be expected? I see tree-sitter-indent exists but isn't being configured above.

First screencast shows indenting the whole buffer in typescript-mode, the second in typescript-tsx-mode.

Screen.Recording.2021-10-03.at.12.52.31.AM.mov
Screen.Recording.2021-10-03.at.12.51.10.AM.mov

Additionally, the left and right chevrons receive the operator face when they are obviously part of the tag element. Is that too the expected behavior?

@acdoussan
Copy link

The configurations shared above don't seem to take care of indenting in my case, is that to be expected? I see tree-sitter-indent exists but isn't being configured above.

First screencast shows indenting the whole buffer in typescript-mode, the second in typescript-tsx-mode.

Screen.Recording.2021-10-03.at.12.52.31.AM.mov
Screen.Recording.2021-10-03.at.12.51.10.AM.mov
Additionally, the left and right chevrons receive the operator face when they are obviously part of the tag element. Is that too the expected behavior?

I had the same issue, ended up using web-mode.

(with-eval-after-load 'web-mode
    (setq
     sgml-basic-offset 2
     css-indent-offset 2
     web-mode-markup-indent-offset 2
     web-mode-css-indent-offset 2
     web-mode-code-indent-offset 2
     web-mode-attr-indent-offset 2)

    (add-to-list 'web-mode-indentation-params '("lineup-args" . nil))

(add-to-list 'auto-mode-alist '("\\.tsx\\'" . web-mode))

@ckruse
Copy link

ckruse commented Oct 20, 2021

I, too, use web-mode, but with a derived mode:

(use-package web-mode
  :hook ((web-mode . lsp)
         (typescript-tsx-mode . lsp))
  :mode (("\\.html\\'" . web-mode)
         ("\\.html\\.eex\\'" . web-mode)
         ("\\.html\\.tera\\'" . web-mode)
         ("\\.tsx\\'" . typescript-tsx-mode))
  :init
  (define-derived-mode typescript-tsx-mode typescript-mode "TypeScript-tsx")
  :config
  (setq web-mode-markup-indent-offset 2
        web-mode-css-indent-offset 2
        web-mode-code-indent-offset 2))

(use-package prettier
  :hook ((typescript-tsx-mode . prettier-mode)
         (typescript-mode . prettier-mode)
         (js-mode . prettier-mode)
         (json-mode . prettier-mode)
         (css-mode . prettier-mode)
         (scss-mode . prettier-mode)))

This way I can enable LSP and Prettier for the derived TSX mode only, and not for web-mode in general.

kazuish528 pushed a commit to shibafu528/dotfiles that referenced this issue Dec 29, 2021
bitspook added a commit to bitspook/spookmax.d that referenced this issue Jan 15, 2022
Will need to keep an eye out for a proper solution. This is just copy-pasta from
emacs-typescript/typescript.el#4 (comment)
theothornhill pushed a commit that referenced this issue Apr 20, 2022
Implement support using recipe suggested by @josemiguelo in this
comment: #4 (comment)
theothornhill pushed a commit that referenced this issue May 8, 2022
Implement support using recipe suggested by @josemiguelo in this
comment: #4 (comment)
@josteink
Copy link
Member

Typescript-support has been added to Emacs core, and it also supports TSX.

Closing this issue. If you still have problems, open a new issue on the GNU Emacs bug-tracker 😄

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.