vuejs / jsx-vue2

monorepo for Babel / Vue JSX related packages
https://jsx-vue2-playground.netlify.app/
1.47k stars 96 forks source link

Vue 3 JSX Design #141

Closed Amour1688 closed 3 years ago

Amour1688 commented 4 years ago

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

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

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',
        }}
      />
    );
  },
}
HcySunYang commented 4 years ago

@edimitchel Maybe you posted the wrong link, that is mine. 🤔

edimitchel commented 4 years ago

@edimitchel Maybe you posted the wrong link, that is mine. 🤔

Yes haha ! Sorry, I first posted his link then I found yours ! I change it !

bjarkihall commented 4 years ago

any | [any] leads to the type any, so allowing it would break the typings. That's why you'd have to separate the definitions:

const value: any;
<Comp vModel={value} /> // This is ambiguous, how would you type it? Should a model value have a narrower type?
<Comp vModelValue={value} /> // You could add yet another attribute type just to skip the array
<Comp vModel={[value]} /> // I think most users would prefer this instead since it's a single syntax

// For multiple models:
const value2: any;
const modifiers: string[] | Record<string, boolean>;
const modifiers2: string[] | Record<string, boolean>;
// this solves the ambiguity for multiple models:
<Comp vModels={[[value, 'first', modifiers], [value2, 'second', modifiers2]]} />
// this is like the event syntax described by @wonderful-panda:
<Comp vModels={{
  first: [value, modifiers],
  second: [value2, modifiers2]
}} />
// I guess this would be possible too, but can't work on vModels straight for the same reason as described above:
<Comp vModelValues={{
  first: value,
  second: value2
}} />

// if value and value2 would be named the same as the args this would become:
const first: any;
const second: any;
<Comp vModelValues={{ first, second }} />;

The any type is really bad for type inference so since the value can be of type any, we have to be able to clearly define if you're passing a value (any), array of values ([any, ...]) or an array of arrays of values ([[any, ...], [any, ...], ...]) - these are vModelValue, vModel and vModels. If the value has a clearer type, I guess we could make all of these work in a single attribute but it's not clear which types are valid and which are not - it would potentially force users to cast to unknown and then to the valid type, which is much more confusing for users and maintainers than just having separate attributes.

The vCustom example also breaks typings - you'd have to provide a "string | Directive" type to the tuple and have a single attribute like vDir/vDirective to resolve/use the directive.

The object syntax for modifiers is probably just linked to destructuring, judging by how they are used in the vModel definitions, but I guess this also opens up for passing more types of payload then booleans to the directive handlers - although that wouldn't be supported by template syntax since modifiers are either present there or not.

I've been experimenting with writing down all of the types we've discussed so far, while looking through vue-next repo, especially the current state of the jsx definitions and the types and tests available, while reading through all of the rfcs. I have some experimental types but would like to work on them a bit further.

If people would like to experiment themselves, they can define vue-shim.d.ts in the root of their project, the directives/models/events attributes could be fewer, I just wanted to include the ideas for completion:

import Vue, {Slot, Slots} from 'vue';
declare module "vue" {
  export interface VueJSX {

    // Basic props/attributes:
    v?: string | string[] | {[v: string]: any}; // escape hatch, can use the template compiler to resolve v- strings and their values
    vShow?: boolean;
    vHtml?: string;
    vSlot?: vSlot;
    vSlots?: vSlots;
    className?: string; // would be nice to make transition from react easier, allow both class and classname

    // Directives:
    vD?: vD;
    vDir?: vD;
    vDirective?: vD;
    vDs?: vDs;
    vDirs?: vDs;
    vDirectives?: vDs;
    vDVs?: vDVs;
    vDirectiveValues?: vDVs;

    // Models:
    vM?: vM;
    vModel?: vM;
    vMs?: vMs;
    vModels?: vMs;
    vMV?: vMV;
    vModelValue?: vMV; // values have any as type, which would break other typings
    vMVs?: vMVs;
    vModelValues?: vMV;

    // Events:
    vE?: vE;
    vOn?: vE;
    vEvent?: vE;
    vEs?: vEs;
    vOns?: vEs;
    vEvents?: vEs;
    vEV?: vEV;
    vEventValue?: vEV;
    vEVs?: vEVs;
    vEventValues?: vEVs;
  }

  // This seemed to be the only thing silencing errors for native jsx elements, like <a />
  export interface HTMLAttributes extends VueJSX {}
  // Is there a way to make e.g. this possible: withDirectives(<div />, [[resolveDirective("demo"), message, "foo", { a: true, b: true }]])
  export interface VNode extends JSX.Element {}
}
declare global {
  namespace JSX {
    interface Element {}
    // I didn't find out how to make the slots syntax become a valid JSX child:
    // interface ElementChildrenAttribute {
    //   vSlots: vSlots;
    // }
    interface IntrinsicAttributes extends VueJSX {}
  }
}

Here are some experimental types:

// How should we go about any vs handpicked values vs boolean (| undefined | null)?
type AnyType = null | undefined | boolean | number | string | symbol | bigint | Function | any[] | object;
type CleanValueType = boolean; // val === any would break type safety

// Default values:
type vdir = string | Directive;
type vval = any; // this can cause trouble!
type evval = Function;
type varg = string;
type vmods = string[] | Record<string, boolean>;

// Directive Tuples:
type vDirMain<
  dir = vdir,
  val = vval,
  arg = varg,
  mods = vmods
> =
  | [dir, val, arg, mods]  // v-demo:foo.a.b="message"
  | [dir, val, arg | mods] // v-demo:foo="message" | // v-demo.a.b="message"
  | [dir, val]             // v-demo="message"
  | [dir] | dir            // v-demo, allow plain dir string | Object

type vDirNameless<
  val = vval,
  arg = varg,
  mods = vmods
> =
  | [val, arg, mods]       // v-demo:foo.a.b="message"
  | [val, arg | mods]      // v-demo:foo="message" | v-demo.a.b="message"
  | [val]                  // v-demo="message"
  | CleanValueType         // v-demo

// Directive Object:
type vDirsObjType = {[key: string]: vDirNameless};

type vDirsObj<
  dirs = vDirsObjType,
  val = vval,
  arg = varg,
  mods = vmods
> = {
  [K in keyof dirs]: (vDirNameless<val, arg, mods> | vArgObj<val, vArgObjType, mods>)
}

// Arg Object:
type vArgObjType = {[key: string]: vArgless};
type vArgless<
  val = vval,
  mods = vmods
> = 
  | [val, mods]
  | [val]
  | CleanValueType

type vArgObj<
  val = vval,
  arg = vArgObjType,
  mods = vmods
> = {
  [K in keyof arg]: vArgless<val, mods>
}

// Final construction:
type vD<d=vdir,v=vval,a=varg,m=vmods> = vDirMain<d,v,a,m>; // [dir, val?, arg?, mods?]
type vDs<d=vdir,v=vval,a=varg,m=vmods> = vDirsObj<vDirsObjType,v,a,m> | vD<d,v,a,m>[]; // [[dir, val?, arg?, mods?], ...] | {dir: [val, arg?, mods?] | boolean, ...} | { dir: { arg: [val, mods?] | boolean } }
type vM<v=vval,a=varg,m=vmods> = vDirNameless<v,a,m>; // [val, arg?, mods?] | boolean
type vMs<v=vval,a=varg,m=vmods> = vArgObj<v,vArgObjType,m> | vM<v,a,m>[]; // [[val, arg?, mods?] | boolean, ...] | { arg: [val, mods?] | boolean }
type vE<v=evval,a=varg,m=vmods> = vDirNameless<v,a,m>; // same as in model
type vEs<v=evval,a=varg,m=vmods> = vArgObj<v,vArgObjType,m> | vE<v,a,m>[];

// Value types:
type vMV<v=vval> = v; // value typings are loose
type vMVs<v=vval,arg=varg> = v[] | {[K in keyof arg]: v};
type vEV<v=evval> = v;
type vEVs<v=evval,arg=varg> = v[] | {[K in keyof arg]: v};
type vDVs<v=AnyType> = {
  [d: string]: {
    [a: string]: v // v === any lets way too many things slip through...
  }
}

// Slots: (I'm still not sure how to go about mixing VNodes and JSX.Element types and how to specify ElementChildrenAttribute)
type vSlot = Slot | VNode | JSX.Element;
type vSlots = Slots | vSlot[];

Which I made some runtime TS tests for (just to see if something was breaking when editing the types):

// SINGLE DIR TESTS:
const dirStr = 'demo';
const dirObj = resolveDirective('demo');
const val = 0 as AnyType;
const arg = 'foo';
const modsArr = ['a', 'b'];
const modsObj = {a: true, b: true};

// @ts-expect-error
const a0Err: vD = val;
const a0: vD = dirStr;
const a0D: vD = dirObj;
// @ts-expect-error
const a1Empty: vD = [];
// @ts-expect-error
const a1Err: vD = [val];
const a1: vD = [dirStr];
const a1Obj: vD = [dirObj];
const a2: vD = [dirStr, val];
const a3: vD = [dirStr, val, arg];
const a3Arr: vD = [dirStr, val, modsArr];
const a3Obj: vD = [dirStr, val, modsObj];
const a4Arr: vD = [dirStr, val, arg, modsArr];
const a4Obj: vD = [dirStr, val, arg, modsObj];
// @ts-expect-error
const a4Err: vD = [dirStr, val, modsArr, arg];

// MULTIPLE DIRS TESTS:
const dirStr2 = 'demo2';
const dirObj2 = resolveDirective('demo2');
const val2 = 1 as AnyType;
const arg2 = 'bar';
const modsArr2 = ['c', 'd'];
const modsObj2 = {c: true, d: true};
// @ts-expect-error
const b0Err: vDs = val2;
// @ts-expect-error
const b0: vDs = dirStr2;
// @ts-expect-error
const b0D: vDs = dirObj2;

const ba0: vDs = []; // can eliminate with spread operator, but why not...
// @ts-expect-error
const ba1Err: vDs = [val2];
const ba1: vDs = [dirStr2];
const ba1Obj: vDs = [dirObj2];
// @ts-expect-error
const ba1TooMany: vDs = [dirStr2, val2];
// @ts-expect-error
const ba2_0: vDs = [[]];
const ba2_1: vDs = [[dirStr]];
const ba2_2: vDs = [[dirStr2, val2]];
// @ts-expect-error
const ba2_2_reverse: vDs = [[val2, dirStr2]];
// @ts-expect-error
const ba2_2_blended: vDs = [val2, [val2, dirStr2]];

const ba3: vDs = [[dirStr2, val2, arg2]];
const b3Arr: vDs = [[dirStr2, val2, modsArr2]];
const b3Obj: vDs = [[dirStr2, val2, modsObj2]];
const b4Arr: vDs = [[dirStr2, val2, arg2, modsArr2]];
const b4Obj: vDs = [[dirStr2, val2, arg2, modsObj2]];
const b4Obj_blended: vDs = [[dirStr2, val2, arg2, modsObj2], [dirStr2], [dirObj2]];
// @ts-expect-error
const b4Err: vDs = [[dirStr, val, modsArr, arg]];

// MULTIPLE DIRS OBJECT TEST:
const c1Empty: vDs = {}; // couldn't find a way to eliminate empty object types, but why not...
const c1Number: vDs = {
  0: [val2]
}
const c1: vDs = {
  [dirStr2]: [val2]
};
const c2: vDs = {
  [dirStr]: [val],
  [dirStr2]: [val2]
};
const c3: vDs = {
  [dirStr]: [val, arg, modsArr],
  // @ts-expect-error
  [dirStr2]: [val2, modsArr2, arg2],
  on: {
    click: [e=>console.log(e.target), ['prevent', 'stop']]
  }
};
const c4: vDs = {
  // @ts-expect-error
  0: [val, arg, modsArr, modsArr],
  1: undefined,
  2: null,
  3: true,
  4: false,
  // @ts-expect-error
  5: val,
  6: {}, // this is hard to block...
  // @ts-expect-error
  7: {foo:'bar'},
  // @ts-expect-error
  [dirStr]: [val, arg, arg, modsArr],
  // @ts-expect-error
  [dirStr2]: [dirStr2, val2, arg2, modsArr2],
};
const cNested: vDVs = {
  on: {
    click: e=>{},
    mousemove: e=>{}
  },
  model: {
    val,
    val2
  },
  // @ts-expect-error
  foo: ['aa', 'a', 'a', 'a', 'a']
}

The fun starts when types like directive names, modifiers are extended with string literals, event names/handlers are mapped with e.g. WindowEventMap and we can write utility functions to generate the structures. These are just proof of concept, since I wanted to see how the typings would hold together in regards to complexity vs. flexibility. Hopefully it can help someone else too and maybe someone with better TS/TSX skills can guide this to a better/cleaner path, these are just the collections of most of the ideas we've had so far. :)

HcySunYang commented 4 years ago

@bjarkihall Great and comprehensive explanation, thanks, did you take a look at https://github.com/HcySunYang/vue-next-jsx? Actually that is our goal, it has dts testing: test-dts and runtime support: runtime, maybe you can contribute something if you are interested.

bjarkihall commented 4 years ago

@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 commented 4 years ago

@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 commented 4 years ago

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 commented 4 years ago

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 commented 4 years ago

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 commented 4 years ago

@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 commented 4 years ago

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 commented 4 years ago

@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 commented 4 years ago

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 commented 4 years ago

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 commented 4 years ago

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 commented 4 years ago

@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 commented 4 years ago

@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 commented 4 years ago

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

ivanheral commented 4 years ago

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 commented 4 years ago
<Button onClick={handleClick} modifier={['passive', 'capture', 'once']}>btn</Button>

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

bjarkihall commented 4 years ago

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 commented 4 years ago

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 commented 4 years ago

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 commented 4 years ago

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

TrungRueta commented 3 years ago

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 commented 3 years ago

any news about HMR support for jsx ?

rkingon commented 3 years ago

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 commented 3 years ago

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 commented 3 years ago

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 commented 3 years ago

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 commented 3 years ago

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