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. -};