jtsx
is an Emacs package for editing JSX
or TSX
files. It provides jtsx-jsx-mode
and jtsx-tsx-mode
major modes implemented respectively on top of js-ts-mode
and tsx-ts-mode
, benefiting thus from the new built-in Tree-sitter feature.
Summary of features:
JSX
elements, toggling the orientation ofJSX
attributesJSX
opening and closing tagsJSX
closing tag and new lineNote that while jtsx-jsx-mode
is fully compatible with pure JS
files, jtsx-tsx-mode
has some rare conflicts with TS
files (e.g. type assertions). It is thus recommanded to use jtsx-typescript-mode
(based on typescript-ts-mode
) for plain TS
files.
Emacs 29.1 or higher, built with tree-sitter
support (./configure --with-tree-sitter
) is required. To check if your Emacs embeds tree-sitter
, typing M-x treesit
should give you at least treesit-install-language-grammar
result.
⚠️ jtsx
does not work with the deprecated tree-sitter package.
⚠️ Even if Emacs is built with tree-sitter
support, tree-sitter
languages are not installed by default. Git
and a C/C++
compiler are necessary to install them.
The straightforward way to install jtsx
is through the Melpa package manager. You can find more informations on how to install a Melpa
package here.
tree-sitter
languages installationInstalling tree-sitter
languages is required by jtsx
(javascript
for jtsx-jsx-mode
, tsx
for jtsx-tsx-mode
, and typescript
for jtsx-typescript-mode
). You can use M-x jtsx-install-treesit-language
command which is a convenient wrapper around treesit-install-language-grammar
for that purpose.
For more advanced usages, see M-x treesit-install-language-grammar
command, or manually compile and set up language libraries.
Here an example of configuration using use-package, to put in the Emacs
init.el
:
jtsx-jsx-mode
to JSX
and JS
filesjtsx-tsx-mode
to TSX
filesjtsx-typescript-mode
to TS
filesjtsx
functions to the same shortcuts for jtsx-jsx-mode
and jtsx-tsx-mode
JSX
/JS
and TSX
/TS
modes (use base mode variables)jtsx
behaviour through provided variableshideshow
minor mode for code folding(use-package jtsx
:ensure t
:mode (("\\.jsx?\\'" . jtsx-jsx-mode)
("\\.tsx\\'" . jtsx-tsx-mode)
("\\.ts\\'" . jtsx-typescript-mode))
:commands jtsx-install-treesit-language
:hook ((jtsx-jsx-mode . hs-minor-mode)
(jtsx-tsx-mode . hs-minor-mode)
(jtsx-typescript-mode . hs-minor-mode))
:custom
;; Optional customizations
;; (js-indent-level 2)
;; (typescript-ts-mode-indent-offset 2)
;; (jtsx-switch-indent-offset 0)
;; (jtsx-indent-statement-block-regarding-standalone-parent nil)
;; (jtsx-jsx-element-move-allow-step-out t)
;; (jtsx-enable-jsx-electric-closing-element t)
;; (jtsx-enable-electric-open-newline-between-jsx-element-tags t)
;; (jtsx-enable-jsx-element-tags-auto-sync nil)
;; (jtsx-enable-all-syntax-highlighting-features t)
:config
(defun jtsx-bind-keys-to-mode-map (mode-map)
"Bind keys to MODE-MAP."
(define-key mode-map (kbd "C-c C-j") 'jtsx-jump-jsx-element-tag-dwim)
(define-key mode-map (kbd "C-c j o") 'jtsx-jump-jsx-opening-tag)
(define-key mode-map (kbd "C-c j c") 'jtsx-jump-jsx-closing-tag)
(define-key mode-map (kbd "C-c j r") 'jtsx-rename-jsx-element)
(define-key mode-map (kbd "C-c <down>") 'jtsx-move-jsx-element-tag-forward)
(define-key mode-map (kbd "C-c <up>") 'jtsx-move-jsx-element-tag-backward)
(define-key mode-map (kbd "C-c C-<down>") 'jtsx-move-jsx-element-forward)
(define-key mode-map (kbd "C-c C-<up>") 'jtsx-move-jsx-element-backward)
(define-key mode-map (kbd "C-c C-S-<down>") 'jtsx-move-jsx-element-step-in-forward)
(define-key mode-map (kbd "C-c C-S-<up>") 'jtsx-move-jsx-element-step-in-backward)
(define-key mode-map (kbd "C-c j w") 'jtsx-wrap-in-jsx-element)
(define-key mode-map (kbd "C-c j u") 'jtsx-unwrap-jsx)
(define-key mode-map (kbd "C-c j d") 'jtsx-delete-jsx-node)
(define-key mode-map (kbd "C-c j t") 'jtsx-toggle-jsx-attributes-orientation)
(define-key mode-map (kbd "C-c j h") 'jtsx-rearrange-jsx-attributes-horizontally)
(define-key mode-map (kbd "C-c j v") 'jtsx-rearrange-jsx-attributes-vertically))
(defun jtsx-bind-keys-to-jtsx-jsx-mode-map ()
(jtsx-bind-keys-to-mode-map jtsx-jsx-mode-map))
(defun jtsx-bind-keys-to-jtsx-tsx-mode-map ()
(jtsx-bind-keys-to-mode-map jtsx-tsx-mode-map))
(add-hook 'jtsx-jsx-mode-hook 'jtsx-bind-keys-to-jtsx-jsx-mode-map)
(add-hook 'jtsx-tsx-mode-hook 'jtsx-bind-keys-to-jtsx-tsx-mode-map))
M-x jtsx-jump-jsx-element-tag-dwim
jumps to the opening or closing tag of the JSX
enclosing element. The furthest tag is choosen.\
M-x jtsx-jump-jsx-opening-tag
jumps to the opening tag of the JSX
enclosing element.\
M-x jtsx-jump-jsx-closing-tag
jumps to the closing tag of the JSX
enclosing element.
M-x jtsx-rename-jsx-element
renames both the opening and closing tags of a JSX
element. Cursor must either be inside the opening or the closing tag.
As an alternative, you can turn jtsx-enable-jsx-element-tags-auto-sync
to t
to enable automatic name synchronization of JSX
element tags. As soon as the name of an opening or a closing tag is edited, its paired tag is immediately synchronized.
jtsx
implements some commands to move a JSX
tag or node through the JSX
structure, re-indenting automatically the modified part of code. JSX
nodes can be an element (self-closing or not), an expression or a text line.
M-x jtsx-move-jsx-element-tag-forward
moves a JSX
element tag (opening or closing) forward.\
M-x jtsx-move-jsx-element-tag-backward
moves a JSX
element tag (opening or closing) backward.
M-x jtsx-move-jsx-element-forward
moves a JSX
element (or any JSX
node) forward.\
M-x jtsx-move-jsx-element-backward
moves a JSX
element (or any JSX
node) backward.
M-x jtsx-move-jsx-element-step-in-forward
moves a JSX
element (or any JSX
node) forward. Step into sibling elements if possible.\
M-x jtsx-move-jsx-element-step-in-backward
moves a JSX
element (or any JSX
node) backward. Step into sibling elements if possible.
Stepping out when moving can be desactivated by setting jtsx-jsx-element-move-allow-step-out
to nil
.
M-x jtsx-wrap-in-jsx-element
wraps JSX
nodes in a new JSX
element. Nodes are selected by a region if there is an active one. Else the node at point is used.
M-x jtsx-unwrap-jsx
unwraps JSX
nodes. The wrapping node to remove is the node at point.
M-x jtsx-delete-jsx-node
deletes the JSX
node at point and its children.
M-x jtsx-toggle-jsx-attributes-orientation
toggles the orientation of JSX
element attributes between horizontal (all on the same line) and vertical (one per line).
M-x jtsx-rearrange-jsx-attributes-horizontally
and M-x jtsx-rearrange-jsx-attributes-vertically
can be used to require explicitly the new orientation.
When entering the >
of an opening tag, the pending closing tag is automatically added right after the cursor if relevant (the function tries to guess if the closing addition is expected or not).
Also, when inside an empty inline jsx element (typically right after automatic insertion of closing tag), pressing ENTER
will insert 2 lines and let the cursor ready to add children elements.
These functionalities can be desactivated by setting jtsx-enable-jsx-electric-closing-element
to nil
and/or jtsx-enable-electric-open-newline-between-jsx-element-tags
to nil
.
ℹ️ Code completion is not part of jtsx
. You can get it to work by using Eglot (built-in in Emacs), or any other lsp package.
jtsx-jsx-mode
and jtsx-tsx-mode
customize built-in Hideshow
package in order to support code folding into JSX parts.
Hideshow
can be enabled with M-x hs-minor-mode
command.
Please refer to Hideshow documentation for usage informations.
Function | Description |
---|---|
jtsx-jsx-mode |
Enable jtsx-jsx-mode . |
jtsx-tsx-mode |
Enable jtsx-tsx-mode . |
jtsx-typescript-mode |
Enable jtsx-typescript-mode . |
jtsx-jump-jsx-element-tag-dwim |
Jump either to the opening or to the closing tag of the JSX element. |
jtsx-jump-jsx-opening-tag |
Jump to the opening tag of the JSX element. |
jtsx-jump-jsx-closing-tag |
Jump to the closing tag of the JSX element. |
jtsx-rename-jsx-element |
Rename a JSX element at point. Point can be in the opening or closing tag. |
jtsx-move-jsx-element-tag-forward |
Move a JSX element tag (opening or closing) forward. |
jtsx-move-jsx-element-tag-backward |
Move a JSX element tag (opening or closing) backward. |
jtsx-move-jsx-element-forward |
Move a JSX element (or any JSX node) forward. |
jtsx-move-jsx-element-backward |
Move a JSX element (or any JSX node) backward. |
jtsx-move-jsx-element-step-in-forward |
Move a JSX element (or any JSX node) forward. Step into sibling elements if possible. |
jtsx-move-jsx-element-step-in-backward |
Move a JSX element (or any JSX node) backward. Step into sibling elements if possible. |
jtsx-wrap-in-jsx-element |
Wrap JSX nodes in a JSX element. Nodes are selected by a region if there is an active one. Else the node at point is used. |
jtsx-unwrap-jsx |
Unwrap JSX nodes wrapped in the node at point. |
jtsx-delete-jsx-node |
Delete a JSX node at point and its children. |
jtsx-toggle-jsx-attributes-orientation |
Toggle the orientation of JSX attributes. |
jtsx-rearrange-jsx-attributes-horizontally |
Rearrange the orientation of JSX attributes horizontally. |
jtsx-rearrange-jsx-attributes-vertically |
Rearrange the orientation of JSX attributes vertically. |
Variables | Default | Description |
---|---|---|
jtsx-switch-indent-offset |
0 |
Offset for indenting the contents of a switch block. The value must not be negative. |
jtsx-indent-statement-block-regarding-standalone-parent |
nil |
Use standalone parent as anchor to evaluate statement block indentation. If t and if the parent of a statement block is not on its own line, the statement block will be indented relative to the beginning of the whole parent continuated expression. |
jtsx-jsx-element-move-allow-step-out |
t |
Allow to step out when moving a jsx element. |
jtsx-enable-jsx-electric-closing-element |
t |
Enable electric JSX closing element feature. |
jtsx-enable-electric-open-newline-between-jsx-element-tags |
t |
Enable electric new line between jsx element tags |
jtsx-enable-jsx-element-tags-auto-sync |
nil |
Enable automatic name synchronization of JSX element opening and closing tags. |
jtsx-enable-all-syntax-highlighting-features |
t |
Enable all available syntax highlighting features. |
An invalid JSX
syntax in the buffer can be the source of the problem. tree-sitter
tries to be resilient regarding syntax validity, but sometimes the parser cannot (or is not smart enough to) guess what the errored code means exactly.
JSX
?Although popular web-mode
and rjsx-mode
are very good packages for JSX
edition, they have been written when Emacs built-in JSX
support was very poor or absent. Currently, Emacs comes with a pretty good JSX
/ TSX
support and even gives the possibility to use tree-sitter
integration. jtsx
takes advantage of these new features to provide a lightweight package targetting both JSX
and TSX
with some handy functionalities.
tree-sitter
language ?Sometimes a bug introduced in a new release of a tree-sitter
language can break some features in jtsx
. You can easily revert to an older version of that language by running M-x eval-expression
and executing this elisp code after having customized the relevant parts, for example:
(let ((treesit-language-source-alist
'((javascript
"https://github.com/tree-sitter/tree-sitter-javascript"
"v0.21.3"
"src"))))
(treesit-install-language-grammar 'javascript))
All kinds of contributions are welcome: issues, discussions, or pull requests. More informations here.