@adobe/helix-rum-enhancer 2.14.0 → 2.14.1

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.
@@ -0,0 +1 @@
1
+ npm test
package/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ ## [2.14.1](https://github.com/adobe/helix-rum-enhancer/compare/v2.14.0...v2.14.1) (2024-06-11)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * re-release after switching build infra ([95a8000](https://github.com/adobe/helix-rum-enhancer/commit/95a800050557cbdec7f70fac1f4784b512ba27de))
7
+
1
8
  # [2.14.0](https://github.com/adobe/helix-rum-enhancer/compare/v2.13.0...v2.14.0) (2024-06-11)
2
9
 
3
10
 
@@ -0,0 +1,13 @@
1
+ /*
2
+ * Copyright 2024 Adobe. All rights reserved.
3
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
+ * you may not use this file except in compliance with the License. You may obtain a copy
5
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+ *
7
+ * Unless required by applicable law or agreed to in writing, software distributed under
8
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
+ * OF ANY KIND, either express or implied. See the License for the specific language
10
+ * governing permissions and limitations under the License.
11
+ */
12
+ export const KNOWN_PROPERTIES = ['weight', 'id', 'referer', 'checkpoint', 't', 'source', 'target', 'cwv', 'CLS', 'FID', 'LCP', 'INP', 'TTFB'];
13
+ export const DEFAULT_TRACKING_EVENTS = ['click', 'cwv', 'form', 'enterleave', 'viewblock', 'viewmedia', 'loadresource', 'utm'];
package/modules/dom.js ADDED
@@ -0,0 +1,66 @@
1
+ /*
2
+ * Copyright 2024 Adobe. All rights reserved.
3
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
+ * you may not use this file except in compliance with the License. You may obtain a copy
5
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+ *
7
+ * Unless required by applicable law or agreed to in writing, software distributed under
8
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
+ * OF ANY KIND, either express or implied. See the License for the specific language
10
+ * governing permissions and limitations under the License.
11
+ */
12
+ export const getTargetValue = (element) => element.getAttribute('data-rum-target') || element.getAttribute('href')
13
+ || element.currentSrc || element.getAttribute('src') || element.dataset.action || element.action;
14
+
15
+ export const targetSelector = (element) => {
16
+ try {
17
+ if (!element) return undefined;
18
+ let value = getTargetValue(element);
19
+ if (!value && element.tagName !== 'A' && element.closest('a')) {
20
+ value = getTargetValue(element.closest('a'));
21
+ }
22
+ if (value && !value.startsWith('https://')) {
23
+ // resolve relative links
24
+ value = new URL(value, window.location).href;
25
+ }
26
+ return value;
27
+ /* c8 ignore next 3 */
28
+ } catch (error) {
29
+ return null;
30
+ }
31
+ };
32
+
33
+ export const sourceSelector = (element) => {
34
+ try {
35
+ if (!element || element === document.body || element === document.documentElement) {
36
+ return undefined;
37
+ }
38
+ if (element.getAttribute('data-rum-source')) {
39
+ return element.getAttribute('data-rum-source');
40
+ }
41
+ const form = element.closest('form');
42
+ let formElementSelector = '';
43
+ if (form && Array.from(form.elements).includes(element)) {
44
+ formElementSelector = element.tagName === 'INPUT' ? `form input[type='${element.getAttribute('type')}']` : `form ${element.tagName.toLowerCase()}`;
45
+ }
46
+
47
+ const blockName = element.closest('.block') ? element.closest('.block').getAttribute('data-block-name') : '';
48
+ if (element.id || formElementSelector) {
49
+ const id = element.id ? `#${element.id}` : '';
50
+ return blockName ? `.${blockName} ${formElementSelector}${id}` : `${formElementSelector}${id}`;
51
+ }
52
+
53
+ if (element.getAttribute('data-block-name')) {
54
+ return `.${element.getAttribute('data-block-name')}`;
55
+ }
56
+
57
+ if (Array.from(element.classList).some((className) => className.match(/button|cta/))) {
58
+ return blockName ? `.${blockName} .button` : '.button';
59
+ }
60
+
61
+ return sourceSelector(element.parentElement);
62
+ /* c8 ignore next 3 */
63
+ } catch (error) {
64
+ return null;
65
+ }
66
+ };
@@ -0,0 +1,21 @@
1
+ /*
2
+ * Copyright 2024 Adobe. All rights reserved.
3
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
+ * you may not use this file except in compliance with the License. You may obtain a copy
5
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+ *
7
+ * Unless required by applicable law or agreed to in writing, software distributed under
8
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
+ * OF ANY KIND, either express or implied. See the License for the specific language
10
+ * governing permissions and limitations under the License.
11
+ */
12
+ export const fflags = {
13
+ has: (flag) => fflags[flag].indexOf(Array.from(window.origin)
14
+ .map((a) => a.charCodeAt(0))
15
+ .reduce((a, b) => a + b, 1) % 1371) !== -1,
16
+ enabled: (flag, callback) => fflags.has(flag) && callback(),
17
+ disabled: (flag, callback) => !fflags.has(flag) && callback(),
18
+ onetrust: [543, 770, 1136],
19
+ ads: [1139, 543],
20
+ email: [1139, 543],
21
+ };
@@ -0,0 +1,345 @@
1
+ /*
2
+ * Copyright 2024 Adobe. All rights reserved.
3
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
+ * you may not use this file except in compliance with the License. You may obtain a copy
5
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+ *
7
+ * Unless required by applicable law or agreed to in writing, software distributed under
8
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
+ * OF ANY KIND, either express or implied. See the License for the specific language
10
+ * governing permissions and limitations under the License.
11
+ */
12
+ /* eslint-env browser */
13
+
14
+ import { KNOWN_PROPERTIES, DEFAULT_TRACKING_EVENTS } from './defaults.js';
15
+ import { fflags } from './fflags.js';
16
+ import { urlSanitizers } from './utils.js';
17
+ import { targetSelector, sourceSelector } from './dom.js';
18
+
19
+ const { sampleRUM, queue, isSelected } = (window.hlx && window.hlx.rum) ? window.hlx.rum : {};
20
+
21
+ const formSubmitListener = (e) => sampleRUM('formsubmit', { target: targetSelector(e.target), source: sourceSelector(e.target) });
22
+ // eslint-disable-next-line no-use-before-define
23
+ const mutationObserver = window.MutationObserver ? new MutationObserver(mutationsCallback) : null;
24
+
25
+ // eslint-disable-next-line no-unused-vars
26
+ function optedIn(checkpoint, data) {
27
+ // TODO: check config service to know if
28
+ return true;
29
+ }
30
+ // Gets configured collection from the config service for the current domain
31
+ function getCollectionConfig() {
32
+ // eslint-disable-next-line max-len
33
+ fflags.enabled('onetrust', () => DEFAULT_TRACKING_EVENTS.push('consent'));
34
+ fflags.enabled('ads', () => DEFAULT_TRACKING_EVENTS.push('paid'));
35
+ fflags.enabled('email', () => DEFAULT_TRACKING_EVENTS.push('email'));
36
+ return DEFAULT_TRACKING_EVENTS;
37
+ }
38
+
39
+ function trackCheckpoint(checkpoint, data, t) {
40
+ const { weight, id } = window.hlx.rum;
41
+ if (optedIn(checkpoint, data) && isSelected) {
42
+ const sendPing = (pdata = data) => {
43
+ // eslint-disable-next-line object-curly-newline, max-len
44
+ const body = JSON.stringify({ weight, id, referer: urlSanitizers[window.hlx.RUM_MASK_URL || 'path'](), checkpoint, t, ...data }, KNOWN_PROPERTIES);
45
+ const { href: url, origin } = new URL(`.rum/${weight}`, sampleRUM.collectBaseURL || sampleRUM.baseURL);
46
+ if (window.location.origin === origin) {
47
+ const headers = { type: 'application/json' };
48
+ navigator.sendBeacon(url, new Blob([body], headers));
49
+ } else {
50
+ navigator.sendBeacon(url, body);
51
+ }
52
+ // eslint-disable-next-line no-console
53
+ console.debug(`ping:${checkpoint}`, pdata);
54
+ };
55
+ sendPing(data);
56
+ }
57
+ }
58
+
59
+ function processQueue() {
60
+ while (queue.length) {
61
+ const ck = queue.shift();
62
+ trackCheckpoint(...ck);
63
+ }
64
+ }
65
+
66
+ function addCWVTracking() {
67
+ setTimeout(() => {
68
+ try {
69
+ const cwvScript = new URL('.rum/web-vitals/dist/web-vitals.iife.js', sampleRUM.baseURL).href;
70
+ if (document.querySelector(`script[src="${cwvScript}"]`)) {
71
+ // web vitals script has been loaded already
72
+ return;
73
+ }
74
+ const script = document.createElement('script');
75
+ script.src = cwvScript;
76
+ script.onload = () => {
77
+ const storeCWV = (measurement) => {
78
+ const data = { cwv: {} };
79
+ data.cwv[measurement.name] = measurement.value;
80
+ if (measurement.name === 'LCP' && measurement.entries.length > 0) {
81
+ const { element } = measurement.entries.pop();
82
+ data.target = targetSelector(element);
83
+ data.source = sourceSelector(element) || (element && element.outerHTML.slice(0, 30));
84
+ }
85
+ sampleRUM('cwv', data);
86
+ };
87
+
88
+ const featureToggle = () => window.location.hostname === 'blog.adobe.com';
89
+ const isEager = (metric) => ['CLS', 'LCP'].includes(metric);
90
+
91
+ // When loading `web-vitals` using a classic script, all the public
92
+ // methods can be found on the `webVitals` global namespace.
93
+ ['FID', 'INP', 'TTFB', 'CLS', 'LCP'].forEach((metric) => {
94
+ const metricFn = window.webVitals[`on${metric}`];
95
+ if (typeof metricFn === 'function') {
96
+ const opts = isEager(metric) ? { reportAllChanges: featureToggle() } : undefined;
97
+ metricFn(storeCWV, opts);
98
+ }
99
+ });
100
+ };
101
+ document.head.appendChild(script);
102
+ } catch (error) {
103
+ // something went wrong
104
+ }
105
+ }, 2000); // wait for delayed
106
+ }
107
+
108
+ function addEnterLeaveTracking() {
109
+ // enter checkpoint when referrer is not the current page url
110
+ const navigate = (source, type) => {
111
+ const payload = { source, target: document.visibilityState };
112
+ // reload: same page, navigate: same origin, enter: everything else
113
+ if (type === 'reload' || source === window.location.href) {
114
+ sampleRUM('reload', payload);
115
+ } else if (type && type !== 'navigate') {
116
+ sampleRUM(type, payload); // back, forward, prerender, etc.
117
+ } else if (source && window.location.origin === new URL(source).origin) {
118
+ sampleRUM('navigate', payload); // internal navigation
119
+ } else {
120
+ sampleRUM('enter', payload); // enter site
121
+ }
122
+ };
123
+
124
+ new PerformanceObserver((list) => list
125
+ .getEntries().map((entry) => navigate(document.referrer, entry.type)))
126
+ .observe({ type: 'navigation', buffered: true });
127
+
128
+ const leave = ((event) => {
129
+ try {
130
+ if (leave.left || (event.type === 'visibilitychange' && document.visibilityState !== 'hidden')) {
131
+ return;
132
+ }
133
+ leave.left = true;
134
+ sampleRUM('leave');
135
+ } catch (error) {
136
+ // something went wrong
137
+ }
138
+ });
139
+ window.addEventListener('visibilitychange', ((event) => leave(event)));
140
+ window.addEventListener('pagehide', ((event) => leave(event)));
141
+ }
142
+
143
+ function addLoadResourceTracking() {
144
+ const observer = new PerformanceObserver((list) => {
145
+ try {
146
+ list.getEntries()
147
+ .filter((entry) => !entry.responseStatus || entry.responseStatus < 400)
148
+ .filter((entry) => window.location.hostname === new URL(entry.name).hostname)
149
+ .filter((entry) => new URL(entry.name).pathname.match('.*(\\.plain\\.html|\\.json)$'))
150
+ .forEach((entry) => {
151
+ sampleRUM('loadresource', { source: entry.name, target: Math.round(entry.duration) });
152
+ });
153
+ list.getEntries()
154
+ .filter((entry) => entry.responseStatus === 404)
155
+ .forEach((entry) => {
156
+ sampleRUM('missingresource', { source: entry.name, target: entry.hostname });
157
+ });
158
+ } catch (error) {
159
+ // something went wrong
160
+ }
161
+ });
162
+ observer.observe({ type: 'resource', buffered: true });
163
+ }
164
+
165
+ function activateBlocksMutationObserver() {
166
+ if (!mutationObserver || mutationObserver.active) {
167
+ return;
168
+ }
169
+ mutationObserver.active = true;
170
+ mutationObserver.observe(
171
+ document.body,
172
+ // eslint-disable-next-line object-curly-newline
173
+ { subtree: true, attributes: true, attributeFilter: ['data-block-status'] },
174
+ );
175
+ }
176
+
177
+ function getIntersectionObsever(checkpoint) {
178
+ if (!window.IntersectionObserver) {
179
+ return null;
180
+ }
181
+ activateBlocksMutationObserver();
182
+ const observer = new IntersectionObserver((entries) => {
183
+ try {
184
+ entries
185
+ .filter((entry) => entry.isIntersecting)
186
+ .forEach((entry) => {
187
+ observer.unobserve(entry.target); // observe only once
188
+ const target = targetSelector(entry.target);
189
+ const source = sourceSelector(entry.target);
190
+ sampleRUM(checkpoint, { target, source });
191
+ });
192
+ } catch (error) {
193
+ // something went wrong
194
+ }
195
+ }, { threshold: 0.25 });
196
+ return observer;
197
+ }
198
+ function addViewBlockTracking(element) {
199
+ const blockobserver = getIntersectionObsever('viewblock');
200
+ if (blockobserver) {
201
+ const blocks = element.getAttribute('data-block-status') ? [element] : element.querySelectorAll('div[data-block-status="loaded"]');
202
+ blocks.forEach((b) => blockobserver.observe(b));
203
+ }
204
+ }
205
+
206
+ function addViewMediaTracking(parent) {
207
+ const mediaobserver = getIntersectionObsever('viewmedia');
208
+ if (mediaobserver) {
209
+ parent.querySelectorAll('img, video, audio, iframe').forEach((m) => {
210
+ if (!m.closest('div .block') || m.closest('div[data-block-status="loaded"]')) {
211
+ mediaobserver.observe(m);
212
+ }
213
+ });
214
+ }
215
+ }
216
+
217
+ function addUTMParametersTracking() {
218
+ const usp = new URLSearchParams(window.location.search);
219
+ [...usp.entries()]
220
+ .filter(([key]) => key.startsWith('utm_'))
221
+ // exclude keys that may leak PII
222
+ .filter(([key]) => key !== 'utm_id')
223
+ .filter(([key]) => key !== 'utm_term')
224
+ .forEach(([source, target]) => sampleRUM('utm', { source, target }));
225
+ }
226
+
227
+ function addAdsParametersTracking() {
228
+ const networks = {
229
+ google: /gclid|gclsrc|wbraid|gbraid/,
230
+ doubleclick: /dclid/,
231
+ microsoft: /msclkid/,
232
+ facebook: /fb(cl|ad_|pxl_)id/,
233
+ twitter: /tw(clid|src|term)/,
234
+ linkedin: /li_fat_id/,
235
+ pinterest: /epik/,
236
+ tiktok: /ttclid/,
237
+ };
238
+ const params = Array.from(new URLSearchParams(window.location.search).keys());
239
+ Object.entries(networks).forEach(([network, regex]) => {
240
+ params.filter((param) => regex.test(param)).forEach((param) => sampleRUM('paid', { source: network, target: param }));
241
+ });
242
+ }
243
+
244
+ function addEmailParameterTracking() {
245
+ const networks = {
246
+ mailchimp: /mc_(c|e)id/,
247
+ marketo: /mkt_tok/,
248
+
249
+ };
250
+ const params = Array.from(new URLSearchParams(window.location.search).keys());
251
+ Object.entries(networks).forEach(([network, regex]) => {
252
+ params.filter((param) => regex.test(param)).forEach((param) => sampleRUM('email', { source: network, target: param }));
253
+ });
254
+ }
255
+
256
+ function addFormTracking(parent) {
257
+ activateBlocksMutationObserver();
258
+ parent.querySelectorAll('form').forEach((form) => {
259
+ form.removeEventListener('submit', formSubmitListener); // listen only once
260
+ form.addEventListener('submit', formSubmitListener);
261
+ });
262
+ }
263
+
264
+ function addCookieConsentTracking() {
265
+ const cmpCookie = document.cookie.split(';')
266
+ .map((c) => c.trim())
267
+ .find((cookie) => cookie.startsWith('OptanonAlertBoxClosed='));
268
+
269
+ if (cmpCookie) {
270
+ sampleRUM('consent', { source: 'onetrust', target: 'hidden' });
271
+ return;
272
+ }
273
+
274
+ let consentMutationObserver;
275
+ const trackShowConsent = () => {
276
+ if (document.querySelector('body > div#onetrust-consent-sdk')) {
277
+ sampleRUM('consent', { source: 'onetrust', target: 'show' });
278
+ if (consentMutationObserver) {
279
+ consentMutationObserver.disconnect();
280
+ }
281
+ return true;
282
+ }
283
+ return false;
284
+ };
285
+
286
+ if (!trackShowConsent()) {
287
+ // eslint-disable-next-line max-len
288
+ consentMutationObserver = window.MutationObserver ? new MutationObserver(trackShowConsent) : null;
289
+ if (consentMutationObserver) {
290
+ consentMutationObserver.observe(
291
+ document.body,
292
+ // eslint-disable-next-line object-curly-newline
293
+ { attributes: false, childList: true, subtree: false },
294
+ );
295
+ }
296
+ }
297
+ }
298
+
299
+ const addObserver = (ck, fn, block) => getCollectionConfig().includes(ck) && fn(block);
300
+ function mutationsCallback(mutations) {
301
+ mutations.filter((m) => m.type === 'attributes' && m.attributeName === 'data-block-status')
302
+ .filter((m) => m.target.dataset.blockStatus === 'loaded')
303
+ .forEach((m) => {
304
+ addObserver('form', addFormTracking, m.target);
305
+ addObserver('viewblock', addViewBlockTracking, m.target);
306
+ addObserver('viewmedia', addViewMediaTracking, m.target);
307
+ });
308
+ }
309
+
310
+ function addTrackingFromConfig() {
311
+ const trackingFunctions = {
312
+ click: () => {
313
+ document.addEventListener('click', (event) => {
314
+ sampleRUM('click', { target: targetSelector(event.target), source: sourceSelector(event.target) });
315
+ });
316
+ },
317
+ cwv: () => addCWVTracking(),
318
+ form: () => addFormTracking(window.document.body),
319
+ enterleave: () => addEnterLeaveTracking(),
320
+ loadresource: () => addLoadResourceTracking(),
321
+ utm: () => addUTMParametersTracking(),
322
+ viewblock: () => addViewBlockTracking(window.document.body),
323
+ viewmedia: () => addViewMediaTracking(window.document.body),
324
+ consent: () => addCookieConsentTracking(),
325
+ paid: () => addAdsParametersTracking(),
326
+ email: () => addEmailParameterTracking(),
327
+ };
328
+
329
+ getCollectionConfig().filter((ck) => trackingFunctions[ck])
330
+ .forEach((ck) => trackingFunctions[ck]());
331
+ }
332
+
333
+ function initEnhancer() {
334
+ try {
335
+ if (sampleRUM) {
336
+ addTrackingFromConfig();
337
+ window.hlx.rum.collector = trackCheckpoint;
338
+ processQueue();
339
+ }
340
+ } catch (error) {
341
+ // something went wrong
342
+ }
343
+ }
344
+
345
+ initEnhancer();
@@ -0,0 +1,17 @@
1
+ /*
2
+ * Copyright 2024 Adobe. All rights reserved.
3
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
+ * you may not use this file except in compliance with the License. You may obtain a copy
5
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+ *
7
+ * Unless required by applicable law or agreed to in writing, software distributed under
8
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
+ * OF ANY KIND, either express or implied. See the License for the specific language
10
+ * governing permissions and limitations under the License.
11
+ */
12
+
13
+ export const urlSanitizers = {
14
+ full: (url = window.location.href) => new URL(url).toString(),
15
+ origin: (url = window.location.href) => new URL(url).origin,
16
+ path: (url = window.location.href) => new URL(url).pathname,
17
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adobe/helix-rum-enhancer",
3
- "version": "2.14.0",
3
+ "version": "2.14.1",
4
4
  "description": "Helix RUM Enhancer",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -8,14 +8,14 @@
8
8
  "setup-playwright": "npx playwright install --with-deps chromium firefox webkit",
9
9
  "test": "npm run setup-playwright && web-test-runner --node-resolve --coverage --playwright --browsers chromium firefox webkit",
10
10
  "test:ci": "npm run setup-playwright && web-test-runner --node-resolve --coverage --playwright --browsers chromium firefox webkit",
11
- "test:watch": "web-test-runner --node-resolve --coverage --watch",
11
+ "test:watch": "web-test-runner --node-resolve --playwright --coverage --watch",
12
12
  "lint": "eslint .",
13
13
  "docs": "npx jsdoc2md -c .jsdoc.json --files 'src/*.js' > docs/API.md",
14
14
  "build-bundle": "rollup --config",
15
15
  "prepack": "npm run build-bundle",
16
16
  "semantic-release": "semantic-release",
17
17
  "semantic-release-dry": "semantic-release --dry-run --branches $CI_BRANCH 1.x main",
18
- "prepare": "husky install"
18
+ "prepare": "husky"
19
19
  },
20
20
  "repository": {
21
21
  "type": "git",
@@ -28,7 +28,6 @@
28
28
  },
29
29
  "homepage": "https://github.com/adobe/helix-rum-enhancer#readme",
30
30
  "mocha": {
31
- "require": "test/setup-env.js",
32
31
  "recursive": "true",
33
32
  "reporter": "mocha-multi-reporters",
34
33
  "reporter-options": "configFile=.mocha-multi.json"
@@ -56,6 +55,7 @@
56
55
  "mocha-multi-reporters": "1.5.1",
57
56
  "rollup": "4.18.0",
58
57
  "rollup-plugin-cleanup": "3.2.1",
58
+ "rollup-plugin-eslint-bundle": "9.0.0",
59
59
  "semantic-release": "24.0.0"
60
60
  },
61
61
  "lint-staged": {
package/rollup.config.js CHANGED
@@ -11,6 +11,7 @@
11
11
  */
12
12
 
13
13
  import cleanup from 'rollup-plugin-cleanup';
14
+ import eslint from 'rollup-plugin-eslint-bundle';
14
15
 
15
16
  const banner = `/*
16
17
  * Copyright 2024 Adobe. All rights reserved.
@@ -29,8 +30,8 @@ const banner = `/*
29
30
 
30
31
  const bundles = [
31
32
  {
32
- source: 'src/index.js',
33
- outputFile: 'dist/rum-enhancer',
33
+ source: 'modules/index.js',
34
+ outputFile: 'src/index',
34
35
  },
35
36
  ];
36
37
 
@@ -50,5 +51,10 @@ export default [...bundles.map(({ outputFile, source }) => ({
50
51
  comments: ['eslint', 'jsdoc', /^\//, /^\*(?!\sc8\s)(?!\n \* Copyright)/],
51
52
  maxEmptyLines: -1,
52
53
  }),
54
+ eslint({
55
+ eslintOptions: {
56
+ fix: true,
57
+ },
58
+ }),
53
59
  ],
54
60
  }))];