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

Ability to create compile-time/build-time components to include HTML/SVG/XML-like partials without losing scope #171

Open
darkylmnx opened this issue May 9, 2020 · 1 comment

Comments

@darkylmnx
Copy link

What problem does this feature solve?

Not sure if it should be done here or in the vue-loader repo but here it is:

In many project we often need to

  1. import SVG files into the rendered HTML to be able to customize it with CSS
  2. split a page that has a lot of markup into smaller chunks for better visiblity and team work

But... The current Vue components and Vue API:

  • lacks of an "on-the-fly include" system for HTML/SVG/XML-like asset files that can benefit from attribute inheritance
  • has performance issues for this common task : even if functional components are more performant than stateful components, it still have an unecessary cost for simple HTML/SVG/XML partials that are just created to split the markup and that just need attribute inheritance
  • CSS scope issues with functional components
  • CSS scope issues with v-html
  • performance again: props are unneededly passed while they could just be "copied/included" at build-time from in the parent's context

Whether it is an HTML partial or SVG code, most of the time it will have a root node and be static. Creating a component is the way to go but is most of the time repetitive and will be instantiated and rendered at runtime, which has uneeded performance cost.

  • A compile-tile/build-time component is basically just an "include" that is rendered during build in the regular runtime parent component.
  • It could have an attribute like a path to an asset file or a static XML-like string that would be transformed to render functions inside the parent and not as a runtime component
  • Attributes would just be copied on the root node like static attributes and props could be transformed to expressions being part of the parent

Of course, by nature, these build-time/compile-time components are purely static

What does the proposed API look like?

Currently, if an Vue app needs to import HTML/SVG/XML-like assets, there are 3 known solutions:

1. using a webpack plugin + a require to the file and include it with v-html

<div v-html="require('@/assets/illustration.svg?include')" />
<div v-html="require('@/assets/random-partial.html?include')" />

Issues: impossible to add HTML attributes at the root of the SVG, CSS scope issues that forces to use the deep selector, CSS scope hash not copied to the rendered root element
see: vuejs/vue-loader#1259

2. manually create a component for each asset file to include

Issues: unneededly repetitive, component instantiation cost, functional components need a repetitive boilerplate to inherit attributes since it does not work like regular components:

<template functional>
  <section :class="[data.staticClass, data.class]" v-bind="data.attrs" :style="[data.staticStyle, data.style]">
    ...
  </section>
</template>

see: vuejs/vue#7554

3. automatically create functional components on-the-fly with vue-template-loader

import MySVG from 'my.svg?vue-template'

Issues: caveats of functionnal components, performance, uneeded component instantiation
See: https://codeburst.io/managing-svg-images-in-vue-js-applications-88a0570d8e88

Solution and API

1. The include solution

Use a "build-time" and a "src" prop on the core dynamic component

partial.html (our asset file, could be an SVG or any XML-like with a single root node inside, on not single with Vue 3)

<p class="my-own-class">
  hello i'ma a partial
</p>

App.vue (our "real" runtime parent component file)

<div class="parent">
  <component src="@/assets/partial.html" built-time class="inherited-attribute" />
</div>

No :is prop needed since it's a file include, example would work same with a require( ... ).

App.vue would be transformed to a render function for the runtime that matches this markup:

<div class="parent">
  <p class="inherited-attribute my-own-class">
    hello i'ma a partial
  </p>
</div>

2. The manual component solution

Use a "build-time" propr on a regular imported component or set the component's definition to buildTime: true just like for functionnal components functional: true

Partial.vue

  • props: ['static', 'dynamic'] would be set on the component
  • all the code is evaluated in the parent's scope because all this code is just copied to the parent
<template build-time>
  <section class="my-own-class">
    <!-- some long markup -->
    <p>hello {{ props.static/* or just static */ }}, nice to meet {{ props.dynamic /* or just dynamic */ }}</p>
    <slot />
    <!-- more long markup -->
  </section>
</template>

App.vue (our "real" runtime parent component file)

<div class="parent">
  <Partial class="inherited-attribute" static="world" :dynamic="name" built-time>
    <p>This is a slot</p>
  </Partial>
</div>

App.vue would be transformed to a render function for the runtime that matches this markup:

<div class="parent">
  <section class="inherited-attribute my-own-class">
    <!-- some long markup -->
    <p>hello world, nice to meet {{ name }}</p>
    <p>This is a slot</p>
    <!-- more long markup -->
  </section>
</div>

This would be the API and implementation.
Globally, this is just like doing SSR for components with static markup at build-time.

  • Props are transformed to either static text or ** an expression in the parent's scope**
  • Slots are just copied/included as render functions

PROS :

  • CSS scope hashed could be copied on each markup element as we are doing includes here and replacing instead of instantiating
  • Performance: no more instantiation or function execution at build-time, it just breaks down to render functions each time
  • Performance: code can be hoisted since it's all static
  • DX: long static templates can be shortened to smaller chunks for better readibility and teamwork without sacrificing performance
  • DX: possibility to include easily HTML/SVG/XML-like partials without external loaders and the issues it brings
  • DX: no more repetitive functional component creation with attribute inheritance boilerplate to re-create each time

CONS:

  • API being a little bit different from a runtime component, it may be confusing, but I feel this kind of decision was already made by making functional components not inheriting attributes like regular components, so I guess it's not really an issue here
  • bundle would become bigger if people use partial many times. This is the Bundle size VS browser performance/calculation dilema that has all build-time frameworks. Same issue for Svelte. I guess this decision will just be upto the end user.

Hope this is all clear.

@posva posva transferred this issue from vuejs/vue May 9, 2020
@posva
Copy link
Member

posva commented May 9, 2020

Transferred to RFCs to follow its process

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

2 participants