├── .gitignore ├── README.md ├── dist ├── index.html ├── main.min.js └── main.min.js.LICENSE.txt ├── js ├── main.js └── modules │ ├── Artwork.js │ ├── Common.js │ ├── Controls.js │ ├── GaussianBlur.js │ └── glsl │ ├── blur.frag │ ├── centerCircle.frag │ ├── centerCircle.vert │ ├── output.frag │ ├── screen.vert │ ├── smallCircle.frag │ └── smallCircle.vert ├── package.json └── webpack.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | yarn.lock -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GOOEY-THREE 2 | 3 | This source code and the demo are for a explanation in my blog post. 4 | 5 | 1. Clone this repository. 6 | 2. Install Node.js (v16.14.0) and yarn(v1.22.10) 7 | 3. Run following commands 8 | ``` 9 | yarn install 10 | yarn run dev 11 | ``` 12 | 13 | ## Demo 14 | https://mnmxmx.github.io/gooey-three/dist/ 15 | 16 | ## License 17 | MIT Licence 18 | -------------------------------------------------------------------------------- /dist/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | webgl-demo 7 | 8 | 9 | 10 | 11 | 12 | 29 | 30 | 31 | 34 |
35 |
36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /dist/main.min.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /** 2 | * lil-gui 3 | * https://lil-gui.georgealways.com 4 | * @version 0.18.0 5 | * @author George Michael Brower 6 | * @license MIT 7 | */ 8 | -------------------------------------------------------------------------------- /js/main.js: -------------------------------------------------------------------------------- 1 | // import "../scss/style.scss"; 2 | 3 | import EventBus from "./utils/EventBus"; 4 | window.EventBus = EventBus; 5 | import Artwork from "./modules/Artwork"; 6 | // if(!window.isWebGLDev) window.isWebGLDev = false; 7 | // import 'babel-polyfill'; 8 | 9 | const $wrapper = document.getElementById("webgl-container"); 10 | 11 | $wrapper.style.position = "fixed"; 12 | $wrapper.style.top = 0; 13 | $wrapper.style.left = 0; 14 | $wrapper.style.right = 0; 15 | $wrapper.style.bottom = 0; 16 | 17 | 18 | const artwork = new Artwork({ 19 | $wrapper: $wrapper 20 | }); 21 | 22 | window.getStatusWebGL = function(type){ 23 | return artwork.getStatusWebGL(type); 24 | }; 25 | 26 | window.addEventListener("resize", () => { 27 | artwork.resize(); 28 | }); 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /js/modules/Artwork.js: -------------------------------------------------------------------------------- 1 | import * as THREE from "three" 2 | import common from "./Common"; 3 | import controls from "./Controls" 4 | 5 | import centerVert from "./glsl/centerCircle.vert" 6 | import centerFrag from "./glsl/centerCircle.frag" 7 | 8 | import smallVert from "./glsl/smallCircle.vert" 9 | import smallFrag from "./glsl/smallCircle.frag" 10 | 11 | import GaussianBlur from "./GaussianBlur" 12 | 13 | import screenVert from "./glsl/screen.vert"; 14 | import outputFrag from "./glsl/output.frag" 15 | 16 | 17 | export default class Artwork{ 18 | constructor(props){ 19 | this.props = props; 20 | this.centerCircle = null; 21 | this.smallCircles = null; 22 | this.smallCircleNum = 30; 23 | this.circlePosArray = []; 24 | this.group = new THREE.Group(); 25 | 26 | 27 | this.uniforms = { 28 | uTime: { 29 | value: 0 30 | } 31 | } 32 | this.init(); 33 | } 34 | 35 | init(){ 36 | controls.init(); 37 | common.init({ 38 | $wrapper: this.props.$wrapper 39 | }); 40 | common.scene.add(this.group); 41 | 42 | this.fbo = new THREE.WebGLRenderTarget(common.fbo_dimensions.x, common.fbo_dimensions.y); 43 | 44 | this.gaussianBlur = new GaussianBlur(this.fbo); 45 | 46 | this.outputMesh = new THREE.Mesh( 47 | new THREE.PlaneGeometry(2, 2), 48 | new THREE.ShaderMaterial({ 49 | vertexShader: screenVert, 50 | fragmentShader: outputFrag, 51 | uniforms: { 52 | uDiffuse: { 53 | value: this.gaussianBlur.blurFbos[1].texture 54 | }, 55 | uGooey: { 56 | value: controls.params.enableGooey 57 | } 58 | }, 59 | transparent: true 60 | }) 61 | ); 62 | 63 | this.createCenterCircle(); 64 | this.createSmallCircles(); 65 | 66 | this.update(); 67 | } 68 | 69 | createCenterCircle(){ 70 | this.centerCircle = new THREE.Mesh( 71 | new THREE.CircleGeometry(0.8, 64), 72 | new THREE.ShaderMaterial({ 73 | vertexShader: centerVert, 74 | fragmentShader: centerFrag, 75 | uniforms: { 76 | ...this.uniforms, 77 | uColor: { 78 | value: controls.params.centerColor 79 | } 80 | }, 81 | depthTest: false 82 | }) 83 | ) 84 | 85 | this.centerCircle.renderOrder = 2; 86 | this.group.add(this.centerCircle); 87 | 88 | } 89 | 90 | createSmallCircles(){ 91 | const originalGeometry = new THREE.CircleGeometry(0.6, 64); 92 | const instancedGeometry = new THREE.InstancedBufferGeometry(); 93 | 94 | instancedGeometry.count = this.smallCircleNum; 95 | 96 | const position = originalGeometry.attributes.position.clone(); 97 | const uv = originalGeometry.attributes.uv.clone(); 98 | 99 | const index = originalGeometry.getIndex().clone(); 100 | 101 | instancedGeometry.setAttribute("position", position); 102 | instancedGeometry.setAttribute("uv", uv); 103 | 104 | instancedGeometry.setIndex(index); 105 | 106 | const aVelocity = new THREE.InstancedBufferAttribute( new Float32Array(this.smallCircleNum * 3), 3, false, 1) 107 | instancedGeometry.setAttribute("aVelocity", aVelocity); 108 | 109 | const aColorValue = new THREE.InstancedBufferAttribute( new Float32Array(this.smallCircleNum * 2), 2, false, 1) 110 | instancedGeometry.setAttribute("aColorValue", aColorValue); 111 | 112 | 113 | const aRandom = new THREE.InstancedBufferAttribute( new Float32Array(this.smallCircleNum * 3), 3, false, 1) 114 | instancedGeometry.setAttribute("aRandom", aRandom); 115 | 116 | 117 | for(let i = 0; i < this.smallCircleNum; i++){ 118 | const radian = Math.random() * Math.PI * 2; 119 | aVelocity.setXYZ(i, 120 | Math.cos(radian), 121 | Math.sin(radian), 122 | Math.random() 123 | ); 124 | aColorValue.setXY(i, Math.random(), Math.random()); 125 | 126 | aRandom.setXYZ(i, Math.random(), Math.random(), Math.random()); 127 | } 128 | 129 | 130 | const material = new THREE.ShaderMaterial({ 131 | vertexShader: smallVert, 132 | fragmentShader: smallFrag, 133 | uniforms: { 134 | ...this.uniforms, 135 | uColor1: { 136 | value: controls.params.color1 137 | }, 138 | uColor2: { 139 | value: controls.params.color2 140 | }, 141 | uColor3: { 142 | value: controls.params.color3 143 | }, 144 | uAreaSize: { 145 | value: 10 146 | } 147 | } 148 | }); 149 | 150 | this.smallCircles = new THREE.Mesh(instancedGeometry, material); 151 | this.group.add(this.smallCircles); 152 | } 153 | 154 | resize(){ 155 | common.resize(); 156 | this.fbo.setSize(common.fbo_dimensions.x, common.fbo_dimensions.y); 157 | this.gaussianBlur.resize(); 158 | } 159 | 160 | update(){ 161 | common.update(); 162 | this.outputMesh.material.uniforms.uGooey.value = controls.params.enableGooey 163 | this.uniforms.uTime.value += common.delta; 164 | common.renderer.setClearColor(0xffffff, 0.0) 165 | common.renderer.setRenderTarget(this.fbo); 166 | common.renderer.render(common.scene, common.camera); 167 | 168 | this.gaussianBlur.update(); 169 | 170 | common.renderer.setClearColor(controls.params.bgColor) 171 | common.renderer.setRenderTarget(null); 172 | common.renderer.render(this.outputMesh, common.camera); 173 | window.requestAnimationFrame(this.update.bind(this)); 174 | } 175 | } -------------------------------------------------------------------------------- /js/modules/Common.js: -------------------------------------------------------------------------------- 1 | import * as THREE from "three" 2 | 3 | class Common { 4 | constructor() { 5 | this.$wrapper = null; 6 | this.dimensions = new THREE.Vector2(); 7 | this.dimensions_old = new THREE.Vector2(); 8 | this.aspect = null; 9 | this.isMobile = false; 10 | this.pixelRatio = null; 11 | 12 | this.scene = new THREE.Scene(); 13 | this.camera = null 14 | 15 | this.fbo_dimensions = new THREE.Vector2(); 16 | 17 | this.time = 0; 18 | this.delta = 0; 19 | } 20 | 21 | init({$wrapper}) { 22 | this.pixelRatio = Math.min(1.0, window.devicePixelRatio); 23 | 24 | this.renderer = new THREE.WebGLRenderer({ 25 | antialias: true, 26 | alpha: true, 27 | }); 28 | 29 | this.$canvas = this.renderer.domElement; 30 | $wrapper.appendChild(this.$canvas); 31 | 32 | 33 | this.renderer.setClearColor(0xffffff, 0.0); 34 | 35 | this.renderer.setPixelRatio(this.pixelRatio); 36 | 37 | this.clock = new THREE.Clock(); 38 | this.clock.start(); 39 | this.resize(); 40 | 41 | this.camera = new THREE.PerspectiveCamera(52, this.aspect, 0.1, 100); 42 | this.camera.position.set(0, 0, 10); 43 | this.camera.lookAt(this.scene.position); 44 | } 45 | 46 | resize() { 47 | const width = window.innerWidth; 48 | const height = window.innerHeight; 49 | 50 | this.dimensions_old.copy(this.dimensions); 51 | this.dimensions.set(width, height); 52 | 53 | this.fbo_dimensions.set( 54 | width * this.pixelRatio, 55 | height * this.pixelRatio 56 | ); 57 | 58 | this.aspect = width / height; 59 | 60 | if(this.camera){ 61 | this.camera.aspect = this.aspect; 62 | this.camera.updateProjectionMatrix(); 63 | } 64 | 65 | 66 | this.renderer.setSize(this.dimensions.x, this.dimensions.y); 67 | } 68 | 69 | update() { 70 | const delta = this.clock.getDelta(); 71 | this.delta = delta; 72 | this.time += this.delta; 73 | } 74 | } 75 | 76 | export default new Common(); -------------------------------------------------------------------------------- /js/modules/Controls.js: -------------------------------------------------------------------------------- 1 | import GUI from "lil-gui" 2 | import * as THREE from "three" 3 | 4 | class Controls{ 5 | constructor(){ 6 | this.params = { 7 | bgColor: new THREE.Color(0xe0e7ff), 8 | centerColor: new THREE.Color(0x9d8aff), 9 | color1: new THREE.Color(0x90b5fe), 10 | color2: new THREE.Color(0xd56cfe), 11 | color3: new THREE.Color(0xfea9cb), 12 | blur_radius: 36, 13 | enableGooey: true 14 | } 15 | } 16 | 17 | init(){ 18 | this.gui = new GUI(); 19 | this.gui.addColor(this.params, "bgColor") 20 | this.gui.addColor(this.params, "centerColor"); 21 | this.gui.addColor(this.params, "color1"); 22 | this.gui.addColor(this.params, "color2"); 23 | this.gui.addColor(this.params, "color3"); 24 | 25 | this.gui.add(this.params, "blur_radius", 1, 50, 1); 26 | this.gui.add(this.params, "enableGooey"); 27 | 28 | } 29 | } 30 | 31 | export default new Controls(); -------------------------------------------------------------------------------- /js/modules/GaussianBlur.js: -------------------------------------------------------------------------------- 1 | import * as THREE from "three" 2 | 3 | import common from "./Common" 4 | import vertexShader from "./glsl/screen.vert" 5 | import fragmentShader from "./glsl/blur.frag"; 6 | 7 | import controls from "./Controls" 8 | export default class GaussianBlur{ 9 | constructor(originalFbo){ 10 | this.weight = [] 11 | this.blurRadius = controls.params.blur_radius; 12 | 13 | this.vertical = null; 14 | this.horizontal = null 15 | 16 | this.camera = new THREE.Camera(); 17 | 18 | this.step = new THREE.Vector2() 19 | 20 | this.uniforms = { 21 | uStep: { 22 | value: this.step 23 | }, 24 | uWeight: { 25 | value: this.weight 26 | } 27 | } 28 | 29 | this.defines = { 30 | BLUR_RADIUS: this.blurRadius 31 | } 32 | 33 | this.init(originalFbo); 34 | } 35 | 36 | init(originalFbo){ 37 | this.blurFbos = [ 38 | new THREE.WebGLRenderTarget(common.fbo_dimensions.x, common.fbo_dimensions.y), 39 | new THREE.WebGLRenderTarget(common.fbo_dimensions.x, common.fbo_dimensions.y) 40 | ] 41 | 42 | this.step.set(1 / common.fbo_dimensions.x, 1 / common.fbo_dimensions.y); 43 | 44 | this.makeWeight(); 45 | 46 | this.vertical = new THREE.Mesh( 47 | new THREE.PlaneGeometry(2, 2), 48 | new THREE.ShaderMaterial({ 49 | vertexShader, 50 | fragmentShader, 51 | uniforms: { 52 | ...this.uniforms, 53 | uDiffuse: { 54 | value: originalFbo.texture 55 | }, 56 | uStepSize: { 57 | value: new THREE.Vector2(0.0, 1.0) 58 | } 59 | }, 60 | defines: this.defines 61 | }) 62 | ); 63 | 64 | this.horizontal = new THREE.Mesh( 65 | new THREE.PlaneGeometry(2, 2), 66 | new THREE.ShaderMaterial({ 67 | vertexShader, 68 | fragmentShader, 69 | uniforms: { 70 | ...this.uniforms, 71 | uDiffuse: { 72 | value: this.blurFbos[0].texture 73 | }, 74 | uStepSize: { 75 | value: new THREE.Vector2(1.0, 0.0) 76 | } 77 | }, 78 | defines: this.defines 79 | }) 80 | ); 81 | } 82 | 83 | makeWeight(){ 84 | this.weight = [] 85 | var t = 0.0; 86 | 87 | for(let i = this.blurRadius - 1; i >= 0; i--){ 88 | var r = 1.0 + 2.0 * i; 89 | var w = Math.exp(-0.5 * (r * r) / (this.blurRadius * this.blurRadius)); 90 | this.weight.push(w); 91 | if(i > 0){w *= 2.0;} 92 | t += w; 93 | } 94 | 95 | for(let i = 0; i < this.weight.length; i++){ 96 | this.weight[i] /= t; 97 | } 98 | 99 | this.uniforms.uWeight.value = this.weight; 100 | } 101 | 102 | resize(){ 103 | for(let i = 0; i < this.blurFbos.length; i++){ 104 | this.blurFbos[i].setSize(common.fbo_dimensions.x, common.fbo_dimensions.y); 105 | } 106 | 107 | this.step.set(1 / common.fbo_dimensions.x, 1 / common.fbo_dimensions.y); 108 | } 109 | 110 | update(){ 111 | if(this.blurRadius !== controls.params.blur_radius){ 112 | this.blurRadius = controls.params.blur_radius 113 | this.defines.BLUR_RADIUS = controls.params.blur_radius 114 | this.makeWeight(); 115 | this.vertical.material.needsUpdate = true; 116 | this.horizontal.material.needsUpdate = true; 117 | } 118 | 119 | common.renderer.setRenderTarget(this.blurFbos[0]); 120 | common.renderer.render(this.vertical, this.camera); 121 | 122 | common.renderer.setRenderTarget(this.blurFbos[1]); 123 | common.renderer.render(this.horizontal, this.camera); 124 | } 125 | 126 | 127 | 128 | 129 | 130 | } -------------------------------------------------------------------------------- /js/modules/glsl/blur.frag: -------------------------------------------------------------------------------- 1 | uniform sampler2D uDiffuse; 2 | uniform vec2 uStep; 3 | uniform vec2 uStepSize; 4 | uniform float uWeight[BLUR_RADIUS]; 5 | 6 | 7 | varying vec2 vUv; 8 | 9 | void main() { 10 | float count = float(BLUR_RADIUS) - 1.0; 11 | 12 | vec4 color = vec4(0.0); 13 | vec4 sum = vec4(0.0); 14 | float w; 15 | float sumW = 0.0; 16 | float actualWeight; 17 | 18 | // loop 19 | for(int i = 0; i < BLUR_RADIUS - 1; i++){ 20 | w = uWeight[i]; 21 | 22 | color = texture2D( uDiffuse, vUv - count * uStep * uStepSize); 23 | actualWeight = w * color.a; 24 | sum.rgb += color.rgb * actualWeight; 25 | sum.a += color.a * w; 26 | sumW += actualWeight; 27 | 28 | color = texture2D( uDiffuse, vUv + count * uStep * uStepSize); 29 | actualWeight = w * color.a; 30 | sum.rgb += color.rgb * actualWeight; 31 | sum.a += color.a * w; 32 | sumW += actualWeight; 33 | 34 | count--; 35 | } 36 | 37 | w = uWeight[BLUR_RADIUS - 1]; 38 | 39 | color = texture2D( uDiffuse, vUv ); 40 | actualWeight = w * color.a; 41 | sum.rgb += color.rgb * actualWeight; 42 | sum.a += color.a * w; 43 | sumW += actualWeight; 44 | 45 | gl_FragColor = vec4(sum.rgb / sumW, sum.a); 46 | } -------------------------------------------------------------------------------- /js/modules/glsl/centerCircle.frag: -------------------------------------------------------------------------------- 1 | uniform float uTime; 2 | uniform vec3 uColor; 3 | 4 | varying vec2 vUv; 5 | void main(){ 6 | gl_FragColor = vec4(uColor + (vUv.x + vUv.y) * 0.2, 1.0); 7 | } -------------------------------------------------------------------------------- /js/modules/glsl/centerCircle.vert: -------------------------------------------------------------------------------- 1 | varying vec2 vUv; 2 | 3 | void main(){ 4 | vUv = uv; 5 | gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(position, 1.0); 6 | } -------------------------------------------------------------------------------- /js/modules/glsl/output.frag: -------------------------------------------------------------------------------- 1 | uniform sampler2D uDiffuse; 2 | uniform bool uGooey; 3 | varying vec2 vUv; 4 | void main(){ 5 | vec4 diffuse = texture2D(uDiffuse, vUv); 6 | if(uGooey){ 7 | diffuse.a = min(1.0, diffuse.a * 80.0 - 10.0); 8 | } 9 | gl_FragColor = vec4(diffuse); 10 | } -------------------------------------------------------------------------------- /js/modules/glsl/screen.vert: -------------------------------------------------------------------------------- 1 | varying vec2 vUv; 2 | 3 | void main(){ 4 | vUv = uv; 5 | gl_Position = vec4(position, 1.0); 6 | } -------------------------------------------------------------------------------- /js/modules/glsl/smallCircle.frag: -------------------------------------------------------------------------------- 1 | uniform vec3 uColor1; 2 | uniform vec3 uColor2; 3 | uniform vec3 uColor3; 4 | 5 | varying vec2 vColorValue; 6 | varying vec2 vUv; 7 | 8 | void main(){ 9 | vec3 color = mix(uColor1, uColor2, vColorValue.x); 10 | color = mix(color, uColor3, vColorValue.y); 11 | 12 | color += (vUv.x + vUv.y) * 0.2; 13 | 14 | gl_FragColor = vec4(color, 1.0); 15 | 16 | } -------------------------------------------------------------------------------- /js/modules/glsl/smallCircle.vert: -------------------------------------------------------------------------------- 1 | attribute vec3 aVelocity; 2 | attribute vec2 aColorValue; 3 | attribute vec3 aRandom; 4 | uniform float uTime; 5 | uniform float uAreaSize; 6 | 7 | varying vec2 vColorValue; 8 | varying vec2 vUv; 9 | 10 | void main(){ 11 | 12 | float time = uTime * mix(0.5, 1.5, aRandom.x) * 0.1; 13 | 14 | vec3 velocity = vec3(aVelocity.xy, 0.0); 15 | float life = fract(aVelocity.z + time); 16 | float scale = mix(1.0, 0.5, life) * mix(0.25, 1.0, aRandom.y * aRandom.y); 17 | vec3 pos = position * scale + velocity * life * uAreaSize; 18 | vUv = uv; 19 | 20 | 21 | vColorValue = aColorValue; 22 | gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(pos, 1.0); 23 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mtbs-career", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "scripts": { 7 | "dev": "webpack-dev-server --port 3000 --hot --host 0.0.0.0", 8 | "build": "cross-env NODE_ENV=\"production\" webpack", 9 | "sdf": "image-sdf human.png --spread 64 --color black > human-sdf.png" 10 | }, 11 | "devDependencies": { 12 | "@babel/core": "^7.12.9", 13 | "@babel/preset-env": "^7.12.7", 14 | "babel-loader": "^8.2.2", 15 | "copy-webpack-plugin": "^5.0.5", 16 | "raw-loader": "^3.1.0", 17 | "webpack": "^5.10.1", 18 | "webpack-cli": "^3.3.10", 19 | "webpack-dev-server": "^3.9.0" 20 | }, 21 | "dependencies": { 22 | "animejs": "^3.2.1", 23 | "babel-polyfill": "^6.26.0", 24 | "core-js": "^3.8.1", 25 | "cross-env": "^7.0.3", 26 | "css-loader": "^5.0.1", 27 | "dat.gui": "^0.7.6", 28 | "device": "^0.3.12", 29 | "glslify-import": "^3.1.0", 30 | "glslify-loader": "^2.0.0", 31 | "image-sdf": "^1.0.4", 32 | "lil-gui": "^0.18.0", 33 | "mini-css-extract-plugin": "^1.3.3", 34 | "sass": "^1.30.0", 35 | "sass-loader": "^10.1.0", 36 | "stats-js": "^1.0.1", 37 | "style-loader": "^2.0.0", 38 | "terser-webpack-plugin": "^2.3.0", 39 | "three": "^0.149.0", 40 | "uglify-js": "^3.7.2", 41 | "uglifyjs-webpack-plugin": "^2.2.0", 42 | "webpack-bundle-analyzer": "^4.3.0" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const UglifyJsPlugin = require('uglifyjs-webpack-plugin'); 3 | const TerserPlugin = require('terser-webpack-plugin'); 4 | const entry = {}; 5 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 6 | const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; 7 | 8 | module.exports = { 9 | mode: (process.env.NODE_ENV === 'production') ? 'production' : "development", // 10 | entry: './js/main.js', 11 | output: { 12 | filename: 'main.min.js', 13 | path: __dirname + "/dist" 14 | }, 15 | module: { 16 | rules: [ 17 | { 18 | test: /\.js$/, 19 | exclude: /node_modules/, 20 | use: [ 21 | { 22 | loader: 'babel-loader', 23 | options: { 24 | presets: [['@babel/preset-env', { 25 | targets: { 26 | ie: 11, 27 | esmodules: true 28 | }, 29 | useBuiltIns: 'usage', 30 | corejs: 3 31 | }]] 32 | } 33 | } 34 | ] 35 | }, 36 | { 37 | test: /\.(vert|frag|obj)$/i, 38 | use: [ 39 | 'raw-loader', 40 | { 41 | loader: 'glslify-loader', 42 | options: { 43 | transform: [ 44 | 'glslify-import' 45 | ] 46 | } 47 | } 48 | ] 49 | }, 50 | { 51 | test: /\.s[ac]ss$/i, 52 | use: [ 53 | // Creates `style` nodes from JS strings 54 | { loader: MiniCssExtractPlugin.loader }, 55 | { loader: 'css-loader' }, 56 | { loader: 'sass-loader' } 57 | ], 58 | }, 59 | // { 60 | // test: /\.scss$/, 61 | // use: ExtractTextPlugin.extract({ 62 | // fallback: "style-loader", 63 | // use: ["css-loader", "sass-loader"], 64 | // publicPath: "dist" 65 | // }) 66 | // }, 67 | ], 68 | }, 69 | plugins: [ 70 | new MiniCssExtractPlugin({ 71 | filename: '[name].css' 72 | }), 73 | // new BundleAnalyzerPlugin() 74 | 75 | ], 76 | resolve: { 77 | alias: { 78 | libs: path.resolve(__dirname, 'js/libs') 79 | } 80 | }, 81 | optimization: { 82 | // runtimeChunk: true, 83 | minimize: true, 84 | minimizer: [ 85 | new TerserPlugin({ 86 | parallel: true, 87 | terserOptions: { 88 | // https://github.com/webpack-contrib/terser-webpack-plugin#terseroptions 89 | } 90 | }), 91 | ], 92 | }, 93 | 94 | target: ['web', 'es5'], 95 | 96 | devServer: { 97 | contentBase: path.join(__dirname, 'dist'), 98 | compress: false, 99 | open: true, 100 | hot: true, 101 | disableHostCheck: true 102 | } 103 | }; --------------------------------------------------------------------------------