From c1cebb4b2dc7fedc291d92503a750d2871d2e4d3 Mon Sep 17 00:00:00 2001 From: bluwy Date: Mon, 7 Oct 2024 22:44:47 +0800 Subject: [PATCH] Support custom mode --- .changeset/sixty-coins-worry.md | 29 +++++ .../astro/src/assets/vite-plugin-assets.ts | 16 ++- packages/astro/src/cli/build/index.ts | 3 +- packages/astro/src/cli/flags.ts | 2 +- packages/astro/src/config/index.ts | 11 +- .../src/content/vite-plugin-content-assets.ts | 6 +- .../vite-plugin-content-virtual-mod.ts | 2 +- packages/astro/src/core/app/index.ts | 2 +- packages/astro/src/core/app/pipeline.ts | 6 +- packages/astro/src/core/base-pipeline.ts | 6 +- packages/astro/src/core/build/index.ts | 29 +++-- packages/astro/src/core/build/pipeline.ts | 2 +- packages/astro/src/core/build/static-build.ts | 2 - packages/astro/src/core/build/types.ts | 2 +- packages/astro/src/core/create-vite.ts | 21 ++-- packages/astro/src/core/dev/container.ts | 5 +- packages/astro/src/core/render/route-cache.ts | 8 +- packages/astro/src/core/sync/index.ts | 22 +++- packages/astro/src/env/vite-plugin-env.ts | 8 +- packages/astro/src/types/public/config.ts | 7 +- .../src/vite-plugin-astro-server/pipeline.ts | 7 +- packages/astro/test/astro-mode.test.js | 119 ++++++++++++++++++ .../test/fixtures/astro-mode/.env.development | 1 + .../test/fixtures/astro-mode/.env.production | 1 + .../test/fixtures/astro-mode/.env.staging | 1 + .../test/fixtures/astro-mode/astro.config.mjs | 14 +++ .../test/fixtures/astro-mode/package.json | 8 ++ .../fixtures/astro-mode/src/pages/index.astro | 9 ++ packages/astro/test/test-utils.js | 9 +- pnpm-lock.yaml | 6 + 30 files changed, 285 insertions(+), 79 deletions(-) create mode 100644 .changeset/sixty-coins-worry.md create mode 100644 packages/astro/test/astro-mode.test.js create mode 100644 packages/astro/test/fixtures/astro-mode/.env.development create mode 100644 packages/astro/test/fixtures/astro-mode/.env.production create mode 100644 packages/astro/test/fixtures/astro-mode/.env.staging create mode 100644 packages/astro/test/fixtures/astro-mode/astro.config.mjs create mode 100644 packages/astro/test/fixtures/astro-mode/package.json create mode 100644 packages/astro/test/fixtures/astro-mode/src/pages/index.astro diff --git a/.changeset/sixty-coins-worry.md b/.changeset/sixty-coins-worry.md new file mode 100644 index 000000000000..d696b722ed3a --- /dev/null +++ b/.changeset/sixty-coins-worry.md @@ -0,0 +1,29 @@ +--- +'astro': minor +--- + +Adds support for passing values other than `"production"` or `"development"` to the `--mode` flag, e.g. `"staging"`, `"testing"`, or any value. This allows to change the value of `import.meta.env.MODE` or the loaded `.env` file, and take advantage of Vite's [mode](https://vite.dev/guide/env-and-mode#modes) feature. + +Note that changing the `mode` does not change the kind of code transform handled by Vite and Astro: + +- In `astro dev`, Astro will transform code with debug information. +- In `astro build`, Astro will transform code with the most optimized output and removes debug information. +- In `astro build --dev` (new flag), Astro will transform code with debug information like in `astro dev`. + +This enables various usecases like: + +```bash +# Run the dev server connected to a "staging" API +astro dev --mode staging + +# Build a site that connects to a "staging" API +astro build --mode staging + +# Build a site that connects to a "production" API with additional debug information +astro build --dev + +# Build a site that connects to a "testing" API +astro build --mode testing +``` + +The different modes can be used to load different `.env` files, e.g. `.env.staging` or `.env.production`, which can have different `API_URL` environment variable values (for example). diff --git a/packages/astro/src/assets/vite-plugin-assets.ts b/packages/astro/src/assets/vite-plugin-assets.ts index 7c60a34a2d4f..037fae725a97 100644 --- a/packages/astro/src/assets/vite-plugin-assets.ts +++ b/packages/astro/src/assets/vite-plugin-assets.ts @@ -10,7 +10,7 @@ import { removeBase, removeQueryString, } from '../core/path.js'; -import type { AstroPluginOptions, AstroSettings } from '../types/astro.js'; +import type { AstroSettings } from '../types/astro.js'; import { VALID_INPUT_FORMATS, VIRTUAL_MODULE_ID, VIRTUAL_SERVICE_ID } from './consts.js'; import type { ImageTransform } from './types.js'; import { getAssetsPrefix } from './utils/getAssetsPrefix.js'; @@ -89,12 +89,10 @@ const addStaticImageFactory = ( }; }; -export default function assets({ - settings, - mode, -}: AstroPluginOptions & { mode: string }): vite.Plugin[] { +export default function assets({ settings }: { settings: AstroSettings }): vite.Plugin[] { let resolvedConfig: vite.ResolvedConfig; let shouldEmitFile = false; + let isBuild = false; globalThis.astroAsset = { referencedImages: new Set(), @@ -104,6 +102,9 @@ export default function assets({ // Expose the components and different utilities from `astro:assets` { name: 'astro:assets', + config(_, env) { + isBuild = env.command === 'build'; + }, async resolveId(id) { if (id === VIRTUAL_SERVICE_ID) { return await this.resolve(settings.config.image.service.entrypoint); @@ -143,10 +144,7 @@ export default function assets({ } }, buildStart() { - if (mode != 'build') { - return; - } - + if (!isBuild) return; globalThis.astroAsset.addStaticImage = addStaticImageFactory(settings); }, // In build, rewrite paths to ESM imported images in code to their final location diff --git a/packages/astro/src/cli/build/index.ts b/packages/astro/src/cli/build/index.ts index dd44e6170165..6bc98c37ba8d 100644 --- a/packages/astro/src/cli/build/index.ts +++ b/packages/astro/src/cli/build/index.ts @@ -18,6 +18,7 @@ export async function build({ flags }: BuildOptions) { '--force', 'Clear the content layer and content collection cache, forcing a full rebuild.', ], + ['--dev', 'Create a development build, similar to code transformed in `astro dev`.'], ['--help (-h)', 'See all available flags.'], ], }, @@ -28,5 +29,5 @@ export async function build({ flags }: BuildOptions) { const inlineConfig = flagsToAstroInlineConfig(flags); - await _build(inlineConfig); + await _build(inlineConfig, { dev: !!flags.dev }); } diff --git a/packages/astro/src/cli/flags.ts b/packages/astro/src/cli/flags.ts index e890b242cd08..7466fdda7aea 100644 --- a/packages/astro/src/cli/flags.ts +++ b/packages/astro/src/cli/flags.ts @@ -10,7 +10,7 @@ export function flagsToAstroInlineConfig(flags: Flags): AstroInlineConfig { return { // Inline-only configs configFile: typeof flags.config === 'string' ? flags.config : undefined, - mode: typeof flags.mode === 'string' ? (flags.mode as AstroInlineConfig['mode']) : undefined, + mode: typeof flags.mode === 'string' ? flags.mode : undefined, logLevel: flags.verbose ? 'debug' : flags.silent ? 'silent' : undefined, force: flags.force ? true : undefined, diff --git a/packages/astro/src/config/index.ts b/packages/astro/src/config/index.ts index 77c33fcc5ffc..0aa421f78ded 100644 --- a/packages/astro/src/config/index.ts +++ b/packages/astro/src/config/index.ts @@ -1,4 +1,4 @@ -import type { UserConfig as ViteUserConfig } from 'vite'; +import type { UserConfig as ViteUserConfig, UserConfigFn as ViteUserConfigFn } from 'vite'; import { Logger } from '../core/logger/core.js'; import { createRouteManifest } from '../core/routing/index.js'; import type { AstroInlineConfig, AstroUserConfig } from '../types/public/config.js'; @@ -11,11 +11,11 @@ export function defineConfig(config: AstroUserConfig) { export function getViteConfig( userViteConfig: ViteUserConfig, inlineAstroConfig: AstroInlineConfig = {}, -) { +): ViteUserConfigFn { // Return an async Vite config getter which exposes a resolved `mode` and `command` - return async ({ mode, command }: { mode: 'dev'; command: 'serve' | 'build' }) => { + return async ({ mode, command }) => { // Vite `command` is `serve | build`, but Astro uses `dev | build` - const cmd = command === 'serve' ? 'dev' : command; + const cmd = command === 'serve' ? 'dev' : 'build'; // Use dynamic import to avoid pulling in deps unless used const [ @@ -46,13 +46,12 @@ export function getViteConfig( const devSSRManifest = createDevelopmentManifest(settings); const viteConfig = await createVite( { - mode, plugins: [ // Initialize the content listener astroContentListenPlugin({ settings, logger, fs }), ], }, - { settings, logger, mode, sync: false, manifest, ssrManifest: devSSRManifest }, + { settings, command: cmd, logger, mode, sync: false, manifest, ssrManifest: devSSRManifest }, ); await runHookConfigDone({ settings, logger }); return mergeConfig(viteConfig, userViteConfig); diff --git a/packages/astro/src/content/vite-plugin-content-assets.ts b/packages/astro/src/content/vite-plugin-content-assets.ts index 282f402e6c69..f4b8ed98ec70 100644 --- a/packages/astro/src/content/vite-plugin-content-assets.ts +++ b/packages/astro/src/content/vite-plugin-content-assets.ts @@ -21,10 +21,8 @@ import { import { hasContentFlag } from './utils.js'; export function astroContentAssetPropagationPlugin({ - mode, settings, }: { - mode: string; settings: AstroSettings; }): Plugin { let devModuleLoader: ModuleLoader; @@ -67,9 +65,7 @@ export function astroContentAssetPropagationPlugin({ } }, configureServer(server) { - if (mode === 'dev') { - devModuleLoader = createViteLoader(server); - } + devModuleLoader = createViteLoader(server); }, async transform(_, id, options) { if (hasContentFlag(id, PROPAGATED_ASSET_FLAG)) { diff --git a/packages/astro/src/content/vite-plugin-content-virtual-mod.ts b/packages/astro/src/content/vite-plugin-content-virtual-mod.ts index ca601e0383fc..01dfc3fc8138 100644 --- a/packages/astro/src/content/vite-plugin-content-virtual-mod.ts +++ b/packages/astro/src/content/vite-plugin-content-virtual-mod.ts @@ -56,7 +56,7 @@ export function astroContentVirtualModPlugin({ name: 'astro-content-virtual-mod-plugin', enforce: 'pre', configResolved(config) { - IS_DEV = config.mode === 'development'; + IS_DEV = !config.isProduction; dataStoreFile = getDataStoreFile(settings, IS_DEV); }, async resolveId(id) { diff --git a/packages/astro/src/core/app/index.ts b/packages/astro/src/core/app/index.ts index 9e124879e22a..48c6a12aaeae 100644 --- a/packages/astro/src/core/app/index.ts +++ b/packages/astro/src/core/app/index.ts @@ -114,7 +114,7 @@ export class App { return AppPipeline.create(manifestData, { logger: this.#logger, manifest: this.#manifest, - mode: 'production', + runtimeMode: 'production', renderers: this.#manifest.renderers, defaultRoutes: createDefaultRoutes(this.#manifest), resolve: async (specifier: string) => { diff --git a/packages/astro/src/core/app/pipeline.ts b/packages/astro/src/core/app/pipeline.ts index 9317f9de1ca5..3492d56c59b0 100644 --- a/packages/astro/src/core/app/pipeline.ts +++ b/packages/astro/src/core/app/pipeline.ts @@ -15,7 +15,7 @@ export class AppPipeline extends Pipeline { { logger, manifest, - mode, + runtimeMode, renderers, resolve, serverLike, @@ -25,7 +25,7 @@ export class AppPipeline extends Pipeline { AppPipeline, | 'logger' | 'manifest' - | 'mode' + | 'runtimeMode' | 'renderers' | 'resolve' | 'serverLike' @@ -36,7 +36,7 @@ export class AppPipeline extends Pipeline { const pipeline = new AppPipeline( logger, manifest, - mode, + runtimeMode, renderers, resolve, serverLike, diff --git a/packages/astro/src/core/base-pipeline.ts b/packages/astro/src/core/base-pipeline.ts index afab92df1d29..ad7de6de5a7d 100644 --- a/packages/astro/src/core/base-pipeline.ts +++ b/packages/astro/src/core/base-pipeline.ts @@ -32,9 +32,9 @@ export abstract class Pipeline { readonly logger: Logger, readonly manifest: SSRManifest, /** - * "development" or "production" + * "development" or "production" only */ - readonly mode: RuntimeMode, + readonly runtimeMode: RuntimeMode, readonly renderers: SSRLoadedRenderer[], readonly resolve: (s: string) => Promise, /** @@ -51,7 +51,7 @@ export abstract class Pipeline { readonly compressHTML = manifest.compressHTML, readonly i18n = manifest.i18n, readonly middleware = manifest.middleware, - readonly routeCache = new RouteCache(logger, mode), + readonly routeCache = new RouteCache(logger, runtimeMode), /** * Used for `Astro.site`. */ diff --git a/packages/astro/src/core/build/index.ts b/packages/astro/src/core/build/index.ts index bd50268eac99..c06b37c33827 100644 --- a/packages/astro/src/core/build/index.ts +++ b/packages/astro/src/core/build/index.ts @@ -31,7 +31,14 @@ import { collectPagesData } from './page-data.js'; import { staticBuild, viteBuild } from './static-build.js'; import type { StaticBuildOptions } from './types.js'; import { getTimeStat } from './util.js'; + export interface BuildOptions { + /** + * Create a development build, similar to code transformed in `astro dev`. + * + * @default false + */ + dev?: boolean; /** * Teardown the compiler WASM instance after build. This can improve performance when * building once, but may cause a performance hit if building multiple times in a row. @@ -52,7 +59,7 @@ export default async function build( inlineConfig: AstroInlineConfig, options: BuildOptions = {}, ): Promise { - ensureProcessNodeEnv('production'); + ensureProcessNodeEnv(options.dev ? 'development' : 'production'); applyPolyfill(); const logger = createNodeLogger(inlineConfig); const { userConfig, astroConfig } = await resolveConfig(inlineConfig, 'build'); @@ -67,29 +74,31 @@ export default async function build( const builder = new AstroBuilder(settings, { ...options, logger, - mode: inlineConfig.mode, + mode: inlineConfig.mode ?? 'production', + runtimeMode: options.dev ? 'development' : 'production', }); await builder.run(); } interface AstroBuilderOptions extends BuildOptions { logger: Logger; - mode?: RuntimeMode; + mode: string; + runtimeMode: RuntimeMode; } class AstroBuilder { private settings: AstroSettings; private logger: Logger; - private mode: RuntimeMode = 'production'; + private mode: string; + private runtimeMode: RuntimeMode; private origin: string; private manifest: ManifestData; private timer: Record; private teardownCompiler: boolean; constructor(settings: AstroSettings, options: AstroBuilderOptions) { - if (options.mode) { - this.mode = options.mode; - } + this.mode = options.mode; + this.runtimeMode = options.runtimeMode; this.settings = settings; this.logger = options.logger; this.teardownCompiler = options.teardownCompiler ?? true; @@ -127,7 +136,6 @@ class AstroBuilder { const viteConfig = await createVite( { - mode: this.mode, server: { hmr: false, middlewareMode: true, @@ -136,7 +144,7 @@ class AstroBuilder { { settings: this.settings, logger: this.logger, - mode: 'build', + mode: this.mode, command: 'build', sync: false, manifest: this.manifest, @@ -145,6 +153,7 @@ class AstroBuilder { const { syncInternal } = await import('../sync/index.js'); await syncInternal({ + mode: this.mode, settings: this.settings, logger, fs, @@ -193,7 +202,7 @@ class AstroBuilder { settings: this.settings, logger: this.logger, manifest: this.manifest, - mode: this.mode, + runtimeMode: this.runtimeMode, origin: this.origin, pageNames, teardownCompiler: this.teardownCompiler, diff --git a/packages/astro/src/core/build/pipeline.ts b/packages/astro/src/core/build/pipeline.ts index 523962078512..478d0a21ad08 100644 --- a/packages/astro/src/core/build/pipeline.ts +++ b/packages/astro/src/core/build/pipeline.ts @@ -78,7 +78,7 @@ export class BuildPipeline extends Pipeline { super( options.logger, manifest, - options.mode, + options.runtimeMode, manifest.renderers, resolve, serverLike, diff --git a/packages/astro/src/core/build/static-build.ts b/packages/astro/src/core/build/static-build.ts index 4e5414bbb08d..b000b28b73ae 100644 --- a/packages/astro/src/core/build/static-build.ts +++ b/packages/astro/src/core/build/static-build.ts @@ -156,7 +156,6 @@ async function ssrBuild( const { lastVitePlugins, vitePlugins } = await container.runBeforeHook('server', input); const viteBuildConfig: vite.InlineConfig = { ...viteConfig, - mode: viteConfig.mode || 'production', logLevel: viteConfig.logLevel ?? 'error', build: { target: 'esnext', @@ -269,7 +268,6 @@ async function clientBuild( const viteBuildConfig: vite.InlineConfig = { ...viteConfig, - mode: viteConfig.mode || 'production', build: { target: 'esnext', ...viteConfig.build, diff --git a/packages/astro/src/core/build/types.ts b/packages/astro/src/core/build/types.ts index 400f957e0bc0..363e03ca7664 100644 --- a/packages/astro/src/core/build/types.ts +++ b/packages/astro/src/core/build/types.ts @@ -30,7 +30,7 @@ export interface StaticBuildOptions { settings: AstroSettings; logger: Logger; manifest: ManifestData; - mode: RuntimeMode; + runtimeMode: RuntimeMode; origin: string; pageNames: string[]; viteConfig: InlineConfig; diff --git a/packages/astro/src/core/create-vite.ts b/packages/astro/src/core/create-vite.ts index 8e4806ead11b..6680a1962021 100644 --- a/packages/astro/src/core/create-vite.ts +++ b/packages/astro/src/core/create-vite.ts @@ -45,18 +45,18 @@ import astroHmrReloadPlugin from '../vite-plugin-hmr-reload/index.js'; type CreateViteOptions = { settings: AstroSettings; logger: Logger; - // will be undefined when using `getViteConfig` - command?: 'dev' | 'build'; + mode: string; fs?: typeof nodeFs; sync: boolean; manifest: ManifestData; + ssrManifest?: SSRManifest; } & ( | { - mode: 'dev'; + command: 'dev'; ssrManifest: SSRManifest; } | { - mode: 'build'; + command: 'build'; ssrManifest?: SSRManifest; } ); @@ -90,7 +90,7 @@ export async function createVite( ): Promise { const astroPkgsConfig = await crawlFrameworkPkgs({ root: fileURLToPath(settings.config.root), - isBuild: mode === 'build', + isBuild: command === 'build', viteUserConfig: settings.config.vite, isFrameworkPkgByJson(pkgJson) { // Certain packages will trigger the checks below, but need to be external. A common example are SSR adapters @@ -126,6 +126,7 @@ export async function createVite( const commonConfig: vite.InlineConfig = { // Tell Vite not to combine config from vite.config.js with our provided inline config configFile: false, + mode, cacheDir: fileURLToPath(new URL('./node_modules/.vite/', settings.config.root)), // using local caches allows Astro to be used in monorepos, etc. clearScreen: false, // we want to control the output, not Vite customLogger: createViteLogger(logger, settings.config.vite.logLevel), @@ -142,7 +143,7 @@ export async function createVite( astroScriptsPlugin({ settings }), // The server plugin is for dev only and having it run during the build causes // the build to run very slow as the filewatcher is triggered often. - mode === 'dev' && vitePluginAstroServer({ settings, logger, fs, manifest, ssrManifest }), // ssrManifest is only required in dev mode, where it gets created before a Vite instance is created, and get passed to this function + command === 'dev' && vitePluginAstroServer({ settings, logger, fs, manifest, ssrManifest }), // ssrManifest is only required in dev mode, where it gets created before a Vite instance is created, and get passed to this function envVitePlugin({ settings }), astroEnv({ settings, mode, sync }), markdownVitePlugin({ settings, logger }), @@ -154,10 +155,10 @@ export async function createVite( astroScannerPlugin({ settings, logger, manifest }), astroContentVirtualModPlugin({ fs, settings }), astroContentImportPlugin({ fs, settings, logger }), - astroContentAssetPropagationPlugin({ mode, settings }), + astroContentAssetPropagationPlugin({ settings }), vitePluginMiddleware({ settings }), vitePluginSSRManifest(), - astroAssetsPlugin({ settings, logger, mode }), + astroAssetsPlugin({ settings }), astroPrefetch({ settings }), astroTransitions({ settings }), astroDevToolbar({ settings, logger }), @@ -184,7 +185,7 @@ export async function createVite( : undefined, // disable HMR for test watch: { // Prevent watching during the build to speed it up - ignored: mode === 'build' ? ['**'] : undefined, + ignored: command === 'build' ? ['**'] : undefined, }, }, resolve: { @@ -219,7 +220,7 @@ export async function createVite( }, ssr: { noExternal: [...ALWAYS_NOEXTERNAL, ...astroPkgsConfig.ssr.noExternal], - external: [...(mode === 'dev' ? ONLY_DEV_EXTERNAL : []), ...astroPkgsConfig.ssr.external], + external: [...(command === 'dev' ? ONLY_DEV_EXTERNAL : []), ...astroPkgsConfig.ssr.external], }, build: { assetsDir: settings.config.build.assets }, }; diff --git a/packages/astro/src/core/dev/container.ts b/packages/astro/src/core/dev/container.ts index c1c13e0c70ab..3f9090498be2 100644 --- a/packages/astro/src/core/dev/container.ts +++ b/packages/astro/src/core/dev/container.ts @@ -92,9 +92,9 @@ export async function createContainer({ warnMissingAdapter(logger, settings); + const mode = inlineConfig?.mode ?? 'development'; const viteConfig = await createVite( { - mode: 'development', server: { host, headers, open }, optimizeDeps: { include: rendererClientEntries, @@ -103,7 +103,7 @@ export async function createContainer({ { settings, logger, - mode: 'dev', + mode, command: 'dev', fs, sync: false, @@ -114,6 +114,7 @@ export async function createContainer({ await syncInternal({ settings, + mode, logger, skip: { content: true, diff --git a/packages/astro/src/core/render/route-cache.ts b/packages/astro/src/core/render/route-cache.ts index 247f3aa235a9..21c823cd0d24 100644 --- a/packages/astro/src/core/render/route-cache.ts +++ b/packages/astro/src/core/render/route-cache.ts @@ -88,11 +88,11 @@ interface RouteCacheEntry { export class RouteCache { private logger: Logger; private cache: Record = {}; - private mode: RuntimeMode; + private runtimeMode: RuntimeMode; - constructor(logger: Logger, mode: RuntimeMode = 'production') { + constructor(logger: Logger, runtimeMode: RuntimeMode = 'production') { this.logger = logger; - this.mode = mode; + this.runtimeMode = runtimeMode; } /** Clear the cache. */ @@ -105,7 +105,7 @@ export class RouteCache { // NOTE: This shouldn't be called on an already-cached component. // Warn here so that an unexpected double-call of getStaticPaths() // isn't invisible and developer can track down the issue. - if (this.mode === 'production' && this.cache[key]?.staticPaths) { + if (this.runtimeMode === 'production' && this.cache[key]?.staticPaths) { this.logger.warn(null, `Internal Warning: route cache overwritten. (${key})`); } this.cache[key] = entry; diff --git a/packages/astro/src/core/sync/index.ts b/packages/astro/src/core/sync/index.ts index c5bc540e9b28..bedef5bccf50 100644 --- a/packages/astro/src/core/sync/index.ts +++ b/packages/astro/src/core/sync/index.ts @@ -35,6 +35,7 @@ import { createRouteManifest } from '../routing/index.js'; import { ensureProcessNodeEnv } from '../util.js'; export type SyncOptions = { + mode: string; /** * @internal only used for testing */ @@ -79,7 +80,14 @@ export default async function sync( throw err; } - return await syncInternal({ settings, logger, fs, force: inlineConfig.force, manifest }); + return await syncInternal({ + settings, + logger, + mode: 'production', + fs, + force: inlineConfig.force, + manifest, + }); } /** @@ -105,6 +113,7 @@ export async function clearContentLayerCache({ * @experimental The JavaScript API is experimental */ export async function syncInternal({ + mode, logger, fs = fsMod, settings, @@ -120,7 +129,7 @@ export async function syncInternal({ try { if (!skip?.content) { - await syncContentCollections(settings, { fs, logger, manifest }); + await syncContentCollections(settings, { mode, fs, logger, manifest }); settings.timer.start('Sync content layer'); let store: MutableDataStore | undefined; try { @@ -208,7 +217,12 @@ function writeInjectedTypes(settings: AstroSettings, fs: typeof fsMod) { */ async function syncContentCollections( settings: AstroSettings, - { logger, fs, manifest }: Required>, + { + mode, + logger, + fs, + manifest, + }: Required>, ): Promise { // Needed to load content config const tempViteServer = await createServer( @@ -219,7 +233,7 @@ async function syncContentCollections( ssr: { external: [] }, logLevel: 'silent', }, - { settings, logger, mode: 'build', command: 'build', fs, sync: true, manifest }, + { settings, logger, mode, command: 'build', fs, sync: true, manifest }, ), ); diff --git a/packages/astro/src/env/vite-plugin-env.ts b/packages/astro/src/env/vite-plugin-env.ts index fffc37c5e105..816f460b318f 100644 --- a/packages/astro/src/env/vite-plugin-env.ts +++ b/packages/astro/src/env/vite-plugin-env.ts @@ -14,7 +14,7 @@ import { getEnvFieldType, validateEnvVariable } from './validators.js'; interface AstroEnvPluginParams { settings: AstroSettings; - mode: 'dev' | 'build' | string; + mode: string; sync: boolean; } @@ -27,11 +27,7 @@ export function astroEnv({ settings, mode, sync }: AstroEnvPluginParams): Plugin name: 'astro-env-plugin', enforce: 'pre', buildStart() { - const loadedEnv = loadEnv( - mode === 'dev' ? 'development' : 'production', - fileURLToPath(settings.config.root), - '', - ); + const loadedEnv = loadEnv(mode, fileURLToPath(settings.config.root), ''); for (const [key, value] of Object.entries(loadedEnv)) { if (value !== undefined) { process.env[key] = value; diff --git a/packages/astro/src/types/public/config.ts b/packages/astro/src/types/public/config.ts index fda779bdcb09..23c344db7b4e 100644 --- a/packages/astro/src/types/public/config.ts +++ b/packages/astro/src/types/public/config.ts @@ -1684,9 +1684,12 @@ export interface AstroInlineOnlyConfig { */ configFile?: string | false; /** - * The mode used when building your site to generate either "development" or "production" code. + * The mode used when building your site. It's passed to Vite that affects how `.env` files are loaded and the value + * of `import.meta.env.MODE`. See the [Vite docs](https://vitejs.dev/guide/env-and-mode.html) for more information. + * + * @default "development" for `astro dev`, "production" for `astro build` */ - mode?: RuntimeMode; + mode?: string; /** * The logging level to filter messages logged by Astro. * - "debug": Log everything, including noisy debugging diagnostics. diff --git a/packages/astro/src/vite-plugin-astro-server/pipeline.ts b/packages/astro/src/vite-plugin-astro-server/pipeline.ts index c4bd54111817..21c92db60cfe 100644 --- a/packages/astro/src/vite-plugin-astro-server/pipeline.ts +++ b/packages/astro/src/vite-plugin-astro-server/pipeline.ts @@ -45,11 +45,10 @@ export class DevPipeline extends Pipeline { readonly config = settings.config, readonly defaultRoutes = createDefaultRoutes(manifest), ) { - const mode = 'development'; const resolve = createResolve(loader, config.root); const serverLike = settings.buildOutput === 'server'; const streaming = true; - super(logger, manifest, mode, [], resolve, serverLike, streaming); + super(logger, manifest, 'development', [], resolve, serverLike, streaming); manifest.serverIslandMap = settings.serverIslandMap; manifest.serverIslandNameMap = settings.serverIslandNameMap; } @@ -72,14 +71,14 @@ export class DevPipeline extends Pipeline { const { config: { root }, loader, - mode, + runtimeMode, settings, } = this; const filePath = new URL(`${routeData.component}`, root); const scripts = new Set(); // Inject HMR scripts - if (isPage(filePath, settings) && mode === 'development') { + if (isPage(filePath, settings) && runtimeMode === 'development') { scripts.add({ props: { type: 'module', src: '/@vite/client' }, children: '', diff --git a/packages/astro/test/astro-mode.test.js b/packages/astro/test/astro-mode.test.js new file mode 100644 index 000000000000..7bd420209829 --- /dev/null +++ b/packages/astro/test/astro-mode.test.js @@ -0,0 +1,119 @@ +import assert from 'node:assert/strict'; +import { after, afterEach, before, describe, it } from 'node:test'; +import * as cheerio from 'cheerio'; +import { loadFixture } from './test-utils.js'; + +describe('--mode', () => { + /** @type {import('./test-utils.js').Fixture} */ + let fixture; + + before(async () => { + fixture = await loadFixture({ + root: './fixtures/astro-mode/', + }); + }); + + afterEach(() => { + // Reset so it doesn't interfere with other tests as builds below + // will interact with env variables loaded from .env files + delete process.env.NODE_ENV; + // `astro:env` writes to `process.env` currently which should be avoided, + // otherwise consecutive builds can't get the latest `.env` values. Workaround + // here for now be resetting it. + delete process.env.TITLE; + }); + + describe('build', () => { + before(async () => { + await fixture.build(); + }); + + it('works', async () => { + const html = await fixture.readFile('/index.html'); + const $ = cheerio.load(html); + assert.equal($('#env-mode').text(), 'production'); + assert.equal($('#env-dev').text(), 'false'); + assert.equal($('#env-prod').text(), 'true'); + assert.equal($('#env-title').text(), 'production'); + assert.equal($('#env-astro-title').text(), 'production'); + }); + }); + + describe('build --mode testing --dev', () => { + before(async () => { + await fixture.build({ mode: 'testing' }, { dev: true }); + }); + + it('works', async () => { + const html = await fixture.readFile('/index.html'); + const $ = cheerio.load(html); + assert.equal($('#env-mode').text(), 'testing'); + assert.equal($('#env-dev').text(), 'true'); + assert.equal($('#env-prod').text(), 'false'); + assert.equal($('#env-title').text(), ''); + assert.equal($('#env-astro-title').text(), 'unset'); + }); + }); + + describe('build --mode staging', () => { + before(async () => { + await fixture.build({ mode: 'staging' }); + }); + + it('works', async () => { + const html = await fixture.readFile('/index.html'); + const $ = cheerio.load(html); + assert.equal($('#env-mode').text(), 'staging'); + assert.equal($('#env-dev').text(), 'false'); + assert.equal($('#env-prod').text(), 'true'); + assert.equal($('#env-title').text(), 'staging'); + assert.equal($('#env-astro-title').text(), 'staging'); + }); + }); + + describe('dev', () => { + /** @type {import('./test-utils.js').DevServer} */ + let devServer; + before(async () => { + devServer = await fixture.startDevServer(); + }); + after(async () => { + await devServer.stop(); + }); + + it('works', async () => { + const res = await fixture.fetch('/'); + assert.equal(res.status, 200); + const html = await res.text(); + const $ = cheerio.load(html); + assert.equal($('#env-mode').text(), 'development'); + assert.equal($('#env-dev').text(), 'true'); + assert.equal($('#env-prod').text(), 'false'); + assert.equal($('#env-title').text(), 'development'); + assert.equal($('#env-astro-title').text(), 'development'); + }); + }); + + describe('dev --mode develop', () => { + /** @type {import('./test-utils.js').DevServer} */ + let devServer; + before(async () => { + devServer = await fixture.startDevServer({ mode: 'develop' }); + }); + after(async () => { + await devServer.stop(); + }); + + it('works', async () => { + const res = await fixture.fetch('/'); + assert.equal(res.status, 200); + const html = await res.text(); + const $ = cheerio.load(html); + assert.equal($('#env-mode').text(), 'develop'); + assert.equal($('#env-dev').text(), 'true'); + assert.equal($('#env-prod').text(), 'false'); + assert.equal($('#env-title').text(), ''); + assert.equal($('#env-astro-title').text(), 'unset'); + }); + }); +}); diff --git a/packages/astro/test/fixtures/astro-mode/.env.development b/packages/astro/test/fixtures/astro-mode/.env.development new file mode 100644 index 000000000000..c1f85a94d705 --- /dev/null +++ b/packages/astro/test/fixtures/astro-mode/.env.development @@ -0,0 +1 @@ +TITLE=development diff --git a/packages/astro/test/fixtures/astro-mode/.env.production b/packages/astro/test/fixtures/astro-mode/.env.production new file mode 100644 index 000000000000..9ac63d13874c --- /dev/null +++ b/packages/astro/test/fixtures/astro-mode/.env.production @@ -0,0 +1 @@ +TITLE=production diff --git a/packages/astro/test/fixtures/astro-mode/.env.staging b/packages/astro/test/fixtures/astro-mode/.env.staging new file mode 100644 index 000000000000..3dae29686680 --- /dev/null +++ b/packages/astro/test/fixtures/astro-mode/.env.staging @@ -0,0 +1 @@ +TITLE=staging diff --git a/packages/astro/test/fixtures/astro-mode/astro.config.mjs b/packages/astro/test/fixtures/astro-mode/astro.config.mjs new file mode 100644 index 000000000000..8c1219899736 --- /dev/null +++ b/packages/astro/test/fixtures/astro-mode/astro.config.mjs @@ -0,0 +1,14 @@ +import { defineConfig, envField } from 'astro/config'; + +export default defineConfig({ + env: { + schema: { + TITLE: envField.string({ + context: 'client', + access: 'public', + optional: true, + default: 'unset', + }), + }, + }, +}); diff --git a/packages/astro/test/fixtures/astro-mode/package.json b/packages/astro/test/fixtures/astro-mode/package.json new file mode 100644 index 000000000000..dc84a21d5e8d --- /dev/null +++ b/packages/astro/test/fixtures/astro-mode/package.json @@ -0,0 +1,8 @@ +{ + "name": "@test/astro-mode", + "version": "0.0.0", + "private": true, + "dependencies": { + "astro": "workspace:*" + } +} diff --git a/packages/astro/test/fixtures/astro-mode/src/pages/index.astro b/packages/astro/test/fixtures/astro-mode/src/pages/index.astro new file mode 100644 index 000000000000..8a17ba03cacd --- /dev/null +++ b/packages/astro/test/fixtures/astro-mode/src/pages/index.astro @@ -0,0 +1,9 @@ +--- +import { TITLE } from 'astro:env/client'; +--- + +
{import.meta.env.MODE}
+
{import.meta.env.DEV.toString()}
+
{import.meta.env.PROD.toString()}
+
{import.meta.env.TITLE}
+
{TITLE}
diff --git a/packages/astro/test/test-utils.js b/packages/astro/test/test-utils.js index e97d781422b8..f0bdf8542fb6 100644 --- a/packages/astro/test/test-utils.js +++ b/packages/astro/test/test-utils.js @@ -162,7 +162,8 @@ export async function loadFixture(inlineConfig) { build: async (extraInlineConfig = {}, options = {}) => { globalContentLayer.dispose(); globalContentConfigObserver.set({ status: 'init' }); - process.env.NODE_ENV = 'production'; + // Reset NODE_ENV so it can be re-set by `build()` + delete process.env.NODE_ENV; return build(mergeConfig(inlineConfig, extraInlineConfig), { teardownCompiler: false, ...options, @@ -175,7 +176,8 @@ export async function loadFixture(inlineConfig) { startDevServer: async (extraInlineConfig = {}) => { globalContentLayer.dispose(); globalContentConfigObserver.set({ status: 'init' }); - process.env.NODE_ENV = 'development'; + // Reset NODE_ENV so it can be re-set by `dev()` + delete process.env.NODE_ENV; devServer = await dev(mergeConfig(inlineConfig, extraInlineConfig)); config.server.host = parseAddressToHost(devServer.address.address); // update host config.server.port = devServer.address.port; // update port @@ -233,7 +235,8 @@ export async function loadFixture(inlineConfig) { } }, preview: async (extraInlineConfig = {}) => { - process.env.NODE_ENV = 'production'; + // Reset NODE_ENV so it can be re-set by `preview()` + delete process.env.NODE_ENV; const previewServer = await preview(mergeConfig(inlineConfig, extraInlineConfig)); config.server.host = parseAddressToHost(previewServer.host); // update host config.server.port = previewServer.port; // update port diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0b51a09ab5a5..7be6ca459809 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2330,6 +2330,12 @@ importers: specifier: workspace:* version: link:../../.. + packages/astro/test/fixtures/astro-mode: + dependencies: + astro: + specifier: workspace:* + version: link:../../.. + packages/astro/test/fixtures/astro-not-response: dependencies: astro: