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

Export experimental JS API #7979

Merged
merged 12 commits into from
Aug 18, 2023
35 changes: 35 additions & 0 deletions .changeset/many-pears-explode.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
---
'astro': major
---

Export experimental `dev`, `build`, `preview`, and `sync` APIs from `astro`. These APIs allow you to run Astro's commands programmatically, and replaces the previous entrypoint that runs the Astro CLI.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just pointing out that I see the word "experimental" here and that typically means "behind an experimental flag" for us. If that's the case, then we should show the flag to enable and should include them in config-reference as an experimental entry.

If it's closer to something like "new" or "use at your own risk" or "good luck!", we might want to consider a different word or phrase that is closer to that meaning.

When it comes time for docs, we do have a CLI page in reference, and we could consider including this on that page. The page starts out pretty user-friendly, but then could progress into something like this.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The APIs are directly imported so I don't think we can have a flag for it. It's closer to "use at your own risk" though, and I'm not exactly sure what's the alternative to "experimental", maybe "work-in-progress" or "unstable"? 🤔

When it comes time for docs, we do have a CLI page in reference, and we could consider including this on that page. The page starts out pretty user-friendly, but then could progress into something like this.

That sounds great. Since these APIs are experimental, and it's signature will likely change, do you think it's worth introducing a full section for these APIs as a start? Or I was thinking we can lightly touch on the JS API names for each CLI commands:

astro dev
...
You can also run the command programatically with the experimental dev() API exported from the "astro" package.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Our usual approach with experimental things is to give them an isolated page or section — makes it easier to avoid lots of repetition saying each time something is mentioned is experimental, makes it easier to update the docs as experimental features change, plus avoids drawing too much attention to a feature we’re still stabilising.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've made a first draft of the API under the CLI commands page as a section at withastro/docs#4234

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that's a good place to start this, and keep it self-contained on that page as an Advanced section! Let's run with that idea for now.

bluwy marked this conversation as resolved.
Show resolved Hide resolved

While these APIs are experimental, the inline config parameter is relatively stable without foreseeable changes. However, the returned results of these APIs are more likely to change in the future.

```ts
import { dev, build, preview, sync, type AstroInlineConfig } from 'astro';

// Inline Astro config object.
// Provide a path to a configuration file to load or set options directly inline.
const inlineConfig: AstroInlineConfig = {
// Inline-specific options...
configFile: './astro.config.mjs',
logLevel: 'info',
// Standard Astro config options...
site: 'https://example.com',
};

// Start the Astro dev server
const devServer = await dev(inlineConfig);
await devServer.stop();

// Build your Astro project
await build(inlineConfig);

// Preview your built project
const previewServer = await preview(inlineConfig);
await previewServer.stop();

// Generate types for your Astro project
await sync(inlineConfig);
```
2 changes: 2 additions & 0 deletions packages/astro/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export type * from './dist/@types/astro.js';
export * from './dist/core/index.js';
7 changes: 4 additions & 3 deletions packages/astro/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
},
"bugs": "https://github.com/withastro/astro/issues",
"homepage": "https://astro.build",
"types": "./dist/@types/astro.d.ts",
"types": "./index.d.ts",
"typesVersions": {
"*": {
"app": [
Expand All @@ -28,8 +28,8 @@
},
"exports": {
".": {
"types": "./dist/@types/astro.d.ts",
"default": "./astro.js"
"types": "./index.d.ts",
"default": "./dist/core/index.js"
},
"./env": "./env.d.ts",
"./types": "./types.d.ts",
Expand Down Expand Up @@ -87,6 +87,7 @@
"tsconfigs",
"dist",
"astro.js",
"index.d.ts",
"config.d.ts",
"config.mjs",
"zod.d.ts",
Expand Down
28 changes: 27 additions & 1 deletion packages/astro/src/@types/astro.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1357,13 +1357,39 @@ export interface AstroConfig extends z.output<typeof AstroConfigSchema> {
// TypeScript still confirms zod validation matches this type.
integrations: AstroIntegration[];
}
/**
* An inline Astro config that takes highest priority when merging with the user config,
* and includes inline-specific options to configure how Astro runs.
*/
export interface AstroInlineConfig extends AstroUserConfig, AstroInlineOnlyConfig {}
export interface AstroInlineOnlyConfig {
/**
* A custom path to the Astro config file. If relative, it'll resolve based on the current working directory.
* Set to false to disable loading any config files.
*
* If this value is undefined or unset, Astro will search for an `astro.config.(js,mjs,ts)` file relative to
* the `root` and load the config file if found.
*
* The inline config passed in this object will take highest priority when merging with the loaded user config.
bluwy marked this conversation as resolved.
Show resolved Hide resolved
*/
configFile?: string | false;
/**
* The mode used when building your site to generate either "development" or "production" code.
*/
mode?: RuntimeMode;
/**
* The logging level to filter messages logged by Astro.
* - "debug": Log all messages and debug information.
* - "info": Log all messages.
bluwy marked this conversation as resolved.
Show resolved Hide resolved
* - "warn": Log only warnings and errors.
* - "error": Log only errors.
* - "silent": Log no messages.
*
* @default "info"
*/
logLevel?: LoggerLevel;
/**
* @internal for testing only
* @internal for testing only, use `logLevel` instead.
*/
logging?: LogOptions;
}
Expand Down
4 changes: 1 addition & 3 deletions packages/astro/src/cli/build/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,5 @@ export async function build({ flags }: BuildOptions) {

const inlineConfig = flagsToAstroInlineConfig(flags);

await _build(inlineConfig, {
teardownCompiler: true,
});
await _build(inlineConfig);
}
2 changes: 1 addition & 1 deletion packages/astro/src/cli/check/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export async function check(flags: Arguments) {
// Run sync before check to make sure types are generated.
// NOTE: In the future, `@astrojs/check` can expose a `before lint` hook so that this works during `astro check --watch` too.
// For now, we run this once as usually `astro check --watch` is ran alongside `astro dev` which also calls `astro sync`.
const { sync } = await import('../../core/sync/index.js');
const { default: sync } = await import('../../core/sync/index.js');
const inlineConfig = flagsToAstroInlineConfig(flags);
const exitCode = await sync(inlineConfig);
if (exitCode !== 0) {
Expand Down
2 changes: 1 addition & 1 deletion packages/astro/src/cli/sync/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type yargs from 'yargs-parser';
import { printHelp } from '../../core/messages.js';
import { sync as _sync } from '../../core/sync/index.js';
import _sync from '../../core/sync/index.js';
import { flagsToAstroInlineConfig } from '../flags.js';

interface SyncOptions {
Expand Down
4 changes: 3 additions & 1 deletion packages/astro/src/core/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# `core/`

Code that executes within the top-level Node context. Contains the main Astro logic for the `build` and `dev` commands, and also manages the Vite server and SSR.
Code that executes within the top-level Node context. Contains the main Astro logic for the `build`, `dev`, `preview`, and `sync` commands, and also manages the Vite server and SSR.

The `core/index.ts` file is the main entrypoint for the `astro` package.
bluwy marked this conversation as resolved.
Show resolved Hide resolved

[See CONTRIBUTING.md](../../../../CONTRIBUTING.md) for a code overview.

Expand Down
14 changes: 11 additions & 3 deletions packages/astro/src/core/build/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,22 @@ export interface BuildOptions {
/**
* 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.
*
* @internal only used for testing
* @default true
*/
teardownCompiler?: boolean;
}

/** `astro build` */
/**
* Builds your site for deployment. By default, this will generate static files and place them in a dist/ directory.
* If SSR is enabled, this will generate the necessary server files to serve your site.
*
* @experimental The JavaScript API is experimental
*/
export default async function build(
bluwy marked this conversation as resolved.
Show resolved Hide resolved
inlineConfig: AstroInlineConfig,
options: BuildOptions
options?: BuildOptions
): Promise<void> {
applyPolyfill();
const logging = createNodeLogging(inlineConfig);
Expand Down Expand Up @@ -82,7 +90,7 @@ class AstroBuilder {
}
this.settings = settings;
this.logging = options.logging;
this.teardownCompiler = options.teardownCompiler ?? false;
this.teardownCompiler = options.teardownCompiler ?? true;
this.routeCache = new RouteCache(this.logging);
this.origin = settings.config.site
? new URL(settings.config.site).origin
Expand Down
7 changes: 6 additions & 1 deletion packages/astro/src/core/dev/dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,12 @@ export interface DevServer {
stop(): Promise<void>;
}

/** `astro dev` */
/**
* Runs Astro’s development server. This is a local HTTP server that doesn’t bundle assets.
* It uses Hot Module Replacement (HMR) to update your browser as you save changes in your editor.
*
* @experimental The JavaScript API is experimental
*/
export default async function dev(inlineConfig: AstroInlineConfig): Promise<DevServer> {
const devStart = performance.now();
await telemetry.record([]);
Expand Down
26 changes: 26 additions & 0 deletions packages/astro/src/core/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// This is the main entrypoint when importing the `astro` package.

import type { AstroInlineConfig } from '../@types/astro.js';
import { default as _build } from './build/index.js';
import { default as _sync } from './sync/index.js';

export { default as dev } from './dev/index.js';
export { default as preview } from './preview/index.js';

/**
* Builds your site for deployment. By default, this will generate static files and place them in a dist/ directory.
* If SSR is enabled, this will generate the necessary server files to serve your site.
*
* @experimental The JavaScript API is experimental
*/
// Wrap `_build` to prevent exposing the second internal options parameter
export const build = (inlineConfig: AstroInlineConfig) => _build(inlineConfig);

/**
* Generates TypeScript types for all Astro modules. This sets up a `src/env.d.ts` file for type inferencing,
* and defines the `astro:content` module for the Content Collections API.
*
* @experimental The JavaScript API is experimental
*/
// Wrap `_sync` to prevent exposing the second internal options parameter
export const sync = (inlineConfig: AstroInlineConfig) => _sync(inlineConfig);
11 changes: 7 additions & 4 deletions packages/astro/src/core/preview/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,13 @@ import { createSettings } from '../config/settings.js';
import createStaticPreviewServer from './static-preview-server.js';
import { getResolvedHostForHttpServer } from './util.js';

/** The primary dev action */
export default async function preview(
inlineConfig: AstroInlineConfig
): Promise<PreviewServer | undefined> {
/**
* Starts a local server to serve your static dist/ directory. This command is useful for previewing
* your build locally, before deploying it. It is not designed to be run in production.
*
* @experimental The JavaScript API is experimental
*/
export default async function preview(inlineConfig: AstroInlineConfig): Promise<PreviewServer> {
const logging = createNodeLogging(inlineConfig);
const { userConfig, astroConfig } = await resolveConfig(inlineConfig ?? {}, 'preview');
telemetry.record(eventCliSession('preview', userConfig));
Expand Down
13 changes: 9 additions & 4 deletions packages/astro/src/core/sync/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,7 @@ export type ProcessExit = 0 | 1;

export type SyncOptions = {
/**
* Only used for testing
* @internal
* @internal only used for testing
*/
fs?: typeof fsMod;
};
Expand All @@ -32,7 +31,13 @@ export type SyncInternalOptions = SyncOptions & {
logging: LogOptions;
};

export async function sync(
/**
* Generates TypeScript types for all Astro modules. This sets up a `src/env.d.ts` file for type inferencing,
* and defines the `astro:content` module for the Content Collections API.
*
* @experimental The JavaScript API is experimental
*/
export default async function sync(
inlineConfig: AstroInlineConfig,
options?: SyncOptions
): Promise<ProcessExit> {
Expand All @@ -48,7 +53,7 @@ export async function sync(
command: 'build',
});

return await syncInternal(settings, { logging, fs: options?.fs });
return await syncInternal(settings, { ...options, logging });
}

/**
Expand Down
7 changes: 3 additions & 4 deletions packages/astro/test/test-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,14 @@ import path from 'node:path';
import { fileURLToPath } from 'node:url';
import stripAnsi from 'strip-ansi';
import { check } from '../dist/cli/check/index.js';
import { dev, preview } from '../dist/core/index.js';
import build from '../dist/core/build/index.js';
import sync from '../dist/core/sync/index.js';
import { RESOLVED_SPLIT_MODULE_ID } from '../dist/core/build/plugins/plugin-ssr.js';
import { getVirtualModulePageNameFromPath } from '../dist/core/build/plugins/util.js';
import { makeSplitEntryPointFileName } from '../dist/core/build/static-build.js';
import { mergeConfig, resolveConfig } from '../dist/core/config/index.js';
import dev from '../dist/core/dev/index.js';
import { nodeLogDestination } from '../dist/core/logger/node.js';
import preview from '../dist/core/preview/index.js';
import { sync } from '../dist/core/sync/index.js';

// Disable telemetry when running tests
process.env.ASTRO_TELEMETRY_DISABLED = true;
Expand Down Expand Up @@ -148,7 +147,7 @@ export async function loadFixture(inlineConfig) {
return {
build: async (extraInlineConfig = {}) => {
process.env.NODE_ENV = 'production';
return build(mergeConfig(inlineConfig, extraInlineConfig));
return build(mergeConfig(inlineConfig, extraInlineConfig), { teardownCompiler: false });
},
sync: async (extraInlineConfig = {}, opts) => {
return sync(mergeConfig(inlineConfig, extraInlineConfig), opts);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { expect } from 'chai';
import { fileURLToPath } from 'node:url';
import { sync as _sync } from '../../../dist/core/sync/index.js';
import _sync from '../../../dist/core/sync/index.js';
import { createFsWithFallback } from '../test-utils.js';

const root = new URL('../../fixtures/content-mixed-errors/', import.meta.url);
Expand Down
2 changes: 1 addition & 1 deletion packages/astro/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"extends": "../../tsconfig.base.json",
"include": ["src", "index.d.ts"],
"include": ["src"],
"compilerOptions": {
"allowJs": true,
"declarationDir": "./dist",
Expand Down