diff --git a/README.md b/README.md index 0b28771..25ff36b 100644 --- a/README.md +++ b/README.md @@ -201,7 +201,7 @@ await navigation.navigate("/").finished;
Polyfill Global Window Types See [`@types/dom-navigation`](https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/dom-navigation/package.json) for a standardised type definition for the Navigation API -which can be utilised alongside this polyfill. +which can be utilised alongside this polyfill. ```bash yarn add --dev @types/dom-navigation @@ -219,4 +219,39 @@ This should then be included as a type in your `tsconfig.json`: } ``` +
+ +
Polyfill Serializer + +You may want to set a custom serializer to store state in history + +The default serializer is [JSON](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON) + +In the past, a [structured clone like serializer](https://www.npmjs.com/package/@ungap/structured-clone) was used. This may be useful for you if +you're using native types rather than just JSON compatible values. + +An example of making use of a custom serializer with the polyfill: + +```typescript +import { setSerializer } from "@virtualstate/navigation/polyfill"; +import { serialize, deserialize } from "@ungap/structured-clone"; + +setSerializer({ + stringify(value) { + return serialize(value) + }, + parse(value) { + return deserialize(value) + } +}); +``` + +
+ +## What's Changed + +
Change Log + +- (1.0.1-alpha.x) Updated default serializer for polyfill to JSON [#35](https://github.com/virtualstate/navigation/pull/35) +
\ No newline at end of file diff --git a/example/polyfill-rollup.js b/example/polyfill-rollup.js index c721aa6..2514db9 100644 --- a/example/polyfill-rollup.js +++ b/example/polyfill-rollup.js @@ -1,3 +1,7 @@ +'use strict'; + +Object.defineProperty(exports, '__esModule', { value: true }); + let globalNavigation = undefined; if (typeof window !== "undefined" && window.navigation) { const navigation = window.navigation; @@ -1756,25 +1760,15 @@ function getNavigation() { return (navigation$1 = new Navigation()); } - -const getStructuredClone = () => json; - -async function getStructuredCloneModule() { - const { stringify, parse } = await Promise.resolve().then(function () { return json; }); - return { stringify, parse }; +let GLOBAL_SERIALIZER = JSON; +function setSerializer(serializer) { + GLOBAL_SERIALIZER = serializer; } -function structuredCloneFallback() { - const stringify = JSON.stringify.bind(JSON), parse = JSON.parse.bind(JSON); - return { - stringify, - parse - }; +function stringify(value) { + return GLOBAL_SERIALIZER.stringify(value); } -function stringify$1(value) { - return getStructuredClone().stringify(value); -} -function parse$1(value) { - return getStructuredClone().parse(value); +function parse(value) { + return GLOBAL_SERIALIZER.parse(value); } const AppLocationCheckChange = Symbol.for("@virtualstate/navigation/location/checkChange"); @@ -2095,7 +2089,7 @@ function setHistoryState(navigation, history, entry, persist, limit) { if (typeof sessionStorage === "undefined") return; try { - const raw = stringify$1(getSerializableState()); + const raw = stringify(getSerializableState()); sessionStorage.setItem(entry.key, raw); } catch { } @@ -2132,7 +2126,9 @@ function getHistoryState(history, entry) { const raw = sessionStorage.getItem(entry.key); if (!raw) return undefined; - const state = parse$1(raw); + const state = parse(raw); + if (!like(state)) + return undefined; if (!isStateHistoryWithMeta(state)) return undefined; return state[NavigationKey].state; @@ -2687,267 +2683,5 @@ if (shouldApplyPolyfill(navigation)) { } } -const VOID = -1; -const PRIMITIVE = 0; -const ARRAY = 1; -const OBJECT = 2; -const DATE = 3; -const REGEXP = 4; -const MAP = 5; -const SET = 6; -const ERROR = 7; -const BIGINT = 8; -// export const SYMBOL = 9; - -const env = typeof self === 'object' ? self : globalThis; - -const deserializer = ($, _) => { - const as = (out, index) => { - $.set(index, out); - return out; - }; - - const unpair = index => { - if ($.has(index)) - return $.get(index); - - const [type, value] = _[index]; - switch (type) { - case PRIMITIVE: - case VOID: - return as(value, index); - case ARRAY: { - const arr = as([], index); - for (const index of value) - arr.push(unpair(index)); - return arr; - } - case OBJECT: { - const object = as({}, index); - for (const [key, index] of value) - object[unpair(key)] = unpair(index); - return object; - } - case DATE: - return as(new Date(value), index); - case REGEXP: { - const {source, flags} = value; - return as(new RegExp(source, flags), index); - } - case MAP: { - const map = as(new Map, index); - for (const [key, index] of value) - map.set(unpair(key), unpair(index)); - return map; - } - case SET: { - const set = as(new Set, index); - for (const index of value) - set.add(unpair(index)); - return set; - } - case ERROR: { - const {name, message} = value; - return as(new env[name](message), index); - } - case BIGINT: - return as(BigInt(value), index); - case 'BigInt': - return as(Object(BigInt(value)), index); - } - return as(new env[type](value), index); - }; - - return unpair; -}; - -/** - * @typedef {Array} Record a type representation - */ - -/** - * Returns a deserialized value from a serialized array of Records. - * @param {Record[]} serialized a previously serialized value. - * @returns {any} - */ -const deserialize = serialized => deserializer(new Map, serialized)(0); - -const EMPTY = ''; - -const {toString} = {}; -const {keys} = Object; - -const typeOf = value => { - const type = typeof value; - if (type !== 'object' || !value) - return [PRIMITIVE, type]; - - const asString = toString.call(value).slice(8, -1); - switch (asString) { - case 'Array': - return [ARRAY, EMPTY]; - case 'Object': - return [OBJECT, EMPTY]; - case 'Date': - return [DATE, EMPTY]; - case 'RegExp': - return [REGEXP, EMPTY]; - case 'Map': - return [MAP, EMPTY]; - case 'Set': - return [SET, EMPTY]; - } - - if (asString.includes('Array')) - return [ARRAY, asString]; - - if (asString.includes('Error')) - return [ERROR, asString]; - - return [OBJECT, asString]; -}; - -const shouldSkip = ([TYPE, type]) => ( - TYPE === PRIMITIVE && - (type === 'function' || type === 'symbol') -); - -const serializer = (strict, json, $, _) => { - - const as = (out, value) => { - const index = _.push(out) - 1; - $.set(value, index); - return index; - }; - - const pair = value => { - if ($.has(value)) - return $.get(value); - - let [TYPE, type] = typeOf(value); - switch (TYPE) { - case PRIMITIVE: { - let entry = value; - switch (type) { - case 'bigint': - TYPE = BIGINT; - entry = value.toString(); - break; - case 'function': - case 'symbol': - if (strict) - throw new TypeError('unable to serialize ' + type); - entry = null; - break; - case 'undefined': - return as([VOID], value); - } - return as([TYPE, entry], value); - } - case ARRAY: { - if (type) - return as([type, [...value]], value); - - const arr = []; - const index = as([TYPE, arr], value); - for (const entry of value) - arr.push(pair(entry)); - return index; - } - case OBJECT: { - if (type) { - switch (type) { - case 'BigInt': - return as([type, value.toString()], value); - case 'Boolean': - case 'Number': - case 'String': - return as([type, value.valueOf()], value); - } - } - - if (json && ('toJSON' in value)) - return pair(value.toJSON()); - - const entries = []; - const index = as([TYPE, entries], value); - for (const key of keys(value)) { - if (strict || !shouldSkip(typeOf(value[key]))) - entries.push([pair(key), pair(value[key])]); - } - return index; - } - case DATE: - return as([TYPE, value.toISOString()], value); - case REGEXP: { - const {source, flags} = value; - return as([TYPE, {source, flags}], value); - } - case MAP: { - const entries = []; - const index = as([TYPE, entries], value); - for (const [key, entry] of value) { - if (strict || !(shouldSkip(typeOf(key)) || shouldSkip(typeOf(entry)))) - entries.push([pair(key), pair(entry)]); - } - return index; - } - case SET: { - const entries = []; - const index = as([TYPE, entries], value); - for (const entry of value) { - if (strict || !shouldSkip(typeOf(entry))) - entries.push(pair(entry)); - } - return index; - } - } - - const {message} = value; - return as([TYPE, {name: type, message}], value); - }; - - return pair; -}; - -/** - * @typedef {Array} Record a type representation - */ - -/** - * Returns an array of serialized Records. - * @param {any} value a serializable value. - * @param {{lossy?: boolean}?} options an object with a `lossy` property that, - * if `true`, will not throw errors on incompatible types, and behave more - * like JSON stringify would behave. Symbol and Function will be discarded. - * @returns {Record[]} - */ - const serialize = (value, {json, lossy} = {}) => { - const _ = []; - return serializer(!(json || lossy), !!json, new Map, _)(value), _; -}; - -/*! (c) Andrea Giammarchi - ISC */ - -const {parse: $parse, stringify: $stringify} = JSON; -const options = {json: true, lossy: true}; - -/** - * Revive a previously stringified structured clone. - * @param {string} str previously stringified data as string. - * @returns {any} whatever was previously stringified as clone. - */ -const parse = str => deserialize($parse(str)); - -/** - * Represent a structured clone value as string. - * @param {any} any some clone-able value to stringify. - * @returns {string} the value stringified. - */ -const stringify = any => $stringify(serialize(any, options)); - -var json = /*#__PURE__*/Object.freeze({ - __proto__: null, - parse: parse, - stringify: stringify -}); +exports.setSerializer = setSerializer; +//# sourceMappingURL=polyfill-rollup.js.map diff --git a/example/rollup.js b/example/rollup.js index a1de8bb..e57e437 100644 --- a/example/rollup.js +++ b/example/rollup.js @@ -2016,27 +2016,15 @@ async function transition(navigation) { return finalPromise; } -/** post rollup replace json **/ -const structuredClone = (await getStructuredCloneModule() - .catch(structuredCloneFallback)); -const getStructuredClone = () => structuredClone; -/** post rollup replace json **/ -async function getStructuredCloneModule() { - const { stringify, parse } = await Promise.resolve().then(function () { return json; }); - return { stringify, parse }; -} -function structuredCloneFallback() { - const stringify = JSON.stringify.bind(JSON), parse = JSON.parse.bind(JSON); - return { - stringify, - parse - }; +let GLOBAL_SERIALIZER = JSON; +function setSerializer(serializer) { + GLOBAL_SERIALIZER = serializer; } -function stringify$1(value) { - return getStructuredClone().stringify(value); +function stringify(value) { + return GLOBAL_SERIALIZER.stringify(value); } -function parse$1(value) { - return getStructuredClone().parse(value); +function parse(value) { + return GLOBAL_SERIALIZER.parse(value); } const globalWindow = typeof window === "undefined" ? undefined : window; @@ -2098,7 +2086,7 @@ function setHistoryState(navigation, history, entry, persist, limit) { if (typeof sessionStorage === "undefined") return; try { - const raw = stringify$1(getSerializableState()); + const raw = stringify(getSerializableState()); sessionStorage.setItem(entry.key, raw); } catch { } @@ -2135,7 +2123,9 @@ function getHistoryState(history, entry) { const raw = sessionStorage.getItem(entry.key); if (!raw) return undefined; - const state = parse$1(raw); + const state = parse(raw); + if (!like(state)) + return undefined; if (!isStateHistoryWithMeta(state)) return undefined; return state[NavigationKey].state; @@ -2686,270 +2676,5 @@ function applyPolyfill(options = DEFAULT_POLYFILL_OPTIONS) { return navigation; } -const VOID = -1; -const PRIMITIVE = 0; -const ARRAY = 1; -const OBJECT = 2; -const DATE = 3; -const REGEXP = 4; -const MAP = 5; -const SET = 6; -const ERROR = 7; -const BIGINT = 8; -// export const SYMBOL = 9; - -const env = typeof self === 'object' ? self : globalThis; - -const deserializer = ($, _) => { - const as = (out, index) => { - $.set(index, out); - return out; - }; - - const unpair = index => { - if ($.has(index)) - return $.get(index); - - const [type, value] = _[index]; - switch (type) { - case PRIMITIVE: - case VOID: - return as(value, index); - case ARRAY: { - const arr = as([], index); - for (const index of value) - arr.push(unpair(index)); - return arr; - } - case OBJECT: { - const object = as({}, index); - for (const [key, index] of value) - object[unpair(key)] = unpair(index); - return object; - } - case DATE: - return as(new Date(value), index); - case REGEXP: { - const {source, flags} = value; - return as(new RegExp(source, flags), index); - } - case MAP: { - const map = as(new Map, index); - for (const [key, index] of value) - map.set(unpair(key), unpair(index)); - return map; - } - case SET: { - const set = as(new Set, index); - for (const index of value) - set.add(unpair(index)); - return set; - } - case ERROR: { - const {name, message} = value; - return as(new env[name](message), index); - } - case BIGINT: - return as(BigInt(value), index); - case 'BigInt': - return as(Object(BigInt(value)), index); - } - return as(new env[type](value), index); - }; - - return unpair; -}; - -/** - * @typedef {Array} Record a type representation - */ - -/** - * Returns a deserialized value from a serialized array of Records. - * @param {Record[]} serialized a previously serialized value. - * @returns {any} - */ -const deserialize = serialized => deserializer(new Map, serialized)(0); - -const EMPTY = ''; - -const {toString} = {}; -const {keys} = Object; - -const typeOf = value => { - const type = typeof value; - if (type !== 'object' || !value) - return [PRIMITIVE, type]; - - const asString = toString.call(value).slice(8, -1); - switch (asString) { - case 'Array': - return [ARRAY, EMPTY]; - case 'Object': - return [OBJECT, EMPTY]; - case 'Date': - return [DATE, EMPTY]; - case 'RegExp': - return [REGEXP, EMPTY]; - case 'Map': - return [MAP, EMPTY]; - case 'Set': - return [SET, EMPTY]; - } - - if (asString.includes('Array')) - return [ARRAY, asString]; - - if (asString.includes('Error')) - return [ERROR, asString]; - - return [OBJECT, asString]; -}; - -const shouldSkip = ([TYPE, type]) => ( - TYPE === PRIMITIVE && - (type === 'function' || type === 'symbol') -); - -const serializer = (strict, json, $, _) => { - - const as = (out, value) => { - const index = _.push(out) - 1; - $.set(value, index); - return index; - }; - - const pair = value => { - if ($.has(value)) - return $.get(value); - - let [TYPE, type] = typeOf(value); - switch (TYPE) { - case PRIMITIVE: { - let entry = value; - switch (type) { - case 'bigint': - TYPE = BIGINT; - entry = value.toString(); - break; - case 'function': - case 'symbol': - if (strict) - throw new TypeError('unable to serialize ' + type); - entry = null; - break; - case 'undefined': - return as([VOID], value); - } - return as([TYPE, entry], value); - } - case ARRAY: { - if (type) - return as([type, [...value]], value); - - const arr = []; - const index = as([TYPE, arr], value); - for (const entry of value) - arr.push(pair(entry)); - return index; - } - case OBJECT: { - if (type) { - switch (type) { - case 'BigInt': - return as([type, value.toString()], value); - case 'Boolean': - case 'Number': - case 'String': - return as([type, value.valueOf()], value); - } - } - - if (json && ('toJSON' in value)) - return pair(value.toJSON()); - - const entries = []; - const index = as([TYPE, entries], value); - for (const key of keys(value)) { - if (strict || !shouldSkip(typeOf(value[key]))) - entries.push([pair(key), pair(value[key])]); - } - return index; - } - case DATE: - return as([TYPE, value.toISOString()], value); - case REGEXP: { - const {source, flags} = value; - return as([TYPE, {source, flags}], value); - } - case MAP: { - const entries = []; - const index = as([TYPE, entries], value); - for (const [key, entry] of value) { - if (strict || !(shouldSkip(typeOf(key)) || shouldSkip(typeOf(entry)))) - entries.push([pair(key), pair(entry)]); - } - return index; - } - case SET: { - const entries = []; - const index = as([TYPE, entries], value); - for (const entry of value) { - if (strict || !shouldSkip(typeOf(entry))) - entries.push(pair(entry)); - } - return index; - } - } - - const {message} = value; - return as([TYPE, {name: type, message}], value); - }; - - return pair; -}; - -/** - * @typedef {Array} Record a type representation - */ - -/** - * Returns an array of serialized Records. - * @param {any} value a serializable value. - * @param {{lossy?: boolean}?} options an object with a `lossy` property that, - * if `true`, will not throw errors on incompatible types, and behave more - * like JSON stringify would behave. Symbol and Function will be discarded. - * @returns {Record[]} - */ - const serialize = (value, {json, lossy} = {}) => { - const _ = []; - return serializer(!(json || lossy), !!json, new Map, _)(value), _; -}; - -/*! (c) Andrea Giammarchi - ISC */ - -const {parse: $parse, stringify: $stringify} = JSON; -const options = {json: true, lossy: true}; - -/** - * Revive a previously stringified structured clone. - * @param {string} str previously stringified data as string. - * @returns {any} whatever was previously stringified as clone. - */ -const parse = str => deserialize($parse(str)); - -/** - * Represent a structured clone value as string. - * @param {any} any some clone-able value to stringify. - * @returns {string} the value stringified. - */ -const stringify = any => $stringify(serialize(any, options)); - -var json = /*#__PURE__*/Object.freeze({ - __proto__: null, - parse: parse, - stringify: stringify -}); - -export { AppLocationAwaitFinished, AppLocationCheckChange, AppLocationTransitionURL, AppLocationUrl, EventTarget, NAVIGATION_LOCATION_DEFAULT_URL, Navigation, NavigationCanIntercept, NavigationCurrentEntryChangeEvent, NavigationDisposeState, NavigationFormData, NavigationGetState, NavigationHistory, NavigationLocation, NavigationSetCurrentIndex, NavigationSetCurrentKey, NavigationSetEntries, NavigationSetOptions, NavigationSetState, NavigationSync, NavigationTransitionFinally, NavigationUserInitiated, applyPolyfill, getCompletePolyfill, getPolyfill, isInterceptEvent, isNavigationNavigationType, transition }; +export { AppLocationAwaitFinished, AppLocationCheckChange, AppLocationTransitionURL, AppLocationUrl, EventTarget, NAVIGATION_LOCATION_DEFAULT_URL, Navigation, NavigationCanIntercept, NavigationCurrentEntryChangeEvent, NavigationDisposeState, NavigationFormData, NavigationGetState, NavigationHistory, NavigationLocation, NavigationSetCurrentIndex, NavigationSetCurrentKey, NavigationSetEntries, NavigationSetOptions, NavigationSetState, NavigationSync, NavigationTransitionFinally, NavigationUserInitiated, applyPolyfill, getCompletePolyfill, getPolyfill, isInterceptEvent, isNavigationNavigationType, setSerializer, transition }; //# sourceMappingURL=rollup.js.map diff --git a/example/routes-rollup.js b/example/routes-rollup.js index 27af1bb..f64940d 100644 --- a/example/routes-rollup.js +++ b/example/routes-rollup.js @@ -3724,3 +3724,4 @@ function routes(...args) { } export { Router, enableURLPatternCache, getRouter, getRouterRoutes, isRouter, route, routes, transitionEvent }; +//# sourceMappingURL=routes-rollup.js.map diff --git a/import-map-deno.json b/import-map-deno.json index dcc4169..98eee48 100644 --- a/import-map-deno.json +++ b/import-map-deno.json @@ -24,8 +24,6 @@ "@virtualstate/navigation-imported/event-target/sync": "./src/event-target/sync-event-target.ts", "@virtualstate/navigation-imported/event-target/async": "./src/event-target/async-event-target.ts", "@virtualstate/navigation-imported/event-target": "./src/event-target/sync-event-target.ts", - "@ungap/structured-clone": "https://cdn.skypack.dev/@ungap/structured-clone", - "@ungap/structured-clone/json": "https://cdn.skypack.dev/@ungap/structured-clone/json", "dom-lite": "https://cdn.skypack.dev/dom-lite", "iterable": "https://cdn.skypack.dev/iterable@6.0.1-beta.5", "https://cdn.skypack.dev/-/iterable@v5.7.0-CNtyuMJo9f2zFu6CuB1D/dist=es2019,mode=imports/optimized/iterable.js": "https://cdn.skypack.dev/iterable@6.0.1-beta.5", @@ -84,9 +82,9 @@ "./src/global-abort-controller": "./src/global-abort-controller.ts", "./esnext/util/parse-dom-deno.js": "./src/util/parse-dom-deno.ts", "./esnext/util/parse-dom.js": "./src/util/parse-dom-deno.ts", - "./esnext/util/structured-json": "./src/util/structured-json.ts", - "./esnext/util/structured-json.js": "./src/util/structured-json.ts", - "./src/util/structured-json.js": "./src/util/structured-json.ts", + "./esnext/util/serialization": "./src/util/serialization.ts", + "./esnext/util/serialization.js": "./src/util/serialization.ts", + "./src/util/serialization.js": "./src/util/serialization.ts", "./src/config": "./src/config.ts", "./src/is": "./src/is.ts", "./src/base-url": "./src/base-url.ts", diff --git a/package.json b/package.json index 6a62489..da8bdc6 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,7 @@ "./esnext/polyfill": "./esnext/polyfill.js", "./esnext/get-polyfill": "./esnext/get-polyfill.js", "./esnext/apply-polyfill": "./esnext/apply-polyfill.js", - "./esnext/util/structured-json": "./esnext/util/structured-json.js", + "./esnext/util/serialization": "./esnext/util/serialization.js", "./polyfill": { "require": "./esnext/polyfill-rollup.js", "import": "./esnext/polyfill.js", @@ -84,7 +84,6 @@ "author": "Fabian Cook ", "license": "MIT", "dependencies": { - "@ungap/structured-clone": "^1.0.2", "@virtualstate/composite-key": "^1.0.0" }, "devDependencies": { diff --git a/scripts/post-build.js b/scripts/post-build.js index 2bec979..0e79687 100644 --- a/scripts/post-build.js +++ b/scripts/post-build.js @@ -37,12 +37,10 @@ const cwd = resolve(dirname(pathname), "..") }); await bundle.write({ sourcemap: true, - output: { - file: "./esnext/tests/rollup.js", - }, + file: "./esnext/tests/rollup.js", inlineDynamicImports: true, - format: "cjs", - interop: "auto", + format: "es", + interop: "esModule", globals: { "esnext/tests/navigation.playwright.js": "globalThis" } @@ -100,9 +98,7 @@ const cwd = resolve(dirname(pathname), "..") }); await bundle.write({ sourcemap: true, - output: { - file: "./esnext/routes-rollup.js", - }, + file: "./esnext/routes-rollup.js", inlineDynamicImports: true, format: "esm", interop: "auto", @@ -137,15 +133,14 @@ const cwd = resolve(dirname(pathname), "..") }); await bundle.write({ sourcemap: true, - output: { - file: "./esnext/polyfill-rollup.js", - }, + file: "./esnext/polyfill-rollup.js", inlineDynamicImports: true, format: "cjs", - interop: "auto", + interop: "esModule", globals: { - } + }, + exports: "auto" }); } @@ -213,43 +208,11 @@ if (!process.env.NO_COVERAGE_BADGE_UPDATE) { { - async function rollupReplacements(fileName) { - let file = await fs.readFile(fileName, "utf-8"); - - const importJsonMarker = "/** post rollup replace json **/"; - - function replaceInsideMarkers(marker, replacement) { - const startIndex = file.indexOf(marker), - endIndex = file.lastIndexOf(marker) + marker.length; - - const fileStart = file.slice(0, startIndex - 1), - fileEnd = file.slice(endIndex + 1); - - const replacing = file.slice(startIndex, endIndex); - - if (typeof replacement === "function") { - replacement = replacement(replacing.replaceAll(marker, "")); - } - - file = `${fileStart}\n\n${replacement}\n\n${fileEnd}` - } - - replaceInsideMarkers(importJsonMarker, "const getStructuredClone = () => json;"); - - await fs.writeFile(fileName, file); - } - - await rollupReplacements("esnext/polyfill-rollup.js"); - - await fs.cp("esnext/rollup.js", "esnext/rollup-input.cjs"); - - await rollupReplacements("esnext/rollup-input.cjs"); - { const bundle = await rollup({ - input: "./esnext/rollup-input.cjs", + input: "./esnext/rollup.js", plugins: [ ignore([ `${cwd}/esnext/tests/navigation.playwright.js`, @@ -277,8 +240,6 @@ if (!process.env.NO_COVERAGE_BADGE_UPDATE) { }); } - await fs.rm("./esnext/rollup-input.cjs"); - await fs.cp("esnext/polyfill-rollup.js", "example/polyfill-rollup.js"); await fs.cp("esnext/routes-rollup.js", "example/routes-rollup.js"); await fs.cp("esnext/rollup.js", "example/rollup.js"); diff --git a/src/get-polyfill.ts b/src/get-polyfill.ts index 98e33df..2bcf008 100644 --- a/src/get-polyfill.ts +++ b/src/get-polyfill.ts @@ -7,7 +7,7 @@ import { } from "./navigation"; import { InvalidStateError } from "./navigation-errors"; import { InternalNavigationNavigateOptions, NavigationDownloadRequest, NavigationFormData, NavigationOriginalEvent, NavigationUserInitiated } from "./create-navigation-transition"; -import { stringify, parse } from './util/structured-json'; +import { stringify, parse } from './util/serialization'; import {NavigationHistory} from "./history"; import {like, ok} from "./is"; import { @@ -232,7 +232,8 @@ function getHistoryState( const raw = sessionStorage.getItem(entry.key); if (!raw) return undefined; const state = parse(raw); - if (!isStateHistoryWithMeta(state)) return undefined; + if (!like(state)) return undefined; + if (!isStateHistoryWithMeta(state)) return undefined; return state[NavigationKey].state; } catch { return undefined; diff --git a/src/index.ts b/src/index.ts index d194b34..0aa469e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -16,4 +16,5 @@ export { NavigationCurrentEntryChangeEvent } from "./events"; export { applyPolyfill } from "./apply-polyfill" -export { getPolyfill, getCompletePolyfill } from "./get-polyfill"; \ No newline at end of file +export { getPolyfill, getCompletePolyfill } from "./get-polyfill"; +export { setSerializer, Serializer } from "./util/serialization"; \ No newline at end of file diff --git a/src/polyfill.ts b/src/polyfill.ts index 5eb3f1d..7522630 100644 --- a/src/polyfill.ts +++ b/src/polyfill.ts @@ -1,5 +1,6 @@ import { getNavigation } from "./get-navigation"; import { applyPolyfill, shouldApplyPolyfill } from "./apply-polyfill"; +import { setSerializer } from "./util/serialization"; const navigation = getNavigation(); @@ -12,4 +13,8 @@ if (shouldApplyPolyfill(navigation)) { console.error("Failed to apply polyfill"); console.error(error); } +} + +export { + setSerializer } \ No newline at end of file diff --git a/src/tests/dependencies-input.ts b/src/tests/dependencies-input.ts index 6446787..726c1f6 100644 --- a/src/tests/dependencies-input.ts +++ b/src/tests/dependencies-input.ts @@ -22,9 +22,7 @@ export const DefaultDependencies = [ "@virtualstate/composite-key", "dom-lite", "iterable", - "urlpattern-polyfill", - "@ungap/structured-clone", - "@ungap/structured-clone/json" + "urlpattern-polyfill" ] as const; export const DefaultImportMap = Object.fromEntries( DefaultDependencies.filter( diff --git a/src/types/structured-json.d.ts b/src/types/structured-json.d.ts deleted file mode 100644 index 13f00c0..0000000 --- a/src/types/structured-json.d.ts +++ /dev/null @@ -1,4 +0,0 @@ -declare module "@ungap/structured-clone/json" { - export const stringify: (x: any) => string - export const parse: (x: string) => any -} \ No newline at end of file diff --git a/src/util/serialization.ts b/src/util/serialization.ts new file mode 100644 index 0000000..4964476 --- /dev/null +++ b/src/util/serialization.ts @@ -0,0 +1,18 @@ +export interface Serializer { + stringify(value: unknown): string; + parse(value: string): unknown +} + +let GLOBAL_SERIALIZER: Serializer = JSON; + +export function setSerializer(serializer: Serializer) { + GLOBAL_SERIALIZER = serializer; +} + +export function stringify(value: unknown) { + return GLOBAL_SERIALIZER.stringify(value); +} + +export function parse(value: string) { + return GLOBAL_SERIALIZER.parse(value); +} \ No newline at end of file diff --git a/src/util/structured-json.ts b/src/util/structured-json.ts deleted file mode 100644 index f214c4c..0000000 --- a/src/util/structured-json.ts +++ /dev/null @@ -1,30 +0,0 @@ - -/** post rollup replace json **/ -const structuredClone = ( - await getStructuredCloneModule() - .catch(structuredCloneFallback) -) -const getStructuredClone = () => structuredClone -/** post rollup replace json **/ - -async function getStructuredCloneModule() { - const { stringify, parse } = await import("@ungap/structured-clone/json") - return { stringify, parse }; -} - -function structuredCloneFallback() { - const stringify = JSON.stringify.bind(JSON), - parse = JSON.parse.bind(JSON); - return { - stringify, - parse - }; -} - -export function stringify(value: unknown) { - return getStructuredClone().stringify(value); -} - -export function parse(value: string) { - return getStructuredClone().parse(value); -} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 2665320..591a841 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1088,11 +1088,6 @@ "@types/node" "*" "@types/webidl-conversions" "*" -"@ungap/structured-clone@^1.0.2": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.0.2.tgz#112bd30f3c27cb4c7b85d59ee3918c13803238ad" - integrity sha512-06PHwE0K24Wi8FBmC8MuMi/+nQ3DTpcXYL3y/IaZz2ScY2GOJXOe8fyMykVXyLOKxpL2Y0frAnJZmm65OxzMLQ== - "@virtualstate/composite-key@^1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@virtualstate/composite-key/-/composite-key-1.0.0.tgz#352b93df5b9199951cc370897bca8bb5b171a375"