From 050044f47c6e4cf0a9d324784e054a3f74b282eb Mon Sep 17 00:00:00 2001 From: 43081j <43081j@users.noreply.github.com> Date: Sun, 23 Jun 2024 01:08:35 +0100 Subject: [PATCH] feat: migrate from qs Moves the `manager-api`, `preview-api` and `ui/amanger` to use `picoquery` instead of `qs` - a much smaller and faster alternative. Note that we still have `qs` in our overall monorepo tree, but that can be tackled in a follow-up PR. --- code/lib/manager-api/package.json | 1 - code/lib/preview-api/package.json | 3 +- .../src/modules/preview-web/SelectionStore.ts | 2 +- .../src/modules/preview-web/UrlStore.test.ts | 4 +-- .../src/modules/preview-web/UrlStore.ts | 36 ++++++++++--------- code/lib/router/package.json | 2 +- code/ui/manager/package.json | 2 +- .../preview/utils/stringifyQueryParams.tsx | 4 +-- code/yarn.lock | 28 +++++++++++---- 9 files changed, 49 insertions(+), 33 deletions(-) diff --git a/code/lib/manager-api/package.json b/code/lib/manager-api/package.json index 49270d11882d..5868ec58a2a9 100644 --- a/code/lib/manager-api/package.json +++ b/code/lib/manager-api/package.json @@ -62,7 +62,6 @@ }, "devDependencies": { "@types/lodash": "^4.14.167", - "@types/qs": "^6", "@types/semver": "^7.3.4", "flush-promises": "^1.0.2", "react": "^18.2.0", diff --git a/code/lib/preview-api/package.json b/code/lib/preview-api/package.json index 75415839d0d2..27d13a212ad8 100644 --- a/code/lib/preview-api/package.json +++ b/code/lib/preview-api/package.json @@ -50,11 +50,10 @@ "@storybook/csf": "^0.1.8", "@storybook/global": "^5.0.0", "@storybook/types": "workspace:*", - "@types/qs": "^6.9.5", "dequal": "^2.0.2", "lodash": "^4.17.21", "memoizerific": "^1.11.3", - "qs": "^6.10.0", + "picoquery": "^1.0.0", "tiny-invariant": "^1.3.1", "ts-dedent": "^2.0.0", "util-deprecate": "^1.0.2" diff --git a/code/lib/preview-api/src/modules/preview-web/SelectionStore.ts b/code/lib/preview-api/src/modules/preview-web/SelectionStore.ts index 4faa9cb1089f..4b11ced29088 100644 --- a/code/lib/preview-api/src/modules/preview-web/SelectionStore.ts +++ b/code/lib/preview-api/src/modules/preview-web/SelectionStore.ts @@ -20,5 +20,5 @@ export interface SelectionStore { setSelection(selection: Selection): void; - setQueryParams(queryParams: qs.ParsedQs): void; + setQueryParams(queryParams: Record): void; } diff --git a/code/lib/preview-api/src/modules/preview-web/UrlStore.test.ts b/code/lib/preview-api/src/modules/preview-web/UrlStore.test.ts index cc6e57171968..ca1472e9ff42 100644 --- a/code/lib/preview-api/src/modules/preview-web/UrlStore.test.ts +++ b/code/lib/preview-api/src/modules/preview-web/UrlStore.test.ts @@ -39,7 +39,7 @@ describe('UrlStore', () => { ); }); it('should replace legacy parameters but preserve others', () => { - document.location.search = 'foo=bar&selectedStory=selStory&selectedKind=selKind'; + document.location.search = '?foo=bar&selectedStory=selStory&selectedKind=selKind'; setPath({ storyId: 'story--id', viewMode: 'story' }); expect(history.replaceState).toHaveBeenCalledWith( {}, @@ -48,7 +48,7 @@ describe('UrlStore', () => { ); }); it('should ignore + keep hashes', () => { - document.location.search = 'foo=bar&selectedStory=selStory&selectedKind=selKind'; + document.location.search = '?foo=bar&selectedStory=selStory&selectedKind=selKind'; document.location.hash = '#foobar'; setPath({ storyId: 'story--id', viewMode: 'story' }); expect(history.replaceState).toHaveBeenCalledWith( diff --git a/code/lib/preview-api/src/modules/preview-web/UrlStore.ts b/code/lib/preview-api/src/modules/preview-web/UrlStore.ts index b7890cc3687a..5b211235ef99 100644 --- a/code/lib/preview-api/src/modules/preview-web/UrlStore.ts +++ b/code/lib/preview-api/src/modules/preview-web/UrlStore.ts @@ -1,5 +1,5 @@ import { global } from '@storybook/global'; -import qs from 'qs'; +import * as pq from 'picoquery'; import type { ViewMode } from '@storybook/types'; import { parseArgsParam } from './parseArgsParam'; @@ -20,19 +20,20 @@ const getQueryString = ({ extraParams, }: { selection?: Selection; - extraParams?: qs.ParsedQs; + extraParams?: Record; }) => { - const search = typeof document !== 'undefined' ? document.location.search : ''; - const { path, selectedKind, selectedStory, ...rest } = qs.parse(search, { - ignoreQueryPrefix: true, - }); - return qs.stringify( - { + const search = + typeof document !== 'undefined' && document.location.search + ? document.location.search.slice(1) + : ''; + const { path, selectedKind, selectedStory, ...rest } = pq.parse(search); + return ( + '?' + + pq.stringify({ ...rest, ...extraParams, ...(selection && { id: selection.storyId, viewMode: selection.viewMode }), - }, - { encode: false, addQueryPrefix: true } + }) ); }; @@ -45,10 +46,10 @@ export const setPath = (selection?: Selection) => { }; type ValueOf = T[keyof T]; -const isObject = (val: Record) => +const isObject = (val: Record): val is object => val != null && typeof val === 'object' && Array.isArray(val) === false; -const getFirstString = (v: ValueOf): string | void => { +const getFirstString = (v: ValueOf>): string | void => { if (v === undefined) { return undefined; } @@ -58,15 +59,18 @@ const getFirstString = (v: ValueOf): string | void => { if (Array.isArray(v)) { return getFirstString(v[0]); } - if (isObject(v)) { - return getFirstString(Object.values(v).filter(Boolean) as string[]); + if (isObject(v as Record)) { + return getFirstString( + Object.values(v as Record).filter(Boolean) as string[] + ); } return undefined; }; export const getSelectionSpecifierFromPath: () => SelectionSpecifier | null = () => { if (typeof document !== 'undefined') { - const query = qs.parse(document.location.search, { ignoreQueryPrefix: true }); + const queryStr = document.location.search ? document.location.search.slice(1) : ''; + const query = pq.parse(queryStr); const args = typeof query.args === 'string' ? parseArgsParam(query.args) : undefined; const globals = typeof query.globals === 'string' ? parseArgsParam(query.globals) : undefined; @@ -100,7 +104,7 @@ export class UrlStore implements SelectionStore { setPath(this.selection); } - setQueryParams(queryParams: qs.ParsedQs) { + setQueryParams(queryParams: Record) { const query = getQueryString({ extraParams: queryParams }); const { hash = '' } = document.location; history.replaceState({}, '', `${document.location.pathname}${query}${hash}`); diff --git a/code/lib/router/package.json b/code/lib/router/package.json index c9a4b1bf2888..312a61f703fa 100644 --- a/code/lib/router/package.json +++ b/code/lib/router/package.json @@ -51,7 +51,7 @@ "dependencies": { "@storybook/client-logger": "workspace:*", "memoizerific": "^1.11.3", - "qs": "^6.10.0" + "picoquery": "^1.0.0" }, "devDependencies": { "@storybook/global": "^5.0.0", diff --git a/code/ui/manager/package.json b/code/ui/manager/package.json index a976dab95595..62cbcf87ac97 100644 --- a/code/ui/manager/package.json +++ b/code/ui/manager/package.json @@ -97,8 +97,8 @@ "lodash": "^4.17.21", "markdown-to-jsx": "^7.4.5", "memoizerific": "^1.11.3", + "picoquery": "^1.0.0", "polished": "^4.2.2", - "qs": "^6.10.0", "react": "^18.2.0", "react-dom": "^18.2.0", "react-draggable": "^4.4.5", diff --git a/code/ui/manager/src/components/preview/utils/stringifyQueryParams.tsx b/code/ui/manager/src/components/preview/utils/stringifyQueryParams.tsx index 4175503808bb..f98f5aed2066 100644 --- a/code/ui/manager/src/components/preview/utils/stringifyQueryParams.tsx +++ b/code/ui/manager/src/components/preview/utils/stringifyQueryParams.tsx @@ -1,4 +1,4 @@ -import qs from 'qs'; +import { stringify } from 'picoquery'; export const stringifyQueryParams = (queryParams: Record) => - qs.stringify(queryParams, { addQueryPrefix: true, encode: false }).replace(/^\?/, '&'); + '&' + stringify(queryParams); diff --git a/code/yarn.lock b/code/yarn.lock index da5523a1023d..6eedbb821c1e 100644 --- a/code/yarn.lock +++ b/code/yarn.lock @@ -6232,7 +6232,6 @@ __metadata: "@storybook/theming": "workspace:*" "@storybook/types": "workspace:*" "@types/lodash": "npm:^4.14.167" - "@types/qs": "npm:^6" "@types/semver": "npm:^7.3.4" dequal: "npm:^2.0.2" flush-promises: "npm:^1.0.2" @@ -6277,8 +6276,8 @@ __metadata: lodash: "npm:^4.17.21" markdown-to-jsx: "npm:^7.4.5" memoizerific: "npm:^1.11.3" + picoquery: "npm:^1.0.0" polished: "npm:^4.2.2" - qs: "npm:^6.10.0" react: "npm:^18.2.0" react-dom: "npm:^18.2.0" react-draggable: "npm:^4.4.5" @@ -6568,12 +6567,11 @@ __metadata: "@storybook/csf": "npm:^0.1.8" "@storybook/global": "npm:^5.0.0" "@storybook/types": "workspace:*" - "@types/qs": "npm:^6.9.5" ansi-to-html: "npm:^0.6.11" dequal: "npm:^2.0.2" lodash: "npm:^4.17.21" memoizerific: "npm:^1.11.3" - qs: "npm:^6.10.0" + picoquery: "npm:^1.0.0" slash: "npm:^5.0.0" tiny-invariant: "npm:^1.3.1" ts-dedent: "npm:^2.0.0" @@ -6877,7 +6875,7 @@ __metadata: dequal: "npm:^2.0.2" lodash: "npm:^4.17.21" memoizerific: "npm:^1.11.3" - qs: "npm:^6.10.0" + picoquery: "npm:^1.0.0" react: "npm:^18.2.0" react-dom: "npm:^18.2.0" react-router-dom: "npm:6.0.2" @@ -8191,7 +8189,7 @@ __metadata: languageName: node linkType: hard -"@types/qs@npm:*, @types/qs@npm:^6, @types/qs@npm:^6.9.5": +"@types/qs@npm:*": version: 6.9.10 resolution: "@types/qs@npm:6.9.10" checksum: 10c0/6be12e5f062d1b41eb037d59bf9cb65bc9410cedd5e6da832dfd7c8e2b3f4c91e81c9b90b51811140770e5052c6c4e8361181bd9437ddcd4515dc128b7c00353 @@ -14953,6 +14951,13 @@ __metadata: languageName: node linkType: hard +"fast-decode-uri-component@npm:^1.0.1": + version: 1.0.1 + resolution: "fast-decode-uri-component@npm:1.0.1" + checksum: 10c0/039d50c2e99d64f999c3f2126c23fbf75a04a4117e218a149ca0b1d2aeb8c834b7b19d643b9d35d4eabce357189a6a94085f78cf48869e6e26cc59b036284bc3 + languageName: node + linkType: hard + "fast-deep-equal@npm:^3.1.1, fast-deep-equal@npm:^3.1.3": version: 3.1.3 resolution: "fast-deep-equal@npm:3.1.3" @@ -22493,6 +22498,15 @@ __metadata: languageName: node linkType: hard +"picoquery@npm:^1.0.0": + version: 1.0.0 + resolution: "picoquery@npm:1.0.0" + dependencies: + fast-decode-uri-component: "npm:^1.0.1" + checksum: 10c0/d5d89ca0ce6f150db02175b072eb881460bb3dc2a7a50d55be1b4f05d953136efc3815504d93045c8ba014c318f4a4e1f523b1a960db9db0bde357bca28aacde + languageName: node + linkType: hard + "pidtree@npm:0.6.0": version: 0.6.0 resolution: "pidtree@npm:0.6.0" @@ -23340,7 +23354,7 @@ __metadata: languageName: node linkType: hard -"qs@npm:^6.10.0, qs@npm:^6.10.1, qs@npm:^6.11.2, qs@npm:^6.4.0": +"qs@npm:^6.10.1, qs@npm:^6.11.2, qs@npm:^6.4.0": version: 6.11.2 resolution: "qs@npm:6.11.2" dependencies: