Creating the webpack required for three.js
Tags: webpack, javascript, threejs, webdev
Published: 2022-01-07 16:54:44

## 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`

```jsx
<!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:
        
```jsx
import ./style.css
```
        
in `style.css` add:
```css
* {
    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
```jsx
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:
```jsx
"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?**
```jsx
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)
```jsx
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:            
```jsx
const HtmlWebpackPlugin = require('html-webpack-plugin')
```
    
Add Plugin:
```jsx
plugins:[
    new HtmlWebpackPlugin({
        template: path.resolve(__dirname, '../src/index.html'),
        minify: true
    }),
],
```
    
`yarn add html-loader`
Append this rule in the rules array
```jsx
//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)
```jsx
//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.            
```jsx
const MiniCSSExtractPlugin = require('mini-css-extract-plugin')
```

- Declaring the plugin
```jsx
plugins:
[
    new MiniCSSExtractPlugin()
],
```

- Add the following rule:         
```jsx
// CSS
{
    test: /\.css$/,
    use:
    [
        MiniCSSExtractPlugin.loader,
        'css-loader'
    ]
},
```

More Information: [Learn Webpack Pt. 8: Html-loader, File-loader, & Clean-webpack](https://www.youtube.com/watch?v=mnS_1lolc44)
    
## 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.
```jsx
// 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:
```jsx
const CopyWebpackPlugin = require('copy-webpack-plugin')
```        

- Declare it:
```jsx
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
- `yarn add webpack-merge`
- Adding basic things to webpack.dev.js
```jsx
const { merge } = require('webpack-merge')
const commonConfiguration = require('./webpack.common.js')
module.exports = merge(
    commonConfiguration,
    {
        mode: 'development'
    }
)
```         
3. Adding dev script in `package.json`
```jsx
"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.
4. Adding Server Dependencies.
- `yarn add portfinder-sync`
> Find an open port synchronously.
- `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
         
5. Updating wepack dev:
- Importing required modules:
```jsx
const ip = require('internal-ip')
const portFinderSync = require('portfinder-sync')
```          
- Function that prints local domain (where server is running) names distinctly:
```jsx
const infoColor = (_message) => {
    return `\u001b[1m\u001b[34m${_message}\u001b[39m\u001b[22m`
}
```
- Adding devServer key to module exports:
```jsx
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:
```jsx
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.

3. Adding Plugin:
- `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:
```jsx
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()
        ]
    }
)
```

4. Add scripts to `package.json` for a build command.
```jsx
"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:
```jsx
// Shaders
{
    test: /\.(glsl|vs|fs|vert|frag)$/,
    exclude: /node_modules/,
    use: [
        'raw-loader'
    ]
}
```


**Resources:**
[Webpack - A Detailed Introduction - Smashing Magazine](https://www.smashingmagazine.com/2017/02/a-detailed-introduction-to-webpack/)
[Notion version of this blogpost](https://secretive-apogee-2c3.notion.site/Creating-the-webpack-required-for-three-js-3c21a77f77f4479fbe1413225ac02d88)
A lot of the webpack configurations were inspired by Bruno Simons template, which he uses for his fabulous course - Three.js Journey