Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Drop Node 14 support #5782

Merged
merged 22 commits into from
Jan 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/chatty-rivers-camp.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@astrojs/lit': patch
---

Only shim fetch if not already present
16 changes: 16 additions & 0 deletions .changeset/curvy-beds-warn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
'astro': major
'@astrojs/prism': major
'create-astro': major
'@astrojs/mdx': minor
'@astrojs/node': major
'@astrojs/preact': major
'@astrojs/react': major
'@astrojs/solid-js': major
'@astrojs/svelte': major
'@astrojs/vercel': major
'@astrojs/vue': major
'@astrojs/telemetry': major
---

Remove support for Node 14. Minimum supported Node version is now >=16.12.0
7 changes: 7 additions & 0 deletions .changeset/stupid-wolves-explain.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@astrojs/webapi': major
---

Replace node-fetch's polyfill with undici.

Since `undici` does not support it, this change also removes custom support for the `file:` protocol
2 changes: 1 addition & 1 deletion packages/astro-prism/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,6 @@
"@types/prismjs": "1.26.0"
},
"engines": {
"node": "^14.18.0 || >=16.12.0"
"node": ">=16.12.0"
}
}
2 changes: 1 addition & 1 deletion packages/astro/astro.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ async function main() {
// it's okay to hard-code the valid Node versions here since they will not change over time.
if (typeof require === 'undefined') {
console.error(`\nNode.js v${version} is not supported by Astro!
Please upgrade to a version of Node.js with complete ESM support: "^14.18.0 || >=16.12.0"\n`);
Please upgrade to a supported version of Node.js: ">=16.12.0"\n`);
}

// Not supported: Report the most helpful error message possible.
Expand Down
4 changes: 2 additions & 2 deletions packages/astro/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,6 @@
"eol": "^0.9.1",
"memfs": "^3.4.7",
"mocha": "^9.2.2",
"node-fetch": "^3.2.5",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🫡

"node-mocks-http": "^1.11.0",
"rehype-autolink-headings": "^6.1.1",
"rehype-slug": "^5.0.1",
Expand All @@ -207,10 +206,11 @@
"rollup": "^3.9.0",
"sass": "^1.52.2",
"srcset-parse": "^1.1.0",
"undici": "^5.14.0",
"unified": "^10.1.2"
},
"engines": {
"node": "^14.18.0 || >=16.12.0",
"node": ">=16.12.0",
"npm": ">=6.14.0"
}
}
21 changes: 16 additions & 5 deletions packages/astro/src/runtime/server/escape.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { escape } from 'html-escaper';
import { streamAsyncIterator } from './util.js';

// Leverage the battle-tested `html-escaper` npm package.
export const escapeHTML = escape;
Expand Down Expand Up @@ -58,9 +59,19 @@ export function isHTMLBytes(value: any): value is HTMLBytes {
return Object.prototype.toString.call(value) === '[object HTMLBytes]';
}

async function* unescapeChunksAsync(iterable: AsyncIterable<Uint8Array>): any {
for await (const chunk of iterable) {
yield unescapeHTML(chunk as BlessedType);
function hasGetReader(obj: unknown): obj is ReadableStream {
return typeof (obj as any).getReader === 'function';
}

async function* unescapeChunksAsync(iterable: ReadableStream | string): any {
if (hasGetReader(iterable)) {
for await (const chunk of streamAsyncIterator(iterable)) {
yield unescapeHTML(chunk as BlessedType);
}
} else {
for await (const chunk of iterable) {
yield unescapeHTML(chunk as BlessedType);
}
}
}

Expand All @@ -82,7 +93,7 @@ export function unescapeHTML(
}
// If a response, stream out the chunks
else if (str instanceof Response && str.body) {
const body = str.body as unknown as AsyncIterable<Uint8Array>;
const body = str.body;
return unescapeChunksAsync(body);
}
// If a promise, await the result and mark that.
Expand All @@ -92,7 +103,7 @@ export function unescapeHTML(
});
} else if (Symbol.iterator in str) {
return unescapeChunks(str);
} else if (Symbol.asyncIterator in str) {
} else if (Symbol.asyncIterator in str || hasGetReader(str)) {
return unescapeChunksAsync(str);
}
}
Expand Down
10 changes: 6 additions & 4 deletions packages/astro/src/runtime/server/response.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { streamAsyncIterator } from './util.js';

const isNodeJS =
typeof process === 'object' && Object.prototype.toString.call(process) === '[object process]';

Expand All @@ -21,9 +23,9 @@ function createResponseClass() {
async text(): Promise<string> {
if (this.#isStream && isNodeJS) {
let decoder = new TextDecoder();
let body = this.#body as AsyncIterable<Uint8Array>;
let body = this.#body;
let out = '';
for await (let chunk of body) {
for await (let chunk of streamAsyncIterator(body)) {
out += decoder.decode(chunk);
}
return out;
Expand All @@ -33,10 +35,10 @@ function createResponseClass() {

async arrayBuffer(): Promise<ArrayBuffer> {
if (this.#isStream && isNodeJS) {
let body = this.#body as AsyncIterable<Uint8Array>;
let body = this.#body;
let chunks: Uint8Array[] = [];
let len = 0;
for await (let chunk of body) {
for await (let chunk of streamAsyncIterator(body)) {
chunks.push(chunk);
len += chunk.length;
}
Expand Down
14 changes: 14 additions & 0 deletions packages/astro/src/runtime/server/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,17 @@ export function serializeListValue(value: any) {
export function isPromise<T = any>(value: any): value is Promise<T> {
return !!value && typeof value === 'object' && typeof value.then === 'function';
}

export async function* streamAsyncIterator(stream: ReadableStream) {
const reader = stream.getReader();

try {
while (true) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

any code with a while(true) gets my approval

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And a try / finally? This code has it all!

const { done, value } = await reader.read();
if (done) return;
yield value;
}
} finally {
reader.releaseLock();
}
}
4 changes: 2 additions & 2 deletions packages/astro/test/ssr-api-route.test.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { expect } from 'chai';
import { loadFixture } from './test-utils.js';
import { File, FormData } from 'undici';
import testAdapter from './test-adapter.js';
import { FormData, File } from 'node-fetch';
import { loadFixture } from './test-utils.js';

describe('API routes in SSR', () => {
/** @type {import('./test-utils').Fixture} */
Expand Down
10 changes: 5 additions & 5 deletions packages/astro/test/streaming.test.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { isWindows, loadFixture } from './test-utils.js';
import { expect } from 'chai';
import testAdapter from './test-adapter.js';
import * as cheerio from 'cheerio';
import testAdapter from './test-adapter.js';
import { isWindows, loadFixture, streamAsyncIterator } from './test-utils.js';

describe('Streaming', () => {
if (isWindows) return;
Expand Down Expand Up @@ -32,7 +32,7 @@ describe('Streaming', () => {
it('Body is chunked', async () => {
let res = await fixture.fetch('/');
let chunks = [];
for await (const bytes of res.body) {
for await (const bytes of streamAsyncIterator(res.body)) {
let chunk = bytes.toString('utf-8');
chunks.push(chunk);
}
Expand Down Expand Up @@ -61,7 +61,7 @@ describe('Streaming', () => {
const response = await app.render(request);
let chunks = [];
let decoder = new TextDecoder();
for await (const bytes of response.body) {
for await (const bytes of streamAsyncIterator(response.body)) {
let chunk = decoder.decode(bytes);
chunks.push(chunk);
}
Expand Down Expand Up @@ -102,7 +102,7 @@ describe('Streaming disabled', () => {
it('Body is chunked', async () => {
let res = await fixture.fetch('/');
let chunks = [];
for await (const bytes of res.body) {
for await (const bytes of streamAsyncIterator(res.body)) {
let chunk = bytes.toString('utf-8');
chunks.push(chunk);
}
Expand Down
16 changes: 15 additions & 1 deletion packages/astro/test/test-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ polyfill(globalThis, {
});

/**
* @typedef {import('node-fetch').Response} Response
* @typedef {import('undici').Response} Response
* @typedef {import('../src/core/dev/dev').DedvServer} DevServer
* @typedef {import('../src/@types/astro').AstroConfig} AstroConfig
* @typedef {import('../src/core/preview/index').PreviewServer} PreviewServer
Expand Down Expand Up @@ -303,3 +303,17 @@ export const isWindows = os.platform() === 'win32';
export function fixLineEndings(str) {
return str.replace(/\r\n/g, '\n');
}

export async function* streamAsyncIterator(stream) {
const reader = stream.getReader();

try {
while (true) {
const { done, value } = await reader.read();
if (done) return;
yield value;
}
} finally {
reader.releaseLock();
}
}
2 changes: 1 addition & 1 deletion packages/create-astro/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,6 @@
"uvu": "^0.5.3"
},
"engines": {
"node": "^14.18.0 || >=16.12.0"
"node": ">=16.12.0"
}
}
9 changes: 8 additions & 1 deletion packages/integrations/lit/server-shim.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import { installWindowOnGlobal } from '@lit-labs/ssr/lib/dom-shim.js';
installWindowOnGlobal();

if(typeof fetch === 'function') {
const _fetch = fetch;
installWindowOnGlobal();
globalThis.fetch = window.fetch = _fetch;
} else {
installWindowOnGlobal();
}

window.global = window;
document.getElementsByTagName = () => [];
Expand Down
2 changes: 1 addition & 1 deletion packages/integrations/mdx/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,6 @@
"vite": "^4.0.3"
},
"engines": {
"node": "^14.18.0 || >=16.12.0"
"node": ">=16.12.0"
}
}
4 changes: 2 additions & 2 deletions packages/integrations/node/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,12 @@
"astro": "workspace:^2.0.0-beta.0"
},
"devDependencies": {
"@types/node-fetch": "^2.6.2",
"@types/send": "^0.17.1",
"astro": "workspace:*",
"astro-scripts": "workspace:*",
"chai": "^4.3.6",
"mocha": "^9.2.2",
"node-mocks-http": "^1.11.0"
"node-mocks-http": "^1.11.0",
"undici": "^5.14.0"
}
}
2 changes: 1 addition & 1 deletion packages/integrations/node/src/response-iterator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* - https://github.com/apollographql/apollo-client/blob/main/src/utilities/common/responseIterator.ts
*/

import type { Response as NodeResponse } from 'node-fetch';
import type { Response as NodeResponse } from 'undici';
import { Readable as NodeReadableStream } from 'stream';

interface NodeStreamIterator<T> {
Expand Down
2 changes: 1 addition & 1 deletion packages/integrations/preact/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,6 @@
"preact": "^10.6.5"
},
"engines": {
"node": "^14.18.0 || >=16.12.0"
"node": ">=16.12.0"
}
}
2 changes: 1 addition & 1 deletion packages/integrations/react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,6 @@
"@types/react-dom": "^17.0.17 || ^18.0.6"
},
"engines": {
"node": "^14.18.0 || >=16.12.0"
"node": ">=16.12.0"
}
}
2 changes: 1 addition & 1 deletion packages/integrations/solid/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,6 @@
"solid-js": "^1.4.3"
},
"engines": {
"node": "^14.18.0 || >=16.12.0"
"node": ">=16.12.0"
}
}
2 changes: 1 addition & 1 deletion packages/integrations/svelte/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,6 @@
"astro": "workspace:^2.0.0-beta.0"
},
"engines": {
"node": "^14.18.0 || >=16.12.0"
"node": ">=16.12.0"
}
}
11 changes: 1 addition & 10 deletions packages/integrations/vercel/src/serverless/entrypoint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,12 @@ import type { SSRManifest } from 'astro';
import { App } from 'astro/app';
import type { IncomingMessage, ServerResponse } from 'node:http';

import * as requestTransformLegacy from './request-transform/legacy.js';
import * as requestTransformNode18 from './request-transform/node18.js';
import { getRequest, setResponse } from './request-transform';

polyfill(globalThis, {
exclude: 'window document',
});

// Node 18+ has a new API for request/response, while older versions use node-fetch
// When we drop support for Node 14, we can remove the legacy code by switching to undici

const nodeVersion = parseInt(process.version.split('.')[0].slice(1)); // 'v14.17.0' -> 14

const { getRequest, setResponse } =
nodeVersion >= 18 ? requestTransformNode18 : requestTransformLegacy;

export const createExports = (manifest: SSRManifest) => {
const app = new App(manifest);

Expand Down
Loading