Make fetch
injection safe to existing code (#2135)
* fix: inject `fetch` only if not declared * test: improve fetch test * chore(lint): Prettier fix * chore: trigger ci * fix: improve type narrowing Co-authored-by: GitHub Action <github-action@users.noreply.github.com>
This commit is contained in:
parent
1298fd8678
commit
77c3fda379
8 changed files with 57 additions and 0 deletions
5
.changeset/wet-insects-dance.md
Normal file
5
.changeset/wet-insects-dance.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
'astro': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Patch `fetch` support to prioritize authored code. Existing `fetch` imports and declarations are respected.
|
|
@ -76,6 +76,7 @@
|
||||||
"es-module-lexer": "^0.7.1",
|
"es-module-lexer": "^0.7.1",
|
||||||
"esbuild": "0.13.7",
|
"esbuild": "0.13.7",
|
||||||
"estree-util-value-to-estree": "^1.2.0",
|
"estree-util-value-to-estree": "^1.2.0",
|
||||||
|
"estree-walker": "^3.0.0",
|
||||||
"fast-glob": "^3.2.7",
|
"fast-glob": "^3.2.7",
|
||||||
"fast-xml-parser": "^3.19.0",
|
"fast-xml-parser": "^3.19.0",
|
||||||
"html-entities": "^2.3.2",
|
"html-entities": "^2.3.2",
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import type { Plugin } from '../core/vite';
|
import type { Plugin } from '../core/vite';
|
||||||
|
import type { BaseNode, Identifier } from 'estree';
|
||||||
import MagicString from 'magic-string';
|
import MagicString from 'magic-string';
|
||||||
|
import { walk } from 'estree-walker';
|
||||||
|
|
||||||
// https://github.com/vitejs/vite/discussions/5109#discussioncomment-1450726
|
// https://github.com/vitejs/vite/discussions/5109#discussioncomment-1450726
|
||||||
function isSSR(options: undefined | boolean | { ssr: boolean }): boolean {
|
function isSSR(options: undefined | boolean | { ssr: boolean }): boolean {
|
||||||
|
@ -21,6 +23,10 @@ const SUPPORTED_FILES = /\.(astro|svelte|vue|[cm]?js|jsx|[cm]?ts|tsx)$/;
|
||||||
const IGNORED_MODULES = [/astro\/dist\/runtime\/server/, /\/node-fetch\//];
|
const IGNORED_MODULES = [/astro\/dist\/runtime\/server/, /\/node-fetch\//];
|
||||||
const DEFINE_FETCH = `import fetch from 'node-fetch';\n`;
|
const DEFINE_FETCH = `import fetch from 'node-fetch';\n`;
|
||||||
|
|
||||||
|
function isIdentifier(node: BaseNode): node is Identifier {
|
||||||
|
return node.type === 'Identifier';
|
||||||
|
}
|
||||||
|
|
||||||
export default function pluginFetch(): Plugin {
|
export default function pluginFetch(): Plugin {
|
||||||
return {
|
return {
|
||||||
name: '@astrojs/vite-plugin-fetch',
|
name: '@astrojs/vite-plugin-fetch',
|
||||||
|
@ -39,6 +45,26 @@ export default function pluginFetch(): Plugin {
|
||||||
if (!code.includes('fetch')) {
|
if (!code.includes('fetch')) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ast = this.parse(code);
|
||||||
|
let fetchDeclared = false;
|
||||||
|
walk(ast, {
|
||||||
|
enter(node, parent) {
|
||||||
|
if (fetchDeclared) return this.skip();
|
||||||
|
if (isIdentifier(node)) {
|
||||||
|
// Identifier is OK in any type of Expression (CallExpression, UnaryExpression, etc)
|
||||||
|
if (node.name === 'fetch' && !parent.type.endsWith('Expression')) {
|
||||||
|
fetchDeclared = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Fetch is already declared, do not inject a re-declaration!
|
||||||
|
if (fetchDeclared) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
// Ignore specific modules
|
// Ignore specific modules
|
||||||
for (const ignored of IGNORED_MODULES) {
|
for (const ignored of IGNORED_MODULES) {
|
||||||
if (id.match(ignored)) {
|
if (id.match(ignored)) {
|
||||||
|
|
|
@ -27,4 +27,10 @@ describe('Global Fetch', () => {
|
||||||
expect($('#svelte').text()).to.equal('function', 'Fetch supported in .svelte');
|
expect($('#svelte').text()).to.equal('function', 'Fetch supported in .svelte');
|
||||||
expect($('#vue').text()).to.equal('function', 'Fetch supported in .vue');
|
expect($('#vue').text()).to.equal('function', 'Fetch supported in .vue');
|
||||||
});
|
});
|
||||||
|
it('Respects existing code', async () => {
|
||||||
|
const html = await fixture.readFile('/index.html');
|
||||||
|
const $ = cheerio.load(html);
|
||||||
|
expect($('#already-imported').text()).to.equal('function', 'Existing fetch imports respected');
|
||||||
|
expect($('#custom-declaration').text()).to.equal('number', 'Custom fetch declarations respected');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
5
packages/astro/test/fixtures/fetch/src/components/AlreadyImported.astro
vendored
Normal file
5
packages/astro/test/fixtures/fetch/src/components/AlreadyImported.astro
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
import fetch from 'node-fetch'
|
||||||
|
---
|
||||||
|
|
||||||
|
<span id="already-imported">{typeof fetch}</span>
|
5
packages/astro/test/fixtures/fetch/src/components/CustomDeclaration.astro
vendored
Normal file
5
packages/astro/test/fixtures/fetch/src/components/CustomDeclaration.astro
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
const fetch = 0;
|
||||||
|
---
|
||||||
|
|
||||||
|
<span id="custom-declaration">{typeof fetch}</span>
|
|
@ -1,5 +1,7 @@
|
||||||
---
|
---
|
||||||
import Test from '../components/AstroComponent.astro';
|
import Test from '../components/AstroComponent.astro';
|
||||||
|
import AlreadyImported from '../components/AlreadyImported.astro';
|
||||||
|
import CustomDeclaration from '../components/CustomDeclaration.astro';
|
||||||
import JsxComponent from '../components/JsxComponent.jsx';
|
import JsxComponent from '../components/JsxComponent.jsx';
|
||||||
import SvelteComponent from '../components/SvelteComponent.svelte';
|
import SvelteComponent from '../components/SvelteComponent.svelte';
|
||||||
import VueComponent from '../components/VueComponent.vue';
|
import VueComponent from '../components/VueComponent.vue';
|
||||||
|
@ -12,6 +14,8 @@ import VueComponent from '../components/VueComponent.vue';
|
||||||
<body>
|
<body>
|
||||||
<span id="astro-page">{typeof fetch}</span>
|
<span id="astro-page">{typeof fetch}</span>
|
||||||
<Test />
|
<Test />
|
||||||
|
<AlreadyImported />
|
||||||
|
<CustomDeclaration />
|
||||||
<JsxComponent />
|
<JsxComponent />
|
||||||
<SvelteComponent />
|
<SvelteComponent />
|
||||||
<VueComponent />
|
<VueComponent />
|
||||||
|
|
|
@ -5213,6 +5213,11 @@ estree-walker@^2.0.1, estree-walker@^2.0.2:
|
||||||
resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac"
|
resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac"
|
||||||
integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==
|
integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==
|
||||||
|
|
||||||
|
estree-walker@^3.0.0:
|
||||||
|
version "3.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-3.0.0.tgz#ca4b284de9dffb255288c76a44870b360faf14f9"
|
||||||
|
integrity sha512-s6ceX0NFiU/vKPiKvFdR83U1Zffu7upwZsGwpoqfg5rbbq1l50WQ5hCeIvM6E6oD4shUHCYMsiFPns4Jk0YfMQ==
|
||||||
|
|
||||||
esutils@^2.0.2:
|
esutils@^2.0.2:
|
||||||
version "2.0.3"
|
version "2.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64"
|
resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64"
|
||||||
|
|
Loading…
Reference in a new issue