UNPKG

sanity

Version:

Sanity is a real-time content infrastructure with a scalable, hosted backend featuring a Graph Oriented Query Language (GROQ), asset pipelines and fast edge caches

137 lines (118 loc) 3.65 kB
import {ResizeObserver} from '@juggle/resize-observer' import {register as registerESBuild} from 'esbuild-register/dist/node' import jsdomGlobal from 'jsdom-global' import {addHook} from 'pirates' import resolveFrom from 'resolve-from' import {getStudioEnvironmentVariables} from '../server/getStudioEnvironmentVariables' const jsdomDefaultHtml = `<!doctype html> <html> <head><meta charset="utf-8"></head> <body></body> </html>` export function mockBrowserEnvironment(basePath: string): () => void { // Guard against double-registering if (global && global.window && '__mockedBySanity' in global.window) { return () => { /* intentional noop */ } } const domCleanup = jsdomGlobal(jsdomDefaultHtml, {url: 'http://localhost:3333/'}) const windowCleanup = () => global.window.close() const globalCleanup = provideFakeGlobals(basePath) const cleanupFileLoader = addHook( (code, filename) => `module.exports = ${JSON.stringify(filename)}`, { ignoreNodeModules: false, exts: getFileExtensions(), }, ) const {unregister: unregisterESBuild} = registerESBuild({ target: 'node18', format: 'cjs', extensions: ['.js', '.jsx', '.ts', '.tsx', '.mjs'], jsx: 'automatic', define: { // define the `process.env` global ...getStudioEnvironmentVariables({prefix: 'process.env.', jsonEncode: true}), // define the `import.meta.env` global ...getStudioEnvironmentVariables({prefix: 'import.meta.env.', jsonEncode: true}), }, }) return function cleanupBrowserEnvironment() { unregisterESBuild() cleanupFileLoader() globalCleanup() windowCleanup() domCleanup() } } const getFakeGlobals = (basePath: string) => ({ __mockedBySanity: true, requestAnimationFrame: setImmediate, cancelAnimationFrame: clearImmediate, requestIdleCallback: setImmediate, cancelIdleCallback: clearImmediate, ace: tryGetAceGlobal(basePath), InputEvent: global.window?.InputEvent, customElements: global.window?.customElements, ResizeObserver: global.window?.ResizeObserver || ResizeObserver, }) function provideFakeGlobals(basePath: string): () => void { const globalEnv = global as any as Record<string, unknown> const globalWindow = global.window as Record<string, any> const fakeGlobals = getFakeGlobals(basePath) const stubbedGlobalKeys: string[] = [] const stubbedWindowKeys: string[] = [] for (const [rawKey, value] of Object.entries(fakeGlobals)) { if (typeof value === 'undefined') { continue } const key = rawKey as keyof typeof fakeGlobals if (!(key in globalEnv)) { globalEnv[key] = fakeGlobals[key] stubbedGlobalKeys.push(key) } if (!(key in global.window)) { globalWindow[key] = fakeGlobals[key] stubbedWindowKeys.push(key) } } return () => { stubbedGlobalKeys.forEach((key) => { delete globalEnv[key] }) stubbedWindowKeys.forEach((key) => { delete globalWindow[key] }) } } function tryGetAceGlobal(basePath: string) { // Work around an issue where using the @sanity/code-input plugin would crash // due to `ace` not being defined on the global due to odd bundling stategy. const acePath = resolveFrom.silent(basePath, 'ace-builds') if (!acePath) { return undefined } try { // eslint-disable-next-line import/no-dynamic-require return require(acePath) } catch (err) { return undefined } } function getFileExtensions() { return [ '.jpeg', '.jpg', '.png', '.gif', '.svg', '.webp', '.woff', '.woff2', '.ttf', '.eot', '.otf', '.css', ] }