Skip to content

Commit

Permalink
App CSS and fixes (vercel#246)
Browse files Browse the repository at this point in the history
fix app layout transition
add global css support
  • Loading branch information
sokra authored Oct 24, 2022
1 parent 4f69abd commit 0610e47
Show file tree
Hide file tree
Showing 8 changed files with 260 additions and 222 deletions.
101 changes: 78 additions & 23 deletions crates/next-core/js/src/entry/app-renderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ declare global {
}

import type { IncomingMessage, ServerResponse } from "node:http";
import type { FlightManifest } from "next/dist/build/webpack/plugins/flight-manifest-plugin";
import type {
FlightCSSManifest,
FlightManifest,
} from "next/dist/build/webpack/plugins/flight-manifest-plugin";
import type { RenderData } from "types/turbopack";

import "next/dist/server/node-polyfill-fetch";
Expand Down Expand Up @@ -68,10 +71,23 @@ process.stdin.on("data", async (data) => {
buffer.push(data);
});

type LayoutTree = [
string,
{ children?: LayoutTree },
{ page: () => any } | { layout: () => any }
// TODO expose these types in next.js
type ComponentModule = () => any;
export type ComponentsType = {
readonly [componentKey in
| "layout"
| "template"
| "error"
| "loading"
| "not-found"]?: ComponentModule;
} & {
readonly layoutOrPagePath?: string;
readonly page?: ComponentModule;
};
type LoaderTree = [
segment: string,
parallelRoutes: { [parallelRouterKey: string]: LoaderTree },
components: ComponentsType
];

type ServerComponentsManifest = {
Expand All @@ -82,17 +98,36 @@ type ServerComponentsManifestModule = {
};

async function operation(renderData: RenderData) {
const pageModule = LAYOUT_INFO[LAYOUT_INFO.length - 1].module;
const pageItem = LAYOUT_INFO[LAYOUT_INFO.length - 1];
const pageModule = pageItem.module;
const Page = pageModule.default;
let tree: LayoutTree = ["", {}, { page: () => Page }];
let tree: LoaderTree = [
"",
{},
{ page: () => Page, layoutOrPagePath: "page.js" },
];
for (let i = LAYOUT_INFO.length - 2; i >= 0; i--) {
const info = LAYOUT_INFO[i];
const Layout = info.module.default;
tree = [info.segment, { children: tree }, { layout: () => Layout }];
const mod = info.module;
if (mod) {
const Layout = mod.default;
tree = [
info.segment,
{ children: tree },
{ layout: () => Layout, layoutOrPagePath: `layout${i}.js` },
];
} else {
tree = [
info.segment,
{ children: tree },
{ layoutOrPagePath: `layout${i}.js` },
];
}
}

const proxyMethodsForModule = (
id: string
id: string,
css: boolean
): ProxyHandler<FlightManifest[""]> => ({
get(target, name, receiver) {
return {
Expand All @@ -102,15 +137,35 @@ async function operation(renderData: RenderData) {
};
},
});
const proxyMethods: ProxyHandler<FlightManifest> = {
get(target, name, receiver) {
if (name === "__ssr_module_mapping__") {
return manifest;
}
return new Proxy({}, proxyMethodsForModule(name as string));
},
const proxyMethods = (css: boolean): ProxyHandler<FlightManifest> => {
return {
get(target, name, receiver) {
if (name === "__ssr_module_mapping__") {
return manifest;
}
if (name === "__client_css_manifest__") {
return {};
}
return new Proxy({}, proxyMethodsForModule(name as string, css));
},
};
};
const manifest: FlightManifest = new Proxy({} as any, proxyMethods(false));
const serverCSSManifest: FlightCSSManifest = {};
serverCSSManifest.__entry_css__ = {};
for (let i = 0; i < LAYOUT_INFO.length - 1; i++) {
const { chunks } = LAYOUT_INFO[i];
const cssChunks = (chunks || []).filter((path) => path.endsWith(".css"));
serverCSSManifest[`layout${i}.js`] = cssChunks.map((chunk) =>
JSON.stringify([chunk, [chunk]])
);
}
serverCSSManifest.__entry_css__ = {
page: pageItem.chunks
.filter((path) => path.endsWith(".css"))
.map((chunk) => JSON.stringify([chunk, [chunk]])),
};
const manifest: FlightManifest = new Proxy({} as any, proxyMethods);
serverCSSManifest["page.js"] = serverCSSManifest.__entry_css__.page;
const req: IncomingMessage = {
url: renderData.url,
method: renderData.method,
Expand All @@ -126,9 +181,9 @@ async function operation(renderData: RenderData) {
dev: true,
buildManifest: {
polyfillFiles: [],
rootMainFiles: LAYOUT_INFO.flatMap(({ chunks }) => chunks).concat(
BOOTSTRAP
),
rootMainFiles: LAYOUT_INFO.flatMap(({ chunks }) => chunks || [])
.concat(BOOTSTRAP)
.filter((path) => !path.endsWith(".css")),
devFiles: [],
ampDevFiles: [],
lowPriorityFiles: [],
Expand All @@ -141,10 +196,10 @@ async function operation(renderData: RenderData) {
...pageModule,
default: undefined,
tree,
pages: [],
pages: ["page.js"],
},
serverComponentManifest: manifest,
serverCSSManifest: {},
serverCSSManifest,
runtime: "nodejs",
serverComponents: true,
assetPrefix: "",
Expand Down
2 changes: 1 addition & 1 deletion crates/next-core/src/app_render/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ pub mod next_layout_entry_transition;

#[turbo_tasks::value(shared)]
pub struct LayoutSegment {
pub file: FileSystemPathVc,
pub file: Option<FileSystemPathVc>,
pub target: FileSystemPathVc,
}

Expand Down
138 changes: 81 additions & 57 deletions crates/next-core/src/app_source.rs
Original file line number Diff line number Diff line change
Expand Up @@ -287,15 +287,17 @@ async fn create_app_source_for_directory(
}
}
}
if let Some(file) = layout.copied() {
let mut list = layouts.await?.clone_value();
list.push(LayoutSegment { file, target }.cell());
layouts = LayoutSegmentsVc::cell(list);
}
if let Some(file) = page.copied() {
let mut list = layouts.await?.clone_value();
list.push(LayoutSegment { file, target }.cell());
let layout_path = LayoutSegmentsVc::cell(list);
let mut list = layouts.await?.clone_value();
list.push(
LayoutSegment {
file: layout.copied(),
target,
}
.cell(),
);
layouts = LayoutSegmentsVc::cell(list);
if let Some(page_path) = page.copied() {


sources.push(create_node_rendered_source(
server_root,
Expand All @@ -304,7 +306,9 @@ async fn create_app_source_for_directory(
context_ssr,
context,
server_root,
layout_path,
layout_path: layouts,
page_path,
target,
project_root,
intermediate_output_path,
}
Expand Down Expand Up @@ -351,6 +355,8 @@ struct AppRenderer {
context: AssetContextVc,
server_root: FileSystemPathVc,
layout_path: LayoutSegmentsVc,
page_path: FileSystemPathVc,
target: FileSystemPathVc,
project_root: FileSystemPathVc,
intermediate_output_path: FileSystemPathVc,
}
Expand All @@ -365,48 +371,59 @@ impl NodeEntry for AppRenderer {
false
};
let layout_path = self.layout_path.await?;
let page = layout_path
.last()
.ok_or_else(|| anyhow!("layout path must not be empty"))?;
let path = page.await?.file.parent();
let page = self.page_path;
let path = page.parent();
let path_value = &*path.await?;
let segments = layout_path
let layout_and_page = layout_path
.iter()
.copied()
.chain(std::iter::once(
LayoutSegment {
file: Some(page),
target: self.target,
}
.cell(),
))
.try_join()
.await?
.await?;
let segments: Vec<(String, Option<(String, String, String)>)> = layout_and_page
.into_iter()
.fold(
(self.server_root, Vec::new()),
|(last_path, mut futures), segment| {
(segment.target, {
futures.push(async move {
let file_str = segment.file.to_string().await?;
let target = &*segment.target.await?;
let segment_path =
last_path.await?.get_path_to(target).unwrap_or_default();
let identifier = magic_identifier::encode(&format!(
"imported namespace {}",
file_str
));
let chunks_identifier = magic_identifier::encode(&format!(
"client chunks for {}",
file_str
));
if let Some(p) = path_value.get_relative_path_to(&*segment.file.await?)
{
Ok((
p,
stringify_str(segment_path),
identifier,
chunks_identifier,
))
if let Some(file) = segment.file {
let file_str = file.to_string().await?;
let identifier = magic_identifier::encode(&format!(
"imported namespace {}",
file_str
));
let chunks_identifier = magic_identifier::encode(&format!(
"client chunks for {}",
file_str
));
if let Some(p) = path_value.get_relative_path_to(&*file.await?) {
Ok((
stringify_str(segment_path),
Some((p, identifier, chunks_identifier)),
))
} else {
Err(anyhow!(
"Unable to generate import as there
is no relative path to the layout module {} from context
path {}",
file_str,
path.to_string().await?
))
}
} else {
Err(anyhow!(
"Unable to generate import as there is no relative path to \
the layout module {} from context path {}",
file_str,
path.to_string().await?
Ok::<(String, Option<(String, String, String)>), _>((
stringify_str(segment_path),
None,
))
}
});
Expand All @@ -419,18 +436,20 @@ impl NodeEntry for AppRenderer {
.try_join()
.await?;
let mut result = String::new();
for (p, _, identifier, chunks_identifier) in segments.iter() {
writeln!(
result,
r#"("TURBOPACK {{ transition: next-layout-entry; chunking-type: parallel }}");
for (_, import) in segments.iter() {
if let Some((p, identifier, chunks_identifier)) = import {
writeln!(
result,
r#"("TURBOPACK {{ transition: next-layout-entry; chunking-type: parallel }}");
import {}, {{ chunks as {} }} from {};
"#,
identifier,
chunks_identifier,
stringify_str(p)
)?
identifier,
chunks_identifier,
stringify_str(p)
)?
}
}
if let Some(page) = path_value.get_relative_path_to(&*page.await?.file.await?) {
if let Some(page) = path_value.get_relative_path_to(&*page.await?) {
writeln!(
result,
r#"("TURBOPACK {{ transition: next-client }}");
Expand All @@ -440,12 +459,16 @@ import BOOTSTRAP from {};
)?;
}
result.push_str("const LAYOUT_INFO = [");
for (_, segment_str_lit, identifier, chunks_identifier) in segments.iter() {
writeln!(
result,
" {{ segment: {segment_str_lit}, module: {identifier}, chunks: \
{chunks_identifier} }},",
)?
for (segment_str_lit, import) in segments.iter() {
if let Some((_, identifier, chunks_identifier)) = import {
writeln!(
result,
" {{ segment: {segment_str_lit}, module: {identifier}, chunks: \
{chunks_identifier} }},",
)?
} else {
writeln!(result, " {{ segment: {segment_str_lit} }},",)?
}
}
result.push_str("];\n\n");
let base_code = next_js_file("entry/app-renderer.tsx");
Expand All @@ -460,15 +483,16 @@ import BOOTSTRAP from {};
(self.context_ssr, self.intermediate_output_path)
};

let chunking_context = DevChunkingContextVc::new_with_layer(
let chunking_context = DevChunkingContextVc::builder(
self.project_root,
intermediate_output_path,
intermediate_output_path.join("chunks"),
self.server_root.join("_next/static/assets"),
false,
"ssr",
)
.into();
.layer("ssr")
.css_chunk_root_path(self.server_root.join("_next/static/chunks"))
.build();

Ok(NodeRenderingEntry {
module: EcmascriptModuleAssetVc::new(
asset.into(),
Expand Down
5 changes: 3 additions & 2 deletions crates/next-core/src/next_client/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ pub fn get_client_chunking_context(
server_root: FileSystemPathVc,
ty: Value<ContextType>,
) -> ChunkingContextVc {
DevChunkingContextVc::new(
DevChunkingContextVc::builder(
project_root,
server_root,
match ty.into_value() {
Expand All @@ -174,8 +174,9 @@ pub fn get_client_chunking_context(
ContextType::Other => server_root.join("/_chunks"),
},
get_client_assets_path(server_root, ty),
true,
)
.hot_module_replacment()
.build()
.into()
}

Expand Down
Loading

0 comments on commit 0610e47

Please sign in to comment.