@datagrok/helm 2.1.11 → 2.1.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,20 +1,12 @@
1
1
  {
2
2
  "name": "@datagrok/helm",
3
3
  "friendlyName": "Helm",
4
- "version": "2.1.11",
4
+ "version": "2.1.12",
5
5
  "author": {
6
6
  "name": "Oleksandra Serhiienko",
7
7
  "email": "oserhiienko@datagrok.ai"
8
8
  },
9
9
  "description": "Provides support for HELM notation (importing, detecting, rendering, conversion).",
10
- "dependencies": {
11
- "@datagrok-libraries/bio": "^5.32.3",
12
- "@datagrok-libraries/utils": "^1.19.1",
13
- "cash-dom": "^8.1.1",
14
- "datagrok-api": "^1.10.2",
15
- "dayjs": "^1.10.6",
16
- "rxjs": "^6.5.5"
17
- },
18
10
  "sources": [
19
11
  "css/helm.css",
20
12
  "https://ajax.googleapis.com/ajax/libs/dojo/1.10.4/dojo/dojo.js",
@@ -22,8 +14,18 @@
22
14
  "helm/JSDraw/Scilligence.JSDraw2.Resources.js",
23
15
  "helm/JSDraw/Pistoia.HELM-uncompressed.js"
24
16
  ],
17
+ "dependencies": {
18
+ "@datagrok-libraries/bio": "^5.32.7",
19
+ "@datagrok-libraries/utils": "^2.5.0",
20
+ "cash-dom": "^8.1.1",
21
+ "datagrok-api": "^1.10.2",
22
+ "dayjs": "^1.10.6",
23
+ "rxjs": "^6.5.5"
24
+ },
25
25
  "devDependencies": {
26
+ "@datagrok/bio": "^2.1.12",
26
27
  "path": "^0.12.7",
28
+ "source-map-loader": "^4.0.1",
27
29
  "ts-loader": "^9.2.6",
28
30
  "typescript": "^4.4.4",
29
31
  "webpack": "^5.59.1",
@@ -35,16 +37,16 @@
35
37
  },
36
38
  "scripts": {
37
39
  "link-all": "npm link datagrok-api @datagrok-libraries/utils @datagrok-libraries/bio",
38
- "debug-helm": "webpack && grok publish ",
40
+ "debug-helm": "webpack && grok publish",
39
41
  "release-helm": "webpack && grok publish --release",
40
42
  "build-helm": "webpack",
41
43
  "build": "webpack",
42
- "debug-local": "grok publish local",
43
- "release-local": "grok publish local --release",
44
- "debug-helm-dev": "grok publish dev",
45
- "release-helm-dev": "grok publish dev --release",
46
- "debug-helm-public": "grok publish public",
47
- "release-helm-public": "grok publish public --release",
44
+ "debug-helm-local": "webpack && grok publish local",
45
+ "release-helm-local": "webpack && grok publish local --release",
46
+ "debug-helm-dev": "webpack && grok publish dev",
47
+ "release-helm-dev": "webpack && grok publish dev --release",
48
+ "debug-helm-public": "webpack && grok publish public",
49
+ "release-helm-public": "webpack && grok publish public --release",
48
50
  "lint": "eslint \"./src/**/*.ts\"",
49
51
  "lint-fix": "eslint \"./src/**/*.ts\" --fix",
50
52
  "test": "grok test",
@@ -2,15 +2,25 @@ import * as grok from 'datagrok-api/grok';
2
2
  import * as ui from 'datagrok-api/ui';
3
3
  import * as DG from 'datagrok-api/dg';
4
4
 
5
+ import {_package} from './package';
5
6
  import {findMonomers, parseHelm, getParts} from './utils';
6
7
  import {printLeftOrCentered} from '@datagrok-libraries/bio/src/utils/cell-renderer';
7
8
  import {errorToConsole} from '@datagrok-libraries/utils/src/to-console';
8
9
 
10
+ const enum tempTAGS {
11
+ helmSumMaxLengthWords = 'helm-sum-maxLengthWords',
12
+ helmMaxLengthWords = 'helm-maxLengthWords',
13
+ }
9
14
  // Global flag is for replaceAll
10
15
  const helmGapStartRe = /\{(\*\.)+/g;
11
16
  const helmGapIntRe = /\.(\*\.)+/g;
12
17
  const helmGapEndRe = /(\.\*)+\}/g;
13
18
 
19
+ type TempType = { [tagName: string]: any };
20
+
21
+ /** Helm cell renderer in case of no missed monomer draws with JSDraw2.Editor (webeditor),
22
+ * in case of missed monomers presented, draws linear sequences aligned in width per monomer.
23
+ */
14
24
  export class HelmCellRenderer extends DG.GridCellRenderer {
15
25
  get name() { return 'helm'; }
16
26
 
@@ -22,8 +32,19 @@ export class HelmCellRenderer extends DG.GridCellRenderer {
22
32
 
23
33
  onMouseMove(gridCell: DG.GridCell, e: MouseEvent): void {
24
34
  try {
25
- const maxLengthWordsSum = gridCell.cell.column.temp['helm-sum-maxLengthWords'];
26
- const maxIndex = Object.values(gridCell.cell.column.temp['helm-maxLengthWords']).length - 1;
35
+ /* Can not do anything without tableColumn containing temp */
36
+ let tableCol: DG.Column | null = null;
37
+ try { tableCol = gridCell.tableColumn; } catch { }
38
+ if (!tableCol) return;
39
+
40
+ const colTemp: TempType[] = tableCol.temp ?? new Array<TempType>(tableCol.length);
41
+ // Exit if no missed monomers (tags are not presented in colTemp)
42
+ if (!colTemp || Object.keys(colTemp).length == 0) return;
43
+
44
+ const maxLengthWordsSum: { [pos: number]: number } = colTemp[tempTAGS.helmSumMaxLengthWords];
45
+ const maxLengthWords: { [pos: number]: number } = colTemp[tempTAGS.helmMaxLengthWords];
46
+
47
+ const maxIndex = Object.values(maxLengthWords).length - 1;
27
48
  const argsX = e.offsetX - gridCell.gridColumn.left + (gridCell.gridColumn.left - gridCell.bounds.x);
28
49
  let left = 0;
29
50
  let right = maxIndex;
@@ -51,14 +72,15 @@ export class HelmCellRenderer extends DG.GridCellRenderer {
51
72
  const subParts: string[] = parseHelm(s);
52
73
  const allParts: string[] = getParts(subParts, s);
53
74
  const tooltipMessage: HTMLElement[] = [];
54
- for (let i = 0; i < allParts.length; ++i) {
55
- if (monomers.has(allParts[i])) {
56
- tooltipMessage[i] = ui.divV([
57
- ui.divText(`Monomer '${allParts[i]}' not found.`),
75
+ for (let partI = 0; partI < allParts.length; ++partI) {
76
+ if (monomers.has(allParts[partI])) {
77
+ tooltipMessage[partI] = ui.divV([
78
+ ui.divText(`Monomer ${allParts[partI]} not found.`),
58
79
  ui.divText('Open the Context Panel, then expand Manage Libraries')
59
80
  ]);
60
81
  }
61
82
  }
83
+
62
84
  (((tooltipMessage[left]?.childNodes.length ?? 0) > 0)) ?
63
85
  ui.tooltip.show(ui.div(tooltipMessage[left]), e.x + 16, e.y + 16) :
64
86
  ui.tooltip.hide();
@@ -71,57 +93,71 @@ export class HelmCellRenderer extends DG.GridCellRenderer {
71
93
  render(g: CanvasRenderingContext2D, x: number, y: number, w: number, h: number,
72
94
  gridCell: DG.GridCell, cellStyle: DG.GridCellStyle
73
95
  ) {
74
- const grid = gridCell.gridRow !== -1 ? gridCell.grid : undefined;
75
- const undefinedColor = 'rgb(100,100,100)';
76
- const grayColor = '#808080';
77
-
78
- const s: string = !gridCell.cell.value ? '' : gridCell.cell.value
79
- .replaceAll(helmGapStartRe, '{').replaceAll(helmGapIntRe, '.').replaceAll(helmGapEndRe, '}');
80
- const monomers = findMonomers(s);
81
- const subParts: string[] = parseHelm(s);
82
- if (monomers.size == 0 && grid) {
83
- const host = ui.div([], {style: {width: `${w}px`, height: `${h}px`}});
84
- host.setAttribute('dataformat', 'helm');
85
- host.setAttribute('data', s);
86
- gridCell.element = host;
87
- //@ts-ignore
88
- const canvas = new JSDraw2.Editor(host, {width: w, height: h, skin: 'w8', viewonly: true});
89
- return;
90
- } else {
91
- if (!grid) {
92
- const r = window.devicePixelRatio;
93
- h = 28;
94
- g.canvas.height = h * r;
95
- g.canvas.style.height = `${h}px`;
96
- }
97
- w = grid ? Math.min(grid.canvas.width - x, w) : g.canvas.width - x;
98
- g.save();
99
- g.beginPath();
100
- g.rect(x, y, w, h);
101
- g.clip();
102
- g.font = '12px monospace';
103
- g.textBaseline = 'top';
104
- let x1 = x;
105
- const maxLengthWords: any = {};
106
- const maxLengthWordSum: any = {};
107
- const allParts: string[] = getParts(subParts, s);
108
- for (let i = 0; i < allParts.length; ++i) {
109
- maxLengthWords[i] = allParts[i].length * 7;
110
- const color = monomers.has(allParts[i]) ? 'red' : grayColor;
111
- g.fillStyle = undefinedColor;
112
- x1 = printLeftOrCentered(x1, y, w, h, g, allParts[i], color, 0, true, 1.0);
96
+ g.save();
97
+ try {
98
+ /* Can not do anything without tableColumn containing temp */
99
+ let tableCol: DG.Column | null = null;
100
+ try { tableCol = gridCell.tableColumn; } catch { }
101
+ if (!tableCol) return;
102
+
103
+ const grid = gridCell.gridRow !== -1 ? gridCell.grid : undefined;
104
+ const undefinedColor = 'rgb(100,100,100)';
105
+ const grayColor = '#808080';
106
+
107
+ const missedMonomers = findMonomers(gridCell.cell.value);
108
+ const s: string = gridCell.cell.value ?? '';
109
+ const subParts: string[] = parseHelm(s);
110
+
111
+ if (missedMonomers.size == 0) {
112
+ const host = ui.div([], {style: {width: `${w}px`, height: `${h}px`}});
113
+ host.setAttribute('dataformat', 'helm');
114
+ host.setAttribute('data', gridCell.cell.value);
115
+ gridCell.element = host;
116
+ //@ts-ignore
117
+ const canvas = new JSDraw2.Editor(host, {width: w, height: h, skin: 'w8', viewonly: true});
118
+ return;
113
119
  }
114
120
 
115
- maxLengthWordSum[0] = maxLengthWords[0];
116
- for (let i = 1; i < allParts.length; i++)
117
- maxLengthWordSum[i] = maxLengthWordSum[i - 1] + maxLengthWords[i];
121
+ if (missedMonomers.size > 0) {
122
+ if (!grid) {
123
+ const r = window.devicePixelRatio;
124
+ h = 28;
125
+ g.canvas.height = h*r;
126
+ g.canvas.style.height = `${h}px`;
127
+ }
128
+ const maxLengthWords: number[] = tableCol.temp[tempTAGS.helmMaxLengthWords] ?? [];
129
+ if (subParts.length > maxLengthWords.length)
130
+ maxLengthWords.push(...(new Array<number>(subParts.length - maxLengthWords.length).fill(-1)));
118
131
 
119
- gridCell.cell.column.temp = {
120
- 'helm-sum-maxLengthWords': maxLengthWordSum,
121
- 'helm-maxLengthWords': maxLengthWords
122
- };
132
+ w = grid ? Math.min(grid.canvas.width - x, w) : g.canvas.width - x;
133
+ g.save();
134
+ g.beginPath();
135
+ g.rect(x, y, w, h);
136
+ g.clip();
137
+ g.font = '12px monospace';
138
+ g.textBaseline = 'top';
139
+ let x1 = x;
140
+ const allParts: string[] = getParts(subParts, s);
141
+ for (let i = 0; i < allParts.length; ++i) {
142
+ maxLengthWords[i] = Math.max(maxLengthWords[i], allParts[i].length * 7); /* What is 7, width of char ? */
143
+ const color = missedMonomers.has(allParts[i]) ? 'red' : grayColor;
144
+ g.fillStyle = undefinedColor;
145
+ x1 = printLeftOrCentered(x1, y, w, h, g, allParts[i], color, 0, true, 1.0);
146
+ }
147
+
148
+ const maxLengthWordSum: number[] = new Array<number>(maxLengthWords.length);
149
+ maxLengthWordSum[0] = maxLengthWords[0];
150
+ for (let partI = 1; partI < allParts.length; partI++)
151
+ maxLengthWordSum[partI] = maxLengthWordSum[partI - 1] + maxLengthWords[partI];
152
+
153
+ tableCol.temp = {
154
+ [tempTAGS.helmSumMaxLengthWords]: maxLengthWordSum,
155
+ [tempTAGS.helmMaxLengthWords]: maxLengthWords
156
+ };
157
+ return;
158
+ }
159
+ } finally {
123
160
  g.restore();
124
- return;
125
161
  }
126
162
  }
127
163
  }
@@ -1,7 +1,9 @@
1
1
  import * as DG from 'datagrok-api/dg';
2
2
  import * as grok from 'datagrok-api/grok';
3
3
  import {runTests, tests, TestContext} from '@datagrok-libraries/utils/src/test';
4
+
4
5
  import './tests/helm-tests.ts';
6
+ import './tests/findMonomers-tests';
5
7
 
6
8
  export const _package = new DG.Package();
7
9
  export {tests};
package/src/package.ts CHANGED
@@ -20,27 +20,35 @@ export async function initHelm(): Promise<void> {
20
20
  return Promise.all([new Promise((resolve, reject) => {
21
21
  // @ts-ignore
22
22
  dojo.ready(function() { resolve(null); });
23
- }), await grok.functions.call('Bio:getBioLib')]).then(([_, lib]: [void, IMonomerLib]) => {
24
- monomerLib = lib;
25
- rewriteLibraries(); // initHelm()
26
- monomerLib.onChanged.subscribe((_) => {
27
- try {
28
- rewriteLibraries(); // initHelm()
29
-
30
- const polymerTypeList: string[] = monomerLib.getPolymerTypes();
31
- const msgStr: string = 'Monomer lib updated:<br />' + (
32
- polymerTypeList.length == 0 ? 'empty' : polymerTypeList.map((polymerType) => {
33
- return `${polymerType} ${monomerLib.getMonomerSymbolsByType(polymerType).length}`;
34
- }).join('<br />'));
35
-
36
- grok.shell.info(msgStr);
37
- } catch (err: any) {
38
- const errMsg = errorToConsole(err);
39
- console.error('Helm: initHelm monomerLib.onChanged() error:\n' + errMsg);
40
- // throw err; // Prevent disabling event handler
41
- }
23
+ }), grok.functions.call('Bio:getBioLib')])
24
+ .then(([_, lib]: [void, IMonomerLib]) => {
25
+ monomerLib = lib;
26
+ rewriteLibraries(); // initHelm()
27
+ monomerLib.onChanged.subscribe((_) => {
28
+ try {
29
+ rewriteLibraries(); // initHelm()
30
+
31
+ const monTypeList: string[] = monomerLib.getPolymerTypes();
32
+ const msgStr: string = 'Monomer lib updated:<br />' + (
33
+ monTypeList.length == 0 ? 'empty' : monTypeList.map((monType) => {
34
+ return `${monType} ${monomerLib.getMonomerSymbolsByType(monType).length}`;
35
+ }).join('<br />'));
36
+
37
+ grok.shell.info(msgStr);
38
+ } catch (err: any) {
39
+ const errMsg = errorToConsole(err);
40
+ console.error('Helm: initHelm monomerLib.onChanged() error:\n' + errMsg);
41
+ // throw err; // Prevent disabling event handler
42
+ }
43
+ });
44
+ })
45
+ .catch((err: any) => {
46
+ const errMsg: string = err instanceof Error ? err.message : !!err ? err.toString() : 'Exception \'undefined\'';
47
+ grok.shell.error(`Package \'Helm\' init initHelm() error: ${errMsg}`);
48
+ const errRes = new Error(errMsg);
49
+ errRes.stack = err.stack;
50
+ throw errRes;
42
51
  });
43
- });
44
52
  }
45
53
 
46
54
  function rewriteLibraries() {
@@ -97,12 +105,10 @@ export function helmCellRenderer(): HelmCellRenderer {
97
105
  function checkMonomersAndOpenWebEditor(cell?: DG.Cell, value?: string, units?: string) {
98
106
  const cellValue = typeof units === 'undefined' ? cell.value : value;
99
107
  const monomers = findMonomers(cellValue);
100
- if (monomers.size == 0) {
101
- webEditor(cell, value, units);
102
- } else {
108
+ if (monomers.size == 0) { webEditor(cell, value, units); } else {
103
109
  grok.shell.warning(`Monomers ${Array.from(monomers).join(', ')} are absent! <br/>` +
104
- `Please, upload the monomer library! <br/>` +
105
- `<a href="https://datagrok.ai/help/domains/bio/macromolecules" target="_blank">Learn more</a>`);
110
+ `Please, upload the monomer library! <br/>` +
111
+ `<a href="https://datagrok.ai/help/domains/bio/macromolecules" target="_blank">Learn more</a>`);
106
112
  }
107
113
  }
108
114
 
@@ -0,0 +1,56 @@
1
+ import * as grok from 'datagrok-api/grok';
2
+ import * as ui from 'datagrok-api/ui';
3
+ import * as DG from 'datagrok-api/dg';
4
+
5
+ import {_package} from '../package-test';
6
+ import {after, before, category, delay, expect, expectObject, test} from '@datagrok-libraries/utils/src/test';
7
+ import {findMonomers} from '../utils';
8
+ import {getMonomerLibHelper, IMonomerLibHelper} from '@datagrok-libraries/bio/src/monomer-works/monomer-utils';
9
+
10
+ const LIB_STORAGE_NAME = 'Libraries';
11
+ export const LIB_DEFAULT: { [fileName: string]: string } = {'HELMCoreLibrary.json': 'HELMCoreLibrary.json'};
12
+
13
+
14
+ /** Tests with default monomer library */
15
+ category('findMonomers', () => {
16
+
17
+ let monomerLibHelper: IMonomerLibHelper;
18
+ /** Backup actual user's monomer libraries settings */
19
+ let userLibrariesSettings: any = null;
20
+
21
+ before(async () => {
22
+ monomerLibHelper = await getMonomerLibHelper();
23
+ userLibrariesSettings = await grok.dapi.userDataStorage.get(LIB_STORAGE_NAME, true);
24
+
25
+ // Tests 'findMonomers' requires default monomer library loaded
26
+ await grok.dapi.userDataStorage.post(LIB_STORAGE_NAME, LIB_DEFAULT, true);
27
+ await monomerLibHelper.loadLibraries(true); // load default libraries
28
+ });
29
+
30
+ after(async () => {
31
+ await grok.dapi.userDataStorage.put(LIB_STORAGE_NAME, userLibrariesSettings, true);
32
+ });
33
+
34
+ const tests: { [testName: string]: { test: string, tgt: Set<string> } } = {
35
+ 'withoutMissed': {
36
+ test: 'PEPTIDE1{meI.hHis.Aca.N.T.dE.Thr_PO3H2.Aca.D-Tyr_Et}$$$$',
37
+ tgt: new Set<string>(),
38
+ },
39
+ 'withMissed':
40
+ {
41
+ test: 'PEPTIDE1{meI.missed2.Aca.N.T.dE.Thr_PO3H2.Aca.D-Tyr_Et}$$$$',
42
+ tgt: new Set<string>(['missed2'])
43
+ }
44
+ };
45
+
46
+ for (const [testName, testData] of Object.entries(tests)) {
47
+ test(testName, async () => {
48
+ _testFindMonomers(testData.test, testData.tgt);
49
+ });
50
+ }
51
+
52
+ function _testFindMonomers(testHelmValue: string, tgtMissedSet: Set<string>): void {
53
+ const resMissedSet: Set<string> = findMonomers(testHelmValue);
54
+ expectObject(resMissedSet, tgtMissedSet);
55
+ }
56
+ });
@@ -1,9 +1,11 @@
1
- import {after, before, category, delay, expect, expectArray, test} from '@datagrok-libraries/utils/src/test';
2
- //import {findMonomers, helmToFasta, helmToPeptide, helmToRNA, initHelm} from '../package';
1
+ import * as grok from 'datagrok-api/grok';
2
+ import * as ui from 'datagrok-api/ui';
3
+ import * as DG from 'datagrok-api/dg';
4
+
3
5
  import {_package} from '../package-test';
6
+ import {after, before, category, delay, expect, test, expectArray} from '@datagrok-libraries/utils/src/test';
7
+ //import {findMonomers, helmToFasta, helmToPeptide, helmToRNA, initHelm} from '../package';
4
8
  import {parseHelm} from '../utils';
5
- import * as DG from 'datagrok-api/dg';
6
- import * as grok from 'datagrok-api/grok';
7
9
 
8
10
 
9
11
  category('Helm', () => {
package/src/utils.ts CHANGED
@@ -1,4 +1,14 @@
1
1
  import * as DG from 'datagrok-api/dg';
2
+ import {
3
+ RGROUP_CAP_GROUP_NAME,
4
+ RGROUP_CAP_GROUP_SMILES,
5
+ jsonSdfMonomerLibDict,
6
+ MONOMER_SYMBOL,
7
+ RGROUP_ALTER_ID,
8
+ RGROUPS,
9
+ RGROUP_LABEL,
10
+ SDF_MONOMER_NAME
11
+ } from './constants';
2
12
 
3
13
  export function getParts(subParts: string[], s: string): string[] {
4
14
  const j = 0;
@@ -61,7 +71,29 @@ export function parseHelm(s: string) {
61
71
  }
62
72
  return monomers;
63
73
  }
64
- /* this function returns names of monomers that are NOT in the monomer library */
74
+
75
+ // /** Find monomers missed in Helm monomer library configured and
76
+ // * used in org.helm.webeditor / scil.helm.Monomers / org.helm.webeditor.Monomers .
77
+ // */
78
+ // export function findMonomers(helmString: string) {
79
+ // //@ts-ignore
80
+ // const types: string[] = Object.keys(org.helm.webeditor.monomerTypeList());
81
+ // const monomerNameList: any[] = [];
82
+ // const monomerNameI: number = 0;
83
+ // const weMonomers = org.helm.webeditor.Monomers;
84
+ // for (let typeI = 0; typeI < types.length; typeI++) {
85
+ // //@ts-ignore
86
+ // const ofTypeMonomers: {} = weMonomers.getMonomerSet(types[typeI]) ?? {};
87
+ // Object.keys(ofTypeMonomers).forEach((key) => {
88
+ // const monomer: any = ofTypeMonomers[key];
89
+ // monomerNameList[monomerNameI] = monomer.id;
90
+ // monomerNameI += 1;
91
+ // });
92
+ // }
93
+ // const helmPartList = parseHelm(helmString);
94
+ // return new Set(helmPartList.filter((val) => !monomerNameList.includes(val)));
95
+ // }
96
+
65
97
  export function findMonomers(helmString: string) {
66
98
  //@ts-ignore
67
99
  const types = Object.keys(org.helm.webeditor.monomerTypeList());
package/tsconfig.json CHANGED
@@ -30,7 +30,7 @@
30
30
  // "strictNullChecks": true, /* Enable strict null checks. */
31
31
  // "strictFunctionTypes": true, /* Enable strict checking of function types. */
32
32
  // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
33
- // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
33
+ "strictPropertyInitialization": false, /* Enable strict checking of property initialization in classes. */
34
34
  // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
35
35
  // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
36
36
 
package/webpack.config.js CHANGED
@@ -12,20 +12,14 @@ module.exports = {
12
12
  },
13
13
  },
14
14
  resolve: {
15
- extensions: ['.wasm', '.mjs', '.js', '.json', '.ts', '.tsx'],
15
+ fallback: {'url': false},
16
+ extensions: ['.wasm', '.mjs', '.ts', '.js', '.json', '.tsx'],
16
17
  },
17
18
  module: {
18
19
  rules: [
19
- {
20
- test: /\.ts(x?)$/,
21
- use: 'ts-loader',
22
- exclude: /node_modules/,
23
- },
24
- {
25
- test: /\.css$/,
26
- use: ['style-loader', 'css-loader'],
27
- exclude: /node_modules/,
28
- },
20
+ {test: /\.js$/, enforce: 'pre', use: ['source-map-loader']},
21
+ {test: /\.ts(x?)$/, use: 'ts-loader', exclude: /node_modules/},
22
+ {test: /\.css$/, use: ['style-loader', 'css-loader'], exclude: /node_modules/},
29
23
  ],
30
24
  },
31
25
  devtool: 'source-map',