Skip to content

Question: Recommended workflow for .vue and TSX macro files #59

Description

@c01nd01r

Hi! First of all, thanks for the project.

I'm trying to use vue-jsx-vapor in a regular Vue + Vite application that contains mostly .vue SFCs and a few .tsx components using vue-jsx-vapor macros.

Vite is configured as:

import vue from '@vitejs/plugin-vue'
import vueJsxVapor from 'vue-jsx-vapor/vite'

export default defineConfig({
  plugins: [
    vueJsxVapor({
      interop: true,
      macros: true,
    }),
    vue(),
  ],
})

The build works as expected, but I'm a bit unsure about the recommended type-checking workflow.

From my understanding:

  • vue-tsc understands .vue files, but doesn't apply TS Macro transforms to standalone .tsx files.
  • tsmc applies the macro transforms, but doesn't understand .vue SFCs.

This becomes tricky when .vue and .tsx files import each other, since TypeScript follows imports across the boundary.

As an experiment, I wrote a custom checker that uses @volar/typescript's runTsc() and registers both the Vue language plugin and the TS Macro language plugins in the same TypeScript run. That seems to work well for mixed .vue + .tsx projects.

vue-ts-macro-tsc
#!/usr/bin/env node

const { runTsc } = require('@volar/typescript/lib/quickstart/runTsc');
const vueCore = require('@vue/language-core');
const tsMacro = require('@ts-macro/language-plugin');
const tsMacroOptions = require('@ts-macro/language-plugin/options');

const windowsPathReg = /\\/g;

let runExtensions = ['.vue', '.ts', '.tsx'];
let extensionsChangedException;

function unique(items) {
  return [...new Set(items)];
}

function getVueOptions(ts, options) {
  const { configFilePath } = options.options;

  if (typeof configFilePath === 'string') {
    return vueCore.createParsedCommandLine(
      ts,
      ts.sys,
      configFilePath.replace(windowsPathReg, '/'),
    ).vueOptions;
  }

  return vueCore.createParsedCommandLineByJson(
    ts,
    ts.sys,
    (options.host ?? ts.sys).getCurrentDirectory(),
    {},
  ).vueOptions;
}

function run() {
  return runTsc(
    require.resolve('typescript/lib/tsc'),
    {
      extraSupportedExtensions: runExtensions,
      extraExtensionsToRemove: [],
    },
    (ts, options) => {
      const vueOptions = getVueOptions(ts, options);
      const allExtensions = unique([
        ...vueCore.getAllExtensions(vueOptions),
        '.ts',
        '.tsx',
      ]);

      if (
        runExtensions.length !== allExtensions.length ||
        !runExtensions.every((ext) => allExtensions.includes(ext))
      ) {
        runExtensions = allExtensions;
        throw (extensionsChangedException = new Error('extensions changed'));
      }

      const vueLanguagePlugin = vueCore.createVueLanguagePlugin(
        ts,
        options.options,
        vueOptions,
        (id) => id,
      );
      const macroLanguagePlugins = tsMacro.getLanguagePlugins(
        ts,
        options.options,
        tsMacroOptions.getOptions(ts),
      );

      return {
        languagePlugins: [...macroLanguagePlugins, vueLanguagePlugin],
      };
    },
    "require('typescript')",
  );
}

try {
  run();
} catch (error) {
  if (error === extensionsChangedException) {
    run();
  } else {
    throw error;
  }
}

So I was wondering:

  • Is this kind of combined checker the intended approach?
  • Or is there another recommended workflow for projects that mix Vue SFCs and vue-jsx-vapor TSX files?

From the perspective of someone starting from the standard Vue + Vite template, I expected the setup to be roughly:

npm install vue-jsx-vapor

Configure the Vite plugin, and have both vite build and npm run type-check continue to work.

If I'm overlooking the intended setup, I'd really appreciate any guidance.

Thanks!

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions