[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
|
||||
|
||||
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
|
||||
// Movies.tsx
|
||||
import fetch from 'node-fetch';
|
||||
import type { FunctionalComponent } from 'preact';
|
||||
import { h } from 'preact';
|
||||
|
||||
|
@ -55,11 +54,3 @@ const Movies: FunctionalComponent = () => {
|
|||
|
||||
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 markdownVitePlugin from './plugin-markdown.js';
|
||||
import jsxVitePlugin from './plugin-jsx.js';
|
||||
import fetchVitePlugin from './plugin-fetch.js';
|
||||
import { AstroDevServer } from '../../dev';
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
|
@ -86,6 +87,7 @@ export async function loadViteConfig(
|
|||
markdownVitePlugin({ config: astroConfig, devServer }),
|
||||
jsxVitePlugin({ config: astroConfig, logging }),
|
||||
astroPostprocessVitePlugin({ config: astroConfig, devServer }),
|
||||
fetchVitePlugin(),
|
||||
...plugins
|
||||
],
|
||||
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 cheerio from 'cheerio';
|
||||
import { loadFixture } from './test-utils.js';
|
||||
|
@ -11,14 +9,22 @@ before(async () => {
|
|||
await fixture.build();
|
||||
});
|
||||
|
||||
|
||||
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 $ = 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">
|
||||
|
@ -7,6 +10,10 @@ import Child from '../components/Child.jsx';
|
|||
<title>Global fetch</title>
|
||||
</head>
|
||||
<body>
|
||||
<Child />
|
||||
<span id="astro-page">{typeof fetch}</span>
|
||||
<Test />
|
||||
<JsxComponent />
|
||||
<SvelteComponent />
|
||||
<VueComponent />
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
|
Loading…
Reference in a new issue