fix: replace serialize-javascript & random-bytes with custom internal modules
serialize-javascript relies on random-bytes and random-bytes relies on nodes crypto module, unfortunately the crypto module isn't available on the browser, so it breaks the repl. When one tries to upgrade the repl to the latest version of Astro, the browser complains about the missing crypto module. The changes shouldn't affect the behaviour of Astro in any meaning full way.
This commit is contained in:
parent
8d8433ee50
commit
f0d22b9332
7 changed files with 521 additions and 215 deletions
|
@ -120,7 +120,6 @@
|
|||
"resolve": "^1.22.0",
|
||||
"rollup": "^2.70.2",
|
||||
"semver": "^7.3.7",
|
||||
"serialize-javascript": "^6.0.0",
|
||||
"shiki": "^0.10.1",
|
||||
"shorthash": "^0.0.2",
|
||||
"sirv": "^2.0.2",
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
declare module 'serialize-javascript' {
|
||||
export default function serialize(value: any): string;
|
||||
}
|
|
@ -21,7 +21,6 @@ const ALWAYS_EXTERNAL = new Set([
|
|||
...builtinModules.map((name) => `node:${name}`),
|
||||
'@sveltejs/vite-plugin-svelte',
|
||||
'micromark-util-events-to-acorn',
|
||||
'serialize-javascript',
|
||||
'node-fetch',
|
||||
'prismjs',
|
||||
'shiki',
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import type { AstroComponentMetadata, SSRLoadedRenderer } from '../../@types/astro';
|
||||
import type { SSRElement, SSRResult } from '../../@types/astro';
|
||||
import { hydrationSpecifier, serializeListValue } from './util.js';
|
||||
import serializeJavaScript from 'serialize-javascript';
|
||||
import serializeJavaScript from '../utils/serialize-javascript';
|
||||
|
||||
// Serializes props passed into a component so that they can be reused during hydration.
|
||||
// The value is any
|
||||
|
|
47
packages/astro/src/runtime/utils/random-bytes.ts
Normal file
47
packages/astro/src/runtime/utils/random-bytes.ts
Normal file
|
@ -0,0 +1,47 @@
|
|||
// Based off of https://www.npmjs.com/package/@lumeweb/randombytes-browser and https://www.npmjs.com/package/randombytes
|
||||
// This rendition adds better browser support, plus better typescript support
|
||||
|
||||
// limit of Crypto.getRandomValues()
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues
|
||||
const MAX_BYTES = 65536;
|
||||
|
||||
// Node supports requesting up to this number of bytes
|
||||
// https://github.com/nodejs/node/blob/master/lib/internal/crypto/random.js#L48
|
||||
const MAX_UINT32 = 4294967295;
|
||||
|
||||
const GlobalCrypto = globalThis.crypto as Crypto;
|
||||
|
||||
export function randomBytes(size: number, cb?: (...args: any) => any) {
|
||||
if (!(GlobalCrypto && GlobalCrypto.getRandomValues)) {
|
||||
throw new Error('Secure random number generation is not supported by this browser.\nUse Chrome, Firefox or Internet Explorer 11')
|
||||
}
|
||||
|
||||
// phantomjs needs to throw
|
||||
if (size > MAX_UINT32) throw new RangeError('requested too many random bytes');
|
||||
|
||||
let bytes = new Uint32Array(size)
|
||||
|
||||
if (size > 0) { // getRandomValues fails on IE if size == 0
|
||||
if (size > MAX_BYTES) { // this is the max bytes crypto.getRandomValues
|
||||
// can do at once see https://developer.mozilla.org/en-US/docs/Web/API/window.crypto.getRandomValues
|
||||
for (let generated = 0; generated < size; generated += MAX_BYTES) {
|
||||
// buffer.slice automatically checks if the end is past the end of
|
||||
// the buffer so we don't have to here
|
||||
GlobalCrypto.getRandomValues(bytes.slice(generated, generated + MAX_BYTES));
|
||||
}
|
||||
} else {
|
||||
GlobalCrypto.getRandomValues(bytes);
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof cb === 'function') {
|
||||
Promise.resolve().then(() => {
|
||||
return cb(null, bytes);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
export default randomBytes;
|
277
packages/astro/src/runtime/utils/serialize-javascript.ts
Normal file
277
packages/astro/src/runtime/utils/serialize-javascript.ts
Normal file
|
@ -0,0 +1,277 @@
|
|||
// Based off of https://www.npmjs.com/package/serialize-javascript
|
||||
// Had to replace `random-bytes` so it worked better in the browser
|
||||
|
||||
/*
|
||||
Copyright (c) 2014, Yahoo! Inc. All rights reserved.
|
||||
Copyrights licensed under the New BSD License.
|
||||
See the accompanying LICENSE file for terms.
|
||||
*/
|
||||
|
||||
import { randomBytes } from "./random-bytes";
|
||||
|
||||
// Generate an internal UID to make the regexp pattern harder to guess.
|
||||
const UID_LENGTH = 16;
|
||||
const UID = generateUID();
|
||||
const PLACE_HOLDER_REGEXP = new RegExp('(\\\\)?"@__(F|R|D|M|S|A|U|I|B|L)-' + UID + '-(\\d+)__@"', 'g');
|
||||
|
||||
const IS_NATIVE_CODE_REGEXP = /\{\s*\[native code\]\s*\}/g;
|
||||
const IS_PURE_FUNCTION = /function.*?\(/;
|
||||
const IS_ARROW_FUNCTION = /.*?=>.*?/;
|
||||
const UNSAFE_CHARS_REGEXP = /[<>\/\u2028\u2029]/g;
|
||||
|
||||
const RESERVED_SYMBOLS = ['*', 'async'];
|
||||
|
||||
// Mapping of unsafe HTML and invalid JavaScript line terminator chars to their
|
||||
// Unicode char counterparts which are safe to use in JavaScript strings.
|
||||
const ESCAPED_CHARS = {
|
||||
'<': '\\u003C',
|
||||
'>': '\\u003E',
|
||||
'/': '\\u002F',
|
||||
'\u2028': '\\u2028',
|
||||
'\u2029': '\\u2029'
|
||||
};
|
||||
|
||||
function escapeUnsafeChars(unsafeChar: keyof typeof ESCAPED_CHARS): typeof ESCAPED_CHARS[keyof typeof ESCAPED_CHARS] {
|
||||
return ESCAPED_CHARS[unsafeChar];
|
||||
}
|
||||
|
||||
function generateUID() {
|
||||
let bytes = randomBytes(UID_LENGTH) as Uint32Array;
|
||||
let result = '';
|
||||
for (let i = 0; i < UID_LENGTH; ++i) {
|
||||
result += bytes[i].toString(16);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function deleteFunctions(obj: Record<any, any>) {
|
||||
let functionKeys = [];
|
||||
for (let key in obj) {
|
||||
if (typeof obj[key] === "function") {
|
||||
functionKeys.push(key);
|
||||
}
|
||||
}
|
||||
for (let i = 0; i < functionKeys.length; i++) {
|
||||
delete obj[functionKeys[i]];
|
||||
}
|
||||
}
|
||||
|
||||
export type TypeGenericFunction = (...args: any[]) => any;
|
||||
export function serialize(obj: Record<any, any> | any, options?: number | string | Record<any, any>): string | any {
|
||||
options || (options = {});
|
||||
|
||||
// Backwards-compatibility for `space` as the second argument.
|
||||
if (typeof options === 'number' || typeof options === 'string') {
|
||||
options = { space: options };
|
||||
}
|
||||
|
||||
let functions: TypeGenericFunction[] = [];
|
||||
let regexps: RegExp[] = [];
|
||||
let dates: Date[] = [];
|
||||
let maps: Map<any, any>[] = [];
|
||||
let sets: Set<any>[] = [];
|
||||
let arrays: any[] = [];
|
||||
let undefs: undefined[] = [];
|
||||
let infinities: (typeof Infinity)[] = [];
|
||||
let bigInts: BigInt[] = [];
|
||||
let urls: URL[] = [];
|
||||
|
||||
// Returns placeholders for functions and regexps (identified by index)
|
||||
// which are later replaced by their string representation.
|
||||
function replacer(key: any, value: any) {
|
||||
|
||||
// For nested function
|
||||
// @ts-ignore
|
||||
if (options.ignoreFunction) {
|
||||
deleteFunctions(value);
|
||||
}
|
||||
|
||||
if (!value && value !== undefined) {
|
||||
return value;
|
||||
}
|
||||
|
||||
// If the value is an object w/ a toJSON method, toJSON is called before
|
||||
// the replacer runs, so we use this[key] to get the non-toJSONed value.
|
||||
// @ts-ignore
|
||||
let origValue = (this as any)[key];
|
||||
let type = typeof origValue;
|
||||
|
||||
if (type === 'object') {
|
||||
if (origValue instanceof RegExp) {
|
||||
return '@__R-' + UID + '-' + (regexps.push(origValue) - 1) + '__@';
|
||||
}
|
||||
|
||||
if (origValue instanceof Date) {
|
||||
return '@__D-' + UID + '-' + (dates.push(origValue) - 1) + '__@';
|
||||
}
|
||||
|
||||
if (origValue instanceof Map) {
|
||||
return '@__M-' + UID + '-' + (maps.push(origValue) - 1) + '__@';
|
||||
}
|
||||
|
||||
if (origValue instanceof Set) {
|
||||
return '@__S-' + UID + '-' + (sets.push(origValue) - 1) + '__@';
|
||||
}
|
||||
|
||||
if (origValue instanceof Array) {
|
||||
let isSparse = origValue.filter(function () { return true }).length !== origValue.length;
|
||||
if (isSparse) {
|
||||
return '@__A-' + UID + '-' + (arrays.push(origValue) - 1) + '__@';
|
||||
}
|
||||
}
|
||||
|
||||
if (origValue instanceof URL) {
|
||||
return '@__L-' + UID + '-' + (urls.push(origValue) - 1) + '__@';
|
||||
}
|
||||
}
|
||||
|
||||
if (type === 'function') {
|
||||
return '@__F-' + UID + '-' + (functions.push(origValue) - 1) + '__@';
|
||||
}
|
||||
|
||||
if (type === 'undefined') {
|
||||
return '@__U-' + UID + '-' + (undefs.push(origValue) - 1) + '__@';
|
||||
}
|
||||
|
||||
if (type === 'number' && !isNaN(origValue) && !isFinite(origValue)) {
|
||||
return '@__I-' + UID + '-' + (infinities.push(origValue) - 1) + '__@';
|
||||
}
|
||||
|
||||
if (type === 'bigint') {
|
||||
return '@__B-' + UID + '-' + (bigInts.push(origValue) - 1) + '__@';
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
function serializeFunc(fn: TypeGenericFunction) {
|
||||
let serializedFn = fn.toString();
|
||||
if (IS_NATIVE_CODE_REGEXP.test(serializedFn)) {
|
||||
throw new TypeError('Serializing native function: ' + fn.name);
|
||||
}
|
||||
|
||||
// pure functions, example: {key: function() {}}
|
||||
if (IS_PURE_FUNCTION.test(serializedFn)) {
|
||||
return serializedFn;
|
||||
}
|
||||
|
||||
// arrow functions, example: arg1 => arg1+5
|
||||
if (IS_ARROW_FUNCTION.test(serializedFn)) {
|
||||
return serializedFn;
|
||||
}
|
||||
|
||||
let argsStartsAt = serializedFn.indexOf('(');
|
||||
let def = serializedFn.substr(0, argsStartsAt)
|
||||
.trim()
|
||||
.split(' ')
|
||||
.filter(function (val: string) { return val.length > 0 });
|
||||
|
||||
let nonReservedSymbols = def.filter(function (val: string) {
|
||||
return RESERVED_SYMBOLS.indexOf(val) === -1
|
||||
});
|
||||
|
||||
// enhanced literal objects, example: {key() {}}
|
||||
if (nonReservedSymbols.length > 0) {
|
||||
return (def.indexOf('async') > -1 ? 'async ' : '') + 'function'
|
||||
+ (def.join('').indexOf('*') > -1 ? '*' : '')
|
||||
+ serializedFn.substr(argsStartsAt);
|
||||
}
|
||||
|
||||
// arrow functions
|
||||
return serializedFn;
|
||||
}
|
||||
|
||||
// Check if the parameter is function
|
||||
if (options.ignoreFunction && typeof obj === "function") {
|
||||
obj = undefined;
|
||||
}
|
||||
// Protects against `JSON.stringify()` returning `undefined`, by serializing
|
||||
// to the literal string: "undefined".
|
||||
if (obj === undefined) {
|
||||
return String(obj);
|
||||
}
|
||||
|
||||
let str;
|
||||
|
||||
// Creates a JSON string representation of the value.
|
||||
// NOTE: Node 0.12 goes into slow mode with extra JSON.stringify() args.
|
||||
if (options.isJSON && !options.space) {
|
||||
str = JSON.stringify(obj);
|
||||
} else {
|
||||
// @ts-ignore
|
||||
str = JSON.stringify(obj, options.isJSON ? null : replacer, options.space);
|
||||
}
|
||||
|
||||
// Protects against `JSON.stringify()` returning `undefined`, by serializing
|
||||
// to the literal string: "undefined".
|
||||
if (typeof str !== 'string') {
|
||||
return String(str);
|
||||
}
|
||||
|
||||
// Replace unsafe HTML and invalid JavaScript line terminator chars with
|
||||
// their safe Unicode char counterpart. This _must_ happen before the
|
||||
// regexps and functions are serialized and added back to the string.
|
||||
if (options.unsafe !== true) {
|
||||
// @ts-ignore
|
||||
str = str.replace(UNSAFE_CHARS_REGEXP, escapeUnsafeChars);
|
||||
}
|
||||
|
||||
if (functions.length === 0 && regexps.length === 0 && dates.length === 0 && maps.length === 0 && sets.length === 0 && arrays.length === 0 && undefs.length === 0 && infinities.length === 0 && bigInts.length === 0 && urls.length === 0) {
|
||||
return str;
|
||||
}
|
||||
|
||||
// Replaces all occurrences of function, regexp, date, map and set placeholders in the
|
||||
// JSON string with their string representations. If the original value can
|
||||
// not be found, then `undefined` is used.
|
||||
// @ts-ignore
|
||||
return str.replace(PLACE_HOLDER_REGEXP, function (match, backSlash, type, valueIndex) {
|
||||
// The placeholder may not be preceded by a backslash. This is to prevent
|
||||
// replacing things like `"a\"@__R-<UID>-0__@"` and thus outputting
|
||||
// invalid JS.
|
||||
if (backSlash) {
|
||||
return match;
|
||||
}
|
||||
|
||||
if (type === 'D') {
|
||||
return "new Date(\"" + dates[valueIndex].toISOString() + "\")";
|
||||
}
|
||||
|
||||
if (type === 'R') {
|
||||
return "new RegExp(" + serialize(regexps[valueIndex].source) + ", \"" + regexps[valueIndex].flags + "\")";
|
||||
}
|
||||
|
||||
if (type === 'M') {
|
||||
return "new Map(" + serialize(Array.from(maps[valueIndex].entries()), options) + ")";
|
||||
}
|
||||
|
||||
if (type === 'S') {
|
||||
return "new Set(" + serialize(Array.from(sets[valueIndex].values()), options) + ")";
|
||||
}
|
||||
|
||||
if (type === 'A') {
|
||||
return "Array.prototype.slice.call(" + serialize(Object.assign({ length: arrays[valueIndex].length }, arrays[valueIndex]), options) + ")";
|
||||
}
|
||||
|
||||
if (type === 'U') {
|
||||
return 'undefined'
|
||||
}
|
||||
|
||||
if (type === 'I') {
|
||||
return infinities[valueIndex];
|
||||
}
|
||||
|
||||
if (type === 'B') {
|
||||
return "BigInt(\"" + bigInts[valueIndex] + "\")";
|
||||
}
|
||||
|
||||
if (type === 'L') {
|
||||
return "new URL(\"" + urls[valueIndex].toString() + "\")";
|
||||
}
|
||||
|
||||
let fn = functions[valueIndex];
|
||||
|
||||
return serializeFunc(fn);
|
||||
});
|
||||
}
|
||||
|
||||
export default serialize;
|
405
pnpm-lock.yaml
405
pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue