diff --git a/.changeset/nasty-tomatoes-listen.md b/.changeset/nasty-tomatoes-listen.md new file mode 100644 index 000000000..77548971f --- /dev/null +++ b/.changeset/nasty-tomatoes-listen.md @@ -0,0 +1,5 @@ +--- +'create-astro': minor +--- + +Update `create-astro` to handle framework-specific logic based on user preference diff --git a/examples/blog-multiple-authors/astro.config.mjs b/examples/blog-multiple-authors/astro.config.mjs index d72db6491..e6e926d7b 100644 --- a/examples/blog-multiple-authors/astro.config.mjs +++ b/examples/blog-multiple-authors/astro.config.mjs @@ -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 don’t 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' + ] }; diff --git a/examples/blog/astro.config.mjs b/examples/blog/astro.config.mjs index ef2cd38aa..e6e926d7b 100644 --- a/examples/blog/astro.config.mjs +++ b/examples/blog/astro.config.mjs @@ -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 don’t 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' + ] +}; diff --git a/examples/docs/astro.config.mjs b/examples/docs/astro.config.mjs index d97e2804d..6fd6bb47e 100644 --- a/examples/docs/astro.config.mjs +++ b/examples/docs/astro.config.mjs @@ -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 don’t 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' ] diff --git a/examples/framework-multiple/astro.config.mjs b/examples/framework-multiple/astro.config.mjs new file mode 100644 index 000000000..89abd7323 --- /dev/null +++ b/examples/framework-multiple/astro.config.mjs @@ -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 don’t 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', + ] +}; diff --git a/examples/framework-preact/astro.config.mjs b/examples/framework-preact/astro.config.mjs new file mode 100644 index 000000000..2d1454d51 --- /dev/null +++ b/examples/framework-preact/astro.config.mjs @@ -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 don’t 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' + ] +}; diff --git a/examples/framework-react/astro.config.mjs b/examples/framework-react/astro.config.mjs new file mode 100644 index 000000000..f0e022923 --- /dev/null +++ b/examples/framework-react/astro.config.mjs @@ -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 don’t 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' + ] +}; diff --git a/examples/framework-svelte/astro.config.mjs b/examples/framework-svelte/astro.config.mjs new file mode 100644 index 000000000..59ddc78f0 --- /dev/null +++ b/examples/framework-svelte/astro.config.mjs @@ -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 don’t 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' + ] +}; diff --git a/examples/framework-vue/astro.config.mjs b/examples/framework-vue/astro.config.mjs new file mode 100644 index 000000000..1fec0a4dd --- /dev/null +++ b/examples/framework-vue/astro.config.mjs @@ -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 don’t 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' + ] +}; diff --git a/examples/portfolio/astro.config.mjs b/examples/portfolio/astro.config.mjs index d97e2804d..2d1454d51 100644 --- a/examples/portfolio/astro.config.mjs +++ b/examples/portfolio/astro.config.mjs @@ -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 don’t 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' ] diff --git a/examples/starter/astro.config.mjs b/examples/starter/astro.config.mjs index e16ed1327..706dce85b 100644 --- a/examples/starter/astro.config.mjs +++ b/examples/starter/astro.config.mjs @@ -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 don’t 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: { diff --git a/examples/starter/public/style/home.css b/examples/starter/public/style/home.css index c4271a845..70741a8da 100644 --- a/examples/starter/public/style/home.css +++ b/examples/starter/public/style/home.css @@ -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; +} diff --git a/examples/with-nanostores/astro.config.mjs b/examples/with-nanostores/astro.config.mjs index e16ed1327..ce0e15031 100644 --- a/examples/with-nanostores/astro.config.mjs +++ b/examples/with-nanostores/astro.config.mjs @@ -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 don’t 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', + ] }; diff --git a/examples/with-tailwindcss/astro.config.mjs b/examples/with-tailwindcss/astro.config.mjs index 7e6aaa3fd..b69c40cd4 100644 --- a/examples/with-tailwindcss/astro.config.mjs +++ b/examples/with-tailwindcss/astro.config.mjs @@ -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 don’t 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' + ] }; diff --git a/packages/create-astro/package.json b/packages/create-astro/package.json index 9b50f4039..f9ed461f6 100644 --- a/packages/create-astro/package.json +++ b/packages/create-astro/package.json @@ -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", diff --git a/packages/create-astro/src/config.ts b/packages/create-astro/src/config.ts new file mode 100644 index 000000000..6a2b3cb64 --- /dev/null +++ b/packages/create-astro/src/config.ts @@ -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 don’t 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') +} + diff --git a/packages/create-astro/src/frameworks.ts b/packages/create-astro/src/frameworks.ts new file mode 100644 index 000000000..f7db2e0f6 --- /dev/null +++ b/packages/create-astro/src/frameworks.ts @@ -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 ( +
+ +
{count}
+ +
+ ); +} +` + }, + '@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 ( +
+ +
{count}
+ +
+ ); +} +` + }, + '@astrojs/renderer-svelte': { + filename: `src/components/SvelteCounter.svelte`, + content: ` + +
+ +
{ count }
+ +
+` + }, + '@astrojs/renderer-vue': { + filename: `src/components/VueCounter.vue`, + content: ` + + +` + } +}; + +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', + } +]; diff --git a/packages/create-astro/src/index.ts b/packages/create-astro/src/index.ts index e3487e8d4..dcb87086e 100644 --- a/packages/create-astro/src/index.ts +++ b/packages/create-astro/src/index.ts @@ -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 +`; + 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!')); diff --git a/packages/create-astro/src/templates.ts b/packages/create-astro/src/templates.ts index 02daaef32..6852aafb0 100644 --- a/packages/create-astro/src/templates.ts +++ b/packages/create-astro/src/templates.ts @@ -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'] }, ];