Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
28 changes: 28 additions & 0 deletions crates/next-api/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ use crate::{
AppPageRoute, Endpoint, EndpointOutput, EndpointOutputPaths, ModuleGraphs, Route, Routes,
},
server_actions::{build_server_actions_loader, create_server_actions_manifest},
sri_manifest::get_sri_manifest_asset,
webpack_stats::generate_webpack_stats,
};

Expand Down Expand Up @@ -1576,6 +1577,16 @@ impl AppEndpoint {
file_paths_from_root.insert(rcstr!("server/server-reference-manifest.js"));
}

if project
.next_config()
.experimental_sri()
.await?
.as_ref()
.is_some_and(|v| v.algorithm.is_some())
{
file_paths_from_root.insert(rcstr!("server/subresource-integrity-manifest.js"));
}

let mut wasm_paths_from_root = fxindexset![];

let node_root_value = node_root.clone();
Expand Down Expand Up @@ -2012,6 +2023,23 @@ impl Endpoint for AppEndpoint {
let client_relative_root = project.client_relative_path().owned().await?;

let output_assets = output.output_assets();
let output_assets = if let Some(sri) =
&*project.next_config().experimental_sri().await?
&& let Some(algorithm) = sri.algorithm.clone()
{
let sri_manifest = get_sri_manifest_asset(
node_root.join(&format!(
"server/app{}/subresource-integrity-manifest.json",
&self.app_endpoint_entry().await?.original_name
))?,
output_assets,
client_relative_root.clone(),
algorithm,
);
output_assets.concat_asset(sri_manifest)
} else {
output_assets
};

let (server_paths, client_paths) = if project.next_mode().await?.is_development() {
let server_paths = all_asset_paths(output_assets, node_root.clone(), None)
Expand Down
96 changes: 96 additions & 0 deletions crates/next-api/src/asset_hashes_manifest.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
use anyhow::Result;
use serde::{Serializer, ser::SerializeMap};
use turbo_rcstr::RcStr;
use turbo_tasks::{ResolvedVc, Vc};
use turbo_tasks_fs::{File, FileContent, FileSystemPath};
use turbopack_core::{
asset::{Asset, AssetContent},
output::{OutputAsset, OutputAssetsReference},
};

use crate::paths::{AssetPath, AssetPaths};

/// Generates a manifest mapping asset paths to their content hashes. The manifest is generated as a
/// JSON file with the following format:
/// ```json
/// {
/// "path/to/asset1.js": "hash_prefix-contenthash1",
/// "path/to/asset2.css": "hash_prefix-contenthash2",
/// ...
/// }
/// ```
#[turbo_tasks::value]
pub struct AssetHashesManifestAsset {
output_path: FileSystemPath,
asset_paths: ResolvedVc<AssetPaths>,
/// Optional prefix to add to the hash (e.g. "sha256-" for SRI hashes)
hash_prefix: Option<RcStr>,
}

#[turbo_tasks::value_impl]
impl AssetHashesManifestAsset {
#[turbo_tasks::function]
pub fn new(
output_path: FileSystemPath,
asset_paths: ResolvedVc<AssetPaths>,
hash_prefix: Option<RcStr>,
) -> Vc<Self> {
AssetHashesManifestAsset {
output_path,
asset_paths,
hash_prefix,
}
.cell()
}
}

#[turbo_tasks::value_impl]
impl OutputAssetsReference for AssetHashesManifestAsset {}

#[turbo_tasks::value_impl]
impl OutputAsset for AssetHashesManifestAsset {
#[turbo_tasks::function]
async fn path(&self) -> Vc<FileSystemPath> {
self.output_path.clone().cell()
}
}

#[turbo_tasks::value_impl]
impl Asset for AssetHashesManifestAsset {
#[turbo_tasks::function]
async fn content(&self) -> Result<Vc<AssetContent>> {
let files = self.asset_paths.await?;

struct Manifest<'a> {
asset_paths: &'a Vec<AssetPath>,
hash_prefix: &'a Option<RcStr>,
}

impl serde::Serialize for Manifest<'_> {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
let mut map = serializer.serialize_map(Some(self.asset_paths.len()))?;
let mut buf = String::new();
for entry in self.asset_paths {
if let Some(prefix) = self.hash_prefix {
use std::fmt::Write;
buf.clear();
write!(buf, "{}{}", prefix, entry.content_hash).unwrap();
map.serialize_entry(&entry.path, &buf)?;
} else {
map.serialize_entry(&entry.path, &entry.content_hash)?;
}
}
map.end()
}
}

let json = serde_json::to_string(&Manifest {
asset_paths: &files,
hash_prefix: &self.hash_prefix,
})?;

Ok(AssetContent::file(
FileContent::Content(File::from(json)).cell(),
))
}
}
2 changes: 2 additions & 0 deletions crates/next-api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

pub mod analyze;
mod app;
mod asset_hashes_manifest;
mod client_references;
mod dynamic_imports;
mod empty;
Expand All @@ -23,5 +24,6 @@ pub mod project;
pub mod route;
pub mod routes_hashes_manifest;
mod server_actions;
mod sri_manifest;
mod versioned_content_map;
mod webpack_stats;
18 changes: 18 additions & 0 deletions crates/next-api/src/pages.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ use crate::{
},
project::Project,
route::{Endpoint, EndpointOutput, EndpointOutputPaths, ModuleGraphs, Route, Routes},
sri_manifest::get_sri_manifest_asset,
webpack_stats::generate_webpack_stats,
};

Expand Down Expand Up @@ -1607,6 +1608,23 @@ impl Endpoint for PageEndpoint {
let client_relative_root = project.client_relative_path().owned().await?;

let output_assets = output.output_assets();
let output_assets = if let Some(sri) =
&*project.next_config().experimental_sri().await?
&& let Some(algorithm) = sri.algorithm.clone()
{
let sri_manifest = get_sri_manifest_asset(
node_root.join(&format!(
"server/pages{}/subresource-integrity-manifest.json",
get_asset_prefix_from_pathname(&this.pathname)
))?,
output_assets,
client_relative_root.clone(),
algorithm,
);
output_assets.concat_asset(sri_manifest)
} else {
output_assets
};

let (server_paths, client_paths) = if project.next_mode().await?.is_development() {
let server_paths = all_asset_paths(output_assets, node_root.clone(), None)
Expand Down
29 changes: 29 additions & 0 deletions crates/next-api/src/sri_manifest.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
use anyhow::{Result, bail};
use turbo_rcstr::{RcStr, rcstr};
use turbo_tasks::Vc;
use turbo_tasks_fs::FileSystemPath;
use turbo_tasks_hash::HashAlgorithm;
use turbopack_core::output::{OutputAsset, OutputAssets};

use crate::{asset_hashes_manifest::AssetHashesManifestAsset, paths::all_asset_paths};

#[turbo_tasks::function]
pub fn get_sri_manifest_asset(
output_path: FileSystemPath,
output_assets: Vc<OutputAssets>,
client_relative_root: FileSystemPath,
algorithm: RcStr,
) -> Result<Vc<Box<dyn OutputAsset>>> {
let (algorithm, prefix) = match algorithm.as_str() {
"sha256" => (HashAlgorithm::Sha256Base64, rcstr!("sha256-")),
"sha384" => (HashAlgorithm::Sha384Base64, rcstr!("sha384-")),
"sha512" => (HashAlgorithm::Sha512Base64, rcstr!("sha512-")),
_ => bail!("Unsupported SRI algorithm: {}", algorithm),
};

Ok(Vc::upcast(AssetHashesManifestAsset::new(
output_path,
all_asset_paths(output_assets, client_relative_root, Some(algorithm)),
Some(prefix),
)))
}
9 changes: 4 additions & 5 deletions crates/next-core/src/next_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1820,11 +1820,10 @@ impl NextConfig {
Vc::cell(self.experimental.swc_plugins.clone().unwrap_or_default())
}

// TODO not implemented yet
// #[turbo_tasks::function]
// pub fn experimental_sri(&self) -> Vc<OptionSubResourceIntegrity> {
// Vc::cell(self.experimental.sri.clone())
// }
#[turbo_tasks::function]
pub fn experimental_sri(&self) -> Vc<OptionSubResourceIntegrity> {
Vc::cell(self.experimental.sri.clone())
}

#[turbo_tasks::function]
pub fn experimental_turbopack_use_builtin_babel(&self) -> Vc<Option<bool>> {
Expand Down
4 changes: 4 additions & 0 deletions packages/next/src/build/handle-entrypoints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ export async function handleRouteType({
await manifestLoader.loadFontManifest('/_app', 'pages')
await manifestLoader.loadFontManifest(page, 'pages')

await manifestLoader.loadSriManifest(page, 'pages')

if (shouldCreateWebpackStats) {
await manifestLoader.loadWebpackStats(page, 'pages')
}
Expand Down Expand Up @@ -114,6 +116,8 @@ export async function handleRouteType({
manifestLoader.loadActionManifest(page)
manifestLoader.loadFontManifest(page, 'app')

manifestLoader.loadSriManifest(page, 'app')

if (shouldCreateWebpackStats) {
manifestLoader.loadWebpackStats(page, 'app')
}
Expand Down
2 changes: 2 additions & 0 deletions packages/next/src/build/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,8 @@ export type PrerenderManifest = {
preview: __ApiPreviewProps
}

export type SubresourceIntegrityManifest = Record<string, string>

type ManifestBuiltRoute = {
/**
* The route pattern used to match requests for this route.
Expand Down
3 changes: 3 additions & 0 deletions packages/next/src/build/turbopack-build/impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,8 @@ export async function turbopackBuild(): Promise<{
isShortSession: true,
}

const sriEnabled = Boolean(config.experimental.sri?.algorithm)

const project = await bindings.turbo.createProject(
{
...sharedProjectOptions,
Expand Down Expand Up @@ -180,6 +182,7 @@ export async function turbopackBuild(): Promise<{
encryptionKey,
dev: false,
deploymentId: config.deploymentId,
sriEnabled,
})

const currentEntrypoints = await rawEntrypointsToEntrypoints(
Expand Down
1 change: 0 additions & 1 deletion packages/next/src/lib/turbopack-warning.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ const unsupportedTurbopackNextConfigOptions = [
'experimental.extensionAlias',
'experimental.fallbackNodePolyfills',

'experimental.sri.algorithm',
'experimental.swcTraceProfiling',

// Left to be implemented (Might not be needed for Turbopack)
Expand Down
1 change: 1 addition & 0 deletions packages/next/src/server/dev/hot-reloader-turbopack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,7 @@ export async function createHotReloaderTurbopack(
encryptionKey,
dev: true,
deploymentId: nextConfig.deploymentId,
sriEnabled: false,
})

// Dev specific
Expand Down
Loading
Loading