diff --git a/.changeset/young-guests-tap.md b/.changeset/young-guests-tap.md new file mode 100644 index 00000000..9ff143c7 --- /dev/null +++ b/.changeset/young-guests-tap.md @@ -0,0 +1,5 @@ +--- +'wmr': minor +--- + +Add fetching external resources using native fetch API when Node.js version 18 later diff --git a/packages/wmr/src/lib/prerender.js b/packages/wmr/src/lib/prerender.js index 9f5297ca..ad282919 100644 --- a/packages/wmr/src/lib/prerender.js +++ b/packages/wmr/src/lib/prerender.js @@ -96,11 +96,43 @@ async function workerCode({ cwd, out, publicPath, customRoutes }) { let head = { lang: '', title: '', elements: new Set() }; globalThis.wmr = { ssr: { head } }; - // @ts-ignore - globalThis.fetch = async url => { + async function localFetcher(url) { const text = () => fs.readFile(`${out}/${String(url).replace(/^\//, '')}`, 'utf-8'); return { text, json: () => text().then(JSON.parse) }; - }; + } + + const pattern = /^\//; + if (globalThis.fetch === undefined) { + // When Node.js version under 17, native fetch API undefined + // So it will give a warning when the syntax to retrieve the file and the url is passed + // @ts-ignore + globalThis.fetch = async url => { + if (pattern.test(String(url))) { + return localFetcher(url); + } + + console.warn(`fetch is not defined in Node.js version under 17, please upgrade to Node.js version 18 later`); + }; + } else { + // At that time, implement using reassignment to avoid entering an infinite loop. + const fetcher = fetch; + // @ts-ignore + delete globalThis.fetch; + + // When Node.js version 18 later, native fetch API is defined + // Override with own implementation if you want to retrieve local files in Node.js 18 later + // ref https://github.com/preactjs/wmr/pull/935#discussion_r977168334 + // @ts-ignore + globalThis.fetch = async (url, options) => { + if (pattern.test(String(url))) { + return localFetcher(url); + } + + // When fetching an external resource, it returns the native fetch API + // though `fetcher` function is an alias created to avoid infinite loops + return fetcher(url, options); + }; + } // Prevent Rollup from transforming `import()` here. const $import = new Function('s', 'return import(s)'); diff --git a/packages/wmr/test/fixtures/prerender-external-resource-fetch/index.html b/packages/wmr/test/fixtures/prerender-external-resource-fetch/index.html new file mode 100644 index 00000000..d2c9fb7d --- /dev/null +++ b/packages/wmr/test/fixtures/prerender-external-resource-fetch/index.html @@ -0,0 +1,10 @@ + + + + + default title + + + + + diff --git a/packages/wmr/test/fixtures/prerender-external-resource-fetch/index.js b/packages/wmr/test/fixtures/prerender-external-resource-fetch/index.js new file mode 100644 index 00000000..e6a8f375 --- /dev/null +++ b/packages/wmr/test/fixtures/prerender-external-resource-fetch/index.js @@ -0,0 +1,4 @@ +export async function prerender() { + const html = await fetch('https://preactjs.com').then(res => res.text()); + return { html, links: ['/'] }; +} diff --git a/packages/wmr/test/fixtures/prerender-resource-fetch/index.js b/packages/wmr/test/fixtures/prerender-resource-fetch/index.js index f0ec977f..765a62ea 100644 --- a/packages/wmr/test/fixtures/prerender-resource-fetch/index.js +++ b/packages/wmr/test/fixtures/prerender-resource-fetch/index.js @@ -1,4 +1,4 @@ export async function prerender() { - const md = await fetch('content.md').then(res => res.text()); + const md = await fetch('/content.md').then(res => res.text()); return { html: md, links: ['/'] }; } diff --git a/packages/wmr/test/production.test.js b/packages/wmr/test/production.test.js index 417bd157..6588a1a7 100644 --- a/packages/wmr/test/production.test.js +++ b/packages/wmr/test/production.test.js @@ -891,6 +891,40 @@ describe('production', () => { const index = await fs.readFile(indexHtml, 'utf8'); expect(index).toMatch(/# hello world/); }); + + // Whether or not to run the test to fetch external resources depends on whether or not a Node.js version 18 later + const isNodeVersionUnder18 = Number(process.version.split('.')[0].slice(1)) < 18; + // Even if you are using Node.js v18 or higher, you will get the error `globalThis.fetch is undefined` here, so get the version from process.version + // if (globalThis.fetch === undefined) + if (isNodeVersionUnder18) { + it.skip('skip should support fetching resources from external links during prerender on Node.js v18 later', () => {}); + it('should not support fetching resources from external links during prerender on Node.js < v18', async () => { + await loadFixture('prerender-external-resource-fetch', env); + instance = await runWmr(env.tmp.path, 'build', '--prerender'); + const code = await instance.done; + + await withLog(instance.output, async () => { + expect(code).toBe(1); + expect(instance.output.join('\n')).toMatch( + /fetch is not defined in Node.js version under 17, please upgrade to Node.js version 18 later/ + ); + }); + }); + } else { + it.skip('skip should not support fetching resources from external links during prerender on Node.js < v18', () => {}); + it('should support fetching resources from external links during prerender on Node.js v18 later', async () => { + await loadFixture('prerender-external-resource-fetch', env); + instance = await runWmr(env.tmp.path, 'build', '--prerender'); + const code = await instance.done; + + // when workflow of wmr runtime uses Node.js 18 + // Until then check locally. + expect(code).toBe(0); + const indexHtml = path.join(env.tmp.path, 'dist', 'index.html'); + const index = await fs.readFile(indexHtml, 'utf8'); + expect(index).toMatch(/Preact/); + }); + } }); describe('Code Splitting', () => {