Skip to content
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

Add experimental.directRenderScript option #10102

Merged
merged 24 commits into from
Mar 8, 2024
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .changeset/cool-jobs-fetch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
"astro": minor
---

Adds a new `experimental.directRenderScript` option.

This option replaces the `experimental.optimizeHoistedScript` option to use a more reliable strategy to prevent scripts being executed in pages they are not used. The scripts are now directly rendered as declared in Astro files (with features like TypeScript, importing `node_modules`, and deduplicating scripts still working), and should result in scripts running in the correct pages compared to the previous static analysis approach. You can also now conditionally render scripts in your Astro file.

However, as scripts are now directly rendered, they are no longer hoisted to the `<head>` and multiple scripts on a page are no longer bundled together. If you enable this option, you should check if it affects your site's behaviour.

This option will be enabled by default in Astro 5.0.
3 changes: 1 addition & 2 deletions packages/astro/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@
"test:node": "astro-scripts test \"test/**/*.nodetest.js\""
},
"dependencies": {
"@astrojs/compiler": "^2.5.3",
"@astrojs/compiler": "0.0.0-render-script-20240214150051",
"@astrojs/internal-helpers": "workspace:*",
"@astrojs/markdown-remark": "workspace:*",
"@astrojs/telemetry": "workspace:*",
Expand Down Expand Up @@ -197,7 +197,6 @@
"@types/diff": "^5.0.8",
"@types/dlv": "^1.1.4",
"@types/dom-view-transitions": "^1.0.4",
"@types/estree": "^1.0.5",
"@types/hast": "^3.0.3",
"@types/html-escaper": "^3.0.2",
"@types/http-cache-semantics": "^4.0.4",
Expand Down
30 changes: 20 additions & 10 deletions packages/astro/src/@types/astro.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1601,25 +1601,29 @@ export interface AstroUserConfig {
experimental?: {
/**
* @docs
* @name experimental.optimizeHoistedScript
* @name experimental.directRenderScript
* @type {boolean}
* @default `false`
* @version 2.10.4
* @version 4.5.0
* @description
* Prevents unused components' scripts from being included in a page unexpectedly.
* The optimization is best-effort and may inversely miss including the used scripts. Make sure to double-check your built pages
* before publishing.
* Enable hoisted script analysis optimization by adding the experimental flag:
* Directly render scripts as declared in Astro files (with features like TypeScript, importing `node_modules`, and deduplicating
* scripts still working). This should result in scripts running in the correct pages compared to the previous static analysis approach.
* You can also now conditionally render scripts in your Astro file.

* However, as scripts are now directly rendered, they are no longer hoisted to the `<head>` and multiple scripts on a page are no
* longer bundled together. If you enable this option, you should check if it affects your site's behaviour.
*
* This option will be enabled by default in Astro 5.0.
*
* ```js
* {
* experimental: {
* optimizeHoistedScript: true,
* },
* experimental: {
* directRenderScript: true,
* },
* }
* ```
*/
optimizeHoistedScript?: boolean;
directRenderScript?: boolean;

/**
* @docs
Expand Down Expand Up @@ -2691,6 +2695,7 @@ export interface SSRResult {
scripts: Set<SSRElement>;
links: Set<SSRElement>;
componentMetadata: Map<string, SSRComponentMetadata>;
inlinedScripts: Map<string, string>;
createAstro(
Astro: AstroGlobalPartial,
props: Record<string, any>,
Expand Down Expand Up @@ -2725,6 +2730,11 @@ export interface SSRMetadata {
* script in the page HTML before the first Solid component.
*/
rendererSpecificHydrationScripts: Set<string>;
/**
* Used by `renderScript` to track script ids that have been rendered,
* so we only render each once.
*/
renderedScripts: Set<string>;
hasDirectives: Set<string>;
hasRenderedHead: boolean;
headInTree: boolean;
Expand Down
13 changes: 10 additions & 3 deletions packages/astro/src/content/vite-plugin-content-assets.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { extname } from 'node:path';
import { pathToFileURL } from 'node:url';
import type { Plugin, Rollup } from 'vite';
import type { AstroSettings } from '../@types/astro.js';
import type { AstroSettings, SSRElement } from '../@types/astro.js';
import { moduleIsTopLevelPage, walkParentInfos } from '../core/build/graph.js';
import { getPageDataByViteID, type BuildInternals } from '../core/build/internal.js';
import type { AstroBuildPlugin } from '../core/build/plugin.js';
Expand Down Expand Up @@ -70,8 +70,15 @@ export function astroContentAssetPropagationPlugin({
crawledFiles: styleCrawledFiles,
} = await getStylesForURL(pathToFileURL(basePath), devModuleLoader);

const { scripts: hoistedScripts, crawledFiles: scriptCrawledFiles } =
await getScriptsForURL(pathToFileURL(basePath), settings.config.root, devModuleLoader);
// Add hoisted script tags, skip if direct rendering with `directRenderScript`
const { scripts: hoistedScripts, crawledFiles: scriptCrawledFiles } = settings.config
.experimental.directRenderScript
? { scripts: new Set<SSRElement>(), crawledFiles: new Set<string>() }
: await getScriptsForURL(
pathToFileURL(basePath),
settings.config.root,
devModuleLoader
);

// Register files we crawled to be able to retrieve the rendered styles and scripts,
// as when they get updated, we need to re-transform ourselves.
Expand Down
2 changes: 2 additions & 0 deletions packages/astro/src/core/app/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export function deserializeManifest(serializedManifest: SerializedSSRManifest):

const assets = new Set<string>(serializedManifest.assets);
const componentMetadata = new Map(serializedManifest.componentMetadata);
const inlinedScripts = new Map(serializedManifest.inlinedScripts);
const clientDirectives = new Map(serializedManifest.clientDirectives);

return {
Expand All @@ -25,6 +26,7 @@ export function deserializeManifest(serializedManifest: SerializedSSRManifest):
...serializedManifest,
assets,
componentMetadata,
inlinedScripts,
clientDirectives,
routes,
};
Expand Down
1 change: 1 addition & 0 deletions packages/astro/src/core/app/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ export class App {
compressHTML: this.#manifest.compressHTML,
renderers: this.#manifest.renderers,
clientDirectives: this.#manifest.clientDirectives,
inlinedScripts: this.#manifest.inlinedScripts,
resolve: async (specifier: string) => {
if (!(specifier in this.#manifest.entryModules)) {
throw new Error(`Unable to resolve [${specifier}]`);
Expand Down
4 changes: 3 additions & 1 deletion packages/astro/src/core/app/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export type SSRManifest = {
*/
clientDirectives: Map<string, string>;
entryModules: Record<string, string>;
inlinedScripts: Map<string, string>;
assets: Set<string>;
componentMetadata: SSRResult['componentMetadata'];
pageModule?: SinglePageBuiltModule;
Expand All @@ -68,10 +69,11 @@ export type SSRManifestI18n = {

export type SerializedSSRManifest = Omit<
SSRManifest,
'middleware' | 'routes' | 'assets' | 'componentMetadata' | 'clientDirectives'
'middleware' | 'routes' | 'assets' | 'componentMetadata' | 'inlinedScripts' | 'clientDirectives'
> & {
routes: SerializedRouteInfo[];
assets: string[];
componentMetadata: [string, SSRComponentMetadata][];
inlinedScripts: [string, string][];
clientDirectives: [string, string][];
};
1 change: 1 addition & 0 deletions packages/astro/src/core/build/buildPipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export class BuildPipeline extends Pipeline {
mode: staticBuildOptions.mode,
renderers: manifest.renderers,
clientDirectives: manifest.clientDirectives,
inlinedScripts: manifest.inlinedScripts,
compressHTML: manifest.compressHTML,
async resolve(specifier: string) {
if (resolveCache.has(specifier)) {
Expand Down
1 change: 1 addition & 0 deletions packages/astro/src/core/build/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -669,6 +669,7 @@ function createBuildManifest(
trailingSlash: settings.config.trailingSlash,
assets: new Set(),
entryModules: Object.fromEntries(internals.entrySpecifierToBundleMap.entries()),
inlinedScripts: internals.inlinedScripts,
routes: [],
adapterName: '',
clientDirectives: settings.clientDirectives,
Expand Down
8 changes: 8 additions & 0 deletions packages/astro/src/core/build/internal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@ export interface BuildInternals {
// A mapping of hoisted script ids back to the pages which reference it
hoistedScriptIdToPagesMap: Map<string, Set<string>>;

/**
* Used by the `directRenderScript` option. If script is inlined, its id and
* inlined code is mapped here. The resolved id is an URL like "/_astro/something.js"
* but will no longer exist as the content is now inlined in this map.
*/
inlinedScripts: Map<string, string>;

// A mapping of specifiers like astro/client/idle.js to the hashed bundled name.
// Used to render pages with the correct specifiers.
entrySpecifierToBundleMap: Map<string, string>;
Expand Down Expand Up @@ -115,6 +122,7 @@ export function createBuildInternals(): BuildInternals {
cssModuleToChunkIdMap: new Map(),
hoistedScriptIdToHoistedMap,
hoistedScriptIdToPagesMap,
inlinedScripts: new Map(),
entrySpecifierToBundleMap: new Map<string, string>(),
pageToBundleMap: new Map<string, string>(),
pagesByComponent: new Map(),
Expand Down
7 changes: 6 additions & 1 deletion packages/astro/src/core/build/plugins/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { pluginMiddleware } from './plugin-middleware.js';
import { pluginPages } from './plugin-pages.js';
import { pluginPrerender } from './plugin-prerender.js';
import { pluginRenderers } from './plugin-renderers.js';
import { pluginScripts } from './plugin-scripts.js';
import { pluginSSR, pluginSSRSplit } from './plugin-ssr.js';

export function registerAllPlugins({ internals, options, register }: AstroBuildPluginContainer) {
Expand All @@ -28,7 +29,11 @@ export function registerAllPlugins({ internals, options, register }: AstroBuildP
register(astroHeadBuildPlugin(internals));
register(pluginPrerender(options, internals));
register(astroConfigBuildPlugin(options, internals));
register(pluginHoistedScripts(options, internals));
if (options.settings.config.experimental.directRenderScript) {
register(pluginScripts(internals));
} else {
register(pluginHoistedScripts(options, internals));
}
register(pluginSSR(options, internals));
register(pluginSSRSplit(options, internals));
register(pluginChunks());
Expand Down
Loading
Loading