Build a Landing Page with Webpack 4
If you are a designer, a frontend developer or just someone who would like to learn how to use Webpack to build an awesome landing page, then this is for you.
Using build tools can massively improve your development speed and your developer happiness, but learning how to master those tools can be overwhelming, especially if you are new to JavaScript.
Here are the steps you need to take to start making your developer life easier with tools.
- Choose Terminal or Powershell, or install a Terminal. For example Cmder for Windows or iTerm2 for MacOS.
- Install Nodejs on your machine. Here are some resources to help you if you are on Windows, MacOS or Linux
- Get familiar with NPM, a Nodejs package manager. https://www.sitepoint.com/beginners-guide-node-package-manager/
- Learn the very basics of Sass and Handlebars.
- Take a look at the Webpack website
Don’t choose a pre-made build system yet
There are a number of really great Starter projects for various technology stacks available.
- https://github.com/gdi2290/angular-starter for Angular projects
- https://github.com/kriasoft/react-starter-kit for React projects
- … many more
Those projects are very, very good resources for advanced Webpack configurations, but they are not for you if you try to get started.
Unless you know exactly what you are about to build and who is going to use it, there is a large chance that you end up over-engineering right from the start.
Webpack shines when it comes to versatility. It can easily scale from small asset bundling to large corporate build systems.
A simple, yet useful setup
This is the website we are going to create. A simple marketing landing page for your next project.
You can find the complete source code on Github
Let’s jump into defining our needs.
Here are some things that every web projects might need in 2018:
- CSS styles
- A template engine
- Basic JavaScript for newsletter sign-up, image sliders or support chat widgets
- Nice custom fonts
Start by creating a new folder, let’s call it awesome-service
Execute to following commands in your terminal or create the folder manually
mkdir awesome-service
cd awesome-service
Every terminal command in this post assumes that you are in the awesome-service
directory.
The first thing that we need is a package.json
file to install 3rd party libraries like webpack through npm.
Create a new file by running npm init
in your terminal and leave all options as is.
The output should look like this.
{
"name": "awesome-service",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC"
}
Next, we are going to install webpack and webpack-cli by running npm install --save-dev webpack webpack-cli
Now its time to think a little bit about our workflow. The power of tooling in front-end projects is the ability to separate our working code from the code that our tools output. To introduce a convention we create two folders. src
where all our working code lives and dist
where the generated files will be outputted.
mkdir src && mkdir dist && mkdir build
The build
directory is used to organize all build-system related files including the configurations for Webpack. By conventions this file is called webpack.config.js
so let's stick to this name and create the file inside the build
directory.
touch build/webpack.config.js
A very basic Webpack configuration should look like this.
const path = require('path');module.exports = {entry: './src/app.js',output: {filename: 'bundle.js',path: path.resolve(__dirname, '../dist')}};
Let’s give it a try. For that, we create an empty app.js
file in the src
directory. After that, we add a script to our package.json
file which runs Webpack with our config file.
"scripts": {"build": "webpack --config ./build/webpack.config.js","test": "echo \"Error: no test specified\" && exit 1"}
Now you can use the command npm run build
to trigger Webpack. The output should look similar to this.
> webpack --config ./build/webpack.config.jsHash: c7c3ee3e2684c5f6aa62Version: webpack 4.1.1Time: 83msBuilt at: 2018-3-21 08:20:09Asset Size Chunks Chunk Namesbundle.js 545 bytes 0 [emitted] mainEntrypoint main = bundle.js[0] ./src/app.js 0 bytes {0} [built]WARNING in configurationThe 'mode' option has not been set. Set 'mode' option to 'development' or 'production' to enable defaults for this environment.
Compile Handlebars templates to HTML
The next thing we are going to add is support for a template engine. In this example we choose Handlebars, but this process is easily applicable to other engines like pug, Mustache or ejs.
In our src
folder we create the index.handlebars
as our main handlebars file.
<!doctype html><html lang="en-US"><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8"><title>{{htmlWebpackPlugin.options.title}}</title><!--[if lt IE 9]><body></body></html>
In order to tell webpack to use this file and compile it to regular html, we need something called a loader.
Loaders are Webpack’s way of handling all sorts of files. Think of it like that — “We want to load our index.handlebars
file and output the result into the dist
folder."
You will get used to this concept, I promise!
To make it work we need to install two modules.
handlebars-loader
html-webpack-plugin
npm install --save-dev handlebars-loader html-webpack-plugin
Webpack is designed to accept an entry file and do it’s magic from there. This behavior can be enhanced with plugins. The html-webpack-plugin
takes your html file and places all generated scripts, stylesheets and more inside without you taking care of it manually.
Edit your webpack.config.js
file so it looks like this.
const path = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');module.exports = {
entry: {
bundle: './src/app.js'
} ,
output: {
path: path.resolve(__dirname, '../dist')
},
module: {
rules: [
{ test: /\.handlebars$/, loader: "handlebars-loader" },
]
},
plugins: [
new webpack.LoaderOptionsPlugin({
options: {
handlebarsLoader: {}
}
}),
new HtmlWebpackPlugin({
title: 'My awesome service',
template: './src/index.handlebars'
})
]
};
As you can see, we describe our loaders in the modules
object in the rules
array. This is where all your Webpack rules go. Right now we only have one which is the rule for loading handlebars
templates.
/* ... */{ test: /\.handlebars$/, loader: "handlebars-loader" }/* ... */
It tests all processed files for the .handlebars
extension and applies the handlebars-loader
to it.
The HtmlWebpackPlugin
introduces the index.handlebars
file to our webpack build pipeline. We have to specify the path where our template is located plus any amount of arbitrary data. Here it is just a title that we can use in our template like this: <title>{{htmlWebpackPlugin.options.title}}</title>
.
Handling stylesheets with SCSS and Postcss
The next thing we need is a way to integrate our stylesheet. For this example, we use sass to pre-compile our styles. To handle scss
files properly we also need more loaders and plugins.
npm install sass-loader node-sass postcss-loader mini-css-extract-plugin css-loader --save-dev
Now we can tell Webpack how to handle the new .scss
files.
const path = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");module.exports = {
entry: {
bundle: './src/app.js'
} ,
output: {
path: path.resolve(__dirname, '../dist')
},
module: {
rules: [
{ test: /\.handlebars$/, loader: "handlebars-loader" },
{
test: /\.(scss|css)$/,
use: [
MiniCssExtractPlugin.loader,
{
loader: "css-loader",
options: {
}
},
{
loader: "sass-loader",
options: {
}
}
]
}
]
},
plugins: [
/** Since Webpack 4 */
new webpack.LoaderOptionsPlugin({
options: {
handlebarsLoader: {}
}
}),
new MiniCssExtractPlugin({
filename: "[name]-styles.css",
chunkFilename: "[id].css"
}),
new HtmlWebpackPlugin({
title: 'My awesome service',
template: './src/index.handlebars'
})
]
};
To use the stylesheets it is getting a bit more complex, so let’s go through this step by step.
Again we are testing for a new filetype, in this case scss
and also css
files if you choose to use regular css
files alongside.
/* ... */
test: /\.(scss|css)$/
/* ... */
Then we are telling Webpack to use a whole chain of loaders to process each file. When Webpack discovers a file that is matching the test
pattern, it will execute the loaders from Bottom to Top.
So the first loader that kicks in is the sass-loader
.
{loader: "sass-loader",options: {}}
It transpiles scss
files into css
files.
The next loader is the css-loader
. It accepts the output from the previous loader (sass-loader
) and transforms it into a raw string value. Webpack then passes this string to the MiniCssExtractPlugin.loader
which takes care of creating a css
file in our dist
directory.
When you now run the build with npm run build
you will notice that nothing happened, but don't worry. There is just one more piece to the puzzle missing.
The most confusing part is the fact, that the styles don’t get loaded into the dist
folder, unless you require
or import
them inside your app.js
file. The reason for this is, that webpack was designed with modern single-page apps in mind. Many frontend frameworks like React or Angular inline the styles into the javascript bundles to dynamically create <style>
tags.
For now, we want our styles as a separate .css
file in our dist
directory since we don't use a javascript framework.
To fix this and have the stylesheets loaded correctly addimport './main.scss';
to your app.js
file. Now Webpack is aware of our stylesheet (remember that app.js
is our entry file) and can process it.
Alongside the power of sass, we can also use the really nice tool postcss to further automate our workflow. In this example we use postcss
with the autoprefixer
plugin to automatically add browser prefixes for our style rules.
Postcss helps us to focus on writing modern css without keeping all the browser prefixes in mind.
To add Postcss with the autoprefixer
functionality we need to add the postcss-loader
to the Webpack system. Install postcss-loader
and autoprefixer
with npm install --save-dev postcss-loader autoprefixer
and edit the webpack.config.js
file.
const path = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const autoprefixer = require('autoprefixer');module.exports = {
entry: {
bundle: './src/app.js'
} ,
output: {
path: path.resolve(__dirname, '../dist')
},
module: {
rules: [
{ test: /\.handlebars$/, loader: "handlebars-loader" },
{
test: /\.(scss|css)$/,
use: [
MiniCssExtractPlugin.loader,
{
loader: "css-loader",
options: {
}
},
{
loader: "postcss-loader",
options: {
autoprefixer: {
browsers: ["last 2 versions"]
},
plugins: () => [
autoprefixer
]
},
},
{
loader: "sass-loader",
options: {
}
}
]
}
]
},
plugins: [
/** Since Webpack 4 */
new webpack.LoaderOptionsPlugin({
options: {
handlebarsLoader: {}
}
}),
new MiniCssExtractPlugin({
filename: "[name]-styles.css",
chunkFilename: "[id].css"
}),
new HtmlWebpackPlugin({
title: 'My awesome service',
template: './src/index.handlebars'
})
]
};
autoprefixer
allows you to specify a set of browsers you want to target. Here we want to add vendor prefixes for the last 2 versions of all major browsers.
If you need more control over the output visit the Browserlist project to see more options https://github.com/ai/browserslist.
Adding Source maps support
Webpack’s generated output is not meant to be human-readable. You basically lose the ability to debug your code while visiting the site in your browser.
Sourcemaps help you to map the generated output to your original source file. These source maps can be either inline at the end of your output file or in a separate .map
file. Browsers automatically pick up those files and use them to show you the proper lines in your source code for a style rule or a line of JavaScript code.
To add source map support, edit the loader options in your webpack.config.js
file.
const path = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const autoprefixer = require('autoprefixer');module.exports = {
entry: {
bundle: './src/app.js'
} ,
output: {
path: path.resolve(__dirname, '../dist')
},
devtool: "source-map",
module: {
rules: [
{ test: /\.handlebars$/, loader: "handlebars-loader" },
{
test: /\.(scss|css)$/,
use: [
MiniCssExtractPlugin.loader,
{
loader: "css-loader",
options: {
sourceMap: true
}
},
{
loader: "postcss-loader",
options: {
autoprefixer: {
browsers: ["last 2 versions"]
},
sourceMap: true,
plugins: () => [
autoprefixer
]
},
},
{
loader: "sass-loader",
options: {
sourceMap: true
}
}
]
}
]
},
plugins: [
/** Since Webpack 4 */
new webpack.LoaderOptionsPlugin({
options: {
handlebarsLoader: {}
}
}),
new MiniCssExtractPlugin({
filename: "[name]-styles.css",
chunkFilename: "[id].css"
}),
new HtmlWebpackPlugin({
title: 'My awesome service',
template: './src/index.handlebars'
})
]
};
This tells webpack to actually output the generated source maps. You might choose to not do this when you bundle your project files for production use.
When you inspect an element in your browser you can see that it actually maps to the main.scss
file instead of the generated bundle-styles.css
file.
Load image files
Another thing we need is the ability to load images. For example, if you want to add a background image with css.
Install the following packages with npm install --save-dev file-loader image-webpack-loader
.
The file-loader
will take care of loading the images and placing them into the correct folder in your dist
directory. image-webpack-loader
automatically optimizes your images so that we don't accidentally serve a 3.5 MB large image to our users.
Add this loader configuration to your rules
array in your webpack.config.js
file.
/* ... */
{
test: /\.(jpg|png|gif)$/,
use: [
{
loader: "file-loader",
options: {
name: '[name].[ext]',
outputPath: 'static/',
useRelativePath: true,
}
},
{
loader: 'image-webpack-loader',
options: {
mozjpeg: {
progressive: true,
quality: 65
},
optipng: {
enabled: true,
},
pngquant: {
quality: '65-90',
speed: 4
},
gifsicle: {
interlaced: false,
},
webp: {
quality: 75
}
}
}
]
}
/* ... */
Adding a development server
Running npm run build
after every single change to your source code can get frustrating and is not the way we want to use Webpack in your workflow.
To have a development server while building the site we can use webpack-dev-server
.
It’s a tool which serves all of our files in watch mode while we are developing. So every time we make a change Webpack will recompile all files and serve them to our local machine.
npm install --save-dev webpack-dev-server
The development server accepts a number of different configuration options. A very common way to configure webpack-dev-server
is by using a devServer
property in our webpack.config.js
file.
Add the following code in your config file just after devtool: "source-map",
.
/* ... */
devServer: {
port: 3000,
open: true
}
/* ... */
This is a basic configuration for webpack-dev-server
. To start a web server with an npm
script add a start
command to your package.json
file.
{
"name": "awesome-service",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "webpack-dev-server --config ./build/webpack.config.js",
"build": "webpack --config ./build/webpack.config.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"devDependencies": {
"autoprefixer": "^8.1.0",
"css-loader": "^0.28.11",
"handlebars": "^4.0.11",
"handlebars-loader": "^1.7.0",
"html-webpack-plugin": "^3.0.7",
"mini-css-extract-plugin": "^0.2.0",
"node-sass": "^4.7.2",
"postcss-loader": "^2.1.1",
"postcss-normalize": "^4.0.0",
"sass-loader": "^6.0.7",
"style-loader": "^0.20.3",
"webpack": "^4.2.0",
"webpack-cli": "^2.0.12",
"webpack-dev-server": "^3.1.1"
},
"dependencies": {}
}
Back in our console, we can now run npm run start
. Our development server starts and opens your default browser at http://localhost:3000/
.
Adding markup and styles to build the final landing page
Our build system is now in good shape and we can start building our website. I’ll go just very quickly over this so that you get the idea.
Markup
Edit index.handlebars
to add custom fonts and the basic structure of the website.
<!doctype html>
<html lang="en-US"><head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>{{htmlWebpackPlugin.options.title}}</title>
<!--[if lt IE 9]>
<script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
</head><body>
<div class="wrapper">
<div class="content">
{{> partials/header title=htmlWebpackPlugin.options.title}}
{{> partials/newsletter}}
{{> partials/social}}
</div>
<div class="visual"></div>
</div>
<link href="https://fonts.googleapis.com/css?family=IBM+Plex+Sans+Condensed:600|IBM+Plex+Sans:400,500|IBM+Plex+Serif" rel="stylesheet">
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.0.8/css/brands.css" integrity="sha384-IiIL1/ODJBRTrDTFk/pW8j0DUI5/z9m1KYsTm/RjZTNV8RHLGZXkUDwgRRbbQ+Jh"
crossorigin="anonymous">
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.0.8/css/fontawesome.css" integrity="sha384-q3jl8XQu1OpdLgGFvNRnPdj5VIlCvgsDQTQB6owSOHWlAurxul7f+JpUOVdAiJ5P"
crossorigin="anonymous">
</body></html>
We are using handlebars partials features to spit our markup into multiple files.
{{> partials/header title=htmlWebpackPlugin.options.title}}
{{> partials/newsletter}}
{{> partials/social}}
To use these partials we need to create a partials
folder in our src
directory and add the following files.
touch src/partials && touch src/partials/header.handlebars && touch src/partials/newsletter.handlebars && touch src/partials/social.handlebars
Add the markup to the partial files.
header.handlebars
<header>
<h1 class="logo">Awesome Service</h1>
<nav class="navigation">
<ul>
<li>
<a href="/">Home</a>
</li>
<li>
<a href="/">About</a>
</li>
<li>
<a href="/">Contact</a>
</li>
</ul>
</nav>
</header>
newsletter.handlebars
<section class="newsletter">
<h2>Sign-up for the newsletter</h2>
<p>Be one of the first users who joins our awesome service. By registering to the newsletter we'll send you a message as soon as we launch.</p><div class="form">
<div class="form-wrapper">
<input type="text" placeholder="your@email.com" title="Newsletter Sign-Up">
<button type="submit">Sign-Up</button>
</div>
</div>
</section>
social.handlebars
<div class="social">
<h2>Follow us</h2>
<ul>
<li>
<a href="">
<i class="fab fa-instagram"></i>
</a>
</li>
<li>
<a href="">
<i class="fab fa-twitter"></i>
</a>
</li>
<li>
<a href="">
<i class="fab fa-pinterest"></i>
</a>
</li>
</ul>
</div>
Styles
Next, create the main.scss
file in your src
directory and insert these styles.
Remember to write import './main.scss';
in your app.js
files or the styles won't show up in your dist
folder.
/* Variables */$body-font: 'IBM Plex Sans', sans-serif;
$heading-font: 'IBM Plex Sans Condensed', sans-serif;
$base-font-size: 16px;
$font-color: #252227;
$gray: #eee;
$accent: #9c2532;
$pinterest: #bd081c;
$twitter: #1da1f2;
$instagram: #000;
$tablet: 768px;
$desktop: 1024px;body, html {
padding: 0;
margin: 0;
}body {
font-family: $body-font;
font-size: $base-font-size;
color: $font-color;
}
div.wrapper {
display: grid;
width: 100vw;
grid-template-areas: "content" "visual";
grid-template-rows: 61.8034% 38.1966%;
text-align: center;
@media screen and (min-width: $tablet){
grid-template-areas: "content visual";
grid-template-columns: 61.8034% 38.1966%;
grid-template-rows: 1fr 0fr;
height: 100vh;
}@media screen and (min-width: $desktop){
grid-template-areas: "content visual";
grid-template-columns: 61.8034% 38.1966%;
grid-template-rows: 1fr 0fr;
height: 100vh;
text-align: left;
}
}a {
text-decoration: none;
color: black;
&:hover {
color: $accent;
cursor: pointer;
}
}.content {
grid-area: content;
display: grid;
grid-template-areas: "header" "sign-up" "social";
grid-template-rows: 1fr 2fr 1fr;
padding: 2em;
}.visual {
grid-area: visual;
background-color: $gray;
background-repeat: no-repeat;
background-size: cover;
background-position-x: 50%;
@media screen and (min-width: $tablet) {
background-image: url('static/scott-webb-207709-unsplash.jpg');
}
}header {
grid-area: header;
display: flex;
flex-direction: column;
h1 {
display: inline-block;
font-family: $heading-font;
}
nav {
ul {
display: flex;
flex-direction: row;
list-style-type: none;
margin: 0;
padding: 0;
justify-content: center;
@media screen and (min-width: $desktop) {
justify-content: flex-start;
}
li {
padding: 0 1em;
border-left: 1px solid $gray;
&:first-child {
padding-left: 0;
border: none;
}&:last-child {
padding-right: 0;
}
}
}
}
}.newsletter {
grid-area: sign-up;
h2 {
font-family: $heading-font;
}.form-wrapper {
display: flex;
flex-direction: column;
@media screen and (min-width: $desktop) {
flex-direction: row;
}
input {
margin: 1em 0;
flex-grow: 1;
height: 2em;
border-top: 1px solid $gray;
padding: 1em;
border-left: 3px solid $accent;
box-shadow: 2px 3px 12px $gray;
font-size: 16px;
}button {
margin: 1em 0;
background-color: $accent;
width: 100%;
color: white;
font-size: 16px;
font-weight: 700;
border: 1px solid $accent;
height: 2em;
padding: 2em;
line-height: 0em;
box-shadow: 2px 3px 12px $gray;
&:hover {
background-color: lighten($accent, 10);
border: 1px solid lighten($accent, 10);
}
@media screen and (min-width: $desktop) {
margin-left: 24px;
width: auto;
}
}
}
}.social {ul {
display: flex;
flex-direction: row;
list-style-type: none;
margin: 0;
padding: 0;
justify-content: center;
@media screen and (min-width: $desktop) {
justify-content: flex-start;
}
li {
padding: 0 1em;
font-size: 1.5em;
&:first-child {
padding-left: 0;
}&:last-child {
padding-right: 0;
}
}.fa-instagram {
color: $instagram;
}.fa-pinterest {
color: $pinterest;
}.fa-twitter {
color: $twitter;
}
}
}
Images
We are using a free image here as our background image.
.visual {
grid-area: visual;
background-color: $gray;
background-repeat: no-repeat;
background-size: cover;
background-position-x: 50%;
@media screen and (min-width: $tablet) {
background-image: url('static/scott-webb-207709-unsplash.jpg');
}
}
You can download this free image on Unsplash. Create a static
folder in your src
directory and save the image here. Thx to Scott Webb for being so kind to share his great photographs.
Development vs. Production Builds
Webpack supports a mode option. Setting mode to development
gives you the best development experience, while production
optimizes the output for performance and load times.
To start optimizing the output for production modify your package.json
file.
Before:
"build": "webpack --config ./build/webpack.config.js"
After:
"build": "NODE_ENV=production webpack --config ./build/webpack.config.js --mode=production"
The next step is to edit the webpack.config.js
file to set some options for production use.
- Disable source maps in “production” mode
- Minify CSS output
- Minify JS output
- Minify HTML output
const path = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CopyWebpackPlugin = require('copy-webpack-plugin')
const autoprefixer = require('autoprefixer');const isDevelopment = process.env.NODE_ENV !== 'production';module.exports = {
entry: {
bundle: './src/app.js'
} ,
output: {
path: path.resolve(__dirname, '../dist')
},
devtool: isDevelopment && "source-map",
devServer: {
port: 3000,
open: true,
contentBase: path.join(__dirname, "../src"),
},
module: {
rules: [
{ test: /\.handlebars$/, loader: "handlebars-loader" },
{
test: /\.(scss|css)$/,
use: [
MiniCssExtractPlugin.loader,
{
loader: "css-loader",
options: {
sourceMap: isDevelopment,
minimize: !isDevelopment
}
},
{
loader: "postcss-loader",
options: {
autoprefixer: {
browsers: ["last 2 versions"]
},
sourceMap: isDevelopment,
plugins: () => [
autoprefixer
]
},
},
{
loader: "sass-loader",
options: {
sourceMap: isDevelopment
}
}
]
},
{
test: /\.(jpg|png|gif)$/,
use: [
{
loader: "file-loader",
options: {
name: '[name].[ext]',
outputPath: 'static/',
useRelativePath: true,
}
},
{
loader: 'image-webpack-loader',
options: {
mozjpeg: {
progressive: true,
quality: 65
},
optipng: {
enabled: true,
},
pngquant: {
quality: '65-90',
speed: 4
},
gifsicle: {
interlaced: false,
},
webp: {
quality: 75
}
}
}
]
}
]
},
plugins: [
/** Since Webpack 4 */
new webpack.LoaderOptionsPlugin({
options: {
handlebarsLoader: {}
}
}),
new MiniCssExtractPlugin({
filename: "[name]-styles.css",
chunkFilename: "[id].css"
}),
new HtmlWebpackPlugin({
title: 'My awesome service',
template: './src/index.handlebars',
minify: !isDevelopment && {
html5: true,
collapseWhitespace: true,
caseSensitive: true,
removeComments: true,
removeEmptyElements: true
},})
]
};
When you run npm run start
in your terminal, you should see the website in your browser at http://localhost:3000
. When you are happy with the results, you can run npm run build
and deploy your dist
folder to a static site host like Amazon S3 or Netlify
Check out the source code on Github.
What things are you missing for your project?
- Do you prefer Pug over Handlebars?
- Less, Sass or CSS variables?
- Are you missing multi-page support?
- How to handle environment specific configurations?
Let me known in the comments.