Copy files
This commit is contained in:
commit
36f01c6716
22 changed files with 500 additions and 0 deletions
1
.eslintignore
Normal file
1
.eslintignore
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
**/*.js
|
||||||
28
.eslintrc.json
Normal file
28
.eslintrc.json
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
{
|
||||||
|
"root": true,
|
||||||
|
"env": {
|
||||||
|
"browser": true,
|
||||||
|
"es2020": true
|
||||||
|
},
|
||||||
|
"extends": [
|
||||||
|
"eslint:recommended",
|
||||||
|
"plugin:@typescript-eslint/eslint-recommended",
|
||||||
|
"plugin:@typescript-eslint/recommended",
|
||||||
|
"prettier",
|
||||||
|
"prettier/@typescript-eslint"
|
||||||
|
],
|
||||||
|
"parser": "@typescript-eslint/parser",
|
||||||
|
"parserOptions": {
|
||||||
|
"ecmaVersion": 11,
|
||||||
|
"sourceType": "module"
|
||||||
|
},
|
||||||
|
"plugins": ["unused-imports", "@typescript-eslint", "prettier"],
|
||||||
|
"rules": {
|
||||||
|
"prettier/prettier": "error",
|
||||||
|
"no-unused-vars": "off",
|
||||||
|
"unused-imports/no-unused-imports-ts": "error",
|
||||||
|
"@typescript-eslint/no-unused-vars": ["warn", { "argsIgnorePattern": "^_" }],
|
||||||
|
"@typescript-eslint/no-explicit-any": "off",
|
||||||
|
"@typescript-eslint/explicit-module-boundary-types": "off"
|
||||||
|
}
|
||||||
|
}
|
||||||
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
*.psd filter=lfs diff=lfs merge=lfs -text
|
||||||
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
dist
|
||||||
|
node_modules
|
||||||
|
package-lock.json
|
||||||
|
.vscode
|
||||||
7
.prettierrc
Normal file
7
.prettierrc
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"trailingComma": "es5",
|
||||||
|
"printWidth": 90,
|
||||||
|
"tabWidth": 2,
|
||||||
|
"singleQuote": true,
|
||||||
|
"endOfLine": "lf"
|
||||||
|
}
|
||||||
65
package.json
Normal file
65
package.json
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
{
|
||||||
|
"name": "decla.red",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"description": "",
|
||||||
|
"private": true,
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"start": "webpack-dev-server --mode development",
|
||||||
|
"lint": "npx eslint --fix \"src/**/*.ts\" && npx prettier --write \"src/**/*.ts\"",
|
||||||
|
"build": "webpack && find dist -type f -not -name '*.html' | xargs rm"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "András Schmelczer",
|
||||||
|
"postcss": {
|
||||||
|
"plugins": {
|
||||||
|
"autoprefixer": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"browserslist": [
|
||||||
|
"defaults"
|
||||||
|
],
|
||||||
|
"sideEffects": [
|
||||||
|
"*.scss"
|
||||||
|
],
|
||||||
|
"optimization": {
|
||||||
|
"usedExports": true
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/gl-matrix": "^2.4.5",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^3.10.1",
|
||||||
|
"@typescript-eslint/parser": "^3.10.1",
|
||||||
|
"autoprefixer": "^9.8.6",
|
||||||
|
"clean-webpack-plugin": "^3.0.0",
|
||||||
|
"css-loader": "^3.5.2",
|
||||||
|
"cssnano": "^4.1.10",
|
||||||
|
"eslint": "^7.9.0",
|
||||||
|
"eslint-config-prettier": "^6.11.0",
|
||||||
|
"eslint-plugin-import": "^2.22.0",
|
||||||
|
"eslint-plugin-prettier": "^3.1.4",
|
||||||
|
"eslint-plugin-unused-imports": "^0.1.3",
|
||||||
|
"gl-matrix": "^3.3.0",
|
||||||
|
"html-webpack-inline-source-plugin": "0.0.10",
|
||||||
|
"html-webpack-plugin": "^3.2.0",
|
||||||
|
"image-webpack-loader": "^6.0.0",
|
||||||
|
"mini-css-extract-plugin": "^0.9.0",
|
||||||
|
"optimize-css-assets-webpack-plugin": "^5.0.4",
|
||||||
|
"postcss-loader": "^3.0.0",
|
||||||
|
"prettier": "^2.1.2",
|
||||||
|
"raw-loader": "^4.0.1",
|
||||||
|
"resolve-url-loader": "^3.1.1",
|
||||||
|
"responsive-loader": "^1.2.0",
|
||||||
|
"sass": "^1.26.3",
|
||||||
|
"sass-loader": "^9.0.3",
|
||||||
|
"sdf-2d": "^0.0.0",
|
||||||
|
"sharp": "^0.25.4",
|
||||||
|
"style-loader": "^1.1.4",
|
||||||
|
"svg-url-loader": "^6.0.0",
|
||||||
|
"terser-webpack-plugin": "^2.3.8",
|
||||||
|
"ts-loader": "^8.0.3",
|
||||||
|
"typescript": "^3.9.7",
|
||||||
|
"webpack": "^4.44.1",
|
||||||
|
"webpack-cli": "^3.3.11",
|
||||||
|
"webpack-dev-server": "^3.10.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
4
src/helper/clamp.ts
Normal file
4
src/helper/clamp.ts
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
export const clamp = (value: number, min: number, max: number): number =>
|
||||||
|
Math.min(max, Math.max(min, value));
|
||||||
|
|
||||||
|
export const clamp01 = (value: number): number => Math.min(1, Math.max(0, value));
|
||||||
23
src/helper/delta-time-calculator.ts
Normal file
23
src/helper/delta-time-calculator.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
export class DeltaTimeCalculator {
|
||||||
|
private previousTime: DOMHighResTimeStamp | null = null;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
document.addEventListener('visibilitychange', this.handleVisibilityChange.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
public getNextDeltaTime(currentTime: DOMHighResTimeStamp): DOMHighResTimeStamp {
|
||||||
|
if (this.previousTime === null) {
|
||||||
|
this.previousTime = currentTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
const delta = currentTime - this.previousTime;
|
||||||
|
this.previousTime = currentTime;
|
||||||
|
return delta;
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleVisibilityChange() {
|
||||||
|
if (!document.hidden) {
|
||||||
|
this.previousTime = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
3
src/helper/last.ts
Normal file
3
src/helper/last.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
export function last<T>(a: Array<T>): T | null {
|
||||||
|
return a.length > 0 ? a[a.length - 1] : null;
|
||||||
|
}
|
||||||
1
src/helper/mix.ts
Normal file
1
src/helper/mix.ts
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
export const mix = (from: number, to: number, q: number) => from + (to - from) * q;
|
||||||
18
src/helper/random.ts
Normal file
18
src/helper/random.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
// source:
|
||||||
|
// https://stackoverflow.com/questions/521295/seeding-the-random-number-generator-in-javascript
|
||||||
|
// Mulberry32
|
||||||
|
|
||||||
|
export abstract class Random {
|
||||||
|
private static _seed = 42;
|
||||||
|
|
||||||
|
public static set seed(value: number) {
|
||||||
|
Random._seed = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static getRandom(): number {
|
||||||
|
let t = (Random._seed += 0x6d2b79f5);
|
||||||
|
t = Math.imul(t ^ (t >>> 15), t | 1);
|
||||||
|
t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
|
||||||
|
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
||||||
|
}
|
||||||
|
}
|
||||||
1
src/helper/to-percent.ts
Normal file
1
src/helper/to-percent.ts
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
export const toPercent = (value: number) => `${Math.round(value * 100)}%`;
|
||||||
3
src/helper/wait.ts
Normal file
3
src/helper/wait.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
export const wait = (ms: number): Promise<void> => {
|
||||||
|
return new Promise<void>((resolve, _) => setTimeout(resolve, ms));
|
||||||
|
};
|
||||||
14
src/index.html
Normal file
14
src/index.html
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<title>Test</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<noscript>
|
||||||
|
<h1>Javascript is required for this website.</h1>
|
||||||
|
</noscript>
|
||||||
|
<div id="overlay"></div>
|
||||||
|
<canvas id="main"></canvas>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
35
src/index.scss
Normal file
35
src/index.scss
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
html,
|
||||||
|
body,
|
||||||
|
canvas#main {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
position: relative;
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
#overlay {
|
||||||
|
font-family: Helvetica, Arial, sans-serif;
|
||||||
|
font-size: 0.6em;
|
||||||
|
margin: 0.75em 1em;
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
|
||||||
|
$outline-width: 0.5px;
|
||||||
|
text-shadow: -$outline-width -$outline-width 0 #000,
|
||||||
|
$outline-width -$outline-width 0 #000, -$outline-width $outline-width 0 #000,
|
||||||
|
$outline-width $outline-width 0 #000;
|
||||||
|
|
||||||
|
color: white;
|
||||||
|
white-space: pre;
|
||||||
|
|
||||||
|
@media (max-width: 800px) {
|
||||||
|
font-size: 0.55em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas#main {
|
||||||
|
background-color: hotpink;
|
||||||
|
}
|
||||||
|
}
|
||||||
76
src/index.ts
Normal file
76
src/index.ts
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
import { glMatrix, vec2, vec3 } from 'gl-matrix';
|
||||||
|
import { compile, Renderer } from 'sdf-2d';
|
||||||
|
import { DeltaTimeCalculator } from './helper/delta-time-calculator';
|
||||||
|
import './index.scss';
|
||||||
|
import { RainScene } from './scenes/rain-scene';
|
||||||
|
import { Scene } from './scenes/scene';
|
||||||
|
|
||||||
|
glMatrix.setMatrixArrayType(Array);
|
||||||
|
|
||||||
|
const deltaTimeCalculator = new DeltaTimeCalculator();
|
||||||
|
const canvas = document.querySelector('canvas') as HTMLCanvasElement;
|
||||||
|
const overlay = document.querySelector('#overlay') as HTMLDivElement;
|
||||||
|
|
||||||
|
const drawFrame = (
|
||||||
|
renderer: Renderer,
|
||||||
|
scene: Scene,
|
||||||
|
deltaTime: DOMHighResTimeStamp,
|
||||||
|
currentTime: DOMHighResTimeStamp
|
||||||
|
) => {
|
||||||
|
const { width, height } = canvas.getBoundingClientRect();
|
||||||
|
renderer.setViewArea(vec2.fromValues(0, height), vec2.fromValues(width, height));
|
||||||
|
renderer.autoscaleQuality(deltaTime);
|
||||||
|
|
||||||
|
overlay.innerText = JSON.stringify(
|
||||||
|
renderer.insights,
|
||||||
|
(_, v) => (v.toFixed ? Number(v.toFixed(2)) : v),
|
||||||
|
' '
|
||||||
|
);
|
||||||
|
|
||||||
|
scene.animate(currentTime, { width, height });
|
||||||
|
|
||||||
|
scene.drawables.forEach((d) => renderer.addDrawable(d));
|
||||||
|
renderer.renderDrawables();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleScene = async (scene: Scene) => {
|
||||||
|
const renderer = compile(
|
||||||
|
canvas,
|
||||||
|
scene.descriptors,
|
||||||
|
[vec3.fromValues(0.4, 0.35, 0.6), vec3.fromValues(0, 1, 0), vec3.fromValues(1, 1, 0)],
|
||||||
|
{ tileMultiplier: 8, enableStopwatch: false }
|
||||||
|
);
|
||||||
|
|
||||||
|
let triggerIsOver: () => void;
|
||||||
|
const isOver = new Promise((resolve) => (triggerIsOver = resolve));
|
||||||
|
|
||||||
|
let timeSinceStart = 0;
|
||||||
|
|
||||||
|
const handleFrame = (currentTime: DOMHighResTimeStamp) => {
|
||||||
|
const deltaTime = deltaTimeCalculator.getNextDeltaTime(currentTime);
|
||||||
|
|
||||||
|
drawFrame(renderer, scene, deltaTime, currentTime);
|
||||||
|
|
||||||
|
if ((timeSinceStart += deltaTime) > 200000 * 1000) {
|
||||||
|
triggerIsOver();
|
||||||
|
} else {
|
||||||
|
requestAnimationFrame(handleFrame);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
requestAnimationFrame(handleFrame);
|
||||||
|
await isOver;
|
||||||
|
|
||||||
|
renderer.destroy();
|
||||||
|
};
|
||||||
|
|
||||||
|
const scenes = [new RainScene()];
|
||||||
|
|
||||||
|
const main = async () => {
|
||||||
|
let i = 0;
|
||||||
|
for (;;) {
|
||||||
|
await handleScene(scenes[i++ % scenes.length]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
main();
|
||||||
62
src/scenes/rain-scene.ts
Normal file
62
src/scenes/rain-scene.ts
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
import { vec2 } from 'gl-matrix';
|
||||||
|
import { CircleLight, Drawable, DrawableDescriptor, Flashlight, Tunnel } from 'sdf-2d';
|
||||||
|
import { Random } from '../helper/random';
|
||||||
|
import { Scene } from './scene';
|
||||||
|
|
||||||
|
export class RainScene implements Scene {
|
||||||
|
public droplets: Array<Droplet> = [];
|
||||||
|
|
||||||
|
public constructor() {
|
||||||
|
for (let i = 0; i < 100; i++) {
|
||||||
|
this.droplets.push(new Droplet());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public get descriptors(): Array<DrawableDescriptor> {
|
||||||
|
return [Tunnel.descriptor, CircleLight.descriptor, Flashlight.descriptor];
|
||||||
|
}
|
||||||
|
|
||||||
|
public animate(
|
||||||
|
currentTime: number,
|
||||||
|
viewAreaSize: { width: number; height: number }
|
||||||
|
): void {
|
||||||
|
this.droplets.forEach((d) => d.animate(currentTime, viewAreaSize));
|
||||||
|
}
|
||||||
|
|
||||||
|
public get drawables(): Array<Drawable> {
|
||||||
|
return this.droplets.map((d) => d.drawable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Droplet {
|
||||||
|
public readonly drawable: Tunnel;
|
||||||
|
|
||||||
|
private speed = Random.getRandom() * 0.2 + 0.1;
|
||||||
|
private position = vec2.fromValues(Random.getRandom(), Random.getRandom());
|
||||||
|
private length = Random.getRandom() * 30 + 8;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
const size = Random.getRandom() * 10 + 5;
|
||||||
|
|
||||||
|
this.drawable = new Tunnel(
|
||||||
|
vec2.create(),
|
||||||
|
vec2.create(),
|
||||||
|
size + Random.getRandom() * 5 + 5,
|
||||||
|
size
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public animate(
|
||||||
|
currentTime: number,
|
||||||
|
{ width, height }: { width: number; height: number }
|
||||||
|
) {
|
||||||
|
const heightOffset = 300;
|
||||||
|
vec2.set(
|
||||||
|
this.drawable.from,
|
||||||
|
this.position[0] * width,
|
||||||
|
height - ((this.speed * currentTime) % (height + heightOffset))
|
||||||
|
);
|
||||||
|
|
||||||
|
vec2.add(this.drawable.to, this.drawable.from, vec2.fromValues(0, this.length));
|
||||||
|
}
|
||||||
|
}
|
||||||
10
src/scenes/scene.ts
Normal file
10
src/scenes/scene.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
import { Drawable, DrawableDescriptor } from 'sdf-2d';
|
||||||
|
|
||||||
|
export interface Scene {
|
||||||
|
readonly descriptors: Array<DrawableDescriptor>;
|
||||||
|
animate(
|
||||||
|
currentTime: DOMHighResTimeStamp,
|
||||||
|
viewAreaSize: { width: number; height: number }
|
||||||
|
): void;
|
||||||
|
readonly drawables: Array<Drawable>;
|
||||||
|
}
|
||||||
BIN
static/declared.png
Normal file
BIN
static/declared.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
BIN
static/declared.psd
(Stored with Git LFS)
Normal file
BIN
static/declared.psd
(Stored with Git LFS)
Normal file
Binary file not shown.
14
tsconfig.json
Normal file
14
tsconfig.json
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "./dist/",
|
||||||
|
"sourceMap": true,
|
||||||
|
"noImplicitAny": false,
|
||||||
|
"target": "es5",
|
||||||
|
"downlevelIteration": true,
|
||||||
|
"allowJs": true,
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"moduleResolution": "Node",
|
||||||
|
"module": "commonjs",
|
||||||
|
"lib": ["es2015", "dom"]
|
||||||
|
}
|
||||||
|
}
|
||||||
127
webpack.config.js
Normal file
127
webpack.config.js
Normal file
|
|
@ -0,0 +1,127 @@
|
||||||
|
const path = require('path');
|
||||||
|
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||||
|
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
|
||||||
|
const TerserJSPlugin = require('terser-webpack-plugin');
|
||||||
|
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
|
||||||
|
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||||
|
const HtmlWebpackInlineSourcePlugin = require('html-webpack-inline-source-plugin');
|
||||||
|
const Sass = require('sass');
|
||||||
|
|
||||||
|
const isProduction = process.env.NODE_ENV == 'production';
|
||||||
|
const isDevelopment = !isProduction;
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
watchOptions: {
|
||||||
|
ignored: /node_modules/,
|
||||||
|
},
|
||||||
|
devtool: 'inline-source-map',
|
||||||
|
devServer: {
|
||||||
|
host: '0.0.0.0',
|
||||||
|
disableHostCheck: true,
|
||||||
|
},
|
||||||
|
optimization: {
|
||||||
|
minimize: true,
|
||||||
|
minimizer: [
|
||||||
|
new TerserJSPlugin({
|
||||||
|
sourceMap: isDevelopment,
|
||||||
|
cache: true,
|
||||||
|
test: /\.ts$/i,
|
||||||
|
terserOptions: {
|
||||||
|
ecma: 5,
|
||||||
|
warnings: true,
|
||||||
|
parse: {},
|
||||||
|
compress: { defaults: true },
|
||||||
|
mangle: true,
|
||||||
|
module: false,
|
||||||
|
output: null,
|
||||||
|
toplevel: true,
|
||||||
|
nameCache: null,
|
||||||
|
ie8: false,
|
||||||
|
keep_classnames: false,
|
||||||
|
keep_fnames: false,
|
||||||
|
safari10: false,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
new OptimizeCSSAssetsPlugin({}),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
new CleanWebpackPlugin(),
|
||||||
|
new HtmlWebpackPlugin({
|
||||||
|
xhtml: true,
|
||||||
|
template: './src/index.html',
|
||||||
|
minify: {
|
||||||
|
collapseWhitespace: true,
|
||||||
|
removeComments: true,
|
||||||
|
removeRedundantAttributes: true,
|
||||||
|
removeScriptTypeAttributes: true,
|
||||||
|
removeStyleLinkTypeAttributes: true,
|
||||||
|
useShortDoctype: true,
|
||||||
|
},
|
||||||
|
inlineSource: '.(js|css)$',
|
||||||
|
}),
|
||||||
|
new HtmlWebpackInlineSourcePlugin(),
|
||||||
|
new MiniCssExtractPlugin({
|
||||||
|
filename: '[name].[contenthash].css',
|
||||||
|
chunkFilename: '[id].[contenthash].css',
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
entry: {
|
||||||
|
index: './src/index.ts',
|
||||||
|
},
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
test: /\.(glsl)$/,
|
||||||
|
use: {
|
||||||
|
loader: 'raw-loader',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.ico$/i,
|
||||||
|
use: {
|
||||||
|
loader: 'file-loader',
|
||||||
|
query: {
|
||||||
|
outputPath: '/',
|
||||||
|
name: '[name].[ext]',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.scss$/i,
|
||||||
|
use: [
|
||||||
|
MiniCssExtractPlugin.loader,
|
||||||
|
'css-loader',
|
||||||
|
'postcss-loader',
|
||||||
|
{
|
||||||
|
loader: 'resolve-url-loader',
|
||||||
|
options: {
|
||||||
|
keepQuery: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
loader: 'sass-loader',
|
||||||
|
options: {
|
||||||
|
sourceMap: true,
|
||||||
|
implementation: Sass,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.ts$/,
|
||||||
|
use: {
|
||||||
|
loader: 'ts-loader',
|
||||||
|
},
|
||||||
|
exclude: /node_modules/,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
extensions: ['.ts', '.js', '.glsl'],
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
filename: '[name].[contenthash].js',
|
||||||
|
path: path.resolve(__dirname, 'dist'),
|
||||||
|
},
|
||||||
|
};
|
||||||
Loading…
Add table
Add a link
Reference in a new issue