DEV Community

Cover image for How I set up my emacs for TypeScript + React
Laura Viglioni
Laura Viglioni

Posted on • Updated on

How I set up my emacs for TypeScript + React

What exactly is my point here?

I recently knew typescript and with it, some joy and some tears. For a very long time, I've been working with React and using an emacs mode called rjsx-mode and I always loved it, I think this is a mode so good I use it even with regular JavaScript files.

Then I started with TypeScript and I was both amazed and saddened by it. For regular TS files, .ts it was amazing. The spacemacs typescript-mode was even better than rjsx-mode, all the types are shown perfectly in the bottom bar, autocomplete, auto-import... Everything. And thanks to tide. The sad part is: there is no tsx-mode, to write our .tsx files, we have to use web-mode. Don't get me wrong, this is a great mode, but React + Typescript were not the goals back then.

My point here is to put the best parts of all three modes in all of them!

This text might be a little long, but I hope it will help :)

Here my configs in practice:

An important point here: I use nowadays spacemacs and part of my code examples here will be in its way of doing things, but there is no magic, spacemacs is just emacs with some already written extra lisp (haha). You will be able to do it in your other emacs. But I really recommend you to start with spacemacs, especially if you are new to emacs. Changed my life, but this is a theme for another text.

Let's get started

You must have some modes installed. If you are using spacemacs most of them will be installed automatically when you install the layers. Those packages are:
rjsx-mode, typescript-mode, web-mode, tide, company, yasnippet, prettier-js (sorry if I forgot to list some pkg here)

Or in a simpler way, on your .spacemacs file add these layers on dotspacemacs-configuration-layers:



(defun dotspacemacs/layers ()
  ;; ... 
  dotspacemacs-configuration-layers
   '(
     html
     (typescript :variables
                 javascript-backend 'tide
                 typescript-fmt-tool 'prettier
                 typescript-linter 'eslint)
     (javascript :variables
                 javascript-backend 'tide
                 javascript-fmt-tool 'prettier
                 node-add-modules-path t)
 ;; ...


Enter fullscreen mode Exit fullscreen mode

see typescript layer, javascript layer, html layer

and on dotspacemacs-additional-packages



;; ...
 dotspacemacs-additional-packages
   '(
     rjsx-mode
     yasnippet-snippets
     prettier-js
     ;; ...


Enter fullscreen mode Exit fullscreen mode

Some of these imports require you to install third-party libs:



npm i -g tern prettier


Enter fullscreen mode Exit fullscreen mode

If you are not using spacemacs, you must to require each package on your .emacs file. All the GitHub pages of these modes have clear instructions for the installation :)

Applying tide to rjsx and web modes

The basics configs are done, we have all the three modes installed and working, what now?

Tide is a great mode that does a lot of magic for you, I'd recommend you to read its README. It runs automatically on typescript-mode and it would be great to use it on your other js/ts modes.

I have all my config files separated and import them in dotspacemacs/user-config, but you can put all these extra configs directly on this section or on your .emacs file if you're not using spacemacs.

First, we define a tide function config, later apply it on all those modes:



(defun dotspacemacs/user-config ()
 ;; ...
 ;; tide def func:
 (defun tide-setup-hook ()
    (tide-setup)
    (eldoc-mode)
    (tide-hl-identifier-mode +1)
    (setq web-mode-enable-auto-quoting nil)
    (setq web-mode-markup-indent-offset 2)
    (setq web-mode-code-indent-offset 2)
    (setq web-mode-attr-indent-offset 2)
    (setq web-mode-attr-value-indent-offset 2)
    (setq lsp-eslint-server-command '("node" (concat (getenv "HOME") "/var/src/vscode-eslint/server/out/eslintServer.js") "--stdio"))
    (set (make-local-variable 'company-backends)
         '((company-tide company-files :with company-yasnippet)
           (company-dabbrev-code company-dabbrev))))

;; hooks
(add-hook 'before-save-hook 'tide-format-before-save)


;; use rjsx-mode for .js* files except json and use tide with rjsx
(add-to-list 'auto-mode-alist '("\\.js.*$" . rjsx-mode))
(add-to-list 'auto-mode-alist '("\\.json$" . json-mode))
(add-hook 'rjsx-mode-hook 'tide-setup-hook)


;; web-mode extra config
(add-hook 'web-mode-hook 'tide-setup-hook
          (lambda () (pcase (file-name-extension buffer-file-name)
                  ("tsx" ('tide-setup-hook))
                  (_ (my-web-mode-hook)))))
(flycheck-add-mode 'typescript-tslint 'web-mode)
(add-hook 'web-mode-hook 'company-mode)
(add-hook 'web-mode-hook 'prettier-js-mode)
(add-hook 'web-mode-hook #'turn-on-smartparens-mode t)
 ;; ...


Enter fullscreen mode Exit fullscreen mode

These last lines add our tide setup for .tsx files and some other sub-modules that already exists in the other two modes.

Also, I recommend using these modes globally:



;; yasnippet
(yas-global-mode 1)

;; flycheck
(global-flycheck-mode)
(add-hook 'after-init-hook #'global-flycheck-mode)

;; company-mode 
(global-company-mode)


Enter fullscreen mode Exit fullscreen mode

Using rjsx snippets on all three modes

The one thing rjsx-mode has better than the other two modes is its snippets, so let's use it everywhere :)

There are two ways here, the first you can find on your .emacs.d where is the default dir for snippets configs (on spacemacs is .emacs.d/layers/+completion/auto-completion/local/snippets/), the second is define your own:



(add-to-list 'yas-snippet-dirs "~/path/to/your/dir")
;; notice that this add-to-list must be called before this:
(yas-global-mode 1)


Enter fullscreen mode Exit fullscreen mode

The procedure here is very simple: inside your snippet dir, create a dir with the mode name, i.e. web-mode/ and inside it create a file called .yas-parents with the mode names you want to "steal" the snippets. In our case:

snippets-dir/

web-mode/

.yas-parents

typescript-mode/

.yas-parents


On my own configs:
Picture of these dirs in a tree

Add these lines to the files:

typescript-mode/.yas-parents



rjsx-mode


Enter fullscreen mode Exit fullscreen mode

web-mode/.yas-parents



rjsx-mode
prog-mode
js-mode


Enter fullscreen mode Exit fullscreen mode

Last but not least

With all these configs you're now able to auto-import, format/import on saving, check types, check definitions... Everything :)

But I'd like to recommend you another package called paredit: it is a lib for lisp languages (if you are coding in any lisp family this package should be mandatory!) but once you get the shortcuts, you want to use them in every language, it is possible with this function call:



(sp-use-paredit-bindings)


Enter fullscreen mode Exit fullscreen mode

I also use neotree with all-the-icons to create my sidebar. Since this text is already too long, I will write about this specific config another day :)

And prettify symbols too!

I really hope you liked it and I hope this to be useful to you on your emacs journey.

This is my spacemacs config repo, all my lisps are in laurisp/ (haha) dir.

Be safe, use masks, stay home, use emacs.
xoxo

edit: I've found today some strange behaviour of import-js so I removed it from this tutorial. I'll try using tide for organizing imports on save, if I'm successful I update it here :)

Top comments (16)

Collapse
 
davidasg180 profile image
David Alberto Serrano Garcia • Edited

Hi,
Taking for reference your video, at the second 00:10 you import map and when you accept the auto-complete and it automatically add the import on the top.
in vscode and neovim on both I manage to do that, but i don't know what i'm doing wrong to not making work that feature in spacemacs.
What tool or configuration make possible to achieve that?
It's a Typescript React environment
Thanks a lot

Collapse
 
lucasmafra profile image
Lucas Mafra

Hi David, import-js is what you're looking for github.com/Galooshi/emacs-import-j...

Collapse
 
viglioni profile image
Laura Viglioni

I had problems with import-js, that's why I removed from this text. Tide should be enough for auto importing 🤔

Collapse
 
viglioni profile image
Laura Viglioni

I'm sorry for the delay if I'm not mistaken the company package does that.

If it helps those are my configs about js/ts:

github.com/Viglioni/spacemacs/blob...
github.com/Viglioni/spacemacs/blob...
github.com/Viglioni/spacemacs/blob...

Collapse
 
llemaitre19 profile image
Loïc Lemaître

Starting Emacs 29.1, there is the built-in tsx-ts-mode major mode for editing Typescript + JSX. You could also be interested by jtsx package that extends tsx-ts-mode (and js-ts-mode too) to provide some handy additional functionalities.

Collapse
 
emaphp profile image
Emmanuel Antico

why not lsp though? I'm confused about the fact that the major-mode shown is web, instead of React (which is a layer too). you can force that mode doing M-x and then rjsx-mode.

Collapse
 
viglioni profile image
Laura Viglioni

Yes! I'm trying lsp now and i think it is great, I will soon write a text about my TS config with LSP :)

it is quite easier than with tide and better.

Collapse
 
hagenek profile image
Georg H. Ekeberg

Hi. Please write that text! I am configuring now and need some assitance. Thanks for writing the first post also.

Collapse
 
rajasegar profile image
Rajasegar Chandran

This is the perfect tutorial I have been looking for, thank you

Collapse
 
viglioni profile image
Laura Viglioni

thx!

Collapse
 
bobbybakes profile image
Bobby Baker

Great stuff!

Collapse
 
viglioni profile image
Laura Viglioni

thx!

Collapse
 
tosiek88 profile image
Mateusz

Any chance for vue+typescript, this really sad that vue-mode is no longer develop ;(

Collapse
 
viglioni profile image
Laura Viglioni

ooh :(
I never used vue :(

Collapse
 
alechstong profile image
Alex Tong

Hi Laura, why do you need import-js. Doesn't Tide do auto-import already?

Collapse
 
viglioni profile image
Laura Viglioni

As a matter of fact yes! I used to format imports on save, but it is not necessary :)