Creating the webpack required for three.js

Step 1 - Basic setup:

  1. Go to directory
  2. npm init → Initialise npm
  3. create src folder and put script.js, index.html and style.css in it.

in index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document Name</title>
</head>
<body>

</body>
</html>

in script.js add:

import ./style.css

in style.css add:

* {
    margin: 0;
    padding: 0;

PS: We adding just the basic boiler plate.

Step 2 - Webpack Setup

  1. yarn add webpack webpack-cli in CLI to add webpack dependencies: webpack and webpack-cli
  2. Create bundler folder at the same level as src
  3. Create webpack.common.js in it.
  4. Removie "main" from package.json → removing the entry point from package.json to avoid clashes.

Step 3 - Webpack Configuration:

  • Creating entry and output -> in webpack.common.js
const path = require('path');

module.exports = {
    entry: path.resolve(__dirname, '../src/script.js'),
    output:
    {
        filename: 'bundle.[contenthash].js',
        path: path.resolve(__dirname, '../dist')
    },
        devtool: 'source-map',
}
  • For testing in package.json add the following scripts:
"scripts": {
    "test": "webpack --config ./bundler/webpack.common.js",
},

here we are specifing that instead of having a webpack.config.js file in our root folder we have seperated it on to a bundler folder.

You can run npm run test any time in between to see the output in the dist folder

Step 4 - Installing loaders, plugins and writing corresponding rules for webpack configuration:

  • How to write rules?
module.exports = {
    entry: path.resolve(__dirname, '../src/script.js'),
    output:
    {
        filename: 'bundle.[contenthash].js',
        path: path.resolve(__dirname, '../dist')
    },
        devtool: 'source-map',
    module: {
        rules: [
        ]
    }
};
  • Adding Plugins:
  1. Require them at the top of javascript file:
  2. Declaring the plugin: Add the plugin key as a property of the modules.export object and its value is an array where we declare the plugins along with configurations (if any)
module.exports = {
    entry: path.resolve(__dirname, '../src/script.js'),
    output:
    {
        filename: 'bundle.[contenthash].js',
        path: path.resolve(__dirname, '../dist')
    },
        devtool: 'source-map',
        plugins:[
        ...
    ],
    module: {
        rules: [
        ]
    }
};
  • HTML support in webpack: yarn add html-webpack-plugin: This is a webpack plugin that simplifies creation of HTML files to serve your webpack bundles. This is especially useful for webpack bundles that include a hash in the filename which changes every compilation.

Require the plugin:

const HtmlWebpackPlugin = require('html-webpack-plugin')

Add Plugin:

plugins:[
    new HtmlWebpackPlugin({
        template: path.resolve(__dirname, '../src/index.html'),
        minify: true
    }),
],

yarn add html-loader
Append this rule in the rules array

//HTMl:
{
    test: /\.(html)$/,
    use: ['html-loader']
},

Doing this will now output an html file as well in the dist folder.

  • JS support in webpack:

yarn add @babel/core
yarn add @babel/preset-env
yarn add babel-loader

(Currently we are adding them as user dependencies but we can change it to dev dependencies)

//JS
{
    test: /\.js$/,
    exclude: /node_modules/,
    use:[
        'babel-loader'
    ]
},
  • CSS support in webpack:

yarn add mini-css-extract-plugin

yarn add css-loader

  • Require the dependency at the top of the page.
const MiniCSSExtractPlugin = require('mini-css-extract-plugin')
  • Declaring the plugin
plugins:
[
    new MiniCSSExtractPlugin()
],
  • Add the following rule:
// CSS
{
    test: /\.css$/,
    use:
    [
        MiniCSSExtractPlugin.loader,
        'css-loader'
    ]
},

Step 5 - Adding file-loader other loaders for handeling fonts and images

  • yarn add file-loader
  • Add the following rules for working with images and fonts being used in the app.
// Images
{
    test: /\.(jpg|png|gif|svg)$/,
    use:
    [
        {
            loader: 'file-loader',
            options: {
                outputPath: 'assets/images/'
            }
        }
    ]
},

// Fonts
{
    test: /\.(ttf|eot|woff|woff2)$/,
    use:
    [
        {
            loader: 'file-loader',
            options: {
                outputPath: 'assets/fonts/'
            }
        }
    ]
},

In options we are specifying that after building the app, put images, fonts in the assets folder.

When we run a npm run build to create a production ready distribution folder, webpack will browse through your code and as soon as it finds something like image or fonts, it will automatically create an asset folder inside which there will be an image folder to store that imported image and there will be an font folder in assets created to store its corresponding font.

Step 6 - Adding copy-webpack-plugin:**

Copies individual files or entire directories, which already exist, to the build directory.

The idea here is you will have a static folder in dev where you will store all your fonts, images, etc, and in prod build you want this to be all copied in the build folder.

Without this plugin, in the final production folder that is created, only those images will be bundles that are imported in the javascript.

Also make sure from now on you have atleast one image inside the static folder, else it will trown an error.

The structure inside the static folder will be replicated in the dist (production build) folder.

Make sure you create a static folder first.

  • yarn add copy-webpack-plugin
  • Require/import it:
const CopyWebpackPlugin = require('copy-webpack-plugin')
  • Declare it:
plugins:[
    new CopyWebpackPlugin({
        patterns: [
            { from: path.resolve(__dirname, '../static') }
        ]
    }),
    ....
],

Here, From now on make sure that there is a test image in your static folder else building it (npm run test) will result in an error.

Step 7 - Creating Dev Configuration: to start a live server.

We will use webpack.common.js a commn configuration that will be used by for the dev and prod configurations:

In Dev server, files get build in memory and are destroyed as soon as the server id destroyed.

  1. Create a webpack.dev.js file in bundler folder
  2. Importing commonConfiguration from webpack.common.js To import we will need webpack-merge module
  3. yarn add webpack-merge
  4. Adding basic things to webpack.dev.js
const { merge } = require('webpack-merge')
const commonConfiguration = require('./webpack.common.js')
module.exports = merge(
    commonConfiguration,
    {
        mode: 'development'
    }
)
  1. Adding dev script in package.json
"scripts": {
  "test": "webpack --config ./bundler/webpack.common.js",
  "dev": "webpack serve --config ./bundler/webpack.dev.js",
},

here the serve flag will make it live reload, but before running it, there are still a few things we need to add to serve this app.

  1. Adding Server Dependencies.
  2. yarn add portfinder-sync > Find an open port synchronously.
  3. yarn add D webpack-dev-server

    Dev Server for web applications, ideal for buildless es module workflows. Optionally supports simple code transformations.
    This is added so now the webpack 'serve' command is recognised

  4. Updating wepack dev:

  5. Importing required modules:

const ip = require('internal-ip')
const portFinderSync = require('portfinder-sync')
  • Function that prints local domain (where server is running) names distinctly:
const infoColor = (_message) => {
    return `\u001b[1m\u001b[34m${_message}\u001b[39m\u001b[22m`
}
  • Adding devServer key to module exports:
devServer: {
  host: '0.0.0.0',
  port: portFinderSync.getPort(8080),
  contentBase: './dist',
  watchContentBase: true,
  open: true,
  https: false,
  useLocalIp: true,
  disableHostCheck: true,
  overlay: true,
  noInfo: true,
  after: function(app, server, compiler)
  {
      const port = server.options.port
      const https = server.options.https ? 's' : ''
      const localIp = ip.v4.sync()
      const domain1 = `http${https}://${localIp}:${port}`
      const domain2 = `http${https}://localhost:${port}`

      console.log(`Project running at:\n  - ${infoColor(domain1)}\n  - ${infoColor(domain2)}`)
  }
}

Try running npm run dev and you should see a live server being sprung up! and this is now live updating with all the changes you make!

Step 8 - Creating build Configuration: to creat a production ready dist folder.

  1. Creating a production configuration file in bundlers folder: bundlers → webpack.prod.js
  2. Adding basic configurations to webpack.prod.js:
const { merge } = require('webpack-merge')
const commonConfiguration = require('./webpack.common.js')

module.exports = merge(
    commonConfiguration,
    {
        mode: 'production',
    }
)

It will use the same merge and commonConfiguration as dev configuration. We will just change the mode.

  1. Adding Plugin:
  2. yarn add clean-webpack-plugin > A webpack plugin to remove/clean your build folder(s). It makes sure that there is no dist folder.
  • Importing and Declating Plugin in the webpack production configuration:
const { merge } = require('webpack-merge')
const commonConfiguration = require('./webpack.common.js')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')

module.exports = merge(
    commonConfiguration,
    {
        mode: 'production',
        plugins:
        [
            new CleanWebpackPlugin()
        ]
    }
)
  1. Add scripts to package.json for a build command.
"scripts": {
  "test": "webpack --config ./bundler/webpack.common.js",
  "dev": "webpack serve --config ./bundler/webpack.dev.js",
  "build": "webpack --config ./bundler/webpack.prod.js"
},

And that should be it, try running npm run build and check the dist folder that would have been created.

Step 9 - Adding raw-loader for loading shaders:

  • yarn add raw-loader
  • Webapack rules:
// Shaders
{
    test: /\.(glsl|vs|fs|vert|frag)$/,
    exclude: /node_modules/,
    use: [
        'raw-loader'
    ]
}

Resources:
Webpack - A Detailed Introduction - Smashing Magazine
Notion version of this blogpost
A lot of the webpack configurations were inspired by Bruno Simons template, which he uses for his fabulous course - Three.js Journey

7