Understanding the Project Structure
Alright, so now that we have set up our project, let’s actually see what it looks like.
First, let’s open up the “package.json” file, which describes the project’s dependencies.
{
"name": "sample-project",
"description": "A Vue.js project",
"version": "1.0.0",
"author": "Bo Andersen ",
"private": true,
"scripts": {
"dev": "cross-env NODE_ENV=development webpack-dev-server --open --hot",
"build": "cross-env NODE_ENV=production webpack --progress --hide-modules"
},
"dependencies": {
"vue": "^2.2.1"
},
"devDependencies": {
"babel-core": "^6.0.0",
"babel-loader": "^6.0.0",
"babel-preset-latest": "^6.0.0",
"cross-env": "^3.0.0",
"css-loader": "^0.25.0",
"file-loader": "^0.9.0",
"vue-loader": "^11.1.4",
"vue-template-compiler": "^2.2.1",
"webpack": "^2.2.0",
"webpack-dev-server": "^2.2.0"
}
}
Notice that all of the dependencies except Vue itself are development dependencies. This includes the Babel JavaScript transpiler, webpack and webpack loaders, as well as a webpack development server. These are the dependencies that were installed when we ran the npm install command. Notice that there are some dependencies named babel. In case you are not familiar with Babel, it is a transpiler which enables you to use the latest JavaScript features without worrying about browser compatibility. Babel takes your JavaScript code and transforms it into code that is compatible with older versions of JavaScript and therefore browsers. Also notice that this is where the command that we ran at the end of the last lecture is defined, as well as a build command. This command makes a production build of our application and minifies the JavaScript code as we will see in the next post.
The next file we will take a look at is webpack.config.js, which is – as its name implies – the webpack configuration file.
var path = require('path')
var webpack = require('webpack')
module.exports = {
entry: './src/main.js',
output: {
path: path.resolve(__dirname, './dist'),
publicPath: '/dist/',
filename: 'build.js'
},
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader',
options: {
loaders: {
}
// other vue-loader options go here
}
},
{
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/
},
{
test: /\.(png|jpg|gif|svg)$/,
loader: 'file-loader',
options: {
name: '[name].[ext]?[hash]'
}
}
]
},
resolve: {
alias: {
'vue$': 'vue/dist/vue.esm.js'
}
},
devServer: {
historyApiFallback: true,
noInfo: true
},
performance: {
hints: false
},
devtool: '#eval-source-map'
}
if (process.env.NODE_ENV === 'production') {
module.exports.devtool = '#source-map'
// http://vue-loader.vuejs.org/en/workflow/production.html
module.exports.plugins = (module.exports.plugins || []).concat([
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: '"production"'
}
}),
new webpack.optimize.UglifyJsPlugin({
sourceMap: true,
compress: {
warnings: false
}
}),
new webpack.LoaderOptionsPlugin({
minimize: true
})
])
}
I am not going to go through this in details, because webpack is outside the scope of this series, but let’s take a quick look at the most important parts of the configuration. Webpack makes bundles by resolving dependencies between files. It does so by following the dependencies from a “start file” you could say. In webpack terminology, this is called an entrypoint. In our project, the entrypoint is defined as the main.js file within the src directory. We will take a look at this file in a moment. We can also see that the path at which webpack places its output files, is within a dist directory. This directory doesn’t exist yet, but we will see it soon when we build the project for production. The bundle that webpack creates is named build.js, and this is also the file that we will see included within the index.html file.
Then we have a couple of loaders, which basically tell webpack how to treat files matching a certain regular expression. The first one is the vue-loader, which is responsible for handling so-called single file components, which is something that I will get back to soon. Another loader is responsible for passing JavaScript files through Babel, and another takes care of image files. If you look at bottom of the file, you can see that if the node environment is production, the resulting JavaScript file is minified, resulting in a much smaller file.
Let’s now take a look at the index.html file.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>sample-project</title>
</head>
<body>
<div id="app"></div>
<script src="/dist/build.js"></script>
</body>
</html>
As you can see, this file is very simple. The first interesting part here is the div with the ID of app. This is where the contents of the application will be inserted at runtime. So while this HTML is the only markup that comes back from the web server when we load the page, Vue will place our content within this element when it is ready.
The other interesting thing is the inclusion of a build.js file located within the dist directory. This file contains all of our application as well as Vue itself, as this is the bundle that webpack creates for us. This JavaScript file gets served by the webpack development web server that is included in the package.json file and that we run with the npm run dev command as you saw in the previous post. Notice, however, that we have no dist directory even though we ran the code at the end of the previous post. This is because the build.js file is only created when we make a build of the project by running npm run build. The dev command does all of its work in memory with the development web server, so you won’t actually see any files on your file system. I will make a build in one of the upcoming posts and show you what it looks like, though.
Moving on, let’s take a short look at the App.vue file.
<template>
<div id="app">
...
</div>
</template>
<script>
export default {
name: 'app',
data () {
return {
msg: 'Welcome to Your Vue.js App'
}
}
}
</script>
<style>
/* ... */
</style>
I will explain the contents of this file in more details in the next post, but for now, notice that we have some HTML markup at the top, some JavaScript, followed by CSS styles. What I want to talk a little bit about now, is the JavaScript code. An object is exported, and this is actually an options object for a component, as we will see during the next posts. This object is very close to the object that we pass to Vue instances as we have seen so far, so here you could add methods, computed properties and everything else that we have worked with until now. One thing to notice, though, is that the data property is no longer an object, but is an ES6 function which returns the data object. That’s something that I will talk more about in the next post, so for now, all I want you to know, is that an object is exported, and this object can be used to create a component.
Now let’s have a look at the main.js file.
import Vue from 'vue'
import App from './App.vue'
new Vue({
el: '#app',
render: h => h(App)
})
Here we see something that should look familiar – a Vue instance. This file is actually the entrypoint that webpack uses to resolve the dependencies in our project. Because we are now using webpack instead of adding Vue in a script tag, we need to import Vue itself into our application. That’s what you see at the first line of the file. The second line imports the object that is exported from the App.vue file that we just saw. This object is then used within the render property that is passed to the Vue instance. This is something that we haven’t looked at before. This is a so-called render function, which means that Vue won’t take contents of the el property’s element and use it for its template, but rather use the render function for getting the template. The result is then placed witin the div element with the ID of app, which we saw within the index.html file.
This render property should therefore contain a function, and Vue passes an argument to this function automatically, which is a function that can be used to create elements with. What happens here, is that the function is an ES6 arrow function which has named this “create element function” as h. The body of the arrow function then just invokes this function and passes in the component options object that was imported from the App.vue file.
This syntax is very short and sweet, but we could also have written it more explicitly. I realize that this may all look quite confusing at first, so let’s take a moment to write a more explicit version of the render function below.
import Vue from 'vue'
import App from './App.vue'
new Vue({
el: '#app',
render: function(createElement) {
createElement(App);
}
})
Those two approaches are identical, but the one that you see above is probably easier for most people to read. So what it does, is that it takes a function as an argument, which can create an element. This function is then invoked and passed the component options object. The “createElement” function can also take other kinds of values, but this is all we need in this case.
Anyways, this is not super important to understand, so don’t worry if you didn’t. Chances are that you won’t ever have to change this, but I just wanted to explain it anyways.
Those were the basics of the project structure. In the next post, we will dive into single file components in greater detail.
Here is what you will learn:
- How to build advanced Vue.js applications (including SPA)
- How Vue.js works under the hood
- Communicating with services through HTTP
- Managing state of large applications with Vuex
- ... and much more!
One comment on »Understanding the Project Structure«
Thank you for posting this nice tutorial.