vuejs / core

🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
https://vuejs.org/
MIT License
47.14k stars 8.25k forks source link

Allow element/component resolution at runtime #876

Closed rigor789 closed 4 years ago

rigor789 commented 4 years ago

What problem does this feature solve?

In NativeScript we have a default set of elements, but plugins are allowed to register new elements - this results in most elements not being known at compile time, thus all unknown elements are treated as components in the render functions.

There's isNativeTag that's meant to solve this, however in our use-case it's not straight-forward. My initial approach was to treat everything as native elements, however this wouldn't work as all user-created components would stop working.

  // ParserOptions
  isNativeTag: tag => {
    if (isBuiltInComponent(tag)) {
      return false
    }
    // in case we encounter an element that is not a built-in component
    // we will assume it's a plugin provided element
    return true
  },

Is there a possibility to optionally transfer this responsibility to the runtime?

What does the proposed API look like?

type NewParserOptions = ParserOptions & { hasUnknownElements: boolean = false }

That would replace resolveComponent calls with a different resolver (perhaps resolveUnknownElement) that we could export from our runtime.

export function resolveUnknownElement(name: string): Component | string | undefined {
  if(isKnownElement(name)) {
    // we would return a string for known native elements to be treated as normal elements
    return name
  }
  // fall back to resolveComponent for the rest
  return resolveComponent(name)
}

I tried to export a custom resolveComponent function from our runtime, but the problem is the children are then treated as slots. This would still need solving with the resolveUnknownElement replacement, but I can't think of a solution yet.

Example of the issue

Render function when we have unknown elements treated as components

import { resolveComponent as _resolveComponent, createVNode as _createVNode, withCtx as _withCtx, openBlock as _openBlock, createBlock as _createBlock } from "vue"

export function render(_ctx, _cache) {
  const _component_NativeElement = _resolveComponent("NativeElement")
  const _component_CustomComponent = _resolveComponent("CustomComponent")
  const _component_StackLayout = _resolveComponent("StackLayout")

  return (_openBlock(), _createBlock(_component_StackLayout, null, {
    // treated as slots, even though it should be a simple array of vnodes 
    default: _withCtx(() => [
      _createVNode(_component_NativeElement),
      _createVNode(_component_CustomComponent)
    ]),
    _: 1
  }))
}

Versus when the element is treated as a native element (replaced with div)

import { resolveComponent as _resolveComponent, createVNode as _createVNode, openBlock as _openBlock, createBlock as _createBlock } from "vue"

export function render(_ctx, _cache) {
  const _component_NativeElement = _resolveComponent("NativeElement")
  const _component_CustomComponent = _resolveComponent("CustomComponent")

  return (_openBlock(), _createBlock("div", null, [
    _createVNode(_component_NativeElement),
    _createVNode(_component_CustomComponent)
  ]))
}

📢 I'm very open for discussion, as I may be solving the wrong problem here.

yyx990803 commented 4 years ago

I think f529dbd happens to address the issues you have here:

We can simply replace resolveComponent with resolveDynamicComponent when hasUnknownElements is true. Does that solve your problem?

rigor789 commented 4 years ago

That sure sounds like the way to go!

So with hasUnknownElements set to true the following templates would produce the same render function:

<StackLayout>
  <Label/>
</StackLayout>
<!-- would be the same as -->
<component is="StackLayout">
  <component is="Label" />
</component>

Is that correct?

yyx990803 commented 4 years ago

Yes, that's correct

yyx990803 commented 4 years ago

Actually, since element vnodes can now accept slots as children, your approach of exporting a custom resolveComponent from your runtime should already work, maybe we don't need the option at all.

rigor789 commented 4 years ago

I'll give it a try and report back!

rigor789 commented 4 years ago

That seems to be working with alpha.10, thanks!