anthonygore / vue-template-extension-loader

A Webpack loader for extending Vue templates
6 stars 0 forks source link

Proposal #1

Open anthonygore opened 6 years ago

anthonygore commented 6 years ago

It'd be nice to be able to extend Vue templates. This would allow you to make similar components with minor variations in the template and still keep your code DRY.

The argument for doing this, and a good API, is summed up nicely here:

https://github.com/vuejs/vue/issues/5401#issue-220428676

There seems to be a lot of support for this feature as indicated by the amount of discussion in these two issues:

https://github.com/vuejs/vue/issues/5401 https://github.com/vuejs/vue/issues/6811

However, this feature is unlikely to be added to the Vue core as it doesn't interest Evan You, which is made particularly clear in his final comment in this thread:

https://github.com/vuejs/vue/issues/5401#issuecomment-316376968

I do agree with his assertion that adding the feature to Vue core would make template syntax confusing for most users who don't need this feature.

For this reason, I think it's best to implement this feature as a third-party package. Vue Loader 15 has provided an interesting possibility in the form of custom blocks for single file components:

https://vue-loader.vuejs.org/guide/custom-blocks.html

The idea of this repo is to create a Webpack loader that would process custom blocks that allow the template extension syntax/API that's been proposed

Extend.vue

<template lang="extend">...</template>

webpack.config.js

{
  module: {
    rules: [
      {
        resourceQuery: /blockType=extend/,
        loader: 'vue-template-extension-loader'
      }
    ]
  }
}

Any thoughts on the validity of idea? If you're interested, be sure to comment or "watch" the repo so we can get some momentum.

ThaDaVos commented 6 years ago

I would still prefer a syntax like the following, maybe instead of <template> use <extension> or <extend> - The reason I prefer this syntax is because you extend based on slots and also, you don't have to extend all slots, only the ones you specifcally want.

Also by using slots it's clear for someones else where the extended template stuff will be placed.

I'm all in for adding this to vue-loader instead of vue!

<!-- Base.vue -->
<template>
  <div>
    <slot name="header">
      <h3>Default header</h3>
    </slot>

    <slot><!-- yup, default --></slot>

    <slot name="body">
      <p>Default body</p>
    </slot>

    <slot name="footer">
      <!-- no footer by default -->
    </slot>

  </div>
</template>
<!-- SomeComponent.vue -->
<template slot="header">
<!-- Headery stuff -->
</template>

<template slot="body">
<!-- Body Stuff -->
</template>

<template slot="footer">
<!-- footer stuff -->
</template>

<script>
import Base from 'Base.vue';

export default {
    name: 'SomeComponent',
    extends: Base,
}

</script>

with <extension> instead of <template>

<!-- SomeComponent.vue -->
<extension slot="header">
<!-- Headery stuff -->
</extension>

<extension slot="body">
<!-- Body Stuff -->
</extension>

<extension slot="footer">
<!-- footer stuff -->
</extension>

<script>
import Base from 'Base.vue';

export default {
    name: 'SomeComponent',
    extends: Base,
}

</script>
ThaDaVos commented 6 years ago

With the above syntax I can use the component as follows

<some-component>
    <h1>Hello World</h1>
</some-component>

The Default slot can still be used because I didn't override it in my extension

anthonygore commented 6 years ago

Hey @dvdbot, thanks for the reply. My issue with using <slot> for this is that slots are conceptually different to template extending and so I think this would cause confusion. For example, slots insert a new component inside, template extension just inserts markup from another file. Slots are resolved at runtime while template extension happens during the build process etc.

For that reason, I think it should work exactly as you've described except instead of using <slot> element, use something like <block> or <extend> or whatever

ThaDaVos commented 6 years ago

If we're going to use <block> or <extend> can there be an option, if we allow naming, if a <block> or <extend> is unused it will be converted to a <slot> with the same name, if possible? Maybe as attribute?

<block persistent="false" name="header"></block>

So by setting persistent to false instead of the default value true - if unused/not overridden the element will be replaced with <slot> else if unused/not overridden maybe removed so there are no compile errors. Also when persistent is set to false you won't be able to override the <block> in a sub-sub-component

lmiller1990 commented 5 years ago

Would this be solved by writing a render function combined with JSX? I too have experienced pain points in reusing components templates... but html templates just feel inherently less powerful than JSX, although (imo) a little more approachable for the average dev.

I've had some success writing render functions with the Vue JSX preprocessor for complex layouts. Rather than making a webpack plugin to extend templates, can we just use JSX and get the full power of JS? Don't use JSX everywhere, just the particular complex components.

Maybe I haven't work on a large enough app to feel the problem in the same way others have - just posting another point of view! Webpack is a great tool and I agree with Evan in that vue/core is not the place for this, but a plugin is a good fit.

ThaDaVos commented 5 years ago

@lmiller1990 I think the idea of this is to keep it simple :P

milworm commented 5 years ago

@anthonygore I think your request is a must have feature for VueJS!

ThaDaVos commented 3 years ago

Any updates?

Matt-Deacalion commented 3 years ago

I just arrived at this requirement and would love to see this implemented as a loader. Having this in the build process feels the cleanest (separate from runtime resolved slots). It reminds me of inheritance in Django and Jinja2 templates.

lmiller1990 commented 3 years ago

Why doesn't someone just implement this as a webpack loader? Prior art is always a good way to get new features moving forward.

I personally won't use this, but I see no technical reason why this can't be implemented right now as a webpack plugin for Vue loader.