Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Exit gracefully when encountering a Node.js bug #8412

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/loud-gifts-obey.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'astro': patch
---

Provide a helpful error message when build fails because of Node's CJS loading bug.
13 changes: 11 additions & 2 deletions packages/astro/src/cli/add/babel.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
import generator from '@babel/generator';
import parser from '@babel/parser';
import traverse from '@babel/traverse';
import * as t from '@babel/types';
import type * as Babel from '@babel/types';

const t = await import('@babel/types')
.catch(error => new Proxy({}, { get: () => {
if (process.version.startsWith('v20.6')) {
console.error("The build could not complete because of a bug in Node.js v20.6.0.\nSee https://github.com/nodejs/node/issues/49497\n\nConsider using Node.js v20.5.1, or update if the issue has been fixed.")
process.exit(1)
}
else { throw error }
} }) as never);

// @ts-expect-error @babel/traverse isn't ESM and needs this trick
export const visit = traverse.default as typeof traverse;
export { t };

export async function generate(ast: t.File) {
export async function generate(ast: Babel.File) {
// @ts-expect-error @babel/generator isn't ESM and needs this trick
const astToText = generator.default as typeof generator;
const { code } = astToText(ast);
Expand Down
3 changes: 2 additions & 1 deletion packages/astro/src/cli/add/imports.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type * as Babel from '@babel/types';
import { t, visit } from './babel.js';

export function ensureImport(root: t.File, importDeclaration: t.ImportDeclaration) {
export function ensureImport(root: Babel.File, importDeclaration: Babel.ImportDeclaration) {
let specifiersToFind = [...importDeclaration.specifiers];

visit(root, {
Expand Down
17 changes: 9 additions & 8 deletions packages/astro/src/cli/add/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import ora from 'ora';
import preferredPM from 'preferred-pm';
import prompts from 'prompts';
import type yargs from 'yargs-parser';
import type * as Babel from "@babel/types"
import { loadTSConfig, resolveConfigPath, resolveRoot } from '../../core/config/index.js';
import {
defaultTSConfig,
Expand Down Expand Up @@ -216,7 +217,7 @@ export async function add(names: string[], { flags }: AddOptions) {
await fs.writeFile(fileURLToPath(configURL), ASTRO_CONFIG_STUB, { encoding: 'utf-8' });
}

let ast: t.File | null = null;
let ast: Babel.File | null = null;
try {
ast = await parseAstroConfig(configURL);

Expand Down Expand Up @@ -342,7 +343,7 @@ function isAdapter(
return integration.type === 'adapter';
}

async function parseAstroConfig(configURL: URL): Promise<t.File> {
async function parseAstroConfig(configURL: URL): Promise<Babel.File> {
const source = await fs.readFile(fileURLToPath(configURL), { encoding: 'utf-8' });
const result = parse(source);

Expand Down Expand Up @@ -389,7 +390,7 @@ Documentation: https://docs.astro.build/en/guides/integrations-guide/`;
return err;
}

async function addIntegration(ast: t.File, integration: IntegrationInfo) {
async function addIntegration(ast: Babel.File, integration: IntegrationInfo) {
const integrationId = t.identifier(toIdent(integration.id));

ensureImport(
Expand Down Expand Up @@ -417,7 +418,7 @@ async function addIntegration(ast: t.File, integration: IntegrationInfo) {
if (prop.key.value === 'integrations') return true;
}
return false;
}) as t.ObjectProperty | undefined;
}) as Babel.ObjectProperty | undefined;

const integrationCall = t.callExpression(integrationId, []);

Expand Down Expand Up @@ -445,7 +446,7 @@ async function addIntegration(ast: t.File, integration: IntegrationInfo) {
});
}

async function setAdapter(ast: t.File, adapter: IntegrationInfo, exportName: string) {
async function setAdapter(ast: Babel.File, adapter: IntegrationInfo, exportName: string) {
const adapterId = t.identifier(toIdent(adapter.id));

ensureImport(
Expand All @@ -470,7 +471,7 @@ async function setAdapter(ast: t.File, adapter: IntegrationInfo, exportName: str
if (prop.key.value === 'output') return true;
}
return false;
}) as t.ObjectProperty | undefined;
}) as Babel.ObjectProperty | undefined;

if (!outputProp) {
configObject.properties.push(
Expand All @@ -487,7 +488,7 @@ async function setAdapter(ast: t.File, adapter: IntegrationInfo, exportName: str
if (prop.key.value === 'adapter') return true;
}
return false;
}) as t.ObjectProperty | undefined;
}) as Babel.ObjectProperty | undefined;

let adapterCall;
switch (adapter.id) {
Expand Down Expand Up @@ -530,7 +531,7 @@ async function updateAstroConfig({
logAdapterInstructions,
}: {
configURL: URL;
ast: t.File;
ast: Babel.File;
flags: yargs.Arguments;
logger: Logger;
logAdapterInstructions: boolean;
Expand Down
3 changes: 2 additions & 1 deletion packages/astro/src/cli/add/wrapper.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { t, visit } from './babel.js';
import type * as Babel from "@babel/types"

export function wrapDefaultExport(ast: t.File, functionIdentifier: t.Identifier) {
export function wrapDefaultExport(ast: Babel.File, functionIdentifier: Babel.Identifier) {
visit(ast, {
ExportDefaultDeclaration(path) {
if (!t.isExpression(path.node.declaration)) return;
Expand Down
29 changes: 19 additions & 10 deletions packages/astro/src/jsx/babel.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
import type { PluginObj } from '@babel/core';
import * as t from '@babel/types';
import * as Babel from '@babel/types';
import { AstroError } from '../core/errors/errors.js';
import { AstroErrorData } from '../core/errors/index.js';
import { resolvePath } from '../core/util.js';
import type { PluginMetadata } from '../vite-plugin-astro/types';

const t = await import('@babel/types')
.catch(error => new Proxy({}, { get: () => {
if (process.version.startsWith('v20.6')) {
console.error("The build could not complete because of a bug in Node.js v20.6.0.\nSee https://github.com/nodejs/node/issues/49497\n\nConsider using Node.js v20.5.1, or update if the issue has been fixed.")
process.exit(1)
}
else { throw error }
} }) as never);

const ClientOnlyPlaceholder = 'astro-client-only';

function isComponent(tagName: string) {
Expand All @@ -15,7 +24,7 @@ function isComponent(tagName: string) {
);
}

function hasClientDirective(node: t.JSXElement) {
function hasClientDirective(node: Babel.JSXElement) {
for (const attr of node.openingElement.attributes) {
if (attr.type === 'JSXAttribute' && attr.name.type === 'JSXNamespacedName') {
return attr.name.namespace.name === 'client';
Expand All @@ -24,7 +33,7 @@ function hasClientDirective(node: t.JSXElement) {
return false;
}

function isClientOnlyComponent(node: t.JSXElement) {
function isClientOnlyComponent(node: Babel.JSXElement) {
for (const attr of node.openingElement.attributes) {
if (attr.type === 'JSXAttribute' && attr.name.type === 'JSXNamespacedName') {
return jsxAttributeToString(attr) === 'client:only';
Expand All @@ -33,12 +42,12 @@ function isClientOnlyComponent(node: t.JSXElement) {
return false;
}

function getTagName(tag: t.JSXElement) {
function getTagName(tag: Babel.JSXElement) {
const jsxName = tag.openingElement.name;
return jsxElementNameToString(jsxName);
}

function jsxElementNameToString(node: t.JSXOpeningElement['name']): string {
function jsxElementNameToString(node: Babel.JSXOpeningElement['name']): string {
if (t.isJSXMemberExpression(node)) {
return `${jsxElementNameToString(node.object)}.${node.property.name}`;
}
Expand All @@ -48,15 +57,15 @@ function jsxElementNameToString(node: t.JSXOpeningElement['name']): string {
return `${node.namespace.name}:${node.name.name}`;
}

function jsxAttributeToString(attr: t.JSXAttribute): string {
function jsxAttributeToString(attr: Babel.JSXAttribute): string {
if (t.isJSXNamespacedName(attr.name)) {
return `${attr.name.namespace.name}:${attr.name.name.name}`;
}
return `${attr.name.name}`;
}

function addClientMetadata(
node: t.JSXElement,
node: Babel.JSXElement,
meta: { resolvedPath: string; path: string; name: string }
) {
const existingAttributes = node.openingElement.attributes.map((attr) =>
Expand Down Expand Up @@ -88,7 +97,7 @@ function addClientMetadata(
}

function addClientOnlyMetadata(
node: t.JSXElement,
node: Babel.JSXElement,
meta: { resolvedPath: string; path: string; name: string }
) {
const tagName = getTagName(node);
Expand Down Expand Up @@ -193,7 +202,7 @@ export default function astroJSX(): PluginObj {
return;
}
const parent = path.findParent((n) => t.isJSXElement(n.node))!;
const parentNode = parent.node as t.JSXElement;
const parentNode = parent.node as Babel.JSXElement;
const tagName = getTagName(parentNode);
if (!isComponent(tagName)) return;
if (!hasClientDirective(parentNode)) return;
Expand Down Expand Up @@ -252,7 +261,7 @@ export default function astroJSX(): PluginObj {
const isAttr = path.findParent((n) => t.isJSXAttribute(n.node));
if (isAttr) return;
const parent = path.findParent((n) => t.isJSXElement(n.node))!;
const parentNode = parent.node as t.JSXElement;
const parentNode = parent.node as Babel.JSXElement;
const tagName = getTagName(parentNode);
if (!isComponent(tagName)) return;
if (!hasClientDirective(parentNode)) return;
Expand Down
10 changes: 9 additions & 1 deletion packages/astro/src/vite-plugin-mdx/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,20 @@ import type { AstroRenderer, AstroSettings } from '../@types/astro';
import type { Logger } from '../core/logger/core.js';
import type { PluginMetadata } from '../vite-plugin-astro/types';

import babel from '@babel/core';
import { CONTENT_FLAG, PROPAGATED_ASSET_FLAG } from '../content/index.js';
import { astroEntryPrefix } from '../core/build/plugins/plugin-component-entry.js';
import { removeQueryString } from '../core/path.js';
import tagExportsPlugin from './tag.js';

const babel = await import('@babel/core')
.catch(error => new Proxy({}, { get: () => {
if (process.version.startsWith('v20.6')) {
console.error("The build could not complete because of a bug in Node.js v20.6.0.\nSee https://github.com/nodejs/node/issues/49497\n\nConsider using Node.js v20.5.1, or update if the issue has been fixed.")
process.exit(1)
}
else { throw error }
} }) as never);

interface TransformJSXOptions {
code: string;
id: string;
Expand Down
10 changes: 9 additions & 1 deletion packages/astro/src/vite-plugin-mdx/tag.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
import type { PluginObj } from '@babel/core';
import * as t from '@babel/types';

const t = await import('@babel/types')
.catch(error => new Proxy({}, { get: () => {
if (process.version.startsWith('v20.6')) {
console.error("The build could not complete because of a bug in Node.js v20.6.0.\nSee https://github.com/nodejs/node/issues/49497\n\nConsider using Node.js v20.5.1, or update if the issue has been fixed.")
process.exit(1)
}
else { throw error }
} }) as never);

/**
* This plugin handles every file that runs through our JSX plugin.
Expand Down
Loading