[Next] fetch
support (#1563)
* fix: polyfill fetch in every ssr scenario * test(fetch): update fetch tests * docs: update data fetching guide to remove caveats about `fetch` and isomorphic usage * refactor: update regex for clarity
This commit is contained in:
parent
7749e18066
commit
62106902bb
9 changed files with 103 additions and 21 deletions
|
@ -32,11 +32,10 @@ console.log(data);
|
||||||
|
|
||||||
## Using `fetch()` outside of Astro Components
|
## Using `fetch()` outside of Astro Components
|
||||||
|
|
||||||
If you want to use `fetch()` in a non-astro component, use the [`node-fetch`](https://github.com/node-fetch/node-fetch) library:
|
If you want to use `fetch()` in a non-astro component, it is also globally available:
|
||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
// Movies.tsx
|
// Movies.tsx
|
||||||
import fetch from 'node-fetch';
|
|
||||||
import type { FunctionalComponent } from 'preact';
|
import type { FunctionalComponent } from 'preact';
|
||||||
import { h } from 'preact';
|
import { h } from 'preact';
|
||||||
|
|
||||||
|
@ -55,11 +54,3 @@ const Movies: FunctionalComponent = () => {
|
||||||
|
|
||||||
export default Movies;
|
export default Movies;
|
||||||
```
|
```
|
||||||
|
|
||||||
If you load a component using `node-fetch` [interactively](/core-concepts/component-hydration), with `client:load`, `client:visible`, etc., you'll need to either not use `node-fetch` or switch to an [isomorphic](https://en.wikipedia.org/wiki/Isomorphic_JavaScript) library that will run both at build time and on the client, as the [`node-fetch` README.md](https://github.com/node-fetch/node-fetch#motivation) recommends:
|
|
||||||
|
|
||||||
> Instead of implementing XMLHttpRequest in Node.js to run browser-specific [Fetch polyfill](https://github.com/github/fetch), why not go from native http to fetch API directly? Hence, node-fetch, minimal code for a window.fetch compatible API on Node.js runtime.
|
|
||||||
>
|
|
||||||
> See Jason Miller's [isomorphic-unfetch](https://www.npmjs.com/package/isomorphic-unfetch) or Leonardo Quixada's [cross-fetch](https://github.com/lquixada/cross-fetch) for isomorphic usage (exports node-fetch for server-side, whatwg-fetch for client-side).
|
|
||||||
|
|
||||||
> Quoted from https://github.com/node-fetch/node-fetch#motivation
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ import astroVitePlugin from './plugin-astro.js';
|
||||||
import astroPostprocessVitePlugin from './plugin-astro-postprocess.js';
|
import astroPostprocessVitePlugin from './plugin-astro-postprocess.js';
|
||||||
import markdownVitePlugin from './plugin-markdown.js';
|
import markdownVitePlugin from './plugin-markdown.js';
|
||||||
import jsxVitePlugin from './plugin-jsx.js';
|
import jsxVitePlugin from './plugin-jsx.js';
|
||||||
|
import fetchVitePlugin from './plugin-fetch.js';
|
||||||
import { AstroDevServer } from '../../dev';
|
import { AstroDevServer } from '../../dev';
|
||||||
|
|
||||||
const require = createRequire(import.meta.url);
|
const require = createRequire(import.meta.url);
|
||||||
|
@ -86,6 +87,7 @@ export async function loadViteConfig(
|
||||||
markdownVitePlugin({ config: astroConfig, devServer }),
|
markdownVitePlugin({ config: astroConfig, devServer }),
|
||||||
jsxVitePlugin({ config: astroConfig, logging }),
|
jsxVitePlugin({ config: astroConfig, logging }),
|
||||||
astroPostprocessVitePlugin({ config: astroConfig, devServer }),
|
astroPostprocessVitePlugin({ config: astroConfig, devServer }),
|
||||||
|
fetchVitePlugin(),
|
||||||
...plugins
|
...plugins
|
||||||
],
|
],
|
||||||
publicDir: fileURLToPath(astroConfig.public),
|
publicDir: fileURLToPath(astroConfig.public),
|
||||||
|
|
58
packages/astro/src/runtime/vite/plugin-fetch.ts
Normal file
58
packages/astro/src/runtime/vite/plugin-fetch.ts
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
import type { Plugin } from 'vite';
|
||||||
|
import MagicString from 'magic-string';
|
||||||
|
|
||||||
|
// https://github.com/vitejs/vite/discussions/5109#discussioncomment-1450726
|
||||||
|
function isSSR(options: undefined | boolean | { ssr: boolean }): boolean {
|
||||||
|
if (options === undefined) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (typeof options === 'boolean') {
|
||||||
|
return options
|
||||||
|
}
|
||||||
|
if (typeof options == 'object') {
|
||||||
|
return !!options.ssr
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// This matches any JS-like file (that we know of)
|
||||||
|
// See https://regex101.com/r/Cgofir/1
|
||||||
|
const SUPPORTED_FILES = /\.(astro|svelte|vue|[cm]?js|jsx|[cm]?ts|tsx)$/;
|
||||||
|
const DEFINE_FETCH = `import fetch from 'node-fetch';\n`;
|
||||||
|
|
||||||
|
export default function pluginFetch(): Plugin {
|
||||||
|
return {
|
||||||
|
name: '@astrojs/vite-plugin-fetch',
|
||||||
|
enforce: 'post',
|
||||||
|
async transform(code, id, opts) {
|
||||||
|
const ssr = isSSR(opts);
|
||||||
|
|
||||||
|
// If this isn't an SSR pass, `fetch` will already be available!
|
||||||
|
if (!ssr) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only transform JS-like files
|
||||||
|
if (!id.match(SUPPORTED_FILES)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optimization: only run on probable matches
|
||||||
|
if (!code.includes('fetch')) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const s = new MagicString(code);
|
||||||
|
s.prepend(DEFINE_FETCH);
|
||||||
|
|
||||||
|
const result = s.toString();
|
||||||
|
|
||||||
|
const map = s.generateMap({
|
||||||
|
source: id,
|
||||||
|
includeContent: true
|
||||||
|
});
|
||||||
|
|
||||||
|
return { code: result, map }
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
|
@ -1,5 +1,3 @@
|
||||||
/**
|
|
||||||
* UNCOMMENT: add fetch() in component support
|
|
||||||
import { expect } from 'chai';
|
import { expect } from 'chai';
|
||||||
import cheerio from 'cheerio';
|
import cheerio from 'cheerio';
|
||||||
import { loadFixture } from './test-utils.js';
|
import { loadFixture } from './test-utils.js';
|
||||||
|
@ -11,14 +9,22 @@ before(async () => {
|
||||||
await fixture.build();
|
await fixture.build();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
describe('Global Fetch', () => {
|
describe('Global Fetch', () => {
|
||||||
it('Is available in non-Astro components.', async () => {
|
it('Is available in Astro pages', async () => {
|
||||||
const html = await fixture.readFile('/index.html');
|
const html = await fixture.readFile('/index.html');
|
||||||
const $ = cheerio.load(html);
|
const $ = cheerio.load(html);
|
||||||
expect($('#jsx').text()).to.equal('function');
|
expect($('#astro-page').text()).to.equal('function', 'Fetch supported in .astro page');
|
||||||
|
});
|
||||||
|
it('Is available in Astro components', async () => {
|
||||||
|
const html = await fixture.readFile('/index.html');
|
||||||
|
const $ = cheerio.load(html);
|
||||||
|
expect($('#astro-component').text()).to.equal('function', 'Fetch supported in .astro components');
|
||||||
|
});
|
||||||
|
it('Is available in non-Astro components', async () => {
|
||||||
|
const html = await fixture.readFile('/index.html');
|
||||||
|
const $ = cheerio.load(html);
|
||||||
|
expect($('#jsx').text()).to.equal('function', 'Fetch supported in .jsx');
|
||||||
|
expect($('#svelte').text()).to.equal('function', 'Fetch supported in .svelte');
|
||||||
|
expect($('#vue').text()).to.equal('function', 'Fetch supported in .vue');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
*/
|
|
||||||
|
|
||||||
it.skip('is skipped', () => {});
|
|
||||||
|
|
1
packages/astro/test/fixtures/fetch/src/components/AstroComponent.astro
vendored
Normal file
1
packages/astro/test/fixtures/fetch/src/components/AstroComponent.astro
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<span id="astro-component">{typeof fetch}</span>
|
1
packages/astro/test/fixtures/fetch/src/components/SvelteComponent.svelte
vendored
Normal file
1
packages/astro/test/fixtures/fetch/src/components/SvelteComponent.svelte
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<span id="svelte">{ typeof fetch }</span>
|
16
packages/astro/test/fixtures/fetch/src/components/VueComponent.vue
vendored
Normal file
16
packages/astro/test/fixtures/fetch/src/components/VueComponent.vue
vendored
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
<template>
|
||||||
|
<span id="vue">{{ type }}</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { defineComponent } from 'vue'
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
setup() {
|
||||||
|
return {
|
||||||
|
type: typeof fetch
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
---
|
---
|
||||||
import Child from '../components/Child.jsx';
|
import Test from '../components/AstroComponent.astro';
|
||||||
|
import JsxComponent from '../components/JsxComponent.jsx';
|
||||||
|
import SvelteComponent from '../components/SvelteComponent.svelte';
|
||||||
|
import VueComponent from '../components/VueComponent.vue';
|
||||||
---
|
---
|
||||||
|
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
|
@ -7,6 +10,10 @@ import Child from '../components/Child.jsx';
|
||||||
<title>Global fetch</title>
|
<title>Global fetch</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<Child />
|
<span id="astro-page">{typeof fetch}</span>
|
||||||
|
<Test />
|
||||||
|
<JsxComponent />
|
||||||
|
<SvelteComponent />
|
||||||
|
<VueComponent />
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
Loading…
Add table
Reference in a new issue