5/16/2018

Using Vue.js in SharePoint Framework Applications. Part II: Default SPFx web part using Vue.js

This is the second post about SharePoint Framework and Vue.js. In this post I'm going to implement basic Client-Side Web Part using Vue.js - basically, "wrap" the markup from Web Part template project with Vue.js component.
List of posts:
  1. Whats and Whys
  2. Default SPFx web part using Vue.js (this post)
In the first post we went through the basic configuration of a project to use both TypeScript and Vue.js.
Now let's go further and include Vue.js in SharePoint Framework project

1. Scaffolding

So, the first thing we should do, as with any other SPFx project, is to "scaffold" it from the template how it is described here
This will create a basic project with a markup implemented using HTML with no framework used.

Now we have a SPFx project prepared and the goal is to replace the HTML markup with a Vue component.

2. Including Vue into the Project

I'm going to implement a web part as a Single File Component (SFC) with a help of vue-class-component. This is one of two available options how to implement an SFC using Vue.js. Another one is to use Vue.extend which is more "classic" way for Vue developers. But vue-class-component is more similar to SPFx development itself. So, as mentioned, I'll use this one.
First of all, we need to install vue module as we did in the first post:
npm i --save vue
Next, we're going to create SFC and implement components inside .vue files. To let webpack know about this type of files we need to install vue-loader. Also, we need to install vue-template-compiler as it is a dependency of vue-loader. We need these modules for bundling only. That's why we'll install them with --save-dev parameter:
npm i --save-dev vue-loader vue-template-compiler
Now, as mentioned, we need vue-class-component and additionally vue-property-decorator to mark Vue component properties:
npm i --save vue-class-component vue-property-decorator
And combining all the modules:
npm i --save vue vue-class-component vue-property-decorator
npm i --save-dev vue-loader vue-property-decorator

3. Modifying Webpack config and Gulp tasks

Next step is to modify gulpfile.js. We need to change multiple things to build our Vue components correctly:
  1. Copy Vue files to the destination folder.
  2. apply vue-loader for .vue files
The first step is to copy .vue files to the destination folder.
For that we can create a Gulp sub task and add it after the TypeScript compiler task:
let copyVueFiles = build.subTask('copy-vue-files', function(gulp, buildOptions, done){
  return gulp.src(['src/**/*.vue'])
           .pipe(gulp.dest(buildOptions.libFolder))
});
build.rig.addPostTypescriptTask(copyVueFiles);
And the second thing is to modify Webpack config to use vue-loader for .vue files.
For that we'll additionally need to install webpack-merge module that allows to correctly modify existing Webpack config.
npm i --save-dev webpack-merge
Now let's add needed loader to the config:
var merge = require('webpack-merge');

build.configureWebpack.mergeConfig({
    additionalConfiguration: (config) => {
        return merge(config, {
            plugins: [
                new VueLoaderPlugin()
            ],
            resolve: {
                alias: {
                    'vue$': 'vue/dist/vue.esm.js'
                }
            },
            module: {
                rules: [{
                    test: /\.vue$/,
                    use: [{
                        loader: 'vue-loader',
                        options: {
                            esModule: true,
                        }
                    }]
                }]
            }
        });
    }
});
Here we're adding VueLoaderPlugin needed for vue-loader and specifying to use vue-loader for .vue files.

4. Implementing Vue Single File Component

Now we're ready to implement web part's markup as a Vue SFC.
For that let's create components folder, SimpleWebPart subfolder, and SimpleWebPart.vue file in it.
Now let's move the markup from the Web Part .ts file to .vue file.
The markup (HTML) should be placed inside template element. So, after copying, the .vue file should look like this:
<template>
    <div class="${ styles.vueSimpleWp }">
        <div class="${ styles.container }">
            <div class="${ styles.row }">
                <div class="${ styles.column }">
                    <span class="${ styles.title }">Welcome to SharePoint!</span>
                    <p class="${ styles.subTitle }">Customize SharePoint experiences using Web Parts.</p>
                    <p class="${ styles.description }">${escape(this.properties.description)}</p>
                    <a href="https://aka.ms/spfx" class="${ styles.button }">
                        <span class="${ styles.label }">Learn more</span>
                    </a>
                </div>
            </div>
        </div>
    </div>
</template>
Next step in SFC creation is to add "model" - the Component class that contains properties, events handlers and business logic.
It could be done directly in .vue file in script element. But we'll create a separate SimpleWebPart.ts file. This file should contain a "component" class (marked with @Component attribute and extend Vue interface.
To be more close to SPFx React development, I will also define ISimpleWebPartProps interface to contain component's properties declaration:
import { Vue, Component, Prop, Provide } from 'vue-property-decorator';

/**
 * Component's properties
 */
export interface ISimpleWebPartProps {
    description: string;
}

/**
 * Class-component
 */
@Component
export default class SimpleWebPart extends Vue implements ISimpleWebPartProps {

    /**
     * implementing ISimpleWebPartProps interface
     */
    @Prop()
    public description: string;
}
And let's include our class into .vue file:
<script>
    module.exports = require('./SimpleWebPart');
</script>
After that we can reference description property in our template.
For that we need to replace ${escape(this.properties.description)} with {{description}}:
<p class="${ styles.description }">{{description}}</p>
Now, let's create SimpleWebPart.scss file and copy all the web part styles there.
Usually, to reference the .scss file in SFC we need to add style element, set lang attribute to class="code">scss and src attribute to ./SimpleWebPart.scss. And additionally add scoped attribute.
But in that case we'll lose processing of .scss files included in SharePoint Framework. It means that we'll lose scoping (which is not important as scoped attribute does the scoping as well) and most important - we'll lose theming variables processing.
To avoid it we can reference styles module in our .ts file, assign its value to "computed" property and use data binding to apply classes to the elements in the template.
So, first, let's import styles module as it's usually done in TypeScript and add a read-only property in SimpleWebPart class to return the module's value:
import styles from './SimpleWebPart.module.scss';

/**
 * Class-component
 */
@Component
export default class SimpleWebPart extends Vue implements ISimpleWebPartProps {

    //...

    /**
     * Readonly property to return styles
     */
    public get styles(): { [key: string]: string } {
        return styles;
    }
}
Now we can use styles property in the template:
<template>
    <div :class="styles.vueSimpleWp">
        <div :class="styles.container">
            <div :class="styles.row">
                <div :class="styles.column">
                    <span :class="styles.title">Welcome to SharePoint!</span>
                    <p :class="styles.subTitle">Customize SharePoint experiences using Web Parts.</p>
                    <p :class="styles.description">{{description}}</p>
                    <a href="https://aka.ms/spfx" :class="styles.button">
                        <span :class="styles.label">Learn more</span>
                    </a>
                </div>
            </div>
        </div>
    </div>
</template>
:class syntax here is a shorthand for v-bind:class that allows to bind property to the class HTML attribute.

5. Adding Vue SFC Component in Web Part

The last step is to add created element instead default HTML in the Web Part.
For that, let's import Vue object, our component and the properties:
import Vue from 'vue';
import SimpleWebPart, { IVueSimpleWpWebPartProps } from './components/SimpleWebPart/SimpleWebPart.vue';
And now let's add the component to markup and provide description value from the Web Part's properties.
public render(): void {
  const id: string = `wp-${this.instanceId}`;
  this.domElement.innerHTML = `<div id="${id}"></div>`;

  let el = new Vue({
    el: `#${id}`,
    render: h => h(SimpleWebPartComponent, {
      props: {
        description: this.properties.description
      }
    })
  });
}
I'm using unique id in the div element to inject Vue component.
Usually it's not a good idea to use ids for any references, but here I've tried to reference directly this.domElement in the Vue constructor. But in that case "reactive" change of web part's properties will not work.
So I decided to go with id.
If you have any other ideas - feel free to share!
Now, if you try to compile the project with gulp command, you'll receive the error
Error - typescript - src/webparts/vueSimpleWp/VueSimpleWpWebPart.ts(16,35): error TS2307: Cannot find module './components/SimpleWebPart/SimpleWebPart.vue'.
That is because TypeScript compiler doesn't know how .vue files look like when they're imported.
To notify TypeScript about the structure of .vue files (and modules) we need to add vue-shims.d.ts file in the src folder with the next content:
// src/vue-shims.d.ts

declare module "*.vue" {
    import Vue from "vue";
    export default Vue;
}
Now everything should work fine and you should see a standard web part markup on your page:

The code for this example is available here
That's it for now!
Have fun and stay tuned!

No comments:

Post a Comment