Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
Ben Nadel at RIA Unleashed (Nov. 2009) with: Kimberly Morrow
Ben Nadel at RIA Unleashed (Nov. 2009) with: Kimberly Morrow@KimberlyMorrow )

Projecting Content Into The Root Application Component Using Slots In Vue.js 2.6.6

By Ben Nadel on

In almost every Vue.js demo that I've looked at, the root Vue() instance is created with an "el" property and a render() function that does nothing but render the "App" component. This approach instantiates the App component in a context in which it has no children (though you could, theoretically, define children in the root render() function). But, what if I wanted to define children in the top-level HTML page? It turns out, if you omit both the render() function and the "template" property in the root Vue() definition, Vue.js will use the in-DOM HTML as the root Vue() template. This allows the
App component to project HTML from the top-level page using the normal Slot-based architecture.


 
 
 

 
 
 
 
 

Run this demo in my JavaScript Demos project on GitHub.

View this code in my JavaScript Demos project on GitHub.

When you pre-compile your Vue.js application, all of your HTML templates get transformed into createElement() calls (or h() calls, if you favor brevity over readability). As such, Vue.js doesn't have to perform any runtime compilation when you open your application. However, if you allow HTML to be projected from the top-level page, Vue.js can't know about it ahead-of-time. As such, Vue.js will have to compile the top-level HTML at runtime. This may impact the time-to-first-interaction of the application. And, it will also require the JavaScript bundle to include the Vue.js compiler, not just the Vue.js runtime. As such, this approach is probably not recommended.

That said, I wanted to try it anyway as I can think of one or two use-cases in which the top-level HTML page may serve as the input for some sort of Domain Specific Language (DSL). So, to set up this thought-experiment, let's look at the top-level HTML that I use for all of my demos:

  • <!doctype html>
  • <html lang="en">
  • <head>
  • <meta charset="utf-8" />
  •  
  • <title>
  • Projecting Content Into The Root Application Component Using Slots In Vue.js 2.6.6
  • </title>
  •  
  • <link href="build/main.bdcc718aeab47815b2cb.css" rel="stylesheet"></head>
  • <body>
  •  
  • <h1>
  • Projecting Content Into The Root Application Component Using Slots In Vue.js 2.6.6
  • </h1>
  •  
  • <my-app>
  • <p>
  • <em>Loading files...</em>
  • </p>
  •  
  • <p>
  • npm Run Scripts:
  • </p>
  •  
  • <ul>
  • <li>
  • <strong>npm run build</strong> &mdash; Compiles the .vue file into bundles.
  • </li>
  • <li>
  • <strong>npm run watch</strong> &mdash; Compiles the .vue file into bundles and then watches files for changes.
  • </li>
  • </ul>
  • </my-app>
  •  
  • <script type="text/javascript" src="build/main.77fde5cf302a90a08010.js"></script>
  • <script type="text/javascript" src="build/runtime.ba170225256633bfa10a.js"></script>
  • <script type="text/javascript" src="build/vendors~main.222974fc2903e0d3c178.js"></script>
  • </body>
  • </html>

Normally, the content in between my "my-app" elements would get wholly replaced with the render-content of my App component. But, in this case, I'm going to omit both the "template" and "render()" properties in my root Vue() instance. This will cause the above outerHTML of the "my-app" element to be used as the root Vue() template:

  • import "./main.polyfill";
  •  
  • // ----------------------------------------------------------------------------------- //
  • // ----------------------------------------------------------------------------------- //
  •  
  • // Import core classes.
  • import Vue from "vue";
  •  
  • // Import application classes.
  • import AppComponent from "./app.component.vue";
  •  
  • // ----------------------------------------------------------------------------------- //
  • // ----------------------------------------------------------------------------------- //
  •  
  • new Vue({
  • el: "my-app",
  •  
  • // When we omit both the render() function and the "template" property, the in-DOM
  • // markup of <my-app> will serve as the component's template. As such, we have to
  • // tell Vue.js how to map custom elements (like <my-app>) onto Vue Components.
  • // --
  • // NOTE: While I am not using other custom elements in this demo, this approach
  • // allows other custom elements to be consumed in the top-level HTML page. Though,
  • // they have to be identified using lowercase Kebab-Case.
  • components: {
  • "my-app": AppComponent
  • }
  •  
  • // Instead of using a render() function, which is the most common example, we're
  • // going to allow the runtime HTML to act as the component template. This allows the
  • // runtime HTML to be projected into the AppComponent using standard slotting.
  • // --
  • // WARNING: This requires RUNTIME COMPILATION of the in-DOM HTML, which may have a
  • // negative affect on performance and time-to-first-interaction.
  •  
  • // I render the root component of the application into the DOM.
  • // render: ( createElement ) => {
  • //
  • // return( createElement( AppComponent ) );
  • //
  • // }
  • });

As you can see, the only thing I'm doing in my root Vue() instance is mapping the custom HTML elements onto Vue.js component. In this case, I only have one custom component, "my-app"; however, if I wanted to use other Vue components in my top-level HTML page, I'd have to map them here as well.

And, now that the "my-app" HTML element is being mapped to my Vue.js AppComponent, I can use Slot-based content projection in my AppComponent:

  • <style scoped src="./app.component.less" />
  •  
  • <template>
  •  
  • <div class="app">
  • <p>
  • I am the AppComponent.
  • And, this is my <strong class="label">Projected Content</strong>:
  • </p>
  •  
  • <div class="projected">
  • <slot></slot>
  • </div>
  •  
  • <p>
  • Fascinating, right?
  • </p>
  • </div>
  •  
  • </template>
  •  
  • <script>
  •  
  • export default {
  • // ...
  • };
  •  
  • </script>

Here, the content from my top-level HTML page is being projected right in the middle of my AppComponent. And, when we run this page, we get the following browser output:


 
 
 

 
 Projecting root HTML content into the App component using slots in Vue.js 2.6.6. 
 
 
 

As you can see, the HTML markup from the top-level page has been successfully projected into the AppComponent using the standard Slot syntax.

I can't imagine that there are too many uses-cases for this kind of approach; and, I will remind you that I am a total beginner with Vue.js 2.6.6; however, I can think of one or two uses-cases in which I might want to enable a non-developer to be able to define consumable HTML mark-up in the index file of a project. If nothing else, this thought-experiment is just helping me understand the Vue.js application life-cycle a bit better.


Reader Comments

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
NEW: Some basic markdown formatting is now supported: bold, italic, blockquotes, lists, fenced code-blocks. Read more about markdown syntax »
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.