Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vue 3 JSX Design #141

Closed
Amour1688 opened this issue Jul 3, 2020 · 81 comments
Closed

Vue 3 JSX Design #141

Amour1688 opened this issue Jul 3, 2020 · 81 comments

Comments

@Amour1688
Copy link
Member

Amour1688 commented Jul 3, 2020

Edit by @yyx990803 : there are currently two JSX transform implementations for Vue 3 with slightly differing syntax (for Vue specific features). We are using this thread to unify the design and land on an official specification of how Vue features should be handled in JSX.

Babel JSX Transform [Github]
alpha

  • patchFlags
  • same as Vue 3 Compiler
  • compatible with Vue 2.x

Syntax

Content

functional component

const App = () => <div></div>

with render

const App = {
  render() {
    return <div>Vue 3.0</div>
  }
}
const App = defineComponent(() => {
  const count = ref(0);

  const inc = () => {
    count.value++;
  };

  return () => (
    <div onClick={inc}>
      {count.value}
    </div>
  )
})

Fragment

const App = () => (
  <>
    <span>I'm</span>
    <span>Fragment</span>
  </>
)

Attributes/Props

const App = () => <input type="email" />

with a dynamic binding:

const placeholderText = 'email'
const App = () => (
  <input
    type="email"
    placeholder={placeholderText}
  />
)

Directives

It is recommended to use camelCase version of it (vModel) in JSX, but you can use kebab-case too (v-model).

v-show

const App = {
  data() {
    return { visible: true };
  },
  render() {
    return <input vShow={this.visible} />;
  },
};

v-model

  • You should use underscore (_) instead of dot (.) for modifiers (vModel_trim={this.test})
export default {
  data: () => ({
    test: 'Hello World',
  }),
  render() {
    return (
      <>
        <input type="text" vModel_trim={this.test} />
        {this.test}
      </>
    )
  },
}

custom directive

const App = {
  directives: { custom: customDirective },
  setup() {
    return () => (
      <a
        vCustom={{
          value: 123,
          arg: 'arg',
        }}
      />
    );
  },
}
@tangjinzhou
Copy link

Compatible with Vue2 should give a deprecated warning.
I prefer not to support in future, if compatible, we need to add runtime, performance will be lost.

@tangjinzhou
Copy link

tangjinzhou commented Jul 3, 2020

About slots,proposed API look like:

<com vSlots={{xxx: ({val})=>[<div>{val}</div>]}}>
    <span></span>
</com>

children has higher priority than vSlots.default or does not support vSlots.default.

@HcySunYang
Copy link
Member

HcySunYang commented Jul 3, 2020

@Amour1688 Great job, in addition to the above, I have some same or different proposals:

Experimental project: vue-next-jsx

Unified syntax

Since tsx does not support JSXNamespacedName and the . cannot appear in the attribute name, it is necessary to unify how to write directives in jsx.

Proposed syntax:

  • Don't support directives shorthand in jsx
  • Use - instead of :
  • and use _ instead of .

Examples

  • v-on
<p v-on-click_stop={ handler }></p>
<Comp v-on-myevent_a_b={ handler } />
  • v-model
    Note that: in Vue3, we use v-model:foo instead of :foo.sync modifier
<input v-model={ refVal.value }></p>
<Comp v-model-foo_a_b={ refVal.value } />

pros

  • No mixing/confusion using camel case and hyphen
  • Can be used for both jsx and tsx

cons

  • Not compatible with vue2's jsx syntax

Restricted slot

Since the scoped slot is still manually built in the Vue2' jsx, so I propose the only way to provide slots for components is:

<Comp>{ mySlots }</Comp>
const mySlots = {
    default: () => [ <p>default</p> ],
    foo: (obj) => [ <p>{ obj.somePorp }</p> ]
}

pros

  • Unified use of named slots and scoped slots
  • Avoid v-slot="props" resulting in type loss in ts

cons

  • Not compatible with vue2's jsx syntax

KeepAlive And Teleport

In Vue3, the children of KeepAlive and Teleport components will not be built as slots, so we need to handle it.

Fragment

Although we don't must to support fragment in jsx plugin, because Vue3 will handle it automatically, but it does bring development convenience:

render() {
    return (
        <>
            <p>Foo</p>
            <div>Bar</div>
        </>
    )
}

Optimization mode

Vue3 makes full use of compile-time information to generate PatchFlags for runtime update performance improvement, Maybe the jsx plugin can also do some similar work

Specify source

Some people install vue, but some install @vue/runtime-dom, so this should be configurable:

{
  "presets": [
    "@babel/env"
  ],
  "plugins": [
    ["@hcysunyang/vue-next-jsx", {
      // Specify source
      "source": "@vue/runtime-dom"
    }]
  ]
}

It affects the import statement:

import { .. } from 'vue'
import { .. } from '@vue/runtime-dom'

v-html / v-text

In Typescript, we must use domPropsInnerHTML, if we support v-html it will be more friendly.

@tangjinzhou
Copy link

@HcySunYang

Don't support directives shorthand in jsx
Use - instead of :
and use _ instead of .

We should declare events instead of skipping the check. In fact,when the event is declared in the props, we can still trigger by emit.
I prefer the camel case.

@tangjinzhou
Copy link

@HcySunYang

Don't support directives shorthand in jsx
Use - instead of :
and use _ instead of .

We should declare events instead of skipping the check. In fact,when the event is declared in the props, we can still trigger by emit.
I prefer the camel case.

Directives

<input vModel={this.newTodoText} />

with a modifier:

<input vModel_trim={this.newTodoText} />

with an argument:

<input onClick={this.newTodoText} />

with an argument and modifiers:

<input onClick_stop_prevent={this.newTodoText} />

v-html:

<p domPropsInnerHTML={html} />

If support modifiers, we can also declare it.
I am not familiar with ts, is there any other better way.

@HcySunYang
Copy link
Member

@tangjinzhou

In Typescript, if you do this:

<p onClick_stop={ handler }>text</p>

will get an error:

image

This is because onClick is treated as a standard html attribute.

In fact, users can still use onClick, but it is not allowed to add modifiers on it, if you want to add modifiers, please use:

<p v-on-click_stop={ handler }></p>

@Amour1688
Copy link
Member Author

Amour1688 commented Jul 3, 2020

In Typescript, if you do this:

<p onClick_stop={ handler }>text</p>

will get an error:

image

This is because onClick is treated as a standard html attribute.

In fact, users can still use onClick, but it is not allowed to add modifiers on it, if you want to add modifiers, please use:

<p v-on-click_stop={ handler }></p>

If it's possible to extend types.

v-on-click It doesn't look good.

I think use props.onXX may be a better method

@tangjinzhou
Copy link

@tangjinzhou

In Typescript, if you do this:

<p onClick_stop={ handler }>text</p>

will get an error:

image

This is because onClick is treated as a standard html attribute.

In fact, users can still use onClick, but it is not allowed to add modifiers on it, if you want to add modifiers, please use:

<p v-on-click_stop={ handler }></p>

@HcySunYang

maybe we should try to resolve it. If skip the check, the effect of using ts will no longer exist.

We can declare it like react :

declare module 'react' {
  interface Attributes {
    vModel?: any;
    // onClick
    onClick_stop?: any;
  }
}

@fgr-araujo
Copy link

Dots to separate is more readable. We know that dot is a layer inside something
Unserscore means compound names.

@fgr-araujo
Copy link

I love to use dash in HTML but in JSX can be confused with a minus sign.

@tangjinzhou
Copy link

We are now using the vueComponent/jsx to refactor ant-design-vue. I expect a smaller change cost, so I hope to be compatible with the syntax in vue2. Even if it may cause a performance loss, we should give the old project enough time to migrate.

@sonicoder86
Copy link

sonicoder86 commented Jul 3, 2020

Why not use JSX as is and use event handlers like this?

<button onClick={activateLasers}>
  Activate Lasers
</button>

I feel it more intuitive for devs coming with React background.

@HcySunYang
Copy link
Member

@BlackSonic, Of course, you can.

One of the purposes of the jsx plugin is to bring the convenience of the template, such as event modifiers, but this does not prevent you from directly using onClick.

@lloydjatkinson
Copy link

lloydjatkinson commented Jul 3, 2020

    return (
      <>
        <input type="text" vModel_trim={this.test} />
        {this.test}
      </>
    )

I think v-model-trim or vModelTrim would be infinitely better than vModel_trim which is both camel case and snake case at the same time. 😬

@Amour1688
Copy link
Member Author

Amour1688 commented Jul 3, 2020

    return (
      <>
        <input type="text" vModel_trim={this.test} />
        {this.test}
      </>
    )

I think v-model-trim or vModelTrim would be infinitely better than vModel_trim which is both camel case and snake case at the same time.

vModelTrim is better than vModel_trim. It's an excellent idea. We need consistent design to reduce the cognitive cost of users.

@Ericnr
Copy link

Ericnr commented Jul 3, 2020

Imo there is a mismatch with JSX props syntax and JS. JS object syntax would be much easier to use:

const placeholderText = 'email'
const name = 'emailInput'
const App = () => (
  <input {
    type: "email",
    placeholder: placeholderText,
    name
  } />
)

@HcySunYang
Copy link
Member

@Amour1688 The vModelTrim is confusing, what is the dir name(Model or ModelTrim?) and what is the modifier? 🤔

@Amour1688
Copy link
Member Author

@Amour1688 The vModelTrim is confusing, what is the dir name(Model or ModelTrim?) and what is the modifier? 🤔

Em... So vModelTrim maybe not a good choice. 🤔

@edimitchel
Copy link

edimitchel commented Jul 3, 2020

What about <input type="text" vModel={ trim(this.test) } /> where trim is to be imported from 'vue'.
The trim function wouldn't transform the content of this.test, but doing something with the get/set for getting a model trimmed.
It's more closer to the JSX philosophy but looks like a filter (feature removed in Vue 3).

Another examples :

<button onClick={middle(clickHandler)} />
<input onChange={ctrl(e(textHandler))} />

@edimitchel
Copy link

Or better one using pipes :

<button onClick={clickHandler |> middle} />
<input onKeyUp={textChanged |> ctrl |> e} />
<textarea vModel={clickHandler |> trim}></textarea>

@LexSwed
Copy link

LexSwed commented Jul 3, 2020

Or better one using pipes

Unfortunately there is no way Vue can rely on Stage 1 features.

But I love the proposal of using just function composition because I think the main goal of using jsx is for using "just javascript" (despite jsx being not js).

What about <input type="text" vModel={ trim(this.test) } /> where trim is to be imported from 'vue'.
The trim function wouldn't transform the content of this.test, but doing something with the get/set for getting a model trimmed.

Maybe easier solution would be to not provide modifiers in JSX as it's not a standard JS feature. In the codebases I worked on there weren't many usages of modifiers, but even if there are many, how difficult it is to use object spread? This can even be transpiled:

export default {
  data: () => ({
    test: 'Hello World',
  }),
  methods: {
    onSubmit: () => {},
  },
  render() {
    return (
      <form ...({ 'onSubmit.prevent': onSubmit }) > ... 
        <input type="text" ...({ 'v-model.trim': this.test }) />
        {this.test}
      </form>
    )
  },
}

But in the end I don't even care whether it was transpiled. Yes we create new objects on every render, but that's basically it.

@HcySunYang
Copy link
Member

Recognizing that many people have some misunderstandings, in fact, in Vue3 you can write jsx just like in React. We are discussing how to support directives args and modifiers, which are optional additional capabilities.

@JasKang
Copy link

JasKang commented Jul 4, 2020

how about this :

base

<button onClick={clickHandler} />

factory function

import {factoryClick, factoryDirective } from 'vue'
<button onClick={factoryClick(clickHandler,['middle'])} />
<input vModel={factoryDirective(a, ['trim'])} />

factoryfunction(value, modifiers: string[]) just return value
JSX parse modifiers , when factoryXXX from vue

@tangjinzhou
Copy link

Directives and Modifiers are optional features.
Maybe @JasKang plan is a good choice.

@yyyanghj
Copy link

yyyanghj commented Jul 4, 2020

I like vModel-foo_trim for v-model:foo.trim="bar"

@antfu
Copy link
Member

antfu commented Jul 5, 2020

Adding my cent here, thought the mix of dashes and underscores is not visually good to me. Overall, the 1:1 mapping of @HcySunYang 's syntax I think is more straightforward for people to read without reading extract docs.

Vote for v-on:myevent.a.b -> v-on-myevent_a_b

@edimitchel
Copy link

edimitchel commented Jul 5, 2020

Overall, the 1:1 mapping of @HcySunYang 's syntax I think is more straightforward for people to read without reading extract docs.
@antfu

Yes, but with some function utilities could be more explicit and less wierd to use. Not to mention wierd props we could have..
These functions could be added outside of the Vue core and directly added to the JSX project with a tree shaking behaviour.

import { trim } from 'vue/jsx'; // or 'vue-jsx'
<input vModel={trim(localModel)} /> {/* default model */}

import Comp, { foo } from './comp'
<Comp vModel={trim(foo(localModel))} /> {/* foo model of Comp */}

foo could be defined in the component like this :

import { toModel } from vue-jsx'';
const localRef = ref('value');
export const foo = toModel(localRef);

This improves the link between components and the typing too !

@wonderful-panda
Copy link

I like @edimitchel 's plan(function style) or @JasKang 's plan, because other plans breaks type-safety.

@bjarkihall
Copy link

bjarkihall commented Jul 17, 2020

@HcySunYang yes I took a look at your repo and @Amour1688 's too but I'm a bit confused since there are two repos and I rather want the design to be fully agreed upon - but since a great idea of a syntax can also be impractical to implement/type, we definitely need to do some experimentation, I've seen some neat approaches and I really like the playground you provided.

It's also a bit complicated to contribute in general since the vue jsx typings reside in the vue-next repo, and it also has some tests for jsx while @wonderful-panda 's "vue-tsx-support" repo has some great typings too, which could likely benefit the ones in the official vue-next repo - can't there just be a single source where we can branch from and experiment and make PRs? :)

Where would the official typings, tests and plugin end up, once most of the features have been decided upon and before Vue 3 is released?

@HcySunYang
Copy link
Member

@bjarkihall We can extend the type without intruding into vue-next, I have made this PR, the type of jsx can be completed on this basis

@HcySunYang
Copy link
Member

In this thread, I learned a lot of knowledge that I didn't realize before, especially the points of @bjarkihall and @wonderful-panda and their past practices. Now I think we have reached a preliminary consensus and we need to work together. @yyx990803 Maybe we need to be in the same repo and contribute together instead of implementing these functionalities separately., this will only bring more confusion to those who want to contribute and users, if that is possible, then, I am willing to archive the vue-next-jsx project and issue a statement to indicate the correct way to the user.

@nickmessing
Copy link
Member

nickmessing commented Jul 21, 2020

It would be nice to compile all of the above into a single spec and then we can get to work if we have a proper spec done.

P.S. Looks like we can only achieve consensus if we make a spec & same RFC process as Vue 3 went through.
P.P.S. I intentionally decided not to participate in the opinion battle over JSX design since I barely used it the last 8 months.

@dagadbm
Copy link

dagadbm commented Jul 23, 2020

Sorry to barge in I literally just discovered yesterday I could use jsx and even the jsx on vue2 is a bit confusingspecially the slots part.

I didn't see any mentions of slots here were they deprecated in vue3?

Additionally are there any plans to just have a single jx source of truth?

I have seen at least some 3 jsx repos and then you have the jsx doc page on Vue which doesn't have the same examples as this page.

I tried yesterday understanding how to use slots and I saw so many different syntaxes and the whole scoped slots thing which was also deprecated.

This issue seems to be very hot right now so I just wanted to leave my feedback/questions for all you guys.

Also regarding the syntax for this issue the best one so far is the latest one which has vModel , vDirective and then it receives the same thing (what is hard is understanding what each argument is and it ends up being a bit weird to have an array of strings at the end why not flat that out as the last event and just gather it as an array in the inside. )

@bjarkihall
Copy link

bjarkihall commented Jul 23, 2020

@dagadbm if you're talking about spreading (not flattening) the mods array I suggested it in "ev2" in a comment above - you'd always have to know if you're either giving arg + mods (string, string[]) og just mods (string[]) - so if you want to spread the mods array, you'd need to know there's no arg first by either always expect it:

<Comp vDir={['demo', value, undefined, 'a', 'b']} /> // v-demo.a.b="value" - you can also write ['demo', value, , 'a', 'b'] or ['demo', value,  '', 'a', 'b'] which is harder to spot

or defining either new attribute (I leaning towards limiting the number of possible attributes since they just confuse things) or a helper function (ev2) which doesn't expect the arg:

<Comp vDir={dirWithMods('demo', value, 'a', 'b')} /> // v-demo.a.b - dirWithMods(directive: string | Directive, value: any, ...mods as string[])

I guess the previous one can be typed (and inferred) properly and it's the least of things to learn and doesn't need any array, so maybe it's clearest: [dir?, val?, arg?, ...mods?][] | { [dir: string]: { [arg: string]: [val, ...mods] } }, although hard to spot the difference between args & mods in the spread types - you'd have to know what's in 3rd vs 4th index of the tuple.

Regarding slots, they've been discussed here - pass the object syntax as children, it's even possible in react and is explained here, it's a similar pattern to render props (slots is just an object of named render functions).

JSX should work straight away as an h/createVNode-wrapper/factory and TSX could even work without babel.
What this thread is mostly discussing is support for better typing and additional syntactic sugar via v- attributes (abstractions for directives, models, events, arguments, modifiers, etc).

The typings need to be worked on since right now JSX.Element expects any to be a valid element, slots are not inferred like emits, FunctionalComponents return any, declareComponent does not validate the return value either, etc.

I think we should start by defining VueElement interface which JSX.Element can extend (no special v- attributes to start with) and clean up the types a bit. If we have a solid JSX core with typescript support we can carefully add attributes like vDirective, vOn, vModel, etc.
This would break some tests like this one but this would also fail in React, since number is not a valid ReactElement:

const el: VueElement = 0 // TS-error
const el2: VueElement = <>{0}</> // valid

so:

import {FunctionalComponent, VueElement} from "vue-next/jsx" // FunctionalComponent: (props?, ctx?) => VueElement
const FC: FunctionalComponent = () => 0 // should be invalid since JSX.Element can only extend an interface
const FC: FunctionalComponent = () => (0 as unknown as VueElement) // should work
const FC2: FunctionalComponent = () => <>{0}</> // valid

I'm still just reading through the v3 docs and just experimenting with vue-next repo locally, but it's promising.

@isnifer
Copy link

isnifer commented Jul 31, 2020

Guys, as an active user of jsx in Vue, I would like to tell my opinion:

  1. It should be just like plain jsx (like react, yes, don't reinvent the wheel)
  2. Stop design jsx api keeping in mind templates

As @yyx990803 mentioned early – people who decided to use jsx just hate templates.

Thanks.

@bjarkihall
Copy link

@isnifer plain JSX should already work in Vue, just like React:

const el = <div onClick={e=>console.log(e)}></div> // const el = h('div', {onClick: e=>console.log(e)})
const StatelessComp = (props, {attrs, slots}) => <div {...props} {...attrs}>{slots}</div> // const StatelessComp = (props, {attrs, slots}) => h('div', {...props, ...attrs}, slots)

The problem is TSX and the amount of any types used for JSX.Element (like FunctionalComponent, defineComponent(fn), RenderFunction, etc) and Vue types/functions that expect a vNode, like withDirectives - if you pass a JSX.Element into these you get:

Argument of type 'Element' is not assignable to parameter of type 'VNode<RendererNode, RendererElement, { [key: string]: any; }>'. Type 'Element' is missing the following properties from type 'VNode<RendererNode, RendererElement, { [key: string]: any; }>': __v_isVNode, __v_skip, type, props, and 18 more.ts(2345)

The discussion has mostly been about being able to use TSX with Vue as a replacement for createVNode/h, not template syntax itself - just like it abstracts createElement in React, and I was suggesting we skipped all "magic" as a starter and actually made things just work like they do in React TSX.

The additional attributes (vModel, vOn, vShow, etc) are reserved/custom directives, which are a Vue render function feature (not just in templates).
You can wrap vNodes in functions to give them directives like described above, but alternatively we've been discussing props-syntax (vDir) which would not require any import, would be type-safe, unified and easy for the runtime to apply - the transformation step should be minimal, if any. But this shouldn't really be rushed, I think.

Also, "hating templates" isn't the only factor of using JSX in Vue. It creates a nice transition to the library for React users, allows you to get a lower-level access to component creation, allows you to gain greater DX since it has support from TS itself and a huge ecosystem with solutions like CSS-in-JS, which some prefer, it also allows you to create multiple small components per file, etc.
SFCs could get better with some additional DX. It's just they need a .vue file support through their own extension like Vetur, which struggles to provide TS features like renaming symbols and each feature (like intellisense, type-checking, color highlighting, etc) has to play catch-up with every .js/.ts feature - the competition hasn't really been fair, but it's great to have the option of using TSX instead of SFC/templatestrings/html, for those who prefer it.

@xialvjun
Copy link

xialvjun commented Aug 6, 2020

I'd like to not support directives to push the community going in the right way(ok ok, directive is not the wrong way) and to simplify the vnode object structure.

As to better DX, we can just:

const count = ref(0);
const {value, onChange} = v_model(count);

// v-model: <input v-model="count" /> to
<input {...v_model(count)} />
// but it seems things like <input {...v_model(reactive_obj.count)}/> is impossible

// v-custom: <div v-loading="is_loading"></div>
<Loading is_loading={is_loading}><div></div></Loading>

@ConradSollitt
Copy link

Greetings, I've done some testing with Vue 3 and JSX today and found a difference between the behavior of Vue.h and h functions from other popular Virtual Dom Libraries (React, Preact, etc).

Since the Vue 3 JSX Discussion is going on here I thought I would add a comment here rather then open a new formal issue on the main https://github.com/vuejs/vue-next repository.

Below are example code and demo links.

Online Babel Repl
https://babeljs.io/repl

This valid JSX:

// @jsx Vue.h
function App() {
    return (
        <Vue.Fragment>
            <h1>Hello World</h1>
            <div>Test with Vue 3</div>
        </Vue.Fragment>
    )
}

Vue.render(
    <App />,
    document.getElementById('root')
);

Is compiled to this by Babel

// @jsx Vue.h
function App() {
  return Vue.h(Vue.Fragment, null, Vue.h("h1", null, "Hello World"), Vue.h("div", null, "Test with Vue 3"));
}

Vue.render(Vue.h(App, null), document.getElementById('root'));

The issue is that an array is currently required for the 3rd parameter in Vue while Babel generates each additional child element as an additional function argument.

// Will not work with Vue 3 (RC 5)
// Vue.h(type, props, ...children)
Vue.h(Vue.Fragment, null, Vue.h("h1", null, "Hello World"), Vue.h("div", null, "Test with Vue 3"));

// Will work with Vue 3
// Vue.h(type, props, [children])
Vue.h(Vue.Fragment, null, [Vue.h("h1", null, "Hello World"), Vue.h("div", null, "Test with Vue 3")]);

Working Demo - Vue 3 online with JSX
I found this because I developed a online small JSX Compiler and was testing Vue 3 with it. For background the JSX Compiler jsxLoader can be used by modern browsers instead of Babel (for IE it downloads Babel Standalone). In my demo I modify the Vue.h function for compatibility with Babel Standalone and the jsxLoader that I wrote.

Original React and Preact Versions of the above page

Changes needed for Vue 3
I tested vue-next (RC 5) and adding the extra 2 lines of code does not break any existing unit tests, however to properly add it additional unit test and TypeScript definitions have to be added. (I've tested that locally as well and can make it work).

export function h(type: any, propsOrChildren?: any, children?: any): VNode {
  if (arguments.length > 3) {
    return createVNode(type, propsOrChildren, Array.from(arguments).slice(2));
  } else ...

@yyx990803 Can this be a feature Vue.h(type, props, ...children) be supported in Vue 3? Personally I think it should as it allows Vue 3 to behave similar to other JSX Libraries out of the box. If it can be supported I would be willing to submit the needed changes and unit tests.

@cereschen
Copy link

cereschen commented Aug 16, 2020

For example

function s(el: RendererElement) {
  if (el.props && Reflect.has(el.props,'v-show')) {
    let val = el.props['v-show']
    Reflect.deleteProperty(el.props, 'v-show')
    return withDirectives(h(el), [[vShow, val]])
  } 
  return el
}
 {s(<span v-show={false} >test</span>)}

Syntax sugar is not necessary

@bjarkihall
Copy link

@cereschen, the syntax sugar is being discussed so you don't need to wrap your components with a function or even implement it yourself - of course it's not "necessary", it's just to remove the need of the extra syntax for the user.

In your case you're telling vue that s is supposed to modify the behavior of the component and vShow is an undefined behavior which s handles and extracts. Why would you even want to declare this simple behavior in 2 places: in the component, where it's removed either way and in the implementation of the wrapper?

// vShow.ts:
import {withDirective, vShow} from 'vue'
export function vShow(el: RendererElement) {
  if (el.props && Reflect.has(el.props,'vShow')) {
    const val = el.props['vShow']
    Reflect.deleteProperty(el.props, 'vShow')
    return withDirectives(h(el), [[vShow, val]])
  } 
  return el
}

// Comp.tsx:
import {vShow as s} from 'vShow'
const Comp = () => s(<span vShow={false}>test</span>)

But you could just implement s like this:

import {vShowSimple as s} from 'vShow'
const Comp = () => s(<span>test</span>, false)

Or have the syntax sugar just take care of vShow without any import:

const Comp = () => <span vShow={false}>test</span>

vShow is just being used as the simplest directive to argue about, since it doesn't accept any argument or modifiers and has a simple boolean value.
As soon as you start adding more complexity/flexibility, you quickly start implementing more cases and wonder why this isn't just handled by vue instead.

I don't think JSX transformation should do too much magic, it's just a higher level syntax for h/createVNode - a Babel plugin could just add performance optimizations but nothing more, starting using vue with JSX/TSX shouldn't require too much setup or tools to get it working.
The main lack of JSX support is simply in the typings (you can get away with way too much without the IDE/TSC ever warning you of any syntactic errors) and how directives (including models etc) are applied to vNodes - I'm okay with it being wrapper-function based and later finding a way to have them abstracted with reserved props, if it doesn't affect performance too much, but that would not be a part of the JSX plugin but the vNode interpretation itself.

@cereschen
Copy link

cereschen commented Aug 17, 2020

@bjarkihall
Indeed, I found this approach problematic when dealing with v-models, but the reason I did it was vite doesn't work with babel loader
In fact, my way is just to wrap a function around the root, which can be done recursively, and not just v-show
But as I said variable passing is always a problem

@fgr-araujo
Copy link

fgr-araujo commented Aug 17, 2020

@isnifer But JSX reinvented the wheel. Template brings the original wheel back.

@ivanheral
Copy link

ivanheral commented Aug 22, 2020

Hi @ConradSollitt, your problem has been solved, will see it in the next release rc8.

I understand perfectly that it is not pleasant to use vue 3 like react using jsx / tsx, I love using templates but I consider that we have to give all possible options to the user. But if we don't have real alternatives ... I prefer to use 'h' like react /preact until we have our own 'h' (vh, vueh, createElement...).

Here I put some examples where the vue 3 syntax can be applied or not (v-if, v-for, v-show...):

vue3 ( webpack + babel): it's possible. Using vue-next-jsx.

vue3 (webpack + babel): it's not possible. Without vue-next-jsx.

vue3 (without build / transform): it's not possible (Maybe in the future 2020). Example:
vue3.

vite (esbuild / rollup): it's not possible now (Maybe in the future 2020). Actually, the file vuejsxcompat (the fix of @ConradSollitt) is used to work with h like react / preact. Now with the fix in vue 3, this file could be deleted.

Possible changes in esbuildService

/// transform esbuild
vue: { jsxFactory: 'h', jsxFragment: 'Fragment' }
// change concatenated
code += "\nimport { h, Fragment } from 'vue'"
// In the near future (own design)
vue: { jsxFactory: 'vh', jsxFragment: 'Fragment' }

In summary, people will do what they want.

Have a nice day 😊

@topaz-h
Copy link

topaz-h commented Aug 26, 2020

<Button onClick={handleClick} modifier={['passive', 'capture', 'once']}>btn</Button>

Add an new attribute to use with directives.
new attribute is Optional!

@bjarkihall
Copy link

Just wanted to point out, TS 4 is released so we can have named tuples now.
Here's an idea of steps to take to make the DX a bit nicer:

  1. Fix the TSX support (typings). It's a bit awkward to work with slots, emits and just components outside of .vue files in general.
  2. Add opt-in Directives support (built-in and custom directives, event modifiers and models) through custom attributes/props (vDirective, vModel, vOn, vShow, etc.) with named tuples which the runtime can apply to the vnode.
  3. HMR support - is it implemented in vue (in the h function), the build tool (e.g. vitejs, webpack), or the babel JSX plugin?
  4. Babel plugin can add performance flags and optionally more features, but I'd really like to be able to just use tsc to compile and run a vue project without any extra compilers, just .js/.jsx/.ts/.tsx files.

@ConradSollitt
Copy link

Thanks @ivanheral and @yyx990803 for making this update!

I’ve reviewed the code change and verified that the demo I created works in several browsers directly from a CDN using Babel Standalone or the JSX Complier I wrote. I don’t expect to develop large Vue 3 apps this way myself (or many people to do so) but it’s nice to know the option exists. Personally Vue Templates are my favorite and I found at work that non- developers (for example Data Scientist with some JS knowledge) can develop dynamic apps with Vue as they could understand the templates much quicker than they wanted to spend learning detailed DOM API’s or JSX.

In addition to the recent JSX testing I keep verifying new updates of Vue 3 with a variety of Vue 2 code I wrote and so far everything is working nicely!

@ivanheral
Copy link

wrapper it with 'h', could be a good option.

Example updated with v-show: vue3+v-show

import { createApp, ref, h } from "https://unpkg.com/vue@next/dist/vue.runtime.esm-browser.prod.js";
import htm from "https://unpkg.com/htm?module";
const html = htm.bind(h);

const App = {
  setup() {
    const show = ref(true);
    const show_hide = () => {
      show.value = !show.value;
    }
    return () => html `
        ${ show.value ? html`<span style="display: block;">Hello React</span>` : null }
        <span style="display: block;" v-show="${show}">Hello Vue 3</span>
        <button style="display: block;" onClick=${show_hide}>show/hide</button>
      `
  }
}
createApp(App).mount("#app")

@HerringtonDarkholme
Copy link
Member

With the upcoming template string type, we can have fully type checked JSX experience in Vue.

@TrungRueta
Copy link

I hope we can bring issue "HMR" in top priority.
I really love use TSX in project replace Vue template because for now Vetur in VScode make code hint really slow, specially when working in large Vue app. For my current project making admin, i need wait avg 3 ~ 4 seconds after stop typing to see vetur validate template/ts code, this issue not happend if writing in .tsx file because vscode make it really fast as hell. Only biggest problem when writing TSX for now is Vue v3 not support hmr, reload full page everytime make a chagne only ok when project small but fast run in issue when bigger.

If Vue team can find quick solution (or plugin webpack, maybe) support HMR feature it will be best! Other issue like directive - slots etc we had workaround solution really, so we can wait a little longer.

@noor-tg
Copy link

noor-tg commented Oct 7, 2020

any news about HMR support for jsx ?

@rkingon
Copy link

rkingon commented Oct 29, 2020

There is some discussion around slots here, but it's a bit hard to follow.

If you're using JSX, I would argue there is no reason to have slots vs props / renderProps (fn=scoped slot).

However, I am mostly curious if we will gain the ability to pass the default slot as a prop rather than as the child of an element.

This becomes very useful in React, the ability to use the children prop.

Thanks

@lhk
Copy link

lhk commented Nov 6, 2020

Hi, I'm new to Vue and trying to figure out best practices.
Ideally, I'd like to have type checking at compile-time.
But that seems tricky. The template code in my components only checks its proptypes at runtime.

Coming from React, I'm familiar with JSX and kind of liked it.
So I thought I'd try using Vue with it.
But the way it's supported is confusing to me.
The render functions section in the official docs seems rather verbose.
And the Babel JSX plugin for Vue doesn't seem like the official way to go.

I've tried reading this github issue but am a bit lost.
It would be great if someone could provide a bit more context.
What's the story on TSX in Vue?
What is missing from the current tooling and why do we need those Typescript 4.1 template strings?

@noor-tg
Copy link

noor-tg commented Nov 6, 2020

Hi, I'm new to Vue and trying to figure out best practices.
Ideally, I'd like to have type checking at compile-time.
But that seems tricky. The template code in my components only checks its proptypes at runtime.

Coming from React, I'm familiar with JSX and kind of liked it.
So I thought I'd try using Vue with it.
But the way it's supported is confusing to me.
The render functions section in the official docs seems rather verbose.
And the Babel JSX plugin for Vue doesn't seem like the official way to go.

I've tried reading this github issue but am a bit lost.
It would be great if someone could provide a bit more context.
What's the story on TSX in Vue?
What is missing from the current tooling and why do we need those Typescript 4.1 template strings?

hey @lhk . what do you expect from using tsx with vue ?
right now I am using tsx with vue 2 (not vue 3). and with the vue class component plugin. so it is less verbose than the docs (at least for vue 2).

As for vue 3 simply setup a new project with vue cli . select vue 3 and typescript when generating the application. and then install this plugin. then setup the plugin in babel config.

after the setup . you just make new tsx file and add some thing like

import { defineComponent } from 'vue'

const Component = defineComponent({
  setup() {
     return () => (<div>hi</div>)
  }
})

or

import { defineComponent } from 'vue'

const Component = defineComponent({
  render() {
     return (<div>hi</div>)
  }
})

jsx for vue 3
https://github.com/vuejs/jsx-next#installation

@dajpes
Copy link

dajpes commented Nov 16, 2020

Hi everybody, what is the difference in vue 3 to use or not use the defineComponent method?

export const ComponentOne  =defineComponent({
setup(){
   return () => <div>Hi</div>
   }
});

//and 

export const ComponentTwo  ={
 setup(){
     return () => <div>Hi</div>
    }
}

Both cases work just fine

@sqal
Copy link

sqal commented Nov 16, 2020

@dajpes https://v3.vuejs.org/api/global-api.html#definecomponent

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests