DEV Community

loading...
Cover image for Making a portfolio using Webpack, 11ty, and Halfmoon

Making a portfolio using Webpack, 11ty, and Halfmoon

aboutdavid profile image David Originally published at blog.aboutdavid.me ・8 min read

Table Of Contents

πŸ› Resource Shopping/Setting up

Ok, what do I mean by resource shopping? We go around the internet looking ("shopping") for resources (e.g. Bootstrap, jQuery plugins, etc) that we will use in our project. For this mini-project, I've chosen:

  • Halfmoon for our design
  • 11ty to compile our base templates
  • Webpack to compile our assets (Halfmoon, Sticky Sidebar, etc)
  • style-loader & css-loader for Webpack so we can compile CSS

Lets install their NPM packages for Webpack so we can bundle them up:

npm i @11ty/eleventy -g -s
npm i webpack webpack-cli -s -d
npm i halfmoon style-loader css-loader
Enter fullscreen mode Exit fullscreen mode

Note: Webpack 5.0.0-beta.1 and higher must use a node version higher than 10.13.0 (LTS). So, since I'm using Glitch to create this, I'm going to change in the package.json:

"engines": { "node": "10.x" }
Enter fullscreen mode Exit fullscreen mode

to

"engines": { "node": "12.x" }
Enter fullscreen mode Exit fullscreen mode

and add a .nvmrc (for netlify) with just 12.9.0. If you are not using Netlify or Glitch, then just make sure you have a higher version installed.

Ok, we have our node versions setup and packages installed, lets start with webpack.

πŸ“¦ Webpack config

Projects that use a bunch of files probably should use something called Webpack. Webpack basically allows you to bundle your assets into 1 (or more) files for your browser. Pretty cool right?

Let's make a file called webpack.config.js with the following:

const path = require("path");

module.exports = {
  entry: "./src/index.js",
  mode: 'development',
  output: {
    filename: "bundle.js",
    path: path.resolve(__dirname, "dist")
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: ["style-loader", "css-loader"]
      }
    ]
  }
};
Enter fullscreen mode Exit fullscreen mode

This basically says "Bundle the file src/index.js into .dist/bundle.js"
(oh and by the way, make the file src/index.js and the folder dist because we will need it in a moment)

In our src/index.js file, let's require our files/packages that we went over in Resource Shopping/Setting up with this code:

require("halfmoon/css/halfmoon.min.css"); // require halfmoon.css
window.halfmoon = require("halfmoon"); // require halfmoon.js
console.log("Webpack loaded!"); // Show in the console that the webpack bundle is loaded.
Enter fullscreen mode Exit fullscreen mode

Now we can bundle them together! Just run webpack and it should bundle your stuff together! Your bundle should be in dist/bundle.js if you made it right.

That's all you need to do to bundle your modules together.

βš™ Setting up 11ty

This will be a shorter section because 11ty does not need any config to run.

Make a folder called _includes. Then in that folder, make a file called base.njk with the following:

<!DOCTYPE html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="HandheldFriendly" content="true">
</head>

<body>
{{ content | safe }}
<footer>

</footer>
</body>
Enter fullscreen mode Exit fullscreen mode

That's right. .njk files (nunjuck files) use HTML! But, whats this {{ content | safe }} thing? Well, since _includes/base.njk is a layout, we can tell our pages (like index.njk) to use _includes/base.njk as a layout.

Now, we can make our index.njk file (which will be located at / when we compile it.) Lets just insert:

---
layout: base.njk
---
<h1>Hello!</h1>
Enter fullscreen mode Exit fullscreen mode

in that file. Before we run it, let's do some automation to make it easier for us to code our portfolio. In our package.json file add:

"scripts": {"start": "eleventy --serve --port=3000"}
Enter fullscreen mode Exit fullscreen mode

to your file. Now you can visit your demo site at locathost:3000 (or just your regular domain if you are using Glitch). It should look something like this:
hello_world

Yay! We have used successfully used 11ty layouts! One last thing: We need to make sure 11ty adds our bundle. So make a new file called .eleventy.js and add the following:

module.exports = function(eleventyConfig) {
  eleventyConfig.addPassthroughCopy("img");
  eleventyConfig.addPassthroughCopy("dist");
};
Enter fullscreen mode Exit fullscreen mode

This tells 11ty to allow the img folder and the dist folder.

πŸ‘¨β€πŸ’» Coding our base templates

Ok, lets get into the design part! Go back to our base layout and include our bundle:

<script src="/dist/bundle.js"></script>
Enter fullscreen mode Exit fullscreen mode

HalfmoonCSS, HalfmoonJS, and Sticky Sidebar should now be included in your webpage!

Now, why don't we add a navbar? Like many sites, this allows your users to easily navigate your site.

Add the following code to your _includes/base.njk file inside the "body" tag:

<!DOCTYPE html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="HandheldFriendly" content="true">
</head>
<body data-set-preferred-mode-onload="true">
<!-- Navbar: https://www.gethalfmoon.com/docs/navbar/ -->
<header>
<nav class="navbar" style="background-image:url('https://cdn.glitch.com/d4417ab9-7b55-405a-bfbf-6194880aa9f7%2Fmarek-piwnicki-bNnYAs5pXbw-unsplash.jpg?v=1603332064144');">
  <!-- Navbar brand -->
  <a href="/" class="navbar-brand">
    <img src="https://cdn.glitch.com/d4417ab9-7b55-405a-bfbf-6194880aa9f7%2Fninja-cat_1f431-200d-1f464.png?v=1603305812684" alt="...">
  </a>
  <!-- Navbar nav -->
  <ul class="navbar-nav ml-auto"> 
    <li class="nav-item active">
      <a href="/" class="nav-link">Home</a>
    </li>
    <button class="btn btn-primary" type="button" onclick="halfmoon.toggleDarkMode();">πŸŒ™</button>
  </ul>
</nav>
</header>
{{ content | safe }}
<footer>
<script src="/dist/bundle.js"></script>
</footer>
</body>
Enter fullscreen mode Exit fullscreen mode

first demo

Hey! Did you see that πŸŒ™ button? That's the darkmode toggle, which allows you to toggle on and off dark mode! It looks something like this:

dark mode

Pretty cool right? You can trigger it yourself using:

halfmoon.toggleDarkMode();
Enter fullscreen mode Exit fullscreen mode

πŸ“„Making the "portfolio" part

Let's get rid of the "Hello" header:

---
layout: base.njk
---
- <h1>Hello!</h1>
Enter fullscreen mode Exit fullscreen mode

We can now make "about me" cards and show off our knowledge of certain skills.

For this part, we will be using:

Make a new file called "about.njk":

---
eleventyExcludeFromCollections: true
---
<div class="container-fluid" style="padding-left: 5px;">
    <div class="card">
        <h4>Who am I?</h4>
        <img src="https://cdn.glitch.com/d4417ab9-7b55-405a-bfbf-6194880aa9f7%2Fwilliam-recinos-qtYhAQnIwSE-unsplash.jpg" width="225" height="150" />
        <p>My name is John Doe. I am a freelance web devoloper with many years of expierence. I also play the violin the my spare time.</p>
        <h2 class="card-title">Skills</h2>
        <p>HTML</p>
        <div class="progress">
            <div class="progress-bar" style="width: 78%;" role="progressbar" aria-valuenow="78" aria-valuemin="0" aria-valuemax="100"></div>
        </div>
        <p>CSS</p>
        <div class="progress">
            <div class="progress-bar" style="width: 64%;" role="progressbar" aria-valuenow="54" aria-valuemin="0" aria-valuemax="100"></div>
        </div>
        <p>Javascript</p>
        <div class="progress">
            <div class="progress-bar" style="width: 54%;" role="progressbar" aria-valuenow="54" aria-valuemin="0" aria-valuemax="100"></div>
        </div>
        <p>Ruby</p>
        <div class="progress">
            <div class="progress-bar" style="width: 32%;" role="progressbar" aria-valuenow="32" aria-valuemin="0" aria-valuemax="100"></div>
        </div>
    </div>
</div>

Enter fullscreen mode Exit fullscreen mode

And add this to your main index.njk file:

{% include "about.njk" %}
Enter fullscreen mode Exit fullscreen mode

this should include the about.njk file into the index.njk file.

You should see something like this:
about cards

You can add new skill bars by adding this:

<p>Skill Name</p> 
<div class="progress"> 
<div class="progress-bar" style="width: value%" role="progressbar" aria-valuenow="value" aria-valuemin="0" aria-valuemax="100"></div> 
</div>
Enter fullscreen mode Exit fullscreen mode

Alright looking good. We also want to show off our projects as any good web developer would do.

Make a new file called "projects.njk" and add the following:

---
eleventyExcludeFromCollections: true
---
<div class="container-fluid" style="padding-left: 5px;">
    <div class="card">
        <h4>Projects:</h4>
        <ul>
            <li><a href="#"><b>Project one:</b></a> Project description</li>
            <li><a href="#"><b>Project two:</b></a> Project description</li>
            <li><a href="#"><b>Project three:</b></a> Project description</li>
        </ul>
    </div>
</div>
Enter fullscreen mode Exit fullscreen mode

Now we can modify our index.njk file to look like this:

{% include "about.njk" %}
+
+ {% include "projects.njk" %}
Enter fullscreen mode Exit fullscreen mode

Great! Now we the about section and our project showcase. We now have a semi-portfolio using Webpack.

🏭 Making it "production" ready

Ok, let's make it production ready. Bascially, Polishing every thing up, doing some SEO, etc.

Changing webpack settings

The webpack bundle we made is huge! So let's tell webpack to remove extra stuff and compress the file. Go into the webpack.config.js file and modify it slightly:

const path = require("path");
+ const { CleanWebpackPlugin } = require("clean-webpack-plugin");
module.exports = {
  entry: "./src/index.js",
-  mode: "development",
+  mode: "production",
  output: {
    filename: "bundle.js",
    path: path.resolve(__dirname, "dist")
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: ["style-loader", "css-loader"]
      }
    ]
  },
+  plugins: [new CleanWebpackPlugin()]
};
Enter fullscreen mode Exit fullscreen mode

Oh and don't forget to install clean-webpack-plugin, which deletes your old bundle so no old data is stored.

npm i clean-webpack-plugin
Enter fullscreen mode Exit fullscreen mode

You may now run webpack and you should now have a production ready bundle!

Improving SEO: Sitemap

Lets make a sitemap. This should allow us to submit the portfolio to Google easier (source):

---
permalink: /sitemap.xml
eleventyExcludeFromCollections: true
---
<?xml version="1.0" encoding="utf-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
{%- for page in collections.all %}
  {% set absoluteUrl %}{{ page.url | url  }}{% endset %}
  <url>
    <loc>https://{{ metadata.domain }}{{ absoluteUrl }}</loc>
  </url>
{%- endfor %}
</urlset>
Enter fullscreen mode Exit fullscreen mode

Improving SEO: Metatags

Metatags allow for website previews. They give your users a preview of your website without having to see it. Sort of like a "try before you buy" thing. You can easily generate some easily with metatags.io. They look like this:

<!-- Primary Meta Tags -->
<title>Demo portfolio: The portfolio of John doe</title>
<meta name="title" content="Demo portfolio: The portfolio of John doe">
<meta name="description" content="My name is John Doe. I have a bunch of experience with HTML, CSS, JavaScript and Ruby.">

<!-- Open Graph / Facebook -->
<meta property="og:type" content="website">
<meta property="og:url" content="https://demo-portfolio-11ty.glitch.me/">
<meta property="og:title" content="Demo portfolio: The portfolio of John doe">
<meta property="og:description" content="My name is John Doe. I have a bunch of experience with HTML, CSS, JavaScript and Ruby.">
<meta property="og:image" content="[IMAGE]">

<!-- Twitter -->
<meta property="twitter:card" content="summary_large_image">
<meta property="twitter:url" content="https://demo-portfolio-11ty.glitch.me/">
<meta property="twitter:title" content="Demo portfolio: The portfolio of John doe">
<meta property="twitter:description" content="My name is John Doe. I have a bunch of experience with HTML, CSS, JavaScript and Ruby.">
<meta property="twitter:image" content="[IMAGE]">
Enter fullscreen mode Exit fullscreen mode

Compressing your HTML

This is pretty easy to do. First install html-minifier with NPM:

npm i html-minifier
Enter fullscreen mode Exit fullscreen mode

Add the following snippet to .eleventy.js:

const htmlmin = require("html-minifier");
eleventyConfig.addTransform("htmlmin", function (content, outputPath) {
    if (outputPath.endsWith(".html") || outputPath.endsWith(".css") || outputPath.endsWith(".js")) {
        let minified = htmlmin.minify(content, {
            useShortDoctype: true,
            removeComments: true,
            collapseWhitespace: true,
            minifyJS: true,
            minifyCSS: true,
        });
        return minified;
    }

    return content;
});
Enter fullscreen mode Exit fullscreen mode

This tells 11ty to use html-minifier to compress HTML, JavaScript, and CSS files.
compressed html

Looking good!

πŸ“” Notes & Credits

In this tutorial, we used Webpack to bundle assets (a simple bundle at that), 11ty to make our portfolio, and Halfmoon for the main design.

Thanks to @khalby786 for making brockly, which made the browser-like previews.

Some snippets of code (such as the sitemap and html mimifier came from 11ty/eleventy-base-blog and the 11ty docs.

You can get a ready made version on Glitch by clicking here (make an account and edit the _data/metadata.json file)

You can see the demo here

Discussion

pic
Editor guide