diff --git a/README.md b/README.md
index a6ac9dda0..2621bc195 100644
--- a/README.md
+++ b/README.md
@@ -28,6 +28,28 @@ Then run:
npm run dev
```
+### ⚙️ Configuration
+
+To configure Astro, add a `astro.config.mjs` file in the root of your project. All of the options can be omitted. Here are the defaults:
+
+```js
+export default {
+ /** Where to resolve all URLs relative to. Useful if you have a monorepo project. */
+ projectRoot: '.',
+ /** Path to Astro components, pages, and data */
+ astroRoot: './astro',
+ /** When running `astro build`, path to final static output */
+ dist: './_site',
+ /** A folder of static files Astro will copy to the root. Useful for favicons, images, and other files that don‘t need processing. */
+ public: './public',
+ /** Extension-specific handlings */
+ extensions: {
+ /** Set this to "preact" or "react" to determine what *.jsx files should load */
+ '.jsx': 'react',
+ },
+};
+```
+
### 💧 Partial Hydration
By default, Astro outputs zero client-side JS. If you'd like to include an interactive component in the client output, you may use any of the following techniques.
@@ -50,7 +72,7 @@ If you‘ve used [Svelte][svelte]’s styles before, Astro works almost the same
I’m a scoped style
```
-#### Sass
+#### 👓 Sass
Astro also supports [Sass][sass] out-of-the-box; no configuration needed:
@@ -71,11 +93,11 @@ Supports:
- `lang="scss"`: load as the `.scss` extension
- `lang="sass"`: load as the `.sass` extension (no brackets; indent-style)
-#### Autoprefixer
+#### 🦊 Autoprefixer
We also automatically add browser prefixes using [Autoprefixer][autoprefixer]. By default, Astro loads the default values, but you may also specify your own by placing a [Browserslist][browserslist] file in your project root.
-#### Tailwind
+#### 🍃 Tailwind
Astro can be configured to use [Tailwind][tailwind] easily! Install the dependencies:
diff --git a/src/cli.ts b/src/cli.ts
index e0f0c0dc3..71c9691d7 100644
--- a/src/cli.ts
+++ b/src/cli.ts
@@ -58,14 +58,13 @@ async function printVersion() {
/** Handle `astro run` command */
async function runCommand(rawRoot: string, cmd: (a: AstroConfig) => Promise) {
- const astroConfig = await loadConfig(rawRoot);
- if (typeof astroConfig === 'undefined') {
- console.error(colors.red(' An astro.config.mjs file is required.\n'));
- printHelp();
+ try {
+ const astroConfig = await loadConfig(rawRoot);
+ return cmd(astroConfig);
+ } catch (err) {
+ console.error(colors.red(err.toString() || err));
process.exit(1);
}
-
- return cmd(astroConfig);
}
const cmdMap = new Map([
diff --git a/src/config.ts b/src/config.ts
index 72583df5a..fe4549929 100644
--- a/src/config.ts
+++ b/src/config.ts
@@ -2,25 +2,70 @@ import type { AstroConfig } from './@types/astro';
import { join as pathJoin, resolve as pathResolve } from 'path';
import { existsSync } from 'fs';
+/** Type util */
+const type = (thing: any): string => (Array.isArray(thing) ? 'Array' : typeof thing);
+
+/** Throws error if a user provided an invalid config. Manually-implemented to avoid a heavy validation library. */
+function validateConfig(config: any): void {
+ // basic
+ if (config === undefined || config === null) throw new Error(`[astro config] Config empty!`);
+ if (typeof config !== 'object') throw new Error(`[astro config] Expected object, received ${typeof config}`);
+
+ // strings
+ for (const key of ['projectRoot', 'astroRoot', 'dist', 'public']) {
+ if (config[key] && typeof config[key] !== 'string') {
+ throw new Error(`[astro config] ${key}: ${JSON.stringify(config[key])}\n Expected string, received ${type(config[key])}.`);
+ }
+ }
+}
+
+/** Set default config values */
+function configDefaults(userConfig?: any): any {
+ const config: any = { ...(userConfig || {}) };
+
+ if (!config.projectRoot) config.projectRoot = '.';
+ if (!config.astroRoot) config.astroRoot = './astro';
+ if (!config.dist) config.dist = './_site';
+ if (!config.public) config.public = './public';
+
+ return config;
+}
+
+/** Turn raw config values into normalized values */
+function normalizeConfig(userConfig: any, root: string): AstroConfig {
+ const config: any = { ...(userConfig || {}) };
+
+ config.projectRoot = new URL(config.projectRoot + '/', root);
+ config.astroRoot = new URL(config.astroRoot + '/', root);
+ config.public = new URL(config.public + '/', root);
+
+ return config as AstroConfig;
+}
+
/** Attempt to load an `astro.config.mjs` file */
-export async function loadConfig(rawRoot: string | undefined): Promise {
+export async function loadConfig(rawRoot: string | undefined): Promise {
if (typeof rawRoot === 'undefined') {
rawRoot = process.cwd();
}
+ let config: any;
+
const root = pathResolve(rawRoot);
const fileProtocolRoot = `file://${root}/`;
const astroConfigPath = pathJoin(root, 'astro.config.mjs');
- if (!existsSync(astroConfigPath)) {
- return undefined;
+ // load
+ if (existsSync(astroConfigPath)) {
+ config = configDefaults((await import(astroConfigPath)).default);
+ } else {
+ config = configDefaults();
}
- const astroConfig: AstroConfig = (await import(astroConfigPath)).default;
- astroConfig.projectRoot = new URL(astroConfig.projectRoot + '/', fileProtocolRoot);
- astroConfig.astroRoot = new URL(astroConfig.astroRoot + '/', fileProtocolRoot);
+ // validate
+ validateConfig(config);
- const publicFolder = astroConfig.public ? astroConfig.public + '/' : './public/';
- astroConfig.public = new URL(publicFolder, fileProtocolRoot);
- return astroConfig;
+ // normalize
+ config = normalizeConfig(config, fileProtocolRoot);
+
+ return config as AstroConfig;
}
diff --git a/test/fixtures/astro-basic/astro.config.mjs b/test/fixtures/astro-basic/astro.config.mjs
deleted file mode 100644
index c7cbdb435..000000000
--- a/test/fixtures/astro-basic/astro.config.mjs
+++ /dev/null
@@ -1,5 +0,0 @@
-export default {
- projectRoot: '.',
- astroRoot: './astro',
- dist: './_site',
-};
diff --git a/test/fixtures/astro-doctype/astro.config.mjs b/test/fixtures/astro-doctype/astro.config.mjs
deleted file mode 100644
index c7cbdb435..000000000
--- a/test/fixtures/astro-doctype/astro.config.mjs
+++ /dev/null
@@ -1,5 +0,0 @@
-export default {
- projectRoot: '.',
- astroRoot: './astro',
- dist: './_site',
-};
diff --git a/test/fixtures/astro-fallback/astro.config.mjs b/test/fixtures/astro-fallback/astro.config.mjs
index ac85e54d8..f50751cfd 100644
--- a/test/fixtures/astro-fallback/astro.config.mjs
+++ b/test/fixtures/astro-fallback/astro.config.mjs
@@ -1,8 +1,5 @@
export default {
- projectRoot: '.',
- astroRoot: './astro',
- dist: './_site',
extensions: {
- '.jsx': 'preact'
- }
+ '.jsx': 'preact',
+ },
};
diff --git a/test/fixtures/astro-markdown/astro.config.mjs b/test/fixtures/astro-markdown/astro.config.mjs
index a5bcba200..f50751cfd 100644
--- a/test/fixtures/astro-markdown/astro.config.mjs
+++ b/test/fixtures/astro-markdown/astro.config.mjs
@@ -1,7 +1,4 @@
export default {
- projectRoot: '.',
- astroRoot: './astro',
- dist: './_site',
extensions: {
'.jsx': 'preact',
},
diff --git a/test/fixtures/astro-request/astro.config.mjs b/test/fixtures/astro-request/astro.config.mjs
deleted file mode 100644
index 48c4c3aad..000000000
--- a/test/fixtures/astro-request/astro.config.mjs
+++ /dev/null
@@ -1,5 +0,0 @@
-export default {
- projectRoot: '.',
- astroRoot: './astro',
- dist: './_site'
-};
diff --git a/test/fixtures/astro-styles-ssr/astro.config.mjs b/test/fixtures/astro-styles-ssr/astro.config.mjs
deleted file mode 100644
index c7cbdb435..000000000
--- a/test/fixtures/astro-styles-ssr/astro.config.mjs
+++ /dev/null
@@ -1,5 +0,0 @@
-export default {
- projectRoot: '.',
- astroRoot: './astro',
- dist: './_site',
-};
diff --git a/test/fixtures/react-component/astro.config.mjs b/test/fixtures/react-component/astro.config.mjs
deleted file mode 100644
index 30955cef0..000000000
--- a/test/fixtures/react-component/astro.config.mjs
+++ /dev/null
@@ -1,6 +0,0 @@
-export default {
- projectRoot: '.',
- astroRoot: './astro',
- dist: './_site',
- // No extensions needed, React is the default.
-};