Converting Vue from coffeescript to javascript

July 13, 2020

This script will extract the coffee from the .vue-files, convert it with decaffeinate and then insert the javascript back into the .vue-file.

#!python

"""
Prerequisites: `npm install -g decaffeinate`.
Run from: inside `src/` folder.
Run with: `find . -type f -name '*.vue' -print0 | xargs -0 -n1 ./bulk-vue-decaf.py`
then: `npm run lint -- --fix`
"""

import os
from sys import argv
import subprocess

def get_coffee_scripttag_bounds(vue_code: str, vue_file_path: str) -> tuple:
  tag_open = '<script lang="coffee">'
  tag_count = vue_code.count(tag_open)
  if tag_count != 1:
    print("ERR: " + vue_file_path + " - number of coffee tags is not 1: " + str(tag_count))
    exit(1)

  start = vue_code.index(tag_open) + len(tag_open)
  end = vue_code.index("</script>", start)
  if start == -1 or end < start:
    print("ERR: " + vue_file_path + " - (start, end) range invalid: " + start + ', ' + end)
    exit(1)
  
  return (start, end)

def extract_coffee_from_vue(vue_file_path: str) -> str:
  """:return: resulting out file"""
  with open(vue_file_path, "r", encoding="utf-8") as f:
    code: str = f.read()
  
  start, end = get_coffee_scripttag_bounds(code, vue_file_path)

  coffeescript = code[start: end]
  if len(coffeescript) == 0:
    print("ERR: " + vue_file_path + " - no code!")
    exit(1)
  
  out_file = vue_file_path + ".coffee"
  if os.path.exists(out_file):
    print("ERR: " + out_file + " exists!")
    exit(1)
  
  with open(out_file, "w+", encoding="utf-8") as out:
    out.write(coffeescript)
  
  return out_file

def run_decaffeinate(coffee_file_path: str) -> str:
  """:return: the new js file"""
  result = subprocess.run(["decaffeinate", "--use-cs2", "--use-js-modules", "--loose-js-modules", coffee_file_path], shell=True, check=True, capture_output=True)
  if result.returncode != 0:
    print("ERR: " + coffee_file_path + " - decaffeinate gave an error. Exit code " + result.returncode)
    print(result.stderr)
    print(result.stdout)
    exit(1)
  else:
    print("INFO: " + coffee_file_path + " - decaffeinated: " + result.stdout.decode("utf-8"))

  return coffee_file_path.replace(".coffee", ".js", 1)

def replace_coffee_with_js_inside_vue(vue_file: str, converted_js_file: str) -> None:
  with open(vue_file, "r", encoding="utf-8") as original:
    code = original.read()
  
  start, end = get_coffee_scripttag_bounds(code, vue_file)

  with open(converted_js_file, "r", encoding="utf-8") as pure_js:
    js_code = pure_js.read()
  
  patched_vue_code = code[0:start] + "\n" + js_code + "\n" + code[end:]
  patched_vue_code = patched_vue_code.replace('<script lang="coffee">', "<script>")

  out_file = vue_file.replace(".vue", ".decaf.vue")
  with open(out_file, "w+", encoding="utf-8") as out:
    out.write(patched_vue_code)
  return out_file

if __name__ == '__main__':
  if len(argv) != 2:
    print("Usage: " + argv[0] + " src/App.vue")
    exit(0)
  
  vue_file = argv[1]
  if not os.path.exists(vue_file):
    print("ERR: invalid file " + vue_file)
    exit(1)
  
  print("INFO: converting " + vue_file + " ...")

  coffee_file = extract_coffee_from_vue(vue_file)
  assert os.path.exists(coffee_file)
  js_file = run_decaffeinate(coffee_file)
  assert os.path.exists(js_file)
  end_result_file = replace_coffee_with_js_inside_vue(vue_file, js_file)
  assert os.path.exists(end_result_file)

  os.unlink(coffee_file)
  os.unlink(js_file)

  os.unlink(vue_file)
  os.rename(end_result_file, vue_file)

  print("Done with " + vue_file)

Converting import/export to require/module.exports

See the GitHub gist with jscodeshift code.