feature: generate pdf from actor

This commit is contained in:
Matthieu CAILLEAUX
2021-10-18 00:35:13 +02:00
parent f96372b236
commit 145207a1c5
14 changed files with 391 additions and 138 deletions

View File

@@ -2,6 +2,6 @@ export const i18n = () => (<any>game).i18n;
export const i18nLocalize = (id: string) => i18n().localize(id); export const i18nLocalize = (id: string) => i18n().localize(id);
export const i18nFormat = (id: string, data?: any) => i18n().format(id, data); export const i18nFormat = (id: string, data?: any) => i18n().format(id, data);
export const TEXT_SIZE = 10; export const TEXT_SIZE = 8;
export const LABEL_SIZE = 8; export const LABEL_SIZE = 6;
export const MARGINS = { top: 10, left: 10, bottom: 10, right: 10 }; export const MARGINS = { top: 10, left: 10, bottom: 10, right: 10 };

View File

@@ -20,7 +20,13 @@ export abstract class AbstractElement {
this.maxWidth = maxWidth; this.maxWidth = maxWidth;
} }
public abstract prepareRender(doc: jsPDF, maxWidth?: number): jsPDF;
public abstract render(doc: jsPDF, maxWidth?: number): jsPDF; public abstract render(doc: jsPDF, maxWidth?: number): jsPDF;
public abstract getHeight(doc?: jsPDF): number; public abstract getHeight(doc?: jsPDF): number;
public abstract getCheckNewPageHeight(doc?: jsPDF): number;
public abstract getElements(): AbstractElement[];
} }

View File

@@ -11,6 +11,10 @@ export class Box extends AbstractElement {
this.h = h; this.h = h;
} }
public prepareRender(doc: jsPDF, _maxWidth?: number): jsPDF {
return doc;
}
public render(doc: jsPDF, _maxWidth?: number): jsPDF { public render(doc: jsPDF, _maxWidth?: number): jsPDF {
doc.rect(this.x, this.y, this.w, this.h); doc.rect(this.x, this.y, this.w, this.h);
return doc; return doc;
@@ -19,4 +23,12 @@ export class Box extends AbstractElement {
public getHeight(_doc): number { public getHeight(_doc): number {
return this.h; return this.h;
} }
public getCheckNewPageHeight(doc?: jsPDF): number {
return this.getHeight(doc);
}
public getElements(): AbstractElement[] {
return [this];
}
} }

View File

@@ -9,20 +9,24 @@ export class Column extends AbstractElement {
this.elements = elements; this.elements = elements;
} }
public render(doc: jsPDF, _maxWidth?: number): jsPDF { public prepareRender(doc: jsPDF, _maxWidth?: number): jsPDF {
const elements = this.elements ?? []; const elements = this.elements ?? [];
let currentY = this.y; let currentY = this.y;
for (let i = 0; i < elements.length; i++) { for (let i = 0; i < elements.length; i++) {
const element = elements[i]; const element = elements[i];
element.x = this.x; element.x = Math.max(element.x, this.x);
element.y = currentY; element.y = currentY;
element.render(doc); element.prepareRender(doc);
currentY += element.getHeight(doc) + 2; currentY += element.getHeight(doc) + 2;
} }
return doc; return doc;
} }
public render(doc: jsPDF, _maxWidth?: number): jsPDF {
return doc;
}
public getHeight(doc): number { public getHeight(doc): number {
return this.elements return this.elements
.map((e) => e.getHeight(doc)) .map((e) => e.getHeight(doc))
@@ -33,4 +37,18 @@ export class Column extends AbstractElement {
return p + c + 2; return p + c + 2;
}); });
} }
public getCheckNewPageHeight(doc?: jsPDF): number {
return this.elements.length > 0
? this.elements[0].getCheckNewPageHeight(doc)
: 0;
}
public getElements(): AbstractElement[] {
const elements: AbstractElement[] = [];
for (const element of this.elements) {
elements.push(...element.getElements());
}
return elements;
}
} }

View File

@@ -1,5 +1,6 @@
import { Box } from './box'; import { Box } from './box';
import jsPDF from 'jspdf'; import jsPDF from 'jspdf';
import { AbstractElement } from './abstract-element';
export class Image extends Box { export class Image extends Box {
public imageData: string; public imageData: string;
@@ -19,4 +20,8 @@ export class Image extends Box {
}); });
return doc; return doc;
} }
public getElements(): AbstractElement[] {
return [this];
}
} }

View File

@@ -1,6 +1,7 @@
import jsPDF, { TextOptionsLight } from 'jspdf'; import jsPDF, { TextOptionsLight } from 'jspdf';
import { Text } from './text'; import { Text } from './text';
import { i18nLocalize, LABEL_SIZE, TEXT_SIZE } from '../constants'; import { i18nLocalize, LABEL_SIZE, TEXT_SIZE } from '../constants';
import { AbstractElement } from './abstract-element';
export class LabelledText extends Text { export class LabelledText extends Text {
public label: string; public label: string;
@@ -23,8 +24,12 @@ export class LabelledText extends Text {
this.updateMaxWidth(maxWidth); this.updateMaxWidth(maxWidth);
} }
public render(doc: jsPDF, maxWidth?: number): jsPDF { public prepareRender(doc: jsPDF, maxWidth?: number): jsPDF {
this.updateMaxWidth(maxWidth); this.updateMaxWidth(maxWidth);
return super.prepareRender(doc, maxWidth);
}
public render(doc: jsPDF, _maxWidth?: number): jsPDF {
const yLabel = this.y + this.getHeightFromPx(doc, LABEL_SIZE); const yLabel = this.y + this.getHeightFromPx(doc, LABEL_SIZE);
const yText = yLabel + this.getHeightFromPx(doc, TEXT_SIZE) + 1; const yText = yLabel + this.getHeightFromPx(doc, TEXT_SIZE) + 1;
doc doc
@@ -50,4 +55,12 @@ export class LabelledText extends Text {
public getHeight(doc): number { public getHeight(doc): number {
return this.getHeightFromPx(doc, TEXT_SIZE + LABEL_SIZE) + 1; return this.getHeightFromPx(doc, TEXT_SIZE + LABEL_SIZE) + 1;
} }
public getCheckNewPageHeight(doc?: jsPDF): number {
return this.getHeight(doc);
}
public getElements(): AbstractElement[] {
return [this];
}
} }

View File

@@ -69,7 +69,7 @@ export class LabelledValues extends Row {
} }
} }
public render(doc: jsPDF, maxWidth?: number): jsPDF { public prepareRender(doc: jsPDF, maxWidth?: number): jsPDF {
const pageWidth = doc.internal.pageSize.width; const pageWidth = doc.internal.pageSize.width;
const rowWidth = pageWidth - this.x - MARGINS.right; const rowWidth = pageWidth - this.x - MARGINS.right;
for (const column of this.elements) { for (const column of this.elements) {
@@ -77,6 +77,10 @@ export class LabelledValues extends Row {
labelledValue.maxWidth = rowWidth / this.nbrOfCol; labelledValue.maxWidth = rowWidth / this.nbrOfCol;
} }
} }
return super.render(doc, maxWidth); return super.prepareRender(doc, maxWidth);
}
public render(doc: jsPDF, _maxWidth?: number): jsPDF {
return doc;
} }
} }

View File

@@ -21,7 +21,7 @@ export class Row extends AbstractElement {
this.maxWidths = maxWidths ?? []; this.maxWidths = maxWidths ?? [];
} }
public render(doc: jsPDF, maxWidth?: number): jsPDF { public prepareRender(doc: jsPDF, maxWidth?: number): jsPDF {
const elements = this.elements ?? []; const elements = this.elements ?? [];
let maxWidths = this.maxWidths ?? []; let maxWidths = this.maxWidths ?? [];
let widthPercents = this.widthPercents ?? []; let widthPercents = this.widthPercents ?? [];
@@ -50,12 +50,16 @@ export class Row extends AbstractElement {
const maxChildWidth = maxWidths[i] ?? percentWidth; const maxChildWidth = maxWidths[i] ?? percentWidth;
element.x = currentX; element.x = currentX;
element.y = this.y; element.y = this.y;
element.render(doc, maxChildWidth); element.prepareRender(doc, maxChildWidth);
currentX += percentWidth; currentX += percentWidth;
} }
return doc; return doc;
} }
public render(doc: jsPDF, _maxWidth?: number): jsPDF {
return doc;
}
public getHeight(doc?: jsPDF): number { public getHeight(doc?: jsPDF): number {
let maxHeight = 0; let maxHeight = 0;
for (const element of this.elements) { for (const element of this.elements) {
@@ -63,4 +67,20 @@ export class Row extends AbstractElement {
} }
return maxHeight; return maxHeight;
} }
public getCheckNewPageHeight(doc?: jsPDF): number {
let maxHeight = 0;
for (const element of this.elements) {
maxHeight = Math.max(maxHeight, element.getCheckNewPageHeight(doc));
}
return maxHeight;
}
public getElements(): AbstractElement[] {
const elements: AbstractElement[] = [];
for (const element of this.elements) {
elements.push(...element.getElements());
}
return elements;
}
} }

39
src/elements/separator.ts Normal file
View File

@@ -0,0 +1,39 @@
import { AbstractElement } from './abstract-element';
import jsPDF from 'jspdf';
import { MARGINS } from '../constants';
export class Separator extends AbstractElement {
constructor(x: number, y: number, maxWidth?: number | undefined) {
super(x, y, maxWidth);
}
public getHeight(_doc?: jsPDF): number {
return 0.5;
}
public getCheckNewPageHeight(doc?: jsPDF): number {
return this.getHeight(doc);
}
public prepareRender(doc: jsPDF, _maxWidth?: number): jsPDF {
return doc;
}
public render(doc: jsPDF, maxWidth?: number): jsPDF {
const pageWidth = doc.internal.pageSize.width;
const maxPageWidth = pageWidth - MARGINS.left - MARGINS.right;
const finalWidth = Math.min(
maxWidth ?? this.maxWidth ?? maxPageWidth,
maxPageWidth
);
doc.setLineWidth(0.25);
doc.line(this.x, this.y, this.x + finalWidth, this.y);
return doc;
}
public getElements(): AbstractElement[] {
return [this];
}
}

View File

@@ -17,11 +17,18 @@ export class Text extends AbstractElement {
this.textOptions = textOptions; this.textOptions = textOptions;
} }
public render(doc: jsPDF, maxWidth?: number): jsPDF { public prepareRender(doc: jsPDF, maxWidth?: number): jsPDF {
this.updateMaxWidth(maxWidth); this.updateMaxWidth(maxWidth);
let finalText = [i18nLocalize(this.text)]; return doc;
}
public render(doc: jsPDF, _maxWidth?: number): jsPDF {
let finalText: string[] = [i18nLocalize(this.text)];
if (this.maxWidth != null) { if (this.maxWidth != null) {
finalText = doc.splitTextToSize(finalText[0], maxWidth ?? 0); finalText = doc.splitTextToSize(finalText[0], this.maxWidth ?? 0);
}
if (finalText.length > 1) {
finalText[0] = finalText[0].replace(/(.){3}$/, '...');
} }
const yText = this.y + this.getHeightFromPx(doc, TEXT_SIZE); const yText = this.y + this.getHeightFromPx(doc, TEXT_SIZE);
doc doc
@@ -42,4 +49,12 @@ export class Text extends AbstractElement {
public getHeight(doc): number { public getHeight(doc): number {
return this.getHeightFromPx(doc, TEXT_SIZE); return this.getHeightFromPx(doc, TEXT_SIZE);
} }
public getCheckNewPageHeight(doc?: jsPDF): number {
return this.getHeight(doc);
}
public getElements(): AbstractElement[] {
return [this];
}
} }

View File

@@ -53,7 +53,7 @@ export class Texts extends Row {
} }
} }
public render(doc: jsPDF, maxWidth?: number): jsPDF { public prepareRender(doc: jsPDF, maxWidth?: number): jsPDF {
const pageWidth = doc.internal.pageSize.width; const pageWidth = doc.internal.pageSize.width;
const rowWidth = pageWidth - this.x - MARGINS.right; const rowWidth = pageWidth - this.x - MARGINS.right;
for (const column of this.elements) { for (const column of this.elements) {
@@ -61,6 +61,10 @@ export class Texts extends Row {
labelledValue.maxWidth = rowWidth / this.nbrOfCol; labelledValue.maxWidth = rowWidth / this.nbrOfCol;
} }
} }
return super.render(doc, maxWidth); return super.prepareRender(doc, maxWidth);
}
public render(doc: jsPDF, _maxWidth?: number): jsPDF {
return doc;
} }
} }

View File

@@ -9,6 +9,8 @@ import { ItemData } from '@league-of-foundry-developers/foundry-vtt-types/src/fo
import { LabelledValues } from './elements/labelled-values'; import { LabelledValues } from './elements/labelled-values';
import { Text } from './elements/text'; import { Text } from './elements/text';
import { Texts } from './elements/texts'; import { Texts } from './elements/texts';
import { Column } from './elements/column';
import { Separator } from './elements/separator';
Hooks.on( Hooks.on(
'renderActorSheetWfrp4eCharacter', 'renderActorSheetWfrp4eCharacter',
@@ -37,18 +39,10 @@ Hooks.on(
const labelledRowHeight = const labelledRowHeight =
Util.getHeightFromPx(docBuilder.doc, TEXT_SIZE + LABEL_SIZE) + 1; Util.getHeightFromPx(docBuilder.doc, TEXT_SIZE + LABEL_SIZE) + 1;
const textRowHeight = Util.getHeightFromPx(docBuilder.doc, TEXT_SIZE);
const row2Y = labelledRowHeight + MARGINS.top + 2;
const row3Y = row2Y + labelledRowHeight + 2;
const row4Y = row3Y + labelledRowHeight + 2;
const row5Y = row4Y + labelledRowHeight + 2;
const row6Y = row5Y + labelledRowHeight + 2;
const row7Y = row6Y + labelledRowHeight + 2;
const row8Y = row7Y + textRowHeight + 2;
const skills = new LabelledValues( const skills = new LabelledValues(
0, 0,
row8Y, 0,
actor.itemCategories.skill actor.itemCategories.skill
.map((item) => { .map((item) => {
return { return {
@@ -59,12 +53,9 @@ Hooks.on(
.sort((a, b) => a.label.localeCompare(b.label)) .sort((a, b) => a.label.localeCompare(b.label))
); );
const row9Y = row8Y + skills.getHeight(docBuilder.doc) + 2;
const row10Y = row9Y + textRowHeight + 2;
const talents = new LabelledValues( const talents = new LabelledValues(
0, 0,
row10Y, 0,
actor.itemCategories.talent actor.itemCategories.talent
.map((item) => { .map((item) => {
return { return {
@@ -79,12 +70,9 @@ Hooks.on(
1 1
); );
const row11Y = row10Y + talents.getHeight(docBuilder.doc) + 2;
const row12Y = row11Y + textRowHeight + 2;
const traits = new Texts( const traits = new Texts(
0, 0,
row12Y, 0,
actor.itemCategories.trait actor.itemCategories.trait
.map((item) => { .map((item) => {
return item.name; return item.name;
@@ -93,20 +81,74 @@ Hooks.on(
4 4
); );
const weaponsMelee = new Texts(
0,
0,
actor.itemCategories.weapon
.filter((w) => w.isMelee)
.map((item) => {
return `${item.name} : ${item.data.data.damage.meleeValue} (${
item.mountDamage
}), ${item.OriginalQualities.concat(item.OriginalFlaws).join(
', '
)}`;
})
.sort((a, b) => a.localeCompare(b)),
1
);
const weaponsRanged = new Texts(
0,
0,
actor.itemCategories.weapon
.filter((w) => w.isRanged)
.map((item) => {
return `${item.name} : ${item.data.data.damage.rangedValue}, ${
item.data.data.range.value
}, ${item.OriginalQualities.concat(item.OriginalFlaws).join(', ')}`;
})
.sort((a, b) => a.localeCompare(b)),
1
);
const ammunitions = new Texts(
0,
0,
actor.itemCategories.ammunition
.map((item) => {
return `${item.data.data.quantity.value} ${item.name} : ${
item.data.data.damage.value.length > 0
? item.data.data.damage.value
: '+0'
}, (${item.data.data.range.value}), ${item.OriginalQualities.concat(
item.OriginalFlaws
).join(', ')}`;
})
.sort((a, b) => a.localeCompare(b)),
2
);
const imageWidth = 25; const imageWidth = 25;
const imageY = labelledRowHeight + MARGINS.top + 2;
const actorImageElement = const actorImageElement =
actorImageData != null actorImageData != null
? new Image(0, row2Y, imageWidth, imageWidth, actorImageData) ? new Image(0, imageY, imageWidth, imageWidth, actorImageData)
: new Box(0, row2Y, imageWidth, imageWidth); : new Box(0, imageY, imageWidth, imageWidth);
docBuilder.build([ docBuilder.build([
actorImageElement,
new Column(0, 0, [
new Row(0, 0, [ new Row(0, 0, [
new LabelledText(0, 0, 'Name', `${actor.name}`), new LabelledText(0, 0, 'Name', `${actor.name}`),
new LabelledText(0, 0, 'Species', `${actorDetails?.species?.value}`), new LabelledText(
0,
0,
'Species',
`${actorDetails?.species?.value}`
),
new LabelledText(0, 0, 'Gender', `${actorDetails?.gender?.value}`), new LabelledText(0, 0, 'Gender', `${actorDetails?.gender?.value}`),
]), ]),
actorImageElement, new Row(imageWidth + MARGINS.left + 1, 0, [
new Row(imageWidth + MARGINS.left + 1, row2Y, [
new LabelledText(0, 0, 'Class', `${careerDetail?.class?.value}`), new LabelledText(0, 0, 'Class', `${careerDetail?.class?.value}`),
new LabelledText( new LabelledText(
0, 0,
@@ -116,7 +158,7 @@ Hooks.on(
), ),
new LabelledText(0, 0, 'Career', `${currentCareer?.name}`), new LabelledText(0, 0, 'Career', `${currentCareer?.name}`),
]), ]),
new Row(imageWidth + MARGINS.left + 1, row3Y, [ new Row(imageWidth + MARGINS.left + 1, 0, [
new LabelledText(0, 0, 'Status', `${actorDetails?.status?.value}`), new LabelledText(0, 0, 'Status', `${actorDetails?.status?.value}`),
new LabelledText(0, 0, 'Age', `${actorDetails?.age?.value}`), new LabelledText(0, 0, 'Age', `${actorDetails?.age?.value}`),
new LabelledText(0, 0, 'Height', `${actorDetails?.height?.value}`), new LabelledText(0, 0, 'Height', `${actorDetails?.height?.value}`),
@@ -128,7 +170,7 @@ Hooks.on(
`${actorDetails?.haircolour?.value}` `${actorDetails?.haircolour?.value}`
), ),
]), ]),
new Row(imageWidth + MARGINS.left + 1, row4Y, [ new Row(imageWidth + MARGINS.left + 1, 0, [
new LabelledText( new LabelledText(
0, 0,
0, 0,
@@ -148,13 +190,28 @@ Hooks.on(
`${actorDetails?.starsign?.value}` `${actorDetails?.starsign?.value}`
), ),
]), ]),
new Row(0, row5Y, [ new Row(0, 0, [
new LabelledText(0, 0, 'CHARAbbrev.WS', `${actorCharacs?.ws?.value}`), new LabelledText(
new LabelledText(0, 0, 'CHARAbbrev.BS', `${actorCharacs?.bs?.value}`), 0,
0,
'CHARAbbrev.WS',
`${actorCharacs?.ws?.value}`
),
new LabelledText(
0,
0,
'CHARAbbrev.BS',
`${actorCharacs?.bs?.value}`
),
new LabelledText(0, 0, 'CHARAbbrev.S', `${actorCharacs?.s?.value}`), new LabelledText(0, 0, 'CHARAbbrev.S', `${actorCharacs?.s?.value}`),
new LabelledText(0, 0, 'CHARAbbrev.T', `${actorCharacs?.t?.value}`), new LabelledText(0, 0, 'CHARAbbrev.T', `${actorCharacs?.t?.value}`),
new LabelledText(0, 0, 'CHARAbbrev.I', `${actorCharacs?.i?.value}`), new LabelledText(0, 0, 'CHARAbbrev.I', `${actorCharacs?.i?.value}`),
new LabelledText(0, 0, 'CHARAbbrev.Ag', `${actorCharacs?.ag?.value}`), new LabelledText(
0,
0,
'CHARAbbrev.Ag',
`${actorCharacs?.ag?.value}`
),
new LabelledText( new LabelledText(
0, 0,
0, 0,
@@ -167,7 +224,12 @@ Hooks.on(
'CHARAbbrev.Int', 'CHARAbbrev.Int',
`${actorCharacs?.int?.value}` `${actorCharacs?.int?.value}`
), ),
new LabelledText(0, 0, 'CHARAbbrev.WP', `${actorCharacs?.wp?.value}`), new LabelledText(
0,
0,
'CHARAbbrev.WP',
`${actorCharacs?.wp?.value}`
),
new LabelledText( new LabelledText(
0, 0,
0, 0,
@@ -175,7 +237,7 @@ Hooks.on(
`${actorCharacs?.fel?.value}` `${actorCharacs?.fel?.value}`
), ),
]), ]),
new Row(0, row6Y, [ new Row(0, 0, [
new LabelledText(0, 0, 'Move', `${actorDetails?.move?.value}`), new LabelledText(0, 0, 'Move', `${actorDetails?.move?.value}`),
new LabelledText(0, 0, 'Walk', `${actorDetails?.move?.walk}`), new LabelledText(0, 0, 'Walk', `${actorDetails?.move?.walk}`),
new LabelledText(0, 0, 'Run', `${actorDetails?.move?.run}`), new LabelledText(0, 0, 'Run', `${actorDetails?.move?.run}`),
@@ -195,12 +257,25 @@ Hooks.on(
`${actorStatus?.wounds?.value}/${actorStatus?.wounds?.max}` `${actorStatus?.wounds?.value}/${actorStatus?.wounds?.max}`
), ),
]), ]),
new Text(0, row7Y, 'Skills'), new Separator(0, 0),
new Text(0, 0, 'Skills'),
skills, skills,
new Text(0, row9Y, 'Talents'), new Separator(0, 0),
new Text(0, 0, 'Talents'),
talents, talents,
new Text(0, row11Y, 'Traits'), new Separator(0, 0),
new Text(0, 0, 'Traits'),
traits, traits,
new Separator(0, 0),
new Text(0, 0, 'SHEET.MeleeWeaponHeader'),
weaponsMelee,
new Separator(0, 0),
new Text(0, 0, 'SHEET.RangedWeaponHeader'),
weaponsRanged,
new Separator(0, 0),
new Text(0, 0, 'Ammunition'),
ammunitions,
]),
]); ]);
docBuilder.doc.save(`${app.actor.name}.pdf`); docBuilder.doc.save(`${app.actor.name}.pdf`);
}); });

View File

@@ -1,18 +1,61 @@
import jsPDF, { jsPDFOptions } from 'jspdf'; import jsPDF, { jsPDFOptions } from 'jspdf';
import { AbstractElement } from './elements/abstract-element'; import { AbstractElement } from './elements/abstract-element';
import { MARGINS } from './constants';
export class PdfBuilder { export class PdfBuilder {
public doc: jsPDF; public doc: jsPDF;
constructor(options: jsPDFOptions) { constructor(options: jsPDFOptions) {
this.doc = new jsPDF(options); this.doc = new jsPDF(options);
this.doc.advancedAPI();
console.dir(this.doc.internal);
} }
public build(elements: AbstractElement[]) { public build(elements: AbstractElement[]) {
const finalElements: AbstractElement[] = [];
for (const element of elements) { for (const element of elements) {
element.prepareRender(this.doc);
finalElements.push(...element.getElements());
}
finalElements.sort((a, b) => {
return a.y - b.y;
});
const pageHeight = this.doc.internal.pageSize.height;
const yMax = pageHeight - MARGINS.bottom;
const pages: AbstractElement[][] = [];
for (const element of finalElements) {
let indexPage = 0;
let currentY = element.y;
const height = element.getCheckNewPageHeight(this.doc);
if (currentY + height > yMax) {
while (currentY + height > yMax) {
indexPage++;
currentY = currentY - yMax + MARGINS.bottom;
if (currentY + height <= yMax) {
if (pages[indexPage] == null) {
pages[indexPage] = [];
}
element.y = currentY;
pages[indexPage].push(element);
}
}
} else {
if (pages[indexPage] == null) {
pages[indexPage] = [];
}
pages[indexPage].push(element);
}
}
let i = 0;
for (const page of pages) {
i++;
for (const element of page) {
element.render(this.doc); element.render(this.doc);
} }
if (i < pages.length) {
this.doc.addPage();
}
}
} }
} }

View File

@@ -2,8 +2,7 @@ const path = require('path');
module.exports = { module.exports = {
entry: './src/main.ts', entry: './src/main.ts',
devtool: 'inline-source-map', devtool: 'eval-source-map',
module: { module: {
rules: [ rules: [
{ {