Skip to content

Commit

Permalink
[Next] fetch support (#1563)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
natemoo-re authored Oct 15, 2021
1 parent e0e5088 commit 4470c81
Show file tree
Hide file tree
Showing 9 changed files with 103 additions and 21 deletions.
11 changes: 1 addition & 10 deletions docs/src/pages/guides/data-fetching.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,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';

Expand All @@ -54,11 +53,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
2 changes: 2 additions & 0 deletions packages/astro/src/runtime/vite/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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),
Expand Down
58 changes: 58 additions & 0 deletions packages/astro/src/runtime/vite/plugin-fetch.ts
Original file line number Diff line number Diff line change
@@ -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 }
},
};
}
22 changes: 14 additions & 8 deletions packages/astro/test/fetch.test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
/**
* UNCOMMENT: add fetch() in component support
import { expect } from 'chai';
import cheerio from 'cheerio';
import { loadFixture } from './test-utils.js';
Expand All @@ -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($('#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($('#jsx').text()).to.equal('function');
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', () => {});
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<span id="astro-component">{typeof fetch}</span>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<span id="svelte">{ typeof fetch }</span>
16 changes: 16 additions & 0 deletions packages/astro/test/fixtures/fetch/src/components/VueComponent.vue
Original file line number Diff line number Diff line change
@@ -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>

13 changes: 10 additions & 3 deletions packages/astro/test/fixtures/fetch/src/pages/index.astro
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
---
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">
<head>
<title>Global fetch</title>
</head>
<body>
<Child />
<span id="astro-page">{typeof fetch}</span>
<Test />
<JsxComponent />
<SvelteComponent />
<VueComponent />
</body>
</html>
</html>

0 comments on commit 4470c81

Please sign in to comment.