Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(dev-server-core): WebSocketsPlugin: polyfill structuredClone to support older browsers (e.g. Safari < 15.4) #2832

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/slimy-moles-call.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@web/dev-server-core': patch
---

WebSocketsPlugin: polyfill `structuredClone` to support older browsers
10 changes: 9 additions & 1 deletion packages/dev-server-core/src/web-sockets/webSocketsPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,14 @@ export function webSocketsPlugin(): Plugin {
// this code is inlined because TS compiles to CJS but we need this to be ESM
return `

/**
* StructuredJSON copied as-is from https://github.com/ungap/structured-clone/blob/v1.2.0/structured-json.js
*/
var StructuredJSON=function(e){"use strict";const r="object"==typeof self?self:globalThis,t=e=>((e,t)=>{const s=(r,t)=>(e.set(t,r),r),n=c=>{if(e.has(c))return e.get(c);const[o,a]=t[c];switch(o){case 0:case-1:return s(a,c);case 1:{const e=s([],c);for(const r of a)e.push(n(r));return e}case 2:{const e=s({},c);for(const[r,t]of a)e[n(r)]=n(t);return e}case 3:return s(new Date(a),c);case 4:{const{source:e,flags:r}=a;return s(new RegExp(e,r),c)}case 5:{const e=s(new Map,c);for(const[r,t]of a)e.set(n(r),n(t));return e}case 6:{const e=s(new Set,c);for(const r of a)e.add(n(r));return e}case 7:{const{name:e,message:t}=a;return s(new r[e](t),c)}case 8:return s(BigInt(a),c);case"BigInt":return s(Object(BigInt(a)),c)}return s(new r[o](a),c)};return n})(new Map,e)(0),s="",{toString:n}={},{keys:c}=Object,o=e=>{const r=typeof e;if("object"!==r||!e)return[0,r];const t=n.call(e).slice(8,-1);switch(t){case"Array":return[1,s];case"Object":return[2,s];case"Date":return[3,s];case"RegExp":return[4,s];case"Map":return[5,s];case"Set":return[6,s]}return t.includes("Array")?[1,t]:t.includes("Error")?[7,t]:[2,t]},a=([e,r])=>0===e&&("function"===r||"symbol"===r),u=(e,{json:r,lossy:t}={})=>{const s=[];return((e,r,t,s)=>{const n=(e,r)=>{const n=s.push(e)-1;return t.set(r,n),n},u=s=>{if(t.has(s))return t.get(s);let[i,f]=o(s);switch(i){case 0:{let r=s;switch(f){case"bigint":i=8,r=s.toString();break;case"function":case"symbol":if(e)throw new TypeError("unable to serialize "+f);r=null;break;case"undefined":return n([-1],s)}return n([i,r],s)}case 1:{if(f)return n([f,[...s]],s);const e=[],r=n([i,e],s);for(const r of s)e.push(u(r));return r}case 2:{if(f)switch(f){case"BigInt":return n([f,s.toString()],s);case"Boolean":case"Number":case"String":return n([f,s.valueOf()],s)}if(r&&"toJSON"in s)return u(s.toJSON());const t=[],l=n([i,t],s);for(const r of c(s))!e&&a(o(s[r]))||t.push([u(r),u(s[r])]);return l}case 3:return n([i,s.toISOString()],s);case 4:{const{source:e,flags:r}=s;return n([i,{source:e,flags:r}],s)}case 5:{const r=[],t=n([i,r],s);for(const[t,n]of s)(e||!a(o(t))&&!a(o(n)))&&r.push([u(t),u(n)]);return t}case 6:{const r=[],t=n([i,r],s);for(const t of s)!e&&a(o(t))||r.push(u(t));return t}}const{message:l}=s;return n([i,{name:f,message:l}],s)};return u})(!(r||t),!!r,new Map,s)(e),s},{parse:i,stringify:f}=JSON,l={json:!0,lossy:!0};return e.parse=e=>t(i(e)),e.stringify=e=>f(u(e,l)),e}({});

// Not the exact same behaviour as structuredClone, but good enough for the basic cases.
const structuredClonePolyfill = (obj) => StructuredJSON.parse(StructuredJSON.stringify(obj));

/**
* Code at this indent adapted from fast-safe-stringify by David Mark Clements
* @license MIT
Expand All @@ -40,7 +48,7 @@ export function webSocketsPlugin(): Plugin {
}

export function stable (obj, replacer, spacer) {
var target = structuredClone(obj)
var target = ('structuredClone' in globalThis) ? structuredClone(obj) : structuredClonePolyfill(obj)
Copy link
Member

@bashmish bashmish Oct 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

normally we recommend to setup polyfills via the polyfills loader plugin for your app/feature
https://github.com/modernweb-dev/web/tree/master/packages/dev-server-polyfill
in this case the API in question is used in the core of WDS itself, so a little bit different situation, but in the long run I think it might still be better to manage polyfills separately using the generic approach, I recommend you to consider that

var tmp = deterministicDecirc(target, '', [], undefined) || target
var res
if (replacerStack.length === 0) {
Expand Down