From 9b1e89b51067abc1b582a99df4a2e94a7f0bb1ac Mon Sep 17 00:00:00 2001 From: Trevor Manz Date: Thu, 26 Oct 2023 17:37:06 -0400 Subject: [PATCH] feat(core): replace `uuid` dependency with `crypto.randomUUID()` (#976) Co-authored-by: etowahadams --- editor/Editor.tsx | 4 ++-- package.json | 4 +--- src/compiler/gosling-to-higlass.ts | 6 +++--- src/compiler/higlass-model.test.ts | 6 +++--- src/compiler/higlass-model.ts | 8 ++++---- src/compiler/spec-preprocess.ts | 10 +++++----- src/core/gosling-component.tsx | 4 ++-- src/core/higlass-component-wrapper.tsx | 6 +++--- src/core/utils/uuid.ts | 8 ++++++++ src/tracks/gosling-brush/brush-track.ts | 4 ++-- .../gosling-mouse-event/mouse-event-model.ts | 8 ++++---- src/tracks/gosling-track/gosling-track-model.ts | 4 ++-- src/tracks/gosling-track/gosling-track.ts | 10 +++++----- vite.config.js | 3 +-- yarn.lock | 5 ----- 15 files changed, 45 insertions(+), 45 deletions(-) create mode 100644 src/core/utils/uuid.ts diff --git a/editor/Editor.tsx b/editor/Editor.tsx index f2c4a369..83e8410d 100644 --- a/editor/Editor.tsx +++ b/editor/Editor.tsx @@ -27,7 +27,7 @@ import EditorPanel, { type EditorLangauge } from './EditorPanel'; import EditorExamples from './EditorExamples'; import './Editor.css'; -import { v4 } from 'uuid'; +import { uuid } from '../src/core/utils/uuid'; function json2js(jsonCode: string) { return `var spec = ${jsonCode} \nexport { spec }; \n`; @@ -723,7 +723,7 @@ function Editor(props: RouteComponentProps) { } return (
{ it('Should set default values correctly', () => { @@ -11,7 +11,7 @@ describe('Should produce higlass model correctly', () => { it('Should set domain correctly', () => { const higlass = new HiGlassModel(); - higlass.addDefaultView(uuid.v1()); + higlass.addDefaultView(uuid()); higlass.setDomain({ chromosome: 'chr2' }, { chromosome: 'chr2', interval: [100, 200] }); expect(higlass.spec().views?.[0].initialXDomain).toEqual([ computeChromSizes().interval['chr2'][0] + 1, @@ -23,7 +23,7 @@ describe('Should produce higlass model correctly', () => { it('Should add brush correctly', () => { const higlass = new HiGlassModel(); - higlass.addDefaultView(uuid.v1()); + higlass.addDefaultView(uuid()); higlass.addBrush('linear', higlass.getLastView().uid ?? '', getTheme(), 'from'); expect(JSON.stringify(higlass.spec())).toContain('viewport-projection-horizontal'); }); diff --git a/src/compiler/higlass-model.ts b/src/compiler/higlass-model.ts index d3c81802..dffc9198 100644 --- a/src/compiler/higlass-model.ts +++ b/src/compiler/higlass-model.ts @@ -1,4 +1,3 @@ -import * as uuid from 'uuid'; import type { HiGlassSpec, Track } from '@gosling-lang/higlass-schema'; import { HiGlassSchema } from '@gosling-lang/higlass-schema'; import type { Assembly, AxisPosition, Domain, DummyTrack, Orientation, ZoomLimits } from '@gosling-lang/gosling-schema'; @@ -9,6 +8,7 @@ import { getAutoCompleteId, computeChromSizes } from '../core/utils/assembly'; import type { CompleteThemeDeep } from '../core/utils/theme'; import exampleHg from '../core/example/hg-view-config-1'; import { insertItemToArray } from '../core/utils/array'; +import { uuid } from '../core/utils/uuid'; export const HIGLASS_AXIS_SIZE = 30; @@ -163,7 +163,7 @@ export class HiGlassModel { (this.getView(viewId) as any)?.tracks.whole.push({ // type: 'viewport-projection-center', type: layout === 'circular' ? 'brush-track' : 'viewport-projection-horizontal', - uid: uuid.v4(), + uid: uuid(), fromViewUid, options: { projectionFillColor: style?.color ?? theme.brush.color, @@ -288,7 +288,7 @@ export class HiGlassModel { this.getLastView().tracks[this.getMainTrackPosition()] = [ { type: 'combined', - uid: `${track.uid ?? uuid.v4()}-${this.getMainTrackPosition()}-combined`, + uid: `${track.uid ?? uuid()}-${this.getMainTrackPosition()}-combined`, // !! Hacky, but it is important to subtract 1px. Currently, HiGlass does not well handle a case where a center track is zero width (e.g., linking between views that contain zero-width center tracks). // https://github.com/higlass/higlass/pull/1041 width: (track as any).width - 1, @@ -324,7 +324,7 @@ export class HiGlassModel { const widthOrHeight = position === 'left' || position === 'right' ? 'width' : 'height'; const axisTrackTemplate: Track = { - // uid: options.id ?? uuid.v1(), // TODO: turning this on makes some tick labels hidden + // uid: options.id ?? uuid(), // TODO: turning this on makes some tick labels hidden type: 'axis-track', chromInfoPath: this.hg.chromInfoPath, options: { diff --git a/src/compiler/spec-preprocess.ts b/src/compiler/spec-preprocess.ts index 3c9264ed..ed53573f 100644 --- a/src/compiler/spec-preprocess.ts +++ b/src/compiler/spec-preprocess.ts @@ -1,4 +1,3 @@ -import * as uuid from 'uuid'; import type { SingleTrack, GoslingSpec, @@ -31,6 +30,7 @@ import { } from './defaults'; import { spreadTracksByData } from '../core/utils/overlay'; import { getStyleOverridden } from '../core/utils/style'; +import { uuid } from '../core/utils/uuid'; /** * Traverse individual tracks and call the callback function to read and/or update the track definition. @@ -176,7 +176,7 @@ export function traverseToFixSpecDownstream(spec: GoslingSpec | SingleView, pare // ID should be assigned to each view and track for an API usage if (!spec.id) { - spec.id = uuid.v4(); + spec.id = uuid(); } if ('tracks' in spec) { @@ -188,11 +188,11 @@ export function traverseToFixSpecDownstream(spec: GoslingSpec | SingleView, pare */ tracks = spreadTracksByData(tracks); - const linkID = uuid.v4(); + const linkID = uuid(); tracks.forEach((track, i, array) => { // ID should be assigned to each view and track for an API usage if (!track.id) { - track.id = uuid.v4(); + track.id = uuid(); } // If size not defined, set default ones @@ -215,7 +215,7 @@ export function traverseToFixSpecDownstream(spec: GoslingSpec | SingleView, pare track.xe.field // Question: Should we consider mark types? (e.g., link might not be supported?) ) { - const newField = uuid.v4(); + const newField = uuid(); const startField = track.x.field; const endField = track.xe.field; const padding = track.displacement.padding; diff --git a/src/core/gosling-component.tsx b/src/core/gosling-component.tsx index 50a0cbba..89f41287 100644 --- a/src/core/gosling-component.tsx +++ b/src/core/gosling-component.tsx @@ -10,10 +10,10 @@ import { createApi, type GoslingApi } from '../api/api'; import { GoslingTemplates } from '..'; import { omitDeep } from './utils/omit-deep'; import { isEqual } from 'lodash-es'; -import * as uuid from 'uuid'; import { publish } from '../api/pubsub'; import type { IdTable } from '../api/track-and-view-ids'; import { preverseZoomStatus } from './utils/higlass-zoom-config'; +import { uuid } from '../core/utils/uuid'; // Before rerendering, wait for a few time so that HiGlass container is resized already. // If HiGlass is rendered and then the container resizes, the viewport position changes, unmatching `xDomain` specified by users. @@ -59,7 +59,7 @@ export const GoslingComponent = forwardRef((props, const hgRef = useRef(null); const theme = getTheme(props.theme || 'light'); - const wrapperDivId = props.id ?? uuid.v4(); + const wrapperDivId = props.id ?? uuid(); /** * Publishes event if there is a new view added diff --git a/src/core/higlass-component-wrapper.tsx b/src/core/higlass-component-wrapper.tsx index c864c2e9..6cb92cf9 100644 --- a/src/core/higlass-component-wrapper.tsx +++ b/src/core/higlass-component-wrapper.tsx @@ -1,12 +1,12 @@ /* eslint-disable react/prop-types */ import type * as PIXI from 'pixi.js'; import React, { useEffect, useState, forwardRef, useMemo } from 'react'; -import * as uuid from 'uuid'; import * as gosling from '..'; // @ts-ignore import { HiGlassComponent } from 'higlass'; import type { HiGlassSpec } from '@gosling-lang/higlass-schema'; +import { uuid } from '../core/utils/uuid'; /** * Register plugin tracks and data fetchers to HiGlass. This is necessary for the first time before using Gosling. @@ -39,9 +39,9 @@ export interface HiGlassComponentWrapperProps { export const HiGlassComponentWrapper = forwardRef( (props, ref) => { // div `id` and `className` for detailed customization - const [wrapperDivId, setWrapperDivId] = useState(props.id ?? uuid.v4()); + const [wrapperDivId, setWrapperDivId] = useState(props.id ?? uuid()); useEffect(() => { - setWrapperDivId(props.id ?? uuid.v4()); + setWrapperDivId(props.id ?? uuid()); }, [props.id]); const viewConfig = props.viewConfig || {}; diff --git a/src/core/utils/uuid.ts b/src/core/utils/uuid.ts new file mode 100644 index 00000000..b5ef982b --- /dev/null +++ b/src/core/utils/uuid.ts @@ -0,0 +1,8 @@ +// for envs where crypto.randomUUID is not available (i.e., Node). +function fallback(): string { + return Math.random().toString(36).substring(2, 10); +} + +export function uuid(): string { + return globalThis.crypto.randomUUID?.() ?? fallback(); +} diff --git a/src/tracks/gosling-brush/brush-track.ts b/src/tracks/gosling-brush/brush-track.ts index cfc5de50..728b38d3 100644 --- a/src/tracks/gosling-brush/brush-track.ts +++ b/src/tracks/gosling-brush/brush-track.ts @@ -1,7 +1,7 @@ import { arc as d3arc } from 'd3-shape'; import type { SubjectPosition, D3DragEvent } from 'd3-drag'; -import * as uuid from 'uuid'; import { RADIAN_GAP, valueToRadian } from '../../core/utils/polar'; +import { uuid } from '../../core/utils/uuid'; type CircularBrushData = { type: 'brush' | 'start' | 'end'; @@ -25,7 +25,7 @@ function BrushTrack(HGC: any, ...args: any[]): any { const [context, options] = params; const { registerViewportChanged, removeViewportChanged, setDomainsCallback } = context; - this.uid = uuid.v1(); + this.uid = uuid(); this.options = options; // Is there actually a linked from view? Or is this projection "independent"? diff --git a/src/tracks/gosling-track/gosling-mouse-event/mouse-event-model.ts b/src/tracks/gosling-track/gosling-mouse-event/mouse-event-model.ts index 4cca1612..c6f6d54a 100644 --- a/src/tracks/gosling-track/gosling-mouse-event/mouse-event-model.ts +++ b/src/tracks/gosling-track/gosling-mouse-event/mouse-event-model.ts @@ -6,7 +6,7 @@ import { isPointNearPoint, isCircleWithinRange } from './polygon'; -import * as uuid from 'uuid'; +import { uuid } from '../../../core/utils/uuid'; export type MouseEventData = PointEventData | LineEventData | PolygonEventData; @@ -52,21 +52,21 @@ export class MouseEventModel { * Add a new mouse event that is polygon-based. */ public addPolygonBasedEvent(value: Datum, polygon: number[]) { - this.data.push({ uid: uuid.v4(), type: 'polygon', value, polygon }); + this.data.push({ uid: uuid(), type: 'polygon', value, polygon }); } /** * Add a new mouse event that is point-based. */ public addPointBasedEvent(value: Datum, pointAndRadius: [number, number, number]) { - this.data.push({ uid: uuid.v4(), type: 'point', value, polygon: pointAndRadius }); + this.data.push({ uid: uuid(), type: 'point', value, polygon: pointAndRadius }); } /** * Add a new mouse event that is line-based. */ public addLineBasedEvent(value: Datum, path: number[]) { - this.data.push({ uid: uuid.v4(), type: 'line', value, polygon: path }); + this.data.push({ uid: uuid(), type: 'line', value, polygon: path }); } /** diff --git a/src/tracks/gosling-track/gosling-track-model.ts b/src/tracks/gosling-track/gosling-track-model.ts index 4c08d96e..051aa06a 100644 --- a/src/tracks/gosling-track/gosling-track-model.ts +++ b/src/tracks/gosling-track/gosling-track-model.ts @@ -1,4 +1,3 @@ -import * as uuid from 'uuid'; import type { ChannelDeep, PredefinedColors, @@ -42,6 +41,7 @@ import { } from '@gosling-lang/gosling-schema'; import { CHANNEL_DEFAULTS } from '../../core/channel'; import { type CompleteThemeDeep, getTheme } from '../../core/utils/theme'; +import { uuid } from '../../core/utils/uuid'; import { MouseEventModel } from '../gosling-track/gosling-mouse-event'; export type ScaleType = @@ -73,7 +73,7 @@ export class GoslingTrackModel { private mouseEventModel: MouseEventModel; constructor(spec: SingleTrack, data: { [k: string]: number | string }[], theme: Required) { - this.id = uuid.v1(); + this.id = uuid(); this.theme = theme ?? getTheme(); diff --git a/src/tracks/gosling-track/gosling-track.ts b/src/tracks/gosling-track/gosling-track.ts index 7754e68d..91c68f32 100644 --- a/src/tracks/gosling-track/gosling-track.ts +++ b/src/tracks/gosling-track/gosling-track.ts @@ -1,5 +1,4 @@ import type * as PIXI from 'pixi.js'; -import * as uuid from 'uuid'; import { isEqual, sampleSize, uniqBy } from 'lodash-es'; import type { ScaleLinear } from 'd3-scale'; import type { @@ -52,6 +51,7 @@ import { import { HIGLASS_AXIS_SIZE } from '../../compiler/higlass-model'; import { flatArrayToPairArray } from '../../core/utils/array'; import { createPluginTrack, type PluginTrackFactory, type TrackConfig } from '../../core/utils/define-plugin-track'; +import { uuid } from '../../core/utils/uuid'; // Set `true` to print in what order each function is called export const PRINT_RENDERING_CYCLE = false; @@ -188,12 +188,12 @@ const factory: PluginTrackFactory = (HGC, context, op this.#assembly = this.options.spec.assembly; // Add unique IDs to each of the overlaid tracks that will be rendered independently. - if ('_overlay' in this.options.spec) { - this.options.spec._overlay = (this.options.spec as OverlaidTrack)._overlay.map(o => { - return { ...o, _renderingId: uuid.v1() }; + if ('overlay' in this.options.spec) { + this.options.spec.overlay = (this.options.spec as OverlaidTrack)._overlay.map(o => { + return { ...o, _renderingId: uuid() }; }); } else { - this.options.spec._renderingId = uuid.v1(); + this.options.spec._renderingId = uuid(); } this.fetchedTiles = {}; diff --git a/vite.config.js b/vite.config.js index 4bb401d0..9c2c07ed 100644 --- a/vite.config.js +++ b/vite.config.js @@ -74,11 +74,10 @@ const alias = { '@gosling-lang/dummy-track': path.resolve(__dirname, './src/tracks/dummy-track/index.ts'), '@data-fetchers': path.resolve(__dirname, './src/data-fetchers/index.ts'), zlib: path.resolve(__dirname, './src/alias/zlib.ts'), - uuid: path.resolve(__dirname, './node_modules/uuid/dist/esm-browser/index.js'), stream: path.resolve(__dirname, './node_modules/stream-browserify') // gmod/gff uses stream-browserify }; -const skipExt = new Set(['@gmod/bbi', 'uuid']); +const skipExt = new Set(['@gmod/bbi']); const external = [...Object.keys(pkg.dependencies), ...Object.keys(pkg.peerDependencies)].filter( dep => !skipExt.has(dep) ); diff --git a/yarn.lock b/yarn.lock index 6ce292e7..aa6ff1fd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1278,11 +1278,6 @@ resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.6.tgz#250a7b16c3b91f672a24552ec64678eeb1d3a08d" integrity sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ== -"@types/uuid@^8.3.1": - version "8.3.4" - resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.4.tgz#bd86a43617df0594787d38b735f55c805becf1bc" - integrity sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw== - "@typescript-eslint/eslint-plugin@^5.56.0": version "5.58.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.58.0.tgz#b1d4b0ad20243269d020ef9bbb036a40b0849829"