diff --git a/.gitignore b/.gitignore index a9467ad3..d42dac5a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ node_modules output dist +test .idea \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index f6b7c0a3..5603437b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,18 +1,19 @@ { "name": "@aarthificial/motion-canvas", - "version": "1.0.14", + "version": "1.2.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@aarthificial/motion-canvas", - "version": "1.0.14", + "version": "1.2.0", "license": "ISC", "dependencies": { "canvas": "^2.9.0", "konva": "^8.3.2", "ts-loader": "^9.2.6", "typescript": "^4.5.5", + "url-loader": "^4.1.1", "webpack": "^5.68.0", "webpack-dev-server": "^4.7.4" }, @@ -627,7 +628,6 @@ "version": "5.2.2", "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", - "dev": true, "engines": { "node": "*" } @@ -1207,7 +1207,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", - "dev": true, "engines": { "node": ">= 4" } @@ -2165,7 +2164,6 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", - "dev": true, "dependencies": { "minimist": "^1.2.5" }, @@ -2216,7 +2214,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.2.tgz", "integrity": "sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A==", - "dev": true, "dependencies": { "big.js": "^5.2.2", "emojis-list": "^3.0.0", @@ -3683,6 +3680,32 @@ "punycode": "^2.1.0" } }, + "node_modules/url-loader": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/url-loader/-/url-loader-4.1.1.tgz", + "integrity": "sha512-3BTV812+AVHHOJQO8O5MkWgZ5aosP7GnROJwvzLS9hWDj00lZ6Z0wNak423Lp9PBZN05N+Jk/N5Si8jRAlGyWA==", + "dependencies": { + "loader-utils": "^2.0.0", + "mime-types": "^2.1.27", + "schema-utils": "^3.0.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "file-loader": "*", + "webpack": "^4.0.0 || ^5.0.0" + }, + "peerDependenciesMeta": { + "file-loader": { + "optional": true + } + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -4657,8 +4680,7 @@ "big.js": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", - "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", - "dev": true + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==" }, "binary-extensions": { "version": "2.2.0", @@ -5104,8 +5126,7 @@ "emojis-list": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", - "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", - "dev": true + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==" }, "encodeurl": { "version": "1.0.2", @@ -5814,7 +5835,6 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", - "dev": true, "requires": { "minimist": "^1.2.5" } @@ -5839,7 +5859,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.2.tgz", "integrity": "sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A==", - "dev": true, "requires": { "big.js": "^5.2.2", "emojis-list": "^3.0.0", @@ -6861,6 +6880,16 @@ "punycode": "^2.1.0" } }, + "url-loader": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/url-loader/-/url-loader-4.1.1.tgz", + "integrity": "sha512-3BTV812+AVHHOJQO8O5MkWgZ5aosP7GnROJwvzLS9hWDj00lZ6Z0wNak423Lp9PBZN05N+Jk/N5Si8jRAlGyWA==", + "requires": { + "loader-utils": "^2.0.0", + "mime-types": "^2.1.27", + "schema-utils": "^3.0.0" + } + }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", diff --git a/package.json b/package.json index 832a9878..12421b53 100644 --- a/package.json +++ b/package.json @@ -7,13 +7,16 @@ "license": "ISC", "scripts": { "prepare": "npm run build", - "build": "tsc" + "build": "tsc", + "test:serve": "node ./tools/serve.mjs ./test/player.ts", + "test:render": "node ./tools/serve.mjs ./test/render.ts" }, "dependencies": { "canvas": "^2.9.0", "konva": "^8.3.2", "ts-loader": "^9.2.6", "typescript": "^4.5.5", + "url-loader": "^4.1.1", "webpack": "^5.68.0", "webpack-dev-server": "^4.7.4" }, diff --git a/src/Player.ts b/src/Player.ts index 03e89816..bf0d863d 100644 --- a/src/Player.ts +++ b/src/Player.ts @@ -2,9 +2,12 @@ import {Project} from './Project'; export function Player(factory: () => Project) { const project = factory(); + project.start(); const interval = setInterval(() => { if (project.next()) { - clearInterval(interval); + project.start(); + project.next(); + // clearInterval(interval); } }, 1000 / project.framesPerSeconds); } diff --git a/src/Project.ts b/src/Project.ts index 1dbbd1ae..3d510fd2 100644 --- a/src/Project.ts +++ b/src/Project.ts @@ -2,6 +2,10 @@ import {Stage, StageConfig} from 'konva/lib/Stage'; import {Rect} from 'konva/lib/shapes/Rect'; import {Layer} from 'konva/lib/Layer'; import {Scene, SceneRunner} from './Scene'; +import {Vector2d} from 'konva/lib/types'; +import {Konva} from "konva/lib/Global"; + +Konva.autoDrawEnabled = false; export enum ProjectSize { FullHD, @@ -13,11 +17,15 @@ const Sizes: Record = { export class Project extends Stage { public readonly background: Rect; + public readonly center: Vector2d; public framesPerSeconds = 60; public frame: number = 0; + private runner: Generator; + private scenes: Scene[] = []; public constructor( + private runnerFactory: (project: Project) => Generator, size: ProjectSize = ProjectSize.FullHD, config: Partial = {}, ) { @@ -28,6 +36,11 @@ export class Project extends Stage { ...config, }); + this.center = { + x: Sizes[size][0] / 2, + y: Sizes[size][1] / 2, + }; + this.background = new Rect({ x: 0, y: 0, @@ -42,19 +55,23 @@ export class Project extends Stage { } public createScene(runner: SceneRunner) { - const layer = new Layer(); - this.add(layer); + const scene = new Scene(this, new Layer(), runner); + this.add(scene.layer); + this.scenes.push(scene); - return new Scene(this, layer, runner); + return scene; } - public setRunner(factory: (project: Project) => Generator) { + public start() { + this.scenes.forEach(scene => scene.layer.destroy()); + this.scenes = []; this.frame = 0; - this.runner = factory(this); + this.runner = this.runnerFactory(this); } public next(): boolean { const result = this.runner.next(); + this.draw(); this.frame++; return result.done; diff --git a/src/Scene.ts b/src/Scene.ts index 13a0d7ea..d06debfc 100644 --- a/src/Scene.ts +++ b/src/Scene.ts @@ -7,8 +7,8 @@ export interface SceneRunner { export class Scene { public constructor( - private project: Project, - private layer: Layer, + public readonly project: Project, + public readonly layer: Layer, private runner: SceneRunner, ) {} diff --git a/src/animations/index.ts b/src/animations/index.ts new file mode 100644 index 00000000..172b322e --- /dev/null +++ b/src/animations/index.ts @@ -0,0 +1,2 @@ +export * from './move'; +export * from './show'; \ No newline at end of file diff --git a/src/animations/move.ts b/src/animations/move.ts index 4e74c2c7..50b65495 100644 --- a/src/animations/move.ts +++ b/src/animations/move.ts @@ -1,6 +1,28 @@ -import {Node} from "konva/lib/Node"; -import {Vector2d} from "konva/lib/types"; +import {Node} from 'konva/lib/Node'; +import {Vector2d} from 'konva/lib/types'; +import {tween} from '../tweening'; -export function move(node: Node, positionTo: Vector2d) { - const positionFrom = node.position(); -} \ No newline at end of file +export function move(node: Node, position: Vector2d): Generator; +export function move( + node: Node, + positionX: number, + positionY: number, +): Generator; +export function move( + node: Node, + positionX: number | Vector2d, + positionY?: number, +): Generator { + const positionFrom = node.position(); + const positionTo = + typeof positionX === 'number' ? {x: positionX, y: positionY} : positionX; + + const distance = Math.sqrt( + Math.pow(positionFrom.x - positionTo.x, 2) + + Math.pow(positionFrom.y - positionTo.y, 2), + ); + + return tween(distance / 1000, value => + node.position(value.vector2d(positionFrom, positionTo)), + ); +} diff --git a/src/components/Arrow.ts b/src/components/Arrow.ts index 3dae631e..1b690507 100644 --- a/src/components/Arrow.ts +++ b/src/components/Arrow.ts @@ -88,6 +88,7 @@ class LineSegment extends Segment { class CircleSegment extends Segment { private readonly length: number; + private readonly delta: number; public constructor( private center: Vector2d, @@ -97,7 +98,10 @@ class CircleSegment extends Segment { private counter: boolean, ) { super(); - this.length = Math.abs(deltaAngle * radius); + this.delta = this.counter + ? this.deltaAngle + : this.deltaAngle + Math.PI * 2; + this.length = Math.abs(this.delta * radius); } get arcLength(): number { @@ -110,12 +114,8 @@ class CircleSegment extends Segment { to: number, move: boolean, ): [Vector2d, Vector2d, Vector2d, Vector2d] { - const delta = this.counter - ? this.deltaAngle - : this.deltaAngle + Math.PI * 2; - - const startAngle = this.startAngle + delta * from; - const endAngle = this.startAngle + delta * to; + const startAngle = this.startAngle + this.delta * from; + const endAngle = this.startAngle + this.delta * to; context.arc( this.center.x, @@ -160,14 +160,16 @@ class CircleSegment extends Segment { } public getOffset(from: number): number { - return this.counter ? 0 : -from * 1.045 * this.deltaAngle * this.radius / 2; + // May wanna go back to (-from * 1.045 * this.delta * this.radius) / 2 + return 0; } } export class Arrow extends Shape { + protected dirty = true; + private segments: Segment[] = []; private arcLength: number = 0; - private dirty = true; _sceneFunc(context: Context) { if (this.dirty) { diff --git a/src/components/Connection.ts b/src/components/Connection.ts new file mode 100644 index 00000000..93dd7990 --- /dev/null +++ b/src/components/Connection.ts @@ -0,0 +1,259 @@ +import {Arrow, ArrowConfig} from './Arrow'; +import {Node} from 'konva/lib/Node'; +import {_registerNode} from 'konva/lib/Global'; +import {Factory} from 'konva/lib/Factory'; +import {GetSet, Vector2d} from 'konva/lib/types'; +import {Direction} from '../types/Origin'; +import {Context} from 'konva/lib/Context'; +import {TimeTween} from '../tweening'; + +export interface ConnectionPoint { + node: Node; + direction: Direction; + offset: number; + vertical?: boolean; +} + +export interface ConnectionConfig extends ArrowConfig { + from: ConnectionPoint; + to: ConnectionPoint; + crossing: Vector2d; +} + +interface Measurement { + direction: -1 | 0 | 1; + range: [number, number]; + rangeOffset: [number, number]; + from: number; + to: number; +} + +function clamp(value: number, min: number, max: number): number { + if (min > max) [min, max] = [max, min]; + return value < min ? min : value > max ? max : value; +} + +export class Connection extends Arrow { + constructor(config?: ConnectionConfig) { + super(config); + } + + private measurePosition( + positionA: number, + sizeA: number, + positionB: number, + sizeB: number, + ): Measurement { + let direction: -1 | 0 | 1; + let range: [number, number]; + let rangeOffset: [number, number]; + + if (positionA - sizeA - 80 > positionB + sizeB) { + direction = -1; + range = [positionA - sizeA - 20, positionB + sizeB + 20]; + rangeOffset = [range[0] - 20, range[1] + 20]; + } else if (positionA + sizeA + 80 < positionB - sizeB) { + direction = 1; + range = [positionA + sizeA + 20, positionB - sizeB - 20]; + rangeOffset = [range[0] + 20, range[1] - 20]; + } else { + direction = 0; + range = [positionA - sizeA - 20, positionB - sizeB - 20]; + rangeOffset = [range[0] - 20, range[1] - 20]; + } + + return {direction, range, rangeOffset, from: positionA, to: positionB}; + } + + private calculateCrossing( + crossing: number, + measurement: Measurement, + ): number { + const fractionX = crossing >= 0 && crossing <= 1; + let clampedCrossing = crossing + measurement.from; + if (measurement.direction !== 0 && !fractionX) { + clampedCrossing = clamp( + clampedCrossing, + measurement.rangeOffset[0], + measurement.rangeOffset[1], + ); + } + + return measurement.direction === 0 + ? Math.min( + measurement.rangeOffset[0], + measurement.rangeOffset[1], + clampedCrossing, + ) + : fractionX + ? TimeTween.map( + measurement.rangeOffset[0], + measurement.rangeOffset[1], + crossing, + ) + : clampedCrossing; + } + + _sceneFunc(context: Context) { + if (this.dirty) { + if (!this.attrs.from || !this.attrs.to) { + this.attrs.points = []; + } else { + const from: ConnectionPoint = this.attrs.from; + const to: ConnectionPoint = this.attrs.to; + + const fromPosition = from.node.absolutePosition(); + const fromSize = from.node.size(); + fromSize.width /= 2; + fromSize.height /= 2; + const toPosition = to.node.absolutePosition(); + const toSize = to.node.size(); + toSize.width /= 2; + toSize.height /= 2; + + this.attrs.points = []; + const horizontal = this.measurePosition( + fromPosition.x, + fromSize.width, + toPosition.x, + toSize.width, + ); + const vertical = this.measurePosition( + fromPosition.y, + fromSize.height, + toPosition.y, + toSize.height, + ); + + if (from.vertical) { + this.attrs.points.push(fromPosition.x, vertical.range[0]); + } else { + this.attrs.points.push(horizontal.range[0], fromPosition.y); + } + + let toVertical = to.vertical; + if ( + from.vertical !== toVertical && + (horizontal.direction === 0 || vertical.direction === 0) + ) { + toVertical = from.vertical; + } + + let distance = 100; + if (from.vertical === toVertical) { + if (from.vertical) { + distance = Math.abs(toPosition.x - fromPosition.x); + if (distance > 0) { + const y = this.calculateCrossing(this.attrs.crossing.y, vertical); + this.attrs.points.push(fromPosition.x, y); + this.attrs.points.push(toPosition.x, y); + } + } else { + distance = Math.abs(toPosition.y - fromPosition.y); + if (distance > 0) { + const x = this.calculateCrossing( + this.attrs.crossing.x, + horizontal, + ); + this.attrs.points.push(x, fromPosition.y); + this.attrs.points.push(x, toPosition.y); + } + } + } else { + if (from.vertical) { + this.attrs.points.push(fromPosition.x, toPosition.y); + } else { + this.attrs.points.push(toPosition.x, fromPosition.y); + } + } + + if (toVertical) { + this.attrs.points.push(toPosition.x, vertical.range[1]); + } else { + this.attrs.points.push(horizontal.range[1], toPosition.y); + } + + this.attrs.radius = Math.min(8, distance / 2); + } + } + + super._sceneFunc(context); + } + + private previousFromNode: Node | null = null; + public fromChanged() { + if (this.previousFromNode === this.attrs.from?.node) return; + this.markAsDirtyCallback ??= () => this.markAsDirty(); + + this.previousFromNode?.off( + 'absoluteTransformChange', + this.markAsDirtyCallback, + ); + this.previousFromNode = this.attrs.from?.node; + this.previousFromNode?.on( + 'absoluteTransformChange', + this.markAsDirtyCallback, + ); + this.markAsDirty(); + } + + private previousToNode: Node | null = null; + public toChanged() { + if (this.previousToNode === this.attrs.to?.node) return; + this.markAsDirtyCallback ??= () => this.markAsDirty(); + + this.previousToNode?.off( + 'absoluteTransformChange', + this.markAsDirtyCallback, + ); + this.previousToNode = this.attrs.to?.node; + this.previousToNode?.on( + 'absoluteTransformChange', + this.markAsDirtyCallback, + ); + this.markAsDirty(); + } + + private markAsDirtyCallback = () => { + this.markAsDirty(); + }; + + from: GetSet; + to: GetSet; + crossing: GetSet; +} + +Connection.prototype.className = 'Connection'; +Connection.prototype._attrsAffectingSize = ['from', 'to']; + +_registerNode(Arrow); + +Factory.addGetterSetter( + Connection, + 'from', + { + node: null, + direction: Direction.Right, + offset: 0, + }, + undefined, + Connection.prototype.fromChanged, +); +Factory.addGetterSetter( + Connection, + 'to', + { + node: null, + direction: Direction.Right, + offset: 0, + }, + undefined, + Connection.prototype.toChanged, +); +Factory.addGetterSetter( + Connection, + 'crossing', + {x: 0, y: 0}, + undefined, + Connection.prototype.markAsDirty, +); diff --git a/src/components/ObjectNode.ts b/src/components/ObjectNode.ts index 44dba0ab..446162de 100644 --- a/src/components/ObjectNode.ts +++ b/src/components/ObjectNode.ts @@ -2,6 +2,7 @@ import {Group} from 'konva/lib/Group'; import {Rect} from 'konva/lib/shapes/Rect'; import {Text} from 'konva/lib/shapes/Text'; import {Vector2d} from 'konva/lib/types'; +import {ContainerConfig} from 'konva/lib/Container'; import {Origin, Direction} from '../types/Origin'; import {tween} from '../tweening'; @@ -10,8 +11,8 @@ export class ObjectNode extends Group { public readonly text: Text; private _origin: Origin = Origin.Middle; - public constructor() { - super(); + public constructor(config?: ContainerConfig) { + super(config); this.box = new Rect({ x: 0, @@ -82,7 +83,7 @@ export class ObjectNode extends Group { const previousWidth = this.text.width(); this.text.width(null); const width = this.text.getTextWidth(); - const height = this.text.getTextHeight(); + const height = this.text.height(); const boxWidth = Math.ceil((width + 80) / 20) * 20; this.text.width(previousWidth); diff --git a/src/components/Sprite.ts b/src/components/Sprite.ts new file mode 100644 index 00000000..dde740f8 --- /dev/null +++ b/src/components/Sprite.ts @@ -0,0 +1,22 @@ +import {Shape, ShapeConfig} from 'konva/lib/Shape'; +import {Context} from 'konva/lib/Context'; +import {Util} from 'konva/lib/Util'; + +export interface SpriteConfig extends ShapeConfig { + image: string; +} + +export class Sprite extends Shape { + private image: HTMLImageElement; + + constructor(config?: SpriteConfig) { + super(config); + this.image = Util.createImageElement(); + this.image.src = config?.image; + } + + _sceneFunc(context: Context) { + context._context.imageSmoothingEnabled = false; + context.drawImage(this.image, -24 * 20, -24 * 20, 24 * 40, 24 * 40); + } +} diff --git a/src/components/index.ts b/src/components/index.ts index 9dc6ad60..37048d88 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -1,2 +1,3 @@ export * from './Arrow'; +export * from './Connection'; export * from './ObjectNode'; \ No newline at end of file diff --git a/src/flow/any.ts b/src/flow/any.ts new file mode 100644 index 00000000..38401dee --- /dev/null +++ b/src/flow/any.ts @@ -0,0 +1,12 @@ +export function* any(...sequences: Generator[]): Generator { + while (sequences.length > 0) { + for (let i = sequences.length - 1; i >= 0; i--) { + const result = sequences[i].next(); + if (result.done) { + return; + } + } + + yield; + } +} diff --git a/src/flow/index.ts b/src/flow/index.ts index 25b2fe03..3dc999a2 100644 --- a/src/flow/index.ts +++ b/src/flow/index.ts @@ -1,2 +1,4 @@ export * from './all'; +export * from './any'; +export * from './loop'; export * from './sequence'; diff --git a/src/flow/loop.ts b/src/flow/loop.ts new file mode 100644 index 00000000..b759f290 --- /dev/null +++ b/src/flow/loop.ts @@ -0,0 +1,5 @@ +export function* loop(iterations: number, factory: () => Generator) { + for (let i = 0; i < iterations; i++) { + yield* factory(); + } +} diff --git a/src/global.d.ts b/src/global.d.ts new file mode 100644 index 00000000..03fb3f4b --- /dev/null +++ b/src/global.d.ts @@ -0,0 +1,4 @@ +declare module "*.png" { + const value: any; + export = value; +} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 00000000..7d12c07f --- /dev/null +++ b/src/index.ts @@ -0,0 +1,2 @@ +export {Sprite} from 'konva/lib/shapes/Sprite'; +export {Util} from 'konva/lib/Util'; diff --git a/tools/render.mjs b/tools/render.mjs index 2017bd2d..2202b590 100644 --- a/tools/render.mjs +++ b/tools/render.mjs @@ -70,6 +70,7 @@ function build(entry) { let totalSize = 0; const project = setup.default.default(createCanvas, Image); + project.start(); while (!project.next()) { const name = String(project.frame).padStart(6, '0'); const content = project.toDataURL().replace(/^data:image\/png;base64,/, ''); diff --git a/tools/serve.mjs b/tools/serve.mjs index 6d13c631..f929d14c 100644 --- a/tools/serve.mjs +++ b/tools/serve.mjs @@ -23,6 +23,17 @@ const compiler = webpack({ allowTsInNodeModules: true, } }, + { + test: /\.(png|jpg|gif)$/i, + use: [ + { + loader: 'url-loader', + options: { + limit: 8192, + }, + }, + ], + }, ], }, resolve: { diff --git a/tsconfig.json b/tsconfig.json index 75308ad0..6c142629 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,6 +8,12 @@ "target": "es6", "declaration": true, "moduleResolution": "node", - "allowSyntheticDefaultImports": true + "allowSyntheticDefaultImports": true, + "paths": { + "MC/*": [ + "../src/*", + "../dist/*" + ] + } } }