Add framework-specific prompt and setup to create-astro (#620)

* feat(create-astro): add framework-specific prompt and setup

* chore: normalize example `astro.config.mjs` files
This commit is contained in:
Nate Moore 2021-07-07 14:52:44 -05:00 committed by GitHub
parent 42a1fd7c16
commit 5d5d67c8f9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 394 additions and 23 deletions

View file

@ -0,0 +1,5 @@
---
'create-astro': minor
---
Update `create-astro` to handle framework-specific logic based on user preference

View file

@ -1,14 +1,17 @@
export default {
// projectRoot: '.', // Where to resolve all URLs relative to. Useful if you have a monorepo project.
// pages: './src/pages', // Path to Astro components, pages, and data
// pages: './src/pages', // Path to Astro components, pages, and data
// dist: './dist', // When running `astro build`, path to final static output
// public: './public', // A folder of static files Astro will copy to the root. Useful for favicons, images, and other files that dont need processing.
buildOptions: {
site: 'http://example.com', // Your public domain, e.g.: https://my-site.dev/. Used to generate sitemaps and canonical URLs.
// sitemap: true, // Generate sitemap (set to "false" to disable)
// site: 'http://example.com', // Your public domain, e.g.: https://my-site.dev/. Used to generate sitemaps and canonical URLs.
sitemap: true, // Generate sitemap (set to "false" to disable)
},
devOptions: {
// port: 3000, // The port to run the dev server on.
// tailwindConfig: '', // Path to tailwind.config.js if used, e.g. './tailwind.config.js'
},
renderers: [
'@astrojs/renderer-preact'
]
};

View file

@ -1,5 +1,17 @@
export default {
// projectRoot: '.', // Where to resolve all URLs relative to. Useful if you have a monorepo project.
// pages: './src/pages', // Path to Astro components, pages, and data
// dist: './dist', // When running `astro build`, path to final static output
// public: './public', // A folder of static files Astro will copy to the root. Useful for favicons, images, and other files that dont need processing.
buildOptions: {
sitemap: true,
// site: 'http://example.com', // Your public domain, e.g.: https://my-site.dev/. Used to generate sitemaps and canonical URLs.
sitemap: true, // Generate sitemap (set to "false" to disable)
},
}
devOptions: {
// port: 3000, // The port to run the dev server on.
// tailwindConfig: '', // Path to tailwind.config.js if used, e.g. './tailwind.config.js'
},
renderers: [
'@astrojs/renderer-preact'
]
};

View file

@ -1,4 +1,16 @@
export default {
// projectRoot: '.', // Where to resolve all URLs relative to. Useful if you have a monorepo project.
// pages: './src/pages', // Path to Astro components, pages, and data
// dist: './dist', // When running `astro build`, path to final static output
// public: './public', // A folder of static files Astro will copy to the root. Useful for favicons, images, and other files that dont need processing.
buildOptions: {
// site: 'http://example.com', // Your public domain, e.g.: https://my-site.dev/. Used to generate sitemaps and canonical URLs.
// sitemap: true, // Generate sitemap (set to "false" to disable)
},
devOptions: {
// port: 3000, // The port to run the dev server on.
// tailwindConfig: '', // Path to tailwind.config.js if used, e.g. './tailwind.config.js'
},
renderers: [
'@astrojs/renderer-preact'
]

View file

@ -0,0 +1,20 @@
export default {
// projectRoot: '.', // Where to resolve all URLs relative to. Useful if you have a monorepo project.
// pages: './src/pages', // Path to Astro components, pages, and data
// dist: './dist', // When running `astro build`, path to final static output
// public: './public', // A folder of static files Astro will copy to the root. Useful for favicons, images, and other files that dont need processing.
buildOptions: {
// site: 'http://example.com', // Your public domain, e.g.: https://my-site.dev/. Used to generate sitemaps and canonical URLs.
// sitemap: true, // Generate sitemap (set to "false" to disable)
},
devOptions: {
// port: 3000, // The port to run the dev server on.
// tailwindConfig: '', // Path to tailwind.config.js if used, e.g. './tailwind.config.js'
},
renderers: [
'@astrojs/renderer-preact',
'@astrojs/renderer-react',
'@astrojs/renderer-svelte',
'@astrojs/renderer-vue',
]
};

View file

@ -0,0 +1,17 @@
export default {
// projectRoot: '.', // Where to resolve all URLs relative to. Useful if you have a monorepo project.
// pages: './src/pages', // Path to Astro components, pages, and data
// dist: './dist', // When running `astro build`, path to final static output
// public: './public', // A folder of static files Astro will copy to the root. Useful for favicons, images, and other files that dont need processing.
buildOptions: {
// site: 'http://example.com', // Your public domain, e.g.: https://my-site.dev/. Used to generate sitemaps and canonical URLs.
// sitemap: true, // Generate sitemap (set to "false" to disable)
},
devOptions: {
// port: 3000, // The port to run the dev server on.
// tailwindConfig: '', // Path to tailwind.config.js if used, e.g. './tailwind.config.js'
},
renderers: [
'@astrojs/renderer-preact'
]
};

View file

@ -0,0 +1,17 @@
export default {
// projectRoot: '.', // Where to resolve all URLs relative to. Useful if you have a monorepo project.
// pages: './src/pages', // Path to Astro components, pages, and data
// dist: './dist', // When running `astro build`, path to final static output
// public: './public', // A folder of static files Astro will copy to the root. Useful for favicons, images, and other files that dont need processing.
buildOptions: {
// site: 'http://example.com', // Your public domain, e.g.: https://my-site.dev/. Used to generate sitemaps and canonical URLs.
// sitemap: true, // Generate sitemap (set to "false" to disable)
},
devOptions: {
// port: 3000, // The port to run the dev server on.
// tailwindConfig: '', // Path to tailwind.config.js if used, e.g. './tailwind.config.js'
},
renderers: [
'@astrojs/renderer-react'
]
};

View file

@ -0,0 +1,17 @@
export default {
// projectRoot: '.', // Where to resolve all URLs relative to. Useful if you have a monorepo project.
// pages: './src/pages', // Path to Astro components, pages, and data
// dist: './dist', // When running `astro build`, path to final static output
// public: './public', // A folder of static files Astro will copy to the root. Useful for favicons, images, and other files that dont need processing.
buildOptions: {
// site: 'http://example.com', // Your public domain, e.g.: https://my-site.dev/. Used to generate sitemaps and canonical URLs.
// sitemap: true, // Generate sitemap (set to "false" to disable)
},
devOptions: {
// port: 3000, // The port to run the dev server on.
// tailwindConfig: '', // Path to tailwind.config.js if used, e.g. './tailwind.config.js'
},
renderers: [
'@astrojs/renderer-svelte'
]
};

View file

@ -0,0 +1,17 @@
export default {
// projectRoot: '.', // Where to resolve all URLs relative to. Useful if you have a monorepo project.
// pages: './src/pages', // Path to Astro components, pages, and data
// dist: './dist', // When running `astro build`, path to final static output
// public: './public', // A folder of static files Astro will copy to the root. Useful for favicons, images, and other files that dont need processing.
buildOptions: {
// site: 'http://example.com', // Your public domain, e.g.: https://my-site.dev/. Used to generate sitemaps and canonical URLs.
// sitemap: true, // Generate sitemap (set to "false" to disable)
},
devOptions: {
// port: 3000, // The port to run the dev server on.
// tailwindConfig: '', // Path to tailwind.config.js if used, e.g. './tailwind.config.js'
},
renderers: [
'@astrojs/renderer-vue'
]
};

View file

@ -1,4 +1,16 @@
export default {
// projectRoot: '.', // Where to resolve all URLs relative to. Useful if you have a monorepo project.
// pages: './src/pages', // Path to Astro components, pages, and data
// dist: './dist', // When running `astro build`, path to final static output
// public: './public', // A folder of static files Astro will copy to the root. Useful for favicons, images, and other files that dont need processing.
buildOptions: {
// site: 'http://example.com', // Your public domain, e.g.: https://my-site.dev/. Used to generate sitemaps and canonical URLs.
// sitemap: true, // Generate sitemap (set to "false" to disable)
},
devOptions: {
// port: 3000, // The port to run the dev server on.
// tailwindConfig: '', // Path to tailwind.config.js if used, e.g. './tailwind.config.js'
},
renderers: [
'@astrojs/renderer-preact'
]

View file

@ -1,10 +1,10 @@
export default {
// projectRoot: '.', // Where to resolve all URLs relative to. Useful if you have a monorepo project.
// pages: './src/pages', // Path to Astro components, pages, and data
// pages: './src/pages', // Path to Astro components, pages, and data
// dist: './dist', // When running `astro build`, path to final static output
// public: './public', // A folder of static files Astro will copy to the root. Useful for favicons, images, and other files that dont need processing.
buildOptions: {
// site: 'http://example.com', // Your public domain, e.g.: https://my-site.dev/. Used to generate sitemaps and canonical URLs.
// site: 'http://example.com', // Your public domain, e.g.: https://my-site.dev/. Used to generate sitemaps and canonical URLs.
// sitemap: true, // Generate sitemap (set to "false" to disable)
},
devOptions: {

View file

@ -36,3 +36,17 @@ h2 {
font-weight: 500;
font-size: clamp(1.5rem, 1rem + 1.25vw, 2rem);
}
.counter {
display: grid;
grid-auto-flow: column;
gap: 1em;
font-size: 2rem;
justify-content: center;
padding: 2rem 1rem;
}
.counter > pre {
text-align: center;
min-width: 3ch;
}

View file

@ -1,14 +1,20 @@
export default {
// projectRoot: '.', // Where to resolve all URLs relative to. Useful if you have a monorepo project.
// pages: './src/pages', // Path to Astro components, pages, and data
// pages: './src/pages', // Path to Astro components, pages, and data
// dist: './dist', // When running `astro build`, path to final static output
// public: './public', // A folder of static files Astro will copy to the root. Useful for favicons, images, and other files that dont need processing.
buildOptions: {
// site: 'http://example.com', // Your public domain, e.g.: https://my-site.dev/. Used to generate sitemaps and canonical URLs.
// site: 'http://example.com', // Your public domain, e.g.: https://my-site.dev/. Used to generate sitemaps and canonical URLs.
// sitemap: true, // Generate sitemap (set to "false" to disable)
},
devOptions: {
// port: 3000, // The port to run the dev server on.
// tailwindConfig: '', // Path to tailwind.config.js if used, e.g. './tailwind.config.js'
tailwindConfig: './tailwind.config.js', // Path to tailwind.config.js if used, e.g. './tailwind.config.js'
},
renderers: [
'@astrojs/renderer-preact',
'@astrojs/renderer-react',
'@astrojs/renderer-svelte',
'@astrojs/renderer-vue',
]
};

View file

@ -1,5 +1,17 @@
export default {
devOptions: {
tailwindConfig: './tailwind.config.js',
// projectRoot: '.', // Where to resolve all URLs relative to. Useful if you have a monorepo project.
// pages: './src/pages', // Path to Astro components, pages, and data
// dist: './dist', // When running `astro build`, path to final static output
// public: './public', // A folder of static files Astro will copy to the root. Useful for favicons, images, and other files that dont need processing.
buildOptions: {
// site: 'http://example.com', // Your public domain, e.g.: https://my-site.dev/. Used to generate sitemaps and canonical URLs.
sitemap: true, // Generate sitemap (set to "false" to disable)
},
devOptions: {
// port: 3000, // The port to run the dev server on.
tailwindConfig: './tailwind.config.js', // Path to tailwind.config.js if used, e.g. './tailwind.config.js'
},
renderers: [
'@astrojs/renderer-preact'
]
};

View file

@ -23,6 +23,7 @@
"create-astro.js"
],
"dependencies": {
"node-fetch": "^2.6.1",
"@types/degit": "^2.8.2",
"@types/prompts": "^2.0.12",
"degit": "^2.8.4",

View file

@ -0,0 +1,19 @@
export const createConfig = ({ renderers }: { renderers: string[] }) => {
return [`export default {
// projectRoot: '.', // Where to resolve all URLs relative to. Useful if you have a monorepo project.
// pages: './src/pages', // Path to Astro components, pages, and data
// dist: './dist', // When running \`astro build\`, path to final static output
// public: './public', // A folder of static files Astro will copy to the root. Useful for favicons, images, and other files that dont need processing.
buildOptions: {
// site: 'http://example.com', // Your public domain, e.g.: https://my-site.dev/. Used to generate sitemaps and canonical URLs.
sitemap: true, // Generate sitemap (set to "false" to disable)
},
devOptions: {
// port: 3000, // The port to run the dev server on.
// tailwindConfig: '', // Path to tailwind.config.js if used, e.g. './tailwind.config.js'
},`,
` renderers: ${JSON.stringify(renderers, undefined, 2).split('\n').map((ln, i) => i !== 0 ? ` ${ln}` : ln).join('\n')},`,
`};
`].join('\n')
}

View file

@ -0,0 +1,109 @@
export const COUNTER_COMPONENTS = {
'@astrojs/renderer-preact': {
filename: `src/components/PreactCounter.jsx`,
content: `import { h } from 'preact';
import { useState } from 'preact/hooks';
export default function PreactCounter({ children }) {
const [count, setCount] = useState(0);
const add = () => setCount((i) => i + 1);
const subtract = () => setCount((i) => i - 1);
return (
<div id="preact" class="counter">
<button onClick={subtract}>-</button>
<pre>{count}</pre>
<button onClick={add}>+</button>
</div>
);
}
`
},
'@astrojs/renderer-react': {
filename: `src/components/ReactCounter.jsx`,
content: `import React, { useState } from 'react';
export default function ReactCounter({ children }) {
const [count, setCount] = useState(0);
const add = () => setCount((i) => i + 1);
const subtract = () => setCount((i) => i - 1);
return (
<div id="react" className="counter">
<button onClick={subtract}>-</button>
<pre>{count}</pre>
<button onClick={add}>+</button>
</div>
);
}
`
},
'@astrojs/renderer-svelte': {
filename: `src/components/SvelteCounter.svelte`,
content: `<script>
let count = 0;
function add() {
count += 1;
}
function subtract() {
count -= 1;
}
</script>
<div id="svelte" class="counter">
<button on:click={subtract}>-</button>
<pre>{ count }</pre>
<button on:click={add}>+</button>
</div>
`
},
'@astrojs/renderer-vue': {
filename: `src/components/VueCounter.vue`,
content: `<template>
<div id="vue" class="counter">
<button @click="subtract()">-</button>
<pre>{{ count }}</pre>
<button @click="add()">+</button>
</div>
</template>
<script>
import { ref } from 'vue';
export default {
setup() {
const count = ref(0)
const add = () => count.value = count.value + 1;
const subtract = () => count.value = count.value - 1;
return {
count,
add,
subtract
}
}
}
</script>
`
}
};
export const FRAMEWORKS = [
{
title: 'Preact',
value: '@astrojs/renderer-preact',
},
{
title: 'React',
value: '@astrojs/renderer-react',
},
{
title: 'Svelte',
value: '@astrojs/renderer-svelte',
},
{
title: 'Vue',
value: '@astrojs/renderer-vue',
}
];

View file

@ -1,10 +1,13 @@
import fs from 'fs';
import path from 'path';
import { bold, cyan, gray, green, red } from 'kleur/colors';
import fetch from 'node-fetch';
import prompts from 'prompts';
import degit from 'degit';
import yargs from 'yargs-parser';
import { FRAMEWORKS, COUNTER_COMPONENTS } from './frameworks.js';
import { TEMPLATES } from './templates.js';
import { createConfig } from './config.js';
const args = yargs(process.argv);
prompts.override(args);
@ -19,7 +22,7 @@ export function mkdirp(dir: string) {
const { version } = JSON.parse(fs.readFileSync(new URL('../package.json', import.meta.url), 'utf-8'));
const POSTPROCESS_FILES = ['package.json']; // some files need processing after copying.
const POSTPROCESS_FILES = ['package.json', 'astro.config.mjs', 'CHANGELOG.md']; // some files need processing after copying.
export async function main() {
console.log('\n' + bold('Welcome to Astro!') + gray(` (create-astro v${version})`));
@ -40,6 +43,9 @@ export async function main() {
if (!response.forceOverwrite) {
process.exit(1);
}
await fs.promises.rm(cwd, { recursive: true });
mkdirp(cwd);
}
} else {
mkdirp(cwd);
@ -54,6 +60,10 @@ export async function main() {
},
]);
if (!options.template) {
process.exit(1);
}
const hash = args.commit ? `#${args.commit}` : '';
const templateTarget = options.template.includes('/') ?
@ -66,6 +76,25 @@ export async function main() {
verbose: false,
});
const selectedTemplate = TEMPLATES.find(template => template.value === options.template);
let renderers: string[] = [];
if (selectedTemplate?.renderers === true) {
const result = /** @type {import('./types/internal').Options} */ await prompts([
{
type: 'multiselect',
name: 'renderers',
message: 'Which frameworks would you like to use?',
choices: FRAMEWORKS,
},
]);
renderers = result.renderers;
} else if (selectedTemplate?.renderers && Array.isArray(selectedTemplate.renderers)) {
renderers = selectedTemplate.renderers;
const titles = renderers.map(renderer => FRAMEWORKS.find(item => item.value === renderer)?.title).join(', ');
console.log(green(``) + bold(` Using template's default renderers`) + gray(' ') + titles);
}
// Copy
try {
// emitter.on('info', info => { console.log(info.message) });
@ -78,20 +107,65 @@ export async function main() {
}
// Post-process in parallel
await Promise.all(
POSTPROCESS_FILES.map(async (file) => {
const fileLoc = path.join(cwd, file);
await Promise.all(POSTPROCESS_FILES.map(async (file) => {
const fileLoc = path.resolve(path.join(cwd, file));
switch (file) {
case 'package.json': {
const packageJSON = JSON.parse(await fs.promises.readFile(fileLoc, 'utf8'));
delete packageJSON.snowpack; // delete snowpack config only needed in monorepo (can mess up projects)
await fs.promises.writeFile(fileLoc, JSON.stringify(packageJSON, undefined, 2));
switch (file) {
case 'CHANGELOG.md': {
if (fs.existsSync(fileLoc)) {
await fs.promises.rm(fileLoc);
}
break;
}
case 'astro.config.mjs': {
if (selectedTemplate?.renderers !== true) {
break;
}
await fs.promises.writeFile(fileLoc, createConfig({ renderers }));
break;
}
})
);
case 'package.json': {
const packageJSON = JSON.parse(await fs.promises.readFile(fileLoc, 'utf8'));
delete packageJSON.snowpack; // delete snowpack config only needed in monorepo (can mess up projects)
// Fetch latest versions of selected renderers
const rendererEntries = await Promise.all(['astro', ...renderers].map((renderer: string) => fetch(`https://registry.npmjs.org/${renderer}/latest`).then((res: any) => res.json()).then((res: any) => [renderer, `^${res['version']}`]))) as any;
packageJSON.devDependencies = { ...packageJSON.devDependencies ?? {}, ...Object.fromEntries(rendererEntries) }
await fs.promises.writeFile(fileLoc, JSON.stringify(packageJSON, undefined, 2));
break;
}
}
}));
// Inject framework components into starter template
if (selectedTemplate?.value === 'starter') {
let importStatements: string[] = [];
let components: string[] = [];
await Promise.all(renderers.map(async renderer => {
const component = COUNTER_COMPONENTS[renderer as keyof typeof COUNTER_COMPONENTS];
const componentName = path.basename(component.filename, path.extname(component.filename));
const absFileLoc = path.resolve(cwd, component.filename);
importStatements.push(`import ${componentName} from '${component.filename.replace(/^src/, '..')}';`);
components.push(`<${componentName}:visible />`);
await fs.promises.writeFile(absFileLoc, component.content);
}));
const pageFileLoc = path.resolve(path.join(cwd, 'src', 'pages', 'index.astro'));
const content = (await fs.promises.readFile(pageFileLoc)).toString();
const lines = content.split('\n');
const indent = ' ';
const doc = `\n<!--
- Use imported Framework Components directly in your markup!
-
- Note: by default, components are NOT interactive on the client.
- The \`:visible\` directive tells Astro to make it interactive.
-
- See https://github.com/snowpackjs/astro/blob/main/docs/core-concepts/component-hydration.md
-->
`;
lines.splice(41, 0, importStatements.length > 0 ? doc.split('\n').map(ln => `${indent}${ln}`).join('\n') : '', ...components.map(ln => `${indent}${ln}`));
lines.splice(3, 0, importStatements.length > 0 ? `// Framework Component Imports` : '', ...importStatements);
await fs.promises.writeFile(pageFileLoc, lines.join('\n'))
}
console.log(bold(green('✔') + ' Done!'));

View file

@ -2,17 +2,21 @@ export const TEMPLATES = [
{
title: 'Starter Kit (Generic)',
value: 'starter',
renderers: true,
},
{
title: 'Blog',
value: 'blog',
renderers: ['@astrojs/renderer-preact']
},
{
title: 'Documentation',
value: 'docs',
renderers: ['@astrojs/renderer-preact']
},
{
title: 'Portfolio',
value: 'portfolio',
renderers: ['@astrojs/renderer-preact']
},
];