Neodymium (or nd for short) is a project template for a Node.js + HTML + CSS application. Nd uses the following Node.js tools to create a basic skeletton for a new app:
-
npm
oryarn
, to install your application, tools and dependencies -
Vue.js: the framework which generates your HTML code at runtime.
-
Webpack 2: a module bundler; it gathers you application's modules into big files your browser can read.
-
Electron: a browser-like tool that wraps the HTML files generated by Webpack and Vue into a cross-platform app.
-
vue-cli: the tool that downloads and installs nd projects.
-
ESLint: the linter which you can use to point out probable mistakes in your code.
To understand this tutorial, you need to be familiar with Node.js and its tools (at the very least, npm
), and with Vue.js (read at least the official tutorial). Being familiar with Webpack and electron and the other tools is a plus, but isn't necessary.
The aim of nd is to simplify making an Electron application: in theory, all you have to do is provide nd with a few custom attributes (project name, description), and let it take care of the boilerplate code for you.
In practice, you may need to look at the boilerplate code at some point to understand how your project is built.
For this tutorial, we will develop a basic "Grocery List" app with nd.
It will be a simple one-page application (that is, a single webpage with no links), with a list of grocery items. Each item will be made of a checkbox, and a line of text which will be crossed out when the box is checked. Below all items, there will be a text input zone and a button to add more items.
The final app should look like this:
This tutorial will take you through the process of installing, developping and running this app with nd.
First off, you need vue-cli:
$ npm install -g vue-cli
vue-cli
is a command line tool for generating project from online templates. You can use it to generate a new neodymium project with:
$ vue init soixantecircuits/nd my-grocery-list
(Note that while the package is named vue-cli
, the command it installs is named vue
; "cli" stands for "Command Line Interface")
vue init
asks you to name the project, add a description, etc; for now you can leave these options to their default values. The most important question is whether or not you want electron support; leave it to "yes".
Next, install dependencies in your node_modules
folder:
$ cd my-grocery-list
$ npm install
Your project is now installed; for now, it only has a showcase app, with some links and a counter component. You can display it with:
$ npm run dev
First, we need to understand what goes on under the hood, when the previous screen is displayed. What goes from typing npm run dev
to displaying "Welcome to neodymium"?
Well, the pipeline is somewhat convoluted, but it goes like this:
-
First,
npm run dev
reads thepackage.json
objects, looks for thescripts.dev
string, and runs it, which runsnode build/dev-server.js
-
dev-server.js
is a complex file; but the gist of it is, it runs a Webpack server and an electron client; the server sendsindex.html
andsrc/main.js
to the client -
src/main.js
replaces the HTML element with id "app" inindex.html
with the componentssrc/components/App.vue
-
src/components/App.vue
is an indirection, which displays the component returned bysrc/lib/router.js
; usually this is the Root component insrc/Root.vue
If you didn't understand everything, don't worry. You only need to remember that the effective entry point of your app is src/Root.vue
; and indeed, if you open it, you can see the HTML code for the content of the default showcase app, inside the <template>
tags.
If you aren't familiar with Webpack, the presence of .vue
files may surprise you. How do they fit into the project?
A Webpack application is not exclusively made of .html
and .js
files. Webpack allows you to define custom extensions, and to associate loaders to them. By default, a wepack project would not recognize .vue
files; however, Neodymium configures Webpack (in build/webpack.base.conf.js
, if you're curious) to detect .vue
files, and run the vue-loader
plugin on them every time they're imported through require
or import
.
For instance, in Root.vue
, the line import Counter from './components/Counter'
is actually parsed by Webpack, which detects the src/components/Counter.vue
file, runs vue-loader
on it, and stores the result in the Counter
object in the Root.vue
script.
Each .vue
file represents a Vue-js component, as if it were created by the Vue.component
method, with the contents of the <template>
tag as its template
parameter, and the object exported inside the <script>
tags as its other parameters. The content of the <style>
tags, however, is applied to the entire application (which may be a source of confusing mistakes).
For more information, read the official guide on Components and Single File Components.
For starters, open src/Root.vue
and replace the contents of the <template>
section with the following code (or, alternatively, your own):
<div id="app">
<h1>Groceries</h1>
<div id="grocery-list">
<p class="grocery-item" v-for="item_name of items"> {{ item_name }} </p>
</div>
<input v-model="next_item" />
<button @click="add_item()">Add item</button>
</div>
Then, replace the contents of the <script>
section with the following code:
'use strict'
export default {
data() { return {
items: ["Bread", "Butter", "Milk"],
next_item: ""
}},
methods: {
add_item() {
if (this.next_item) {
this.items.push(this.next_item)
this.next_item = "";
}
}
}
}
This is the standard vue model: the code defining data and operations is written in the <script>
section, and the code defining how to display this data is written in the <template>
section.
Run npm run dev
again; your app should now look like this:
Most apps are made of several components, to allow developpers to compartimentalize and reuse code.
We will create a GroceryItem
component, which will include a line of text, and a checkbox that crosses the text out when it's clicked.
Create a new file in the component folder:
$ cd src/components
$ touch GroceryItem.vue
And paste the following code into it:
<template>
<div class="grocery-item" :class="{strike: checked}">
<input type=checkbox v-model="checked"></input>
{{ name }}
</div>
</template>
<script>
'use strict'
export default {
props: ["name"],
data() { return {
checked: false
}}
}
</script>
<style>
.strike {
text-decoration: line-through;
}
</style>
To include this component in Root.vue
, replace the contents of the <script>
section with
'use strict'
import GroceryItem from './components/GroceryItem'
export default {
components: {
GroceryItem
},
data() { return {
items: ["Bread", "Butter", "Milk"],
next_item: ""
}},
methods: {
add_item() {
if (this.next_item) {
this.items.push(this.next_item)
this.next_item = "";
}
}
}
}
and in the <template>
section, replace the following line:
<p class="grocery-item" v-for="item_name of items"> {{ item_name }} </p>
with:
<grocery-item v-for="item_name of items" :name="item_name" />
Run npm run dev
again; your app should look like this at last:
If you're not familiar with Vue components, this part of the previous code probably seemed strange to you:
components: {
GroceryItem
},
There are several concepts in play behind this piece of code:
-
Local Component Registration: We're passing a
components
parameter to the component exported byRoot.vue
. This allows it to recognize the content ofcomponents
by name. -
ES6 Propert shorthand: Using
GroceryItem
alone in an object declaration is equivalent to writing"GroceryItem": GroceryItem
. -
Kebab Case Convention: The GroceryItem tag in the
<template>
section is named<grocery-item>
, which is the kebab-case version of the name. While it could be written as<GroceryItem>
(that is, in CamelCase), the kebab-case version is closer to HTML convention. To accomodate that, Vue automatically "translates" CamelCase to kebab-case.
So the following code:
components: {
GroceryItem
},
is equivalent to
components: {
"GroceryItem": GroceryItem
},
and
components: {
"grocery-item": GroceryItem
},
which tells Vue "the <grocery-item>
tags in my template are to be replaced with the component GroceryItem
".
The nd template includes multiple scripts in package.json
;
To check that your project respects the standardJS
norm, you can run eslint
on your files with:
npm run lint
To build your project into an executable binary file, run:
npm run build
This will create a build folder named /releases/my-grocery-list-MY_OS_VERSION
, with a my-grocery-list
binary executable. Running this executable will open your application in a fullscreen window. Since we haven't coded an 'Exit' button in, you can exit the app with Alt+F4
, or any equivalent method.
You should now have a fully functional Grocery List app.
If you have any questions or remarks, you can contact us through our website, or leave an issue with a [TUTORIAL]
prefix here on Github.
Good luck with your apps!