So far, we have always kept our controllers separate from templates/views and styles. You may not be pleased with SFC as there are mixing concerns. To some extent, this is a matter of taste, but as components grow, it makes less and less sense to keep everything in a single file.
Actually, we can also separate the different parts of a component with Vue.js and TypeScript. We'll see how in this section.
We'll use the previous code sample as a starting point. Make a copy of the Chapter09/05-vue-ts folder from this book's assets if you want to follow along.
We then need to add the following dependencies:
npm install vue-template-loader webpack-stream --save-dev
Now, we will recreate the ExampleSFC component using multiple files. We don't need that file anymore, so you can safely delete it: src/components/ExampleSFC.vue.
Create an example.ts file under src/components with the following content:
import {Component, Prop, Vue} from 'vue-property-decorator'; @Component export default class Example extends Vue { @Prop({ default: 'default message', required: false, }) private message!: string; }
For brevity, we've only kept the message property.
Now, create example.html next to the TS file. For now, we'll simply render the message property:
<span>The message is: {{message}}</span>
To establish a link between the component class and our HTML template, we'll leverage the vue-template-loader (https://github.com/ktsn/vue-template-loader) library that we added earlier. This library is a webpack loader: https://webpack.js.org/loaders.
To load our HTML template, we need to create a shim.
Go ahead and create a shims-html.d.ts file in the src folder with the following content:
// reference: https://dev.to/georgehanson/building-vuejs-
// applications-with-typescript-1j2n declare module '*.html' { import Vue, {ComponentOptions, FunctionalComponentOptions} from
'vue'; interface WithRender { <V extends Vue, U extends ComponentOptions<V> |
FunctionalComponentOptions>(options: U): U; <V extends typeof Vue>(component: V): V; } const withRender: WithRender; export default withRender; }
Don't worry too much if you don't understand this code; it is quite involved. All you need to care about is the fact that this shim simply helps TypeScript understand what is returned when HTML files are imported. Without this, TypeScript wouldn't be able to compile the next bit.
Now that we have the template and HTML shim in place, let's go ahead and load our template.
Replace the code in the src/components/example.ts file with the following code:
import { Component, Prop, Vue } from 'vue-property-decorator'; import WithRender from './example.html'; @WithRender @Component export class Example extends Vue { @Prop({ default: 'default message', required: false, }) private message!: string; }
Here, we are importing our template file and using the WithRender token as a decorator for our class.
There are a few more steps we need to take before we've completed our task.
Obviously, we need to adapt the src/App.vue component so that it loads the new component:
<template> <div id="app"> <Example message="Custom message" /> </div> </template> <script lang="ts"> import {Component, Vue} from 'vue-property-decorator'; import {Example} from './components/example'; @Component({ components: { Example, }, }) export default class App extends Vue { } </script> <style> </style>
Finally, we need to customize the build of Vue in order to configure the HTML template loader.
If you try to launch the application at this point, you'll probably get the following error:
Failed to compile. ./src/components/example.html 1:4 Module parse failed: Unexpected token (1:4) You may need an appropriate loader to handle this file type. > The message is: {{message}}
As the error indicates, no loader can currently handle the HTML files.
Just like the Angular CLI, Vue.js sports a batteries included Webpack build. Moreover, similar to Angular, Vue.js also allows us to tweak the build process as required. The official documentation covers this in detail: https://cli.vuejs.org/guide/webpack.html.
Create a vue.config.js file at the root of the project with the following content:
module.exports = { configureWebpack: { module: { rules: [ { test: /.html$/, loader: "vue-template-loader", exclude: /index.html/ } ] } } };
In this file, we have declared a Webpack rule that links .html files (except index.html) with vue-template-loader.
That's it! If you run the application, you should see the output that was shown in this example.
This was a bit tedious, but the good news is that these are manipulations that you only need to do once.
There are pros and cons to SFCs, and we won't argue about which approach is best. It'll be up to you and your team to decide.