Hi! Today I am going to talk about one of the most useful Emacs functions I have written. I use it almost every day while developing Javascript.

The Javascript project I work on is pretty big and has deep folder structures. When you want to import a Javascript file you need to know the path to that file relative to the current file. For me it’s really annoying to manually thinking about where that path is and I many times make an error. Was it "../../../frontend/components/Test.js" or "../../frontend/components/Test.js"?

Because I am an Emacs user I wrote a simple script to automatically generate the import statment with the relative file path to the file. The scripts uses ido to let the user select the js-file in the projectile project, and then calculates the relative path and inserts it wherever the cursor currently is.

My helpful screenshot

I am just going to leave the script here: (EDIT: I created js-import if you want to use this!)

(require 'f)
(require 'json)
(require 'subr-x)
(require 'projectile)

(defun js/get-project-dependencies ()
  (let ((json-object-type 'hash-table))
    (hash-table-keys
     (gethash "dependencies"
              (json-read-from-string (f-read-text (concat (projectile-project-root) "package.json") 'utf-8))))))

(defun string-ends-with-p (string suffix)
  "Return t if STRING ends with SUFFIX."
  (and (string-match (rx-to-string `(: ,suffix eos) t)
                     string)
       t))

(defun js/is-js-file (file)
  (or (string-ends-with-p file ".js") (string-ends-with-p file ".jsx")))

(defun js/import ()
  (interactive)
  (let* ((filtered-project-files
          (-filter 'js/is-js-file (projectile-current-project-files)))
         (all (append (js/get-project-dependencies) filtered-project-files))
         (selected-file (ido-completing-read "Select a file to import: " all))
         (selected-file-name (f-filename (f-no-ext selected-file)))
         (selected-file-relative-path
          (f-relative
           (concat (projectile-project-root) (f-no-ext selected-file))
           (file-name-directory (buffer-file-name)))))
    (insert (concat
             "import "
             selected-file-name
             " from \""
             (if (js/is-js-file selected-file) (concat "./" selected-file-relative-path) selected-file-name)
             "\";"))))