summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--.changeset/new-hotels-unite.md6
-rw-r--r--.changeset/ten-candles-relate.md7
-rw-r--r--packages/astro/src/@types/astro.ts3
-rw-r--r--packages/astro/src/cli/index.ts86
-rw-r--r--packages/astro/src/config/index.ts9
-rw-r--r--packages/astro/src/core/config/config.ts4
-rw-r--r--packages/astro/src/core/config/settings.ts15
-rw-r--r--packages/astro/src/core/dev/index.ts7
-rw-r--r--packages/astro/src/integrations/index.ts7
-rw-r--r--packages/astro/test/test-utils.js9
-rw-r--r--packages/integrations/tailwind/src/index.ts50
11 files changed, 129 insertions, 74 deletions
diff --git a/.changeset/new-hotels-unite.md b/.changeset/new-hotels-unite.md
new file mode 100644
index 000000000..8febc2d47
--- /dev/null
+++ b/.changeset/new-hotels-unite.md
@@ -0,0 +1,6 @@
+---
+'astro': minor
+---
+
+- Added `isRestart` and `addWatchFile` to integration step `isRestart`.
+- Restart dev server automatically when tsconfig changes.
diff --git a/.changeset/ten-candles-relate.md b/.changeset/ten-candles-relate.md
new file mode 100644
index 000000000..402e46a1c
--- /dev/null
+++ b/.changeset/ten-candles-relate.md
@@ -0,0 +1,7 @@
+---
+'@astrojs/tailwind': minor
+---
+
+## HMR on config file changes
+
+New in this release is the ability for config changes to automatically reflect via HMR. Now when you edit your `tsconfig.json` or `tailwind.config.js` configs, the changes will reload automatically without the need to restart your dev server.
diff --git a/packages/astro/src/@types/astro.ts b/packages/astro/src/@types/astro.ts
index 0d6e4d5a5..b95aae047 100644
--- a/packages/astro/src/@types/astro.ts
+++ b/packages/astro/src/@types/astro.ts
@@ -891,6 +891,7 @@ export interface AstroSettings {
}[];
tsConfig: TsConfigJson | undefined;
tsConfigPath: string | undefined;
+ watchFiles: string[];
}
export type AsyncRendererComponentFn<U> = (
@@ -1142,8 +1143,10 @@ export interface AstroIntegration {
'astro:config:setup'?: (options: {
config: AstroConfig;
command: 'dev' | 'build';
+ isRestart: boolean;
updateConfig: (newConfig: Record<string, any>) => void;
addRenderer: (renderer: AstroRenderer) => void;
+ addWatchFile: (path: URL | string) => void;
injectScript: (stage: InjectedScriptStage, content: string) => void;
injectRoute: (injectRoute: InjectedRoute) => void;
// TODO: Add support for `injectElement()` for full HTML element injection, not just scripts.
diff --git a/packages/astro/src/cli/index.ts b/packages/astro/src/cli/index.ts
index 93e589396..ab52f6415 100644
--- a/packages/astro/src/cli/index.ts
+++ b/packages/astro/src/cli/index.ts
@@ -9,7 +9,6 @@ import add from '../core/add/index.js';
import build from '../core/build/index.js';
import {
createSettings,
- loadTSConfig,
openConfig,
resolveConfigPath,
resolveFlags,
@@ -168,12 +167,7 @@ async function runCommand(cmd: string, flags: yargs.Arguments) {
});
if (!initialAstroConfig) return;
telemetry.record(event.eventCliSession(cmd, initialUserConfig, flags));
- let initialTsConfig = loadTSConfig(root);
- let settings = createSettings({
- config: initialAstroConfig,
- tsConfig: initialTsConfig?.config,
- tsConfigPath: initialTsConfig?.path,
- });
+ let settings = createSettings(initialAstroConfig, root);
// Common CLI Commands:
// These commands run normally. All commands are assumed to have been handled
@@ -191,42 +185,48 @@ async function runCommand(cmd: string, flags: yargs.Arguments) {
const handleServerRestart = (logMsg: string) =>
async function (changedFile: string) {
- if (
- !restartInFlight &&
- (configFlag
- ? // If --config is specified, only watch changes for this file
- configFlagPath && normalizePath(configFlagPath) === normalizePath(changedFile)
- : // Otherwise, watch for any astro.config.* file changes in project root
- new RegExp(
- `${normalizePath(resolvedRoot)}.*astro\.config\.((mjs)|(cjs)|(js)|(ts))$`
- ).test(normalizePath(changedFile)))
- ) {
- restartInFlight = true;
- console.clear();
- try {
- const newConfig = await openConfig({
- cwd: root,
- flags,
- cmd,
- logging,
- isConfigReload: true,
- });
- info(logging, 'astro', logMsg + '\n');
- let astroConfig = newConfig.astroConfig;
- let tsconfig = loadTSConfig(root);
- settings = createSettings({
- config: astroConfig,
- tsConfig: tsconfig?.config,
- tsConfigPath: tsconfig?.path,
- });
- await stop();
- await startDevServer({ isRestart: true });
- } catch (e) {
- await handleConfigError(e, { cwd: root, flags, logging });
- await stop();
- info(logging, 'astro', 'Continuing with previous valid configuration\n');
- await startDevServer({ isRestart: true });
- }
+ if (restartInFlight) return;
+
+ let shouldRestart = false;
+
+ // If the config file changed, reload the config and restart the server.
+ shouldRestart = configFlag
+ ? // If --config is specified, only watch changes for this file
+ !!configFlagPath && normalizePath(configFlagPath) === normalizePath(changedFile)
+ : // Otherwise, watch for any astro.config.* file changes in project root
+ new RegExp(
+ `${normalizePath(resolvedRoot)}.*astro\.config\.((mjs)|(cjs)|(js)|(ts))$`
+ ).test(normalizePath(changedFile));
+
+ if (!shouldRestart && settings.watchFiles.length > 0) {
+ // If the config file didn't change, check if any of the watched files changed.
+ shouldRestart = settings.watchFiles.some(
+ (path) => normalizePath(path) === normalizePath(changedFile)
+ );
+ }
+
+ if (!shouldRestart) return;
+
+ restartInFlight = true;
+ console.clear();
+ try {
+ const newConfig = await openConfig({
+ cwd: root,
+ flags,
+ cmd,
+ logging,
+ isRestart: true,
+ });
+ info(logging, 'astro', logMsg + '\n');
+ let astroConfig = newConfig.astroConfig;
+ settings = createSettings(astroConfig, root);
+ await stop();
+ await startDevServer({ isRestart: true });
+ } catch (e) {
+ await handleConfigError(e, { cwd: root, flags, logging });
+ await stop();
+ info(logging, 'astro', 'Continuing with previous valid configuration\n');
+ await startDevServer({ isRestart: true });
}
};
diff --git a/packages/astro/src/config/index.ts b/packages/astro/src/config/index.ts
index 8b38ed354..6601a7a5a 100644
--- a/packages/astro/src/config/index.ts
+++ b/packages/astro/src/config/index.ts
@@ -16,7 +16,7 @@ export function getViteConfig(inlineConfig: UserConfig) {
const [
{ mergeConfig },
{ nodeLogDestination },
- { openConfig, createSettings, loadTSConfig },
+ { openConfig, createSettings },
{ createVite },
{ runHookConfigSetup, runHookConfigDone },
] = await Promise.all([
@@ -34,12 +34,7 @@ export function getViteConfig(inlineConfig: UserConfig) {
cmd,
logging,
});
- const initialTsConfig = loadTSConfig(inlineConfig.root);
- const settings = createSettings({
- config,
- tsConfig: initialTsConfig?.config,
- tsConfigPath: initialTsConfig?.path,
- });
+ const settings = createSettings(config, inlineConfig.root);
await runHookConfigSetup({ settings, command: cmd, logging });
const viteConfig = await createVite(
{
diff --git a/packages/astro/src/core/config/config.ts b/packages/astro/src/core/config/config.ts
index bbaeee186..07f6b0320 100644
--- a/packages/astro/src/core/config/config.ts
+++ b/packages/astro/src/core/config/config.ts
@@ -135,7 +135,7 @@ interface LoadConfigOptions {
validate?: boolean;
logging: LogOptions;
/** Invalidate when reloading a previously loaded config */
- isConfigReload?: boolean;
+ isRestart?: boolean;
}
/**
@@ -222,7 +222,7 @@ async function tryLoadConfig(
flags: configOptions.flags,
});
if (!configPath) return undefined;
- if (configOptions.isConfigReload) {
+ if (configOptions.isRestart) {
// Hack: Write config to temporary file at project root
// This invalidates and reloads file contents when using ESM imports or "resolve"
const tempConfigPath = path.join(
diff --git a/packages/astro/src/core/config/settings.ts b/packages/astro/src/core/config/settings.ts
index a69697726..8b7bfbec8 100644
--- a/packages/astro/src/core/config/settings.ts
+++ b/packages/astro/src/core/config/settings.ts
@@ -1,24 +1,21 @@
-import type { TsConfigJson } from 'tsconfig-resolver';
import type { AstroConfig, AstroSettings } from '../../@types/astro';
import jsxRenderer from '../../jsx/renderer.js';
+import { loadTSConfig } from './tsconfig.js';
-export interface CreateSettings {
- config: AstroConfig;
- tsConfig?: TsConfigJson;
- tsConfigPath?: string;
-}
+export function createSettings(config: AstroConfig, cwd?: string): AstroSettings {
+ const tsconfig = loadTSConfig(cwd);
-export function createSettings({ config, tsConfig, tsConfigPath }: CreateSettings): AstroSettings {
return {
config,
- tsConfig,
- tsConfigPath,
+ tsConfig: tsconfig?.config,
+ tsConfigPath: tsconfig?.path,
adapter: undefined,
injectedRoutes: [],
pageExtensions: ['.astro', '.md', '.html'],
renderers: [jsxRenderer],
scripts: [],
+ watchFiles: tsconfig?.exists ? [tsconfig.path, ...tsconfig.extendedPaths] : [],
};
}
diff --git a/packages/astro/src/core/dev/index.ts b/packages/astro/src/core/dev/index.ts
index 2979deeff..bd3659671 100644
--- a/packages/astro/src/core/dev/index.ts
+++ b/packages/astro/src/core/dev/index.ts
@@ -35,7 +35,12 @@ export default async function dev(
const devStart = performance.now();
applyPolyfill();
await options.telemetry.record([]);
- settings = await runHookConfigSetup({ settings, command: 'dev', logging: options.logging });
+ settings = await runHookConfigSetup({
+ settings,
+ command: 'dev',
+ logging: options.logging,
+ isRestart: options.isRestart,
+ });
const { host, port } = settings.config.server;
const { isRestart = false } = options;
diff --git a/packages/astro/src/integrations/index.ts b/packages/astro/src/integrations/index.ts
index d533a1a8e..9a9acd2dd 100644
--- a/packages/astro/src/integrations/index.ts
+++ b/packages/astro/src/integrations/index.ts
@@ -1,5 +1,6 @@
import { bold } from 'kleur/colors';
import type { AddressInfo } from 'net';
+import { fileURLToPath } from 'node:url';
import type { InlineConfig, ViteDevServer } from 'vite';
import {
AstroConfig,
@@ -37,10 +38,12 @@ export async function runHookConfigSetup({
settings,
command,
logging,
+ isRestart = false,
}: {
settings: AstroSettings;
command: 'dev' | 'build';
logging: LogOptions;
+ isRestart?: boolean;
}): Promise<AstroSettings> {
// An adapter is an integration, so if one is provided push it.
if (settings.config.adapter) {
@@ -66,6 +69,7 @@ export async function runHookConfigSetup({
const hooks: HookParameters<'astro:config:setup'> = {
config: updatedConfig,
command,
+ isRestart,
addRenderer(renderer: AstroRenderer) {
if (!renderer.name) {
throw new Error(`Integration ${bold(integration.name)} has an unnamed renderer.`);
@@ -86,6 +90,9 @@ export async function runHookConfigSetup({
injectRoute: (injectRoute) => {
updatedSettings.injectedRoutes.push(injectRoute);
},
+ addWatchFile: (path) => {
+ updatedSettings.watchFiles.push(path instanceof URL ? fileURLToPath(path) : path);
+ },
};
// Semi-private `addPageExtension` hook
function addPageExtension(...input: (string | string[])[]) {
diff --git a/packages/astro/test/test-utils.js b/packages/astro/test/test-utils.js
index c1fe6958c..a4f9191f9 100644
--- a/packages/astro/test/test-utils.js
+++ b/packages/astro/test/test-utils.js
@@ -3,7 +3,7 @@ import { polyfill } from '@astrojs/webapi';
import fs from 'fs';
import { fileURLToPath } from 'url';
import { loadConfig } from '../dist/core/config/config.js';
-import { createSettings, loadTSConfig } from '../dist/core/config/index.js';
+import { createSettings } from '../dist/core/config/index.js';
import dev from '../dist/core/dev/index.js';
import build from '../dist/core/build/index.js';
import preview from '../dist/core/preview/index.js';
@@ -95,12 +95,7 @@ export async function loadFixture(inlineConfig) {
if (inlineConfig.base && !inlineConfig.base.endsWith('/')) {
config.base = inlineConfig.base + '/';
}
- let tsconfig = loadTSConfig(fileURLToPath(cwd));
- let settings = createSettings({
- config,
- tsConfig: tsconfig?.config,
- tsConfigPath: tsconfig?.path,
- });
+ let settings = createSettings(config, fileURLToPath(cwd));
if (config.integrations.find((integration) => integration.name === '@astrojs/mdx')) {
// Enable default JSX integration. It needs to come first, so unshift rather than push!
const { default: jsxRenderer } = await import('astro/jsx/renderer.js');
diff --git a/packages/integrations/tailwind/src/index.ts b/packages/integrations/tailwind/src/index.ts
index 1e5008f6c..2f1b68e28 100644
--- a/packages/integrations/tailwind/src/index.ts
+++ b/packages/integrations/tailwind/src/index.ts
@@ -1,6 +1,7 @@
-import load from '@proload/core';
+import load, { resolve } from '@proload/core';
import type { AstroIntegration } from 'astro';
import autoprefixerPlugin from 'autoprefixer';
+import fs from 'fs/promises';
import path from 'path';
import tailwindPlugin, { Config as TailwindConfig } from 'tailwindcss';
import resolveConfig from 'tailwindcss/resolveConfig.js';
@@ -17,7 +18,7 @@ function getDefaultTailwindConfig(srcUrl: URL): TailwindConfig {
}) as TailwindConfig;
}
-async function getUserConfig(root: URL, configPath?: string) {
+async function getUserConfig(root: URL, configPath?: string, isRestart = false) {
const resolvedRoot = fileURLToPath(root);
let userConfigPath: string | undefined;
@@ -26,7 +27,42 @@ async function getUserConfig(root: URL, configPath?: string) {
userConfigPath = fileURLToPath(new URL(configPathWithLeadingSlash, root));
}
- return await load('tailwind', { mustExist: false, cwd: resolvedRoot, filePath: userConfigPath });
+ if (isRestart) {
+ // Hack: Write config to temporary file at project root
+ // This invalidates and reloads file contents when using ESM imports or "resolve"
+ const resolvedConfigPath = (await resolve('tailwind', {
+ mustExist: false,
+ cwd: resolvedRoot,
+ filePath: userConfigPath,
+ })) as string;
+
+ const { dir, base } = path.parse(resolvedConfigPath);
+ const tempConfigPath = path.join(dir, `.temp.${Date.now()}.${base}`);
+ await fs.copyFile(resolvedConfigPath, tempConfigPath);
+
+ const result = await load('tailwind', {
+ mustExist: false,
+ cwd: resolvedRoot,
+ filePath: tempConfigPath,
+ });
+
+ try {
+ await fs.unlink(tempConfigPath);
+ } catch {
+ /** file already removed */
+ }
+
+ return {
+ ...result,
+ filePath: resolvedConfigPath,
+ };
+ } else {
+ return await load('tailwind', {
+ mustExist: false,
+ cwd: resolvedRoot,
+ filePath: userConfigPath,
+ });
+ }
}
type TailwindOptions =
@@ -55,9 +91,9 @@ export default function tailwindIntegration(options?: TailwindOptions): AstroInt
return {
name: '@astrojs/tailwind',
hooks: {
- 'astro:config:setup': async ({ config, injectScript }) => {
+ 'astro:config:setup': async ({ config, injectScript, addWatchFile, isRestart }) => {
// Inject the Tailwind postcss plugin
- const userConfig = await getUserConfig(config.root, customConfigPath);
+ const userConfig = await getUserConfig(config.root, customConfigPath, isRestart);
if (customConfigPath && !userConfig?.value) {
throw new Error(
@@ -67,6 +103,10 @@ export default function tailwindIntegration(options?: TailwindOptions): AstroInt
);
}
+ if (userConfig?.filePath) {
+ addWatchFile(userConfig.filePath);
+ }
+
const tailwindConfig: TailwindConfig =
(userConfig?.value as TailwindConfig) ?? getDefaultTailwindConfig(config.srcDir);
config.style.postcss.plugins.push(tailwindPlugin(tailwindConfig));