From 16107b6a10514ef1b563e585ec9add4b14f42b94 Mon Sep 17 00:00:00 2001 From: Chris Swithinbank Date: Mon, 9 Jan 2023 10:23:21 +0100 Subject: [PATCH 01/13] Remove Astro-flavored Markdown from `@astrojs/markdown-remark` (#5785) --- .changeset/poor-chicken-film.md | 5 + .../astro/src/vite-plugin-markdown/index.ts | 1 - packages/markdown/remark/package.json | 10 -- packages/markdown/remark/src/index.ts | 29 +--- .../markdown/remark/src/mdast-util-mdxish.ts | 12 -- packages/markdown/remark/src/mdxjs.ts | 27 ---- .../remark/src/rehype-collect-headings.ts | 21 +-- packages/markdown/remark/src/rehype-escape.ts | 22 --- .../markdown/remark/src/rehype-expressions.ts | 18 --- .../markdown/remark/src/rehype-islands.ts | 43 ------ packages/markdown/remark/src/rehype-jsx.ts | 65 --------- packages/markdown/remark/src/remark-escape.ts | 15 --- .../remark/src/remark-mark-and-unravel.ts | 72 ---------- packages/markdown/remark/src/remark-mdxish.ts | 61 --------- packages/markdown/remark/src/remark-unwrap.ts | 44 ------ packages/markdown/remark/src/types.ts | 1 - .../markdown/remark/test/autolinking.test.js | 72 ---------- .../markdown/remark/test/components.test.js | 78 ----------- .../markdown/remark/test/entities.test.js | 15 +-- .../markdown/remark/test/expressions.test.js | 125 ------------------ .../markdown/remark/test/strictness.test.js | 98 -------------- pnpm-lock.yaml | 20 --- 22 files changed, 12 insertions(+), 842 deletions(-) create mode 100644 .changeset/poor-chicken-film.md delete mode 100644 packages/markdown/remark/src/mdast-util-mdxish.ts delete mode 100644 packages/markdown/remark/src/mdxjs.ts delete mode 100644 packages/markdown/remark/src/rehype-escape.ts delete mode 100644 packages/markdown/remark/src/rehype-expressions.ts delete mode 100644 packages/markdown/remark/src/rehype-islands.ts delete mode 100644 packages/markdown/remark/src/rehype-jsx.ts delete mode 100644 packages/markdown/remark/src/remark-escape.ts delete mode 100644 packages/markdown/remark/src/remark-mark-and-unravel.ts delete mode 100644 packages/markdown/remark/src/remark-mdxish.ts delete mode 100644 packages/markdown/remark/src/remark-unwrap.ts delete mode 100644 packages/markdown/remark/test/components.test.js delete mode 100644 packages/markdown/remark/test/expressions.test.js delete mode 100644 packages/markdown/remark/test/strictness.test.js diff --git a/.changeset/poor-chicken-film.md b/.changeset/poor-chicken-film.md new file mode 100644 index 000000000000..ecbc3b44caa7 --- /dev/null +++ b/.changeset/poor-chicken-film.md @@ -0,0 +1,5 @@ +--- +'@astrojs/markdown-remark': major +--- + +Drop support for legacy Astro-flavored Markdown diff --git a/packages/astro/src/vite-plugin-markdown/index.ts b/packages/astro/src/vite-plugin-markdown/index.ts index 10e4cb8c0743..34ed3647b1ff 100644 --- a/packages/astro/src/vite-plugin-markdown/index.ts +++ b/packages/astro/src/vite-plugin-markdown/index.ts @@ -71,7 +71,6 @@ export default function markdown({ settings, logging }: AstroPluginOptions): Plu const renderResult = await renderMarkdown(raw.content, { ...settings.config.markdown, fileURL: new URL(`file://${fileId}`), - isAstroFlavoredMd: false, isExperimentalContentCollections: settings.config.experimental.contentCollections, contentDir: getContentPaths(settings.config).contentDir, frontmatter: raw.data, diff --git a/packages/markdown/remark/package.json b/packages/markdown/remark/package.json index 8e60a06c3dbe..f8d2ec35e819 100644 --- a/packages/markdown/remark/package.json +++ b/packages/markdown/remark/package.json @@ -25,19 +25,11 @@ "test": "mocha --exit --timeout 20000" }, "dependencies": { - "@astrojs/micromark-extension-mdx-jsx": "^1.0.3", "@astrojs/prism": "^1.0.0", "acorn": "^8.7.1", - "acorn-jsx": "^5.3.2", "github-slugger": "^1.4.0", "hast-util-to-html": "^8.0.3", "import-meta-resolve": "^2.1.0", - "mdast-util-from-markdown": "^1.2.0", - "mdast-util-mdx-expression": "^1.2.1", - "mdast-util-mdx-jsx": "^1.2.0", - "micromark-extension-mdx-expression": "^1.0.3", - "micromark-extension-mdx-md": "^1.0.0", - "micromark-util-combine-extensions": "^1.0.0", "rehype-raw": "^6.1.1", "rehype-stringify": "^9.0.3", "remark-gfm": "^3.0.1", @@ -46,7 +38,6 @@ "remark-smartypants": "^2.0.0", "shiki": "^0.11.1", "unified": "^10.1.2", - "unist-util-map": "^3.1.1", "unist-util-visit": "^4.1.0", "vfile": "^5.3.2" }, @@ -59,7 +50,6 @@ "@types/unist": "^2.0.6", "astro-scripts": "workspace:*", "chai": "^4.3.6", - "micromark-util-types": "^1.0.2", "mocha": "^9.2.2" } } diff --git a/packages/markdown/remark/src/index.ts b/packages/markdown/remark/src/index.ts index 24cffa287b9d..91680f3fd848 100644 --- a/packages/markdown/remark/src/index.ts +++ b/packages/markdown/remark/src/index.ts @@ -8,18 +8,10 @@ import type { import { toRemarkInitializeAstroData } from './frontmatter-injection.js'; import { loadPlugins } from './load-plugins.js'; import { rehypeHeadingIds } from './rehype-collect-headings.js'; -import rehypeEscape from './rehype-escape.js'; -import rehypeExpressions from './rehype-expressions.js'; -import rehypeIslands from './rehype-islands.js'; -import rehypeJsx from './rehype-jsx.js'; import toRemarkContentRelImageError from './remark-content-rel-image-error.js'; -import remarkEscape from './remark-escape.js'; -import remarkMarkAndUnravel from './remark-mark-and-unravel.js'; -import remarkMdxish from './remark-mdxish.js'; import remarkPrism from './remark-prism.js'; import scopedStyles from './remark-scoped-styles.js'; import remarkShiki from './remark-shiki.js'; -import remarkUnwrap from './remark-unwrap.js'; import rehypeRaw from 'rehype-raw'; import rehypeStringify from 'rehype-stringify'; @@ -61,7 +53,6 @@ export async function renderMarkdown( remarkRehype = markdownConfigDefaults.remarkRehype, gfm = markdownConfigDefaults.gfm, smartypants = markdownConfigDefaults.smartypants, - isAstroFlavoredMd = false, isExperimentalContentCollections = false, contentDir, frontmatter: userFrontmatter = {}, @@ -72,7 +63,7 @@ export async function renderMarkdown( let parser = unified() .use(markdown) .use(toRemarkInitializeAstroData({ userFrontmatter })) - .use(isAstroFlavoredMd ? [remarkMdxish, remarkMarkAndUnravel, remarkUnwrap, remarkEscape] : []); + .use([]); if (gfm) { parser.use(remarkGfm); @@ -109,15 +100,7 @@ export async function renderMarkdown( markdownToHtml as any, { allowDangerousHtml: true, - passThrough: isAstroFlavoredMd - ? [ - 'raw', - 'mdxFlowExpression', - 'mdxJsxFlowElement', - 'mdxJsxTextElement', - 'mdxTextExpression', - ] - : [], + passThrough: [], ...remarkRehype, }, ], @@ -127,13 +110,7 @@ export async function renderMarkdown( parser.use([[plugin, pluginOpts]]); }); - parser - .use( - isAstroFlavoredMd - ? [rehypeJsx, rehypeExpressions, rehypeEscape, rehypeIslands, rehypeHeadingIds] - : [rehypeHeadingIds, rehypeRaw] - ) - .use(rehypeStringify, { allowDangerousHtml: true }); + parser.use([rehypeHeadingIds, rehypeRaw]).use(rehypeStringify, { allowDangerousHtml: true }); let vfile: MarkdownVFile; try { diff --git a/packages/markdown/remark/src/mdast-util-mdxish.ts b/packages/markdown/remark/src/mdast-util-mdxish.ts deleted file mode 100644 index 10b19f5be0c9..000000000000 --- a/packages/markdown/remark/src/mdast-util-mdxish.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { mdxExpressionFromMarkdown, mdxExpressionToMarkdown } from 'mdast-util-mdx-expression'; -import { mdxJsxFromMarkdown, mdxJsxToMarkdown } from 'mdast-util-mdx-jsx'; - -export function mdxFromMarkdown(): any { - return [mdxExpressionFromMarkdown, mdxJsxFromMarkdown]; -} - -export function mdxToMarkdown(): any { - return { - extensions: [mdxExpressionToMarkdown, mdxJsxToMarkdown], - }; -} diff --git a/packages/markdown/remark/src/mdxjs.ts b/packages/markdown/remark/src/mdxjs.ts deleted file mode 100644 index c3a9849ea97a..000000000000 --- a/packages/markdown/remark/src/mdxjs.ts +++ /dev/null @@ -1,27 +0,0 @@ -// Note: The code in this file is based on `micromark-extension-mdxjs` -// and was adapted to use our fork `@astrojs/micromark-extension-mdx-jsx` -// instead of `micromark-extension-mdx-jsx` to allow some extended syntax. -// See `@astrojs/micromark-extension-mdx-jsx` on NPM for more details. -// Also, support for ESM imports & exports in Markdown content was removed. - -import { mdxJsx } from '@astrojs/micromark-extension-mdx-jsx'; -import { Parser } from 'acorn'; -import acornJsx from 'acorn-jsx'; -import type { Options } from 'micromark-extension-mdx-expression'; -import { mdxExpression } from 'micromark-extension-mdx-expression'; -import { mdxMd } from 'micromark-extension-mdx-md'; -import { combineExtensions } from 'micromark-util-combine-extensions'; -import type { Extension } from 'micromark-util-types'; - -export function mdxjs(options: Options): Extension { - const settings: any = Object.assign( - { - acorn: Parser.extend(acornJsx()), - acornOptions: { ecmaVersion: 2020, sourceType: 'module' }, - addResult: true, - }, - options - ); - - return combineExtensions([mdxExpression(settings), mdxJsx(settings), mdxMd]); -} diff --git a/packages/markdown/remark/src/rehype-collect-headings.ts b/packages/markdown/remark/src/rehype-collect-headings.ts index 03a3c6a23eae..142f796d6490 100644 --- a/packages/markdown/remark/src/rehype-collect-headings.ts +++ b/packages/markdown/remark/src/rehype-collect-headings.ts @@ -21,7 +21,6 @@ export function rehypeHeadingIds(): ReturnType { const depth = Number.parseInt(level); let text = ''; - let isJSX = false; visit(node, (child, __, parent) => { if (child.type === 'element' || parent == null) { return; @@ -36,31 +35,17 @@ export function rehypeHeadingIds(): ReturnType { text += child.value; } else { text += child.value.replace(/\{/g, '${'); - isJSX = isJSX || child.value.includes('{'); } } }); node.properties = node.properties || {}; if (typeof node.properties.id !== 'string') { - if (isJSX) { - // HACK: serialized JSX from internal plugins, ignore these for slug - const raw = toHtml(node.children, { allowDangerousHtml: true }) - .replace(/\n(<)/g, '<') - .replace(/(>)\n/g, '>'); - // HACK: for ids that have JSX content, use $$slug helper to generate slug at runtime - node.properties.id = `$$slug(\`${text}\`)`; - (node as any).type = 'raw'; - ( - node as any - ).value = `<${node.tagName} id={${node.properties.id}}>${raw}`; - } else { - let slug = slugger.slug(text); + let slug = slugger.slug(text); - if (slug.endsWith('-')) slug = slug.slice(0, -1); + if (slug.endsWith('-')) slug = slug.slice(0, -1); - node.properties.id = slug; - } + node.properties.id = slug; } headings.push({ depth, slug: node.properties.id, text }); diff --git a/packages/markdown/remark/src/rehype-escape.ts b/packages/markdown/remark/src/rehype-escape.ts deleted file mode 100644 index a4cc32cf724a..000000000000 --- a/packages/markdown/remark/src/rehype-escape.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { SKIP, visit } from 'unist-util-visit'; - -export function escapeEntities(value: string): string { - return value.replace(/&/g, '&').replace(//g, '>'); -} - -export default function rehypeEscape(): any { - return function (node: any): any { - return visit(node, 'element', (el) => { - if (el.tagName === 'code' || el.tagName === 'pre') { - el.properties['is:raw'] = true; - // Visit all raw children and escape HTML tags to prevent Markdown code - // like "This is a ` + +

Hello world!

+ + + + + + + diff --git a/packages/astro/test/postcss.test.js b/packages/astro/test/postcss.test.js index 28de600da4a5..b2d663533252 100644 --- a/packages/astro/test/postcss.test.js +++ b/packages/astro/test/postcss.test.js @@ -18,7 +18,7 @@ describe('PostCSS', function () { // get bundled CSS (will be hashed, hence DOM query) const html = await fixture.readFile('/index.html'); const $ = cheerio.load(html); - const bundledCSSHREF = $('link[rel=stylesheet][href^=/assets/]').attr('href'); + const bundledCSSHREF = $('link[rel=stylesheet][href^=/_astro/]').attr('href'); bundledCSS = (await fixture.readFile(bundledCSSHREF.replace(/^\/?/, '/'))) .replace(/\s/g, '') .replace('/n', ''); diff --git a/packages/astro/test/sourcemap.test.js b/packages/astro/test/sourcemap.test.js index 4937e2236448..146db2f2a34d 100644 --- a/packages/astro/test/sourcemap.test.js +++ b/packages/astro/test/sourcemap.test.js @@ -10,7 +10,7 @@ describe('Sourcemap', async () => { }); it('Builds sourcemap', async () => { - const dir = await fixture.readdir('./assets'); + const dir = await fixture.readdir('./_astro'); const counterMap = dir.find((file) => file.match(/^Counter\.\w+\.js\.map$/)); expect(counterMap).to.be.ok; }); diff --git a/packages/astro/test/tailwindcss.test.js b/packages/astro/test/tailwindcss.test.js index 83061248f84c..2a37c6572e2b 100644 --- a/packages/astro/test/tailwindcss.test.js +++ b/packages/astro/test/tailwindcss.test.js @@ -22,7 +22,7 @@ describe('Tailwind', () => { // get bundled CSS (will be hashed, hence DOM query) const html = await fixture.readFile('/index.html'); $ = cheerio.load(html); - const bundledCSSHREF = $('link[rel=stylesheet][href^=/assets/]').attr('href'); + const bundledCSSHREF = $('link[rel=stylesheet][href^=/_astro/]').attr('href'); bundledCSS = await fixture.readFile(bundledCSSHREF.replace(/^\/?/, '/')); }); @@ -61,7 +61,7 @@ describe('Tailwind', () => { it('handles Markdown pages', async () => { const html = await fixture.readFile('/markdown-page/index.html'); const $md = cheerio.load(html); - const bundledCSSHREF = $md('link[rel=stylesheet][href^=/assets/]').attr('href'); + const bundledCSSHREF = $md('link[rel=stylesheet][href^=/_astro/]').attr('href'); const mdBundledCSS = await fixture.readFile(bundledCSSHREF.replace(/^\/?/, '/')); expect(mdBundledCSS, 'includes used component classes').to.match(/\.bg-purple-600{/); }); @@ -69,7 +69,7 @@ describe('Tailwind', () => { it('handles MDX pages (with integration)', async () => { const html = await fixture.readFile('/mdx-page/index.html'); const $md = cheerio.load(html); - const bundledCSSHREF = $md('link[rel=stylesheet][href^=/assets/]').attr('href'); + const bundledCSSHREF = $md('link[rel=stylesheet][href^=/_astro/]').attr('href'); const mdBundledCSS = await fixture.readFile(bundledCSSHREF.replace(/^\/?/, '/')); expect(mdBundledCSS, 'includes used component classes').to.match(/\.bg-purple-600{/); }); diff --git a/packages/integrations/deno/src/index.ts b/packages/integrations/deno/src/index.ts index 68452be94a33..82da151888cd 100644 --- a/packages/integrations/deno/src/index.ts +++ b/packages/integrations/deno/src/index.ts @@ -7,6 +7,7 @@ import { fileURLToPath } from 'url'; interface BuildConfig { server: URL; serverEntry: string; + assets: string; } interface Options { @@ -86,7 +87,7 @@ export default function createIntegration(args?: Options): AstroIntegration { // Remove chunks, if they exist. Since we have bundled via esbuild these chunks are trash. try { const chunkFileNames = - _vite?.build?.rollupOptions?.output?.chunkFileNames ?? 'assets/chunks/chunk.[hash].mjs'; + _vite?.build?.rollupOptions?.output?.chunkFileNames ?? `chunks/chunk.[hash].mjs`; const chunkPath = npath.dirname(chunkFileNames); const chunksDirUrl = new URL(chunkPath + '/', _buildConfig.server); await fs.promises.rm(chunksDirUrl, { recursive: true, force: true }); diff --git a/packages/integrations/image/src/build/ssg.ts b/packages/integrations/image/src/build/ssg.ts index e7cde2185e44..4d5087011d04 100644 --- a/packages/integrations/image/src/build/ssg.ts +++ b/packages/integrations/image/src/build/ssg.ts @@ -172,10 +172,10 @@ export async function ssgBuild({ let outputFileURL: URL; if (isRemoteImage(src)) { - outputFileURL = new URL(path.join('./assets', path.basename(filename)), outDir); + outputFileURL = new URL(path.join(`./${config.build.assets}`, path.basename(filename)), outDir); outputFile = fileURLToPath(outputFileURL); } else { - outputFileURL = new URL(path.join('./assets', filename), outDir); + outputFileURL = new URL(path.join(`./${config.build.assets}`, filename), outDir); outputFile = fileURLToPath(outputFileURL); } diff --git a/packages/integrations/image/src/index.ts b/packages/integrations/image/src/index.ts index cc8093981d24..3767003f3223 100644 --- a/packages/integrations/image/src/index.ts +++ b/packages/integrations/image/src/index.ts @@ -21,6 +21,7 @@ const UNSUPPORTED_ADAPTERS = new Set([ interface BuildConfig { client: URL; server: URL; + assets: string; } interface ImageIntegration { @@ -130,7 +131,7 @@ export default function integration(options: IntegrationOptions = {}): AstroInte // Doing this here makes sure that base is ignored when building // staticImages to /dist, but the rendered HTML will include the // base prefix for `src`. - return prependForwardSlash(joinPaths(_config.base, 'assets', filename)); + return prependForwardSlash(joinPaths(_config.base, _buildConfig.assets, filename)); } // Helpers for building static images should only be available for SSG @@ -146,7 +147,7 @@ export default function integration(options: IntegrationOptions = {}): AstroInte // For the Squoosh service, copy all wasm files to dist/chunks. // Because the default loader is dynamically imported (above), // Vite will bundle squoosh to dist/chunks and expect to find the wasm files there - await copyWasmFiles(new URL('./assets/chunks', dir)); + await copyWasmFiles(new URL('./chunks', dir)); } if (loader && 'transform' in loader && staticImages.size > 0) { @@ -166,7 +167,7 @@ export default function integration(options: IntegrationOptions = {}): AstroInte }, 'astro:build:ssr': async () => { if (resolvedOptions.serviceEntryPoint === '@astrojs/image/squoosh') { - await copyWasmFiles(new URL('./assets/chunks/', _buildConfig.server)); + await copyWasmFiles(new URL('./chunks/', _buildConfig.server)); } }, }, diff --git a/packages/integrations/image/test/background-color-image-ssr.test.js b/packages/integrations/image/test/background-color-image-ssr.test.js index 148c57a5b923..6bc5f333f303 100644 --- a/packages/integrations/image/test/background-color-image-ssr.test.js +++ b/packages/integrations/image/test/background-color-image-ssr.test.js @@ -23,7 +23,7 @@ describe('SSR image with background', function () { f: 'jpeg', w: '256', h: '256', - href: /^\/assets\/file-icon.\w{8}.png/, + href: /^\/_astro\/file-icon.\w{8}.png/, bg: 'dimgray', }, }, @@ -34,7 +34,7 @@ describe('SSR image with background', function () { f: 'jpeg', w: '256', h: '256', - href: /^\/assets\/file-icon.\w{8}.png/, + href: /^\/_astro\/file-icon.\w{8}.png/, bg: '#696969', }, }, @@ -45,7 +45,7 @@ describe('SSR image with background', function () { f: 'jpeg', w: '256', h: '256', - href: /^\/assets\/file-icon.\w{8}.png/, + href: /^\/_astro\/file-icon.\w{8}.png/, bg: '#666', }, }, @@ -56,7 +56,7 @@ describe('SSR image with background', function () { f: 'jpeg', w: '256', h: '256', - href: /^\/assets\/file-icon.\w{8}.png/, + href: /^\/_astro\/file-icon.\w{8}.png/, bg: 'rgb(105,105,105)', }, }, @@ -67,7 +67,7 @@ describe('SSR image with background', function () { f: 'jpeg', w: '256', h: '256', - href: /^\/assets\/file-icon.\w{8}.png/, + href: /^\/_astro\/file-icon.\w{8}.png/, bg: 'rgb(105, 105, 105)', }, }, @@ -78,7 +78,7 @@ describe('SSR image with background', function () { f: 'jpeg', w: '256', h: '256', - href: /^\/assets\/file-icon.\w{8}.png/, + href: /^\/_astro\/file-icon.\w{8}.png/, bg: 'rgb(105,105,105,0.5)', }, }, @@ -89,7 +89,7 @@ describe('SSR image with background', function () { f: 'jpeg', w: '256', h: '256', - href: /^\/assets\/file-icon.\w{8}.png/, + href: /^\/_astro\/file-icon.\w{8}.png/, bg: 'rgb(105, 105, 105, 0.5)', }, }, diff --git a/packages/integrations/image/test/get-image.test.js b/packages/integrations/image/test/get-image.test.js index 6e73627355a5..74fa67feb6f7 100644 --- a/packages/integrations/image/test/get-image.test.js +++ b/packages/integrations/image/test/get-image.test.js @@ -11,7 +11,7 @@ describe('getImage', function () { }); it('Remote images works', async () => { - const assets = await fixture.readdir('/assets'); + const assets = await fixture.readdir('/_astro'); expect(assets).to.have.a.lengthOf(1); }); }); diff --git a/packages/integrations/image/test/image-ssg.test.js b/packages/integrations/image/test/image-ssg.test.js index 2b724c1e3aa3..ce91c00ea411 100644 --- a/packages/integrations/image/test/image-ssg.test.js +++ b/packages/integrations/image/test/image-ssg.test.js @@ -220,43 +220,43 @@ describe('SSG images - build', function () { { title: 'Local images', id: '#social-jpg', - regex: /^\/assets\/social.\w{8}_\w{4,10}.jpg/, + regex: /^\/_astro\/social.\w{8}_\w{4,10}.jpg/, size: { width: 506, height: 253, type: 'jpg' }, }, { title: 'Filename with spaces', id: '#spaces', - regex: /^\/assets\/introducing astro.\w{8}_\w{4,10}.webp/, + regex: /^\/_astro\/introducing astro.\w{8}_\w{4,10}.webp/, size: { width: 768, height: 414, type: 'webp' }, }, { title: 'Inline imports', id: '#inline', - regex: /^\/assets\/social.\w{8}_\w{4,10}.jpg/, + regex: /^\/_astro\/social.\w{8}_\w{4,10}.jpg/, size: { width: 506, height: 253, type: 'jpg' }, }, { title: 'Remote images', id: '#google', - regex: /^\/assets\/googlelogo_color_272x92dp_\w{4,10}.webp/, + regex: /^\/_astro\/googlelogo_color_272x92dp_\w{4,10}.webp/, size: { width: 544, height: 184, type: 'webp' }, }, { title: 'Remote without file extension', id: '#ipsum', - regex: /^\/assets\/200x300_\w{4,10}/, + regex: /^\/_astro\/200x300_\w{4,10}/, size: { width: 200, height: 300, type: 'jpg' }, }, { title: 'Public images', id: '#hero', - regex: /^\/assets\/hero_\w{4,10}.webp/, + regex: /^\/_astro\/hero_\w{4,10}.webp/, size: { width: 768, height: 414, type: 'webp' }, }, { title: 'Remote images', id: '#bg-color', - regex: /^\/assets\/googlelogo_color_272x92dp_\w{4,10}.jpeg/, + regex: /^\/_astro\/googlelogo_color_272x92dp_\w{4,10}.jpeg/, size: { width: 544, height: 184, type: 'jpg' }, }, ].forEach(({ title, id, regex, size }) => { @@ -302,43 +302,43 @@ describe('SSG images with subpath - build', function () { { title: 'Local images', id: '#social-jpg', - regex: /^\/docs\/assets\/social.\w{8}_\w{4,10}.jpg/, + regex: /^\/docs\/_astro\/social.\w{8}_\w{4,10}.jpg/, size: { width: 506, height: 253, type: 'jpg' }, }, { title: 'Filename with spaces', id: '#spaces', - regex: /^\/docs\/assets\/introducing astro.\w{8}_\w{4,10}.webp/, + regex: /^\/docs\/_astro\/introducing astro.\w{8}_\w{4,10}.webp/, size: { width: 768, height: 414, type: 'webp' }, }, { title: 'Inline imports', id: '#inline', - regex: /^\/docs\/assets\/social.\w{8}_\w{4,10}.jpg/, + regex: /^\/docs\/_astro\/social.\w{8}_\w{4,10}.jpg/, size: { width: 506, height: 253, type: 'jpg' }, }, { title: 'Remote images', id: '#google', - regex: /^\/docs\/assets\/googlelogo_color_272x92dp_\w{4,10}.webp/, + regex: /^\/docs\/_astro\/googlelogo_color_272x92dp_\w{4,10}.webp/, size: { width: 544, height: 184, type: 'webp' }, }, { title: 'Remote without file extension', id: '#ipsum', - regex: /^\/docs\/assets\/200x300_\w{4,10}/, + regex: /^\/docs\/_astro\/200x300_\w{4,10}/, size: { width: 200, height: 300, type: 'jpg' }, }, { title: 'Public images', id: '#hero', - regex: /^\/docs\/assets\/hero_\w{4,10}.webp/, + regex: /^\/docs\/_astro\/hero_\w{4,10}.webp/, size: { width: 768, height: 414, type: 'webp' }, }, { title: 'Remote images', id: '#bg-color', - regex: /^\/docs\/assets\/googlelogo_color_272x92dp_\w{4,10}.jpeg/, + regex: /^\/docs\/_astro\/googlelogo_color_272x92dp_\w{4,10}.jpeg/, size: { width: 544, height: 184, type: 'jpg' }, }, ].forEach(({ title, id, regex, size }) => { diff --git a/packages/integrations/image/test/image-ssr-build.test.js b/packages/integrations/image/test/image-ssr-build.test.js index 65c1b30839ee..4b985c0ad9e9 100644 --- a/packages/integrations/image/test/image-ssr-build.test.js +++ b/packages/integrations/image/test/image-ssr-build.test.js @@ -20,19 +20,19 @@ describe('SSR images - build', async function () { title: 'Local images', id: '#social-jpg', url: '/_image', - query: { f: 'jpg', w: '506', h: '253', href: /^\/assets\/social.\w{8}.jpg/ }, + query: { f: 'jpg', w: '506', h: '253', href: /^\/_astro\/social.\w{8}.jpg/ }, }, { title: 'Filename with spaces', id: '#spaces', url: '/_image', - query: { f: 'webp', w: '768', h: '414', href: /^\/assets\/introducing astro.\w{8}.jpg/ }, + query: { f: 'webp', w: '768', h: '414', href: /^\/_astro\/introducing astro.\w{8}.jpg/ }, }, { title: 'Inline imports', id: '#inline', url: '/_image', - query: { f: 'jpg', w: '506', h: '253', href: /^\/assets\/social.\w{8}.jpg/ }, + query: { f: 'jpg', w: '506', h: '253', href: /^\/_astro\/social.\w{8}.jpg/ }, }, { title: 'Remote images', @@ -131,7 +131,7 @@ describe('SSR images with subpath - build', function () { title: 'Local images', id: '#social-jpg', url: '/_image', - query: { f: 'jpg', w: '506', h: '253', href: /^\/docs\/assets\/social.\w{8}.jpg/ }, + query: { f: 'jpg', w: '506', h: '253', href: /^\/docs\/_astro\/social.\w{8}.jpg/ }, }, { title: 'Filename with spaces', @@ -141,14 +141,14 @@ describe('SSR images with subpath - build', function () { f: 'webp', w: '768', h: '414', - href: /^\/docs\/assets\/introducing astro.\w{8}.jpg/, + href: /^\/docs\/_astro\/introducing astro.\w{8}.jpg/, }, }, { title: 'Inline imports', id: '#inline', url: '/_image', - query: { f: 'jpg', w: '506', h: '253', href: /^\/docs\/assets\/social.\w{8}.jpg/ }, + query: { f: 'jpg', w: '506', h: '253', href: /^\/docs\/_astro\/social.\w{8}.jpg/ }, }, { title: 'Remote images', diff --git a/packages/integrations/image/test/picture-ssg.test.js b/packages/integrations/image/test/picture-ssg.test.js index b92e6f3665ed..e5da7ba1aa27 100644 --- a/packages/integrations/image/test/picture-ssg.test.js +++ b/packages/integrations/image/test/picture-ssg.test.js @@ -211,42 +211,42 @@ describe('SSG pictures - build', function () { { title: 'Local images', id: '#social-jpg', - regex: /^\/assets\/social.\w{8}_\w{4,10}.jpg/, + regex: /^\/_astro\/social.\w{8}_\w{4,10}.jpg/, size: { width: 506, height: 253, type: 'jpg' }, alt: 'Social image', }, { title: 'Filename with spaces', id: '#spaces', - regex: /^\/assets\/introducing astro.\w{8}_\w{4,10}.jpg/, + regex: /^\/_astro\/introducing astro.\w{8}_\w{4,10}.jpg/, size: { width: 768, height: 414, type: 'jpg' }, alt: 'spaces', }, { title: 'Inline images', id: '#inline', - regex: /^\/assets\/social.\w{8}_\w{4,10}.jpg/, + regex: /^\/_astro\/social.\w{8}_\w{4,10}.jpg/, size: { width: 506, height: 253, type: 'jpg' }, alt: 'Inline social image', }, { title: 'Remote images', id: '#google', - regex: /^\/assets\/googlelogo_color_272x92dp_\w{4,10}.png/, + regex: /^\/_astro\/googlelogo_color_272x92dp_\w{4,10}.png/, size: { width: 544, height: 184, type: 'png' }, alt: 'Google logo', }, { title: 'Remote without file extension', id: '#ipsum', - regex: /^\/assets\/200x300_\w{4,10}/, + regex: /^\/_astro\/200x300_\w{4,10}/, size: { width: 200, height: 300, type: 'jpg' }, alt: 'ipsum', }, { title: 'Public images', id: '#hero', - regex: /^\/assets\/hero_\w{4,10}.jpg/, + regex: /^\/_astro\/hero_\w{4,10}.jpg/, size: { width: 768, height: 414, type: 'jpg' }, alt: 'Hero image', }, @@ -318,35 +318,35 @@ describe('SSG pictures with subpath - build', function () { { title: 'Local images', id: '#social-jpg', - regex: /^\/docs\/assets\/social.\w{8}_\w{4,10}.jpg/, + regex: /^\/docs\/_astro\/social.\w{8}_\w{4,10}.jpg/, size: { width: 506, height: 253, type: 'jpg' }, alt: 'Social image', }, { title: 'Inline images', id: '#inline', - regex: /^\/docs\/assets\/social.\w{8}_\w{4,10}.jpg/, + regex: /^\/docs\/_astro\/social.\w{8}_\w{4,10}.jpg/, size: { width: 506, height: 253, type: 'jpg' }, alt: 'Inline social image', }, { title: 'Remote images', id: '#google', - regex: /^\/docs\/assets\/googlelogo_color_272x92dp_\w{4,10}.png/, + regex: /^\/docs\/_astro\/googlelogo_color_272x92dp_\w{4,10}.png/, size: { width: 544, height: 184, type: 'png' }, alt: 'Google logo', }, { title: 'Remote without file extension', id: '#ipsum', - regex: /^\/docs\/assets\/200x300_\w{4,10}/, + regex: /^\/docs\/_astro\/200x300_\w{4,10}/, size: { width: 200, height: 300, type: 'jpg' }, alt: 'ipsum', }, { title: 'Public images', id: '#hero', - regex: /^\/docs\/assets\/hero_\w{4,10}.jpg/, + regex: /^\/docs\/_astro\/hero_\w{4,10}.jpg/, size: { width: 768, height: 414, type: 'jpg' }, alt: 'Hero image', }, diff --git a/packages/integrations/image/test/picture-ssr-build.test.js b/packages/integrations/image/test/picture-ssr-build.test.js index c5852f822add..8da476166f13 100644 --- a/packages/integrations/image/test/picture-ssr-build.test.js +++ b/packages/integrations/image/test/picture-ssr-build.test.js @@ -20,21 +20,21 @@ describe('SSR pictures - build', function () { title: 'Local images', id: '#social-jpg', url: '/_image', - query: { f: 'jpg', w: '506', h: '253', href: /^\/assets\/social.\w{8}.jpg/ }, + query: { f: 'jpg', w: '506', h: '253', href: /^\/_astro\/social.\w{8}.jpg/ }, alt: 'Social image', }, { title: 'Filename with spaces', id: '#spaces', url: '/_image', - query: { w: '768', h: '414', f: 'jpg', href: /^\/assets\/introducing astro.\w{8}.jpg/ }, + query: { w: '768', h: '414', f: 'jpg', href: /^\/_astro\/introducing astro.\w{8}.jpg/ }, alt: 'spaces', }, { title: 'Inline imports', id: '#inline', url: '/_image', - query: { f: 'jpg', w: '506', h: '253', href: /^\/assets\/social.\w{8}.jpg/ }, + query: { f: 'jpg', w: '506', h: '253', href: /^\/_astro\/social.\w{8}.jpg/ }, alt: 'Inline social image', }, { @@ -131,21 +131,21 @@ describe('SSR pictures with subpath - build', function () { title: 'Local images', id: '#social-jpg', url: '/_image', - query: { f: 'jpg', w: '506', h: '253', href: /^\/docs\/assets\/social.\w{8}.jpg/ }, + query: { f: 'jpg', w: '506', h: '253', href: /^\/docs\/_astro\/social.\w{8}.jpg/ }, alt: 'Social image', }, { title: 'Filename with spaces', id: '#spaces', url: '/_image', - query: { w: '768', h: '414', f: 'jpg', href: /^\/docs\/assets\/introducing astro.\w{8}.jpg/ }, + query: { w: '768', h: '414', f: 'jpg', href: /^\/docs\/_astro\/introducing astro.\w{8}.jpg/ }, alt: 'spaces', }, { title: 'Inline imports', id: '#inline', url: '/_image', - query: { f: 'jpg', w: '506', h: '253', href: /^\/docs\/assets\/social.\w{8}.jpg/ }, + query: { f: 'jpg', w: '506', h: '253', href: /^\/docs\/_astro\/social.\w{8}.jpg/ }, alt: 'Inline social image', }, { diff --git a/packages/integrations/image/test/rotation.test.js b/packages/integrations/image/test/rotation.test.js index 49fe198b1f56..baa412e2cd03 100644 --- a/packages/integrations/image/test/rotation.test.js +++ b/packages/integrations/image/test/rotation.test.js @@ -32,7 +32,7 @@ describe('Image rotation', function () { it('Landscape images', () => { for (let i = 0; i < 9; i++) { const image = $(`#landscape-${i}`); - const regex = new RegExp(`\^/assets\/Landscape_${i}.\\w{8}_\\w{4,10}.jpg`); + const regex = new RegExp(`\^/_astro\/Landscape_${i}.\\w{8}_\\w{4,10}.jpg`); expect(image.attr('src')).to.match(regex); expect(image.attr('width')).to.equal('1800'); @@ -49,7 +49,7 @@ describe('Image rotation', function () { it('Portait images', () => { for (let i = 0; i < 9; i++) { const image = $(`#portrait-${i}`); - const regex = new RegExp(`\^/assets\/Portrait_${i}.\\w{8}_\\w{4,10}.jpg`); + const regex = new RegExp(`\^/_astro\/Portrait_${i}.\\w{8}_\\w{4,10}.jpg`); expect(image.attr('src')).to.match(regex); expect(image.attr('width')).to.equal('1200'); diff --git a/packages/integrations/image/test/with-mdx.test.js b/packages/integrations/image/test/with-mdx.test.js index 963cd9b970bb..ec7de4f62696 100644 --- a/packages/integrations/image/test/with-mdx.test.js +++ b/packages/integrations/image/test/with-mdx.test.js @@ -28,31 +28,31 @@ describe('Images in MDX - build', function () { { title: 'Local images', id: '#social-jpg', - regex: /^\/assets\/social.\w{8}_\w{4,10}.jpg/, + regex: /^\/_astro\/social.\w{8}_\w{4,10}.jpg/, size: { width: 506, height: 253, type: 'jpg' }, }, { title: 'Inline imports', id: '#inline', - regex: /^\/assets\/social.\w{8}_\w{4,10}.jpg/, + regex: /^\/_astro\/social.\w{8}_\w{4,10}.jpg/, size: { width: 506, height: 253, type: 'jpg' }, }, { title: 'Remote images', id: '#google', - regex: /^\/assets\/googlelogo_color_272x92dp_\w{4,10}.webp/, + regex: /^\/_astro\/googlelogo_color_272x92dp_\w{4,10}.webp/, size: { width: 544, height: 184, type: 'webp' }, }, { title: 'Public images', id: '#hero', - regex: /^\/assets\/hero_\w{4,10}.webp/, + regex: /^\/_astro\/hero_\w{4,10}.webp/, size: { width: 768, height: 414, type: 'webp' }, }, { title: 'Background color', id: '#bg-color', - regex: /^\/assets\/googlelogo_color_272x92dp_\w{4,10}.jpeg/, + regex: /^\/_astro\/googlelogo_color_272x92dp_\w{4,10}.jpeg/, size: { width: 544, height: 184, type: 'jpg' }, }, ].forEach(({ title, id, regex, size }) => { diff --git a/packages/integrations/netlify/src/integration-edge-functions.ts b/packages/integrations/netlify/src/integration-edge-functions.ts index 48be316d9d18..a05f8f3a0fff 100644 --- a/packages/integrations/netlify/src/integration-edge-functions.ts +++ b/packages/integrations/netlify/src/integration-edge-functions.ts @@ -10,6 +10,7 @@ interface BuildConfig { server: URL; client: URL; serverEntry: string; + assets: string; } const SHIM = `globalThis.process = { @@ -100,7 +101,7 @@ async function bundleServerEntry({ serverEntry, server }: BuildConfig, vite: any // Remove chunks, if they exist. Since we have bundled via esbuild these chunks are trash. try { const chunkFileNames = - vite?.build?.rollupOptions?.output?.chunkFileNames ?? 'assets/chunks/chunk.[hash].mjs'; + vite?.build?.rollupOptions?.output?.chunkFileNames ?? `chunks/chunk.[hash].mjs`; const chunkPath = npath.dirname(chunkFileNames); const chunksDirUrl = new URL(chunkPath + '/', server); await fs.promises.rm(chunksDirUrl, { recursive: true, force: true }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fe1dbc3316d5..174d68cc0266 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1582,6 +1582,16 @@ importers: astro: link:../../.. preact: 10.11.3 + packages/astro/test/fixtures/build-assets: + specifiers: + '@astrojs/preact': workspace:* + astro: workspace:* + preact: ^10.11.0 + dependencies: + '@astrojs/preact': link:../../../../integrations/preact + astro: link:../../.. + preact: 10.11.3 + packages/astro/test/fixtures/client-address: specifiers: astro: workspace:* From cb5dbcc51fba808115ced5a9b1b937577486516a Mon Sep 17 00:00:00 2001 From: natemoo-re Date: Mon, 9 Jan 2023 16:03:47 +0000 Subject: [PATCH 07/13] [ci] format --- packages/astro/src/core/build/static-build.ts | 4 +--- packages/astro/src/core/fs/index.ts | 6 +++--- packages/integrations/image/src/build/ssg.ts | 5 ++++- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/packages/astro/src/core/build/static-build.ts b/packages/astro/src/core/build/static-build.ts index 1536d91557c8..13f40c8da358 100644 --- a/packages/astro/src/core/build/static-build.ts +++ b/packages/astro/src/core/build/static-build.ts @@ -2,7 +2,6 @@ import * as eslexer from 'es-module-lexer'; import glob from 'fast-glob'; import fs from 'fs'; import { bgGreen, bgMagenta, black, dim } from 'kleur/colors'; -import path from 'path'; import { fileURLToPath } from 'url'; import * as vite from 'vite'; import { astroBundleDelayedAssetPlugin } from '../../content/index.js'; @@ -10,9 +9,8 @@ import { BuildInternals, createBuildInternals, eachPrerenderedPageData, - isHoistedScript, } from '../../core/build/internal.js'; -import { emptyDir, removeDir, removeEmptyDirs } from '../../core/fs/index.js'; +import { emptyDir, removeEmptyDirs } from '../../core/fs/index.js'; import { appendForwardSlash, prependForwardSlash } from '../../core/path.js'; import { isModeServerWithNoAdapter } from '../../core/util.js'; import { runHookBuildSetup } from '../../integrations/index.js'; diff --git a/packages/astro/src/core/fs/index.ts b/packages/astro/src/core/fs/index.ts index 4b627a3ebdfc..35afde043d44 100644 --- a/packages/astro/src/core/fs/index.ts +++ b/packages/astro/src/core/fs/index.ts @@ -15,12 +15,12 @@ export function removeEmptyDirs(root: URL): void { const dir = fileURLToPath(root); if (!fs.statSync(dir).isDirectory()) return; let files = fs.readdirSync(dir); - + if (files.length > 0) { - files.map(file => { + files.map((file) => { const url = new URL(`./${file}`, appendForwardSlash(root.toString())); removeEmptyDirs(url); - }) + }); files = fs.readdirSync(dir); } diff --git a/packages/integrations/image/src/build/ssg.ts b/packages/integrations/image/src/build/ssg.ts index 4d5087011d04..b432447e3acc 100644 --- a/packages/integrations/image/src/build/ssg.ts +++ b/packages/integrations/image/src/build/ssg.ts @@ -172,7 +172,10 @@ export async function ssgBuild({ let outputFileURL: URL; if (isRemoteImage(src)) { - outputFileURL = new URL(path.join(`./${config.build.assets}`, path.basename(filename)), outDir); + outputFileURL = new URL( + path.join(`./${config.build.assets}`, path.basename(filename)), + outDir + ); outputFile = fileURLToPath(outputFileURL); } else { outputFileURL = new URL(path.join(`./${config.build.assets}`, filename), outDir); From f35411487b85b78e9baee566c78ad0c27308d649 Mon Sep 17 00:00:00 2001 From: Chris Swithinbank Date: Mon, 9 Jan 2023 17:06:29 +0100 Subject: [PATCH 08/13] [markdown-remark] Remove unused dependencies (#5804) * [markdown-remark] Remove unused dependency Follow up to #5785. Missed one dependency that is no longer used and can be removed. * [markdown-remark] Remove unused dependency * Lockfile fix * Update lockfile [again] --- packages/markdown/remark/package.json | 2 -- pnpm-lock.yaml | 4 ---- 2 files changed, 6 deletions(-) diff --git a/packages/markdown/remark/package.json b/packages/markdown/remark/package.json index 4270e15c3544..1cb4f3b09991 100644 --- a/packages/markdown/remark/package.json +++ b/packages/markdown/remark/package.json @@ -29,9 +29,7 @@ }, "dependencies": { "@astrojs/prism": "^1.0.0", - "acorn": "^8.7.1", "github-slugger": "^1.4.0", - "hast-util-to-html": "^8.0.3", "import-meta-resolve": "^2.1.0", "rehype-raw": "^6.1.1", "rehype-stringify": "^9.0.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 174d68cc0266..85466367163f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3479,11 +3479,9 @@ importers: '@types/mdast': ^3.0.10 '@types/mocha': ^9.1.1 '@types/unist': ^2.0.6 - acorn: ^8.7.1 astro-scripts: workspace:* chai: ^4.3.6 github-slugger: ^1.4.0 - hast-util-to-html: ^8.0.3 import-meta-resolve: ^2.1.0 mocha: ^9.2.2 rehype-raw: ^6.1.1 @@ -3498,9 +3496,7 @@ importers: vfile: ^5.3.2 dependencies: '@astrojs/prism': link:../../astro-prism - acorn: 8.8.1 github-slugger: 1.5.0 - hast-util-to-html: 8.0.4 import-meta-resolve: 2.2.1 rehype-raw: 6.1.1 rehype-stringify: 9.0.3 From 302e0ef8f5d5232e3348afe680e599f3e537b5c5 Mon Sep 17 00:00:00 2001 From: Bjorn Lu Date: Tue, 10 Jan 2023 01:14:07 +0800 Subject: [PATCH 09/13] Default preview host to localhost (#5753) * Initial refactor * Extract as vite plugin * Cleanup vite plugin * Reduce option passing * Use localhost as preview default host * Simplify base handling * Fix host handling * Add changeset * Remove unused imports * Remove unused sirv dep * Try pin playwright to 1.28.1 * Update playwright * Try this * Speed up CI * Try fix page off * Refactor networkidle * Ensure open connections are destroyed when the preview server is closed * Revert debug code Co-authored-by: Matthew Phillips --- .changeset/lemon-bobcats-kick.md | 5 + packages/astro/package.json | 5 +- packages/astro/src/core/dev/dev.ts | 5 +- packages/astro/src/core/messages.ts | 67 +----- packages/astro/src/core/preview/index.ts | 8 +- .../src/core/preview/static-preview-server.ts | 190 +++++------------- packages/astro/src/core/preview/util.ts | 10 +- .../core/preview/vite-plugin-astro-preview.ts | 68 +++++++ packages/astro/src/core/util.ts | 8 - packages/astro/test/cli.test.js | 44 ++-- packages/integrations/prefetch/package.json | 4 +- .../prefetch/test/basic-prefetch.test.js | 4 +- .../prefetch/test/custom-selectors.test.js | 4 +- .../prefetch/test/style-prefetch.test.js | 2 +- pnpm-lock.yaml | 29 ++- 15 files changed, 183 insertions(+), 270 deletions(-) create mode 100644 .changeset/lemon-bobcats-kick.md create mode 100644 packages/astro/src/core/preview/vite-plugin-astro-preview.ts diff --git a/.changeset/lemon-bobcats-kick.md b/.changeset/lemon-bobcats-kick.md new file mode 100644 index 000000000000..444863635afd --- /dev/null +++ b/.changeset/lemon-bobcats-kick.md @@ -0,0 +1,5 @@ +--- +'astro': major +--- + +Default preview host to `localhost` instead of `127.0.0.1`. This allows the static server and integration preview servers to serve under ipv6. diff --git a/packages/astro/package.json b/packages/astro/package.json index 7191f8488051..43f25171d500 100644 --- a/packages/astro/package.json +++ b/packages/astro/package.json @@ -155,8 +155,8 @@ "rehype": "^12.0.1", "resolve": "^1.22.0", "semver": "^7.3.7", + "server-destroy": "^1.0.1", "shiki": "^0.11.1", - "sirv": "^2.0.2", "slash": "^4.0.0", "string-width": "^5.1.2", "strip-ansi": "^7.0.1", @@ -171,7 +171,7 @@ "zod": "^3.17.3" }, "devDependencies": { - "@playwright/test": "^1.22.2", + "@playwright/test": "^1.29.2", "@types/babel__generator": "^7.6.4", "@types/babel__traverse": "^7.17.1", "@types/chai": "^4.3.1", @@ -191,6 +191,7 @@ "@types/resolve": "^1.20.2", "@types/rimraf": "^3.0.2", "@types/send": "^0.17.1", + "@types/server-destroy": "^1.0.1", "@types/unist": "^2.0.6", "astro-scripts": "workspace:*", "chai": "^4.3.6", diff --git a/packages/astro/src/core/dev/dev.ts b/packages/astro/src/core/dev/dev.ts index 62c01ce78579..074501b04a34 100644 --- a/packages/astro/src/core/dev/dev.ts +++ b/packages/astro/src/core/dev/dev.ts @@ -49,9 +49,6 @@ export default async function dev( // Start listening to the port const devServerAddressInfo = await startContainer(restart.container); - const site = settings.config.site - ? new URL(settings.config.base, settings.config.site) - : undefined; info( options.logging, null, @@ -59,7 +56,7 @@ export default async function dev( startupTime: performance.now() - devStart, resolvedUrls: restart.container.viteServer.resolvedUrls || { local: [], network: [] }, host: settings.config.server.host, - site, + base: settings.config.base, isRestart: options.isRestart, }) ); diff --git a/packages/astro/src/core/messages.ts b/packages/astro/src/core/messages.ts index d46bf99916e3..817958876d7b 100644 --- a/packages/astro/src/core/messages.ts +++ b/packages/astro/src/core/messages.ts @@ -14,14 +14,11 @@ import { underline, yellow, } from 'kleur/colors'; -import type { AddressInfo } from 'net'; -import os from 'os'; import { ResolvedServerUrls } from 'vite'; import { ZodError } from 'zod'; import { renderErrorMarkdown } from './errors/dev/utils.js'; import { AstroError, CompilerError, ErrorWithMetadata } from './errors/index.js'; -import { removeTrailingForwardSlash } from './path.js'; -import { emoji, getLocalAddress, padMultilineString } from './util.js'; +import { emoji, padMultilineString } from './util.js'; const PREFIX_PADDING = 6; @@ -58,31 +55,26 @@ export function serverStart({ startupTime, resolvedUrls, host, - site, + base, isRestart = false, }: { startupTime: number; resolvedUrls: ResolvedServerUrls; host: string | boolean; - site: URL | undefined; + base: string; isRestart?: boolean; }): string { // PACKAGE_VERSION is injected at build-time const version = process.env.PACKAGE_VERSION ?? '0.0.0'; - const rootPath = site ? site.pathname : '/'; const localPrefix = `${dim('┃')} Local `; const networkPrefix = `${dim('┃')} Network `; const emptyPrefix = ' '.repeat(11); const localUrlMessages = resolvedUrls.local.map((url, i) => { - return `${i === 0 ? localPrefix : emptyPrefix}${bold( - cyan(removeTrailingForwardSlash(url) + rootPath) - )}`; + return `${i === 0 ? localPrefix : emptyPrefix}${bold(cyan(new URL(url).origin + base))}`; }); const networkUrlMessages = resolvedUrls.network.map((url, i) => { - return `${i === 0 ? networkPrefix : emptyPrefix}${bold( - cyan(removeTrailingForwardSlash(url) + rootPath) - )}`; + return `${i === 0 ? networkPrefix : emptyPrefix}${bold(cyan(new URL(url).origin + base))}`; }); if (networkUrlMessages.length === 0) { @@ -109,50 +101,6 @@ export function serverStart({ .join('\n'); } -export function resolveServerUrls({ - address, - host, - https, -}: { - address: AddressInfo; - host: string | boolean; - https: boolean; -}): ResolvedServerUrls { - const { address: networkAddress, port } = address; - const localAddress = getLocalAddress(networkAddress, host); - const networkLogging = getNetworkLogging(host); - const toDisplayUrl = (hostname: string) => `${https ? 'https' : 'http'}://${hostname}:${port}`; - - let local = toDisplayUrl(localAddress); - let network: string | null = null; - - if (networkLogging === 'visible') { - const ipv4Networks = Object.values(os.networkInterfaces()) - .flatMap((networkInterface) => networkInterface ?? []) - .filter( - (networkInterface) => - networkInterface?.address && - // Node < v18 - ((typeof networkInterface.family === 'string' && networkInterface.family === 'IPv4') || - // Node >= v18 - (typeof networkInterface.family === 'number' && (networkInterface as any).family === 4)) - ); - for (let { address: ipv4Address } of ipv4Networks) { - if (ipv4Address.includes('127.0.0.1')) { - const displayAddress = ipv4Address.replace('127.0.0.1', localAddress); - local = toDisplayUrl(displayAddress); - } else { - network = toDisplayUrl(ipv4Address); - } - } - } - - return { - local: [local], - network: network ? [network] : [], - }; -} - export function telemetryNotice() { const headline = yellow(`Astro now collects ${bold('anonymous')} usage data.`); const why = `This ${bold('optional program')} will help shape our roadmap.`; @@ -228,11 +176,6 @@ export function cancelled(message: string, tip?: string) { .join('\n'); } -/** Display port in use */ -export function portInUse({ port }: { port: number }): string { - return `Port ${port} in use. Trying a new one…`; -} - const LOCAL_IP_HOSTS = new Set(['localhost', '127.0.0.1']); export function getNetworkLogging(host: string | boolean): 'none' | 'host-to-expose' | 'visible' { diff --git a/packages/astro/src/core/preview/index.ts b/packages/astro/src/core/preview/index.ts index d4a5c8bb2cb5..4488e590387a 100644 --- a/packages/astro/src/core/preview/index.ts +++ b/packages/astro/src/core/preview/index.ts @@ -23,11 +23,9 @@ export default async function preview( logging: logging, }); await runHookConfigDone({ settings: settings, logging: logging }); - const host = getResolvedHostForHttpServer(settings.config.server.host); - const { port, headers } = settings.config.server; if (settings.config.output === 'static') { - const server = await createStaticPreviewServer(settings, { logging, host, port, headers }); + const server = await createStaticPreviewServer(settings, logging); return server; } if (!settings.adapter) { @@ -55,8 +53,8 @@ export default async function preview( outDir: settings.config.outDir, client: settings.config.build.client, serverEntrypoint: new URL(settings.config.build.serverEntry, settings.config.build.server), - host, - port, + host: getResolvedHostForHttpServer(settings.config.server.host), + port: settings.config.server.port, base: settings.config.base, }); diff --git a/packages/astro/src/core/preview/static-preview-server.ts b/packages/astro/src/core/preview/static-preview-server.ts index f74755aed550..8f5a88103920 100644 --- a/packages/astro/src/core/preview/static-preview-server.ts +++ b/packages/astro/src/core/preview/static-preview-server.ts @@ -1,15 +1,14 @@ -import type { AddressInfo } from 'net'; -import type { AstroSettings } from '../../@types/astro'; -import type { LogOptions } from '../logger/core'; - -import fs from 'fs'; -import http, { OutgoingHttpHeaders } from 'http'; +import http from 'http'; import { performance } from 'perf_hooks'; -import sirv from 'sirv'; import { fileURLToPath } from 'url'; -import { notFoundTemplate, subpathNotUsedTemplate } from '../../template/4xx.js'; +import { preview, type PreviewServer as VitePreviewServer } from 'vite'; +import type { AstroSettings } from '../../@types/astro'; +import type { LogOptions } from '../logger/core'; import { error, info } from '../logger/core.js'; import * as msg from '../messages.js'; +import { getResolvedHostForHttpServer } from './util.js'; +import { vitePluginAstroPreview } from './vite-plugin-astro-preview.js'; +import enableDestroy from 'server-destroy'; export interface PreviewServer { host?: string; @@ -19,160 +18,65 @@ export interface PreviewServer { stop(): Promise; } -const HAS_FILE_EXTENSION_REGEXP = /^.*\.[^\\]+$/; - -/** The primary dev action */ export default async function createStaticPreviewServer( settings: AstroSettings, - { - logging, - host, - port, - headers, - }: { - logging: LogOptions; - host: string | undefined; - port: number; - headers: OutgoingHttpHeaders | undefined; - } + logging: LogOptions ): Promise { const startServerTime = performance.now(); - const defaultOrigin = 'http://localhost'; - const trailingSlash = settings.config.trailingSlash; - /** Base request URL. */ - let baseURL = new URL(settings.config.base, new URL(settings.config.site || '/', defaultOrigin)); - const staticFileServer = sirv(fileURLToPath(settings.config.outDir), { - dev: true, - etag: true, - maxAge: 0, - setHeaders: (res, pathname, stats) => { - for (const [name, value] of Object.entries(headers ?? {})) { - if (value) res.setHeader(name, value); - } - }, - }); - // Create the preview server, send static files out of the `dist/` directory. - const server = http.createServer((req, res) => { - const requestURL = new URL(req.url as string, defaultOrigin); - - // respond 404 to requests outside the base request directory - if (!requestURL.pathname.startsWith(baseURL.pathname)) { - res.statusCode = 404; - res.end(subpathNotUsedTemplate(baseURL.pathname, requestURL.pathname)); - return; - } - - /** Relative request path. */ - const pathname = requestURL.pathname.slice(baseURL.pathname.length - 1); - const isRoot = pathname === '/'; - const hasTrailingSlash = isRoot || pathname.endsWith('/'); - - function sendError(message: string) { - res.statusCode = 404; - res.end(notFoundTemplate(pathname, message)); - } - - switch (true) { - case hasTrailingSlash && trailingSlash == 'never' && !isRoot: - sendError('Not Found (trailingSlash is set to "never")'); - return; - case !hasTrailingSlash && - trailingSlash == 'always' && - !isRoot && - !HAS_FILE_EXTENSION_REGEXP.test(pathname): - sendError('Not Found (trailingSlash is set to "always")'); - return; - default: { - // HACK: rewrite req.url so that sirv finds the file - req.url = '/' + req.url?.replace(baseURL.pathname, ''); - staticFileServer(req, res, () => { - const errorPagePath = fileURLToPath(settings.config.outDir + '/404.html'); - if (fs.existsSync(errorPagePath)) { - res.statusCode = 404; - res.setHeader('Content-Type', 'text/html;charset=utf-8'); - res.end(fs.readFileSync(errorPagePath)); - } else { - staticFileServer(req, res, () => { - sendError('Not Found'); - }); - } - }); - return; - } - } - }); - - let httpServer: http.Server; - - /** Expose dev server to `port` */ - function startServer(timerStart: number): Promise { - let showedPortTakenMsg = false; - let showedListenMsg = false; - return new Promise((resolve, reject) => { - const listen = () => { - httpServer = server.listen(port, host, async () => { - if (!showedListenMsg) { - const resolvedUrls = msg.resolveServerUrls({ - address: server.address() as AddressInfo, - host: settings.config.server.host, - https: false, - }); - info( - logging, - null, - msg.serverStart({ - startupTime: performance.now() - timerStart, - resolvedUrls, - host: settings.config.server.host, - site: baseURL, - }) - ); - } - showedListenMsg = true; - resolve(); - }); - httpServer?.on('error', onError); - }; - - const onError = (err: NodeJS.ErrnoException) => { - if (err.code && err.code === 'EADDRINUSE') { - if (!showedPortTakenMsg) { - info(logging, 'astro', msg.portInUse({ port })); - showedPortTakenMsg = true; // only print this once - } - port++; - return listen(); // retry - } else { - error(logging, 'astro', err.stack || err.message); - httpServer?.removeListener('error', onError); - reject(err); // reject - } - }; - - listen(); + let previewServer: VitePreviewServer; + try { + previewServer = await preview({ + configFile: false, + base: settings.config.base, + appType: 'mpa', + build: { + outDir: fileURLToPath(settings.config.outDir), + }, + preview: { + host: settings.config.server.host, + port: settings.config.server.port, + headers: settings.config.server.headers, + }, + plugins: [vitePluginAstroPreview(settings)], }); + } catch (err) { + if (err instanceof Error) { + error(logging, 'astro', err.stack || err.message); + } + throw err; } - // Start listening on `hostname:port`. - await startServer(startServerTime); + enableDestroy(previewServer.httpServer); + + // Log server start URLs + info( + logging, + null, + msg.serverStart({ + startupTime: performance.now() - startServerTime, + resolvedUrls: previewServer.resolvedUrls, + host: settings.config.server.host, + base: settings.config.base, + }) + ); // Resolves once the server is closed function closed() { return new Promise((resolve, reject) => { - httpServer!.addListener('close', resolve); - httpServer!.addListener('error', reject); + previewServer.httpServer.addListener('close', resolve); + previewServer.httpServer.addListener('error', reject); }); } return { - host, - port, + host: getResolvedHostForHttpServer(settings.config.server.host), + port: settings.config.server.port, closed, - server: httpServer!, + server: previewServer.httpServer, stop: async () => { await new Promise((resolve, reject) => { - httpServer.close((err) => (err ? reject(err) : resolve(undefined))); + previewServer.httpServer.destroy((err) => (err ? reject(err) : resolve(undefined))); }); }, }; diff --git a/packages/astro/src/core/preview/util.ts b/packages/astro/src/core/preview/util.ts index 556946d2bd32..d02e4c3c8a09 100644 --- a/packages/astro/src/core/preview/util.ts +++ b/packages/astro/src/core/preview/util.ts @@ -1,7 +1,7 @@ export function getResolvedHostForHttpServer(host: string | boolean) { if (host === false) { // Use a secure default - return '127.0.0.1'; + return 'localhost'; } else if (host === true) { // If passed --host in the CLI without arguments return undefined; // undefined typically means 0.0.0.0 or :: (listen on all IPs) @@ -9,3 +9,11 @@ export function getResolvedHostForHttpServer(host: string | boolean) { return host; } } + +export function stripBase(path: string, base: string): string { + if (path === base) { + return '/'; + } + const baseWithSlash = base.endsWith('/') ? base : base + '/'; + return path.replace(RegExp('^' + baseWithSlash), '/'); +} diff --git a/packages/astro/src/core/preview/vite-plugin-astro-preview.ts b/packages/astro/src/core/preview/vite-plugin-astro-preview.ts new file mode 100644 index 000000000000..95ff4d225996 --- /dev/null +++ b/packages/astro/src/core/preview/vite-plugin-astro-preview.ts @@ -0,0 +1,68 @@ +import fs from 'fs'; +import { fileURLToPath } from 'url'; +import { Plugin } from 'vite'; +import { AstroSettings } from '../../@types/astro.js'; +import { notFoundTemplate, subpathNotUsedTemplate } from '../../template/4xx.js'; +import { stripBase } from './util.js'; + +const HAS_FILE_EXTENSION_REGEXP = /^.*\.[^\\]+$/; + +export function vitePluginAstroPreview(settings: AstroSettings): Plugin { + const { base, outDir, trailingSlash } = settings.config; + + return { + name: 'astro:preview', + apply: 'serve', + configurePreviewServer(server) { + server.middlewares.use((req, res, next) => { + // respond 404 to requests outside the base request directory + if (!req.url!.startsWith(base)) { + res.statusCode = 404; + res.end(subpathNotUsedTemplate(base, req.url!)); + return; + } + + const pathname = stripBase(req.url!, base); + const isRoot = pathname === '/'; + + // Validate trailingSlash + if (!isRoot) { + const hasTrailingSlash = pathname.endsWith('/'); + + if (hasTrailingSlash && trailingSlash == 'never') { + res.statusCode = 404; + res.end(notFoundTemplate(pathname, 'Not Found (trailingSlash is set to "never")')); + return; + } + + if ( + !hasTrailingSlash && + trailingSlash == 'always' && + !HAS_FILE_EXTENSION_REGEXP.test(pathname) + ) { + res.statusCode = 404; + res.end(notFoundTemplate(pathname, 'Not Found (trailingSlash is set to "always")')); + return; + } + } + + next(); + }); + + return () => { + server.middlewares.use((req, res) => { + const errorPagePath = fileURLToPath(outDir + '/404.html'); + if (fs.existsSync(errorPagePath)) { + res.statusCode = 404; + res.setHeader('Content-Type', 'text/html;charset=utf-8'); + res.end(fs.readFileSync(errorPagePath)); + } else { + const pathname = stripBase(req.url!, base); + res.statusCode = 404; + res.end(notFoundTemplate(pathname, 'Not Found')); + } + }); + }; + }, + }; +} diff --git a/packages/astro/src/core/util.ts b/packages/astro/src/core/util.ts index d6f95062a958..79148797b5ae 100644 --- a/packages/astro/src/core/util.ts +++ b/packages/astro/src/core/util.ts @@ -164,14 +164,6 @@ export function emoji(char: string, fallback: string) { return process.platform !== 'win32' ? char : fallback; } -export function getLocalAddress(serverAddress: string, host: string | boolean): string { - if (typeof host === 'boolean' || host === 'localhost') { - return 'localhost'; - } else { - return serverAddress; - } -} - /** * Simulate Vite's resolve and import analysis so we can import the id as an URL * through a script tag or a dynamic import as-is. diff --git a/packages/astro/test/cli.test.js b/packages/astro/test/cli.test.js index fb5d54d38fa8..2c1dd0dead9d 100644 --- a/packages/astro/test/cli.test.js +++ b/packages/astro/test/cli.test.js @@ -77,17 +77,11 @@ describe('astro cli', () => { const localURL = new URL(local); const networkURL = new URL(network); - if (cmd === 'dev') { - expect(localURL.hostname).to.be.oneOf( - ['localhost', '127.0.0.1'], - `Expected local URL to be on localhost` - ); - } else { - expect(localURL.hostname).to.be.equal( - flagValue ?? 'localhost', - `Expected local URL to be on localhost` - ); - } + expect(localURL.hostname).to.be.oneOf( + ['localhost', '127.0.0.1'], + `Expected local URL to be on localhost` + ); + // Note: our tests run in parallel so this could be 3000+! expect(Number.parseInt(localURL.port)).to.be.greaterThanOrEqual( 3000, @@ -113,17 +107,11 @@ describe('astro cli', () => { expect(network).to.not.be.undefined; const localURL = new URL(local); - if (cmd === 'dev') { - expect(localURL.hostname).to.be.oneOf( - ['localhost', '127.0.0.1'], - `Expected local URL to be on localhost` - ); - } else { - expect(localURL.hostname).to.be.equal( - 'localhost', - `Expected local URL to be on localhost` - ); - } + expect(localURL.hostname).to.be.oneOf( + ['localhost', '127.0.0.1'], + `Expected local URL to be on localhost` + ); + expect(() => new URL(networkURL)).to.throw(); }); }); @@ -140,14 +128,10 @@ describe('astro cli', () => { expect(network).to.be.undefined; const localURL = new URL(local); - if (cmd === 'dev') { - expect(localURL.hostname).to.be.oneOf( - ['localhost', '127.0.0.1'], - `Expected local URL to be on localhost` - ); - } else { - expect(localURL.hostname).to.be.equal(flagValue, `Expected local URL to be on localhost`); - } + expect(localURL.hostname).to.be.oneOf( + ['localhost', '127.0.0.1'], + `Expected local URL to be on localhost` + ); }); }); }); diff --git a/packages/integrations/prefetch/package.json b/packages/integrations/prefetch/package.json index 11f9bdc8ea00..414bd2c33697 100644 --- a/packages/integrations/prefetch/package.json +++ b/packages/integrations/prefetch/package.json @@ -31,13 +31,13 @@ "test:match": "playwright test -g" }, "devDependencies": { - "@playwright/test": "^1.26.0", + "@playwright/test": "^1.29.2", "@types/chai": "^4.3.1", "@types/chai-as-promised": "^7.1.5", "@types/mocha": "^9.1.1", "astro": "workspace:*", "astro-scripts": "workspace:*", - "playwright": "^1.22.2" + "playwright": "^1.29.2" }, "dependencies": { "throttles": "^1.0.1" diff --git a/packages/integrations/prefetch/test/basic-prefetch.test.js b/packages/integrations/prefetch/test/basic-prefetch.test.js index 576bd19bd2e3..6bab8a478d08 100644 --- a/packages/integrations/prefetch/test/basic-prefetch.test.js +++ b/packages/integrations/prefetch/test/basic-prefetch.test.js @@ -19,7 +19,7 @@ test.describe('Basic prefetch', () => { test('skips /admin', async ({ page, astro }) => { const requests = []; - page.on('request', async (request) => requests.push(request.url())); + page.on('request', (request) => requests.push(request.url())); await page.goto(astro.resolveUrl('/')); @@ -56,7 +56,7 @@ test.describe('Basic prefetch', () => { test('skips /admin', async ({ page, astro }) => { const requests = []; - page.on('request', async (request) => requests.push(request.url())); + page.on('request', (request) => requests.push(request.url())); await page.goto(astro.resolveUrl('/')); diff --git a/packages/integrations/prefetch/test/custom-selectors.test.js b/packages/integrations/prefetch/test/custom-selectors.test.js index d57ac3b908cb..803e1dc3b401 100644 --- a/packages/integrations/prefetch/test/custom-selectors.test.js +++ b/packages/integrations/prefetch/test/custom-selectors.test.js @@ -27,7 +27,7 @@ test.describe('Custom prefetch selectors', () => { test('only prefetches /contact', async ({ page, astro }) => { const requests = []; - page.on('request', async (request) => requests.push(request.url())); + page.on('request', (request) => requests.push(request.url())); await page.goto(astro.resolveUrl('/')); @@ -64,7 +64,7 @@ test.describe('Custom prefetch selectors', () => { test('only prefetches /contact', async ({ page, astro }) => { const requests = []; - page.on('request', async (request) => requests.push(request.url())); + page.on('request', (request) => requests.push(request.url())); await page.goto(astro.resolveUrl('/')); diff --git a/packages/integrations/prefetch/test/style-prefetch.test.js b/packages/integrations/prefetch/test/style-prefetch.test.js index 8e89a35e336a..b9bb0b04369a 100644 --- a/packages/integrations/prefetch/test/style-prefetch.test.js +++ b/packages/integrations/prefetch/test/style-prefetch.test.js @@ -39,7 +39,7 @@ test.describe('Style prefetch', () => { test('style fetching', async ({ page, astro }) => { const requests = []; - page.on('request', async (request) => requests.push(request.url())); + page.on('request', (request) => requests.push(request.url())); await page.goto(astro.resolveUrl('/')); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 85466367163f..6434adaf6f79 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -396,7 +396,7 @@ importers: '@babel/plugin-transform-react-jsx': ^7.17.12 '@babel/traverse': ^7.18.2 '@babel/types': ^7.18.4 - '@playwright/test': ^1.22.2 + '@playwright/test': ^1.29.2 '@types/babel__core': ^7.1.19 '@types/babel__generator': ^7.6.4 '@types/babel__traverse': ^7.17.1 @@ -418,6 +418,7 @@ importers: '@types/resolve': ^1.20.2 '@types/rimraf': ^3.0.2 '@types/send': ^0.17.1 + '@types/server-destroy': ^1.0.1 '@types/unist': ^2.0.6 '@types/yargs-parser': ^21.0.0 acorn: ^8.8.1 @@ -466,8 +467,8 @@ importers: rollup: ^3.9.0 sass: ^1.52.2 semver: ^7.3.7 + server-destroy: ^1.0.1 shiki: ^0.11.1 - sirv: ^2.0.2 slash: ^4.0.0 srcset-parse: ^1.1.0 string-width: ^5.1.2 @@ -529,8 +530,8 @@ importers: rehype: 12.0.1 resolve: 1.22.1 semver: 7.3.8 + server-destroy: 1.0.1 shiki: 0.11.1 - sirv: 2.0.2 slash: 4.0.0 string-width: 5.1.2 strip-ansi: 7.0.1 @@ -564,6 +565,7 @@ importers: '@types/resolve': 1.20.2 '@types/rimraf': 3.0.2 '@types/send': 0.17.1 + '@types/server-destroy': 1.0.1 '@types/unist': 2.0.6 astro-scripts: link:../../scripts chai: 4.3.7 @@ -3139,13 +3141,13 @@ importers: packages/integrations/prefetch: specifiers: - '@playwright/test': ^1.26.0 + '@playwright/test': ^1.29.2 '@types/chai': ^4.3.1 '@types/chai-as-promised': ^7.1.5 '@types/mocha': ^9.1.1 astro: workspace:* astro-scripts: workspace:* - playwright: ^1.22.2 + playwright: ^1.29.2 throttles: ^1.0.1 dependencies: throttles: 1.0.1 @@ -7055,6 +7057,7 @@ packages: /@types/node/14.18.36: resolution: {integrity: sha512-FXKWbsJ6a1hIrRxv+FoukuHnGTgEzKYGi7kilfMae96AL9UNkPFNWJEEYWzdRI9ooIkbr4AKldyuSTLql06vLQ==} + dev: true /@types/node/16.18.11: resolution: {integrity: sha512-3oJbGBUWuS6ahSnEq1eN2XrCyf4YsWI8OyCvo7c64zQJNplk3mO84t53o8lfTk+2ji59g5ycfc6qQ3fdHliHuA==} @@ -7126,7 +7129,7 @@ packages: /@types/resolve/1.17.1: resolution: {integrity: sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==} dependencies: - '@types/node': 14.18.36 + '@types/node': 18.11.18 /@types/resolve/1.20.2: resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==} @@ -7141,7 +7144,7 @@ packages: /@types/sax/1.2.4: resolution: {integrity: sha512-pSAff4IAxJjfAXUG6tFkO7dsSbTmf8CtUpfhhZ5VhkRpC4628tJhh3+V6H1E+/Gs9piSzYKT5yzHO5M4GG9jkw==} dependencies: - '@types/node': 17.0.45 + '@types/node': 18.11.18 dev: false /@types/scheduler/0.16.2: @@ -7162,6 +7165,12 @@ packages: '@types/node': 18.11.18 dev: true + /@types/server-destroy/1.0.1: + resolution: {integrity: sha512-77QGr7waZbE0Y0uF+G+uH3H3SmhyA78Jf2r5r7QSrpg0U3kSXduWpGjzP9PvPLR/KCy+kHjjpnugRHsYTnHopg==} + dependencies: + '@types/node': 18.11.18 + dev: true + /@types/set-cookie-parser/2.4.2: resolution: {integrity: sha512-fBZgytwhYAUkj/jC/FAV4RQ5EerRup1YQsXQCh8rZfiHkc4UahC192oH0smGwsXol3cL3A5oETuAHeQHmhXM4w==} dependencies: @@ -11129,7 +11138,7 @@ packages: resolution: {integrity: sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==} engines: {node: '>= 10.13.0'} dependencies: - '@types/node': 14.18.36 + '@types/node': 18.11.18 merge-stream: 2.0.0 supports-color: 7.2.0 @@ -13994,6 +14003,10 @@ packages: randombytes: 2.1.0 dev: true + /server-destroy/1.0.1: + resolution: {integrity: sha512-rb+9B5YBIEzYcD6x2VKidaa+cqYBJQKnU4oe4E3ANwRRN56yk/ua1YCJT1n21NTS8w6CcOclAKNP3PhdCXKYtQ==} + dev: false + /set-blocking/2.0.0: resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} From 9bb08bfe8c400d468de454c69810e18794263439 Mon Sep 17 00:00:00 2001 From: matthewp Date: Mon, 9 Jan 2023 17:16:20 +0000 Subject: [PATCH 10/13] [ci] format --- packages/astro/src/core/preview/static-preview-server.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/astro/src/core/preview/static-preview-server.ts b/packages/astro/src/core/preview/static-preview-server.ts index 8f5a88103920..0103ea74e2a5 100644 --- a/packages/astro/src/core/preview/static-preview-server.ts +++ b/packages/astro/src/core/preview/static-preview-server.ts @@ -1,5 +1,6 @@ import http from 'http'; import { performance } from 'perf_hooks'; +import enableDestroy from 'server-destroy'; import { fileURLToPath } from 'url'; import { preview, type PreviewServer as VitePreviewServer } from 'vite'; import type { AstroSettings } from '../../@types/astro'; @@ -8,7 +9,6 @@ import { error, info } from '../logger/core.js'; import * as msg from '../messages.js'; import { getResolvedHostForHttpServer } from './util.js'; import { vitePluginAstroPreview } from './vite-plugin-astro-preview.js'; -import enableDestroy from 'server-destroy'; export interface PreviewServer { host?: string; From 1f92d64ea35c03fec43aff64eaf704dc5a9eb30a Mon Sep 17 00:00:00 2001 From: Erika <3019731+Princesseuh@users.noreply.github.com> Date: Mon, 9 Jan 2023 22:59:20 +0100 Subject: [PATCH 11/13] Drop Node 14 support (#5782) * chore: Update engines field * fix(deps): Remove node-fetch * feat(polyfills): Remove node-fetch for undici * feat(webapi): Remove node-fetch from the webapis polyfills for undici * feat(core): Remove node-fetch for undici in Astro core * feat(telemetry): Remove node-fetch for undici * feat(node): Remove node-fetch for undici in node integration * feat(vercel): Remove node-fetch for undici in Vercel integration * chore: update lockfile * chore: update lockfile * chore: changeset * fix(set): Fix set directives not streaming correctly on Node 16 * Try another approach * Debugging * Debug fetch * Use global fetch if there is one * changeset for lit * Remove web-streams-polyfill * Remove web-streams-polyfill license note * Update .changeset/stupid-wolves-explain.md Co-authored-by: Nate Moore Co-authored-by: Matthew Phillips Co-authored-by: Nate Moore --- .changeset/chatty-rivers-camp.md | 5 + .changeset/curvy-beds-warn.md | 16 +++ .changeset/stupid-wolves-explain.md | 7 ++ packages/astro-prism/package.json | 2 +- packages/astro/astro.js | 2 +- packages/astro/package.json | 4 +- packages/astro/src/runtime/server/escape.ts | 21 +++- packages/astro/src/runtime/server/response.ts | 10 +- packages/astro/src/runtime/server/util.ts | 14 +++ packages/astro/test/ssr-api-route.test.js | 4 +- packages/astro/test/streaming.test.js | 10 +- packages/astro/test/test-utils.js | 16 ++- packages/create-astro/package.json | 2 +- packages/integrations/lit/server-shim.js | 9 +- packages/integrations/mdx/package.json | 2 +- packages/integrations/node/package.json | 4 +- .../node/src/response-iterator.ts | 2 +- packages/integrations/preact/package.json | 2 +- packages/integrations/react/package.json | 2 +- packages/integrations/solid/package.json | 2 +- packages/integrations/svelte/package.json | 2 +- .../vercel/src/serverless/entrypoint.ts | 11 +- .../node18.ts => request-transform.ts} | 0 .../serverless/request-transform/legacy.ts | 111 ------------------ packages/integrations/vue/package.json | 2 +- packages/telemetry/package.json | 4 +- packages/telemetry/src/post.ts | 2 +- packages/webapi/LICENSE | 4 - packages/webapi/README.md | 4 - packages/webapi/package.json | 5 +- packages/webapi/run/build.js | 20 ++-- packages/webapi/src/lib/fetch.ts | 102 ---------------- packages/webapi/src/ponyfill.ts | 9 +- packages/webapi/src/types.d.ts | 2 - packages/webapi/test/fetch.js | 60 ---------- pnpm-lock.yaml | 60 +++------- 36 files changed, 147 insertions(+), 387 deletions(-) create mode 100644 .changeset/chatty-rivers-camp.md create mode 100644 .changeset/curvy-beds-warn.md create mode 100644 .changeset/stupid-wolves-explain.md rename packages/integrations/vercel/src/serverless/{request-transform/node18.ts => request-transform.ts} (100%) delete mode 100644 packages/integrations/vercel/src/serverless/request-transform/legacy.ts delete mode 100644 packages/webapi/src/lib/fetch.ts diff --git a/.changeset/chatty-rivers-camp.md b/.changeset/chatty-rivers-camp.md new file mode 100644 index 000000000000..99e24e82e447 --- /dev/null +++ b/.changeset/chatty-rivers-camp.md @@ -0,0 +1,5 @@ +--- +'@astrojs/lit': patch +--- + +Only shim fetch if not already present diff --git a/.changeset/curvy-beds-warn.md b/.changeset/curvy-beds-warn.md new file mode 100644 index 000000000000..a09fb9a8176c --- /dev/null +++ b/.changeset/curvy-beds-warn.md @@ -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 diff --git a/.changeset/stupid-wolves-explain.md b/.changeset/stupid-wolves-explain.md new file mode 100644 index 000000000000..742e90147ace --- /dev/null +++ b/.changeset/stupid-wolves-explain.md @@ -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 diff --git a/packages/astro-prism/package.json b/packages/astro-prism/package.json index 938ffe757902..1de10b5951e9 100644 --- a/packages/astro-prism/package.json +++ b/packages/astro-prism/package.json @@ -35,6 +35,6 @@ "@types/prismjs": "1.26.0" }, "engines": { - "node": "^14.18.0 || >=16.12.0" + "node": ">=16.12.0" } } diff --git a/packages/astro/astro.js b/packages/astro/astro.js index 311f9cddad98..b873016bcb44 100755 --- a/packages/astro/astro.js +++ b/packages/astro/astro.js @@ -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. diff --git a/packages/astro/package.json b/packages/astro/package.json index 43f25171d500..e572c07f593b 100644 --- a/packages/astro/package.json +++ b/packages/astro/package.json @@ -199,7 +199,6 @@ "eol": "^0.9.1", "memfs": "^3.4.7", "mocha": "^9.2.2", - "node-fetch": "^3.2.5", "node-mocks-http": "^1.11.0", "rehype-autolink-headings": "^6.1.1", "rehype-slug": "^5.0.1", @@ -208,10 +207,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" } } diff --git a/packages/astro/src/runtime/server/escape.ts b/packages/astro/src/runtime/server/escape.ts index 48041a96b0d9..879f1e75bfb9 100644 --- a/packages/astro/src/runtime/server/escape.ts +++ b/packages/astro/src/runtime/server/escape.ts @@ -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; @@ -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): 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); + } } } @@ -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; + const body = str.body; return unescapeChunksAsync(body); } // If a promise, await the result and mark that. @@ -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); } } diff --git a/packages/astro/src/runtime/server/response.ts b/packages/astro/src/runtime/server/response.ts index ae374d1aa14e..a39ceaa439d0 100644 --- a/packages/astro/src/runtime/server/response.ts +++ b/packages/astro/src/runtime/server/response.ts @@ -1,3 +1,5 @@ +import { streamAsyncIterator } from './util.js'; + const isNodeJS = typeof process === 'object' && Object.prototype.toString.call(process) === '[object process]'; @@ -21,9 +23,9 @@ function createResponseClass() { async text(): Promise { if (this.#isStream && isNodeJS) { let decoder = new TextDecoder(); - let body = this.#body as AsyncIterable; + 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; @@ -33,10 +35,10 @@ function createResponseClass() { async arrayBuffer(): Promise { if (this.#isStream && isNodeJS) { - let body = this.#body as AsyncIterable; + 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; } diff --git a/packages/astro/src/runtime/server/util.ts b/packages/astro/src/runtime/server/util.ts index 9f0fdbec253f..b38fe5ef1b9f 100644 --- a/packages/astro/src/runtime/server/util.ts +++ b/packages/astro/src/runtime/server/util.ts @@ -31,3 +31,17 @@ export function serializeListValue(value: any) { export function isPromise(value: any): value is Promise { return !!value && typeof value === 'object' && typeof value.then === 'function'; } + +export async function* streamAsyncIterator(stream: ReadableStream) { + const reader = stream.getReader(); + + try { + while (true) { + const { done, value } = await reader.read(); + if (done) return; + yield value; + } + } finally { + reader.releaseLock(); + } +} diff --git a/packages/astro/test/ssr-api-route.test.js b/packages/astro/test/ssr-api-route.test.js index 33b1ffdabcf7..cafbdf32c70a 100644 --- a/packages/astro/test/ssr-api-route.test.js +++ b/packages/astro/test/ssr-api-route.test.js @@ -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} */ diff --git a/packages/astro/test/streaming.test.js b/packages/astro/test/streaming.test.js index bc50c031818e..47dedac22a47 100644 --- a/packages/astro/test/streaming.test.js +++ b/packages/astro/test/streaming.test.js @@ -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; @@ -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); } @@ -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); } @@ -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); } diff --git a/packages/astro/test/test-utils.js b/packages/astro/test/test-utils.js index 63b79d7635b4..27e4caa5ea9c 100644 --- a/packages/astro/test/test-utils.js +++ b/packages/astro/test/test-utils.js @@ -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 @@ -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(); + } +} diff --git a/packages/create-astro/package.json b/packages/create-astro/package.json index 05e2ecebf3c2..b009db1a8865 100644 --- a/packages/create-astro/package.json +++ b/packages/create-astro/package.json @@ -54,6 +54,6 @@ "uvu": "^0.5.3" }, "engines": { - "node": "^14.18.0 || >=16.12.0" + "node": ">=16.12.0" } } diff --git a/packages/integrations/lit/server-shim.js b/packages/integrations/lit/server-shim.js index 9a4c7e408ed0..873d3cd8278e 100644 --- a/packages/integrations/lit/server-shim.js +++ b/packages/integrations/lit/server-shim.js @@ -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 = () => []; diff --git a/packages/integrations/mdx/package.json b/packages/integrations/mdx/package.json index d9e138893f00..8ca5c9bfd926 100644 --- a/packages/integrations/mdx/package.json +++ b/packages/integrations/mdx/package.json @@ -71,6 +71,6 @@ "vite": "^4.0.3" }, "engines": { - "node": "^14.18.0 || >=16.12.0" + "node": ">=16.12.0" } } diff --git a/packages/integrations/node/package.json b/packages/integrations/node/package.json index 3e3bd22580f5..779b8792be2b 100644 --- a/packages/integrations/node/package.json +++ b/packages/integrations/node/package.json @@ -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" } } diff --git a/packages/integrations/node/src/response-iterator.ts b/packages/integrations/node/src/response-iterator.ts index 7700e9331e92..becd8be1b505 100644 --- a/packages/integrations/node/src/response-iterator.ts +++ b/packages/integrations/node/src/response-iterator.ts @@ -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 { diff --git a/packages/integrations/preact/package.json b/packages/integrations/preact/package.json index 60932161656d..acb94c320001 100644 --- a/packages/integrations/preact/package.json +++ b/packages/integrations/preact/package.json @@ -47,6 +47,6 @@ "preact": "^10.6.5" }, "engines": { - "node": "^14.18.0 || >=16.12.0" + "node": ">=16.12.0" } } diff --git a/packages/integrations/react/package.json b/packages/integrations/react/package.json index 677d0d24a0ce..4bc1c1afad5c 100644 --- a/packages/integrations/react/package.json +++ b/packages/integrations/react/package.json @@ -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" } } diff --git a/packages/integrations/solid/package.json b/packages/integrations/solid/package.json index 94f654e8277e..334b0fb8173e 100644 --- a/packages/integrations/solid/package.json +++ b/packages/integrations/solid/package.json @@ -44,6 +44,6 @@ "solid-js": "^1.4.3" }, "engines": { - "node": "^14.18.0 || >=16.12.0" + "node": ">=16.12.0" } } diff --git a/packages/integrations/svelte/package.json b/packages/integrations/svelte/package.json index 6a073d4b8a7b..1ad7abcc5d7a 100644 --- a/packages/integrations/svelte/package.json +++ b/packages/integrations/svelte/package.json @@ -47,6 +47,6 @@ "astro": "workspace:^2.0.0-beta.0" }, "engines": { - "node": "^14.18.0 || >=16.12.0" + "node": ">=16.12.0" } } diff --git a/packages/integrations/vercel/src/serverless/entrypoint.ts b/packages/integrations/vercel/src/serverless/entrypoint.ts index daa811015b86..71ad2bfaef7f 100644 --- a/packages/integrations/vercel/src/serverless/entrypoint.ts +++ b/packages/integrations/vercel/src/serverless/entrypoint.ts @@ -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); diff --git a/packages/integrations/vercel/src/serverless/request-transform/node18.ts b/packages/integrations/vercel/src/serverless/request-transform.ts similarity index 100% rename from packages/integrations/vercel/src/serverless/request-transform/node18.ts rename to packages/integrations/vercel/src/serverless/request-transform.ts diff --git a/packages/integrations/vercel/src/serverless/request-transform/legacy.ts b/packages/integrations/vercel/src/serverless/request-transform/legacy.ts deleted file mode 100644 index 7212431c72a0..000000000000 --- a/packages/integrations/vercel/src/serverless/request-transform/legacy.ts +++ /dev/null @@ -1,111 +0,0 @@ -import type { App } from 'astro/app'; -import type { IncomingMessage, ServerResponse } from 'node:http'; -import { Readable } from 'node:stream'; - -const clientAddressSymbol = Symbol.for('astro.clientAddress'); - -/* - Credits to the SvelteKit team - https://github.com/sveltejs/kit/blob/69913e9fda054fa6a62a80e2bb4ee7dca1005796/packages/kit/src/node.js -*/ - -function get_raw_body(req: IncomingMessage) { - return new Promise((fulfil, reject) => { - const h = req.headers; - - if (!h['content-type']) { - return fulfil(null); - } - - req.on('error', reject); - - const length = Number(h['content-length']); - - // https://github.com/jshttp/type-is/blob/c1f4388c71c8a01f79934e68f630ca4a15fffcd6/index.js#L81-L95 - if (isNaN(length) && h['transfer-encoding'] == null) { - return fulfil(null); - } - - let data = new Uint8Array(length || 0); - - if (length > 0) { - let offset = 0; - req.on('data', (chunk) => { - const new_len = offset + Buffer.byteLength(chunk); - - if (new_len > length) { - return reject({ - status: 413, - reason: 'Exceeded "Content-Length" limit', - }); - } - - data.set(chunk, offset); - offset = new_len; - }); - } else { - req.on('data', (chunk) => { - const new_data = new Uint8Array(data.length + chunk.length); - new_data.set(data, 0); - new_data.set(chunk, data.length); - data = new_data; - }); - } - - req.on('end', () => { - fulfil(data); - }); - }); -} - -export async function getRequest(base: string, req: IncomingMessage): Promise { - let headers = req.headers as Record; - if (req.httpVersionMajor === 2) { - // we need to strip out the HTTP/2 pseudo-headers because node-fetch's - // Request implementation doesn't like them - headers = Object.assign({}, headers); - delete headers[':method']; - delete headers[':path']; - delete headers[':authority']; - delete headers[':scheme']; - } - const request = new Request(base + req.url, { - method: req.method, - headers, - body: await get_raw_body(req), // TODO stream rather than buffer - }); - Reflect.set(request, clientAddressSymbol, headers['x-forwarded-for']); - return request; -} - -export async function setResponse( - app: App, - res: ServerResponse, - response: Response -): Promise { - const headers = Object.fromEntries(response.headers); - - if (response.headers.has('set-cookie')) { - // @ts-expect-error (headers.raw() is non-standard) - headers['set-cookie'] = response.headers.raw()['set-cookie']; - } - - if (app.setCookieHeaders) { - const setCookieHeaders: Array = Array.from(app.setCookieHeaders(response)); - if (setCookieHeaders.length) { - res.setHeader('Set-Cookie', setCookieHeaders); - } - } - - res.writeHead(response.status, headers); - - if (response.body instanceof Readable) { - response.body.pipe(res); - } else { - if (response.body) { - res.write(await response.arrayBuffer()); - } - - res.end(); - } -} diff --git a/packages/integrations/vue/package.json b/packages/integrations/vue/package.json index c0655a85db2f..a8b9b8fd87f3 100644 --- a/packages/integrations/vue/package.json +++ b/packages/integrations/vue/package.json @@ -54,6 +54,6 @@ "astro": "workspace:^2.0.0-beta.0" }, "engines": { - "node": "^14.18.0 || >=16.12.0" + "node": ">=16.12.0" } } diff --git a/packages/telemetry/package.json b/packages/telemetry/package.json index bf45c1560a3c..32bd2a1d1e77 100644 --- a/packages/telemetry/package.json +++ b/packages/telemetry/package.json @@ -32,7 +32,7 @@ "dset": "^3.1.2", "is-docker": "^3.0.0", "is-wsl": "^2.2.0", - "node-fetch": "^3.2.5", + "undici": "^5.14.0", "which-pm-runs": "^1.1.0" }, "devDependencies": { @@ -45,6 +45,6 @@ "mocha": "^9.2.2" }, "engines": { - "node": "^14.18.0 || >=16.12.0" + "node": ">=16.12.0" } } diff --git a/packages/telemetry/src/post.ts b/packages/telemetry/src/post.ts index a0647075f988..4ce22738843b 100644 --- a/packages/telemetry/src/post.ts +++ b/packages/telemetry/src/post.ts @@ -1,4 +1,4 @@ -import fetch from 'node-fetch'; +import { fetch } from 'undici'; const ASTRO_TELEMETRY_ENDPOINT = `https://telemetry.astro.build/api/v1/record`; diff --git a/packages/webapi/LICENSE b/packages/webapi/LICENSE index 9dda027eb4b2..7dc74ec3874e 100644 --- a/packages/webapi/LICENSE +++ b/packages/webapi/LICENSE @@ -31,7 +31,3 @@ Code from [event-target-shim](https://www.npmjs.com/package/event-target-shim) i Code from [fetch-blob](https://www.npmjs.com/package/fetch-blob) is licensed under the MIT License (MIT), Copyright Jimmy Wärting. Code from [formdata-polyfill](https://www.npmjs.com/package/formdata-polyfill) is licensed under the MIT License (MIT), Copyright Jimmy Wärting. - -Code from [node-fetch](https://www.npmjs.com/package/node-fetch) is licensed under the MIT License (MIT), Copyright Node Fetch Team. - -Code from [web-streams-polyfill](https://www.npmjs.com/package/web-streams-polyfill) is licensed under the MIT License (MIT), Copyright Mattias Buelens and Diwank Singh Tomer. diff --git a/packages/webapi/README.md b/packages/webapi/README.md index 8e1c66280444..2f7726e9a839 100644 --- a/packages/webapi/README.md +++ b/packages/webapi/README.md @@ -173,7 +173,3 @@ Code from [event-target-shim](https://www.npmjs.com/package/event-target-shim) i Code from [fetch-blob](https://www.npmjs.com/package/fetch-blob) is licensed under the MIT License (MIT), Copyright Jimmy Wärting. Code from [formdata-polyfill](https://www.npmjs.com/package/formdata-polyfill) is licensed under the MIT License (MIT), Copyright Jimmy Wärting. - -Code from [node-fetch](https://www.npmjs.com/package/node-fetch) is licensed under the MIT License (MIT), Copyright Node Fetch Team. - -Code from [web-streams-polyfill](https://www.npmjs.com/package/web-streams-polyfill) is licensed under the MIT License (MIT), Copyright Mattias Buelens and Diwank Singh Tomer. diff --git a/packages/webapi/package.json b/packages/webapi/package.json index f4e417b47445..e712e541d89c 100644 --- a/packages/webapi/package.json +++ b/packages/webapi/package.json @@ -51,7 +51,7 @@ "homepage": "https://github.com/withastro/astro/tree/main/packages/webapi#readme", "dependencies": { "global-agent": "^3.0.0", - "node-fetch": "^3.2.5" + "undici": "^5.14.0" }, "devDependencies": { "@rollup/plugin-alias": "^3.1.9", @@ -74,8 +74,7 @@ "rollup-plugin-terser": "^7.0.2", "tslib": "^2.4.0", "typescript": "~4.7.3", - "urlpattern-polyfill": "^1.0.0-rc5", - "web-streams-polyfill": "^3.2.1" + "urlpattern-polyfill": "^1.0.0-rc5" }, "scripts": { "build": "node run/build.js", diff --git a/packages/webapi/run/build.js b/packages/webapi/run/build.js index 63e17e84ce06..154d6ffbd57c 100644 --- a/packages/webapi/run/build.js +++ b/packages/webapi/run/build.js @@ -1,17 +1,17 @@ -import { rollup } from 'rollup' +import { default as alias } from '@rollup/plugin-alias' +import { default as inject } from '@rollup/plugin-inject' import { nodeResolve } from '@rollup/plugin-node-resolve' -import path from 'node:path' -import { createRequire } from 'node:module' +import { default as typescript } from '@rollup/plugin-typescript' +import { default as MagicString } from 'magic-string' import { readFile as nodeReadFile, rename, rm, writeFile, } from 'node:fs/promises' -import { default as MagicString } from 'magic-string' -import { default as alias } from '@rollup/plugin-alias' -import { default as inject } from '@rollup/plugin-inject' -import { default as typescript } from '@rollup/plugin-typescript' +import { createRequire } from 'node:module' +import path from 'node:path' +import { rollup } from 'rollup' const readFileCache = Object.create(null) const require = createRequire(import.meta.url) @@ -76,13 +76,13 @@ const plugins = [ MediaQueryList: ['./MediaQueryList', 'MediaQueryList'], Node: ['./Node', 'Node'], ReadableStream: [ - 'web-streams-polyfill/dist/ponyfill.es6.mjs', + 'node:stream/web', 'ReadableStream', ], ShadowRoot: ['./Node', 'ShadowRoot'], Window: ['./Window', 'Window'], 'globalThis.ReadableStream': [ - 'web-streams-polyfill/dist/ponyfill.es6.mjs', + 'node:stream/web', 'ReadableStream', ], }), @@ -178,7 +178,7 @@ async function build() { inputOptions: { input: 'src/polyfill.ts', plugins: plugins, - external: ['node-fetch', 'global-agent'], + external: ['undici', 'global-agent'], onwarn(warning, warn) { if (warning.code !== 'UNRESOLVED_IMPORT') warn(warning) }, diff --git a/packages/webapi/src/lib/fetch.ts b/packages/webapi/src/lib/fetch.ts deleted file mode 100644 index f8500a84670a..000000000000 --- a/packages/webapi/src/lib/fetch.ts +++ /dev/null @@ -1,102 +0,0 @@ -import { bootstrap as bootstrapGlobalAgent } from 'global-agent' -import type { RequestInit } from 'node-fetch' -import { default as nodeFetch, Headers, Request, Response } from 'node-fetch' -import Stream from 'node:stream' -import * as _ from './utils' - -bootstrapGlobalAgent({ - environmentVariableNamespace: '', -}) - -export { Headers, Request, Response } - -export const fetch = { - fetch( - resource: string | Request, - init?: Partial - ): Promise { - const resourceURL = new URL( - _.__object_isPrototypeOf(Request.prototype, resource) - ? (resource as Request).url - : _.pathToPosix(resource), - typeof Object(globalThis.process).cwd === 'function' - ? 'file:' + _.pathToPosix(process.cwd()) + '/' - : 'file:' - ) - - if (resourceURL.protocol.toLowerCase() === 'file:') { - return import('node:fs').then((fs) => { - try { - const stats = fs.statSync(resourceURL) - const body = fs.createReadStream(resourceURL) - - return new Response(body, { - status: 200, - statusText: '', - headers: { - 'content-length': String(stats.size), - date: new Date().toUTCString(), - 'last-modified': new Date(stats.mtimeMs).toUTCString(), - }, - }) - } catch (error) { - const body = new Stream.Readable() - - body._read = () => {} - body.push(null) - - return new Response(body, { - status: 404, - statusText: '', - headers: { - date: new Date().toUTCString(), - }, - }) - } - }) - } else { - return nodeFetch(resource, init) - } - }, -}.fetch - -type USVString = {} & string - -interface FetchInit { - body: RequestInit['body'] - cache: - | 'default' - | 'no-store' - | 'reload' - | 'no-cache' - | 'force-cache' - | 'only-if-cached' - credentials: 'omit' | 'same-origin' | 'include' - headers: Headers | Record - method: - | 'GET' - | 'HEAD' - | 'POST' - | 'PUT' - | 'DELETE' - | 'CONNECT' - | 'OPTIONS' - | 'TRACE' - | 'PATCH' - | USVString - mode: 'cors' | 'no-cors' | 'same-origin' | USVString - redirect: 'follow' | 'manual' | 'error' - referrer: USVString - referrerPolicy: - | 'no-referrer' - | 'no-referrer-when-downgrade' - | 'same-origin' - | 'origin' - | 'strict-origin' - | 'origin-when-cross-origin' - | 'strict-origin-when-cross-origin' - | 'unsafe-url' - integrity: USVString - keepalive: boolean - signal: AbortSignal -} diff --git a/packages/webapi/src/ponyfill.ts b/packages/webapi/src/ponyfill.ts index fc92975b5951..a1088bdb4fb5 100644 --- a/packages/webapi/src/ponyfill.ts +++ b/packages/webapi/src/ponyfill.ts @@ -7,6 +7,7 @@ import { import { Event, EventTarget } from 'event-target-shim' import { Blob, File } from 'fetch-blob/from.js' import { FormData } from 'formdata-polyfill/esm.min.js' +import * as undici from 'undici' import { URLPattern } from 'urlpattern-polyfill' import { ByteLengthQueuingStrategy, @@ -21,7 +22,7 @@ import { WritableStream, WritableStreamDefaultController, WritableStreamDefaultWriter, -} from 'web-streams-polyfill/dist/ponyfill.es6.mjs' +} from 'node:stream/web' import { cancelAnimationFrame, requestAnimationFrame, @@ -30,7 +31,6 @@ import { atob, btoa } from './lib/Base64' import { CharacterData, Comment, Text } from './lib/CharacterData' import { CustomEvent } from './lib/CustomEvent' import { DOMException } from './lib/DOMException' -import { fetch, Headers, Request, Response } from './lib/fetch' import { cancelIdleCallback, requestIdleCallback } from './lib/IdleCallback' import structuredClone from './lib/structuredClone' import { clearTimeout, setTimeout } from './lib/Timeout' @@ -83,6 +83,11 @@ import { initPromise } from './lib/Promise' import { initRelativeIndexingMethod } from './lib/RelativeIndexingMethod' import { initString } from './lib/String' +const fetch = undici.fetch +const Headers = undici.Headers +const Response = undici.Response +const Request = undici.Request + export { AbortController, AbortSignal, diff --git a/packages/webapi/src/types.d.ts b/packages/webapi/src/types.d.ts index 09c57387bcf2..2597566eedd8 100644 --- a/packages/webapi/src/types.d.ts +++ b/packages/webapi/src/types.d.ts @@ -3,5 +3,3 @@ declare module '@ungap/structured-clone/esm/index.js' declare module '@ungap/structured-clone/esm/deserialize.js' declare module '@ungap/structured-clone/esm/serialize.js' declare module 'abort-controller/dist/abort-controller.mjs' -declare module 'node-fetch/src/index.js' -declare module 'web-streams-polyfill/dist/ponyfill.es6.mjs' diff --git a/packages/webapi/test/fetch.js b/packages/webapi/test/fetch.js index ae5ae038649b..49aab31dc2d0 100644 --- a/packages/webapi/test/fetch.js +++ b/packages/webapi/test/fetch.js @@ -22,66 +22,6 @@ describe('Fetch', () => { expect(json).to.be.an('array') }) - it('Fetch with file', async () => { - const { fetch } = target - - const url = new URL('../package.json', import.meta.url) - - const response = await fetch(url) - - expect(response.constructor).to.equal(target.Response) - - expect(response.status).to.equal(200) - expect(response.statusText).to.be.empty - expect(response.headers.has('date')).to.equal(true) - expect(response.headers.has('content-length')).to.equal(true) - expect(response.headers.has('last-modified')).to.equal(true) - - const json = await response.json() - - expect(json.name).to.equal('@astrojs/webapi') - }) - - it('Fetch with missing file', async () => { - const { fetch } = target - - const url = new URL('../missing.json', import.meta.url) - - const response = await fetch(url) - - expect(response.constructor).to.equal(target.Response) - - expect(response.status).to.equal(404) - expect(response.statusText).to.be.empty - expect(response.headers.has('date')).to.equal(true) - expect(response.headers.has('content-length')).to.equal(false) - expect(response.headers.has('last-modified')).to.equal(false) - }) - - it('Fetch with (file) Request', async () => { - const { Request, fetch } = target - - const request = new Request(new URL('../package.json', import.meta.url)) - - const response = await fetch(request) - - expect(response.constructor).to.equal(target.Response) - - const json = await response.json() - - expect(json.name).to.equal('@astrojs/webapi') - }) - - it('Fetch with relative file', async () => { - const { fetch } = target - - const response = await fetch('package.json') - - const json = await response.json() - - expect(json.name).to.equal('@astrojs/webapi') - }) - it('Fetch with data', async () => { const { fetch } = target diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6434adaf6f79..6de33571e8a3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -448,7 +448,6 @@ importers: memfs: ^3.4.7 mime: ^3.0.0 mocha: ^9.2.2 - node-fetch: ^3.2.5 node-mocks-http: ^1.11.0 ora: ^6.1.0 path-browserify: ^1.0.1 @@ -476,6 +475,7 @@ importers: supports-esm: ^1.0.0 tsconfig-resolver: ^3.0.1 typescript: '*' + undici: ^5.14.0 unified: ^10.1.2 unist-util-visit: ^4.1.0 vfile: ^5.3.2 @@ -573,7 +573,6 @@ importers: eol: 0.9.1 memfs: 3.4.13 mocha: 9.2.2 - node-fetch: 3.3.0 node-mocks-http: 1.12.1 rehype-autolink-headings: 6.1.1 rehype-slug: 5.1.0 @@ -582,6 +581,7 @@ importers: rollup: 3.9.1 sass: 1.57.1 srcset-parse: 1.1.0 + undici: 5.14.0 unified: 10.1.2 packages/astro-prism: @@ -3077,7 +3077,6 @@ importers: packages/integrations/node: specifiers: '@astrojs/webapi': ^1.1.1 - '@types/node-fetch': ^2.6.2 '@types/send': ^0.17.1 astro: workspace:* astro-scripts: workspace:* @@ -3085,17 +3084,18 @@ importers: mocha: ^9.2.2 node-mocks-http: ^1.11.0 send: ^0.18.0 + undici: ^5.14.0 dependencies: '@astrojs/webapi': link:../../webapi send: 0.18.0 devDependencies: - '@types/node-fetch': 2.6.2 '@types/send': 0.17.1 astro: link:../../astro astro-scripts: link:../../../scripts chai: 4.3.7 mocha: 9.2.2 node-mocks-http: 1.12.1 + undici: 5.14.0 packages/integrations/node/test/fixtures/api-route: specifiers: @@ -3536,7 +3536,7 @@ importers: is-docker: ^3.0.0 is-wsl: ^2.2.0 mocha: ^9.2.2 - node-fetch: ^3.2.5 + undici: ^5.14.0 which-pm-runs: ^1.1.0 dependencies: ci-info: 3.7.1 @@ -3545,7 +3545,7 @@ importers: dset: 3.1.2 is-docker: 3.0.0 is-wsl: 2.2.0 - node-fetch: 3.3.0 + undici: 5.14.0 which-pm-runs: 1.1.0 devDependencies: '@types/debug': 4.1.7 @@ -3575,16 +3575,15 @@ importers: global-agent: ^3.0.0 magic-string: ^0.25.9 mocha: ^9.2.2 - node-fetch: ^3.2.5 rollup: ^2.79.1 rollup-plugin-terser: ^7.0.2 tslib: ^2.4.0 typescript: ~4.7.3 + undici: ^5.14.0 urlpattern-polyfill: ^1.0.0-rc5 - web-streams-polyfill: ^3.2.1 dependencies: global-agent: 3.0.0 - node-fetch: 3.3.0 + undici: 5.14.0 devDependencies: '@rollup/plugin-alias': 3.1.9_rollup@2.79.1 '@rollup/plugin-inject': 4.0.4_rollup@2.79.1 @@ -3607,7 +3606,6 @@ importers: tslib: 2.4.1 typescript: 4.7.4 urlpattern-polyfill: 1.0.0-rc5 - web-streams-polyfill: 3.2.1 scripts: specifiers: @@ -7044,13 +7042,6 @@ packages: '@types/unist': 2.0.6 dev: false - /@types/node-fetch/2.6.2: - resolution: {integrity: sha512-DHqhlq5jeESLy19TYhLakJ07kNumXWjcDdxXsLUMJZ6ue8VZJj4kLPQVE/2mdHh3xZziNF1xppu5lwmS53HR+A==} - dependencies: - '@types/node': 18.11.18 - form-data: 3.0.1 - dev: true - /@types/node/12.20.55: resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==} dev: true @@ -8007,10 +7998,6 @@ packages: resolution: {integrity: sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==} dev: false - /asynckit/0.4.0: - resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} - dev: true - /at-least-node/1.0.0: resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==} engines: {node: '>= 4.0.0'} @@ -8272,7 +8259,6 @@ packages: engines: {node: '>=10.16.0'} dependencies: streamsearch: 1.1.0 - dev: true /bytes/3.1.2: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} @@ -8559,13 +8545,6 @@ packages: resolution: {integrity: sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==} dev: false - /combined-stream/1.0.8: - resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} - engines: {node: '>= 0.8'} - dependencies: - delayed-stream: 1.0.0 - dev: true - /comma-separated-tokens/2.0.3: resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} dev: false @@ -8775,6 +8754,7 @@ packages: /data-uri-to-buffer/4.0.0: resolution: {integrity: sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA==} engines: {node: '>= 12'} + dev: false /dataloader/1.4.0: resolution: {integrity: sha512-68s5jYdlvasItOJnCuI2Q9s4q98g0pCyL3HrcKJu8KNugUl8ahgmZYg38ysLTgQjjXX3H8CJLkAvWrclWfcalw==} @@ -8927,11 +8907,6 @@ packages: slash: 4.0.0 dev: true - /delayed-stream/1.0.0: - resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} - engines: {node: '>=0.4.0'} - dev: true - /delegates/1.0.0: resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==} dev: false @@ -10094,15 +10069,6 @@ packages: dependencies: is-callable: 1.2.7 - /form-data/3.0.1: - resolution: {integrity: sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==} - engines: {node: '>= 6'} - dependencies: - asynckit: 0.4.0 - combined-stream: 1.0.8 - mime-types: 2.1.35 - dev: true - /format/0.2.2: resolution: {integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==} engines: {node: '>=0.4.x'} @@ -12347,6 +12313,7 @@ packages: data-uri-to-buffer: 4.0.0 fetch-blob: 3.2.0 formdata-polyfill: 4.0.10 + dev: false /node-forge/1.3.1: resolution: {integrity: sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==} @@ -14297,7 +14264,6 @@ packages: /streamsearch/1.1.0: resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} engines: {node: '>=10.0.0'} - dev: true /string-width/4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} @@ -14978,6 +14944,12 @@ packages: jiti: 1.16.1 dev: false + /undici/5.14.0: + resolution: {integrity: sha512-yJlHYw6yXPPsuOH0x2Ib1Km61vu4hLiRRQoafs+WUgX1vO64vgnxiCEN9dpIrhZyHFsai3F0AEj4P9zy19enEQ==} + engines: {node: '>=12.18'} + dependencies: + busboy: 1.6.0 + /undici/5.9.1: resolution: {integrity: sha512-6fB3a+SNnWEm4CJbgo0/CWR8RGcOCQP68SF4X0mxtYTq2VNN8T88NYrWVBAeSX+zb7bny2dx2iYhP3XHi00omg==} engines: {node: '>=12.18'} From 10137cd9cc2f16b93160947c8b4f448a7a100109 Mon Sep 17 00:00:00 2001 From: matthewp Date: Mon, 9 Jan 2023 22:01:33 +0000 Subject: [PATCH 12/13] [ci] format --- packages/integrations/lit/server-shim.js | 2 +- packages/integrations/node/src/response-iterator.ts | 2 +- packages/webapi/run/build.js | 10 ++-------- packages/webapi/src/ponyfill.ts | 4 ++-- 4 files changed, 6 insertions(+), 12 deletions(-) diff --git a/packages/integrations/lit/server-shim.js b/packages/integrations/lit/server-shim.js index 873d3cd8278e..3f4a8df4fbd2 100644 --- a/packages/integrations/lit/server-shim.js +++ b/packages/integrations/lit/server-shim.js @@ -1,6 +1,6 @@ import { installWindowOnGlobal } from '@lit-labs/ssr/lib/dom-shim.js'; -if(typeof fetch === 'function') { +if (typeof fetch === 'function') { const _fetch = fetch; installWindowOnGlobal(); globalThis.fetch = window.fetch = _fetch; diff --git a/packages/integrations/node/src/response-iterator.ts b/packages/integrations/node/src/response-iterator.ts index becd8be1b505..890902e1e039 100644 --- a/packages/integrations/node/src/response-iterator.ts +++ b/packages/integrations/node/src/response-iterator.ts @@ -4,8 +4,8 @@ * - https://github.com/apollographql/apollo-client/blob/main/src/utilities/common/responseIterator.ts */ -import type { Response as NodeResponse } from 'undici'; import { Readable as NodeReadableStream } from 'stream'; +import type { Response as NodeResponse } from 'undici'; interface NodeStreamIterator { next(): Promise>; diff --git a/packages/webapi/run/build.js b/packages/webapi/run/build.js index 154d6ffbd57c..2ec835644c53 100644 --- a/packages/webapi/run/build.js +++ b/packages/webapi/run/build.js @@ -75,16 +75,10 @@ const plugins = [ HTMLUnknownElement: ['./Element', 'HTMLUnknownElement'], MediaQueryList: ['./MediaQueryList', 'MediaQueryList'], Node: ['./Node', 'Node'], - ReadableStream: [ - 'node:stream/web', - 'ReadableStream', - ], + ReadableStream: ['node:stream/web', 'ReadableStream'], ShadowRoot: ['./Node', 'ShadowRoot'], Window: ['./Window', 'Window'], - 'globalThis.ReadableStream': [ - 'node:stream/web', - 'ReadableStream', - ], + 'globalThis.ReadableStream': ['node:stream/web', 'ReadableStream'], }), { async load(id) { diff --git a/packages/webapi/src/ponyfill.ts b/packages/webapi/src/ponyfill.ts index a1088bdb4fb5..efaad8cd34ae 100644 --- a/packages/webapi/src/ponyfill.ts +++ b/packages/webapi/src/ponyfill.ts @@ -7,8 +7,6 @@ import { import { Event, EventTarget } from 'event-target-shim' import { Blob, File } from 'fetch-blob/from.js' import { FormData } from 'formdata-polyfill/esm.min.js' -import * as undici from 'undici' -import { URLPattern } from 'urlpattern-polyfill' import { ByteLengthQueuingStrategy, CountQueuingStrategy, @@ -23,6 +21,8 @@ import { WritableStreamDefaultController, WritableStreamDefaultWriter, } from 'node:stream/web' +import * as undici from 'undici' +import { URLPattern } from 'urlpattern-polyfill' import { cancelAnimationFrame, requestAnimationFrame, From 4a1cabfe6b9ef8a6fbbcc0727a0dc6fa300cedaa Mon Sep 17 00:00:00 2001 From: Bjorn Lu Date: Tue, 10 Jan 2023 15:58:55 +0800 Subject: [PATCH 13/13] Cleanup dependencies (#5773) --- .changeset/six-carpets-talk.md | 5 +++ packages/astro/package.json | 11 +------ packages/astro/src/core/config/schema.ts | 33 ------------------- packages/astro/src/core/create-vite.ts | 24 ++------------ packages/astro/src/core/render/ssr-element.ts | 9 ++--- packages/astro/src/core/util.ts | 11 +------ packages/astro/src/template/4xx.ts | 4 +-- packages/astro/src/vite-plugin-jsx/tag.ts | 7 +--- pnpm-lock.yaml | 26 ++------------- 9 files changed, 20 insertions(+), 110 deletions(-) create mode 100644 .changeset/six-carpets-talk.md diff --git a/.changeset/six-carpets-talk.md b/.changeset/six-carpets-talk.md new file mode 100644 index 000000000000..8d0ab63735e5 --- /dev/null +++ b/.changeset/six-carpets-talk.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Cleanup dependencies diff --git a/packages/astro/package.json b/packages/astro/package.json index e572c07f593b..b73779f8c608 100644 --- a/packages/astro/package.json +++ b/packages/astro/package.json @@ -121,7 +121,6 @@ "@babel/traverse": "^7.18.2", "@babel/types": "^7.18.4", "@types/babel__core": "^7.1.19", - "@types/html-escaper": "^3.0.0", "@types/yargs-parser": "^21.0.0", "acorn": "^8.8.1", "boxen": "^6.2.1", @@ -138,22 +137,15 @@ "fast-glob": "^3.2.11", "github-slugger": "^2.0.0", "gray-matter": "^4.0.3", - "html-entities": "^2.3.3", "html-escaper": "^3.0.3", - "import-meta-resolve": "^2.1.0", "kleur": "^4.1.4", "magic-string": "^0.27.0", "mime": "^3.0.0", "ora": "^6.1.0", - "path-browserify": "^1.0.1", "path-to-regexp": "^6.2.1", - "postcss": "^8.4.14", - "postcss-load-config": "^3.1.4", "preferred-pm": "^3.0.3", "prompts": "^2.4.2", - "recast": "^0.20.5", "rehype": "^12.0.1", - "resolve": "^1.22.0", "semver": "^7.3.7", "server-destroy": "^1.0.1", "shiki": "^0.11.1", @@ -182,10 +174,9 @@ "@types/diff": "^5.0.2", "@types/estree": "^0.0.51", "@types/hast": "^2.3.4", + "@types/html-escaper": "^3.0.0", "@types/mime": "^2.0.3", "@types/mocha": "^9.1.1", - "@types/parse5": "^6.0.3", - "@types/path-browserify": "^1.0.0", "@types/prettier": "^2.6.3", "@types/prompts": "^2.0.14", "@types/resolve": "^1.20.2", diff --git a/packages/astro/src/core/config/schema.ts b/packages/astro/src/core/config/schema.ts index a31dfef9e1b1..bdf7d4e5de54 100644 --- a/packages/astro/src/core/config/schema.ts +++ b/packages/astro/src/core/config/schema.ts @@ -1,16 +1,12 @@ import type { RehypePlugin, RemarkPlugin, RemarkRehype } from '@astrojs/markdown-remark'; import { markdownConfigDefaults } from '@astrojs/markdown-remark'; -import type * as Postcss from 'postcss'; import type { ILanguageRegistration, IThemeRegistration, Theme } from 'shiki'; import type { AstroUserConfig, ViteUserConfig } from '../../@types/astro'; import { OutgoingHttpHeaders } from 'http'; -import postcssrc from 'postcss-load-config'; import { BUNDLED_THEMES } from 'shiki'; -import { fileURLToPath } from 'url'; import { z } from 'zod'; import { appendForwardSlash, prependForwardSlash, trimSlashes } from '../path.js'; -import { isObject } from '../util.js'; const ASTRO_CONFIG_DEFAULTS: AstroUserConfig & any = { root: '.', @@ -183,35 +179,6 @@ export const AstroConfigSchema = z.object({ legacy: z.object({}).optional().default({}), }); -interface PostCSSConfigResult { - options: Postcss.ProcessOptions; - plugins: Postcss.Plugin[]; -} - -async function resolvePostcssConfig(inlineOptions: any, root: URL): Promise { - if (isObject(inlineOptions)) { - const options = { ...inlineOptions }; - delete options.plugins; - return { - options, - plugins: inlineOptions.plugins || [], - }; - } - const searchPath = typeof inlineOptions === 'string' ? inlineOptions : fileURLToPath(root); - try { - // @ts-ignore - return await postcssrc({}, searchPath); - } catch (err: any) { - if (!/No PostCSS Config found/.test(err.message)) { - throw err; - } - return { - options: {}, - plugins: [], - }; - } -} - export function createRelativeSchema(cmd: string, fileProtocolRoot: URL) { // We need to extend the global schema to add transforms that are relative to root. // This is type checked against the global schema to make sure we still match. diff --git a/packages/astro/src/core/create-vite.ts b/packages/astro/src/core/create-vite.ts index 351ea631815f..cccda8545d9f 100644 --- a/packages/astro/src/core/create-vite.ts +++ b/packages/astro/src/core/create-vite.ts @@ -24,7 +24,6 @@ import markdownVitePlugin from '../vite-plugin-markdown/index.js'; import astroScannerPlugin from '../vite-plugin-scanner/index.js'; import astroScriptsPlugin from '../vite-plugin-scripts/index.js'; import astroScriptsPageSSRPlugin from '../vite-plugin-scripts/page-ssr.js'; -import { resolveDependency } from './util.js'; interface CreateViteOptions { settings: AstroSettings; @@ -33,7 +32,7 @@ interface CreateViteOptions { fs?: typeof nodeFs; } -const ALWAYS_NOEXTERNAL = new Set([ +const ALWAYS_NOEXTERNAL = [ // This is only because Vite's native ESM doesn't resolve "exports" correctly. 'astro', // Vite fails on nested `.astro` imports without bundling @@ -43,21 +42,7 @@ const ALWAYS_NOEXTERNAL = new Set([ '@nanostores/preact', // fontsource packages are CSS that need to be processed '@fontsource/*', -]); - -function getSsrNoExternalDeps(projectRoot: URL): string[] { - let noExternalDeps = []; - for (const dep of ALWAYS_NOEXTERNAL) { - try { - resolveDependency(dep, projectRoot); - noExternalDeps.push(dep); - } catch { - // ignore dependency if *not* installed / present in your project - // prevents hard error from Vite! - } - } - return noExternalDeps; -} +]; /** Return a common starting point for all Vite actions */ export async function createVite( @@ -166,10 +151,7 @@ export async function createVite( dedupe: ['astro'], }, ssr: { - noExternal: [ - ...getSsrNoExternalDeps(settings.config.root), - ...astroPkgsConfig.ssr.noExternal, - ], + noExternal: [...ALWAYS_NOEXTERNAL, ...astroPkgsConfig.ssr.noExternal], // shiki is imported by Code.astro, which is no-externalized (processed by Vite). // However, shiki's deps are in CJS and trips up Vite's dev SSR transform, externalize // shiki to load it with node instead. diff --git a/packages/astro/src/core/render/ssr-element.ts b/packages/astro/src/core/render/ssr-element.ts index 63c02ff3ebc7..36fbca9e8572 100644 --- a/packages/astro/src/core/render/ssr-element.ts +++ b/packages/astro/src/core/render/ssr-element.ts @@ -1,14 +1,15 @@ +import slashify from 'slash'; import type { SSRElement } from '../../@types/astro'; - -import npath from 'path-browserify'; -import { appendForwardSlash } from '../../core/path.js'; +import { appendForwardSlash, removeLeadingForwardSlash } from '../../core/path.js'; function getRootPath(base?: string): string { return appendForwardSlash(new URL(base || '/', 'http://localhost/').pathname); } function joinToRoot(href: string, base?: string): string { - return npath.posix.join(getRootPath(base), href); + const rootPath = getRootPath(base); + const normalizedHref = slashify(href); + return appendForwardSlash(rootPath) + removeLeadingForwardSlash(normalizedHref); } export function createLinkStylesheetElement(href: string, base?: string): SSRElement { diff --git a/packages/astro/src/core/util.ts b/packages/astro/src/core/util.ts index 79148797b5ae..f80eb5fe731d 100644 --- a/packages/astro/src/core/util.ts +++ b/packages/astro/src/core/util.ts @@ -1,8 +1,7 @@ import fs from 'fs'; import path from 'path'; -import resolve from 'resolve'; import slash from 'slash'; -import { fileURLToPath, pathToFileURL } from 'url'; +import { fileURLToPath } from 'url'; import { normalizePath } from 'vite'; import type { AstroConfig, AstroSettings, RouteType } from '../@types/astro'; import { SUPPORTED_MARKDOWN_FILE_EXTENSIONS } from './constants.js'; @@ -83,14 +82,6 @@ export function parseNpmName( }; } -export function resolveDependency(dep: string, projectRoot: URL) { - const resolved = resolve.sync(dep, { - basedir: fileURLToPath(projectRoot), - }); - // For Windows compat, we need a fully resolved `file://` URL string - return pathToFileURL(resolved).toString(); -} - /** * Convert file URL to ID for viteServer.moduleGraph.idToModuleMap.get(:viteID) * Format: diff --git a/packages/astro/src/template/4xx.ts b/packages/astro/src/template/4xx.ts index e498d7e304c0..7c57c058f91e 100644 --- a/packages/astro/src/template/4xx.ts +++ b/packages/astro/src/template/4xx.ts @@ -1,4 +1,4 @@ -import { encode } from 'html-entities'; +import { escape } from 'html-escaper'; import { baseCSS } from './css.js'; interface ErrorTemplateOptions { @@ -58,7 +58,7 @@ export default function template({ ${ body || ` -
Path: ${encode(pathname)}
+
Path: ${escape(pathname)}
` } diff --git a/packages/astro/src/vite-plugin-jsx/tag.ts b/packages/astro/src/vite-plugin-jsx/tag.ts index adf7334190b7..eab920f63720 100644 --- a/packages/astro/src/vite-plugin-jsx/tag.ts +++ b/packages/astro/src/vite-plugin-jsx/tag.ts @@ -1,7 +1,5 @@ import type { PluginObj } from '@babel/core'; import * as t from '@babel/types'; -import { resolve as importMetaResolve } from 'import-meta-resolve'; -import { fileURLToPath } from 'url'; /** * This plugin handles every file that runs through our JSX plugin. @@ -18,9 +16,6 @@ export default async function tagExportsWithRenderer({ rendererName: string; root: URL; }): Promise { - const astroServerPath = fileURLToPath( - await importMetaResolve('astro/server/index.js', root.toString()) - ); return { visitor: { Program: { @@ -36,7 +31,7 @@ export default async function tagExportsWithRenderer({ t.identifier('__astro_tag_component__') ), ], - t.stringLiteral(astroServerPath) + t.stringLiteral('astro/server/index.js') ) ); }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6de33571e8a3..339f46529747 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -411,8 +411,6 @@ importers: '@types/html-escaper': ^3.0.0 '@types/mime': ^2.0.3 '@types/mocha': ^9.1.1 - '@types/parse5': ^6.0.3 - '@types/path-browserify': ^1.0.0 '@types/prettier': ^2.6.3 '@types/prompts': ^2.0.14 '@types/resolve': ^1.20.2 @@ -440,9 +438,7 @@ importers: fast-glob: ^3.2.11 github-slugger: ^2.0.0 gray-matter: ^4.0.3 - html-entities: ^2.3.3 html-escaper: ^3.0.3 - import-meta-resolve: ^2.1.0 kleur: ^4.1.4 magic-string: ^0.27.0 memfs: ^3.4.7 @@ -450,19 +446,14 @@ importers: mocha: ^9.2.2 node-mocks-http: ^1.11.0 ora: ^6.1.0 - path-browserify: ^1.0.1 path-to-regexp: ^6.2.1 - postcss: ^8.4.14 - postcss-load-config: ^3.1.4 preferred-pm: ^3.0.3 prompts: ^2.4.2 - recast: ^0.20.5 rehype: ^12.0.1 rehype-autolink-headings: ^6.1.1 rehype-slug: ^5.0.1 rehype-toc: ^3.0.2 remark-code-titles: ^0.1.2 - resolve: ^1.22.0 rollup: ^3.9.0 sass: ^1.52.2 semver: ^7.3.7 @@ -496,7 +487,6 @@ importers: '@babel/traverse': 7.20.12 '@babel/types': 7.20.7 '@types/babel__core': 7.1.20 - '@types/html-escaper': 3.0.0 '@types/yargs-parser': 21.0.0 acorn: 8.8.1 boxen: 6.2.1 @@ -513,22 +503,15 @@ importers: fast-glob: 3.2.12 github-slugger: 2.0.0 gray-matter: 4.0.3 - html-entities: 2.3.3 html-escaper: 3.0.3 - import-meta-resolve: 2.2.1 kleur: 4.1.5 magic-string: 0.27.0 mime: 3.0.0 ora: 6.1.2 - path-browserify: 1.0.1 path-to-regexp: 6.2.1 - postcss: 8.4.21 - postcss-load-config: 3.1.4_postcss@8.4.21 preferred-pm: 3.0.3 prompts: 2.4.2 - recast: 0.20.5 rehype: 12.0.1 - resolve: 1.22.1 semver: 7.3.8 server-destroy: 1.0.1 shiki: 0.11.1 @@ -556,10 +539,9 @@ importers: '@types/diff': 5.0.2 '@types/estree': 0.0.51 '@types/hast': 2.3.4 + '@types/html-escaper': 3.0.0 '@types/mime': 2.0.3 '@types/mocha': 9.1.1 - '@types/parse5': 6.0.3 - '@types/path-browserify': 1.0.0 '@types/prettier': 2.7.2 '@types/prompts': 2.4.2 '@types/resolve': 1.20.2 @@ -6985,7 +6967,6 @@ packages: /@types/html-escaper/3.0.0: resolution: {integrity: sha512-OcJcvP3Yk8mjYwf/IdXZtTE1tb/u0WF0qa29ER07ZHCYUBZXSN29Z1mBS+/96+kNMGTFUAbSz9X+pHmHpZrTCw==} - dev: false /@types/http-cache-semantics/4.0.1: resolution: {integrity: sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==} @@ -7067,10 +7048,7 @@ packages: /@types/parse5/6.0.3: resolution: {integrity: sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g==} - - /@types/path-browserify/1.0.0: - resolution: {integrity: sha512-XMCcyhSvxcch8b7rZAtFAaierBYdeHXVvg2iYnxOV0MCQHmPuRRmGZPFDRzPayxcGiiSL1Te9UIO+f3cuj0tfw==} - dev: true + dev: false /@types/prettier/2.7.2: resolution: {integrity: sha512-KufADq8uQqo1pYKVIYzfKbJfBAc0sOeXqGbFaSpv8MRmC/zXgowNZmFcbngndGk922QDmOASEXUZCaY48gs4cg==}