3-7 Vue projects and build tools

Introduction to Node.js

As we try to build more complicated (client side of) a web app, we’ve to handle a growing number of source code files (including HTML, JavaScript and other assets). We need to break down a large JavaScript program into several modules for better management. We may also want to install and use modules from third-party libraries (e.g. the Element Plus desktop UI library).

To handle the complexity of such projects, web developers often apply build tools to facilitate the development and testing processes and automate the building of distributable. In this section, we introduce the Vite tool and demonstrate how to build an Vue project with components and third-party library.

We need to have basic understanding of several tools and programming concepts before we dive into the study of Vue components.

Node and node project

From its official page, “Node.js is a JavaScript runtime built on Chrome’s V8 JavaScript engine”. Node.js is commonly used to develop network services, and, in particular, application servers for web apps (which will be the main topic in chapter 4).

In this chapter, we’ll mainly use Node.js as a platform to run development tools of Vue projects. For now, install node from the official page. After installation, check the version as below.

$ node -v

A JavaScript project most likely contains more than one JavaScript files and other resources (e.g. HTML, images) placed in a folder. The folder contains a file package.json which describes the project. You can create package.json with the following steps. See online reference for the meaning of properties in the file.

$ mkdir proj01
$ cd proj01
$ npm init

Now, create a simple JavaScript program hello.js in the project. Run it with the command node hello.js.

// hello.js
console.log("Hello Node!");

Packages and NPM

Bundled with Node.js is a package manager called NPM, a command line tool to download (and manage) JavaScript packages from package registry. The default registry is npmjs.com, which has recently become the largest JavaScript registry for both server-side and client-side libraries and also tools written in JavaScript.

You can easily install a package into a Node project with the command line tool npm. The following commands installs a toy tool called cowsay locally in the current project, and then run it with the command line tool npx.

Actually, npx will download a package on-the-fly if it cannot find the package locally. Refer to online reference for more information.

$ npm install cowsay
$ npx cowsay Hello npm!

A more useful tool is a simple web server http-server written in JavaScript.

$ npm install http-server
$ npx http-server

By default, npm install installs a package and its dependencies in the folder node_modules inside the project folder. Check it out. Moreover, npm install also automatically adds an entry in the package.json.

{
  "name": "proj1",
  "version": "1.0.0",
  "scripts": {
    "web": "http-server",
    "hello": "cowsay \"Hello npm script!\""
  },
  "dependencies": {
    "cowsay": "^1.5.0",
    "http-server": "^13.0.1"
  }
}

With the dependencies of a project listed in package.json, you can install all the required package for a project with a simple command in the future.

$ npm install

You can also create scripts in package.json to run commands with npm run. Download the sample package c37-proj01.zip and watch the demo in class.

Vite, a build tool for Vue

In the next few sections, we will use a build tool called Vite (official site) to develop, test and build Vue applications.

Using a build tool has several advantages, compared to our previous approach of loading JavaScript libraries from CDN and writing JavaScript code in a single *.js file.

  1. We can manage client-side JavaScript libraries using a package manager (we’ll use npm). A package manager can automatically install dependencies of a package, and take care of versioning of packages.
  2. Build tools support loaders and pre-processors, which enable more convenient and powerful formats for programming (e.g. TypeScript). In particular, we’ll use SFC (Single File Component) to group the HTML template, CSS style and JavaScript code of a component into a single file.
  3. Build tools can translate JavaScript codes to versions that are compatible with older versions of web browsers. This allows developers to take advantage of modern JavaScript programming constructs (e.g. modules).
  4. Module bundlers can trace out the JavaScript codes (function, class) starting from <script> element in HTML. They can bundle multiple JavaScript files into one (or a few) file, and this can reduce the time that a web browser will load the web application.
  5. They support tree shaking, also known as dead code elimination, and will not include in the bundles any code that is not used by the web app.

Project configuration

We will use a simple Node project c37-sfc.zip to illustrate how to use Vite to develop a Vue project. First, download the example and expand the zip in a working folder. The following in the package.json of the project.

{
  "name": "c37-sfc",
  "version": "0.1.0",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "serve": "vite preview"
  },
  "dependencies": {
    "vue": "^3.2.6"
  },
  "devDependencies": {
    "@vitejs/plugin-vue": "^1.6.0",
    "@vue/compiler-sfc": "^3.2.6",
    "vite": "^2.5.2"
  }
}

This project has two kinds of dependencies. dependencies refers to packages that are required by the application itself. Execution of the application requires these packages. In this example, we have only vue 3. But if you use other libraries (e.g. Element Plus), you should install them into the project (e.g. npm install element-plus).

The other kind of dependencies is devDependencies. These packages are not necessary while we’re developing the project. You can install packages for development with npm install packageName --save-dev.

Another config file in the project is vite.config.js. This configures the Vite build tool. For our purpose, we mostly only need to change the input to the bundler.

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
  plugins: [vue()],
  build: {
    rollupOptions: {
      input: {
        // list all HTML files that contain Vue components
        index: './index.html',
      }
    }
  }
})

Dev server and live-reload

The Vue application in this example (c37-sfc.zip) consists of three files. The HTML file is index.html, a browser loads this file to run the web app. The HTML file loads the JavaScript module main.js, which creates a Vue app using createApp(). The root component of the app is provided by a SFC app.vue.

We can run a development web server to test the app.

$ npm run dev

Open a browser to connect to the URL that is given by the above command. Can you run the web app?

Vite also supports live reload. When you make some changes to the source code, the browser will reload automatically to show the update. Try to make some changes to the HTML or JavaScript code and see that the browser refreshes to show the up-to-date version of the app.

Building a dist package

When you’re reading to deploy the web app to a production web server, you can run npm run build to build an application bundle in the folder dist. Check the content of the folder.

$ npm run build
$ npm run serve

Vite also includes a simple web server to test the application bundle. Run npm run serve to start the testing web server.

A tour of the source code

Vite uses ES modules (online reference) to manage the Vue components and other JavaScript files in a project. For example, index.html loads main.js with the following line.

<div id="app"></div>

<!-- Loading main.js as a JavaScript module -->
<script type="module" src="./main.js"></script>

main.js sets up the Vue application. The first import statement imports a function createApp from the Vue package previously installed by npm install. The second import statement imports an options object of the root component from a SFC called app.vue. main.js then creates a Vue app according to the options object, and mounts it to the <div> with id app in the HTML file.

// import 'createApp' from the Vue library as a module
import { createApp } from "vue";

// import the options object from the SFC
import opts from "./app.vue";

createApp(opts).mount('#app');

Next, let’s look at the content of the SFC file app.vue, which contains the root component of the app. There are three sections in a SFC: the template, the CSS style and the JavaScript code.

The template section provides the template code of the component. The JavaScript section exports the options object of the component, which is imported by main.js earlier.

<template>
The time now is 
<div class='clock'>{{ timeNow }}</div>
</template>

<script>
export default {
  data() {
    return { timeNow: undefined }
  },
  created() {
    this.timeNow = (new Date()).toLocaleTimeString();
    setInterval( ()=>{
      this.timeNow = (new Date()).toLocaleTimeString();
    }, 1000);
  }
};
</script>

<style scoped>
.clock { padding: 2px; width: 6em; border: 1px solid gray; text-align: center; }
</style>

SFC may also include a section for style. SFC allows us to restrict the style rules for this component only with <style scoped>.

Using third-party components

In this section, we go through the source code of another web app, which integrates the three UI component examples in the previous lecture. The link c37-feature provides a deployed version of the web app, which you can run in browser. The source code can be downloaded at c37-feature.

First, notice that package.json adds Element-Plus as dependencies.

  "dependencies": {
    "element-plus": "^1.1.0-beta.8",
    "vue": "^3.2.6"
  },

This app has four components. The root component app.vue includes three other components, comp01.vue, comp02.vue, comp03.vue. The three child components correspond to the three examples in the previous lecture about common component features. Notice several things in the following listing of app.vue:

<template>
  <el-card>
    <template #header>Properties / Attributes</template>
    <comp-part1></comp-part1>
  </el-card> 

  <el-card>
    <template #header>Bi-directional binding</template>
    <comp-part2></comp-part2>
  </el-card> 

  <el-card>
    <template #header>Slots</template>
    <comp-part3></comp-part3>
  </el-card> 
</template>

<script>
import { ElCard } from "element-plus";
import 'element-plus/dist/index.css';

import CompPart1 from "./comp01.vue";
import CompPart2 from "./comp02.vue";
import CompPart3 from "./comp03.vue";

export default {
  components: { ElCard, CompPart1, CompPart2, CompPart3 }
};
</script>
  1. it imports the options object (as default) from the SFC of each child component. The variable name (e.g. CompPart1) is arbitrary, but should follow the Pascal case.
  2. it registers the child components in the options object of the root component. (components: { CompPart1 }). Without this, you cannot use the child component in the template.
  3. components: { CompPart1 } is a shorthand for components: { CompPart1: CompPart1 }. Because element names in HTML are case-insensitive, Vue will convert the PascalCase CompPart1 to kebab-case comp-part1 in the template.
  4. In the template, we inject the child component as <comp-part1></comp-part1>.
  5. Since we use one component from the Element Plus library, We also need to import <el-card> from the Node module element-plus, which we has installed earlier using npm install.
  6. The Element Plus library comes with a CSS style sheet for setting the appearance of its components. We use the statement import 'element-plus/dist/index.css' to tell Vue to refer to that CSS in the HTML page that includes this component (the root component).

Next, examine the source code of the child component comp01.vue. Similar to the situation in the root component, here, we need to import <el-input> and <el-progress> from element-plus, and register them in this component. We also import the Element-Plus CSS in this SFC because this component is using components from the library. The Vite build tool will only import the CSS once in the HTML page.

<template>
   <el-input placeholder="what's up?"
      suffix-icon="el-icon-chat-dot-round"
      v-model="message"></el-input>

   <el-progress :text-inside="true" :stroke-width="30"
                :percentage="progress" :color="customColors">     
   </el-progress>

   <el-button type="primary" @click="changeProgress">Change progress</el-button>
</template>

<script>
import { ElInput, ElProgress, ElButton } from "element-plus";
import 'element-plus/dist/index.css';

export default {
  components: { 
    ElInput: ElInput, 
    ElProgress: ElProgress,
    ElButton: ElButton,
  },
  data() { /* omitted */ },
  methods: { /* omitted */ }
}
</script>

<style scoped>
.el-input, .el-progress { width: 300px; margin: 10px 10px; }
</style>

Downloadable sample code

  • c37-proj01.zip - first sample of Node project
  • c37-sfc.zip - simple Vue app using SFC and Vite build tool
  • c37-feature - a Vue app to integrate the previous examples on common component features. this example demonstrates how to load third-party libraries and how a root component can include child components.