Build a Website with Modern Tooling and No Frameworks

In this tutorial we will be building a website without a front-end framework but making use of Webpack to support writing modern JavaScript (ES6+), including ES Modules and Sass.

We all love React, Vue, Angular, [front-end framework]. But, it might be overkill to use them if you just need to build a simple website.

[topads][/topads]

Features include:

  • Live reload in development
  • Webpack
  • Sass compilation (and minification/autoprefixing in production)
  • ES6+ transpilation (and minification/uglyfication in production)
  • ES Modules

Setting up

To create the directory structure and all the necessary files copy the following to your terminal (Windows users, I’m not sure if the commands mkdir and touch will work for you. If not, you will have to create the directories and files one by one.)

mkdir -p vanilla-website/{public/assets/{images,scripts,styles},src/{scripts,styles}}

cd vanilla-website
yarn init -y

touch .babelrc webpack.config.js public/index.html src/{scripts/{main.js,utils.js},styles/{main.scss,reset.scss}}

You should end up with the following project structure

|-- .babelrc
|-- package.json
|-- /public
| |-- /assets
| | |-- /images
| | |-- /scripts
| | |-- /styles
| |-- index.html
|-- /src
| |-- /scripts
| | |-- main.js
| | |-- utils.js
| |-- /styles
| |-- main.scss
| |-- reset.scss
|-- webpack.config.js
  • /public — holds the website
  • /src — holds the source files

Still, in the root of the project, copy the following to your terminal to install the needed dependencies

yarn add -D @babel/core babel-loader @babel/preset-env css-loader live-server mini-css-extract-plugin node-sass concurrently postcss postcss-loader postcss-preset-env sass-loader webpack webpack-cli
  • @babel/core, babel-loader, @babel/preset-env — Transpile our ES6+ JavaScript to ES5, or whatever browsers we decide to support
  • css-loader, mini-css-extract-plugin, node-sass, postcss-loader, postcss, postcss-preset-env, sass-loader — Process our Sass, including autoprefixing, minification and extracting the CSS to a separate file
  • live-server — Development server with auto-reloading capabilities when files change
  • concurrently — Run multiple npm-scripts in parallel or sequential
  • webpack, webpack-cli — Module bundler, the glue that puts everything together

Now you can open the project in your favorite editor (mine is VS Code) and bring up package.json. Copy the following scripts

"scripts": {
  "dev:assets": "webpack --watch",
  "dev:start": "live-server --open=./public/ --host=localhost --watch=./public/",
  "dev": "concurrently 'yarn:dev:*' ",
  "build": "NODE_ENV=production webpack"
},
  • dev:assets — Running Webpack in watch mode will watch for changes to files in our /src directory and recompile everything. Webpack will place the compiled files under the /public directory
  • dev:start — Start our development server, launch the browser to the /public directory and watch for changes under the /public directory. Website will open to http://localhost:8080/public
  • dev — Run in parallel all of the scripts that start with dev:. Basically, it will run dev:assets and dev:start giving us a nice development environment with live reload
  • build — Run Webpack in production mode, optimizing CSS and JavaScript files. The outputted files will replace the CSS/JS development files under the /public directory, then you can take everything under this directory and deploy it to your production server

Note to Windows users

You can install cross-env and use the package in the build script to support adding node environment variables via CLI.

Something like this: "build": "cross-env NODE_ENV=production webpack"

[signupform][/signupform]

Configuration

Open .babelrc and copy the following

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "targets": {
          "browsers": ["> 1%"]
        }
      }
    ]
  ]
}

Here we are making use of @babel/preset-env and instructing Babel to transpile our JavaScript code to support browsers that are used in more than 1% of the global market browser share. You can modify the target browsers according to your needs by using supported queries.

Here is the full list of the browsers with our current configuration: http://browserl.ist/?q=%3E+1%25

Remember in our build script we set NODE_ENV to production? Well, we read that environment variable with process.env.NODE_ENV, and that’s how we know we are running in production mode.

It is time to configure Webpack, open webpack.config.js and copy the following

const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

const devMode = process.env.NODE_ENV !== 'production';

module.exports = {
  // Tells Webpack which built-in optimizations to use
  // In 'production' mode, Webpack will minify and uglify our JS code
  // If you leave this out, Webpack will default to 'production'
  mode: devMode ? 'development' : 'production',

  // Webpack needs to know where to start the bundling process,
  // so we define the main JS and Sass files, both under
  // the './src' directory
  entry: ['./src/scripts/main.js', './src/styles/main.scss'],

  // This is where we define the path where Webpack will place
  // the bundled JS file
  output: {
    path: path.resolve(__dirname, 'public'),

    // The name of the output bundle. Path is also relative
    // to the output path
    filename: 'assets/scripts/bundle.js',

    // Indicates where the images are stored and will use
    // this path when generating the CSS files.
    // Example, in main.scss I have
    // url('../../public/assets/images/venice-italy.jpg')
    // and when generating the CSS file, it will be outputted
    // as url(../images/venice-italy.jpg), which is relative
    // to /styles/main.css
    assetModuleFilename: 'assets/images/[name][ext]',
  },
  module: {
    // Array of rules that tells Webpack how the modules (output)
    // will be created
    rules: [
      {
        // Look for JavaScript files and apply the babel-loader
        // excluding the './node_modules' directory. It uses the
        // configuration in `.babelrc`
        test: /\.(js)$/,
        exclude: /node_modules/,
        use: ['babel-loader'],
      },
      {
        // Look for Sass files and process them according to the
        // rules specified in the different loaders
        test: /\.(sa|sc)ss$/,

        // Use the following loaders from right-to-left, so it will
        // use sass-loader first and ending with MiniCssExtractPlugin
        use: [
          {
            // Extracts the CSS into a separate file and uses the
            // defined configurations in the 'plugins' section
            loader: MiniCssExtractPlugin.loader,
          },
          {
            // Interprets CSS
            loader: 'css-loader',
            options: {
              importLoaders: 2,
            },
          },
          {
            // Use PostCSS to minify and autoprefix with vendor rules
            // for older browser compatibility
            loader: 'postcss-loader',
            options: {
              postcssOptions: {
                // We instruct PostCSS to use postcss-preset-env
                // when in production mode, otherwise do nothing
                plugins: devMode
                  ? []
                  : [
                      [
                        'postcss-preset-env',
                        {
                          // Compile our CSS code to support browsers
                          // that are used in more than 1% of the
                          // global market browser share. You can modify
                          // the target browsers according to your needs
                          // by using supported queries.
                          // https://github.com/browserslist/browserslist#queries
                          browsers: ['>1%'],
                        },
                      ],
                    ],
              },
            },
          },
          {
            // Adds support for Sass files, if using Less, then
            // use the less-loader
            loader: 'sass-loader',
          },
        ],
      },
      {
        // Adds support to load images in your CSS rules. It looks for
        // .png, .jpg, .jpeg and .gif. Filename is specified in output.
        test: /\.(png|jpe?g|gif)$/,

        // emits a separate file and exports the URL.
        // Previously achievable by using file-loader
        type: 'asset/resource',
      },
    ],
  },
  plugins: [
    // Configuration options for MiniCssExtractPlugin. Here I'm only
    // indicating what the CSS outputted file name should be and
    // the location
    new MiniCssExtractPlugin({
      filename: 'assets/styles/main.css',
    }),
  ],
};

Refer to the comments in the code to understand the configuration.

Coding the site

I will not be explaining everything from here on, since the focus of this tutorial is mainly to show how to set-up your environment for development.

Open ./public/index.html and copy the following

<!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>No Framework Website</title>

<link rel="stylesheet" href="./assets/styles/main.css">
</head>

<body>
  <div class="wrapper">
    <header>
      <h1>No Framework Website</h1>
    </header>
    <main>
      <h2>This site rocks ??</h2>
      <div class="bacon">
        <img src="./assets/images/oval.svg" alt="loading">
      </div>
    </main>
    <footer>
      <p>Made with ?? by <a href="https://twitter.com/_esausilva" target="_blank">Esau Silva</a></p>
    </footer>
  </div>
  <script src="./assets/scripts/bundle.js"></script>
</body>

</html>

Two things to notice here: the path to our stylesheet and our JavaScript file (highlighted). This is exactly where Webpack is going to place our processed files.

Open ./src/styles/reset.scss and copy the following

html {
  height: 100%;
  box-sizing: border-box;
  font-size: 100%;
}
*,*:before,*:after { box-sizing: inherit; }
body {
  margin: 0;
  padding: 0;
  height: 100%;
  overflow-x: hidden;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial,
    sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
  color: #3b3b3b;
}
h1,h2,h3,h4,h5 {
  margin: 6px;
  padding: 0;
}

Open ./src/styles/main.scss and copy the following

@import 'reset.scss';
.wrapper {
  min-height: 100vh;
  display: grid;
  grid-template-columns: 1fr 2fr 1fr;
  grid-template-rows: auto 1fr auto;
  line-height: 23px;
  & > * { grid-column: 2 / -2; }
  header {
    grid-column: 1 / -1;
    background: #fdbc9a url('../../public/assets/images/venice-italy.jpg') no-repeat center;
    background-size: cover;
    height: 500px;
    margin-bottom: 20px;
    display: flex;
    justify-content: center;
    align-items: center;
    color: #fff;
    text-align: center;
  h1 {
      font-size: 3.5em;
      text-shadow: -2px -2px 3px rgb(163, 162, 162);
    }
  }
  footer {
    height: 50px;
    display: flex;
    justify-content: flex-end;
  }
}
.bacon > img {
  width: 75px;
  display: block;
  margin: auto;
}

Open ./src/scripts/utils.js and copy the following

export const GetBacon = () => {
  const body = fetch('https://baconipsum.com/api/?type=all-meat&paras=3').then( res => res.json() );
  return body;
};

Fetches the Bacon Ipsum API and brings back 3 paragraphs.

Open ./src/scripts/main.js and copy the following

import { GetBacon } from './utils';

const baconEl = document.querySelector('.bacon');

GetBacon()
  .then(res => {
    const markup = res.reduce((acc, val) => (acc += `<p>${val}</p>`), '');
    baconEl.innerHTML = markup;
  }).catch(err => (baconEl.innerHTML = err));

Nothing complicated here; we’re just creating three dummy paragraphs and injecting them into the website.

The whole point is to show you can write modern JavaScript, including ES Modules, which will be processed through Webpack and transpiled to JavsScript that browsers can understand.

Finally, download these two images, image1 and image2, and place them under ./public/assets/images directory.

Now with everything done, open your terminal to the root of the project and type the following for development

yarn dev

Watch the browser live reload as you make changes to your source files. Type the following for production

yarn build

Take the ./public directory and deploy it to your server!!

You can get the full code on the Github repository.

Conclusion

You made it to the end. Congratulations ?. Now you have the tools to start creating sites with modern JavaScript and Sass. From here, it will be easy to extend Webpack if you want to support more things.

You could even combine Webpack and a templating language to create more robust websites, like Nunjucks or Handlebars.

Thank you for reading and I hope you enjoyed it. If you have any questions, suggestions or corrections let me know in the comments below. Don’t forget to give this article a share and you can follow me on TwitterGitHubMediumLinkedIn.


I originally published this article in Bits and Pieces

[bottomads][/bottomads]

Spread the love

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.