UnrefinedBrain / vue-metamorph

AST-based Codemod Framework for Vue projects
https://vue-metamorph.dev/
MIT License
88 stars 1 forks source link

expose a interface to override the parserOptions of vue-eslint-parser #90

Open haoliangwu opened 6 months ago

haoliangwu commented 6 months ago

first, thanks for your awesome works for this project, I am engaging in migration of large scale webapp from nuxt2 to nuxt3, this project really save me time for migration.

anyway, our project contains some .vue files which template is written with pug lang, in this case, the cli throws error like:

[
  {
    filename: '/Users/haoliang.wu/lyon/nba/webfront/pages/index.vue',
    error: TypeError: Cannot read properties of undefined (reading 'parent')
        at parseVue (file:///Users/haoliang.wu/lyon/nba/webfront/codemods/node_modules/vue-metamorph/dist/main.js:754:47)
        at transformVueFile (file:///Users/haoliang.wu/lyon/nba/webfront/codemods/node_modules/vue-metamorph/dist/main.js:804:45)
        at transform (file:///Users/haoliang.wu/lyon/nba/webfront/codemods/node_modules/vue-metamorph/dist/main.js:938:12)
        at Object.run (file:///Users/haoliang.wu/lyon/nba/webfront/codemods/node_modules/vue-metamorph/dist/main.js:1198:25)
  }
]

I found it could be solved by overriding the parseOptions to let vue-eslint-parser use vue-eslint-parser-template-tokenizer-pug parser to parse pug templates, but I have to hack into the source code of node_modules(I use patch-package right now).

so maybe it is better to expose a interface to override the parserOptions of vue-eslint-parser.

UnrefinedBrain commented 6 months ago

Hmm. I'm not very familiar with pug but I'll think about how to support it better. One problem I can see is that the stringify code outputs html and not pug.

Ideally it would just see <template lang="pug"> and adjust the parse/stringify strategy automatically for that file.

Thanks for raising this issue. I'll work on it a bit later

haoliangwu commented 6 months ago

hi @UnrefinedBrain ,

Finally, I resolve the migration for pug template before executing vue-metamorph with some scripts by using magic-string and pug compiler. Just compile and tranform the content between <template> tags into html syntax.

So if you are not familiar with pug and I thought there are workarounds here, maybe we could close it first and re-open it in the future once other users also request this feature.

haoliangwu commented 6 months ago

By the way, I'd like to share the scripts I used here(sorry for inline content, because the repo is enterprise private repo):

import MagicString from 'magic-string'
import * as pug from 'pug'
import stripIndent from 'strip-indent'

const LANG_PUG = /\slang="pug"/
const TPL_START_TAG = /<template(.+?)>/
const TPL_END_TAG = '</template>'

export function pug2html(raw) {
  const ms = new MagicString(raw)

  const templateStartTagStartMatch = raw.match(TPL_START_TAG)
  const templateStartTagStart = templateStartTagStartMatch?.index ?? -1

  if (templateStartTagStart < 0) {
    return
  }

  const templateStartTagEnd = templateStartTagStart + templateStartTagStartMatch[0].length

  // it is safe to use indexOf here because we can only declare </template> once
  const templateEndTagStart = raw.indexOf(TPL_END_TAG)
  const templateEndTagEnd = templateEndTagStart + TPL_END_TAG.length

  // update the template start tag
  ms.update(templateStartTagStart, templateStartTagEnd, templateStartTagStartMatch[0].replace(LANG_PUG, ''))

  // if the template content is empty, we remove template totally
  if (ms.slice(templateStartTagEnd, templateEndTagStart).trim().length === 0) {
    ms.remove(templateStartTagStart, templateEndTagEnd)
  } else {
    const rawTpl = ms.slice(templateStartTagEnd, templateEndTagStart)

    const compileHtml = pug.compile(stripIndent(rawTpl), {
      // https://github.com/yyx990803/pug-plain-loader/blob/master/index.js
      doctype: 'html',
      pretty: true,
    })

    // update the template
    ms.update(
      templateStartTagEnd,
      templateEndTagStart,
      compileHtml()
        // revert the escaped symbol for html spec
        .replace(/&amp;/g, '&')
        .replace(/&lt;/g, '<')
        .replace(/&gt;/g, '>')
        // it should be " but we assume it is a js expression
        // and we prefer to use ' here
        .replace(/&quot;/g, "'")
        .replace(/&#039;/g, "'") + '\n',
    )
  }

  return ms.toString().trimStart()
}
#!/usr/bin/env node

import fs from 'fs'
import path from 'path'
import url from 'url'
import { globSync } from 'glob'
import args from 'args'

import * as utils from './utils.js'

const commander = args.option(['p', 'pattern'], 'the glob pattern to match Vue SFC files, eg: **/*.vue')

const flags = args.parse(process.argv)

if (flags.pattern) {
  const failedList = []

  const __filename = url.fileURLToPath(import.meta.url)
  const __dirname = path.dirname(__filename)

  // todo: dynamically
  const vueSFCs = await globSync(flags.pattern, {
    ignore: 'node_modules/**',
    absolute: true,
  })

  vueSFCs.forEach(filePath => {
    const raw = fs.readFileSync(filePath, 'utf-8')
    try {
      const tranformed = utils.pug2html(raw)

      if (!tranformed) return

      fs.writeFileSync(filePath, tranformed, 'utf-8')
    } catch (err) {
      console.error(err)

      failedList.push(filePath)
    }
  })

  if (failedList.length > 0) {
    console.error(`The following Vue SFC files failed to tranform:`)
    console.log(failedList.join('\n'))
  }
} else {
  commander.showHelp()
}

the scripts here must contains some bugs but it is enough for my project.