- 2015/12/12: Added source map support
This post follows the steps and rationale used to build a Webpack-based boilerplate for WebGL. If you just want to look at the code, you can grab the source here.
We will start off by installing Webpack globally. I like doing this so I can just use
the command line from anywhere:
Let’s create a new directory called
We will be saving a list of libraries and tools we need to
package.json so let’s create one first:
-y flag uses default values; we will edit these shortly. Personally, I’d rather edit
these fields in a text editor rather than input them into a prompt.
Next, we will install our dependencies required for development, including:
webpack: The webpack documentation recommends saving webpack as a local dev. dependency
webpack-dev-server: Used to watch files & recompile on changes, servers files
babel-core: Compiler used to make our ES6 code usable in modern browsers
babel-preset-es2015: Since version 6, Babel comes with no settings out of the box. This is a preset to compile ES6.
html-webpack-plugin: A plugin used to auto-generate a HTML file with all webpack modules included.
webpack-glsl-loader: Webpack plugins to load our shaders from files into strings
Let’s install these modules all at once:
At this point, I usually like to open
package.json and start making some edits. This is what I end up with:
We’ll come back to this file to add some commands to use to the
scripts key. For now, let’s hop
back to the terminal.
I wanted the file structure for an intial project to be simple:
This structure can be created with
Now that we have a clear idea of what files are going to be a part of the project, let’s get a Git repository
going and set up a
.gitignore up and enter the following:
Since the contents of these two folders are generated, we shouldn’t commit them to version control.
With that out the way, let us continue by setting up Webpack.
Webpack can be configured a few different ways: with its own configuration files, via the command line with flags,
or as a module in Gulp/Grunt. We’ll be using the config file route. Create a file in the root of your project
webpack.config.js. Input the following:
Let’s take a look at what’s going on here:
Firstly, we import some modules such as
path and our
html-webpack-plugin. We also define some
constants containing the absolute paths to the folders we wil keep various types of files in, along
with the entry and output paths.
We also check for the presence of the environmental variable
NODE_ENV to determine whether to
build production or development bundles.
html-webpack-plugin generates HTML files that already have appropriate
tags to bundled JS and CSS bundles. We’ll make use of that plugin with a few options.
title is the
value of the
template is a path to the HTML file we want to base our index.html off
inject allows us to control where our
script tags are being created. Valid values
head. We will set up our HTML template after configuring Webpack.
Here we define where we want Webpack to place the modules it creates.
root key lets you tell Webpack which folders to search in when importing one file into another.
For example, imagine the following file structure:
The contents of
js/utility/VectorUtils.js look something like this:
Without setting the
resolve.root property in our Webpack settings, the way to include
Dude.js would look something like this:
resolve.root to include our
src/js path, we can treat that as a root directory that Webpack will search through to find other modules. This lets us write import paths like this:
Since we’ve also added
src as a root path, we’ll be able to include shaders just by writing something like this:
This makes importing files easy, no matter what directory you happen to be working in.
loader key on
module tells Webpack which files to load into modules, and how to load them.
test is a
want Webpack to find modules ending with
.js. We don’t want Webpack to do anything with our
dependencies unless we explicitly import one, so we tell Webpack to
exclude them. The
is the name of the Webpack plugin used to process JS files. Since we want to be able to write using
ES6 syntax, Babel will handle that responsibility. Because Babel 6 comes with no options out of the
box, we need to tell it to use the
es2015 presets package we installed earlier. Enabling directory
caching will give us faster compile times so of course we enable that.
Our last loader is for our shader files, ending with
.glsl. Here, we simply tell Webpack to use
Some loaders will perform optimizations when building bundles depending on whether the
is set to true or false.
At the moment, this tells Webpack which style of source maps to use.
source-map is recommended for
production use only, so we’ll use
eval-source-map which is faster and produces cache-able source
With that, our configuration of Webpack is complete. Not too bad, eh?
package.json and add some commands to
scripts that will make interacting with
webpack a bit easier:
Webpack will check for
webpack.config.js and use it if found. I’m using flags to show build
progress and to give the output a bit more colour. Any key added to the
scripts object can be used
npm run <keyname>. This gives us three easily accessible commands:
NODE_ENV=production npm run build: Builds our project into
/dist/. Uses a flag to tell Webpack we want loaders to build production-mode modules.
npm run watch: Builds our project and watches files for changes, re-builds on change.
npm run dev-server: Builds our project and watches files for changes, also serves files from a web server. The
--inline --hotflags enable Hot Module replacement, which will update your page without any user input. You can read more about HMR in webpack-dev-server’s documentation.
Now we have one thing left to do, and that is to create our
/dist. This is what the contents
That weird value used in the
title tag allows Webpack to change the page’s title depending on the
value of the
title key passed to
html-webpack-plugin. As you can see, no
script tags are
needed - Webpack will handle that for us.
And with that, the boilerplate is complete. “But Dale,” you exclaim, “I don’t see the point of this. You made a simple Webpack config, so what?”
My main motivations behind this came from my own experiences trying to learn WebGL. I’ve noticed that many WebGL tutorials will demonstrate shader usage in WebGL in one of two ways:
- Have a
scriptelement of type
Personally, I don’t like either of these solutions. The first one is just ugly, unreadable and difficult to maintain yet most of the WebGL resources I see introduce shaders using this method. The second is slightly better, but I prefer to keep shaders in their own files, where I can use an editor with syntax highlighting to make readability a bit easier.
I completely understand why tutorial writers do this. WebGL is complex, and wrapping your head around everything needed to render a simple triangle in shader-based GL can be a lot to take in at once. Once you get your head around those concepts, though, then what? What’s the best way to deal with shaders moving forward as a project scales? I’m not claiming this boilerplate is anywhere close to the best, but for me it works. Hopefully it does for you too (and if it doesn’t, please open an issue/PR on the GitHub repo and let me know).
Mini-example: Hello Triangle
Let’s go through a short example of how one would potentially use this boilerplate to easily load
GLSL shaders. Imagine we have two shaders in our
src/shaders directory, called
box_frag.glsl containing our vertex and fragment shader, respectively. No need for
tags or multi-line strings! Just import it when you need it, where you need it.
I hope you found this useful in some way. If you have any suggestions on how to improve the boilerplate, please open an issue on the GitHub repo.