浏览代码

@uppy/thumbnail-generator: migrate to TS (#4978)

Co-authored-by: Antoine du Hamel <duhamelantoine1995@gmail.com>
Merlijn Vos 1 年之前
父节点
当前提交
d6ae3b4bad

+ 0 - 11
packages/@uppy/core/src/Uppy.ts

@@ -483,17 +483,6 @@ export class Uppy<M extends Meta, B extends Body> {
     this.#emitter.emit(event, ...args)
     this.#emitter.emit(event, ...args)
   }
   }
 
 
-  /** @deprecated */
-  on<K extends keyof DeprecatedUppyEventMap<M, B>>(
-    event: K,
-    callback: DeprecatedUppyEventMap<M, B>[K],
-  ): this
-
-  on<K extends keyof _UppyEventMap<M, B>>(
-    event: K,
-    callback: _UppyEventMap<M, B>[K],
-  ): this
-
   on<K extends keyof UppyEventMap<M, B>>(
   on<K extends keyof UppyEventMap<M, B>>(
     event: K,
     event: K,
     callback: UppyEventMap<M, B>[K],
     callback: UppyEventMap<M, B>[K],

+ 1 - 0
packages/@uppy/thumbnail-generator/.npmignore

@@ -0,0 +1 @@
+tsconfig.*

+ 1 - 1
packages/@uppy/thumbnail-generator/src/index.test.js

@@ -1,7 +1,7 @@
 import { afterEach, beforeEach, describe, it, expect, vi } from 'vitest'
 import { afterEach, beforeEach, describe, it, expect, vi } from 'vitest'
 import { UIPlugin } from '@uppy/core'
 import { UIPlugin } from '@uppy/core'
 import emitter from 'namespace-emitter'
 import emitter from 'namespace-emitter'
-import ThumbnailGeneratorPlugin from './index.js'
+import ThumbnailGeneratorPlugin from './index.ts'
 
 
 const delay = duration => new Promise(resolve => setTimeout(resolve, duration))
 const delay = duration => new Promise(resolve => setTimeout(resolve, duration))
 
 

+ 179 - 79
packages/@uppy/thumbnail-generator/src/index.js → packages/@uppy/thumbnail-generator/src/index.ts

@@ -1,48 +1,83 @@
-import { UIPlugin } from '@uppy/core'
+import { UIPlugin, Uppy, type UIPluginOptions } from '@uppy/core'
 import dataURItoBlob from '@uppy/utils/lib/dataURItoBlob'
 import dataURItoBlob from '@uppy/utils/lib/dataURItoBlob'
 import isObjectURL from '@uppy/utils/lib/isObjectURL'
 import isObjectURL from '@uppy/utils/lib/isObjectURL'
 import isPreviewSupported from '@uppy/utils/lib/isPreviewSupported'
 import isPreviewSupported from '@uppy/utils/lib/isPreviewSupported'
+// eslint-disable-next-line @typescript-eslint/ban-ts-comment
+// @ts-ignore untyped
 import { rotation } from 'exifr/dist/mini.esm.mjs'
 import { rotation } from 'exifr/dist/mini.esm.mjs'
 
 
-import locale from './locale.js'
+import type { DefinePluginOpts } from '@uppy/core/lib/BasePlugin.ts'
+import type { Body, Meta, UppyFile } from '@uppy/utils/lib/UppyFile'
+import locale from './locale.ts'
+// eslint-disable-next-line @typescript-eslint/ban-ts-comment
+// @ts-ignore We don't want TS to generate types for the package.json
 import packageJson from '../package.json'
 import packageJson from '../package.json'
 
 
+declare module '@uppy/core' {
+  // eslint-disable-next-line @typescript-eslint/no-unused-vars
+  export interface UppyEventMap<M extends Meta, B extends Body> {
+    'thumbnail:all-generated': () => void
+    'thumbnail:generated': (file: UppyFile<M, B>, preview: string) => void
+    'thumbnail:error': (file: UppyFile<M, B>, error: Error) => void
+    'thumbnail:request': (file: UppyFile<M, B>) => void
+    'thumbnail:cancel': (file: UppyFile<M, B>) => void
+  }
+}
+
+interface Rotation {
+  deg: number
+  rad: number
+  scaleX: number
+  scaleY: number
+  dimensionSwapped: boolean
+  css: boolean
+  canvas: boolean
+}
+
 /**
 /**
  * Save a <canvas> element's content to a Blob object.
  * Save a <canvas> element's content to a Blob object.
  *
  *
- * @param {HTMLCanvasElement} canvas
- * @returns {Promise}
  */
  */
-function canvasToBlob (canvas, type, quality) {
+function canvasToBlob(
+  canvas: HTMLCanvasElement,
+  type: string,
+  quality: number,
+): Promise<Blob | File> {
   try {
   try {
-    canvas.getContext('2d').getImageData(0, 0, 1, 1)
+    canvas.getContext('2d')!.getImageData(0, 0, 1, 1)
   } catch (err) {
   } catch (err) {
     if (err.code === 18) {
     if (err.code === 18) {
-      return Promise.reject(new Error('cannot read image, probably an svg with external resources'))
+      return Promise.reject(
+        new Error('cannot read image, probably an svg with external resources'),
+      )
     }
     }
   }
   }
 
 
   if (canvas.toBlob) {
   if (canvas.toBlob) {
-    return new Promise(resolve => {
+    return new Promise<Blob | null>((resolve) => {
       canvas.toBlob(resolve, type, quality)
       canvas.toBlob(resolve, type, quality)
     }).then((blob) => {
     }).then((blob) => {
       if (blob === null) {
       if (blob === null) {
-        throw new Error('cannot read image, probably an svg with external resources')
+        throw new Error(
+          'cannot read image, probably an svg with external resources',
+        )
       }
       }
       return blob
       return blob
     })
     })
   }
   }
-  return Promise.resolve().then(() => {
-    return dataURItoBlob(canvas.toDataURL(type, quality), {})
-  }).then((blob) => {
-    if (blob === null) {
-      throw new Error('could not extract blob, probably an old browser')
-    }
-    return blob
-  })
+  return Promise.resolve()
+    .then(() => {
+      return dataURItoBlob(canvas.toDataURL(type, quality), {})
+    })
+    .then((blob) => {
+      if (blob === null) {
+        throw new Error('could not extract blob, probably an old browser')
+      }
+      return blob
+    })
 }
 }
 
 
-function rotateImage (image, translate) {
+function rotateImage(image: HTMLImageElement, translate: Rotation) {
   let w = image.width
   let w = image.width
   let h = image.height
   let h = image.height
 
 
@@ -55,13 +90,19 @@ function rotateImage (image, translate) {
   canvas.width = w
   canvas.width = w
   canvas.height = h
   canvas.height = h
 
 
-  const context = canvas.getContext('2d')
+  const context = canvas.getContext('2d')!
   context.translate(w / 2, h / 2)
   context.translate(w / 2, h / 2)
   if (translate.canvas) {
   if (translate.canvas) {
     context.rotate(translate.rad)
     context.rotate(translate.rad)
     context.scale(translate.scaleX, translate.scaleY)
     context.scale(translate.scaleX, translate.scaleY)
   }
   }
-  context.drawImage(image, -image.width / 2, -image.height / 2, image.width, image.height)
+  context.drawImage(
+    image,
+    -image.width / 2,
+    -image.height / 2,
+    image.width,
+    image.height,
+  )
 
 
   return canvas
   return canvas
 }
 }
@@ -70,7 +111,7 @@ function rotateImage (image, translate) {
  * Make sure the image doesn’t exceed browser/device canvas limits.
  * Make sure the image doesn’t exceed browser/device canvas limits.
  * For ios with 256 RAM and ie
  * For ios with 256 RAM and ie
  */
  */
-function protect (image) {
+function protect(image: HTMLCanvasElement): HTMLCanvasElement {
   // https://stackoverflow.com/questions/6081483/maximum-size-of-a-canvas-element
   // https://stackoverflow.com/questions/6081483/maximum-size-of-a-canvas-element
 
 
   const ratio = image.width / image.height
   const ratio = image.width / image.height
@@ -92,59 +133,81 @@ function protect (image) {
     const canvas = document.createElement('canvas')
     const canvas = document.createElement('canvas')
     canvas.width = maxW
     canvas.width = maxW
     canvas.height = maxH
     canvas.height = maxH
-    canvas.getContext('2d').drawImage(image, 0, 0, maxW, maxH)
+    canvas.getContext('2d')!.drawImage(image, 0, 0, maxW, maxH)
     return canvas
     return canvas
   }
   }
 
 
   return image
   return image
 }
 }
 
 
+export interface ThumbnailGeneratorOptions extends UIPluginOptions {
+  thumbnailWidth?: number | null
+  thumbnailHeight?: number | null
+  thumbnailType?: string
+  waitForThumbnailsBeforeUpload?: boolean
+  lazy?: boolean
+}
+
+const defaultOptions = {
+  thumbnailWidth: null,
+  thumbnailHeight: null,
+  thumbnailType: 'image/jpeg',
+  waitForThumbnailsBeforeUpload: false,
+  lazy: false,
+}
+
+type Opts = DefinePluginOpts<
+  ThumbnailGeneratorOptions,
+  keyof typeof defaultOptions
+>
+
 /**
 /**
  * The Thumbnail Generator plugin
  * The Thumbnail Generator plugin
  */
  */
 
 
-export default class ThumbnailGenerator extends UIPlugin {
+export default class ThumbnailGenerator<
+  M extends Meta,
+  B extends Body,
+> extends UIPlugin<Opts, M, B> {
   static VERSION = packageJson.version
   static VERSION = packageJson.version
 
 
-  constructor (uppy, opts) {
-    super(uppy, opts)
+  queue: string[]
+
+  queueProcessing: boolean
+
+  defaultThumbnailDimension: number
+
+  thumbnailType: string
+
+  constructor(uppy: Uppy<M, B>, opts?: ThumbnailGeneratorOptions) {
+    super(uppy, { ...defaultOptions, ...opts })
     this.type = 'modifier'
     this.type = 'modifier'
     this.id = this.opts.id || 'ThumbnailGenerator'
     this.id = this.opts.id || 'ThumbnailGenerator'
     this.title = 'Thumbnail Generator'
     this.title = 'Thumbnail Generator'
     this.queue = []
     this.queue = []
     this.queueProcessing = false
     this.queueProcessing = false
     this.defaultThumbnailDimension = 200
     this.defaultThumbnailDimension = 200
-    this.thumbnailType = this.opts.thumbnailType || 'image/jpeg'
+    this.thumbnailType = this.opts.thumbnailType
 
 
     this.defaultLocale = locale
     this.defaultLocale = locale
 
 
-    const defaultOptions = {
-      thumbnailWidth: null,
-      thumbnailHeight: null,
-      waitForThumbnailsBeforeUpload: false,
-      lazy: false,
-    }
-
-    this.opts = { ...defaultOptions, ...opts }
     this.i18nInit()
     this.i18nInit()
 
 
     if (this.opts.lazy && this.opts.waitForThumbnailsBeforeUpload) {
     if (this.opts.lazy && this.opts.waitForThumbnailsBeforeUpload) {
-      throw new Error('ThumbnailGenerator: The `lazy` and `waitForThumbnailsBeforeUpload` options are mutually exclusive. Please ensure at most one of them is set to `true`.')
+      throw new Error(
+        'ThumbnailGenerator: The `lazy` and `waitForThumbnailsBeforeUpload` options are mutually exclusive. Please ensure at most one of them is set to `true`.',
+      )
     }
     }
   }
   }
 
 
-  /**
-   * Create a thumbnail for the given Uppy file object.
-   *
-   * @param {{data: Blob}} file
-   * @param {number} targetWidth
-   * @param {number} targetHeight
-   * @returns {Promise}
-   */
-  createThumbnail (file, targetWidth, targetHeight) {
+  createThumbnail(
+    file: UppyFile<M, B>,
+    targetWidth: number | null,
+    targetHeight: number | null,
+  ): Promise<string> {
     const originalUrl = URL.createObjectURL(file.data)
     const originalUrl = URL.createObjectURL(file.data)
 
 
-    const onload = new Promise((resolve, reject) => {
+    const onload = new Promise<HTMLImageElement>((resolve, reject) => {
       const image = new Image()
       const image = new Image()
       image.src = originalUrl
       image.src = originalUrl
       image.addEventListener('load', () => {
       image.addEventListener('load', () => {
@@ -157,16 +220,27 @@ export default class ThumbnailGenerator extends UIPlugin {
       })
       })
     })
     })
 
 
-    const orientationPromise = rotation(file.data).catch(() => 1)
+    const orientationPromise = rotation(file.data).catch(
+      () => 1,
+    ) as Promise<Rotation>
 
 
     return Promise.all([onload, orientationPromise])
     return Promise.all([onload, orientationPromise])
       .then(([image, orientation]) => {
       .then(([image, orientation]) => {
-        const dimensions = this.getProportionalDimensions(image, targetWidth, targetHeight, orientation.deg)
+        const dimensions = this.getProportionalDimensions(
+          image,
+          targetWidth,
+          targetHeight,
+          orientation.deg,
+        )
         const rotatedImage = rotateImage(image, orientation)
         const rotatedImage = rotateImage(image, orientation)
-        const resizedImage = this.resizeImage(rotatedImage, dimensions.width, dimensions.height)
+        const resizedImage = this.resizeImage(
+          rotatedImage,
+          dimensions.width,
+          dimensions.height,
+        )
         return canvasToBlob(resizedImage, this.thumbnailType, 80)
         return canvasToBlob(resizedImage, this.thumbnailType, 80)
       })
       })
-      .then(blob => {
+      .then((blob) => {
         return URL.createObjectURL(blob)
         return URL.createObjectURL(blob)
       })
       })
   }
   }
@@ -177,9 +251,15 @@ export default class ThumbnailGenerator extends UIPlugin {
    * account. If neither width nor height are given, the default dimension
    * account. If neither width nor height are given, the default dimension
    * is used.
    * is used.
    */
    */
-  getProportionalDimensions (img, width, height, rotation) { // eslint-disable-line no-shadow
+  getProportionalDimensions(
+    img: HTMLImageElement,
+    width: number | null,
+    height: number | null,
+    deg: number,
+  ): { width: number; height: number } {
+    // eslint-disable-line no-shadow
     let aspect = img.width / img.height
     let aspect = img.width / img.height
-    if (rotation === 90 || rotation === 270) {
+    if (deg === 90 || deg === 270) {
       aspect = img.height / img.width
       aspect = img.height / img.width
     }
     }
 
 
@@ -209,7 +289,11 @@ export default class ThumbnailGenerator extends UIPlugin {
    * Returns a Canvas with the resized image on it.
    * Returns a Canvas with the resized image on it.
    */
    */
   // eslint-disable-next-line class-methods-use-this
   // eslint-disable-next-line class-methods-use-this
-  resizeImage (image, targetWidth, targetHeight) {
+  resizeImage(
+    image: HTMLCanvasElement,
+    targetWidth: number,
+    targetHeight: number,
+  ): HTMLCanvasElement {
     // Resizing in steps refactored to use a solution from
     // Resizing in steps refactored to use a solution from
     // https://blog.uploadcare.com/image-resize-in-browsers-is-broken-e38eed08df01
     // https://blog.uploadcare.com/image-resize-in-browsers-is-broken-e38eed08df01
 
 
@@ -227,7 +311,7 @@ export default class ThumbnailGenerator extends UIPlugin {
       const canvas = document.createElement('canvas')
       const canvas = document.createElement('canvas')
       canvas.width = sW
       canvas.width = sW
       canvas.height = sH
       canvas.height = sH
-      canvas.getContext('2d').drawImage(img, 0, 0, sW, sH)
+      canvas.getContext('2d')!.drawImage(img, 0, 0, sW, sH)
       img = canvas
       img = canvas
 
 
       sW = Math.round(sW / x)
       sW = Math.round(sW / x)
@@ -240,23 +324,26 @@ export default class ThumbnailGenerator extends UIPlugin {
   /**
   /**
    * Set the preview URL for a file.
    * Set the preview URL for a file.
    */
    */
-  setPreviewURL (fileID, preview) {
+  setPreviewURL(fileID: string, preview: string): void {
     this.uppy.setFileState(fileID, { preview })
     this.uppy.setFileState(fileID, { preview })
   }
   }
 
 
-  addToQueue (item) {
-    this.queue.push(item)
+  addToQueue(fileID: string): void {
+    this.queue.push(fileID)
     if (this.queueProcessing === false) {
     if (this.queueProcessing === false) {
       this.processQueue()
       this.processQueue()
     }
     }
   }
   }
 
 
-  processQueue () {
+  processQueue(): Promise<void> {
     this.queueProcessing = true
     this.queueProcessing = true
     if (this.queue.length > 0) {
     if (this.queue.length > 0) {
-      const current = this.uppy.getFile(this.queue.shift())
+      const current = this.uppy.getFile(this.queue.shift() as string)
       if (!current) {
       if (!current) {
-        this.uppy.log('[ThumbnailGenerator] file was removed before a thumbnail could be generated, but not removed from the queue. This is probably a bug', 'error')
+        this.uppy.log(
+          '[ThumbnailGenerator] file was removed before a thumbnail could be generated, but not removed from the queue. This is probably a bug',
+          'error',
+        )
         return Promise.resolve()
         return Promise.resolve()
       }
       }
       return this.requestThumbnail(current)
       return this.requestThumbnail(current)
@@ -269,16 +356,29 @@ export default class ThumbnailGenerator extends UIPlugin {
     return Promise.resolve()
     return Promise.resolve()
   }
   }
 
 
-  requestThumbnail (file) {
+  requestThumbnail(file: UppyFile<M, B>): Promise<void> {
     if (isPreviewSupported(file.type) && !file.isRemote) {
     if (isPreviewSupported(file.type) && !file.isRemote) {
-      return this.createThumbnail(file, this.opts.thumbnailWidth, this.opts.thumbnailHeight)
-        .then(preview => {
+      return this.createThumbnail(
+        file,
+        this.opts.thumbnailWidth,
+        this.opts.thumbnailHeight,
+      )
+        .then((preview) => {
           this.setPreviewURL(file.id, preview)
           this.setPreviewURL(file.id, preview)
-          this.uppy.log(`[ThumbnailGenerator] Generated thumbnail for ${file.id}`)
-          this.uppy.emit('thumbnail:generated', this.uppy.getFile(file.id), preview)
+          this.uppy.log(
+            `[ThumbnailGenerator] Generated thumbnail for ${file.id}`,
+          )
+          this.uppy.emit(
+            'thumbnail:generated',
+            this.uppy.getFile(file.id),
+            preview,
+          )
         })
         })
-        .catch(err => {
-          this.uppy.log(`[ThumbnailGenerator] Failed thumbnail for ${file.id}:`, 'warning')
+        .catch((err) => {
+          this.uppy.log(
+            `[ThumbnailGenerator] Failed thumbnail for ${file.id}:`,
+            'warning',
+          )
           this.uppy.log(err, 'warning')
           this.uppy.log(err, 'warning')
           this.uppy.emit('thumbnail:error', this.uppy.getFile(file.id), err)
           this.uppy.emit('thumbnail:error', this.uppy.getFile(file.id), err)
         })
         })
@@ -286,12 +386,12 @@ export default class ThumbnailGenerator extends UIPlugin {
     return Promise.resolve()
     return Promise.resolve()
   }
   }
 
 
-  onFileAdded = (file) => {
+  onFileAdded = (file: UppyFile<M, B>): void => {
     if (
     if (
-      !file.preview
-      && file.data
-      && isPreviewSupported(file.type)
-      && !file.isRemote
+      !file.preview &&
+      file.data &&
+      isPreviewSupported(file.type) &&
+      !file.isRemote
     ) {
     ) {
       this.addToQueue(file.id)
       this.addToQueue(file.id)
     }
     }
@@ -300,7 +400,7 @@ export default class ThumbnailGenerator extends UIPlugin {
   /**
   /**
    * Cancel a lazy request for a thumbnail if the thumbnail has not yet been generated.
    * Cancel a lazy request for a thumbnail if the thumbnail has not yet been generated.
    */
    */
-  onCancelRequest = (file) => {
+  onCancelRequest = (file: UppyFile<M, B>): void => {
     const index = this.queue.indexOf(file.id)
     const index = this.queue.indexOf(file.id)
     if (index !== -1) {
     if (index !== -1) {
       this.queue.splice(index, 1)
       this.queue.splice(index, 1)
@@ -310,7 +410,7 @@ export default class ThumbnailGenerator extends UIPlugin {
   /**
   /**
    * Clean up the thumbnail for a file. Cancel lazy requests and free the thumbnail URL.
    * Clean up the thumbnail for a file. Cancel lazy requests and free the thumbnail URL.
    */
    */
-  onFileRemoved = (file) => {
+  onFileRemoved = (file: UppyFile<M, B>): void => {
     const index = this.queue.indexOf(file.id)
     const index = this.queue.indexOf(file.id)
     if (index !== -1) {
     if (index !== -1) {
       this.queue.splice(index, 1)
       this.queue.splice(index, 1)
@@ -322,8 +422,8 @@ export default class ThumbnailGenerator extends UIPlugin {
     }
     }
   }
   }
 
 
-  onRestored = () => {
-    const restoredFiles = this.uppy.getFiles().filter(file => file.isRestored)
+  onRestored = (): void => {
+    const restoredFiles = this.uppy.getFiles().filter((file) => file.isRestored)
     restoredFiles.forEach((file) => {
     restoredFiles.forEach((file) => {
       // Only add blob URLs; they are likely invalid after being restored.
       // Only add blob URLs; they are likely invalid after being restored.
       if (!file.preview || isObjectURL(file.preview)) {
       if (!file.preview || isObjectURL(file.preview)) {
@@ -332,11 +432,11 @@ export default class ThumbnailGenerator extends UIPlugin {
     })
     })
   }
   }
 
 
-  onAllFilesRemoved = () => {
+  onAllFilesRemoved = (): void => {
     this.queue = []
     this.queue = []
   }
   }
 
 
-  waitUntilAllProcessed = (fileIDs) => {
+  waitUntilAllProcessed = (fileIDs: string[]): Promise<void> => {
     fileIDs.forEach((fileID) => {
     fileIDs.forEach((fileID) => {
       const file = this.uppy.getFile(fileID)
       const file = this.uppy.getFile(fileID)
       this.uppy.emit('preprocess-progress', file, {
       this.uppy.emit('preprocess-progress', file, {
@@ -365,7 +465,7 @@ export default class ThumbnailGenerator extends UIPlugin {
     })
     })
   }
   }
 
 
-  install () {
+  install(): void {
     this.uppy.on('file-removed', this.onFileRemoved)
     this.uppy.on('file-removed', this.onFileRemoved)
     this.uppy.on('cancel-all', this.onAllFilesRemoved)
     this.uppy.on('cancel-all', this.onAllFilesRemoved)
 
 
@@ -383,7 +483,7 @@ export default class ThumbnailGenerator extends UIPlugin {
     }
     }
   }
   }
 
 
-  uninstall () {
+  uninstall(): void {
     this.uppy.off('file-removed', this.onFileRemoved)
     this.uppy.off('file-removed', this.onFileRemoved)
     this.uppy.off('cancel-all', this.onAllFilesRemoved)
     this.uppy.off('cancel-all', this.onAllFilesRemoved)
 
 

+ 0 - 0
packages/@uppy/thumbnail-generator/src/locale.js → packages/@uppy/thumbnail-generator/src/locale.ts


+ 25 - 0
packages/@uppy/thumbnail-generator/tsconfig.build.json

@@ -0,0 +1,25 @@
+{
+  "extends": "../../../tsconfig.shared",
+  "compilerOptions": {
+    "noImplicitAny": false,
+    "outDir": "./lib",
+    "paths": {
+      "@uppy/utils/lib/*": ["../utils/src/*"],
+      "@uppy/core": ["../core/src/index.js"],
+      "@uppy/core/lib/*": ["../core/src/*"]
+    },
+    "resolveJsonModule": false,
+    "rootDir": "./src",
+    "skipLibCheck": true
+  },
+  "include": ["./src/**/*.*"],
+  "exclude": ["./src/**/*.test.ts"],
+  "references": [
+    {
+      "path": "../utils/tsconfig.build.json"
+    },
+    {
+      "path": "../core/tsconfig.build.json"
+    }
+  ]
+}

+ 21 - 0
packages/@uppy/thumbnail-generator/tsconfig.json

@@ -0,0 +1,21 @@
+{
+  "extends": "../../../tsconfig.shared",
+  "compilerOptions": {
+    "emitDeclarationOnly": false,
+    "noEmit": true,
+    "paths": {
+      "@uppy/utils/lib/*": ["../utils/src/*"],
+      "@uppy/core": ["../core/src/index.js"],
+      "@uppy/core/lib/*": ["../core/src/*"],
+    },
+  },
+  "include": ["./package.json", "./src/**/*.*"],
+  "references": [
+    {
+      "path": "../utils/tsconfig.build.json",
+    },
+    {
+      "path": "../core/tsconfig.build.json",
+    },
+  ],
+}