diff --git a/.eslintrc.cjs b/.eslintrc.cjs index c10ca83a4..d01fd35b3 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -12,10 +12,10 @@ module.exports = { '@typescript-eslint/no-unused-vars': 'off', '@typescript-eslint/no-use-before-define': 'off', '@typescript-eslint/no-var-requires': 'off', + 'multiline-comment-style': ['warn', 'starred-block'], 'no-console': 'warn', 'no-shadow': 'error', 'prefer-const': 'off', - 'prefer-rest-params': 'off', - 'require-jsdoc': 'off', + // 'require-jsdoc': 'error', // re-enable this to enforce JSDoc for all functions }, }; diff --git a/packages/astro/src/@types/astro-core.ts b/packages/astro/src/@types/astro-core.ts index 575a4ece4..826aadbbd 100644 --- a/packages/astro/src/@types/astro-core.ts +++ b/packages/astro/src/@types/astro-core.ts @@ -63,7 +63,8 @@ export interface AstroUserConfig { buildOptions?: { /** Your public domain, e.g.: https://my-site.dev/. Used to generate sitemaps and canonical URLs. */ site?: string; - /** Generate an automatically-generated sitemap for your build. + /** + * Generate an automatically-generated sitemap for your build. * Default: true */ sitemap?: boolean; @@ -95,14 +96,16 @@ export interface AstroUserConfig { vite?: vite.InlineConfig; } -// NOTE(fks): We choose to keep our hand-generated AstroUserConfig interface so that -// we can add JSDoc-style documentation and link to the definition file in our repo. -// However, Zod comes with the ability to auto-generate AstroConfig from the schema -// above. If we ever get to the point where we no longer need the dedicated -// @types/config.ts file, consider replacing it with the following lines: -// -// export interface AstroUserConfig extends z.input { -// } +/* + * NOTE(fks): We choose to keep our hand-generated AstroUserConfig interface so that + * we can add JSDoc-style documentation and link to the definition file in our repo. + * However, Zod comes with the ability to auto-generate AstroConfig from the schema + * above. If we ever get to the point where we no longer need the dedicated + * @types/config.ts file, consider replacing it with the following lines: + * + * export interface AstroUserConfig extends z.input { + * } + */ export type AstroConfig = z.output; export type AsyncRendererComponentFn = (Component: any, props: any, children: string | undefined, metadata?: AstroComponentMetadata) => Promise; diff --git a/packages/astro/src/core/build/index.ts b/packages/astro/src/core/build/index.ts index 48e476ece..a36abfe02 100644 --- a/packages/astro/src/core/build/index.ts +++ b/packages/astro/src/core/build/index.ts @@ -55,7 +55,10 @@ class AstroBuilder { async build() { const timer: Record = {}; // keep track of performance timers - // 1. initialize fresh Vite instance + /* + * Setup + * Create the Vite server with production build settings + */ timer.viteStart = performance.now(); const { logging, origin } = this; const viteConfig = await createVite( @@ -73,12 +76,15 @@ class AstroBuilder { this.viteServer = viteServer; debug(logging, 'build', timerMessage('Vite started', timer.viteStart)); - // 2. get all routes + /* + * Render pages + * Convert .astro -> .html + */ timer.renderStart = performance.now(); const assets: Record = {}; // additional assets to be written const allPages: Record = {}; - // 2a. determine all possible routes first before rendering + // pre-render: determine all possible routes from dynamic pages await Promise.all( this.manifest.routes.map(async (route) => { // static route @@ -100,7 +106,7 @@ class AstroBuilder { }) ); - // 2b. after all paths have been determined, render all pages + // render: convert Astro to HTML const input: InputHTMLOptions[] = []; await Promise.all( Object.entries(allPages).map(([component, route]) => @@ -126,7 +132,10 @@ class AstroBuilder { ); debug(logging, 'build', timerMessage('All pages rendered', timer.renderStart)); - // 3. build with Vite + /* + * Production Build + * Use Vite’s build process to generate files + */ timer.buildStart = performance.now(); await vite.build({ logLevel: 'error', @@ -151,7 +160,13 @@ class AstroBuilder { }); debug(logging, 'build', timerMessage('Vite build finished', timer.buildStart)); - // 4. write assets to disk + /* + * Post-build files + * Write other files to disk Vite may not know about. + * TODO: is there a way to handle these as part of the previous step? + */ + + // RSS timer.assetsStart = performance.now(); Object.keys(assets).map((k) => { if (!assets[k]) return; @@ -162,10 +177,9 @@ class AstroBuilder { }); debug(logging, 'build', timerMessage('Additional assets copied', timer.assetsStart)); - // 5. build sitemap + // Sitemap timer.sitemapStart = performance.now(); if (this.config.buildOptions.sitemap && this.config.buildOptions.site) { - const sitemapStart = performance.now(); const sitemap = generateSitemap(input.map(({ name }) => new URL(`/${name}`, this.config.buildOptions.site).href)); const sitemapPath = new URL('./sitemap.xml', this.config.dist); await fs.promises.mkdir(new URL('./', sitemapPath), { recursive: true }); @@ -173,10 +187,11 @@ class AstroBuilder { } debug(logging, 'build', timerMessage('Sitemap built', timer.sitemapStart)); - // 6. clean up + /* + * Clean up + * Close the Vite server instance, and print stats + */ await viteServer.close(); - - // 7. log output if (logging.level && levels[logging.level] <= levels['info']) { await this.printStats({ cwd: this.config.dist, pageCount: input.length }); } diff --git a/packages/astro/src/core/config.ts b/packages/astro/src/core/config.ts index 9657d36dc..aa74473f2 100644 --- a/packages/astro/src/core/config.ts +++ b/packages/astro/src/core/config.ts @@ -72,8 +72,10 @@ export const AstroConfigSchema = z.object({ /** Turn raw config values into normalized values */ export async function validateConfig(userConfig: any, root: string): Promise { const fileProtocolRoot = pathToFileURL(root + path.sep); - // We need to extend the global schema to add transforms that are relative to root. - // This is type checked against the global schema to make sure we still match. + /* + * We need to extend the global schema to add transforms that are relative to root. + * This is type checked against the global schema to make sure we still match. + */ const AstroConfigRelativeSchema = AstroConfigSchema.extend({ projectRoot: z .string() diff --git a/packages/astro/src/core/create-vite.ts b/packages/astro/src/core/create-vite.ts index f15b150ac..45a015e5a 100644 --- a/packages/astro/src/core/create-vite.ts +++ b/packages/astro/src/core/create-vite.ts @@ -42,21 +42,21 @@ export async function createVite( await Promise.all( astroConfig.renderers.map(async (name) => { const { default: renderer } = await import(name); - // 1. prepare client-side hydration code for browser + // prepare client-side hydration code for browser if (renderer.client) { optimizedDeps.add(name + renderer.client.substr(1)); } - // 2. knownEntrypoints and polyfills need to be added to the client + // knownEntrypoints and polyfills need to be added to the client for (let dep of [...(renderer.knownEntrypoints || []), ...(renderer.polyfills || [])]) { if (dep[0] === '.') dep = name + dep.substr(1); // if local polyfill, use full path optimizedDeps.add(dep); dedupe.add(dep); // we can try and dedupe renderers by default } - // 3. let renderer inject Vite plugins + // let renderer inject Vite plugins if (renderer.vitePlugins) { plugins.push(...renderer.vitePlugins); } - // 4. mark external packages as external to Vite + // mark external packages as external to Vite if (renderer.external) { for (const dep of renderer.external) { external.add(dep); diff --git a/packages/astro/src/core/dev/index.ts b/packages/astro/src/core/dev/index.ts index ebab51bd2..25ab88ff4 100644 --- a/packages/astro/src/core/dev/index.ts +++ b/packages/astro/src/core/dev/index.ts @@ -70,18 +70,22 @@ export class AstroDevServer { /** Start dev server */ async start() { - // 1. profile startup time - const devStart = performance.now(); - - // 2. create Vite instance + /* + * Setup + * Create the Vite serer in dev mode + */ + const devStart = performance.now(); // profile startup time this.viteServer = await this.createViteServer(); - // 3. add middlewares + // middlewares this.app.use((req, res, next) => this.handleRequest(req, res, next)); this.app.use(this.viteServer.middlewares); this.app.use((req, res, next) => this.renderError(req, res, next)); - // 4. listen on port (and retry if taken) + /* + * Listen + * Start external connect server and listen on configured port + */ await new Promise((resolve, reject) => { const onError = (err: NodeJS.ErrnoException) => { if (err.code && err.code === 'EADDRINUSE') { @@ -193,9 +197,11 @@ export class AstroDevServer { this.manifest = createRouteManifest({ config: this.config }); }); viteServer.watcher.on('change', () => { - // No need to rebuild routes on file content changes. - // However, we DO want to clear the cache in case - // the change caused a getStaticPaths() return to change. + /* + * No need to rebuild routes on file content changes. + * However, we DO want to clear the cache in case + * the change caused a getStaticPaths() return to change. + */ this.routeCache = {}; }); diff --git a/packages/astro/src/core/logger.ts b/packages/astro/src/core/logger.ts index 78a84eba7..a0a04b737 100644 --- a/packages/astro/src/core/logger.ts +++ b/packages/astro/src/core/logger.ts @@ -13,8 +13,10 @@ function getLoggerLocale(): string { const defaultLocale = 'en-US'; if (process.env.LANG) { const extractedLocale = process.env.LANG.split('.')[0].replace(/_/g, '-'); - // Check if language code is atleast two characters long (ie. en, es). - // NOTE: if "c" locale is encountered, the default locale will be returned. + /* + * Check if language code is at least two characters long (ie. en, es). + * NOTE: if "c" locale is encountered, the default locale will be returned. + */ if (extractedLocale.length < 2) return defaultLocale; else return extractedLocale; } else return defaultLocale; diff --git a/packages/astro/src/core/ssr/index.ts b/packages/astro/src/core/ssr/index.ts index 04e4e6d74..02fb1c423 100644 --- a/packages/astro/src/core/ssr/index.ts +++ b/packages/astro/src/core/ssr/index.ts @@ -38,9 +38,11 @@ const cache = new Map>(); // TODO: improve validation and error handling here. async function resolveRenderer(viteServer: ViteDevServer, renderer: string) { const resolvedRenderer: any = {}; - // We can dynamically import the renderer by itself because it shouldn't have - // any non-standard imports, the index is just meta info. - // The other entrypoints need to be loaded through Vite. + /* + * We can dynamically import the renderer by itself because it shouldn't have + * any non-standard imports, the index is just meta info. + * The other entrypoints need to be loaded through Vite. + */ const { default: { name, client, polyfills, hydrationPolyfills, server }, } = await import(renderer); @@ -73,14 +75,20 @@ async function resolveRenderers(viteServer: ViteDevServer, ids: string[]): Promi /** use Vite to SSR */ export async function ssr({ astroConfig, filePath, logging, mode, origin, pathname, route, routeCache, viteServer }: SSROptions): Promise { try { - // 1. resolve renderers + /* + * Renderers + * Load all renderers to be used for SSR + */ // Important this happens before load module in case a renderer provides polyfills. const renderers = await resolveRenderers(viteServer, astroConfig.renderers); - // 2. load module + /* + * Pre-render + * Load module through Vite and do pre-render work like dynamic routing + */ const mod = (await viteServer.ssrLoadModule(fileURLToPath(filePath))) as ComponentInstance; - // 3. handle dynamic routes + // handle dynamic routes let params: Params = {}; let pageProps: Props = {}; if (route && !route.pathname) { @@ -110,7 +118,10 @@ export async function ssr({ astroConfig, filePath, logging, mode, origin, pathna pageProps = { ...matchedStaticPath.props } || {}; } - // 4. render page + /* + * Render + * Convert .astro to .html + */ const Component = await mod.default; if (!Component) throw new Error(`Expected an exported Astro component but received typeof ${typeof Component}`); @@ -144,12 +155,14 @@ export async function ssr({ astroConfig, filePath, logging, mode, origin, pathna let html = await renderPage(result, Component, pageProps, null); - // 5. modify response + /* + * Dev Server + * Apply Vite HMR, Astro HMR, and other dev-only transformations needed from Vite plugins. + */ if (mode === 'development') { html = await viteServer.transformIndexHtml(fileURLToPath(filePath), html, pathname); } - // 6. finish return html; } catch (e: any) { viteServer.ssrFixStacktrace(e); diff --git a/packages/astro/src/core/ssr/paginate.ts b/packages/astro/src/core/ssr/paginate.ts index e1b9b1527..f81753f74 100644 --- a/packages/astro/src/core/ssr/paginate.ts +++ b/packages/astro/src/core/ssr/paginate.ts @@ -1,15 +1,5 @@ import { GetStaticPathsResult, PaginatedCollectionProp, PaginateFunction, Params, Props, RouteData } from '../../@types/astro-core'; -// return filters.map((filter) => { -// const filteredRecipes = allRecipes.filter((recipe) => -// filterKeys.some((key) => recipe[key] === filter) -// ); -// return paginate(filteredRecipes, { -// params: { slug: slugify(filter) }, -// props: { filter }, -// }); -// }); - export function generatePaginateFunction(routeMatch: RouteData): PaginateFunction { return function paginateUtility(data: any[], args: { pageSize?: number; params?: Params; props?: Props } = {}) { let { pageSize: _pageSize, params: _params, props: _props } = args; diff --git a/packages/astro/src/core/ssr/sitemap.ts b/packages/astro/src/core/ssr/sitemap.ts index 2c337bc8b..9de7ea6e6 100644 --- a/packages/astro/src/core/ssr/sitemap.ts +++ b/packages/astro/src/core/ssr/sitemap.ts @@ -1,6 +1,7 @@ /** Construct sitemap.xml given a set of URLs */ export function generateSitemap(pages: string[]): string { // TODO: find way to respect URLs here + // TODO: find way to exclude pages from sitemap const urls = [...pages]; // copy just in case original copy is needed diff --git a/packages/astro/src/core/util.ts b/packages/astro/src/core/util.ts index 8adbaa927..1e696a4ec 100644 --- a/packages/astro/src/core/util.ts +++ b/packages/astro/src/core/util.ts @@ -52,20 +52,20 @@ export function codeFrame(src: string, loc: ErrorPayload['err']['loc']): string const lines = src.replace(/\r\n/g, '\n').split('\n'); - // 1. grab 2 lines before, and 3 lines after focused line + // grab 2 lines before, and 3 lines after focused line const visibleLines = []; for (let n = -2; n <= 2; n++) { if (lines[loc.line + n]) visibleLines.push(loc.line + n); } - // 2. figure out gutter width + // figure out gutter width let gutterWidth = 0; for (const lineNo of visibleLines) { let w = `> ${lineNo}`; if (w.length > gutterWidth) gutterWidth = w.length; } - // 3. print lines + // print lines let output = ''; for (const lineNo of visibleLines) { const isFocusedLine = lineNo === loc.line - 1; diff --git a/packages/astro/src/runtime/server/index.ts b/packages/astro/src/runtime/server/index.ts index c5654587c..27eb5b5d2 100644 --- a/packages/astro/src/runtime/server/index.ts +++ b/packages/astro/src/runtime/server/index.ts @@ -10,14 +10,18 @@ export { createMetadata } from './metadata.js'; const { generate, GENERATOR } = astring; -// A more robust version alternative to `JSON.stringify` that can handle most values -// see https://github.com/remcohaszing/estree-util-value-to-estree#readme +/* + * A more robust version alternative to `JSON.stringify` that can handle most values + * See https://github.com/remcohaszing/estree-util-value-to-estree#readme + */ const customGenerator: astring.Generator = { ...GENERATOR, Literal(node, state) { if (node.raw != null) { - // escape closing script tags in strings so browsers wouldn't interpret them as - // closing the actual end tag in HTML + /* + * escape closing script tags in strings so browsers wouldn't interpret them as + * closing the actual end tag in HTML + */ state.write(node.raw.replace('', '<\\/script>')); } else { GENERATOR.Literal(node, state); @@ -35,9 +39,11 @@ async function _render(child: any): Promise { if (Array.isArray(child)) { return (await Promise.all(child.map((value) => _render(value)))).join('\n'); } else if (typeof child === 'function') { - // Special: If a child is a function, call it automatically. - // This lets you do {() => ...} without the extra boilerplate - // of wrapping it in a function and calling it. + /* + * Special: If a child is a function, call it automatically. + * This lets you do {() => ...} without the extra boilerplate + * of wrapping it in a function and calling it. + */ return _render(child()); } else if (typeof child === 'string') { return child; @@ -261,7 +267,7 @@ function createFetchContentFn(url: URL) { ...mod.frontmatter, content: mod.metadata, file: new URL(spec, url), - url: urlSpec.includes('/pages/') && urlSpec.replace(/^.*\/pages\//, '/').replace(/\.md$/, '') + url: urlSpec.includes('/pages/') && urlSpec.replace(/^.*\/pages\//, '/').replace(/\.md$/, ''), }; }) .filter(Boolean); diff --git a/packages/astro/src/vite-plugin-astro-postprocess/index.ts b/packages/astro/src/vite-plugin-astro-postprocess/index.ts index c4f4f1ab5..20cda778d 100644 --- a/packages/astro/src/vite-plugin-astro-postprocess/index.ts +++ b/packages/astro/src/vite-plugin-astro-postprocess/index.ts @@ -11,7 +11,7 @@ interface AstroPluginOptions { devServer?: AstroDevServer; } -// esbuild transforms the component-scoped Astro into Astro2, so need to check both. +// esbuild transforms the component-scoped Astro into Astro2, so need to check both. const validAstroGlobalNames = new Set(['Astro', 'Astro2']); export default function astro({ config, devServer }: AstroPluginOptions): Plugin { @@ -23,8 +23,10 @@ export default function astro({ config, devServer }: AstroPluginOptions): Plugin return null; } - // Optimization: only run on a probably match - // Open this up if need for post-pass extends past fetchContent + /* + * Optimization: only run on a probably match + * Open this up if need for post-pass extends past fetchContent + */ if (!code.includes('fetchContent')) { return null; } diff --git a/packages/astro/src/vite-plugin-astro/index.ts b/packages/astro/src/vite-plugin-astro/index.ts index 865a466f4..4d69bcfaa 100644 --- a/packages/astro/src/vite-plugin-astro/index.ts +++ b/packages/astro/src/vite-plugin-astro/index.ts @@ -28,15 +28,14 @@ export default function astro({ config, devServer }: AstroPluginOptions): Plugin let tsResult: TransformResult | undefined; try { - // 1. Transform from `.astro` to valid `.ts` - // use `sourcemap: "inline"` so that the sourcemap is included in the "code" result that we pass to esbuild. + // `.astro` -> `.ts` tsResult = await transform(source, { site: config.buildOptions.site, sourcefile: id, sourcemap: 'both', internalURL: 'astro/internal', }); - // 2. Compile `.ts` to `.js` + // `.ts` -> `.js` const { code, map } = await esbuild.transform(tsResult.code, { loader: 'ts', sourcemap: 'external', sourcefile: id }); return { diff --git a/packages/astro/src/vite-plugin-fetch/index.ts b/packages/astro/src/vite-plugin-fetch/index.ts index 7359653f2..b8af71d4e 100644 --- a/packages/astro/src/vite-plugin-fetch/index.ts +++ b/packages/astro/src/vite-plugin-fetch/index.ts @@ -4,19 +4,21 @@ import MagicString from 'magic-string'; // https://github.com/vitejs/vite/discussions/5109#discussioncomment-1450726 function isSSR(options: undefined | boolean | { ssr: boolean }): boolean { if (options === undefined) { - return false + return false; } if (typeof options === 'boolean') { - return options + return options; } if (typeof options == 'object') { - return !!options.ssr + return !!options.ssr; } - return false + return false; } -// This matches any JS-like file (that we know of) -// See https://regex101.com/r/Cgofir/1 +/* + * This matches any JS-like file (that we know of) + * See https://regex101.com/r/Cgofir/1 + */ const SUPPORTED_FILES = /\.(astro|svelte|vue|[cm]?js|jsx|[cm]?ts|tsx)$/; const DEFINE_FETCH = `import fetch from 'node-fetch';\n`; @@ -26,22 +28,22 @@ export default function pluginFetch(): Plugin { enforce: 'post', async transform(code, id, opts) { const ssr = isSSR(opts); - + // If this isn't an SSR pass, `fetch` will already be available! if (!ssr) { return null; } - + // Only transform JS-like files if (!id.match(SUPPORTED_FILES)) { return null; } - + // Optimization: only run on probable matches if (!code.includes('fetch')) { return null; } - + const s = new MagicString(code); s.prepend(DEFINE_FETCH); @@ -49,10 +51,10 @@ export default function pluginFetch(): Plugin { const map = s.generateMap({ source: id, - includeContent: true + includeContent: true, }); - - return { code: result, map } + + return { code: result, map }; }, }; } diff --git a/packages/astro/src/vite-plugin-jsx/index.ts b/packages/astro/src/vite-plugin-jsx/index.ts index 5248fbe65..4c4467c7c 100644 --- a/packages/astro/src/vite-plugin-jsx/index.ts +++ b/packages/astro/src/vite-plugin-jsx/index.ts @@ -18,8 +18,10 @@ const IMPORT_STATEMENTS: Record = { preact: "import { h } from 'preact'", 'solid-js': "import 'solid-js/web'", }; -// The `tsx` loader in esbuild will remove unused imports, so we need to -// be careful about esbuild not treating h, React, Fragment, etc. as unused. +/* + * The `tsx` loader in esbuild will remove unused imports, so we need to + * be careful about esbuild not treating h, React, Fragment, etc. as unused. + */ const PREVENT_UNUSED_IMPORTS = ';;(React,Fragment,h);'; interface AstroPluginJSXOptions { @@ -53,14 +55,23 @@ export default function jsx({ config, logging }: AstroPluginJSXOptions): Plugin } } - // attempt 0: if we only have one renderer, we can skip a bunch of work! + /* + * Single JSX renderer + * If we only have one renderer, we can skip a bunch of work! + */ if (JSX_RENDERERS.size === 1) { return transformJSX({ code, id, renderer: [...JSX_RENDERERS.values()][0], ssr: ssr || false }); } - // attempt 1: try and guess framework from imports (file can’t import React and Preact) + /* + * Multiple JSX renderers + * Determine for each .jsx or .tsx file what it wants to use to Render + */ // we need valid JS here, so we can use `h` and `Fragment` as placeholders + + // try and guess renderer from imports (file can’t import React and Preact) + // NOTE(fks, matthewp): Make sure that you're transforming the original contents here. const { code: codeToScan } = await esbuild.transform(code + PREVENT_UNUSED_IMPORTS, { loader: getLoader(path.extname(id)), @@ -85,7 +96,7 @@ export default function jsx({ config, logging }: AstroPluginJSXOptions): Plugin } } - // attempt 2: look for @jsxImportSource comment + // if no imports were found, look for @jsxImportSource comment if (!importSource) { const multiline = code.match(/\/\*\*[\S\s]*\*\//gm) || []; for (const comment of multiline) { diff --git a/packages/astro/src/vite-plugin-markdown/index.ts b/packages/astro/src/vite-plugin-markdown/index.ts index ef0a62647..19cbf3c12 100644 --- a/packages/astro/src/vite-plugin-markdown/index.ts +++ b/packages/astro/src/vite-plugin-markdown/index.ts @@ -20,7 +20,7 @@ export default function markdown({ config }: AstroPluginOptions): Plugin { if (id.endsWith('.md')) { let source = await fs.promises.readFile(id, 'utf8'); - // 2. Transform from `.md` to valid `.astro` + // `.md` -> `.astro` let render = config.markdownOptions.render; let renderOpts = {}; if (Array.isArray(render)) { @@ -46,14 +46,14 @@ ${setup} astroResult = `${prelude}\n${astroResult}`; } - // 2. Transform from `.astro` to valid `.ts` + // `.astro` -> `.ts` let { code: tsResult } = await transform(astroResult, { sourcefile: id, sourcemap: 'inline', internalURL: 'astro/internal' }); tsResult = `\nexport const metadata = ${JSON.stringify(metadata)}; export const frontmatter = ${JSON.stringify(content)}; ${tsResult}`; - // 3. Compile `.ts` to `.js` + // `.ts` -> `.js` const { code, map } = await esbuild.transform(tsResult, { loader: 'ts', sourcemap: 'inline', sourcefile: id }); return {