This is an automated email from the ASF dual-hosted git repository.

wusheng pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/skywalking-client-js.git


The following commit(s) were added to refs/heads/master by this push:
     new 4602cb4  feat: tracing Core Web Vitals (#139)
4602cb4 is described below

commit 4602cb4f1055f019b2ab535254c61e90fc54348f
Author: Fine0830 <[email protected]>
AuthorDate: Sat Sep 14 10:27:42 2024 +0800

    feat: tracing Core Web Vitals (#139)
---
 package.json                                       |   2 +-
 src/monitor.ts                                     |   9 +-
 src/performance/index.ts                           | 172 ++++++++++++++++-----
 src/performance/perf.ts                            |  32 ++--
 src/performance/{type.d.ts => type.ts}             |  12 +-
 src/{trace/type.d.ts => services/bfcache.ts}       |  37 +++--
 src/services/constant.ts                           |   2 -
 src/services/{types.d.ts => eventsListener.ts}     |  29 ++--
 src/services/{types.d.ts => getNavigationEntry.ts} |  34 ++--
 src/services/getVisibilityObserver.ts              |  61 ++++++++
 src/services/observe.ts                            |  51 ++++++
 src/services/{types.d.ts => types.ts}              |  19 +++
 src/trace/{type.d.ts => type.ts}                   |   0
 src/{types.d.ts => types.ts}                       |   1 +
 webpack.config.js                                  |   4 +-
 15 files changed, 351 insertions(+), 114 deletions(-)

diff --git a/package.json b/package.json
index 44d439e..a0b067d 100644
--- a/package.json
+++ b/package.json
@@ -3,7 +3,7 @@
   "version": "0.12.0",
   "description": "Client-side JavaScript exception and tracing library for 
Apache SkyWalking APM",
   "main": "index.js",
-  "types": "lib/src/index.d.ts",
+  "types": "lib/src/index.ts",
   "repository": "apache/skywalking-client-js",
   "homepage": "skywalking.apache.org",
   "license": "Apache 2.0",
diff --git a/src/monitor.ts b/src/monitor.ts
index 70871c7..89a7a58 100644
--- a/src/monitor.ts
+++ b/src/monitor.ts
@@ -49,12 +49,11 @@ const ClientMonitor = {
     traceSegment(this.customOptions);
   },
   performance(configs: any) {
-    // trace and report perf data and pv to serve when page loaded
-    if (document.readyState === 'complete') {
-      tracePerf.getPerf(configs);
-    } else {
+    tracePerf.getPerf(configs);
+    if (configs.enableSPA) {
+      // hash router
       window.addEventListener(
-        'load',
+        'hashchange',
         () => {
           tracePerf.getPerf(configs);
         },
diff --git a/src/performance/index.ts b/src/performance/index.ts
index e93b997..88742f0 100644
--- a/src/performance/index.ts
+++ b/src/performance/index.ts
@@ -15,57 +15,146 @@
  * limitations under the License.
  */
 
-import { CustomOptionsType } from '../types';
+import {CustomOptionsType} from '../types';
 import Report from '../services/report';
+import {prerenderChangeListener} from "../services/eventsListener";
 import pagePerf from './perf';
 import FMP from './fmp';
-import { IPerfDetail } from './type';
+import {observe} from "../services/observe";
+import {LCPMetric, FIDMetric} from "./type";
+import {LayoutShift} from "../services/types";
+import {getVisibilityObserver} from '../services/getVisibilityObserver';
+import {getActivationStart} from '../services/getNavigationEntry';
 
 class TracePerf {
-  private perfConfig = {
-    perfDetail: {},
-  } as { perfDetail: IPerfDetail };
-
+  private options: CustomOptionsType = {
+    pagePath: '',
+    serviceVersion: '',
+    service: '',
+    collector: ''
+  };
+  private perfInfo = {};
+  private coreWebMetrics: {[key: string]: string | number | undefined} = {};
   public getPerf(options: CustomOptionsType) {
-    this.recordPerf(options);
-    if (options.enableSPA) {
-      // hash router
+    this.options = options;
+    this.perfInfo = {
+      pagePath: options.pagePath,
+      serviceVersion: options.serviceVersion,
+      service: options.service,
+    }
+    this.coreWebMetrics = new Proxy({...this.perfInfo, collector: 
options.collector}, handler);
+    // trace and report perf data and pv to serve when page loaded
+    if (document.readyState === 'complete') {
+      this.getBasicPerf();
+    } else {
       window.addEventListener(
-        'hashchange',
+        'load',
         () => {
-          this.recordPerf(options);
+          this.getBasicPerf();
         },
         false,
       );
     }
+    this.getCorePerf();
   }
 
-  public async recordPerf(options: CustomOptionsType) {
-    let fmp: { fmpTime: number | undefined } = { fmpTime: undefined };
-    if (options.autoTracePerf && options.useFmp) {
-      fmp = await new FMP();
+  private async getCorePerf() {
+    if (this.options.useWebVitals) {
+      this.LCP();
+      this.FID();
+      this.CLS();
     }
-    // auto report pv and perf data
-    setTimeout(() => {
-      if (options.autoTracePerf) {
-        this.perfConfig.perfDetail = new pagePerf().getPerfTiming();
+    if (this.options.useFmp) {
+      const {fmpTime} = await new FMP();
+      this.coreWebMetrics.fmpTime = Math.floor(fmpTime);
+    }
+  }
+  private CLS() {
+    let clsTime = 0;
+    let partValue = 0;
+    let entryList: LayoutShift[] = [];
+
+    const handleEntries = (entries: LayoutShift[]) => {
+      entries.forEach((entry) => {
+        // Count layout shifts without recent user input only
+        if (!entry.hadRecentInput) {
+          const firstEntry = entryList[0];
+          const lastEntry = entryList[entryList.length - 1];
+          if (
+            partValue &&
+            entry.startTime - lastEntry.startTime < 1000 &&
+            entry.startTime - firstEntry.startTime < 5000
+          ) {
+            partValue += entry.value;
+            entryList.push(entry);
+          } else {
+            partValue = entry.value;
+            entryList = [entry];
+          }
+        }
+      });
+      if (partValue > clsTime) {
+        this.coreWebMetrics.clsTime = Math.floor(partValue);
       }
-      const perfDetail = options.autoTracePerf
-        ? {
-            ...this.perfConfig.perfDetail,
-            fmpTime: options.useFmp ? parseInt(String(fmp.fmpTime), 10) : 
undefined,
+    };
+
+    observe('layout-shift', handleEntries);
+  }
+  private LCP() {
+    prerenderChangeListener(() => {
+      const visibilityObserver = getVisibilityObserver();
+      const processEntries = (entries: LCPMetric['entries']) => {
+        entries = entries.slice(-1);
+        for (const entry of entries) {
+          if (entry.startTime < visibilityObserver.firstHiddenTime) {
+            this.coreWebMetrics.lcpTime = Math.floor(Math.max(entry.startTime 
- getActivationStart(), 0));
           }
-        : undefined;
-      const perfInfo = {
-        ...perfDetail,
-        pagePath: options.pagePath,
-        serviceVersion: options.serviceVersion,
-        service: options.service,
+        }
+      };
+  
+     observe('largest-contentful-paint', processEntries);
+    })
+  }
+  private FID() {
+    prerenderChangeListener(() => {
+      const visibilityWatcher = getVisibilityObserver();
+      const processEntry = (entry: PerformanceEventTiming) => {
+        // Only report if the page wasn't hidden prior to the first input.
+        if (entry.startTime < visibilityWatcher.firstHiddenTime) {
+          const fidTime = Math.floor(entry.processingStart - entry.startTime);
+          const perfInfo = {
+            fidTime,
+            ...this.perfInfo,
+          };
+          this.reportPerf(perfInfo);
+        }
+      };
+  
+      const processEntries = (entries: FIDMetric['entries']) => {
+        entries.forEach(processEntry);
       };
-      new Report('PERF', options.collector).sendByXhr(perfInfo);
-      // clear perf data
-      this.clearPerf();
-    }, 6000);
+  
+      observe('first-input', processEntries);
+    })
+  }
+  private getBasicPerf() {
+    // auto report pv and perf data
+    const perfDetail = this.options.autoTracePerf ? new 
pagePerf().getPerfTiming() : {};
+    const perfInfo = {
+      ...perfDetail,
+      ...this.perfInfo,
+    };
+    this.reportPerf(perfInfo);
+  }
+
+  public reportPerf(data: {[key: string]: number | string}, collector?: 
string) {
+    const perf = {
+      ...data,
+      ...this.perfInfo
+    };
+    new Report('PERF', collector || this.options.collector).sendByXhr(perf);
+    // clear perf data
+    this.clearPerf();
   }
 
   private clearPerf() {
@@ -73,10 +162,21 @@ class TracePerf {
       return;
     }
     window.performance.clearResourceTimings();
-    this.perfConfig = {
-      perfDetail: {},
-    } as { perfDetail: IPerfDetail };
   }
 }
 
 export default new TracePerf();
+
+const handler = {
+  set(target: {[key: string]: number | string | undefined}, prop: string, 
value: number | string | undefined) {
+    target[prop] = value;
+    if (!isNaN(Number(target.fmpTime)) && !isNaN(Number(target.lcpTime)) && 
!isNaN(Number(target.clsTime))) {
+      const source: {[key: string]: number | string | undefined} = {
+        ...target,
+        collector: undefined,
+      };
+      new TracePerf().reportPerf(source, String(target.collector));
+    }
+    return true;
+  }
+};
diff --git a/src/performance/perf.ts b/src/performance/perf.ts
index 4b10d3e..820cf2f 100644
--- a/src/performance/perf.ts
+++ b/src/performance/perf.ts
@@ -15,12 +15,13 @@
  * limitations under the License.
  */
 import { IPerfDetail } from './type';
+import {getNavigationEntry} from '../services/getNavigationEntry';
 class PagePerf {
   public getPerfTiming(): IPerfDetail {
     try {
       let { timing } = window.performance as PerformanceNavigationTiming | 
any; // PerformanceTiming
       if (typeof window.PerformanceNavigationTiming === 'function') {
-        const nt2Timing = performance.getEntriesByType('navigation')[0];
+        const nt2Timing = getNavigationEntry();
 
         if (nt2Timing) {
           timing = nt2Timing;
@@ -29,33 +30,32 @@ class PagePerf {
       let redirectTime = 0;
 
       if (timing.navigationStart !== undefined) {
-        redirectTime = parseInt(String(timing.fetchStart - 
timing.navigationStart), 10);
+        redirectTime = Math.floor(timing.fetchStart - timing.navigationStart);
       } else if (timing.redirectEnd !== undefined) {
-        redirectTime = parseInt(String(timing.redirectEnd - 
timing.redirectStart), 10);
+        redirectTime = Math.floor(timing.redirectEnd - timing.redirectStart);
       } else {
         redirectTime = 0;
       }
 
       return {
         redirectTime,
-        dnsTime: parseInt(String(timing.domainLookupEnd - 
timing.domainLookupStart), 10),
-        ttfbTime: parseInt(String(timing.responseStart - timing.requestStart), 
10), // Time to First Byte
-        tcpTime: parseInt(String(timing.connectEnd - timing.connectStart), 10),
-        transTime: parseInt(String(timing.responseEnd - timing.responseStart), 
10),
-        domAnalysisTime: parseInt(String(timing.domInteractive - 
timing.responseEnd), 10),
-        fptTime: parseInt(String(timing.responseEnd - timing.fetchStart), 10), 
// First Paint Time or Blank Screen Time
-        domReadyTime: parseInt(String(timing.domContentLoadedEventEnd - 
timing.fetchStart), 10),
-        loadPageTime: parseInt(String(timing.loadEventStart - 
timing.fetchStart), 10), // Page full load time
+        dnsTime: Math.floor(timing.domainLookupEnd - timing.domainLookupStart),
+        ttfbTime: Math.floor(timing.responseStart - timing.requestStart), // 
Time to First Byte
+        tcpTime: Math.floor(timing.connectEnd - timing.connectStart),
+        transTime: Math.floor(timing.responseEnd - timing.responseStart),
+        domAnalysisTime: Math.floor(timing.domInteractive - 
timing.responseEnd),
+        fptTime: Math.floor(timing.responseEnd - timing.fetchStart), // First 
Paint Time or Blank Screen Time
+        domReadyTime: Math.floor(timing.domContentLoadedEventEnd - 
timing.fetchStart),
+        loadPageTime: Math.floor(timing.loadEventStart - timing.fetchStart), 
// Page full load time
         // Synchronous load resources in the page
-        resTime: parseInt(String(timing.loadEventStart - 
timing.domContentLoadedEventEnd), 10),
+        resTime: Math.floor(timing.loadEventStart - 
timing.domContentLoadedEventEnd),
         // Only valid for HTTPS
         sslTime:
           location.protocol === 'https:' && timing.secureConnectionStart > 0
-            ? parseInt(String(timing.connectEnd - 
timing.secureConnectionStart), 10)
+            ? Math.floor(timing.connectEnd - timing.secureConnectionStart)
             : undefined,
-        ttlTime: parseInt(String(timing.domInteractive - timing.fetchStart), 
10), // time to interact
-        firstPackTime: parseInt(String(timing.responseStart - 
timing.domainLookupStart), 10), // first pack time
-        fmpTime: 0, // First Meaningful Paint
+        ttlTime: Math.floor(timing.domInteractive - timing.fetchStart), // 
time to interact
+        firstPackTime: Math.floor(timing.responseStart - 
timing.domainLookupStart), // first pack time
       };
     } catch (e) {
       throw e;
diff --git a/src/performance/type.d.ts b/src/performance/type.ts
similarity index 88%
rename from src/performance/type.d.ts
rename to src/performance/type.ts
index 6eb4785..81d06b6 100644
--- a/src/performance/type.d.ts
+++ b/src/performance/type.ts
@@ -14,6 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+import {LargestContentfulPaint} from "../services/types";
 export interface ICalScore {
   dpss: ICalScore[];
   st: number;
@@ -39,5 +40,14 @@ export type IPerfDetail = {
   sslTime: number | undefined; // Only valid for HTTPS
   ttlTime: number | undefined; // Time to interact
   firstPackTime: number | undefined; // first pack time
-  fmpTime: number | undefined; // First Meaningful Paint
 };
+
+export interface LCPMetric {
+  name: 'LCP';
+  entries: LargestContentfulPaint[];
+}
+
+export interface FIDMetric {
+  name: 'FID';
+  entries: PerformanceEventTiming[];
+}
\ No newline at end of file
diff --git a/src/trace/type.d.ts b/src/services/bfcache.ts
similarity index 66%
copy from src/trace/type.d.ts
copy to src/services/bfcache.ts
index 2e49bd6..da803ce 100644
--- a/src/trace/type.d.ts
+++ b/src/services/bfcache.ts
@@ -15,24 +15,23 @@
  * limitations under the License.
  */
 
-export interface SegmentFields {
-  traceId: string;
-  service: string;
-  spans: SpanFields[];
-  serviceInstance: string;
-  traceSegmentId: string;
+interface onBFCacheRestoreCallback {
+  (event: PageTransitionEvent): void;
 }
 
-export interface SpanFields {
-  operationName: string;
-  startTime: number;
-  endTime: number;
-  spanId: number;
-  spanLayer: string;
-  spanType: string;
-  isError: boolean;
-  parentSpanId: number;
-  componentId: number;
-  peer: string;
-  tags?: any;
-}
+let bfcacheRestoreTime = -1;
+
+export const getBFCacheRestoreTime = () => bfcacheRestoreTime;
+
+export function onBFCacheRestore(cb: onBFCacheRestoreCallback) {
+  addEventListener(
+    'pageshow',
+    (event) => {
+      if (event.persisted) {
+        bfcacheRestoreTime = event.timeStamp;
+        cb(event);
+      }
+    },
+    true,
+  );
+};
diff --git a/src/services/constant.ts b/src/services/constant.ts
index 7fb5922..d30d326 100644
--- a/src/services/constant.ts
+++ b/src/services/constant.ts
@@ -1,5 +1,3 @@
-import Report from './report';
-
 /**
  * Licensed to the Apache Software Foundation (ASF) under one or more
  * contributor license agreements.  See the NOTICE file distributed with
diff --git a/src/services/types.d.ts b/src/services/eventsListener.ts
similarity index 68%
copy from src/services/types.d.ts
copy to src/services/eventsListener.ts
index 2785520..5c863d1 100644
--- a/src/services/types.d.ts
+++ b/src/services/eventsListener.ts
@@ -14,21 +14,18 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
-export interface ErrorInfoFields {
-  uniqueId: string;
-  category: string;
-  grade: string;
-  message: any;
-  errorUrl: string;
-  line?: number;
-  col?: number;
-  stack?: string;
-  firstReportedError?: boolean;
+export function prerenderChangeListener(callback: () => void) {
+  if ((document as any).prerendering) {
+    addEventListener('prerenderingchange', callback, true);
+    return;
+  }
+  callback();
 }
 
-export interface ReportFields {
-  service: string;
-  serviceVersion: string;
-  pagePath: string;
-}
+export function onHidden (cb: () => void) {
+  document.addEventListener('visibilitychange', () => {
+    if (document.visibilityState === 'hidden') {
+      cb();
+    }
+  });
+};
diff --git a/src/services/types.d.ts b/src/services/getNavigationEntry.ts
similarity index 61%
copy from src/services/types.d.ts
copy to src/services/getNavigationEntry.ts
index 2785520..a735b89 100644
--- a/src/services/types.d.ts
+++ b/src/services/getNavigationEntry.ts
@@ -15,20 +15,22 @@
  * limitations under the License.
  */
 
-export interface ErrorInfoFields {
-  uniqueId: string;
-  category: string;
-  grade: string;
-  message: any;
-  errorUrl: string;
-  line?: number;
-  col?: number;
-  stack?: string;
-  firstReportedError?: boolean;
-}
+export function getNavigationEntry() {
+  const navigationEntry: PerformanceEntry | any =
+    self.performance &&
+    performance.getEntriesByType &&
+    performance.getEntriesByType('navigation')[0];
 
-export interface ReportFields {
-  service: string;
-  serviceVersion: string;
-  pagePath: string;
-}
+  if (
+    navigationEntry &&
+    navigationEntry.responseStart > 0 &&
+    navigationEntry.responseStart < performance.now()
+  ) {
+    return navigationEntry;
+  }
+};
+
+export function getActivationStart() {
+  const entry = getNavigationEntry();
+  return (entry && entry.activationStart) || 0;
+};
diff --git a/src/services/getVisibilityObserver.ts 
b/src/services/getVisibilityObserver.ts
new file mode 100644
index 0000000..33750ab
--- /dev/null
+++ b/src/services/getVisibilityObserver.ts
@@ -0,0 +1,61 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import {onBFCacheRestore} from './bfcache';
+
+let firstHiddenTime = -1;
+
+function initHiddenTime () {
+  return document.visibilityState === 'hidden' && !(document as 
any).prerendering
+    ? 0
+    : Infinity;
+};
+
+function onVisibilityUpdate(event: Event) {
+  if (document.visibilityState === 'hidden' && firstHiddenTime > -1) {
+    firstHiddenTime = event.type === 'visibilitychange' ? event.timeStamp : 0;
+    removeChangeListeners();
+  }
+};
+
+function addChangeListeners() {
+  addEventListener('visibilitychange', onVisibilityUpdate, true);
+  addEventListener('prerenderingchange', onVisibilityUpdate, true);
+};
+
+function removeChangeListeners() {
+  removeEventListener('visibilitychange', onVisibilityUpdate, true);
+  removeEventListener('prerenderingchange', onVisibilityUpdate, true);
+};
+
+export function getVisibilityObserver() {
+  if (firstHiddenTime < 0) {
+    firstHiddenTime = initHiddenTime();
+    addChangeListeners();
+    onBFCacheRestore(() => {
+      setTimeout(() => {
+        firstHiddenTime = initHiddenTime();
+        addChangeListeners();
+      }, 0);
+    });
+  }
+  return {
+    get firstHiddenTime() {
+      return firstHiddenTime;
+    },
+  };
+};
diff --git a/src/services/observe.ts b/src/services/observe.ts
new file mode 100644
index 0000000..92f6242
--- /dev/null
+++ b/src/services/observe.ts
@@ -0,0 +1,51 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import {LargestContentfulPaint, LayoutShift} from "./types";
+interface PerformanceEntryObj {
+  'layout-shift': LayoutShift[];
+  'largest-contentful-paint': LargestContentfulPaint[];
+  'first-input': PerformanceEventTiming[];
+}
+
+export function observe <K extends keyof PerformanceEntryObj>(
+  type: K,
+  callback: (entries: PerformanceEntryObj[K]) => void,
+  opts?: PerformanceObserverInit,
+): PerformanceObserver {
+  try {
+    if (PerformanceObserver.supportedEntryTypes.includes(type)) {
+      const perfObs = new PerformanceObserver((list) => {
+
+        Promise.resolve().then(() => {
+          callback(list.getEntries() as PerformanceEntryObj[K]);
+        });
+      });
+      perfObs.observe(
+        Object.assign(
+          {
+            type,
+            buffered: true,
+          },
+          opts || {},
+        ) as PerformanceObserverInit,
+      );
+      return perfObs;
+    }
+  } catch (e) {
+    console.error(e);
+  }
+};
\ No newline at end of file
diff --git a/src/services/types.d.ts b/src/services/types.ts
similarity index 68%
rename from src/services/types.d.ts
rename to src/services/types.ts
index 2785520..2185508 100644
--- a/src/services/types.d.ts
+++ b/src/services/types.ts
@@ -32,3 +32,22 @@ export interface ReportFields {
   serviceVersion: string;
   pagePath: string;
 }
+export interface LargestContentfulPaint extends PerformanceEntry {
+  readonly renderTime: DOMHighResTimeStamp;
+  readonly loadTime: DOMHighResTimeStamp;
+  readonly size: number;
+  readonly id: string;
+  readonly url: string;
+  readonly element: Element | null;
+}
+
+interface LayoutShiftAttribution {
+  node?: Node;
+  previousRect: DOMRectReadOnly;
+  currentRect: DOMRectReadOnly;
+}
+export interface LayoutShift extends PerformanceEntry {
+  value: number;
+  sources: LayoutShiftAttribution[];
+  hadRecentInput: boolean;
+}
diff --git a/src/trace/type.d.ts b/src/trace/type.ts
similarity index 100%
rename from src/trace/type.d.ts
rename to src/trace/type.ts
diff --git a/src/types.d.ts b/src/types.ts
similarity index 98%
rename from src/types.d.ts
rename to src/types.ts
index 764639c..9c6eb3e 100644
--- a/src/types.d.ts
+++ b/src/types.ts
@@ -28,6 +28,7 @@ export interface CustomOptionsType extends 
CustomReportOptions {
   noTraceOrigins?: (string | RegExp)[];
   traceTimeInterval?: number;
   customTags?: TagOption[];
+  useWebVitals?: boolean;
 }
 
 export interface CustomReportOptions {
diff --git a/webpack.config.js b/webpack.config.js
index a099599..6b82fd6 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -44,8 +44,8 @@ const config = {
     new WebpackConcatPlugin({
       bundles: [
         {
-          dest: './lib/src/types.d.ts',
-          src: './src/**/*.d.ts',
+          dest: './lib/src/types.ts',
+          src: './src/**/*.ts',
         },
       ],
     }),

Reply via email to