├── .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 | };
--------------------------------------------------------------------------------