quasar icon indicating copy to clipboard operation
quasar copied to clipboard

Support JSX/TSX

Open panhezeng opened this issue 3 years ago • 44 comments

quasar create quasar-tsx --branch next

module.exports = { presets: [ '@quasar/babel-preset-app', ], plugins: ['@vue/babel-plugin-jsx'] }

https://github.com/vuejs/jsx-next

panhezeng avatar Mar 26 '21 14:03 panhezeng

tsx is not supported in quasar yet unfortunately afaik I guess there are more prio issues to be done currently Seems like the components go through something else first before coming to the jsx transpiler and it breaks

Would be nice if this gets supported at some time because it has much cleaner syntax than render functions

Smrtnyk avatar Mar 26 '21 16:03 Smrtnyk

@panhezeng Im using TSX in "3.0.0-beta.10" Quasar/App, your babel.config.js is correct, but you need config "babel loader" on "quasar.conf.js";

see my config on "extendWebpack"

      extendWebpack (cfg, { isServer, isClient }) {
        cfg.resolve.alias = {
          ...cfg.resolve.alias,
          vue$: 'vue/dist/vue.esm-bundler.js',
          '@': path.resolve(__dirname, './')
        }
        cfg.resolve.extensions.push('.tsx')
        cfg.module.rules.push({
          test: /\.ts(x?)$/,
          exclude: /(node_modules|quasar)/,
          use: [
            {
              loader: 'babel-loader',
              options: { babelrc: true }
            },
            {
              loader: 'ts-loader',
              options: {
                appendTsSuffixTo: [/\.vue$/],
                transpileOnly: true

              }
            }
          ]
        })
        cfg.module.rules.push({
          test: /\.tsx$/,
          exclude: /(node_modules|quasar)/,
          use: [
            {
              loader: 'babel-loader'
            },
            {
              loader: 'ts-loader',
              options: {
                appendTsSuffixTo: [/\.vue$/],
                transpileOnly: true
              }
            }
          ]
        })
      }
    },

in addition to this configuration, you need to define the suppertTS

    supportTS: {
      tsCheckerConfig: {
        eslint: {
          enabled: false,
          files: './src/**/*.vue'
        }
      }
    },

joaomede avatar Apr 03 '21 22:04 joaomede

@joaomede Is q-dialog working for you in tsx? For me it is broken and always shown in the component as a visible block element

Smrtnyk avatar Apr 05 '21 19:04 Smrtnyk

Actually I get for all quasar components a warning that it cannot resolve when used in tsx file

Smrtnyk avatar Apr 05 '21 22:04 Smrtnyk

@joaomede did you manage to make JSX/TSX work with Quasar? Do you have some time to contribute with a PR adding it out of the box?

IlCallo avatar Aug 10 '21 10:08 IlCallo

@IlCallo Yes man, I Will Do

joaomede avatar Aug 10 '21 11:08 joaomede

Thanks :) Reach me out on Discord if you need any assistance

IlCallo avatar Aug 10 '21 14:08 IlCallo

Hello Guys, @IlCallo, @Smrtnyk, @panhezeng. I published a working example of "Quasar v2 / Vue 3" with official plugin "https://github.com/vuejs/jsx-next".

https://github.com/joaomede/Quasar-Vue3-TSX https://codesandbox.io/s/github/joaomede/Quasar-Vue3-TSX

Comments:

  • quasar v2 components are not returning JSX/TSX types, to solve this the "any" type needs to be pegged after import, eg:
import { defineComponent } from 'vue'
import { QBtn as Btn } from 'quasar'

const Component = defineComponent({
  setup() {
    const QBtn = Btn as any
    return () => <QBtn label={'label here'}>
  }
})

joaomede avatar Aug 10 '21 22:08 joaomede

you can add this to quasar.conf

sourceFiles: {
            rootComponent: "src/App",
        },

and rename your App.vue to App.tsx

Smrtnyk avatar Aug 10 '21 22:08 Smrtnyk

I don't have the type issues with quasar components when I use kebab-case naming for them for some reason

Smrtnyk avatar Aug 10 '21 22:08 Smrtnyk

@Smrtnyk, updated my example https://codesandbox.io/s/github/joaomede/Quasar-Vue3-TSX, changing the file "app.vue" to "app.tsx", thanks for that.

about this:

I don't have the type issues with quasar components when I use kebab-case naming for them for some reason

  • The configuration expects this type: Captura de tela de 2021-08-10 20-00-14
  • however, quasar components return this type: Captura de tela de 2021-08-10 20-00-20

Therefore, the error will always appear; when you use kebeb-case, the configuration assumes that you are using regular html, since in the quasar configuration you defined the components.

joaomede avatar Aug 10 '21 23:08 joaomede

@joaomede how can I return interfaces which are both TSX compatible AND retain existing behaviour (to describe the component public API)?

IlCallo avatar Aug 12 '21 12:08 IlCallo

@IlCallo, I believe that's the problem here, the project generates types, overlapping the originals, https://github.com/quasarframework/quasar/blob/918af69abb4e855f0848cd699b0413fed65e51e2/ui/build/build.types.js#L311 I believe this needs to be revised as the original types returned by the components work correctly.

joaomede avatar Aug 12 '21 14:08 joaomede

Captura de tela de 2021-08-12 11-17-26 the exports types is all correct, the problem is on replaced types, in https://github.com/quasarframework/quasar/blob/918af69abb4e855f0848cd699b0413fed65e51e2/ui/build/build.types.js#L311

joaomede avatar Aug 12 '21 14:08 joaomede

Interesting... Since the naming convention holds with interface and file name, I should be able to just import the type from the file itself for all components, will check what I can do with it

IlCallo avatar Aug 12 '21 15:08 IlCallo

@IlCallo, I generated an type expected by JSX, including the JSDoc comments from the props are working.

import { LooseDictionary, QBtn as Button } from 'quasar'
import { defineComponent, DefineComponent, ComponentOptionsMixin, EmitsOptions, AllowedComponentProps, VNodeProps, ComponentCustomProps } from 'vue'

export type PublicProps = VNodeProps & AllowedComponentProps & ComponentCustomProps

type NewType = DefineComponent<{}, () => JSX.Element, {}, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, EmitsOptions, string, PublicProps, Readonly<{} & {}>, {
    /**
     * Size in CSS units, including unit name or standard size name (xs|sm|md|lg|xl)
     */
     size? : string
     /**
      * Define the button HTML DOM type
      */
     type? : 'a' | 'submit' | 'button' | 'reset'
     /**
      * Equivalent to Vue Router <router-link> 'to' property
      */
     to? : string | LooseDictionary
     /**
      * Equivalent to Vue Router <router-link> 'replace' property
      */
     replace? : boolean
     /**
      * Equivalent to Vue Router <router-link> 'append' property
      */
     append? : boolean
     /**
      * The text that will be shown on the button
      */
     label? : string | number
     /**
      * Icon name following Quasar convention; Make sure you have the icon library installed unless you are using 'img:' prefix
      */
     icon? : string
     /**
      * Icon name following Quasar convention; Make sure you have the icon library installed unless you are using 'img:' prefix
      */
     iconRight? : string
     /**
      * Use 'outline' design
      */
     outline? : boolean
     /**
      * Use 'flat' design
      */
     flat? : boolean
     /**
      * Remove shadow
      */
     unelevated? : boolean
     /**
      * Applies a more prominent border-radius for a squared shape button
      */
     rounded? : boolean
     /**
      * Use 'push' design
      */
     push? : boolean
     /**
      * Applies a glossy effect
      */
     glossy? : boolean
     /**
      * Makes button size and shape to fit a Floating Action Button
      */
     fab? : boolean
     /**
      * Makes button size and shape to fit a small Floating Action Button
      */
     fabMini? : boolean
     /**
      * Apply custom padding (vertical [horizontal]); Size in CSS units, including unit name or standard size name (none|xs|sm|md|lg|xl); Also removes the min width and height when set
      */
     padding? : string
     /**
      * Color name for component from the Quasar Color Palette
      */
     color? : string
     /**
      * Overrides text color (if needed); Color name from the Quasar Color Palette
      */
     textColor? : string
     /**
      * Avoid turning label text into caps (which happens by default)
      */
     noCaps? : boolean
     /**
      * Avoid label text wrapping
      */
     noWrap? : boolean
     /**
      * Dense mode; occupies less space
      */
     dense? : boolean
     /**
      * Configure material ripple (disable it by setting it to 'false' or supply a config object)
      */
     ripple? : boolean | LooseDictionary
     /**
      * Tabindex HTML attribute value
      */
     tabindex? : number | string
     /**
      * Label or content alignment
      */
     align? : 'left' | 'right' | 'center' | 'around' | 'between' | 'evenly'
     /**
      * Stack icon and label vertically instead of on same line (like it is by default)
      */
     stack? : boolean
     /**
      * When used on flexbox parent, button will stretch to parent's height
      */
     stretch? : boolean
     /**
      * Put button into loading state (displays a QSpinner -- can be overridden by using a 'loading' slot)
      */
     loading? : boolean
     /**
      * Put component in disabled mode
      */
     disable? : boolean
     /**
      * Makes a circle shaped button
      */
     round? : boolean
     /**
      * Percentage (0.0 < x < 100.0); To be used along 'loading' prop; Display a progress bar on the background
      */
     percentage? : number
     /**
      * Progress bar on the background should have dark color; To be used along with 'percentage' and 'loading' props
      */
     darkPercentage? : boolean
     /**
      * Emulate click on QBtn
      * @param evt JS event object
      */
     click (evt? : LooseDictionary): void
 }>

const QBtn = Button as any as NewType

export default defineComponent({
  setup () {
    return () => (
      <QBtn label={'Working!!!'}></QBtn>
    )
  }
})

joaomede avatar Aug 13 '21 00:08 joaomede

What's Readonly<{} & {}> for?

IlCallo avatar Aug 13 '21 13:08 IlCallo

I believe they need to receive similar values ​​as props, however, it will serve for "single-file.vue" intellisense, but this needs to be validated better.

joaomede avatar Aug 13 '21 14:08 joaomede

original declare from vue https://github.com/vuejs/vue-next/blob/f258f5d2c2774eb2a2c2e4a0ac235b23d0cf9968/packages/runtime-core/src/apiDefineComponent.ts

export type PublicProps = VNodeProps &
  AllowedComponentProps &
  ComponentCustomProps

export type DefineComponent<
  PropsOrPropOptions = {},
  RawBindings = {},
  D = {},
  C extends ComputedOptions = ComputedOptions,
  M extends MethodOptions = MethodOptions,
  Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
  Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
  E extends EmitsOptions = Record<string, any>,
  EE extends string = string,
  PP = PublicProps,
  Props = Readonly<ExtractPropTypes<PropsOrPropOptions>> & EmitsToProps<E>,
  Defaults = ExtractDefaultPropTypes<PropsOrPropOptions>
> = ComponentPublicInstanceConstructor<
  CreateComponentPublicInstance<
    Props,
    RawBindings,
    D,
    C,
    M,
    Mixin,
    Extends,
    E,
    PP & Props,
    Defaults,
    true
  > &
    Props
> &
  ComponentOptionsBase<
    Props,
    RawBindings,
    D,
    C,
    M,
    Mixin,
    Extends,
    E,
    EE,
    Defaults
  > &
  PP

joaomede avatar Aug 13 '21 14:08 joaomede

Meanwhile, I found a way to infer the DefineComponent type from the JS file and generate it as a TS definition, but since we're using render functions seems like there's no way to automatically infer the public API defined via expose().

Also note there's this change pending in dev branch: https://github.com/quasarframework/quasar/commit/918af69abb4e855f0848cd699b0413fed65e51e2

Can you try if patching ComponentConstructor helper type in some way can help us get TSX support? I performed that changeto get full inference into Vue Test Utils, but is still unreleased

To test it, you should clone quasar repo locally, enter ui folder, run yarn install, then yarn build , then rm -rf node_modules (this is important) and finally install the local dependency into your project with yarn add <quasar package folder>/ui

IlCallo avatar Aug 13 '21 14:08 IlCallo

@IlCallo, you branch, https://github.com/quasarframework/quasar/commit/918af69abb4e855f0848cd699b0413fed65e51e2 solved the problem partially, lint stopped complaining, however, the component is only exposing 2 keys, they are; "key" and "ref", the declared props are not found yet. Captura de tela de 2021-08-13 11-59-04

ghost avatar Aug 13 '21 15:08 ghost

Yep, that's the same problem I reached right now in today experiments branch here

Apparently there's no way to infer stuff exposed via expose. I found a way to extract props from inferred DefineComponent interface doing Parameters<(typeof QAjaxBar)['setup'][0]> (QAjaxBar is the type generated starting from the JS file), so if you only need the props we can expore a bit more on that side.

Consider that we need to fix many problems and add JS annotations to get acceptable inferred types, I only did a small experimento on QAjaxBar today


the declared props are not found yet

That's why I told you to tinker a bit with ComponentConstructor helper, maybe you can use existing docs-generated interfaces to add something there, even if I guess you'll then lose distinction between props, methods, etc and break VTU extraction in some way 🤔

IlCallo avatar Aug 13 '21 15:08 IlCallo

I got in touch with Vue Core team, an advanced guide to Vue interfaces should be in the works, as well as a refactor of TS classes to make them more easy to use by library authors. They'll export a JSX compatible interface for components too, so let's cross fingers 🤞🏻

IlCallo avatar Sep 02 '21 14:09 IlCallo

any updates? JSX/TSX works with Vite, but types not provide class and style attributes.

mrBrownys avatar Dec 16 '21 07:12 mrBrownys

Seems like Vue team on this regard stopped due to the major docs rewrite they're doing Pinged them to get some info

Last time I heard them they told me it's possible to use RenderComponent interface to declare a JSX-compatible component, but that interface doesn't seem exist anymore

IlCallo avatar Dec 31 '21 11:12 IlCallo

I'd definitely be interested in having this supported by default with the CLI, I can get by using the render methods for now but having JSX definitely helps for readability in certain cases.

darkevilmac avatar Feb 13 '22 06:02 darkevilmac

any updates? JSX/TSX works with Vite, but types not provide class and style attributes.

Does this class and style attributes already solved?

tony-gm avatar Mar 16 '22 15:03 tony-gm

Is there a proper fix for this that retains the types? Trying to use tsx with quasar components, can't define a click handler for QBtn as click event is not a property for some reason. https://github.com/vuejs/babel-plugin-transform-vue-jsx

LiamKarlMitchell avatar Oct 22 '22 02:10 LiamKarlMitchell

Allowing JSX/TSX for newly authored components and slowly transition existing components to use it would be huge and make contributing a lot easier. SFCs would help similarly though the switch from render functions to JSX is a lot simpler and makes it easy to adopt.

There have been several instances where I wanted to contribute (mostly small stuff) but was deterred by the need to manually write render functions.

septatrix avatar Mar 09 '23 15:03 septatrix

We don't plan to convert Quasar components to JSX/TSX or SFC, this issue is about supporting JSX/TSX into userland We need the flexibility, and thus complexity, of render functions to write components optimized for performance

IlCallo avatar Mar 09 '23 18:03 IlCallo