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

Use Vite to load non JS astro configs #5377

Merged
merged 14 commits into from
Nov 15, 2022
5 changes: 5 additions & 0 deletions .changeset/swift-bees-hope.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'astro': patch
---

Uses vite to load astro.config.ts files
4 changes: 2 additions & 2 deletions packages/astro/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -111,11 +111,11 @@
"@babel/plugin-transform-react-jsx": "^7.17.12",
"@babel/traverse": "^7.18.2",
"@babel/types": "^7.18.4",
"@proload/core": "^0.3.3",
"@proload/plugin-tsm": "^0.2.1",
"@types/babel__core": "^7.1.19",
"@types/html-escaper": "^3.0.0",
"@types/yargs-parser": "^21.0.0",
"@proload/core": "^0.3.3",
"@proload/plugin-tsm": "^0.2.1",
"boxen": "^6.2.1",
"ci-info": "^3.3.1",
"common-ancestor-path": "^1.0.1",
Expand Down
5 changes: 3 additions & 2 deletions packages/astro/src/cli/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import * as colors from 'kleur/colors';
import type { Arguments as Flags } from 'yargs-parser';
import yargs from 'yargs-parser';
import { z } from 'zod';
import fs from 'fs';
import {
createSettings,
openConfig,
Expand Down Expand Up @@ -88,7 +89,7 @@ async function handleConfigError(
e: any,
{ cwd, flags, logging }: { cwd?: string; flags?: Flags; logging: LogOptions }
) {
const path = await resolveConfigPath({ cwd, flags });
const path = await resolveConfigPath({ cwd, flags, fs });
if (e instanceof Error) {
if (path) {
error(logging, 'astro', `Unable to load ${colors.bold(path)}\n`);
Expand Down Expand Up @@ -173,7 +174,7 @@ async function runCommand(cmd: string, flags: yargs.Arguments) {
const { default: devServer } = await import('../core/dev/index.js');

const configFlag = resolveFlags(flags).config;
const configFlagPath = configFlag ? await resolveConfigPath({ cwd: root, flags }) : undefined;
const configFlagPath = configFlag ? await resolveConfigPath({ cwd: root, flags, fs }) : undefined;

await devServer(settings, {
configFlag,
Expand Down
4 changes: 2 additions & 2 deletions packages/astro/src/core/add/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { AstroTelemetry } from '@astrojs/telemetry';
import boxen from 'boxen';
import { diffWords } from 'diff';
import { execa } from 'execa';
import { existsSync, promises as fs } from 'fs';
import fsMod, { existsSync, promises as fs } from 'fs';
import { bold, cyan, dim, green, magenta, red, yellow } from 'kleur/colors';
import ora from 'ora';
import path from 'path';
Expand Down Expand Up @@ -164,7 +164,7 @@ export default async function add(names: string[], { cwd, flags, logging, teleme
}
}

const rawConfigPath = await resolveConfigPath({ cwd, flags });
const rawConfigPath = await resolveConfigPath({ cwd, flags, fs: fsMod });
let configURL = rawConfigPath ? pathToFileURL(rawConfigPath) : undefined;

if (configURL) {
Expand Down
76 changes: 17 additions & 59 deletions packages/astro/src/core/config/config.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,16 @@
import type { Arguments as Flags } from 'yargs-parser';
import type { AstroConfig, AstroUserConfig, CLIFlags } from '../../@types/astro';

import load, { ProloadError, resolve } from '@proload/core';
import loadTypeScript from '@proload/plugin-tsm';
import fs from 'fs';
import * as colors from 'kleur/colors';
import path from 'path';
import { fileURLToPath, pathToFileURL } from 'url';
import * as vite from 'vite';
import { mergeConfig as mergeViteConfig } from 'vite';
import { AstroError, AstroErrorData } from '../errors/index.js';
import { LogOptions } from '../logger/core.js';
import { arraify, isObject, isURL } from '../util.js';
import { createRelativeSchema } from './schema.js';

load.use([loadTypeScript]);
import { loadConfigWithVite } from './vite-load.js';

export const LEGACY_ASTRO_CONFIG_KEYS = new Set([
'projectRoot',
Expand Down Expand Up @@ -151,7 +147,7 @@ interface LoadConfigOptions {
* instead of the resolved config
*/
export async function resolveConfigPath(
configOptions: Pick<LoadConfigOptions, 'cwd' | 'flags'>
configOptions: Pick<LoadConfigOptions, 'cwd' | 'flags'> & { fs: typeof fs }
): Promise<string | undefined> {
const root = resolveRoot(configOptions.cwd);
const flags = resolveFlags(configOptions.flags || {});
Expand All @@ -165,14 +161,14 @@ export async function resolveConfigPath(
// Resolve config file path using Proload
// If `userConfigPath` is `undefined`, Proload will search for `astro.config.[cm]?[jt]s`
try {
const configPath = await resolve('astro', {
mustExist: !!userConfigPath,
cwd: root,
filePath: userConfigPath,
const config = await loadConfigWithVite({
configPath: userConfigPath,
root,
fs: configOptions.fs
});
return configPath;
return config.filePath;
} catch (e) {
if (e instanceof ProloadError && flags.config) {
if (flags.config) {
throw new AstroError({
...AstroErrorData.ConfigNotFound,
message: AstroErrorData.ConfigNotFound.message(flags.config),
Expand All @@ -195,7 +191,7 @@ export async function openConfig(configOptions: LoadConfigOptions): Promise<Open
const flags = resolveFlags(configOptions.flags || {});
let userConfig: AstroUserConfig = {};

const config = await tryLoadConfig(configOptions, flags, root);
const config = await tryLoadConfig(configOptions, root);
if (config) {
userConfig = config.value;
}
Expand All @@ -216,7 +212,6 @@ interface TryLoadConfigResult {

async function tryLoadConfig(
configOptions: LoadConfigOptions,
flags: CLIFlags,
root: string
): Promise<TryLoadConfigResult | undefined> {
const fsMod = configOptions.fsMod ?? fs;
Expand All @@ -225,6 +220,7 @@ async function tryLoadConfig(
let configPath = await resolveConfigPath({
cwd: configOptions.cwd,
flags: configOptions.flags,
fs: fsMod
});
if (!configPath) return undefined;
if (configOptions.isRestart) {
Expand All @@ -246,52 +242,14 @@ async function tryLoadConfig(
};
configPath = tempConfigPath;
}

const config = await load('astro', {
mustExist: !!configPath,
cwd: root,
filePath: configPath,

// Create a vite server to load the config
const config = await loadConfigWithVite({
configPath,
fs: fsMod,
root
});

return config as TryLoadConfigResult;
} catch (e) {
if (e instanceof ProloadError && flags.config) {
throw new AstroError({
...AstroErrorData.ConfigNotFound,
message: AstroErrorData.ConfigNotFound.message(flags.config),
});
}

const configPath = await resolveConfigPath(configOptions);
if (!configPath) {
throw e;
}

// Fallback to use Vite DevServer
const viteServer = await vite.createServer({
server: { middlewareMode: true, hmr: false },
optimizeDeps: { entries: [] },
clearScreen: false,
appType: 'custom',
// NOTE: Vite doesn't externalize linked packages by default. During testing locally,
// these dependencies trip up Vite's dev SSR transform. In the future, we should
// avoid `vite.createServer` and use `loadConfigFromFile` instead.
ssr: {
external: ['@astrojs/mdx', '@astrojs/react'],
},
});
try {
const mod = await viteServer.ssrLoadModule(configPath);

if (mod?.default) {
return {
value: mod.default,
filePath: configPath,
};
}
} finally {
await viteServer.close();
}
} finally {
await finallyCleanup();
}
Expand All @@ -306,7 +264,7 @@ export async function loadConfig(configOptions: LoadConfigOptions): Promise<Astr
const flags = resolveFlags(configOptions.flags || {});
let userConfig: AstroUserConfig = {};

const config = await tryLoadConfig(configOptions, flags, root);
const config = await tryLoadConfig(configOptions, root);
if (config) {
userConfig = config.value;
}
Expand Down
141 changes: 141 additions & 0 deletions packages/astro/src/core/config/vite-load.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import * as vite from 'vite';
import npath from 'path';
import { pathToFileURL } from 'url';
import type fsType from 'fs';
import { AstroError, AstroErrorData } from '../errors/index.js';

// Fallback for legacy
import load from '@proload/core';
import loadTypeScript from '@proload/plugin-tsm';

load.use([loadTypeScript]);

export interface ViteLoader {
root: string;
viteServer: vite.ViteDevServer;
}

async function createViteLoader(root: string): Promise<ViteLoader> {
const viteServer = await vite.createServer({
server: { middlewareMode: true, hmr: false },
optimizeDeps: { entries: [] },
clearScreen: false,
appType: 'custom',
ssr: {
// NOTE: Vite doesn't externalize linked packages by default. During testing locally,
// these dependencies trip up Vite's dev SSR transform. In the future, we should
// avoid `vite.createServer` and use `loadConfigFromFile` instead.
external: ['@astrojs/tailwind', '@astrojs/mdx', '@astrojs/react']
}
});

return {
root,
viteServer,
};
}

interface LoadConfigWithViteOptions {
root: string;
configPath: string | undefined;
fs: typeof fsType;
}

export async function loadConfigWithVite({ configPath, fs, root }: LoadConfigWithViteOptions): Promise<{
value: Record<string, any>;
filePath?: string;
}> {
let paths: string [];
if(configPath) {
// Go ahead and check if the file exists and throw if not.
try {
await fs.promises.stat(configPath);
} catch {
throw new AstroError({
...AstroErrorData.ConfigNotFound,
message: AstroErrorData.ConfigNotFound.message(configPath),
});
}

paths = [configPath];
} else {
paths = [
'astro.config.mjs',
'astro.config.js',
'astro.config.ts',
'astro.config.mts',
'astro.config.cjs',
'astro.config.cjs'
].map(path => npath.join(root, path));
}

// Initialize a ViteLoader variable. We may never need to create one
// but if we do, we only want to create it once.
let loader: ViteLoader | null = null;

try {
for(const file of paths) {
// First verify the file event exists
try {
await fs.promises.stat(file);
} catch {
continue;
}
bluwy marked this conversation as resolved.
Show resolved Hide resolved

// Try loading with Node import()
if(/\.[cm]?js$/.test(file)) {
try {
const config = await import(pathToFileURL(file).toString());
return {
value: config.default ?? {},
filePath: file
};
} catch {}
}

// Try Loading with Vite
if(!loader) {
loader = await createViteLoader(root);
}

try {
const mod = await loader.viteServer.ssrLoadModule(file);
return {
value: mod.default ?? {},
filePath: file
}
} catch {}

// Try loading with Proload
// TODO deprecate - this is only for legacy compatibility
try {
const res = await load('astro', {
mustExist: true,
cwd: root,
filePath: file,
});
return {
value: res?.value ?? {},
filePath: file
};
} catch {}
bluwy marked this conversation as resolved.
Show resolved Hide resolved
}
} finally {
// Tear-down the ViteLoader, if one was created.
if(loader) {
await loader.viteServer.close();
}
}

if(configPath) {
throw new AstroError({
...AstroErrorData.ConfigNotFound,
message: `Unable to find an Astro config file.`,
});
}

return {
value: {},
filePath: undefined
};
}
7 changes: 7 additions & 0 deletions packages/astro/test/fixtures/tailwindcss-ts/astro.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { defineConfig } from 'astro/config';
import tailwind from "@astrojs/tailwind";

// https://astro.build/config
export default defineConfig({
integrations: [tailwind()]
});
11 changes: 11 additions & 0 deletions packages/astro/test/fixtures/tailwindcss-ts/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"name": "@test/tailwindcss-ts",
"version": "0.0.0",
"private": true,
"dependencies": {
"@astrojs/tailwind": "workspace:*",
"astro": "workspace:*",
"tailwindcss": "^3.2.4",
"postcss": ">=8.3.3 <9.0.0"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ['src/**/*.{astro,tsx}']
};
Loading