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 rundev:assets
anddev: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¶s=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 Twitter, GitHub, Medium, LinkedIn.
I originally published this article in Bits and Pieces
[bottomads][/bottomads]