DEV Community

Cover image for Making quick blog with modern ES6
Tonis Anton
Tonis Anton

Posted on • Edited on

Making quick blog with modern ES6

Browser engines have evolved enormously and i want to share some ideas how to quickly build a JavaScript Single Page Application,
using cool browser features like

Async functions
Custom elements V1
ES6 Modules

This will be a very basic project, it will take maybe 15 minutes to get running and we wont need to install any node_modules just write the code and boom it's ready.

You can view the live result here and check the full code here

For developing you should use a server for serving your index.html, you can use whatever you like, for example with node.js you can use http-server

Okay so lets start


Basic setup

First lets create some base files, we need index.html for sure, so let's start with that.

In your project folder create a new file called index.html

And add some content to it

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>My blog</title>
    <link rel="stylesheet" href="Application/styles/index.css">
</head>
<body>
   <div id="root"></div>
</body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/webcomponentsjs/1.2.0/webcomponents-lite.js"></script>
<script type="module" src="Application/App.js"></script>
</html>

We have some basic stuff in it like, title, stylesheet, root, and our script tag, i have also included webcomponents-lite.js as a polyfill to increase the browser support for webcomponents, cause they still lack support on some browsers.

Lets focus on the script tag, we have type=module there, we need to use it if we want to activate ES6 modules support in our JS file, so dont forget it.

<script type="module" src="Application/App.js"></script>

Next lets create our folder structure.

Lets make it like this

  • Application
    • components
    • modules
    • styles
    • App.js

Good lets now move on to writing JavaScript


JavaScript files setup

Our Application starts from App.js so let's create this first.

Copy all this to your App.js

import Router from "https://unpkg.com/navigo@6.0.2/src/index.js";
import { HTML } from "https://unpkg.com/kelbas";

const router = new Router();

router.on("/", () => {
  const element = HTML`<h2>Hello</h2>`;
  document.querySelector("#root").replaceWith(element);
});

router.on("/post/:id", params => {});
router.resolve();

Currently when we open our Application we should see Hello

I will explain a bit what our imported libraries do.

For the Router we will be using navigo, it's a very light weight awesome routing library for the browsers.

Then we have the HTML library and that's created by myself, it extends on the idea of using ES6 template strings to create HTML elements.

Next in our components folder lets create 3 files home.js , post.js and index.js

So our components folder looks like this

  • components
    • home.js
    • index.js
    • post.js

In our index.js lets just export the other 2 files, so we could easily import all our components later.

index.js

export { default as home } from "./home.js"
export { default as post } from "./post.js"

Before we can start building our pages we will add one more thing.

In our modules folder, lets create a file called Element.js

  • Application
    • modules
      • Element.js

Our Element.js will be a wrapper for native HTMLElement, i would recommend using a simple wrapper so we supercharge our development with Custom elements

In the newly created Element.js add

Element.js

export default class Element extends HTMLElement {

    //Element is connected
    async connectedCallback() {
        this.install.apply(this)

        this.beforeRender()
        const element = await this.render()
        this.appendChild(element)

        this.installed()
    }

    //Element is removed
    disconnectedCallback() {
        this.uninstall()
    }

    //Update element
    async update() {
        const element = await this.render()
        this.beforeUpdate()
        this.lastChild.replaceWith(element)
        this.afterUpdate()
    }


    //You can use these function to run your functions between component state changes, like with react.js
    install() { }

    installed() { }

    uninstall() { }

    beforeUpdate() { }

    afterUpdate() { }

    beforeRender() { }
}

Creating Web-component pages

Now we have our project fully setup and we can start implementing pages, as this is meant as a short tutorial i will be using JSON-Placeholder for my data.

We can pull posts in by just calling

fetch('https://jsonplaceholder.typicode.com/posts/')
  .then(response => response.json())

Lets add some JavaScript to our /components/home.js

home.js

import { HTML } from "https://unpkg.com/kelbas"
import Element from "../modules/Element.js"


export default class Home extends Element {
    async getPosts() {
       return await fetch("https://jsonplaceholder.typicode.com/posts").then(res => res.json())
    }

    open(id) {
        this.router.navigate(`/post/${id}`)
    }

    async render() {
        const posts = await this.getPosts()
        return HTML`<section id="posts-container">
                        ${posts.map((post) => {
                            return HTML`<div class="post" onclick=${this.open.bind(this, post.id)}>
                                                <h3 class="post-title">${post.title}</h3>
                                                <p class="post-body">${post.body}</p>
                                        </div>`
                         })}
                    </section>`
    }
}

First we add our required libraries, we import the Element.js that we created before and also the HTML parser that i talked about earlier.

If you have developed with React.js then this could seem quite familiar, which is awesome in my opinion, cause we dont need to bundle JSX to render this and it basically has the same API.

We have an async function to pull the data,
then in the render function first we wait for the data and then show the data retrieved, very simple.
We could easily add a Loading... indicator here but i pass it this time for keeping things simple.

There is also the function open, just add it currently, we will implement this.router later in the App.js

Next lets add some JavaScript to our /components/post.js

post.js

import { HTML } from "https://unpkg.com/kelbas"
import Element from "../modules/Element.js"


export default class Post extends Element {
    async getPost() {
       return await fetch(`https://jsonplaceholder.typicode.com/posts/${this.post}`).then(res => res.json())
    }

    async render() {
        const {title, body} = await this.getPost()

        return HTML`<section id="post-container">
                        <h2>${title}</h2>
                        <p>${body}</p>
                    </section>`
    }
}

This is basically the same as home.js but it will load a single post data, notice the this.post value in getPost function, we will add this as an id in the router at App.js

Now lets go back to our App.js, we will implement our newly created components and update the routes

Copy all of this into your App.js

App.js


import Router from "https://unpkg.com/navigo@6.0.2/src/index.js";
import { HTML } from "https://unpkg.com/kelbas";

const router = new Router();

import * as component from "./components/index.js";

for (let key in component) {
  component[key].prototype.router = router;
  customElements.define(`${key}-element`, component[key]);
}

router.on("/", () => {
  const element = HTML`<home-element id="root"></home-element>`;
  document.querySelector("#root").replaceWith(element);
});

router.on("/post/:id", params => {
  const element = HTML`<post-element id="root"></post-element>`;
        element.post = params.id
  document.querySelector("#root").replaceWith(element);
});

router.resolve();

First we import all our components from /Application/components/index.js

Then we attach our router to all our components so we could easily change routes from inside the component
and we also define the customElements as file-name + element so our home.js becomes home-element in custom-elemets registry.

After defining the customElements you can attach <home-element></home-element> to your index.html and it will work, how cool is that.

We also updated our routes, whats happening there is that when route is fired, we will create our HTML customElement that we imported and then defined before.

Then we will use replaceWith on our <div id="root"></div> element in index.html.

Also make sure to give your elements something to be identified with like id or key, so we can find the element that we want to replace each time route changes, thats why home-element and post-element have id=root also.

And now the final part add, lets add our designs.

Add styles

In your styles folder at

  • Application
    • styles

Lets add index.css and fill it with some simple css

index.css

@import "https://unpkg.com/mustard-ui@latest/dist/css/mustard-ui.min.css";
@import "https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.0/normalize.min.css";

body {
  width: 60%;
  margin: 0 auto;
  background: #e67e22;
}
#posts-container {
  display: grid;
  grid-template-columns: 1fr;
  grid-row-gap: 20px;
  margin: 50px 0;
}

#posts-container .post {
  background: #ecf0f1;
  padding: 10px 15px;
  cursor: pointer;
}

#post-container {
  display: grid;
  grid-template-columns: 1fr;
  grid-row-gap: 20px;
  margin-top:50px;
}

#post-container h2 {
  color: #ecf0f1;
}

#post-container p {
  color: #ecf0f1;
  background: #d35400;
  padding: 20px;
}

If you got in trouble you can check the full-code here

I hope my tutorial was helpful and give your thoughts about it in the comments.

Top comments (4)

Collapse
 
larsklopstra profile image
Lars Klopstra ⚡

How about SEO?

Collapse
 
tonis2 profile image
Tonis Anton • Edited

There are some articles that google can handle SPA pages fine,
for example check this here

This would be a great task to see how google indexes such a blog, i will try it out.

And to make all routes work when i open a post/1 for example, i would just forward all routes to index.html

With nginx

 location / {
         try_files $uri $uri/ /index.html;
    }
Collapse
 
larsklopstra profile image
Lars Klopstra ⚡

Cool, very interesting

Collapse
 
willgilbert profile image
Will Gilbert • Edited

Thanks for writing this blog -- I was lost trying to connect some web components to a router. You've made it very clear.

Great post. It helped quite a bit. And love your "kelbas" JSX utility!