DEV Community

Arswaw
Arswaw

Posted on • Edited on

Use Vue to create a SPA without any Node modules

In this guide I will show you to create a static single-page application with VueJS and ES6 Modules.

A friend of mine was trying to use AWS Cloud9 to build his SPA, but he found working with Node to be cumbersome due to the way Cloud9 works. I had designed a way to build a complex web application without using Node at all while at work. I passed the knowledge on to him and he took to it.

The idea is that you can build a "static-first" web application with optional build tooling later if you wish.

Rather than using Node's module system, you can use the one that's built into JavaScript. This module system shipped in version 6.

This allows you to send your files to the browser exactly as they are. There is no setup. You can clone a git repository directly to /var/www/html on Apache and start developing.

You can use Vanilla JavaScript, but I've found Vue.js does the heavy lifting that I'd have to do anyway in a moderately complex app.

You will need a server to host your static files. I recommend fenix.

You will need to use a browser that supports ES6, such as Opera.

You can see the final code here.

I have encountered problems with Edge when using this methodology, but Rollup.js has been very useful when preparing the app to work cross-browser.

I am in the process of building a CLI that will automate these steps.

Step 1 - Creating your index.html file.

For the most part, your index.html file will remain untouched as most of your work will be done in JavaScript.

  1. Create a project folder with a name of your choosing. Serve it from whatever static fileserver you wish.
  2. Inside the project folder, create an index.html file.
  3. Add html, head, and body tags, then include this line of code between your body tags:
<script type="module" src="main.js">
Enter fullscreen mode Exit fullscreen mode

You can optionally include 'crossorigin' if you run into a 499 error in the console. There is more information in this forum post.

  1. Above the line of code you just wrote, add the following:
<div id="app"></div>
Enter fullscreen mode Exit fullscreen mode

You can call the id whatever you want, as long as it matches with the Vue instance we're about to create.

Step 2 - App entry point, and Vue integration.

Vue can be imported as an ES6 module. All components will be able to make use of it.

You should be in a directory that a single file, which is the index.html file you just created.

  1. Create a main.js file.
  2. Create a vue.js file.
  3. Go to this cdn and select vue.esm.browser.js
  4. Copy and paste the JavaScript code into the vue.js file you just created in your project.
  5. Go to main.js and enter the following code:
import Vue from './vue.js'
Enter fullscreen mode Exit fullscreen mode

Alternatively, you can do this:

import Vue from 'https://cdn.jsdelivr.net/npm/vue@latest/dist/vue.esm.browser.min.js'
Enter fullscreen mode Exit fullscreen mode

Thanks to Mohamed Safouan Besrour on Github for the suggestion and Pull Request!

  1. Immediately after this line, add the Vue instance:
new Vue({
  el: '#app', // This should be the same as your <div id=""> from earlier.
  components: {
    'navbar': Navbar
  },
  template: MainTemplate
})
Enter fullscreen mode Exit fullscreen mode

We've referenced two external files, one component, and one template. We will be importing both. The 'navbar' component has its own template, which it will import on its own, without it having to be imported into main.js

This is important as we want to practice separation of concerns within our componentized app.

Step 3 - Adding templates.

For the apps I have built with this methodology, I have kept all templates in a separate folder structure that mirrors the component folder structure. You can change this around if you want. There is no reason why you can't give each component its own folder with template, and .js file. This is similar to the approach taken by Angular.

  1. Create a templates directory. This should be the only subfolder in your application directory.
  2. Create a file called main-template.js
  3. Add the following code:
// Note that these are backticks.

const MainTemplate = `
    <div>
    <navbar></navbar>
    All content will appear below the horizontal line.
    <hr>
    </div>
`

export { MainTemplate }
Enter fullscreen mode Exit fullscreen mode
  1. In the templates folder, create a navbar-template.js file.
  2. Add the following code:
const NavbarTemplate = `<nav>

<a href="#/">Home</a>
<a href="#/about">About</a>

</nav>`

export { NavbarTemplate }
Enter fullscreen mode Exit fullscreen mode

We have finished creating our templates.

Step 4 - Adding components.

We need a navbar component to attach our template to.

  1. In the root directory of your project, create a 'components' directory.
  2. Create a navbar.js file.
  3. Add the following code:
import { NavbarTemplate } from '../templates/navbar-template.js'

const Navbar = {
  template: NavbarTemplate,
}

export { Navbar }
Enter fullscreen mode Exit fullscreen mode
  1. Return to main.js

Step 5 - Importing our components.

We are almost ready to view the first version of our web application.

  1. In main.js, add the following code between the Vue import, and the Vue instantiation:
import {
  Navbar
} from './components/navbar.js'

import {
  MainTemplate
} from './templates/main-template.js'
Enter fullscreen mode Exit fullscreen mode
  1. Save the file and view your web application in your browser. The URL will depend on your server settings.

We have a web application that can optionally be turned into a SPA. We will do this now.

Step 6 - Adding routing.

We need to import Vue Router. This is optional, but again it does much of the heavy lifting for you when it comes to routing.

  1. Navigate to the vue-router CDN. There doesn't appear to be an ES6 Modules compatible version, so we'll import as CDN.
  2. Copy the link, and add it as a dependency in index.html above the module import:
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue-router/3.0.2/vue-router.js"></script>
Enter fullscreen mode Exit fullscreen mode
  1. Return to main.js and add the following code:
Vue.use(VueRouter)

const router = new VueRouter({
  routes: [{
    path: '/about',
    component: About,
    name: "About Us Page"
  }]
})
Enter fullscreen mode Exit fullscreen mode
  1. In the Vue instance add 'router' above main template:
router,
template: MainTemplate
Enter fullscreen mode Exit fullscreen mode
  1. Create an about template, an about component, and then import the component into main.js
  2. Underneath the hr tag in main-template.js, add:
<router-view></router-view>
Enter fullscreen mode Exit fullscreen mode
  1. Look at your app home page, and then use the navbar to route to the about page.

Add new components and templates to expand your app.

Check the console. If you see any errors, or the app is not rendering the way you expected, check that you followed the steps correctly. If it still does not work, leave a comment below the post.

The main point of this methodology is to show that you don't necessarily need to setup a complex build environment with Node in order to develop a SPA. I believe that this type of development will become more sustainable as module developers release ES6-compatible versions of their software.

A future blog post will focus on using Rollup.js to package this type of app, and setting up CI/CD.

I found another bug with the V2 editor. It causes the numbering to reset after a code block. Notice how my numbers are now 1. after every code block is closed. I will reach out to DEV on Twitter concerning the bug.

I hope this helps anyone who was hoping to build a complex web application with just HTML, CSS, and JavaScript.

Thank you.

Top comments (72)

Collapse
 
aelbore profile image
Jay

nice article :) but attaching css on each component is quite difficult on this, you need to hack around so that you can have it for example

  mounted() {
    const style = document.createElement('style');
    style.innerHTML = styles;
    this.$el.prepend(style);
  }
Enter fullscreen mode Exit fullscreen mode

by the way heres one of my example
plnkr.co/edit/tjhkcfNO15aTNhsU?pre...

Collapse
 
arswaw profile image
Arswaw

Yeah, I know. It's unfortunate that CSS is not modularized like JavaScript is. It would be nice if Vue supported binding CSS to components without build tools. I'm not sure if that's on their roadmap.

For now, the best way I can think of is to use prefixed styles for each component and put them in global CSS so that it's clear which CSS ruleset is meant to affect what.

Thanks for the comment.

Collapse
 
aelbore profile image
Jay • Edited

i updated the example to make use goober css in js it uses prefix to share css :)
now problem solve with vue without bundler

plnkr.co/edit/tjhkcfNO15aTNhsU

Thread Thread
 
arswaw profile image
Arswaw

This is amazing! I didn't know about this. You can just import Goober from the CDN as an ES6 module and contain the CSS in a multiline string just like the template.

I think I need to update the article to reflect this. I will be sure to try this out in my projects.

I really appreciate the work you've put into this. Thanks for telling me about Goober and thanks for using my template!

Thread Thread
 
aelbore profile image
Jay • Edited

have you heard import maps? you can use polyfill i think import maps it is already in draft not sure what stage it is.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Vue Import Maps</title>
  <script defer src="https://unpkg.com/es-module-shims@latest/dist/es-module-shims.js"></script>
  <script type="importmap-shim">
    {
      "imports": {
        "vue": "https://unpkg.com/vue@2.6.10/dist/vue.esm.browser.js?module",
        "goober": "https://unpkg.com/goober/dist/goober.module.js?module"
      }
    }
  </script>
</head>
<body>
  <script type="module-shim">
    import Vue from 'vue'
    import App from './src/App.js'

    new Vue({
      render: h => h(App)
    }).$mount("#app")
  </script>
  <div id="app"></div>
</body>
</html>
Thread Thread
 
arswaw profile image
Arswaw

It's not on any stage because it's not on the W3C standards track. Maybe it will be picked up at some point.

I'm not sure what the benefit of this is over <script src="" or import Vue from 'vuecdn.com'

I hadn't heard of this though, and I'd like to know more.

Thread Thread
 
aelbore profile image
Jay • Edited

yeah your right its not even in draft, i think in my opinion the benefit is you dont need to type/copy or write long url and you can easily find what are your dependencies. :)
another thing is for example (module 1 dependencies of module 2, but the module 1 didnt use the URL)
module 1

import { myExportVar } from 'module-1'
Enter fullscreen mode Exit fullscreen mode

module 2

export { myExportVart }
Enter fullscreen mode Exit fullscreen mode
Thread Thread
 
arswaw profile image
Arswaw

I had thought that this would essentially replace the package.json file.

If so, then import maps bring us one step closer to building data-driven web applications without Node or NPM.

Thread Thread
 
mindplay profile image
Rasmus Schultz

It's still not on standards track, it seems - though it is available and enabled by default in Chrome and Edge.

caniuse.com/import-maps

Pretty cool - this basically is the web equivalent of package.json 🙂

The main benefit of this is not about long or short URLs - it's about ensuring that, when several modules depend on the same things, they're also loading the same versions of those things. CDNs such as Snowpack try to address this by allowing URLs with version constraints, and performing redirects, but that's a half measure - it doesn't perform or scale well, doesn't let you lock to a specific version, etc...

We really need import-maps to proliferate into a standard. By then, for the first time in the history of the web, the platform will actually be able to stand alone, without all the clunky build tools and bundlers. 😄✊

Thread Thread
 
arswaw profile image
Arswaw

That's what I'm hoping! Much of the Node.js ecosystem is designed to accommodate the missing features in JavaScript and the web browser. It's too bad, because while the transition is needed, it will be a long one.

I didn't even know that import maps were used in Chrome and Edge. That just leaves Firefox and Safari if browser developers wanted to implement this without a standard, though I would prefer the standard existed.

You want deterministic dependencies. More than once, a buggy update to a single, one-liner package has broken millions of projects in a day. It's a good thing someone else agrees.

Maybe in 5 years, all the features we're talking about today will replace NPM dependence entirely.

Thread Thread
 
denzuko profile image
Dwight Spencer

That's actually impressive that a feature from backbone and request.js is finally make it into webkit based browsers.

Collapse
 
asaux19 profile image
asaux19

Awesome tutorial!
I would like to add a vuejs frontend to my backend (using spring boot). Is it possible to start the vuejs frontend off of the backend? It doesn't work just routing to the index.html...

Collapse
 
arswaw profile image
Arswaw

Thank you!

You wish to serve the Vue.js frontend from your Spring Boot API? Unfortunately I don't know much about Java, but I do know that my tutorial can be run from any static file server.

What specifically doesn't work?

Collapse
 
asaux19 profile image
asaux19 • Edited

So basically my backend calls the index.html when I open my localhost in the browser. To test it, I added a simple h1 test before the div id="app" and I can see that. But I get an error saying 'failed to load resource' main.js. I double-checked again, in all javascript files I have the exact same code as you.

Thread Thread
 
arswaw profile image
Arswaw

Does it give you error 499? You might try adding crossorigin to the HTML element. You should be able to see this in the article where I add in the main.js import to index.html

Which browser are you using?

Do you have script type="module"?

Are the two files in the same part of the directory?

Thank you for using this methodology. I would like to help you solve this problem. When we do, I can add the solution to the original post.

Thread Thread
 
asaux19 profile image
asaux19

I tried it with both Chrome and firefox. I did add type=module in the script tag. And yes, index.html and main.js are part of the same directory.
I will share my code on github with you. Maybe we will be able to find the reason for the error together and see whether it is an error that occurs more often in that context...

Thread Thread
 
arswaw profile image
Arswaw

I would be happy to assist you. Could you please link the exact file or folder you would like me to look at?

Thread Thread
 
asaux19 profile image
asaux19

in the src/main/resources/templates you can find the index.html as well as the main.js

Thread Thread
 
arswaw profile image
Arswaw

Thank you for your response. I will have a look at this after work.

Collapse
 
arswaw profile image
Arswaw

Hey, @asaux19 I just wanted to let you know that I'm still looking at your issue. I've been easier than expected in the past week and I hope to give you a solution soon.

Collapse
 
eric_dolecki profile image
 Eric E. Dolecki

Quick question. So I can create CSS that isn't SCSS (and scoped to each component). That's very workable. However, how can I assign properties, data, methods, etc. to my components when they are .js files (.vue can include these things, but I'm trying to avoid npm as your solution does).

Collapse
 
arswaw profile image
Arswaw

I'm glad you have your CSS down.

You are able to use anything you expect in the .js file that you could do with any Vue component.

If you find something that doesn't work, I would like to know.

I've tested this extensively. I've put watchers, computed, event listeners, and child components.

Does that answer your question?

Collapse
 
eric_dolecki profile image
 Eric E. Dolecki

Can you paste in an example of the contents of a simple component implementation that has data like an array and property associated with it? In your example stuff there is const with HTML and then an export, but looking for the syntax that's more like what's in a Vue file

Thread Thread
 
arswaw profile image
Arswaw

A very large example from a personal project:

github.com/arswaw/happyweather/blo...

Another example with props and child components:

github.com/arswaw/happyweather/blo...

I even have an example of mixin use. I will have to find it.

Thread Thread
 
eric_dolecki profile image
 Eric E. Dolecki

Yes, that's perfect! Thank you!!

Collapse
 
kingdomflo profile image
Caribou

Hello
Thanks for the tuto
It work on my server without node js, that the goal and thanks :D

But... If a open the index.html in my local computer, I always have the CORS problem with the
I don't understand why... Have another this same problem?

Collapse
 
kingdomflo profile image
Caribou

Well I have find this: npmjs.com/package/http-server
this work when I run the index.html in this http-server localy

Collapse
 
arswaw profile image
Arswaw

Where are you trying to run it that gives you the error?

Collapse
 
arswaw profile image
Arswaw

Can you take a screenshot of where the error is displayed?

Collapse
 
nssimeonov profile image
Templar++

Can you share this code as a github repo?

Collapse
 
arswaw profile image
Arswaw

It is now hosted here.

Collapse
 
nssimeonov profile image
Templar++

Out of curiosity - why are you loading the router like this:

<script src="https://cdnjs.cloudflare.com/ajax/libs/vue-router/3.0.2/vue-router.js"></script>

but not vue.js too?

Thread Thread
 
arswaw profile image
Arswaw • Edited

Because the developers don't include an ES6 compatible version of the router. Only Vue.

EDIT: Now they do.

Collapse
 
arswaw profile image
Arswaw • Edited

Good idea. I will work on this tomorrow.

Collapse
 
yadavravi2801 profile image
yadavravi2801

Thanks for this Awesome tutorial!

It is working perfectly on major browser but i am facing problem in Edge browser.

There is no error or warnings in the console.

So if there is any way i would like to know.

Thanks

Collapse
 
arswaw profile image
Arswaw

I have faced this issue before. It comes down to a bug with ES6 Modules in older versions of Edge. If you can, update to the latest version. It uses the same engine as Google Chrome.

If you can't update, I have used RollupJS to bypass this issue. It will grab all the ES6 Modules and put them in one file. You can use it from the command line.

Does this help.

Collapse
 
cdsaenz profile image
Charly S. • Edited

This is absolutely beautiful. I couldn't make the router work but maybe there's some error I made, anyway I don't care I just plan on "enhancing" page by page (maybe do some ajax loading) and don't need/want node.js in the middle. I think this approach will be improved upon (along with SFC, the final blessing) in the future and will be a game changer for developers like me.

Collapse
 
arswaw profile image
Arswaw

I'm really glad you like it. I hope that you fix your router problem quickly. I wish I had seen your comment earlier so I could help but you seem to be managing fine since you're not making a SPA.

I think that Node.js is great but I don't think that the associated ecosystem should be required to make data-driven websites. I hope that more people see my project and find it as useful as you have.

Thanks.

Collapse
 
cdsaenz profile image
Charly S.

Hi Arswaw I finally made progress with the routing but it became a bit complex with the login/authorization process and all and I quit; I'm not planning to do a SPA (for now!)

My stuff is mostly PHP + Javascript, the traditional. But the ever increasing need for dynamic blocks, which I covered with JQuery (painfully; JQuery is great but not designed for that) I might move to Vue without issue for those "bricks" that I need to add to the page (axios is incredibly straightforward for ajax too).

I now found a very cheap hosting with node.js so now node.js is possible too, but somehow it introduces another layer, and for my usage, this is a very good toolset.

Looking forward to future articles of yours in the matter!

Thread Thread
 
arswaw profile image
Arswaw

Yeah, router guards can be difficult to set up. I'm guessing that's one of the tasks you had to do to prevent people from gaining unauthorized access by manually typing paths in the address bar.

PHP is tried and true. If it works for you that's great. I'm sure that you'll have no problem transferring that knowledge over to Node.js + Vue. You might also use the Fetch API instead of Axios if you haven't considered that already.

Thanks for your comment! I'd like to write some more articles on this to help spur additional interest from developers like you.

Collapse
 
Sloan, the sloth mascot
Comment deleted
Collapse
 
arswaw profile image
Arswaw

Great job! This looks like an awesome project. Great documentation! I will look at this when I return to my computer.

Collapse
 
du_mass92 profile image
JL Griffin

I'm receiving the error

main.js:8 Uncaught ReferenceError: Cannot access 'router' before initialization
at main.js:8

Collapse
 
samer_elhamdo profile image
SAMER || DEV • Edited

You should. Define the variable first, then use it
// main.js :

//1

import Vue from 'cdn.jsdelivr.net/npm/vue@latest/di...'

import {
Navbar
} from './components/navbar.js'

import {
About
} from './components/about.js'

import {
MainTemplate
} from './templates/main-template.js'

//2
Vue.use(VueRouter)
const router = new VueRouter({
routes: [{
path: '/about',
component: About,
name: "About Us Page"
}]
})

//3

new Vue({

el: '#app', // This should be the same as your

from earlier.

components: {

'navbar': Navbar

},

router,

template: MainTemplate

})
Collapse
 
arswaw profile image
Arswaw

I'm surprised I didn't see your comment. I hope that you were able to fix this error.

Collapse
 
priteshusadadiya profile image
Pritesh Usadadiya

this is pure gold. I was just looking for something like this.

Collapse
 
arswaw profile image
Arswaw

Thank you for the appreciation!

Some comments may only be visible to logged-in visitors. Sign in to view all comments.