-
-
Notifications
You must be signed in to change notification settings - Fork 2.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
refactor: build pipeline #8088
refactor: build pipeline #8088
Changes from all commits
b7e0e8b
180408c
8d1ba8b
61d3bcf
42ac066
d64b11c
1095ce6
ce393bd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,211 @@ | ||
import { Pipeline } from '../pipeline.js'; | ||
import type { BuildInternals } from './internal'; | ||
import type { PageBuildData, StaticBuildOptions } from './types'; | ||
import { ASTRO_PAGE_RESOLVED_MODULE_ID } from './plugins/plugin-pages.js'; | ||
import { RESOLVED_SPLIT_MODULE_ID } from './plugins/plugin-ssr.js'; | ||
import { ASTRO_PAGE_EXTENSION_POST_PATTERN } from './plugins/util.js'; | ||
import type { SSRManifest } from '../app/types'; | ||
import type { AstroConfig, AstroSettings, RouteType, SSRLoadedRenderer } from '../../@types/astro'; | ||
import { getOutputDirectory, isServerLikeOutput } from '../../prerender/utils.js'; | ||
import type { EndpointCallResult } from '../endpoint'; | ||
import { createEnvironment } from '../render/index.js'; | ||
import { BEFORE_HYDRATION_SCRIPT_ID } from '../../vite-plugin-scripts/index.js'; | ||
import { createAssetLink } from '../render/ssr-element.js'; | ||
import type { BufferEncoding } from 'vfile'; | ||
|
||
/** | ||
* This pipeline is responsible to gather the files emitted by the SSR build and generate the pages by executing these files. | ||
*/ | ||
export class BuildPipeline extends Pipeline { | ||
#internals: BuildInternals; | ||
#staticBuildOptions: StaticBuildOptions; | ||
#manifest: SSRManifest; | ||
#currentEndpointBody?: { | ||
body: string | Uint8Array; | ||
encoding: BufferEncoding; | ||
}; | ||
|
||
constructor( | ||
staticBuildOptions: StaticBuildOptions, | ||
internals: BuildInternals, | ||
manifest: SSRManifest | ||
) { | ||
const ssr = isServerLikeOutput(staticBuildOptions.settings.config); | ||
super( | ||
createEnvironment({ | ||
adapterName: manifest.adapterName, | ||
logging: staticBuildOptions.logging, | ||
mode: staticBuildOptions.mode, | ||
renderers: manifest.renderers, | ||
clientDirectives: manifest.clientDirectives, | ||
compressHTML: manifest.compressHTML, | ||
async resolve(specifier: string) { | ||
const hashedFilePath = manifest.entryModules[specifier]; | ||
if (typeof hashedFilePath !== 'string') { | ||
// If no "astro:scripts/before-hydration.js" script exists in the build, | ||
// then we can assume that no before-hydration scripts are needed. | ||
if (specifier === BEFORE_HYDRATION_SCRIPT_ID) { | ||
return ''; | ||
} | ||
throw new Error(`Cannot find the built path for ${specifier}`); | ||
} | ||
return createAssetLink(hashedFilePath, manifest.base, manifest.assetsPrefix); | ||
}, | ||
routeCache: staticBuildOptions.routeCache, | ||
site: manifest.site, | ||
ssr, | ||
streaming: true, | ||
}) | ||
); | ||
this.#internals = internals; | ||
this.#staticBuildOptions = staticBuildOptions; | ||
this.#manifest = manifest; | ||
this.setEndpointHandler(this.#handleEndpointResult); | ||
} | ||
|
||
getInternals(): Readonly<BuildInternals> { | ||
return this.#internals; | ||
} | ||
|
||
getSettings(): Readonly<AstroSettings> { | ||
return this.#staticBuildOptions.settings; | ||
} | ||
|
||
getStaticBuildOptions(): Readonly<StaticBuildOptions> { | ||
return this.#staticBuildOptions; | ||
} | ||
|
||
getConfig(): AstroConfig { | ||
return this.#staticBuildOptions.settings.config; | ||
} | ||
|
||
getManifest(): SSRManifest { | ||
return this.#manifest; | ||
} | ||
|
||
/** | ||
* The SSR build emits two important files: | ||
* - dist/server/manifest.mjs | ||
* - dist/renderers.mjs | ||
* | ||
* These two files, put together, will be used to generate the pages. | ||
* | ||
* ## Errors | ||
* | ||
* It will throw errors if the previous files can't be found in the file system. | ||
* | ||
* @param staticBuildOptions | ||
*/ | ||
static async retrieveManifest( | ||
staticBuildOptions: StaticBuildOptions, | ||
internals: BuildInternals | ||
): Promise<SSRManifest> { | ||
const config = staticBuildOptions.settings.config; | ||
const baseDirectory = getOutputDirectory(config); | ||
const manifestEntryUrl = new URL( | ||
`${internals.manifestFileName}?time=${Date.now()}`, | ||
baseDirectory | ||
); | ||
const { manifest } = await import(manifestEntryUrl.toString()); | ||
if (!manifest) { | ||
throw new Error( | ||
"Astro couldn't find the emitted manifest. This is an internal error, please file an issue." | ||
); | ||
} | ||
|
||
const renderersEntryUrl = new URL(`renderers.mjs?time=${Date.now()}`, baseDirectory); | ||
const renderers = await import(renderersEntryUrl.toString()); | ||
if (!renderers) { | ||
throw new Error( | ||
"Astro couldn't find the emitted renderers. This is an internal error, please file an issue." | ||
); | ||
} | ||
return { | ||
...manifest, | ||
renderers: renderers.renderers as SSRLoadedRenderer[], | ||
}; | ||
} | ||
|
||
/** | ||
* It collects the routes to generate during the build. | ||
* | ||
* It returns a map of page information and their relative entry point as a string. | ||
*/ | ||
retrieveRoutesToGenerate(): Map<PageBuildData, string> { | ||
const pages = new Map<PageBuildData, string>(); | ||
|
||
for (const [entryPoint, filePath] of this.#internals.entrySpecifierToBundleMap) { | ||
// virtual pages can be emitted with different prefixes: | ||
// - the classic way are pages emitted with prefix ASTRO_PAGE_RESOLVED_MODULE_ID -> plugin-pages | ||
// - pages emitted using `build.split`, in this case pages are emitted with prefix RESOLVED_SPLIT_MODULE_ID | ||
if ( | ||
entryPoint.includes(ASTRO_PAGE_RESOLVED_MODULE_ID) || | ||
entryPoint.includes(RESOLVED_SPLIT_MODULE_ID) | ||
) { | ||
const [, pageName] = entryPoint.split(':'); | ||
const pageData = this.#internals.pagesByComponent.get( | ||
`${pageName.replace(ASTRO_PAGE_EXTENSION_POST_PATTERN, '.')}` | ||
); | ||
if (!pageData) { | ||
throw new Error( | ||
"Build failed. Astro couldn't find the emitted page from " + pageName + ' pattern' | ||
); | ||
} | ||
|
||
pages.set(pageData, filePath); | ||
} | ||
} | ||
for (const [path, pageData] of this.#internals.pagesByComponent.entries()) { | ||
if (pageData.route.type === 'redirect') { | ||
pages.set(pageData, path); | ||
} | ||
} | ||
return pages; | ||
} | ||
|
||
async #handleEndpointResult(request: Request, response: EndpointCallResult): Promise<Response> { | ||
if (response.type === 'response') { | ||
if (!response.response.body) { | ||
return new Response(null); | ||
} | ||
const ab = await response.response.arrayBuffer(); | ||
const body = new Uint8Array(ab); | ||
this.#currentEndpointBody = { | ||
body: body, | ||
encoding: 'utf-8', | ||
}; | ||
return response.response; | ||
} else { | ||
if (response.encoding) { | ||
this.#currentEndpointBody = { | ||
body: response.body, | ||
encoding: response.encoding, | ||
}; | ||
const headers = new Headers(); | ||
headers.set('X-Astro-Encoding', response.encoding); | ||
return new Response(response.body, { | ||
headers, | ||
}); | ||
Comment on lines
+184
to
+188
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I thought we could store the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I like it! |
||
} else { | ||
return new Response(response.body); | ||
} | ||
} | ||
} | ||
|
||
async computeBodyAndEncoding( | ||
routeType: RouteType, | ||
response: Response | ||
): Promise<{ | ||
body: string | Uint8Array; | ||
encoding: BufferEncoding; | ||
}> { | ||
const encoding = response.headers.get('X-Astro-Encoding') ?? 'utf-8'; | ||
if (this.#currentEndpointBody) { | ||
const currentEndpointBody = this.#currentEndpointBody; | ||
this.#currentEndpointBody = undefined; | ||
return currentEndpointBody; | ||
} else { | ||
return { body: await response.text(), encoding: encoding as BufferEncoding }; | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this check is the reason that so many hybrid render builds are failing,
in
packages/src/core/build/plugins/plugin-manifest.ts:238
you are settingentryModules[BEFORE_HYDRATION_SCRIPT_ID] = ''
, then here you are checkingif (typeof hashedFilePath !== 'string')
and inside that check you are checkingif (specifier === BEFORE_HYDRATION_SCRIPT_ID)
, but as you have already set theBEFORE_HYDRATION_SCRIPT_ID
inplugin-manifest.ts
so this check is never called.as a result, while resolving
BEFORE_HYDRATION_SCRIPT_ID
inpackages/astro/src/runtime/server/hydration.ts:157
we get"/"
instead of""
, for which this check passes andbefore-hydration-url
is generated.So the potential fixes could be to change the function to be
or change the check at
packages/astro/src/runtime/server/hydration.ts:157
to beI am down to make a PR to fix the issue (#8379 and many more), just waiting for confrimations from the maintainers