├── .browserslistrc
├── .eslintrc.js
├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── package-lock.json
├── package.json
├── postcss.config.js
├── robots.txt
├── src
├── js
│ ├── index.js
│ └── vendor
│ │ └── draco
│ │ ├── README.md
│ │ ├── draco_decoder.js
│ │ ├── draco_decoder.wasm
│ │ ├── draco_encoder.js
│ │ ├── draco_wasm_wrapper.js
│ │ └── gltf
│ │ ├── draco_decoder.js
│ │ ├── draco_decoder.wasm
│ │ ├── draco_encoder.js
│ │ └── draco_wasm_wrapper.js
├── sass
│ └── style.scss
└── static
│ ├── audio
│ └── fire_compressed.mp3
│ ├── fonts
│ ├── wedgie_regular-webfont.woff
│ └── wedgie_regular-webfont.woff2
│ ├── html
│ └── index.html
│ ├── images
│ ├── favicons
│ │ └── favicon.ico
│ ├── flame-texture.png
│ ├── grass-texture.jpg
│ ├── lightning-texture.png
│ ├── shine-texture.png
│ └── smoke-texture.png
│ └── models
│ └── dragon
│ ├── dracoDragon.gltf
│ ├── scene.bin
│ └── scene.gltf
├── webpack.common.js
├── webpack.dev.js
└── webpack.prod.js
/.browserslistrc:
--------------------------------------------------------------------------------
1 | # Browsers that we support
2 |
3 | defaults
4 | not IE 11
5 | not IE_Mob 11
6 | maintained node versions
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: ["prettier"],
3 | rules: {
4 | "no-unused-vars": 1,
5 | "eslint linebreak-style": [0, "error", "windows"],
6 | },
7 | env: {
8 | es6: true,
9 | browser: true,
10 | node: true
11 | },
12 | parserOptions: {
13 | sourceType: 'module'
14 | },
15 | extends: ['eslint:recommended',"plugin:prettier/recommended"]
16 | };
17 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /dist
2 | node_modules
3 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - 'node'
4 | script: npm run build
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Ethan Soo Hon
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # threejs-es6-webpack-barebones-boilerplate
2 |
3 |
4 | [](https://travis-ci.com/ethanny2/threejs-es6-webpack-barebones-boilerplate)
5 | [](https://github.com/ethanny2/threejs-es6-webpack-barebones-boilerplate/issues)
6 | [](https://github.com/ethanny2/threejs-es6-webpack-barebones-boilerplate/network)
7 | [](https://github.com/ethanny2/threejs-es6-webpack-barebones-boilerplate/stargazers)
8 | [](https://github.com/ethanny2/threejs-es6-webpack-barebones-boilerplate/blob/master/LICENSE)
9 | [](https://twitter.com/ArrayLikeObj)
10 |
11 | 
12 | ## About
13 |
14 | **This project was created to provide a good starting point for learners who are new to [webpack](https://webpack.js.org/), JavaScript bundlers, and
15 | [three.js](https://threejs.org/)**. I wanted to create an optimal development and production environment that was simple to understand and hassle-free to set up for experts and novices alike. Project key points:
16 |
17 | 1. Unobtrusive - No unnecessary abstractions or imposed design patterns. Quickly get a three.js project up and running.
18 |
19 | 2. Beginner Friendly - Well documented with numerous explanations of concepts and of code.
20 |
21 | 3. Static Site Workflow - This boilerplate provides a streamlined workflow and configuration that also works well for making static sites without three.js.
22 |
23 | ## Prerequisites
24 |
25 | To install and run this boilerplate you must have and [node.js](https://nodejs.org/) and [git](https://git-scm.com/) on your computer before running.
26 |
27 | # Project Structure
28 | :open_file_folder:
29 |
30 | ```
31 | |-- dist --> Build output files; where files are built for production
32 | |-- src --> Source files; where you do development
33 | |-- js
34 | | |-- index.js --> Singular entry point for webpack
35 | | |-- scripts --> Any supplementary scripts or entry points go here
36 | | |-- vendor --> Any vendor code (e.g code you didn't write)
37 | | --SPE.js
38 | |-- sass --> write your css, sass, or scss files. All are formats are supported
39 | |-- style.scss
40 | |-- static --> Where to place all of your static assets and html templates
41 | |-- audio
42 | |-- fonts
43 | |-- html
44 | |-- images
45 | | |-- favicons
46 | |-- models
47 | |-- dragon
48 | |-- webpack.common.js
49 | |-- webpack.dev.js
50 | |-- webpack.prod.js
51 | |-- package-lock.json
52 | |-- package.json
53 | |-- postcss.config.js
54 | |-- .eslintrc.js
55 | |-- .browserslistrc
56 | |-- .gitignore
57 | |-- robots.txt
58 | |-- .travis.yml
59 | ```
60 |
61 | # Usage/NPM Scripts
62 | :scroll:
63 |
64 | ## Installation steps:
65 |
66 | ```
67 | 1. git clone https://github.com/ethanny2/threejs-es6-webpack-barebones-boilerplate.git your-project
68 | 2. cd your-project
69 | 3. npm install
70 | ```
71 |
72 | ## NPM Scripts
73 |
74 | Removes/cleans every file in the dist directory. This is used before each build to remove all the previous build's files. The [rimraf](https://www.npmjs.com/package/rimraf) npm package gives cross-platform support for the UNIX rm -rf (remove files from a directory recursively).
75 |
76 | ```
77 | "clean": "rimraf dist/*"
78 | ```
79 |
80 | Runs the npm [eslint](https://www.npmjs.com/package/eslint) package on the current directory. (represented on UNIX by . ). ESlint will identify any incorrect patterns (expressed as either a warning or an error) and print to your terminal.
81 |
82 | ```
83 | "lint": "eslint ."
84 | ```
85 |
86 | Runs ESlint on the current directory again, but this time fixable errors (errors that have to do with formatting rules) are fixed and saved. _Errors that deal with code quality (e.g unused variables, implicit global variables) must be fixed manually_.
87 |
88 | ```
89 | "lint-fix": "eslint --fix ."
90 | ```
91 |
92 | Sequentially runs the clean script then specifies the configuration file (--config) to start the webpack production build. These webpack commands are available via the already included [webpack-cli](https://github.com/webpack/webpack-cli).
93 |
94 | ```
95 | "build": " npm run clean && webpack --config webpack.prod.js"
96 | ```
97 |
98 | Starts a live reloading development server at localhost:9000. Hot module reloading is enabled for changes in any .css/.sass/.scss file. **Both the development and production configs both produce identical file structures in the dist directory, but the output files for development are not written to disk, they are served from memory for better speed**. This is why the dist directory appears empty when in the development environment.
99 | To stop the server press CTRL+C on Windows or CMD+C on Macs.
100 |
101 | ```
102 | "start-dev": "webpack-dev-server --open --config webpack.dev.js"
103 | ```
104 |
105 | Example using [gLTF Pipeline](https://github.com/AnalyticalGraphicsInc/gltf-pipeline) CLI and [Draco Compression](https://google.github.io/draco/) to compress a .gltf file
106 | .This results in a smaller single .gLTF file with textures included. (Change args to compress new models)
107 |
108 | ```
109 | "compress-draco": "gltf-pipeline -i ./src/static/models/dragon/scene.gltf -o ./src/static/models/dragon/dracoDragon.gltf -d",
110 | ```
111 | Example using [gLTF Pipeline](https://github.com/AnalyticalGraphicsInc/gltf-pipeline) CLI to convert a .gltf file to a .glb file. This results in a single .glb file but doesn't result in a file size reduction. (Change args to compress new models)
112 | ```
113 | "compress-glb": "gltf-pipeline -i ./src/static/models/dragon/scene.gltf -o ./src/static/models/dragon/scene.glb"
114 | ```
115 |
116 | # Table of Contents
117 | :book:
118 |
119 | - [Glossary](#glossary)
120 | - [Configuration and Utility Files](#configuration-and-utility-files)
121 | - [Development Environment](#development-environment)
122 | - [Production Environment](#production-environment)
123 | - [three.js Scene and Examples](#threejs-scene-and-examples)
124 | - [Learning Resources](#learning-resources)
125 | - [Credits](#credits)
126 |
127 | # Glossary
128 | :speech_balloon:
129 |
130 | - [Polyfill](https://developer.mozilla.org/en-US/docs/Glossary/Polyfill): Piece of code (js file in the context of the web) used to provide modern functionality to older browsers that don't natively support it.
131 | - [JavaScript bundlers](https://medium.com/@gimenete/how-javascript-bundlers-work-1fc0d0caf2da): Tools that gather your javascript files and dependencies into (usually) one js file.
132 | - [webpack](https://webpack.js.org/): A static module bundler for modern JavaScript applications. When webpack processes your application, it internally builds a dependency graph which maps every module your project needs and generates one or more bundles.
133 | - [Transpilation](https://scotch.io/tutorials/javascript-transpilers-what-they-are-why-we-need-them): Type of compiler that takes source code written in a specific programming language and produces the equivalent source code in the same or a different programming language.
134 | - [Tree shaking](https://developer.mozilla.org/en-US/docs/Glossary/Tree_shaking): Is a term (in the context of JavaScript) used to describe the removal of dead/unused code. Bundlers such as webpack and Rollup automatically remove dead code when bundling multiple JavaScript files.
135 | - [gLTF](https://wiki.fileformat.com/3d/gltf/): A one-size fits all file format that stores 3D model information in a JSON format. This results in a reduction of the size of the 3D asset and the time needed to unpack those assets. (Format optimized for web)
136 | - [glb](https://wiki.fileformat.com/3d/glb/): A binary file format representation of 3D models saved in the GL Transmission Format (glTF). This binary format stores all of the .gLTF's assets (JSON, .bin, textures) in the binary blob.
137 | - [Draco compression](https://google.github.io/draco/): An open source library for compression and decompressing 3D assets.
138 | - [Threejs](https://github.com/mrdoob/three.js/): A 3D JavaScript library that abstracts obtuse webGL concepts into streamlined code base.
139 | - [WebGL](https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API): A JavaScript API for rendering high-performance interactive 3D and 2D graphics within any compatible web browser without the use of plug-ins
140 | - [Minification](https://developer.mozilla.org/en-US/docs/Glossary/minification): The process of removing unnecessary or redundant data without affecting how a resource is processed by the browser
141 | - [Production Environment](https://dev.to/flippedcoding/difference-between-development-stage-and-production-d0p): An environment that represents the finished website live on the internet. Usually this environment prioritizes minification, compression and optimization of assets to produce the best site.
142 | - [Development Environment](https://dev.to/flippedcoding/difference-between-development-stage-and-production-d0p): An environment that resides locally on your computer where you can test features and code. Usually has some mechanism in place to run the project on localhost with live reloading.
143 | - [Loaders](https://webpack.js.org/concepts/): Community written packages that make it possible to add file types other than .js to webpack's dependency graph building
144 | - [Regular Expression](https://developer.mozilla.org/en-US/docs/Glossary/Regular_expression): Rules that govern which sequence of character come up in a search. (Also known as regex)
145 | - [Glob](https://gulpjs.com/docs/en/getting-started/explaining-globs): A string of literal and/or wildcard characters used to match file paths. Globing is the act of locating files on a filesystem using one or more globs.
146 | - [CommonJS](https://flaviocopes.com/commonjs/): Standard module specification used in Node.js used to break JavaScript programs into smaller more managable pieces called modules. (e.g require(...) is the CommonJS format)
147 | - [ES Modules](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules): Standard module specification used/supported by most modern browsers. webpack recognizes both CommonJS and ES Module syntax.
148 | - [Script defer attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script): Boolean attribute set on script tags that indicates to a browser that the script is meant to be executed after the document has been parsed, but before firing DOMContentLoaded.
149 | - [Source maps](https://developer.mozilla.org/en-US/docs/Tools/Debugger/How_to/Use_a_source_map): A file that maps from the transformed source to the original source, enabling the browser to reconstruct the original source and present the reconstructed original in the debugger. (Babel/transpilation and minification are two examples of processes that transform your code)
150 | - [Vendor prefixes](https://developer.mozilla.org/en-US/docs/Glossary/Vendor_Prefix): Prefixes specified by browser vendors to enable the use of experimental modern/cutting-edge CSS features. Developers should wait to include the un-prefixed property until browser behavior is standardized
151 | - [Chunk](https://webpack.js.org/guides/code-splitting/): A name used by webpack to represents a module of code that is the result code-splitting your program
152 | - [Linter](https://sourcelevel.io/blog/what-is-a-linter-and-why-your-team-should-use-it): Is a tool that analyzes source code to flag programming errors, bugs, stylistic errors, and suspicious constructs
153 | - [Compression](https://developer.mozilla.org/en-US/docs/Web/HTTP/Compression): A method used in software development to decrease the size and redundancy of a particular file to make it more compact and efficient to serve over the web.
154 | - [HTTP/1](https://developer.mozilla.org/en-US/docs/Glossary/HTTP): Network protocol that enables transfer of hypermedia documents on the Web, typically between a browser and a server so that humans can read them. Characterized by making multiple requests per asset needed (not so efficient)
155 | - [HTTP 2](https://developer.mozilla.org/en-US/docs/Glossary/HTTP_2): A major revision of the HTTP network protocol. The primary goals for HTTP/2 are to reduce latency by enabling full request and response multiplexing, minimize protocol overhead via efficient compression of HTTP header fields.
156 | - [Hot Module Replacement](https://webpack.js.org/concepts/hot-module-replacement/): Exchanges, adds, or removes modules while an application is running, without a full reload. This is particularly useful for a development environment where you can maintain application state while changing the application source.
157 | - [Tweening](https://www.webopedia.com/TERM/T/tweening.html): Short for in-betweening, the process of generating intermediate frames between two images to give the appearance that the first image evolves smoothly into the second image. (e.g Animation of an object/mesh from one position to another)
158 |
159 | # Configuration and Utility Files
160 | :wrench:
161 |
162 | ## .browserslistrc
163 |
164 | [Browserlist](https://github.com/browserslist/browserslist) is a configuration settings file that is shared across multiple different tools used to build the front-end of websites. In this configuration file you can specify which browsers you wish to support. In this project the NPM packages [Autoprefixer](https://github.com/postcss/autoprefixer) and [Babel](https://github.com/babel/babel/tree/master/packages/babel-preset-env) are reliant on this configuration file to produce an output that is usable on the browsers specified in this file.
165 |
166 | For instance the contents of my file specify -> (not IE 11), so any features needed to support IE 11 browsers that would normally be added by Autoprefixer and Babel are excluded during the build process.
167 |
168 | Each line in this file represents a new browser query:
169 |
170 | - "defaults" is provided by Browserlist and represents a reasonable starting configuration for most projects
171 | - "maintained node versions" means _target all nodejs versions still maintained by the Node.js foundation_.
172 |
173 | The full list and how to make construct advanced queries can be found on the Browserlist github page.
174 |
175 | ```
176 | defaults
177 | not IE 11
178 | not IE_Mob 11
179 | maintained node versions
180 | ```
181 |
182 |
183 | ## .eslintrc.js
184 |
185 | [ESLint](https://eslint.org/) is a tool for identifying and reporting on patterns found in ECMAScript/JavaScript code, with the goal of making code more consistent and avoiding bugs. Through the use of this configuration file you can specify rules that propagate one of the following things...
186 |
187 | - **Nothing** (0): ESLint ignores any rule with 0
188 | - **Warning** (1): ESLint prints warning to terminal if rule is broken
189 | - **Error** (2): ESLint creates an error and the program will not compile if this rule is broken
190 |
191 | In this configuration I set the rule "no-unused-vars" to 1, so it will warn if there are any unused variables but it will still allow my program to compile. "eslint linebreak-style" is a fix for Windows computers. Normally this eslint configuration will propagate an error every time you hit the enter key on a computer running Windows because the enter key returns CRLF, whereas on UNIX environments hitting enter will just produce an LF character. This line supresses that error on Windows. [Read More](https://github.com/diegohaz/arc/issues/171)
192 |
193 | ```
194 | ...
195 | rules: {
196 | "no-unused-vars": 1,
197 | "eslint linebreak-style": [0, "error", "windows"],
198 | }
199 | ...
200 | ```
201 |
202 | While you could definitely have ESLint handle both formatting styles and code-quality rules a very common configuration (and the one used by this project) is using ESLint in combination with another code formatter called [Prettier](https://prettier.io/). Prettier handles the formatting of your code, and ESlint handles the code-quality rules. You can read more about these rules at the following [link](https://prettier.io/docs/en/comparison.html).
203 |
204 | However if you have both ESLint and Prettier installed ESLint will attempt to both handle formatting styles and code-quality rules by default; this may led to an annoying situation where your prettier formatter is breaking your ESLint or vice-versa. In order to prevent this we use a combination of [eslint-config-prettier](https://github.com/prettier/eslint-config-prettier) and [eslint-plugin-prettier](https://github.com/prettier/eslint-plugin-prettier).
205 |
206 | The extends fields allows you to use a predefined/shared set of ESlint rules from another configuration. This is where the majority of the ESLint rules come from as I have only explicity declared 2 rules as seen above. The order of this array is processed from right to left so it will gather rules from "plugin:prettier/recommended" first then extend/supplement that rule set with the rules found in "eslint:recommended". [Read more](https://stackoverflow.com/questions/46544082/it-this-the-correct-way-of-extending-eslint-rules)
207 |
208 | ```
209 | ...
210 | plugins: ["prettier"]
211 | ...
212 | extends: ["eslint:recommended","plugin:prettier/recommended"]
213 | ```
214 |
215 | ## postcss.config.js
216 |
217 | This configuration file is used by the [postcss-loader](https://github.com/postcss/postcss-loader) NPM package. Postcss-loader is a webpack loader that is used to transform your CSS while adding a number of beneficial features such as increased readability, vendor-prefixes and polyfills. [Read more](https://postcss.org/)
218 |
219 | In this case we want to add vendor-prefixes automatically to our CSS code so we specify the postcss-loader to use the [Autoprefixer](https://github.com/postcss/autoprefixer) in this configuration file.
220 |
221 | ```
222 | plugins: [require("autoprefixer")]
223 | ```
224 |
225 | ## robots.txt
226 |
227 | A common file included on websites that specifies which pages of your site can be accessed/indexed by a web crawler (bot that indexes content for search engines). Below we allow all web crawlers (User-agent) to access our site with the wild card (\*) symbol. Additionally, we allow all web crawlers to access all parts of our site; note this is the configuration is used by default and doesn't even require a robots.txt file. [Read more](https://support.google.com/webmasters/answer/6062596?hl=en)
228 |
229 | ```
230 | User-agent: *
231 | Allow: /
232 | ```
233 |
234 | ## webpack.common.js
235 |
236 | webpack is a static module bundler for JavaScript applications. It uses configuration files to determine how to bundle your files and build its [dependency graph](https://webpack.js.org/concepts/dependency-graph/). As per the webpack documentation, it is best to have different files for both development and production environment as the needs/goals of these builds greatly differ. Of course, in order to keep the config files as DRY as possible the files are separated into their respective environments and the settings shared between the two environments are placed in this common file. Later the settings from this file will be combined separately with both the dev and production config files through the use of [webpack-merge](https://github.com/survivejs/webpack-merge).
237 |
238 | ### __Entry/Output__
239 |
240 | Here there is a singular entry point of the program (where the bundle starts) at index.js and singular bundle produced in dist/js called [name].bundle.js which will contain most of your JS code needed to run your application.
241 |
242 | ```
243 | const entry = path.resolve(__dirname, "./src/js/index.js");
244 | entry: {
245 | main: entry
246 | },
247 | output: {
248 | filename: "js/[name].bundle.js",
249 | path: path.resolve(__dirname, "dist")
250 | }
251 | ```
252 |
253 | Many people also specify an entry point for vendor code (nodeModules and other code) as well but according to the official webpack documentation...
254 |
255 | _In webpack version < 4 it was common to add vendors as a separate entry point to compile it as a separate file (in combination with the CommonsChunkPlugin).
256 | This is discouraged in webpack 4. Instead, the optimization.splitChunks option takes care of separating vendors and app modules and creating a separate file. Do not create an entry for vendors or other stuff that is not the starting point of execution._ [Read more](https://webpack.js.org/concepts/entry-points/)
257 |
258 | ### __Loaders and Rules__
259 |
260 | By default webpack is only able to create dependency graphs and bundle JavaScript files; in order to load all your assets through webpack (images, audio, 3d models, css, html) we rely on the rich ecosystem of community written webpack loaders available on NPM. **Note: webpack loaders are evaluated from the bottom up. This means the last loader (furthest toward the bottom) specified in a particular ruleset is the first to run.**
261 |
262 | In this a regular expression (in test) targets all .js files (except node_modules) in the project and runs it through these loaders.
263 | + [eslint-loader](https://github.com/webpack-contrib/eslint-loader) lints all .js files
264 | + babel (which requires 3 NPM packages to function [babel-loader](https://www.npmjs.com/package/babel-loader/v/8.0.0-beta.1) ,[@babel/core](https://www.npmjs.com/package/@babel/core) and [@babel/preset-env](https://www.npmjs.com/package/@babel/preset-env)) transpiles ES6 code to ES5 so older browsers can use your code as well.
265 | + Enabling the caching options on both loaders greatly speeds up build times in both prod and dev.
266 |
267 | ```
268 | ...
269 | {
270 | test: /\.m?js$/i,
271 | exclude: nodePath,
272 | use: [
273 | // Transplies from ES6 to ES5.
274 | {
275 | loader: "babel-loader",
276 | options: {
277 | cacheDirectory: true,
278 | presets: ["@babel/preset-env"],
279 | cacheCompression: true
280 | }
281 | },
282 | // Lint javascript before transpiling
283 | {
284 | loader: "eslint-loader",
285 | options: {
286 | cache: true
287 | }
288 | }
289 | ]
290 | }
291 | ...
292 | ```
293 |
294 | This rule is used to process CSS/SASS styles. The regex matches all SASS, SCSS and CSS files so you may write your styles in any of those formats.
295 | + [sass-loader](https://www.npmjs.com/package/sass-loader) compiles your SASS/SCSS code (if applicable) to CSS.
296 | + postcss-loader is used to add vendor-prefixes to your CSS.
297 | + [css-loader](https://www.npmjs.com/package/css-loader) translates CSS to CommonJS webpack can understand; this loader interprets @import and url() like import/require() and will resolve them.
298 | + Finally [mini-css-extract-plugin](https://github.com/webpack-contrib/mini-css-extract-plugin) is extract CSS into separate files if you wish.
299 | + hmr enables styles to be updated on the dev server without having to reload the page.
300 | + public path specifies what path the loader should use to resolve assets that are used within CSS files (e.g import() a font in a stylesheet) after the project is built.(*This is not the path to your static assets in src/*)
301 | + sourceMap is enabled for most loaders for easier debugging
302 | ```
303 | {
304 | test: /\.(sa|sc|c)ss$/i,
305 | use: [
306 | {
307 | loader: MiniCssExtractPlugin.loader,
308 | options: {
309 | // Path all assets AFTER build process
310 | publicPath: "../",
311 | hmr: true
312 | }
313 | },
314 | // Translates CSS into CommonJS
315 | {
316 | loader: "css-loader",
317 | options: {
318 | sourceMap: true
319 | }
320 | },
321 | // Adds vendor prefixes with Autoprefixer
322 | "postcss-loader",
323 | {
324 | // Compiles SASS to CSS
325 | loader: "sass-loader",
326 | options: {
327 | sourceMap: true
328 | }
329 | }
330 | ]
331 | }
332 | ```
333 | ### __Plugins__
334 | Another feature of webpack that enables you to add helpful tooling to your build process.
335 |
336 |
337 | [html-webpack-plugin](https://github.com/jantimon/html-webpack-plugin) is a plugin used to generate html files either through your templating engine of choice or from an html file you supply. This plugin also automatically places your bundled .js output file, and CSS output file in the html file automatically. _This plugin alone will NOT allow you to include images in your html file (img src=... ) that are detected by the webpack build process._ This is accomplished through a combination of html-loader and file-loader.
338 | + fileName: is the name of the output file
339 | + template: is where to find the html template use as a base.
340 | + favicon: sets the favicon of the generated page. If you want more options to generate multiple favicons try the [favicons-webpack-plugin](https://www.npmjs.com/package/favicons-webpack-plugin). _Be aware including this plugin adds a lot of overhead to build times._
341 | + inject: tells the plugin where to place the bundled JavaScript files; in this case we place them in the head as opposed to right before the end of the body tag for performance gain reasons (in combination with the "defer" attribute on script tags).
342 | ```
343 | new HtmlWebpackPlugin({
344 | title: "Threejs ES6 Simple Boilerplate",
345 | filename: "index.html",
346 | template: "./src/static/html/index.html",
347 | favicon: "./src/static/images/favicons/favicon.ico",
348 | inject: "head"
349 | }),
350 | //new HtmlWebpackPlugin({...}) to make more html pages
351 | ```
352 | [preload-webpack-plugin](https://github.com/GoogleChromeLabs/preload-webpack-plugin) is a plugin that injects rel="preload" attributes into certain link tags created by the HtmlWebpackPlugin. This is used on fonts exclusively as they are most conducive to preload and support good user experience and preventing FOUC (flash of un-styled content/text). [Read more](https://developer.mozilla.org/en-US/docs/Web/HTML/Preloading_content).
353 |
354 | + rel: indicates what attribute we wish to add. (can also be prefetch)
355 | + fileWhiteList: array of values of which file types to whitelist. One regex is used to match all font file types.
356 | + as: Is a key that returns the type of resource (font,script are possible vals) that will be targeted by this plugin. This function uses a regex to check all font types and return "font" if any are matched.
357 | + include: "allAssets" ensures this preloads fonts also resolved using file-loader.
358 | [Read more](https://github.com/GoogleChromeLabs/preload-webpack-plugin/issues/89)
359 |
360 | ```
361 | new PreloadWebpackPlugin({
362 | rel: "preload",
363 | as(entry) {
364 | if (/\.(woff|woff2|ttf|otf)$/.test(entry)) return "font";
365 | },
366 | fileWhitelist: [/\.(woff|woff2|ttf|otf)$/],
367 | //Includes all assets; needs more clarification
368 | include: "allAssets"
369 | })
370 | ```
371 | [script-ext-html-webpack-plugin](https://github.com/numical/script-ext-html-webpack-plugin) similar to PreloadWebpackPlugin but this one adds the defer attribute to the script tags in your html files. In conjunction with placing the all bundled .js files in head this increases performance. [Read more](https://flaviocopes.com/javascript-async-defer/)
372 | ```
373 | new ScriptExtHtmlWebpackPlugin({
374 | defaultAttribute: "defer"
375 | }),
376 | ```
377 |
378 | ### __Optimizations and Chunking__
379 |
380 | #### Chunks/ Code Splitting
381 | To avoid the issue of duplicate code and dependencies webpack has a feature that allows you to break you code up into chunks (also known as code splitting). As of webpack version < 4 code splitting into separate chunks is achieved through optimization.splitChunks property of a webpack config file.
382 |
383 | *Side Note: In webpack < 4 there are already intelligent defaults in place for creating chunks that fit web performance best practices. [Learn more](https://webpack.js.org/plugins/split-chunks-plugin/)
384 | #### Caching
385 | If a new build produces a new file name that differs from the version in the user's cache it will trigger a redownload of that content. Once your site has been deployed to a server a common technique is to add a hash within the file name (e.g script.7e2c49a622975ebd9b7e.js) and then change the hash value _ONLY IF_ the contents of the file changed.
386 |
387 | This ensures the visitor will only download files that change from build to build and have the rest served from their cache for better performance. Using unique hashes to explicitly force the user to redownload certain files is called _cache-busting_ and is very easy to configure and use in webpack 4. In combination with code splitting this is a very powerful tool to keep your live site fast for returning visitors. [Read more](https://webpack.js.org/guides/caching/)
388 |
389 |
390 | #### Output substitutions
391 | To automatically generate unique hashes for files webpack uses a feature called [**substitutions**](https://webpack.js.org/configuration/output/#outputfilename) that are able to add templating to the output of file names. There are several substitutions available, but the ones used in this project are
392 | + [name]: Interpreted as the name of the file used to create the output
393 | + [ext]: Interpreted as the extension of the file used to create the output
394 | + [hash]: Interpreted as a unique hash that changes each time any file in the build changes
395 | + [contenthash]: Interpreted as a unique hash that only changes if the content of a file does
396 |
397 | [hash vs contenthash vs chunkhash](https://medium.com/@sahilkkrazy/hash-vs-chunkhash-vs-contenthash-e94d38a32208)
398 |
399 |
400 | webpack has a default behavior that will change the [contenthash] even when nothing in that particular file has changed. This is because every time you build webpack generates an unique piece of code that contains the manifest and the runtime environment responsible for linking the code together. If left on this means that the end-user will ultimately redownload whole files even when the content is identical to the one in their cache. In order to prevent this we use the following settings
401 | + runtimeChunk:"single" -> Tells webpack to take the runtime code needed build project and offload it into a runtime .js file so on subsequent builds if no content changed their contenthashes will remain the same.
402 | + moduleIds:"hashed" -> Without this option set webpack will increment the module.id field of each all chunks when a build is triggered; this often leads to the vendor/node_modules folder chunk changing its contenthash value (even though no vendor code was changed or added) and triggering a large redownload of data for the user.
403 | + splitChunks: In conjunction with cacheGroups is how to perform chunking in webpack. In the example below I name a chunk styles and target all .css via a regex.
404 |
405 | ```
406 | ...
407 | optimization: {
408 | runtimeChunk: "single",
409 | moduleIds: "hashed",
410 | splitChunks: {
411 | cacheGroups: {
412 | styles: {
413 | name: "styles",
414 | test: /\.css$/,
415 | chunks: "all",
416 | enforce: true
417 | }
418 | }
419 | }
420 | }
421 | ...
422 | ```
423 |
424 | # Development Environment
425 | :computer:
426 | ### __Overview__
427 | The development environment builds to dist/. It includes a development server with live-reload, loaders for static files, and basic chunking optimizations. Hashing is not needed instead files are output with this simple substitution [name].[ext]
428 |
429 | ### __Loaders__
430 | + [file-loader](https://webpack.js.org/loaders/file-loader/): is used to load most static assets including images, html files, fonts, 3D models and audio. Add to the regex to suit your own needs.
431 | + esModule: false -> This setting is critical to getting this library to work with html-loader. This turns off file-loaders ES2015 module syntax and reverts it to CommonJS so it will correctly parse image src attributes with html-loader. [Read More](https://stackoverflow.com/questions/59070216/webpack-file-loader-outputs-object-module) html-loader doesn't yet support es module syntax and it is a known [issue](https://github.com/webpack-contrib/html-loader/issues/203).
432 |
433 | ```
434 | {
435 | test: /\.(png|svg|jpe?g|gif|ico)$/i,
436 | use: {
437 | loader: "file-loader",
438 | options: {
439 | outputPath: "images/",
440 | name: "[name].[ext]",
441 | esModule: false
442 | }
443 | }
444 | }
445 | ```
446 |
447 | ### __Dev Server__
448 | Spins up a development server at the specified port using [webpack-dev-server](https://webpack.js.org/configuration/dev-server/).
449 | + hot:true -> Enables hot module reloading of the server
450 | + compress:true -> Serves development build via compressed formats for better performance.
451 | + contentBase -> Specifies path to build/output directory.
452 |
453 | ```
454 | devServer: {
455 | contentBase: "./dist",
456 | compress: true,
457 | hot: true,
458 | port: 9000
459 | }
460 | ```
461 |
462 | ### __Plugins__
463 | Styles are loaded in the webpack.common.js config file and the mini-extract-css-plugin is used to configure the output of the .css file created by the development build.
464 | ```
465 | new MiniCssExtractPlugin({
466 | filename: "css/style.css",
467 | chunkFilename: "css/style.[id].css"
468 | })
469 | ```
470 | ### __Optimizations__
471 | The node_modules folder is separated and chunked into a group called vendor to prevent redownload. This is done by default in webpack 4 but included for clarity's sake.
472 |
473 | ```
474 | ...
475 | cacheGroups: {
476 | vendor: {
477 | test: /[\\/]node_modules[\\/]/,
478 | name: "vendor",
479 | chunks: "all",
480 | reuseExistingChunk: true
481 | },
482 | }
483 | ...
484 | ```
485 |
486 | # Production Environment
487 | :tada:
488 | ### __Overview__
489 | The production environment builds to dist/. There are a number of optimization in place that help produce the fastest most efficient production site such as... compression of image files, minification of .html, .js , .css files, gzip compression of all files, and automatic removal of unused css styles through out the whole project. Each file is output with a [contenthash] substitution in order to facilitate proper hashing on file names that only change if a particular file's content does.
490 |
491 | ### __Loaders__
492 | + [file-loader](https://webpack.js.org/loaders/file-loader/): Loads in the same manner as the development environment and is used to include most static assets including images, html files, fonts, 3D models and audio. Add to the regex to suit your own needs.
493 |
494 | ```
495 | {
496 | test: /\.(png|svg|jpe?g|gif|ico)$/i,
497 | use: {
498 | loader: "file-loader",
499 | options: {
500 | outputPath: "images/",
501 | name: "[name].[contenthash].[ext]",
502 | esModule: false
503 | }
504 | }
505 | }
506 | ```
507 |
508 | ### __Plugins__
509 | + mini-css-extract-loader: Styles are loaded in the webpack.common.js config file and the mini-extract-css-plugin is used to configure the output of the .css file created by the production build.
510 | ```
511 | new MiniCssExtractPlugin({
512 | filename: "css/style.css",
513 | chunkFilename: "css/style.[id].css"
514 | })
515 | ```
516 | + [imagemin-webpack-plugin](https://www.npmjs.com/package/imagemin-webpack-plugin): Used to compress your image files that are produced by your production build process. By default this plugin has a set a default compression methods that compresses gif, png and jpg files. The [imagemin-mozjpeg](https://www.npmjs.com/package/imagemin-mozjpeg) package is a plugin for Imagemin that offers a wide range of options for compression quality of jpeg images.
517 | ```
518 | new ImageminPlugin({
519 | optipng: {
520 | optimizationLevel: 6
521 | },
522 | plugins: [
523 | imageminMozjpeg({
524 | quality: 100,
525 | progressive: true
526 | })
527 | ]
528 | })
529 | ```
530 | + [compression-webpack-plugin](https://www.npmjs.com/package/compression-webpack-plugin): Used to gzip all files that match the specified test regex. In this case all html, css, and js files are gziped. _Note both the normal version of the file and the zipped version are produced. When served over a network that accepts gzipped content the zipped version will be sent_.
531 | ```
532 | new CompressionPlugin({
533 | test: /\.(html|css|js)(\?.*)?$/i
534 | })
535 | ```
536 | + [purgecss-webpack-plugin](https://www.npmjs.com/package/purgecss-webpack-plugin): [Purgecss](https://www.purgecss.com/) is a tool that detects any unused css code in your project and deletes it so you are not serving unnecessary styles; this is particularly helpful when using a styling framework like Bootstrap that includes a lot of extraneous code.
537 | + paths: Is used in conjunction with [glob](https://www.npmjs.com/package/glob) to specify a pattern that searches the entire project for css to potentially delete.
538 | ```
539 | new PurgecssPlugin({
540 | paths: glob.sync("src/**/*", { nodir: true })
541 | })
542 | ```
543 | _Note: These plugins are included in the optimization.minimizer section because they deal with minification of code._
544 | + [terser-webpack-plugin](https://github.com/webpack-contrib/terser-webpack-plugin): Is a tool used to minify all .js files (target by default) in the project. Running cache, and parallel speeds up the minification process.
545 | ```
546 | new TerserJSPlugin({
547 | cache: true,
548 | parallel: true,
549 | sourceMap: true
550 | })
551 | ```
552 | + [optimize-css-assets-webpack-plugin](https://www.npmjs.com/package/optimize-css-assets-webpack-plugin): A webpack plugin to optimize and minimize CSS assets. Set of defaults is used so no configuration is needed.
553 | ```
554 | new OptimizeCSSAssetsPlugin({})
555 | ```
556 |
557 | ### __Optimizations__
558 | In the production environment is really where chunking optimizations matter. A common tactic (and the one used in the development environment) is to create a custom chunk for all node_modules code. This is a good first step to reducing the end-users bundle size as they will only redownload those node_modules dependencies if any of the code within them changes. However, with this configuration the user has to download the _whole vendor chunk if any of the files in node_modules changes_. This could incur redownloading of data that is identical to the one in the end user's cache.
559 |
560 | To fix this we take it a step further we create a chunk for each individual NPM package within the node_modules/ folder so that if any dependency is altered it ensures the return visitor of the site only download the NPM packages that changed.
561 |
562 | This results in a js file for each NPM package included in the dependencies of your project. In the past where most websites were served via HTTP/1 more requests meant an overall slower site and a longer load time. Today most site serve requests via [HTTP/2](https://kinsta.com/learn/what-is-http2/) which allows parallel multiplexed requests and is generally fast regardless of the number of requests.
563 | [Source](https://medium.com/hackernoon/the-100-correct-way-to-split-your-chunks-with-webpack-f8a9df5b7758)
564 | [Source2](https://calendar.perfplanet.com/2019/bundling-javascript-for-performance-best-practices/)
565 |
566 |
567 | ```
568 | ...
569 | cacheGroups: {
570 | vendor: {
571 | test: /[\\/]node_modules[\\/]/,
572 | reuseExistingChunk: true,
573 | name(module) {
574 | const packageName = module.context.match(
575 | /[\\/]node_modules[\\/](.*?)([\\/]|$)/
576 | )[1];
577 | return `vendor/npm.${packageName.replace("@", "")}`;
578 | }
579 | },
580 | }
581 | ...
582 | ```
583 |
584 |
585 | # three.js Scene and Examples
586 | :three:
587 |
588 | ## Basic Scene Setup
589 | There are already a number of good resources detailing how to setup a basic three.js scene. In this project I used [discoverthreejs.com](https://discoverthreejs.com/book/first-steps/first-scene/)'s code to create the scene and cube mesh. Similarly the [offical three.js documentation](https://threejs.org/docs/index.html#manual/en/introduction/Creating-a-scene) has a great guide for setting up a scene. For beginners I recommend reading both.
590 |
591 | ## Imports
592 | At the top of the file is where you import all dependencies and vendor code you need in your program. webpack is able to recognize a wide variety of different module formats (AMD, CommonJS etc...) but for source files I choose ES2015 module syntax.
593 | ### Library/Vendor Code
594 | _Note: Since the three.js npm package is based on the module version many features must be explicitly exported to be used as they are not included in the main module file._ These are features are found in the node_modules/three/examples/... path. Many tutorials online are written using the non-module version of three.js where loaders are included under the THREE namespace (e.g THREE.GLTFLoader(...)), so if there is an error always check if you imported the specific component first.
595 | [Read more](https://threejs.org/docs/#manual/en/introduction/Import-via-modules)
596 |
597 | Internally the [GLTFLoader](https://threejs.org/docs/#examples/en/loaders/GLTFLoader) and [DRACOLoader](https://threejs.org/docs/#examples/en/loaders/DRACOLoader) rely on ES6 promises to work. The promise polyfill is included to support these loaders on IE11.
598 |
599 | ```
600 | import * as THREE from "three";
601 | import TWEEN from "@tweenjs/tween.js";
602 | import { WEBGL } from "three/examples/jsm/WebGL.js";
603 | import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
604 | import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader.js";
605 | import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
606 | import * as Stats from "stats.js";
607 | import "promise-polyfill/src/polyfill";
608 | import { SPE } from "./vendor/SPE.js";
609 | ```
610 | ### Static Assets
611 | Thanks to the included loaders we are now able to parse nearly any type of static resource and add it to the dependency graph. Below audio, images and a 3D model are parsed via fileLoader so we are able to import static resources in the same way as any other ES2015 module.
612 | ```
613 | import fireSfx from "../static/audio/fire_compressed.mp3";
614 | import modelTexture from "../static/images/grass-texture.jpg";
615 | import particleTexture from "../static/images/lightning-texture.png";
616 | import dragonModel from "../static/models/dragon/dracoDragon.gltf";
617 | ```
618 | Here we import our html and style files. We do this to make webpack aware of these resources (they are added to the dependency graph) so that html-loader can be parse img src tags from our html file.
619 | ```
620 | import "../sass/style.scss";
621 | import "../static/html/index.html";
622 | ```
623 | now in index.html ... the src attribute of img tags are able to be replaced by html-loader.
624 | ```
625 |
626 | ```
627 | ## Loading Screen
628 | Loaders in three.js load models asynchronously. This means that the other components of the scene are not blocked by the loading of a model. However, loading a 3D model may take some time and a solution many sites opt for is to display a loading screen until the model is fully loaded. The loading screen code from this boilerplate is from the [three.jsforum](https://discourse.threejs.org/t/basic-loading-screen/2332) and the loading animation is from [this codepen](https://codepen.io/ZyrianovViacheslav/pen/wWVrLQ). There are 3 step when creating a loading screen in three.js:
629 | 1. Add the loading screen html markup and styles. On initial load have the load screen cover the entire viewport.
630 | ```
631 |
644 | ...
645 | .loader{
646 | z-index:2;
647 | $loader-size: 8;
648 | $animation-duration: 2s;
649 | $animation-speed: 10;
650 | ...
651 | ```
652 | 2. Set up a [LoadingManager](https://threejs.org/docs/#api/en/loaders/managers/LoadingManager) and give it callbacks for what happens for its onLoad() event. This callback is triggered when the model is fully loaded; indicating its time to remove the loading screen by lowering its opcaity. (fade-out class)
653 | ```
654 | manager = new THREE.LoadingManager();
655 | manager.onLoad = function() {
656 | const loadingScreen = document.getElementById("loader-wrap");
657 | loadingScreen.classList.add("fade-out");
658 | loadingScreen.addEventListener("transitionend", onTransitionEnd);
659 | };
660 | ```
661 | 3. Pass the created manager instance into the constructor of your loader
662 | ```
663 | loader = new GLTFLoader(manager);
664 | ```
665 | ## Tween.js + three.js
666 | It is entirely possible to animate your three.js scene by incrementing values in the update() function of the render loop.
667 | ```
668 | function update() {
669 | mesh.rotation.z += 0.01;
670 | mesh.rotation.x += 0.01;
671 | mesh.rotation.y += 0.01;
672 | }
673 | ```
674 | This is good for simple animations but for a more robust animation experience Tween.js is a library that pairs well with three.js. A tween (from in-between) is a concept that allows you to change the values of the properties of an object in a smooth way. Tween.js works well with three.js because many of the Objects in your scene have their properties (Object3D .position, .rotation, .scale etc...) expressed via a three.js class called [vector3](https://threejs.org/docs/#api/en/math/Vector3).
675 |
676 | Tweens take in 3D vectors by default so we can easily animate the rotation or a similar property.
677 | + Here a tween is set on the cube's (called mesh) rotation. It is rotated over a course of 8s (duration = 8000) from the position (x:0, y:0, z:0) to (x:Math.PI, y:Math.PI, z:Math.PI) a full rotation.
678 | + After starting the tween, be sure to call TWEEN.update(); in update() so the tween is updated on every frame
679 | ```
680 | cubeRotateTweenA = new TWEEN.Tween(mesh.rotation)
681 | .to(rotateCoords, duration)
682 | .easing(TWEEN.Easing.Linear.None)
683 | .onUpdate(() => {
684 | });
685 | // Start the tween
686 | cubeRotateTweenA.start();
687 | cubeRotateTweenA.repeat(Infinity);
688 |
689 | function update(){
690 | ...
691 | TWEEN.update();
692 | ...
693 | }
694 | ```
695 | For the full options see the [tween documentation user's guide](https://github.com/tweenjs/tween.js/blob/master/docs/user_guide.md).
696 |
697 | ## Loading a Model
698 | This boilerplate currently only supports the loading of 3D models that can be contained into a single file. For example many .gLTF files can come with upwards of 3 files needed to properly render the model.
699 | + scene.gltf (JSON file that describes structure and composition of scene)
700 | + scene.bin (Binary data that contains geometry or animation data)
701 | + textures (Image files that contain texture data for the model)
702 |
703 | [Read More about GLTF](https://www.khronos.org/files/gltf20-reference-guide.pdf)
704 |
705 | In webpack there really is not an elegant way to associate all 3 files with each other while having each of the files added to webpack's dependency graph. One solution would be to just statically copy the necessary files using the [CopyWebpackplugin](https://webpack.js.org/plugins/copy-webpack-plugin/) and just reference them as static resources. This works but the assets are not added to the dependency graph which is the main feature for bundling these assets with webpack.
706 |
707 | Instead its better to just combine all the necessary files into a single file (if possible for your 3D model format). For .gltf files they are able to be combined into a single .glb file which includes the textures and binary data for the .gltf model. Similarly compressing a .gltf file with Draco compression results in a single .gltf file that has the binary data and textures embedded into that single file. Because the .gltf format is optimized for the web _you should generally always be using .glb or draco compressed .gltf models_
708 |
709 | ### Gltf-pipeline compression
710 | [gltf-pipeline](https://github.com/AnalyticalGraphicsInc/gltf-pipeline) is a tool made for changing .gltf files to .glb files (size doesn't change; but model is contained in a single .glb file) and compressing .gltf files to draco compressed .gltf files. The package offers a node.js API but there seems to be [issues](https://github.com/AnalyticalGraphicsInc/cesium/issues/8452) with the latest release that prevent the API from working correctly on newer versions of node.
711 |
712 | The CLI-tool works correctly and here is a sample command to convert .gltf -> draco compressed .gltf
713 | + -i input file: model.gltf
714 | + -o output file: modelDraco.gltf
715 | + -d Runs draco compression
716 | ```
717 | gltf-pipeline -i model.gltf -o modelDraco.gltf -d
718 | ```
719 | To get an idea of how well this compression works I have included both the uncompressed scene.gltf and scene.bin and the resultant draco compressed file.
720 |
721 | ##### Uncompressed dragon
722 | _Nearly 30MB_
723 | ```
724 | scene.bin (~30,000kb) + scene.gltf(~20kb) = 30,020kb
725 | ```
726 | ##### Draco Compressed dragon
727 | _1.3MB!_
728 | ```
729 | dracoDragon.gltf(~1300kb) = 1300kb
730 | ```
731 | ### Loading a draco compressed .gltf
732 | In three.js the draco loader is used for loading pure .drc (draco) files. In order to load a draco compressed .gtlf we need instances of both GLTFLoader and DRACOLoader.
733 |
734 |
735 |
736 | _You must pass the correct decoding algorithm path to the draco loader in order to correctly display the model._ This decoding files are located at the path location node_modules/three/examples/js/libs/draco. webpack doesn't currently have a way to import a whole directory so the best solution is use CopyWebpackPlugin to copy the draco decoding directory on builds in both dev/prod and link to the path statically.
737 | ```
738 | new CopyPlugin([
739 | {
740 | from: path.resolve(__dirname, "./src/js/vendor/draco"),
741 | to: "js/vendor/draco"
742 | }
743 | ])
744 | ```
745 |
746 |
747 | Once both loaders have been instantiated; pass the draco loader to be used internally by the gltf loader via the .setDRACOLoader() method. Now you may load the model normally.
748 | ```
749 | loader = new GLTFLoader(manager);
750 | dracoLoader = new DRACOLoader();
751 | dracoLoader.setDecoderPath(dracoDecodePath);
752 | loader.setDRACOLoader(dracoLoader);
753 | ```
754 | #### Adding a texture to a loaded 3D model
755 | In order to apply your own custom textures to your loaded models you first need to create a new instance of TextureLoader() and pass in your image. In order to correctly orient the texture on a .gltf model
756 | ```
757 | texture = new THREE.TextureLoader().load(modelTexture);
758 | // these settings are needed to correctly apply a texture to a .gLTF model
759 | texture.encoding = THREE.sRGBEncoding;
760 | texture.flipY = false;
761 | texture.wrapS = THREE.RepeatWrapping;
762 | texture.wrapT = THREE.RepeatWrapping;
763 | ```
764 | [Read more](https://stackoverflow.com/questions/54033037/need-help-on-imported-gltf-model-with-changing-texture-of-it')
765 |
766 | Once the .gltf is loaded we use Object3D's [Traverse](https://threejs.org/docs/#api/en/core/Object3D.traverse) method that takes a callback and executes it on all of the model's descendants. We specifically look at each descendant and if it is a mesh we change the map and metalness property of [MeshStandardMaterial](https://threejs.org/docs/#api/en/materials/MeshStandardMaterial)
767 | ```
768 | gltf.scene.traverse(function(child) {
769 | if (child instanceof THREE.Mesh) {
770 | //child.material is an instance of MeshStandardMaterial
771 | child.material.map = texture;
772 | child.material.metalness = 1;
773 | }
774 | });
775 | ```
776 | ### [Shader Particle Engine](http://squarefeet.github.io/ShaderParticleEngine/)
777 | Since three.js doesn't have a native particle system implementation I included this particle library as good starting point for learning particles. This library is rather dated and a [rewrite](https://github.com/squarefeet/ShaderParticleEngine/issues/50) was in the works at one point... Running the engine will produce console warnings similar to
778 | ```
779 | THREE.BufferAttribute: .dynamic has been deprecated. Use .usage instead
780 | ```
781 | but the core functionality of the particle system still works great. This library was intended to be included after three.js as a script tag so that the THREE namespace would be in the global scope. This works well for the script tags, but **the npm package/build of SPE doesn't work as Three is no longer in the global scope**.
782 |
783 | To solve this I just imported three into SPE.js and used ES2015 syntax to export the SPE class. So the module version of this library that works is located in src\js\vendor\SPE.js. This is the only file you need so you may remove the NPM dependency from package.json.
784 |
785 | The library has an excellent API and documentation and the example particle stream in this boilerplate is taken from [this example](https://github.com/squarefeet/ShaderParticleEngine/blob/master/examples/basic.html).
786 |
787 | That being said the library is open source and desperately in need a of ES6 rewrite if anyone wants to contribute.
788 |
789 | # Learning Resources
790 | :books:
791 | ### __three.js__
792 | + [Threejs Resources Page](https://threejs.org/docs/#manual/en/introduction/Useful-links)
793 | + [Threejs in browser editor](https://threejs.org/editor/): Online app to experiment with Threejs
794 | + [Stanford 3D Scanning Repository](http://graphics.stanford.edu/data/3Dscanrep/): Free 3D models to experiment with
795 | + [Threejs loader](https://discoverthreejs.com/apps/loader/): Online app where you can test your glTF, FBX and dae 3D models.
796 | + [Gltf Viewer](https://gltf.insimo.com/): Online app where you can upload .glTF files and either convert them to .glb files or compress them with draco compression.
797 | + [Discover Threejs](https://discoverthreejs.com/): Good beginner resource and introduction to setting up a Threejs scene written by one of the libraries main contributors. (Not finished)
798 | + [Sketchfab](https://sketchfab.com/): Selection of free and paid 3D models in many formats. Models are usually licensed with [Attribution 4.0](https://creativecommons.org/licenses/by/4.0/) which means you are free to adapt and share the model as long as you give credit.
799 | + [Google poly](https://poly.google.com/): Another curated selection of free 3D models available on a wide variety of formats.
800 | + [Turbosquid](https://www.turbosquid.com/): Collection of paid and free 3D models to experiment with.
801 | + [Threejs fundamentals](https://threejsfundamentals.org/): Excellent (if not one of the only) resource to learn modern threejs. Topics range from beginner to advanced.
802 | ### __webpack__
803 | + [webpack docs](https://webpack.js.org/guides/getting-started/): Great documentation and generally has everything you need to understand webpack.
804 | + [webpack beginner guide article](https://dev.to/pixelgoo/how-to-configure-webpack-from-scratch-for-a-basic-website-46a5): Article I found useful when learning webpack.
805 | + [More on webpack chunks](https://gist.github.com/sokra/1522d586b8e5c0f5072d7565c2bee693): Good guide on webpack < 4 chunking mechanism
806 | + [webpack starter kits](https://webpack.js.org/starter-kits/): Collection of user created boilerplates curated by webpack that offer good starting webpack configs for a variety projects.
807 |
808 | # Credits
809 | :bookmark_tabs:
810 | + Favicon Source: [pikpng](https://www.pikpng.com/pngvi/hRxThh_3d-silver-cube-3d-transparent-cube-png-clipart/)
811 | + Dragon 3D Model Source: [Sketchfab](https://sketchfab.com/3d-models/dragon-f4b218fb3b0d49e9b3a27367850517b8). Originally from the Stanford 3D scanning repository.
812 | + Fire Audio Source: [freesoundeffects](https://www.freesoundeffects.com/free-track/crackling-fireplace-89305/)
813 | + Loading Screen Code: [Threejs Forum](https://discourse.threejs.org/t/basic-loading-screen/2332)
814 | + Wedgie Font: [1001freefonts](https://www.1001freefonts.com/wedgie.font)
815 | + Particle images pack: [opengameart](https://opengameart.org/content/particle-pack-80-sprites)
816 |
817 |
818 | If there is something you feel you can explain better, or other fixes to this project feel free to do a PR.
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "threejs-es6-webpack-boilerplate",
3 | "version": "1.0.0",
4 | "author": "Ethan Soo Hon",
5 | "description": "Boilerplate for threejs es6 webpack workflow",
6 | "main": "index.js",
7 | "scripts": {
8 | "clean": "rimraf dist/*",
9 | "lint": "eslint .",
10 | "lint-fix": "eslint --fix .",
11 | "build": " npm run clean && webpack --config webpack.prod.js",
12 | "build-dll": "webpack --config webpack.vendor.config.js",
13 | "start-dev": "webpack-dev-server --open --config webpack.dev.js",
14 | "compress-draco": "gltf-pipeline -i ./src/static/models/dragon/scene.gltf -o ./src/static/models/dragon/dracoDragon.gltf -d",
15 | "compress-glb": "gltf-pipeline -i ./src/static/models/dragon/scene.gltf -o ./src/static/models/dragon/scene.glb"
16 | },
17 | "repository": {
18 | "type": "git",
19 | "url": "git+https://github.com/ethanny2/threejs-es6-webpack-barebones-boilerplate"
20 | },
21 | "keywords": [
22 | "Threejs"
23 | ],
24 | "license": "ISC",
25 | "bugs": {
26 | "url": "https://github.com/ethanny2/threejs-es6-webpack-barebones-boilerplate/issues"
27 | },
28 | "homepage": "https://github.com/ethanny2/PersonalSite#readme",
29 | "devDependencies": {
30 | "@babel/core": "^7.11.4",
31 | "@babel/preset-env": "^7.11.0",
32 | "autoprefixer": "^9.8.6",
33 | "babel-loader": "^8.1.0",
34 | "compression-webpack-plugin": "^5.0.1",
35 | "copy-webpack-plugin": "^6.0.3",
36 | "css-loader": "^3.6.0",
37 | "eslint": "^6.8.0",
38 | "eslint-config-prettier": "^6.11.0",
39 | "eslint-loader": "^3.0.4",
40 | "eslint-plugin-prettier": "^3.1.4",
41 | "exports-loader": "^1.1.0",
42 | "file-loader": "^5.1.0",
43 | "gltf-pipeline": "^2.1.10",
44 | "html-loader": "^0.5.5",
45 | "html-webpack-plugin": "^3.2.0",
46 | "image-webpack-loader": "^6.0.0",
47 | "imagemin-mozjpeg": "^8.0.0",
48 | "imagemin-webpack-plugin": "^2.4.2",
49 | "imports-loader": "^1.1.0",
50 | "mini-css-extract-plugin": "^0.8.2",
51 | "node-sass": "^4.14.1",
52 | "optimize-css-assets-webpack-plugin": "^5.0.3",
53 | "postcss-loader": "^3.0.0",
54 | "preload-webpack-plugin": "^3.0.0-beta.4",
55 | "prettier": "^1.19.1",
56 | "purgecss-webpack-plugin": "^1.6.0",
57 | "rimraf": "^3.0.2",
58 | "sass-loader": "^8.0.2",
59 | "script-ext-html-webpack-plugin": "^2.1.4",
60 | "stats.js": "^0.17.0",
61 | "terser-webpack-plugin": "^2.3.8",
62 | "webpack": "^4.44.1",
63 | "webpack-cli": "^3.3.12",
64 | "webpack-dev-server": "^3.11.0",
65 | "webpack-merge": "^4.2.2"
66 | },
67 | "dependencies": {
68 | "@tweenjs/tween.js": "^18.6.0",
69 | "promise-polyfill": "8.1.3",
70 | "shader-particle-engine": "git+https://github.com/blaze33/ShaderParticleEngine.git#d019c8bf3766997f39dccda943fcee9586e84d94",
71 | "three": "^0.111.0"
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: [require("autoprefixer")]
3 | };
4 |
--------------------------------------------------------------------------------
/robots.txt:
--------------------------------------------------------------------------------
1 | User-agent: *
2 | Allow: /
--------------------------------------------------------------------------------
/src/js/index.js:
--------------------------------------------------------------------------------
1 | import * as THREE from "three";
2 | import TWEEN from "@tweenjs/tween.js";
3 | import { WEBGL } from "three/examples/jsm/WebGL.js";
4 | import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
5 | import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader.js";
6 | import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
7 | import * as Stats from "stats.js";
8 | import "promise-polyfill/src/polyfill";
9 | /*
10 | Do not use/import the SPE node_module as it doesn't properly work without three in the global scope
11 | */
12 | import SPE from "shader-particle-engine";
13 |
14 | /*
15 | Importing static assets for use in your .js file
16 | */
17 | import fireSfx from "../static/audio/fire_compressed.mp3";
18 | import modelTexture from "../static/images/grass-texture.jpg";
19 | import particleTexture from "../static/images/lightning-texture.png";
20 | import dragonModel from "../static/models/dragon/dracoDragon.gltf";
21 |
22 | /*
23 | Adding your style files and html files to webpacks dependency graph so it can be
24 | parsed by loaders
25 | */
26 | import "../sass/style.scss";
27 | import "../static/html/index.html";
28 |
29 | // To enable caching across all loaders that use FileLoader
30 | THREE.Cache.enabled = true;
31 |
32 | let container;
33 | let camera;
34 | let renderer;
35 | let scene;
36 | let mesh;
37 | let stats;
38 | let controls;
39 | let loader;
40 | let dracoLoader;
41 | let texture;
42 | let emitter, particleGroup;
43 | let audioElem;
44 | let manager;
45 | const duration = 8000;
46 | let cubeRotateTweenA;
47 | // Used in cubeRotateTweenA to perform a full rotation from 0.
48 | const rotateCoords = new THREE.Vector3(Math.PI, Math.PI, Math.PI);
49 | const clock = new THREE.Clock();
50 | // path to draco decoding files; necessary to use DracoLoader
51 | const dracoDecodePath = "./js/vendor/draco/";
52 |
53 | function init() {
54 | // Create stats for performance monitoring
55 | stats = new Stats();
56 | stats.showPanel(0, 1, 2);
57 | document.getElementById("stats").appendChild(stats.dom);
58 |
59 | // Setup loading manager and callbacks it uses to control loading screen
60 | manager = new THREE.LoadingManager();
61 | manager.onLoad = function() {
62 | const loadingScreen = document.getElementById("loader-wrap");
63 | loadingScreen.classList.add("fade-out");
64 | loadingScreen.addEventListener("transitionend", onTransitionEnd);
65 | };
66 | manager.onError = function(url) {
67 | console.log("There was an error loading " + url);
68 | };
69 |
70 | // Get a reference to the container element that will hold our scene
71 | container = document.querySelector("#three-container");
72 | scene = new THREE.Scene();
73 | scene.background = new THREE.Color(0x8fbcd4);
74 |
75 | // every object is initially created at ( 0, 0, 0 )
76 | // we'll move the camera back a bit so that we can view the scene
77 | const fov = 35;
78 | const aspect = container.clientWidth / container.clientHeight;
79 | const near = 0.1;
80 | const far = 100;
81 | camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
82 | camera.position.set(0, 0, 10);
83 |
84 | // create the cube mesh
85 | const geometry = new THREE.BoxBufferGeometry(2, 2, 2);
86 | const material = new THREE.MeshStandardMaterial({ color: 0x800080 });
87 | mesh = new THREE.Mesh(geometry, material);
88 | scene.add(mesh);
89 |
90 | // Create a directional light
91 | const light = new THREE.DirectionalLight(0xffffff, 3.0);
92 | light.position.set(10, 10, 10);
93 | scene.add(light);
94 |
95 | // Add helper grid
96 | var gridHelper = new THREE.GridHelper(20, 20);
97 | gridHelper.position.set(0, -2, 0);
98 | scene.add(gridHelper);
99 |
100 | // Add helper axis
101 | var axesHelper = new THREE.AxesHelper(5);
102 | scene.add(axesHelper);
103 |
104 | // Load in a texture for the dragon model
105 | texture = new THREE.TextureLoader().load(modelTexture);
106 | // these settings are needed to correctly apply a texture to a .gLTF model
107 | texture.encoding = THREE.sRGBEncoding;
108 | texture.flipY = false;
109 | texture.wrapS = THREE.RepeatWrapping;
110 | texture.wrapT = THREE.RepeatWrapping;
111 |
112 | // Set up gltf loader with the manager to track model loading
113 | loader = new GLTFLoader(manager);
114 | dracoLoader = new DRACOLoader();
115 | dracoLoader.setDecoderPath(dracoDecodePath);
116 | loader.setDRACOLoader(dracoLoader);
117 |
118 | // Load the dracoCompressed .gLTF model
119 | loader.load(
120 | dragonModel,
121 | function(gltf) {
122 | // apply texture loaded via traverse()
123 | gltf.scene.traverse(function(child) {
124 | if (child instanceof THREE.Mesh) {
125 | //child.material is an instance of MeshStandardMaterial
126 | child.material.map = texture;
127 | child.material.metalness = 1;
128 | }
129 | });
130 | // resize and move model, face dragon toward screen
131 | gltf.scene.scale.set(0.08, 0.08, 0.08);
132 | gltf.scene.rotation.set(0, Math.PI / 2, 0);
133 | gltf.scene.position.set(3, -0.5, 0);
134 | scene.add(gltf.scene);
135 | },
136 | // called while loading is progressing
137 | function(xhr) {
138 | if (xhr.lengthComputable) {
139 | console.log("percent: " + (xhr.loaded / xhr.total) * 100);
140 | }
141 | },
142 | // called when loading has errors
143 | function(error) {
144 | console.log("An error occured loading gltf model: " + error);
145 | }
146 | );
147 |
148 | // Initialize the rotation tween from 0deg to 360deg
149 | cubeRotateTweenA = new TWEEN.Tween(mesh.rotation)
150 | .to(rotateCoords, duration)
151 | .easing(TWEEN.Easing.Linear.None)
152 | .onUpdate(() => {
153 | /*
154 | Not needed to implement;
155 | three.js renderer will look at all Object3D instances and their properties before
156 | rendering,you don't need to use an explicit onUpdate callback to increment the rotation.
157 | */
158 | });
159 | // Start the tween
160 | cubeRotateTweenA.start();
161 | cubeRotateTweenA.repeat(Infinity);
162 |
163 | // create a WebGLRenderer and set its width and height
164 | renderer = new THREE.WebGLRenderer({ antialias: true });
165 | renderer.setSize(container.clientWidth, container.clientHeight);
166 | renderer.outputEncoding = THREE.sRGBEncoding;
167 | renderer.setPixelRatio(window.devicePixelRatio);
168 | // add the automatically created