varletjs / varlet

A Vue3 component library based on Material Design 2 and 3, supporting mobile and desktop.
https://varletjs.org/#/en-US/index
MIT License
5k stars 628 forks source link

vue文件里面支持tsx #1759

Closed chenyulun closed 2 months ago

chenyulun commented 2 months ago

Feature request 🚀

Expected:

部分组件可能能接受Vnode自定义渲染内容,不想额外写.tsx单独写函数,希望能支持vue文件里面支持tsx

Examples:

<script setup lang="tsx">
const ItemRender = (text: string) =>  <span>{text}</span>
<script>

需要完成的修改包括:

  1. 编译的时候获取lang值,然后传给compileScript让他先用babel处理后再用esbuild处理
  2. varlet-eslint-config不支持lang="tsx",eslint也要修复

本地项目案例代码:

export async function compileSFC(sfc: string) {
  const sources: string = await readFile(sfc, 'utf-8')
  const id = hash(sources)
  const { descriptor } = parseSFC(sources, { sourceMap: false, filename: sfc })
  const { script, scriptSetup, template, styles } = descriptor

  let scriptContent
  let scriptLang = 'js' // 添加默认的语言
  let bindingMetadata

  if (script || scriptSetup) {
    if (scriptSetup) {
      // const sfcDir = dirname(sfc);
      const { content, bindings, lang } = compileScriptSFC(descriptor, {
        id,
        // issue https://github.com/varletjs/varlet/issues/1458
        fs: {
          fileExists: file => existsSync(file.replace(ES_DIR, SRC_DIR)),
          readFile: file => readFileSync(file.replace(ES_DIR, SRC_DIR), 'utf-8'),
        },
      })
      scriptContent = content
      scriptLang = lang || 'js' // 获取语言并且存储
      bindingMetadata = bindings
    }
    else {
      // script only
      scriptContent = script!.content
      scriptLang = script?.lang || 'js' // 获取语言并且存储
    }

    scriptContent = replaceExportToDeclare(scriptContent)
  }

  if (!scriptContent) {
    scriptContent = declareEmptySFC()
  }

  // scoped
  const hasScope = styles.some(style => style.scoped)
  const scopeId = hasScope ? `data-v-${id}` : ''

  if (template) {
    const render = compileTemplate({
      id,
      source: template.content,
      filename: sfc,
      compilerOptions: {
       // tsx+ts的ts编译
        expressionPlugins: SFC_TYPESCRIPT_LANG.includes(descriptor.scriptSetup?.lang || descriptor.script?.lang || '') ? ['typescript'] : undefined,
        scopeId,
        bindingMetadata,
      },
    }).code

    scriptContent = injectRender(scriptContent, render)
  }
//  ...其他代码
 // compileScript多加一个参数
  await compileScript(scriptContent, sfc, SFC_JSX_LANG.includes(scriptLang))
  }
}

compileScript.ts文件的修改

export async function compileScript(script: string, file: string, isVueJsx = false) {
 // 因为是虚拟文件,不能直接给file重写后缀,会导致isTsx找不到源文件,通过单独定义参数来走babel编译
  let code = isVueJsx || (isJsx(file) || isTsx(file)) ? await compileScriptByBabel(script, file) : script

  code = await compileScriptByEsbuild(code)
  const { alias } = await getPuiConfig()

  if (code) {
    code = resolveDependence(file, code, alias)
    code = extractStyleDependencies(file, code, IMPORT_CSS_RE)
    code = extractStyleDependencies(file, code, IMPORT_LESS_RE)
    code = extractStyleDependencies(file, code, IMPORT_SCSS_RE)
    removeSync(file)
    writeFileSync(replaceExt(file, getScriptExtname()), code, 'utf8')
  }
}

Others:

如果可以的话,我可以提PR

chenyulun commented 2 months ago

再者,packages/varlet-ui/src/card/Card.vue里面的<string>toSizeUnit(width)可能修改为toSizeUnit(width) as string

chenyulun commented 2 months ago

eslint的修改参考如下:

module.exports = {
// 其他代码
  overrides: [
    {
      files: ['*.vue'],
      parserOptions: {
        // Note: only `null` can override the previous config. `undefined` would be ignored.
        project: null,

        // Use `espree` for js/jsx script blocks to get better performance.
        // Note this format can only be used when there's no `project` set.
        parser: {
          js: 'espree',
          jsx: 'espree',

          ts: require.resolve('@typescript-eslint/parser'),
          tsx: require.resolve('@typescript-eslint/parser')

          // Leave the template parser unspecified, so that it could be determined by `<script lang="...">`.
        },
        extraFileExtensions: ['.vue'],
        ecmaFeatures: {
          jsx: true
        }
      },

      rules: {
        // Allow `<script lang="ts">` & `<script lang="tsx">`, but not other langs.
        'vue/block-lang': [
          'error', {
            script: {
              lang: ['ts', 'tsx'],
              allowNoLang: true
            }
          }
        ],

        // Don't apply those rules that need type information for `.vue` files.
        // Because it's not supported for `script lang="tsx"` in `.vue` files yet.
        // https://github.com/vuejs/create-vue/issues/123#issuecomment-1189934982
        // That includes (as of v22):
        '@typescript-eslint/dot-notation': 'off',
        '@typescript-eslint/no-throw-literal': 'off',

        '@typescript-eslint/naming-convention': 'off',
        '@typescript-eslint/no-base-to-string': 'off',
        '@typescript-eslint/no-floating-promises': 'off',
        '@typescript-eslint/no-for-in-array': 'off',
        '@typescript-eslint/no-implied-eval': 'off',
        '@typescript-eslint/no-misused-promises': 'off',
        '@typescript-eslint/no-unnecessary-boolean-literal-compare': 'off',
        '@typescript-eslint/no-unnecessary-type-assertion': 'off',
        '@typescript-eslint/prefer-includes': 'off',
        '@typescript-eslint/prefer-nullish-coalescing': 'off',
        '@typescript-eslint/prefer-optional-chain': 'off',
        '@typescript-eslint/prefer-readonly': 'off',
        '@typescript-eslint/prefer-reduce-type-parameter': 'off',
        '@typescript-eslint/promise-function-async': 'off',
        '@typescript-eslint/require-array-sort-compare': 'off',
        '@typescript-eslint/restrict-plus-operands': 'off',
        '@typescript-eslint/restrict-template-expressions': 'off',
        '@typescript-eslint/return-await': 'off',
        '@typescript-eslint/strict-boolean-expressions': 'off'
      }
    }
  ],
}

或者参考使用@antfu/eslint-config,升级eslint到9

haoziqaq commented 2 months ago

不需要支持 lang="tsx",需要接受 vnode 的情况,可以参考 varlet 组件源码中的

export const MaybeVNode = defineComponent({
  props: {
    is: {
      type: [String, Object] as PropType<string | VNode>,
    },

    tag: {
      type: String,
      default: 'span',
    },
  },
  setup(props) {
    return () => (isString(props.is) ? h(props.tag, props.is) : props.is)
  },
})

处理 vnode 更加平滑,并且能最大程度的保留模板编译优势。