Update project

This commit is contained in:
schmelczerandras 2020-09-20 20:15:27 +02:00
parent bcbf4224c2
commit fd64d9491d
16 changed files with 311 additions and 99 deletions

50
.github/workflows/main.yaml vendored Normal file
View file

@ -0,0 +1,50 @@
name: Deploy everything
on:
push:
branches:
- main
env:
CONTAINER_REGISTRY: schmelczera
DOMAIN: schmelczer.dev
PROJECT_NAME: sdf-2d-demo
jobs:
build-frontend:
runs-on: ubuntu-latest
steps:
- name: Checkout current branch with lfs
uses: actions/checkout@main
with:
lfs: true
- name: Setup auth tokens
run: |
docker login -u ${{ secrets.DOCKER_USER }} -p ${{ secrets.DOCKER_PASSWORD }}
- name: Build and push frontend
run: |
docker build . -t $CONTAINER_REGISTRY/$PROJECT_NAME
docker push $CONTAINER_REGISTRY/$PROJECT_NAME
deploy:
runs-on: ubuntu-latest
needs:
- build
steps:
- name: Checkout current branch with lfs
uses: actions/checkout@main
with:
lfs: true
- name: Setup auth tokens
run: |
# SSH key
mkdir ~/.ssh
echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_ed25519
chmod 400 ~/.ssh/id_ed25519
ssh -o StrictHostKeyChecking=no root@$DOMAIN uptime
- name: Stack deploy
run: |
DOCKER_HOST=ssh://root@$DOMAIN docker login -u ${{ secrets.DOCKER_USER }} -p ${{ secrets.DOCKER_PASSWORD }}
DOCKER_HOST=ssh://root@$DOMAIN docker stack deploy $PROJECT_NAME -c docker-compose.yml --with-registry-auth

View file

@ -7,12 +7,11 @@ WORKDIR /home/node
COPY src src COPY src src
COPY static static COPY static static
COPY package.json custom.d.ts tsconfig.json webpack.config.js ./ COPY package.json tsconfig.json webpack.config.js ./
RUN npm install RUN npm install
RUN npm run build RUN npm run build
FROM nginx:alpine FROM nginx:alpine
HEALTHCHECK --interval=1m --timeout=10s CMD curl --fail http://localhost/ || exit 1 HEALTHCHECK --interval=1m --timeout=10s CMD curl --fail http://localhost/ || exit 1

View file

@ -1,4 +1,4 @@
<img src="static/logo.svg" width=128 height=128 alt="logo" /> <img src="static/logo-colored.svg" width=128 height=128 alt="logo" />
# SDF-2D demo # SDF-2D demo

31
docker-compose.yml Normal file
View file

@ -0,0 +1,31 @@
version: "3.8"
services:
sdf-2d-demo:
init: true
image: schmelczera/sdf-2d-demo
networks:
- network
deploy:
replicas: 3
resources:
limits:
cpus: "0.1"
memory: 16M
reservations:
cpus: "0.1"
memory: 16M
placement:
max_replicas_per_node: 1
update_config:
parallelism: 1
failure_action: rollback
delay: 10s
monitor: 10s
restart_policy:
condition: on-failure
window: 30s
networks:
network:
driver: overlay

View file

@ -7,7 +7,7 @@
"scripts": { "scripts": {
"start": "webpack-dev-server --mode development", "start": "webpack-dev-server --mode development",
"lint": "npx eslint --fix \"src/**/*.ts\" && npx prettier --write \"src/**/*.ts\"", "lint": "npx eslint --fix \"src/**/*.ts\" && npx prettier --write \"src/**/*.ts\"",
"build": "webpack --mode production && find dist -type f -not -regex \"dist\\/.*\\.\\(html\\|png\\|ico\\|svg\\|jpg\\)\" | xargs rm && sed -i 's/^\\/\\/#.*.map//' dist/index.html" "build": "rm -rf dist/* && webpack --mode production && find dist -type f -not -regex \"dist\\/.*\\.\\(html\\|png\\|ico\\|svg\\|jpg\\)\" | xargs rm && sed -i 's/^\\/\\/#.*.map//' dist/index.html"
}, },
"keywords": [], "keywords": [],
"author": "András Schmelczer <andras@schmelczer.dev> (https://schmelczer.dev/)", "author": "András Schmelczer <andras@schmelczer.dev> (https://schmelczer.dev/)",
@ -20,19 +20,12 @@
"browserslist": [ "browserslist": [
"defaults" "defaults"
], ],
"sideEffects": [ "sideEffects": false,
"*.scss",
"*.png",
"*.jpg",
"*.ico",
"*.svg"
],
"optimization": {
"usedExports": true
},
"devDependencies": { "devDependencies": {
"@typescript-eslint/eslint-plugin": "^3.10.1", "@typescript-eslint/eslint-plugin": "^3.10.1",
"@typescript-eslint/parser": "^3.10.1", "@typescript-eslint/parser": "^3.10.1",
"@types/gl-matrix": "^2.4.5",
"gl-matrix": "^3.3.0",
"autoprefixer": "^9.8.6", "autoprefixer": "^9.8.6",
"css-loader": "^3.5.2", "css-loader": "^3.5.2",
"eslint": "^7.9.0", "eslint": "^7.9.0",

View file

@ -0,0 +1,2 @@
export const prettyPrint = (o: any): string =>
JSON.stringify(o, (_, v) => (v.toFixed ? Number(v.toFixed(2)) : v), ' ');

View file

@ -2,37 +2,23 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<title>SDF-2D demo</title> <title>SDF-2D demo</title>
<meta property="og:image:width" content="1500" /> <meta property="og:image:width" content="1438" />
<meta property="og:image:height" content="785" /> <meta property="og:image:height" content="638" />
<meta property="og:url" content="https://sdf2d.schmelczer.dev" /> <meta property="og:url" content="https://sdf2d.schmelczer.dev" />
<meta property="og:image" content="https://sdf2d.schmelczer.dev/og-image.jpg" /> <meta property="og:image" content="https://sdf2d.schmelczer.dev/og-image.jpg" />
<meta name="theme-color" content="#103783" /> <meta name="theme-color" content="#103783" />
<meta <meta
name="description" name="description"
content="Some simple demos to showcase the possibilities of the sdf-2d library library. Click on the title to find out more." content="Some simple demos to showcase the possibilities of the sdf-2d library library. Click on the title to find out more."
/> />
<link rel="apple-touch-icon" sizes="180x180" href="apple-touch-icon.png" />
<link rel="icon" type="image/png" sizes="32x32" href="favicon-32x32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="favicon-16x16.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<link
rel="apple-touch-icon"
sizes="180x180"
href="static/favicons/apple-touch-icon.png"
/>
<link
rel="icon"
type="image/png"
sizes="32x32"
href="static/favicons/favicon-32x32.png"
/>
<link
rel="icon"
type="image/png"
sizes="16x16"
href="static/favicons/favicon-16x16.png"
/>
</head> </head>
<body> <body>
<noscript>Javascript is required for this website.</noscript> <noscript>Javascript is required for this website.</noscript>

View file

@ -3,15 +3,18 @@ import '../static/favicons/apple-touch-icon.png';
import '../static/favicons/favicon-16x16.png'; import '../static/favicons/favicon-16x16.png';
import '../static/favicons/favicon-32x32.png'; import '../static/favicons/favicon-32x32.png';
import '../static/favicons/favicon.ico'; import '../static/favicons/favicon.ico';
//import '../static/og-image.jpg'; import '../static/logo-white.svg';
import '../static/og-image.jpg';
import { DeltaTimeCalculator } from './helper/delta-time-calculator'; import { DeltaTimeCalculator } from './helper/delta-time-calculator';
import { removeUnnecessaryOutlines } from './helper/remove-unnecessary-outlines'; import { removeUnnecessaryOutlines } from './helper/remove-unnecessary-outlines';
import { BlobScene } from './scenes/blob-scene'; import { BlobScene } from './scenes/blob/blob-scene';
import { RainScene } from './scenes/rain/rain-scene';
import { Scene } from './scenes/scene'; import { Scene } from './scenes/scene';
import { TunnelScene } from './scenes/tunnel-scene';
import './styles/index.scss'; import './styles/index.scss';
const scenes = [/*TunnelScene, RainScene*/ BlobScene]; const scenes = [TunnelScene, RainScene, BlobScene];
const sceneIntervalInSeconds = 80000; const sceneIntervalInSeconds = 8;
glMatrix.setMatrixArrayType(Array); glMatrix.setMatrixArrayType(Array);
removeUnnecessaryOutlines(); removeUnnecessaryOutlines();

View file

@ -1,6 +1,8 @@
import { vec3 } from 'gl-matrix'; import { vec2, vec3 } from 'gl-matrix';
import { Circle, CircleLight, compile, InvertedTunnel, Renderer } from 'sdf-2d'; import { Circle, CircleLight, compile, InvertedTunnel, Renderer } from 'sdf-2d';
import { Scene } from './scene'; import { prettyPrint } from '../../helper/pretty-print';
import { Scene } from '../scene';
import { Blob } from './blob';
export class BlobScene implements Scene { export class BlobScene implements Scene {
private renderer: Renderer; private renderer: Renderer;
@ -19,25 +21,33 @@ export class BlobScene implements Scene {
this.renderer = await compile( this.renderer = await compile(
canvas, canvas,
[ [
{
...InvertedTunnel.descriptor,
shaderCombinationSteps: [0, 1, 2, 4, 8, 12],
},
Circle.descriptor, Circle.descriptor,
{ {
...CircleLight.descriptor, ...CircleLight.descriptor,
shaderCombinationSteps: [1, 2, 3, 4, 5, 6, 7], shaderCombinationSteps: [1, 2],
}, },
Blob.descriptor,
], ],
[vec3.fromValues(0.4, 1, 0.6), vec3.fromValues(1, 1, 0), vec3.fromValues(0.3, 1, 1)] [
vec3.fromValues(0, 0, 0),
vec3.fromValues(224 / 255, 96 / 255, 126 / 255),
vec3.fromValues(119 / 255, 173 / 255, 120 / 255),
]
); );
this.renderer.setRuntimeSettings({ this.renderer.setRuntimeSettings({
ambientLight: vec3.fromValues(0.35, 0.1, 0.45), ambientLight: vec3.fromValues(0.35, 0.1, 0.45),
shadowLength: 550, shadowLength: 800,
}); });
const { width, height } = this.canvas.getBoundingClientRect();
const length = vec2.length([width, height]);
this.blob = new Blob([width / 2, -length / 4 + length / 2]);
} }
private blob: Blob;
private deltaSinceStart = 0; private deltaSinceStart = 0;
public drawNextFrame( public drawNextFrame(
currentTime: DOMHighResTimeStamp, currentTime: DOMHighResTimeStamp,
@ -48,20 +58,28 @@ export class BlobScene implements Scene {
this.renderer.setViewArea([0, height], [width, height]); this.renderer.setViewArea([0, height], [width, height]);
this.renderer.autoscaleQuality(deltaTime); this.renderer.autoscaleQuality(deltaTime);
this.overlay.innerText = JSON.stringify( this.overlay.innerText = prettyPrint(this.renderer.insights);
this.renderer.insights,
(_, v) => (v.toFixed ? Number(v.toFixed(2)) : v),
' '
);
const q = (this.deltaSinceStart % 5000) / 2500; const length = vec2.length([width, height]);
console.log(q);
const q = (this.deltaSinceStart % 8000) / 4000;
this.blob.animate(this.deltaSinceStart);
[ [
new Circle([width / 2, -width / 4], width / 2), new Circle([width / 2, -length / 4], length / 2),
new CircleLight([q * width, Math.sin(q * Math.PI) * height], [1, 0.8, 0], 1), this.blob,
new CircleLight( new CircleLight(
[(q - 1) * width, Math.sin((q - 1) * Math.PI) * height], [
(Math.cos((1 - q) * Math.PI) * length) / 2 + width / 2,
(Math.sin((1 - q) * Math.PI) * length) / 2,
],
[1, 0.8, 0],
1
),
new CircleLight(
[
(Math.cos(-q * Math.PI) * length) / 2 + width / 2,
(Math.sin(-q * Math.PI) * length) / 2,
],
[0, 0.8, 1], [0, 0.8, 1],
1 1
), ),

139
src/scenes/blob/blob.ts Normal file
View file

@ -0,0 +1,139 @@
import { mat2d, vec2 } from 'gl-matrix';
import { Circle, Drawable, DrawableDescriptor } from 'sdf-2d';
export class Blob extends Drawable {
public static descriptor: DrawableDescriptor = {
sdf: {
shader: `
uniform struct {
vec2 headCenter;
vec2 leftFootCenter;
vec2 rightFootCenter;
float headRadius;
float footRadius;
float k;
}[BLOB_COUNT] blobs;
float smoothMin(float a, float b)
{
const float k = 80.0;
float res = exp2( -k*a ) + exp2( -k*b );
return -log2( res )/k;
}
float circleDistance(vec2 circleCenter, float radius) {
return distance(position, circleCenter) - radius;
}
void blobMinDistance(inout float minDistance, inout float color) {
for (int i = 0; i < BLOB_COUNT; i++) {
float headDistance = circleDistance(blobs[i].headCenter, blobs[i].headRadius);
float leftFootDistance = circleDistance(blobs[i].leftFootCenter, blobs[i].footRadius);
float rightFootDistance = circleDistance(blobs[i].rightFootCenter, blobs[i].footRadius);
float res = min(
smoothMin(headDistance, leftFootDistance),
smoothMin(headDistance, rightFootDistance)
);
minDistance = min(minDistance, res);
color = mix(1.0, color, step(distanceNdcPixelSize + SURFACE_OFFSET, res));
}
}
`,
distanceFunctionName: 'blobMinDistance',
},
uniformName: 'blobs',
uniformCountMacroName: 'BLOB_COUNT',
shaderCombinationSteps: [1],
empty: new Blob(vec2.fromValues(0, 0)),
};
public readonly boundingCircleRadius = 200;
protected readonly headRadius = 70;
protected readonly footRadius = 30;
private readonly headOffset = vec2.fromValues(0, 120);
private leftFootOffset = vec2.fromValues(-30, this.footRadius);
private rightFootOffset = vec2.fromValues(30, this.footRadius);
private leftFootOffsetDefault = vec2.fromValues(-30, this.footRadius);
private rightFootOffsetDefault = vec2.fromValues(30, this.footRadius);
protected boundingCircle = new Circle(vec2.create(), this.boundingCircleRadius);
protected head = new Circle(vec2.create(), this.headRadius);
protected leftFoot = new Circle(vec2.create(), this.footRadius);
protected rightFoot = new Circle(vec2.create(), this.footRadius);
public constructor(private center: vec2) {
super();
this.position = center;
}
public set position(value: vec2) {
vec2.copy(this.boundingCircle.center, value);
vec2.add(this.head.center, value, this.headOffset);
vec2.add(this.leftFoot.center, value, this.leftFootOffset);
vec2.add(this.rightFoot.center, value, this.rightFootOffset);
}
public animate(time: number) {
const offset = 20;
const q = (time % 1000) / 500;
vec2.subtract(
this.rightFootOffset,
this.rightFootOffsetDefault,
vec2.fromValues(q * offset, 0)
);
vec2.subtract(
this.leftFootOffset,
this.leftFootOffsetDefault,
vec2.fromValues(q * offset, 0)
);
vec2.add(
this.rightFootOffset,
this.rightFootOffset,
vec2.fromValues(
Math.min(1, q) * offset * 2,
Math.sin(Math.min(1, q) * Math.PI) * 10
)
);
vec2.add(
this.leftFootOffset,
this.leftFootOffset,
vec2.fromValues(
(q > 1 ? q - 1 : 0) * offset * 2,
Math.sin((q > 1 ? q - 1 : 0) * Math.PI) * 10
)
);
this.position = this.center;
}
public minDistance(target: vec2): number {
return this.boundingCircle.minDistance(target);
}
protected getObjectToSerialize(transform2d: mat2d, transform1d: number): any {
return {
headCenter: vec2.transformMat2d(vec2.create(), this.head.center, transform2d),
leftFootCenter: vec2.transformMat2d(
vec2.create(),
this.leftFoot.center,
transform2d
),
rightFootCenter: vec2.transformMat2d(
vec2.create(),
this.rightFoot.center,
transform2d
),
headRadius: this.headRadius * transform1d,
footRadius: this.footRadius * transform1d,
};
}
}

View file

@ -1,5 +1,6 @@
import { vec2, vec3 } from 'gl-matrix'; import { vec2, vec3 } from 'gl-matrix';
import { CircleLight, compile, Renderer, Tunnel } from 'sdf-2d'; import { CircleLight, compile, Renderer, Tunnel } from 'sdf-2d';
import { prettyPrint } from '../../helper/pretty-print';
import { Scene } from '../scene'; import { Scene } from '../scene';
import { Droplet } from './droplet'; import { Droplet } from './droplet';
@ -7,7 +8,7 @@ export class RainScene implements Scene {
private droplets: Array<Droplet> = []; private droplets: Array<Droplet> = [];
private light1: CircleLight = new CircleLight( private light1: CircleLight = new CircleLight(
vec2.create(), vec2.create(),
vec3.fromValues(1, 0, 1), vec3.fromValues(0.5, 0, 1),
1 1
); );
private light2: CircleLight = new CircleLight( private light2: CircleLight = new CircleLight(
@ -20,12 +21,6 @@ export class RainScene implements Scene {
private canvas: HTMLCanvasElement; private canvas: HTMLCanvasElement;
private overlay: HTMLDivElement; private overlay: HTMLDivElement;
public constructor() {
for (let i = 0; i < 40; i++) {
this.droplets.push(new Droplet());
}
}
public async initialize( public async initialize(
canvas: HTMLCanvasElement, canvas: HTMLCanvasElement,
overlay: HTMLDivElement overlay: HTMLDivElement
@ -51,6 +46,10 @@ export class RainScene implements Scene {
ambientLight: vec3.fromValues(0.45, 0.25, 0.45), ambientLight: vec3.fromValues(0.45, 0.25, 0.45),
tileMultiplier: 10, tileMultiplier: 10,
}); });
for (let i = 0; i < (canvas.getBoundingClientRect().width / 1000) * 20; i++) {
this.droplets.push(new Droplet());
}
} }
public drawNextFrame( public drawNextFrame(
@ -60,12 +59,7 @@ export class RainScene implements Scene {
const { width, height } = this.canvas.getBoundingClientRect(); const { width, height } = this.canvas.getBoundingClientRect();
this.renderer.setViewArea(vec2.fromValues(0, height), vec2.fromValues(width, height)); this.renderer.setViewArea(vec2.fromValues(0, height), vec2.fromValues(width, height));
this.renderer.autoscaleQuality(deltaTime); this.renderer.autoscaleQuality(deltaTime);
this.overlay.innerText = prettyPrint(this.renderer.insights);
this.overlay.innerText = JSON.stringify(
this.renderer.insights,
(_, v) => (v.toFixed ? Number(v.toFixed(2)) : v),
' '
);
const viewAreaSize = this.renderer.viewAreaSize; const viewAreaSize = this.renderer.viewAreaSize;

View file

@ -2,6 +2,7 @@ import { vec2, vec3 } from 'gl-matrix';
import { CircleLight, compile, InvertedTunnel, Renderer } from 'sdf-2d'; import { CircleLight, compile, InvertedTunnel, Renderer } from 'sdf-2d';
import { clamp } from '../helper/clamp'; import { clamp } from '../helper/clamp';
import { last } from '../helper/last'; import { last } from '../helper/last';
import { prettyPrint } from '../helper/pretty-print';
import { Random } from '../helper/random'; import { Random } from '../helper/random';
import { Scene } from './scene'; import { Scene } from './scene';
@ -101,11 +102,7 @@ export class TunnelScene implements Scene {
); );
this.renderer.autoscaleQuality(deltaTime); this.renderer.autoscaleQuality(deltaTime);
this.overlay.innerText = JSON.stringify( this.overlay.innerText = prettyPrint(this.renderer.insights);
this.renderer.insights,
(_, v) => (v.toFixed ? Number(v.toFixed(2)) : v),
' '
);
[ [
...this.tunnels.filter( ...this.tunnels.filter(

View file

@ -2,18 +2,18 @@
<defs> <defs>
<g id="ray-a"> <g id="ray-a">
<rect x="18" y="-3" height="6" rx="3"> <rect x="18" y="-3" height="6" rx="3">
<animate attributeName="width" values="20;25;20" dur="4s" repeatCount="indefinite" /> <animate attributeName="width" values="20;25;20" dur="5s" repeatCount="indefinite" />
</rect> </rect>
<circle cy="0" r="3"> <circle cy="0" r="3">
<animate attributeName="cx" values="47;40;47" dur="4s" repeatCount="indefinite" /> <animate attributeName="cx" values="47;40;47" dur="5s" repeatCount="indefinite" />
</circle> </circle>
</g> </g>
<g id="ray-b"> <g id="ray-b">
<rect x="18" y="-3" height="6" rx="3"> <rect x="18" y="-3" height="6" rx="3">
<animate attributeName="width" values="25;20;25" dur="4s" repeatCount="indefinite" /> <animate attributeName="width" values="25;20;25" dur="5s" repeatCount="indefinite" />
</rect> </rect>
<circle cy="0" r="3"> <circle cy="0" r="3">
<animate attributeName="cx" values="40;47;40" dur="4s" repeatCount="indefinite" /> <animate attributeName="cx" values="40;47;40" dur="5s" repeatCount="indefinite" />
</circle> </circle>
</g> </g>
</defs> </defs>
@ -33,7 +33,7 @@
attributeName="transform" attributeName="transform"
type="rotate" type="rotate"
values="0;90" values="0;90"
dur="8s" dur="10s"
repeatCount="indefinite" repeatCount="indefinite"
/> />
</g> </g>

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Before After
Before After

BIN
static/og-image.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

View file

@ -3,12 +3,12 @@
"outDir": "./dist/", "outDir": "./dist/",
"sourceMap": true, "sourceMap": true,
"noImplicitAny": false, "noImplicitAny": false,
"target": "es5", "target": "es6",
"downlevelIteration": true, "downlevelIteration": true,
"allowJs": true, "allowJs": true,
"experimentalDecorators": true, "experimentalDecorators": true,
"moduleResolution": "Node", "moduleResolution": "Node",
"module": "commonjs", "module": "commonjs",
"lib": ["es2015", "dom"] "lib": ["es2017", "dom"]
} }
} }

View file

@ -6,9 +6,6 @@ const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const HtmlWebpackInlineSourcePlugin = require('html-webpack-inline-source-plugin'); const HtmlWebpackInlineSourcePlugin = require('html-webpack-inline-source-plugin');
const Sass = require('sass'); const Sass = require('sass');
const isProduction = process.env.NODE_ENV == 'production';
const isDevelopment = !isProduction;
const PATHS = { const PATHS = {
entryPoint: path.resolve(__dirname, 'src/index.ts'), entryPoint: path.resolve(__dirname, 'src/index.ts'),
bundles: path.resolve(__dirname, 'dist'), bundles: path.resolve(__dirname, 'dist'),
@ -23,7 +20,7 @@ module.exports = {
filename: '[name].[contenthash].js', filename: '[name].[contenthash].js',
path: PATHS.bundles, path: PATHS.bundles,
}, },
devtool: isDevelopment ? 'source-map' : 'null', devtool: 'source-map',
watchOptions: { watchOptions: {
aggregateTimeout: 600, aggregateTimeout: 600,
ignored: /node_modules/, ignored: /node_modules/,
@ -34,10 +31,11 @@ module.exports = {
}, },
optimization: { optimization: {
minimize: true, minimize: true,
usedExports: true,
minimizer: [ minimizer: [
new TerserJSPlugin({ new TerserJSPlugin({
sourceMap: true, sourceMap: true,
test: /\.js$/i, test: /\.js$/,
}), }),
new OptimizeCSSAssetsPlugin({}), new OptimizeCSSAssetsPlugin({}),
], ],
@ -65,7 +63,7 @@ module.exports = {
module: { module: {
rules: [ rules: [
{ {
test: /\.scss$/i, test: /\.scss$/,
use: [ use: [
MiniCssExtractPlugin.loader, MiniCssExtractPlugin.loader,
'css-loader', 'css-loader',
@ -93,15 +91,7 @@ module.exports = {
exclude: /node_modules/, exclude: /node_modules/,
}, },
{ {
test: /\.svg$/, test: /\.(ico|png|jpg)$/,
loader: 'svg-url-loader',
options: {
limit: 10 * 1024,
noquotes: true,
},
},
{
test: /\.(ico|png|jpg)$/i,
use: { use: {
loader: 'file-loader', loader: 'file-loader',
query: { query: {
@ -110,6 +100,16 @@ module.exports = {
}, },
}, },
}, },
{
test: /\.(svg)$/,
use: {
loader: 'file-loader',
query: {
outputPath: '/static',
name: '[name].[ext]',
},
},
},
{ {
test: /\.js$/, test: /\.js$/,
enforce: 'pre', enforce: 'pre',