fix Next.rs API (#53456)
### What? * fixes problems in Next.rs API introduced by #52846 * adds test infrastructure for experimental turbo testing * adds two test cases to verify the infrastructure * add grouping of output logs in run-tests * simplify template loading ### Why? ### How?
This commit is contained in:
parent
eecd8dc146
commit
61baae126f
27 changed files with 1850 additions and 1745 deletions
27
.github/workflows/build_and_test.yml
vendored
27
.github/workflows/build_and_test.yml
vendored
|
@ -125,6 +125,15 @@ jobs:
|
|||
afterBuild: turbo run rust-check
|
||||
secrets: inherit
|
||||
|
||||
test-experimental-turbopack-dev:
|
||||
name: test experimental turbopack dev
|
||||
needs: ['build-native', 'build-next']
|
||||
uses: ./.github/workflows/build_reusable.yml
|
||||
with:
|
||||
skipForDocsOnly: 'yes'
|
||||
afterBuild: RUST_BACKTRACE=0 NEXT_EXTERNAL_TESTS_FILTERS="$(pwd)/test/turbopack-tests-manifest.js" EXPERIMENTAL_TURBOPACK=1 NEXT_E2E_TEST_TIMEOUT=240000 NEXT_TEST_MODE=dev node run-tests.js --test-pattern '^(test\/development)/.*\.test\.(js|jsx|ts|tsx)$' --timings -c ${TEST_CONCURRENCY}
|
||||
secrets: inherit
|
||||
|
||||
test-turbopack-dev:
|
||||
name: test turbopack dev
|
||||
needs: ['build-native', 'build-next']
|
||||
|
@ -134,6 +143,21 @@ jobs:
|
|||
afterBuild: RUST_BACKTRACE=0 NEXT_EXTERNAL_TESTS_FILTERS="$(pwd)/packages/next-swc/crates/next-dev-tests/tests-manifest.js" TURBOPACK=1 __INTERNAL_CUSTOM_TURBOPACK_BINDINGS="$(pwd)/packages/next-swc/native/next-swc.linux-x64-gnu.node" NEXT_E2E_TEST_TIMEOUT=240000 NEXT_TEST_MODE=dev node run-tests.js --test-pattern '^(test\/development)/.*\.test\.(js|jsx|ts|tsx)$' --timings -c ${TEST_CONCURRENCY}
|
||||
secrets: inherit
|
||||
|
||||
test-experimental-turbopack-integration:
|
||||
name: test experimental turbopack integration
|
||||
needs: ['build-native', 'build-next']
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
group: [1]
|
||||
|
||||
uses: ./.github/workflows/build_reusable.yml
|
||||
with:
|
||||
nodeVersion: 16
|
||||
skipForDocsOnly: 'yes'
|
||||
afterBuild: RUST_BACKTRACE=0 NEXT_EXTERNAL_TESTS_FILTERS="$(pwd)/test/turbopack-tests-manifest.js" EXPERIMENTAL_TURBOPACK=1 node run-tests.js --timings -g ${{ matrix.group }}/1 -c ${TEST_CONCURRENCY} --type integration
|
||||
secrets: inherit
|
||||
|
||||
test-turbopack-integration:
|
||||
name: test turbopack integration
|
||||
needs: ['build-native', 'build-next']
|
||||
|
@ -148,7 +172,6 @@ jobs:
|
|||
skipForDocsOnly: 'yes'
|
||||
afterBuild: RUST_BACKTRACE=0 NEXT_EXTERNAL_TESTS_FILTERS="$(pwd)/packages/next-swc/crates/next-dev-tests/tests-manifest.js" TURBOPACK=1 __INTERNAL_CUSTOM_TURBOPACK_BINDINGS="$(pwd)/packages/next-swc/native/next-swc.linux-x64-gnu.node" node run-tests.js --timings -g ${{ matrix.group }}/5 -c ${TEST_CONCURRENCY} --type integration
|
||||
secrets: inherit
|
||||
|
||||
test-next-swc-wasm:
|
||||
name: test next-swc wasm
|
||||
needs: ['build-native', 'build-next']
|
||||
|
@ -244,7 +267,9 @@ jobs:
|
|||
'rust-check',
|
||||
'test-next-swc-wasm',
|
||||
'test-turbopack-dev',
|
||||
'test-experimental-turbopack-dev',
|
||||
'test-turbopack-integration',
|
||||
'test-experimental-turbopack-integration',
|
||||
]
|
||||
|
||||
if: always()
|
||||
|
|
|
@ -451,6 +451,7 @@ impl AppEndpoint {
|
|||
loader_tree,
|
||||
self.app_project.app_dir(),
|
||||
self.pathname.clone(),
|
||||
self.original_name.clone(),
|
||||
self.app_project.project().project_path(),
|
||||
)
|
||||
}
|
||||
|
@ -462,6 +463,7 @@ impl AppEndpoint {
|
|||
self.app_project.edge_rsc_module_context(),
|
||||
Vc::upcast(FileSource::new(path)),
|
||||
self.pathname.clone(),
|
||||
self.original_name.clone(),
|
||||
self.app_project.project().project_path(),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -641,7 +641,7 @@ impl PageEndpoint {
|
|||
.project()
|
||||
.node_root()
|
||||
.join("server".to_string()),
|
||||
this.path.root(),
|
||||
this.pages_project.project().project_path(),
|
||||
this.pages_project.ssr_module_context(),
|
||||
this.pages_project.edge_ssr_module_context(),
|
||||
this.pages_project.project().ssr_chunking_context(),
|
||||
|
@ -660,7 +660,7 @@ impl PageEndpoint {
|
|||
.project()
|
||||
.node_root()
|
||||
.join("server-data".to_string()),
|
||||
this.path.root(),
|
||||
this.pages_project.project().project_path(),
|
||||
this.pages_project.ssr_data_module_context(),
|
||||
this.pages_project.edge_ssr_data_module_context(),
|
||||
this.pages_project.project().ssr_data_chunking_context(),
|
||||
|
@ -681,7 +681,7 @@ impl PageEndpoint {
|
|||
.project()
|
||||
.node_root()
|
||||
.join("server".to_string()),
|
||||
this.path.root(),
|
||||
this.pages_project.project().project_path(),
|
||||
this.pages_project.ssr_module_context(),
|
||||
this.pages_project.edge_ssr_module_context(),
|
||||
this.pages_project.project().ssr_chunking_context(),
|
||||
|
|
|
@ -190,7 +190,7 @@ pub async fn get_app_entries(
|
|||
.map(|(pathname, entrypoint)| async move {
|
||||
Ok(match entrypoint {
|
||||
Entrypoint::AppPage {
|
||||
original_name: _,
|
||||
original_name,
|
||||
loader_tree,
|
||||
} => get_app_page_entry(
|
||||
rsc_context,
|
||||
|
@ -199,10 +199,11 @@ pub async fn get_app_entries(
|
|||
*loader_tree,
|
||||
app_dir,
|
||||
pathname.clone(),
|
||||
original_name.clone(),
|
||||
project_root,
|
||||
),
|
||||
Entrypoint::AppRoute {
|
||||
original_name: _,
|
||||
original_name,
|
||||
path,
|
||||
} => get_app_route_entry(
|
||||
rsc_context,
|
||||
|
@ -210,6 +211,7 @@ pub async fn get_app_entries(
|
|||
rsc_context,
|
||||
Vc::upcast(FileSource::new(*path)),
|
||||
pathname.clone(),
|
||||
original_name.clone(),
|
||||
project_root,
|
||||
),
|
||||
})
|
||||
|
|
|
@ -85,6 +85,7 @@ pub async fn get_app_route_favicon_entry(
|
|||
Vc::upcast(source),
|
||||
// TODO(alexkirsz) Get this from the metadata?
|
||||
"/favicon.ico".to_string(),
|
||||
"/favicon.ico".to_string(),
|
||||
project_root,
|
||||
))
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ use turbopack_binding::{
|
|||
turbo::tasks_fs::{rope::RopeBuilder, File, FileSystemPath},
|
||||
turbopack::{
|
||||
core::{
|
||||
asset::AssetContent, context::AssetContext, issue::IssueExt, module::Module,
|
||||
asset::AssetContent, context::AssetContext, issue::IssueExt,
|
||||
reference_type::ReferenceType, virtual_source::VirtualSource,
|
||||
},
|
||||
ecmascript::{chunk::EcmascriptChunkPlaceable, utils::StringifyJs},
|
||||
|
@ -22,7 +22,7 @@ use crate::{
|
|||
next_app::UnsupportedDynamicMetadataIssue,
|
||||
next_server_component::NextServerComponentTransition,
|
||||
parse_segment_config_from_loader_tree,
|
||||
util::{load_next_js, resolve_next_module, NextRuntime},
|
||||
util::{load_next_js_template, virtual_next_js_template_path, NextRuntime},
|
||||
};
|
||||
|
||||
/// Computes the entry for a Next.js app page.
|
||||
|
@ -33,6 +33,7 @@ pub async fn get_app_page_entry(
|
|||
loader_tree: Vc<LoaderTree>,
|
||||
app_dir: Vc<FileSystemPath>,
|
||||
pathname: String,
|
||||
original_name: String,
|
||||
project_root: Vc<FileSystemPath>,
|
||||
) -> Result<Vc<AppEntry>> {
|
||||
let config = parse_segment_config_from_loader_tree(loader_tree, Vc::upcast(nodejs_context));
|
||||
|
@ -77,12 +78,12 @@ pub async fn get_app_page_entry(
|
|||
|
||||
let pages = pages.iter().map(|page| page.to_string()).try_join().await?;
|
||||
|
||||
let original_name = get_original_page_name(&pathname);
|
||||
let original_page_name = get_original_page_name(&original_name);
|
||||
|
||||
let template_file = "/dist/esm/build/webpack/loaders/next-route-loader/templates/app-page.js";
|
||||
let template_file = "build/webpack/loaders/next-route-loader/templates/app-page.js";
|
||||
|
||||
// Load the file from the next.js codebase.
|
||||
let file = load_next_js(project_root, template_file).await?.await?;
|
||||
let file = load_next_js_template(project_root, template_file.to_string()).await?;
|
||||
|
||||
let mut file = file
|
||||
.to_str()?
|
||||
|
@ -96,7 +97,7 @@ pub async fn get_app_page_entry(
|
|||
)
|
||||
.replace(
|
||||
"\"VAR_ORIGINAL_PATHNAME\"",
|
||||
&StringifyJs(&original_name).to_string(),
|
||||
&StringifyJs(&original_page_name).to_string(),
|
||||
)
|
||||
// TODO(alexkirsz) Support custom global error.
|
||||
.replace(
|
||||
|
@ -129,13 +130,7 @@ pub async fn get_app_page_entry(
|
|||
|
||||
let file = File::from(result.build());
|
||||
|
||||
let resolve_result = resolve_next_module(project_root, template_file).await?;
|
||||
|
||||
let Some(template_path) = *resolve_result.first_module().await? else {
|
||||
bail!("Expected to find module");
|
||||
};
|
||||
|
||||
let template_path = template_path.ident().path();
|
||||
let template_path = virtual_next_js_template_path(project_root, template_file.to_string());
|
||||
|
||||
let source = VirtualSource::new(template_path, AssetContent::file(file.into()));
|
||||
|
||||
|
@ -152,7 +147,7 @@ pub async fn get_app_page_entry(
|
|||
|
||||
Ok(AppEntry {
|
||||
pathname: pathname.to_string(),
|
||||
original_name,
|
||||
original_name: original_page_name,
|
||||
rsc_entry,
|
||||
config,
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@ use turbopack_binding::{
|
|||
core::{
|
||||
asset::AssetContent,
|
||||
context::AssetContext,
|
||||
module::Module,
|
||||
reference_type::{
|
||||
EcmaScriptModulesReferenceSubType, EntryReferenceSubType, ReferenceType,
|
||||
},
|
||||
|
@ -22,7 +21,7 @@ use turbopack_binding::{
|
|||
use crate::{
|
||||
next_app::AppEntry,
|
||||
parse_segment_config_from_source,
|
||||
util::{load_next_js, resolve_next_module, NextRuntime},
|
||||
util::{load_next_js_template, virtual_next_js_template_path, NextRuntime},
|
||||
};
|
||||
|
||||
/// Computes the entry for a Next.js app route.
|
||||
|
@ -32,6 +31,7 @@ pub async fn get_app_route_entry(
|
|||
edge_context: Vc<ModuleAssetContext>,
|
||||
source: Vc<Box<dyn Source>>,
|
||||
pathname: String,
|
||||
original_name: String,
|
||||
project_root: Vc<FileSystemPath>,
|
||||
) -> Result<Vc<AppEntry>> {
|
||||
let config = parse_segment_config_from_source(
|
||||
|
@ -49,13 +49,13 @@ pub async fn get_app_route_entry(
|
|||
|
||||
let mut result = RopeBuilder::default();
|
||||
|
||||
let original_name = get_original_route_name(&pathname);
|
||||
let original_page_name = get_original_route_name(&original_name);
|
||||
let path = source.ident().path();
|
||||
|
||||
let template_file = "/dist/esm/build/webpack/loaders/next-route-loader/templates/app-route.js";
|
||||
let template_file = "build/webpack/loaders/next-route-loader/templates/app-route.js";
|
||||
|
||||
// Load the file from the next.js codebase.
|
||||
let file = load_next_js(project_root, template_file).await?.await?;
|
||||
let file = load_next_js_template(project_root, template_file.to_string()).await?;
|
||||
|
||||
let mut file = file
|
||||
.to_str()?
|
||||
|
@ -78,7 +78,7 @@ pub async fn get_app_route_entry(
|
|||
)
|
||||
.replace(
|
||||
"\"VAR_ORIGINAL_PATHNAME\"",
|
||||
&StringifyJs(&original_name).to_string(),
|
||||
&StringifyJs(&original_page_name).to_string(),
|
||||
)
|
||||
.replace(
|
||||
"\"VAR_RESOLVED_PAGE_PATH\"",
|
||||
|
@ -98,13 +98,7 @@ pub async fn get_app_route_entry(
|
|||
|
||||
let file = File::from(result.build());
|
||||
|
||||
let resolve_result = resolve_next_module(project_root, template_file).await?;
|
||||
|
||||
let Some(template_path) = *resolve_result.first_module().await? else {
|
||||
bail!("Expected to find module");
|
||||
};
|
||||
|
||||
let template_path = template_path.ident().path();
|
||||
let template_path = virtual_next_js_template_path(project_root, template_file.to_string());
|
||||
|
||||
let virtual_source = VirtualSource::new(template_path, AssetContent::file(file.into()));
|
||||
|
||||
|
@ -132,7 +126,7 @@ pub async fn get_app_route_entry(
|
|||
|
||||
Ok(AppEntry {
|
||||
pathname: pathname.to_string(),
|
||||
original_name,
|
||||
original_name: original_page_name,
|
||||
rsc_entry,
|
||||
config,
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ use crate::{
|
|||
issue::NextFontIssue,
|
||||
util::{get_scoped_font_family, FontFamilyType},
|
||||
},
|
||||
util::load_next_json,
|
||||
util::load_next_js_templateon,
|
||||
};
|
||||
|
||||
/// An entry in the Google fonts metrics map
|
||||
|
@ -54,8 +54,11 @@ pub(super) async fn get_font_fallback(
|
|||
Ok(match &options.fallback {
|
||||
Some(fallback) => FontFallback::Manual(Vc::cell(fallback.clone())).cell(),
|
||||
None => {
|
||||
let metrics_json =
|
||||
load_next_json(context, "/dist/server/capsize-font-metrics.json").await?;
|
||||
let metrics_json = load_next_js_templateon(
|
||||
context,
|
||||
"dist/server/capsize-font-metrics.json".to_string(),
|
||||
)
|
||||
.await?;
|
||||
let fallback = lookup_fallback(
|
||||
&options.font_family,
|
||||
metrics_json,
|
||||
|
|
|
@ -48,7 +48,7 @@ use super::{
|
|||
get_request_hash, get_request_id, get_scoped_font_family, FontCssProperties, FontFamilyType,
|
||||
},
|
||||
};
|
||||
use crate::{embed_js::next_js_file_path, util::load_next_json};
|
||||
use crate::{embed_js::next_js_file_path, util::load_next_js_templateon};
|
||||
|
||||
pub mod font_fallback;
|
||||
pub mod options;
|
||||
|
@ -266,9 +266,9 @@ impl ImportMappingReplacement for NextFontGoogleCssModuleReplacer {
|
|||
|
||||
#[turbo_tasks::function]
|
||||
async fn load_font_data(project_root: Vc<FileSystemPath>) -> Result<Vc<FontData>> {
|
||||
let data: FontData = load_next_json(
|
||||
let data: FontData = load_next_js_templateon(
|
||||
project_root,
|
||||
"/dist/compiled/@next/font/dist/google/font-data.json",
|
||||
"dist/compiled/@next/font/dist/google/font-data.json".to_string(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
|
|
|
@ -229,6 +229,16 @@ pub async fn get_next_server_import_map(
|
|||
ServerContextType::AppSSR { .. }
|
||||
| ServerContextType::AppRSC { .. }
|
||||
| ServerContextType::AppRoute { .. } => {
|
||||
match mode {
|
||||
NextMode::Development | NextMode::Build => {
|
||||
import_map.insert_wildcard_alias("next/dist/server/", external);
|
||||
import_map.insert_wildcard_alias("next/dist/shared/", external);
|
||||
}
|
||||
NextMode::DevServer => {
|
||||
// The sandbox can't be bundled and needs to be external
|
||||
import_map.insert_exact_alias("next/dist/server/web/sandbox", external);
|
||||
}
|
||||
}
|
||||
import_map.insert_exact_alias(
|
||||
"next/head",
|
||||
request_to_import_mapping(project_path, "next/dist/client/components/noop-head"),
|
||||
|
@ -237,9 +247,6 @@ pub async fn get_next_server_import_map(
|
|||
"next/dynamic",
|
||||
request_to_import_mapping(project_path, "next/dist/shared/lib/app-dynamic"),
|
||||
);
|
||||
|
||||
// The sandbox can't be bundled and needs to be external
|
||||
import_map.insert_exact_alias("next/dist/server/web/sandbox", external);
|
||||
}
|
||||
ServerContextType::Middleware => {}
|
||||
}
|
||||
|
@ -620,17 +627,19 @@ async fn package_lookup_resolve_options(
|
|||
}
|
||||
|
||||
#[turbo_tasks::function]
|
||||
pub async fn get_next_package(project_path: Vc<FileSystemPath>) -> Result<Vc<FileSystemPath>> {
|
||||
pub async fn get_next_package(context_directory: Vc<FileSystemPath>) -> Result<Vc<FileSystemPath>> {
|
||||
let result = resolve(
|
||||
project_path,
|
||||
context_directory,
|
||||
Request::parse(Value::new(Pattern::Constant(
|
||||
"next/package.json".to_string(),
|
||||
))),
|
||||
package_lookup_resolve_options(project_path),
|
||||
package_lookup_resolve_options(context_directory),
|
||||
);
|
||||
let assets = result.primary_sources().await?;
|
||||
let asset = *assets.first().context("Next.js package not found")?;
|
||||
Ok(asset.ident().path().parent())
|
||||
let source = result
|
||||
.first_source()
|
||||
.await?
|
||||
.context("Next.js package not found")?;
|
||||
Ok(source.ident().path().parent())
|
||||
}
|
||||
|
||||
pub async fn insert_alias_option<const N: usize>(
|
||||
|
|
|
@ -13,7 +13,6 @@ use turbopack_binding::{
|
|||
core::{
|
||||
asset::AssetContent,
|
||||
context::AssetContext,
|
||||
module::Module,
|
||||
reference_type::{EntryReferenceSubType, ReferenceType},
|
||||
source::Source,
|
||||
virtual_source::VirtualSource,
|
||||
|
@ -22,7 +21,7 @@ use turbopack_binding::{
|
|||
},
|
||||
};
|
||||
|
||||
use crate::util::{load_next_js, resolve_next_module};
|
||||
use crate::util::{load_next_js_template, virtual_next_js_template_path};
|
||||
|
||||
#[turbo_tasks::function]
|
||||
pub async fn create_page_ssr_entry_module(
|
||||
|
@ -43,17 +42,17 @@ pub async fn create_page_ssr_entry_module(
|
|||
let template_file = match reference_type {
|
||||
ReferenceType::Entry(EntryReferenceSubType::Page) => {
|
||||
// Load the Page entry file.
|
||||
"/dist/esm/build/webpack/loaders/next-route-loader/templates/pages.js"
|
||||
"build/webpack/loaders/next-route-loader/templates/pages.js"
|
||||
}
|
||||
ReferenceType::Entry(EntryReferenceSubType::PagesApi) => {
|
||||
// Load the Pages API entry file.
|
||||
"/dist/esm/build/webpack/loaders/next-route-loader/templates/pages-api.js"
|
||||
"build/webpack/loaders/next-route-loader/templates/pages-api.js"
|
||||
}
|
||||
_ => bail!("Invalid path type"),
|
||||
};
|
||||
|
||||
// Load the file from the next.js codebase.
|
||||
let file = load_next_js(project_root, template_file).await?.await?;
|
||||
let file = load_next_js_template(project_root, template_file.to_string()).await?;
|
||||
|
||||
let mut file = file
|
||||
.to_str()?
|
||||
|
@ -103,13 +102,7 @@ pub async fn create_page_ssr_entry_module(
|
|||
|
||||
let file = File::from(result.build());
|
||||
|
||||
let resolve_result = resolve_next_module(project_root, template_file).await?;
|
||||
|
||||
let Some(template_path) = *resolve_result.first_module().await? else {
|
||||
bail!("Expected to find module");
|
||||
};
|
||||
|
||||
let template_path = template_path.ident().path();
|
||||
let template_path = virtual_next_js_template_path(project_root, template_file.to_string());
|
||||
|
||||
let source = VirtualSource::new(template_path, AssetContent::file(file.into()));
|
||||
|
||||
|
|
|
@ -2,22 +2,16 @@ use anyhow::{bail, Result};
|
|||
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
||||
use serde_json::Value as JsonValue;
|
||||
use swc_core::ecma::ast::Program;
|
||||
use turbo_tasks::{trace::TraceRawVcs, TaskInput, Value, ValueDefault, ValueToString, Vc};
|
||||
use turbo_tasks::{trace::TraceRawVcs, TaskInput, ValueDefault, ValueToString, Vc};
|
||||
use turbo_tasks_fs::rope::Rope;
|
||||
use turbopack_binding::{
|
||||
turbo::tasks_fs::{json::parse_json_rope_with_source_context, FileContent, FileSystemPath},
|
||||
turbopack::{
|
||||
core::{
|
||||
asset::Asset,
|
||||
environment::{ServerAddr, ServerInfo},
|
||||
ident::AssetIdent,
|
||||
issue::{Issue, IssueExt, IssueSeverity, OptionIssueSource},
|
||||
issue::{Issue, IssueExt, IssueSeverity},
|
||||
module::Module,
|
||||
reference_type::{EcmaScriptModulesReferenceSubType, ReferenceType},
|
||||
resolve::{
|
||||
self, handle_resolve_error, node::node_cjs_resolve_options, parse::Request,
|
||||
ModuleResolveResult,
|
||||
},
|
||||
},
|
||||
ecmascript::{
|
||||
analyzer::{JsValue, ObjectPart},
|
||||
|
@ -28,7 +22,10 @@ use turbopack_binding::{
|
|||
},
|
||||
};
|
||||
|
||||
use crate::next_config::{NextConfig, OutputType};
|
||||
use crate::{
|
||||
next_config::{NextConfig, OutputType},
|
||||
next_import_map::get_next_package,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, TaskInput)]
|
||||
pub enum PathType {
|
||||
|
@ -333,14 +330,16 @@ fn parse_config_from_js_value(module: Vc<Box<dyn Module>>, value: &JsValue) -> N
|
|||
config
|
||||
}
|
||||
|
||||
pub async fn load_next_js(context: Vc<FileSystemPath>, path: &str) -> Result<Vc<Rope>> {
|
||||
let resolve_result = resolve_next_module(context, path).await?;
|
||||
#[turbo_tasks::function]
|
||||
pub async fn load_next_js_template(
|
||||
project_path: Vc<FileSystemPath>,
|
||||
path: String,
|
||||
) -> Result<Vc<Rope>> {
|
||||
let file_path = get_next_package(project_path)
|
||||
.join("dist/esm".to_string())
|
||||
.join(path);
|
||||
|
||||
let Some(js_asset) = *resolve_result.first_module().await? else {
|
||||
bail!("Expected to find module");
|
||||
};
|
||||
|
||||
let content = &*js_asset.content().file_content().await?;
|
||||
let content = &*file_path.read().await?;
|
||||
|
||||
let FileContent::Content(file) = content else {
|
||||
bail!("Expected file content for file");
|
||||
|
@ -349,44 +348,23 @@ pub async fn load_next_js(context: Vc<FileSystemPath>, path: &str) -> Result<Vc<
|
|||
Ok(file.content().to_owned().cell())
|
||||
}
|
||||
|
||||
pub async fn resolve_next_module(
|
||||
context: Vc<FileSystemPath>,
|
||||
path: &str,
|
||||
) -> Result<Vc<ModuleResolveResult>> {
|
||||
let request = Request::module(
|
||||
"next".to_owned(),
|
||||
Value::new(path.to_string().into()),
|
||||
Vc::cell(None),
|
||||
);
|
||||
let resolve_options = node_cjs_resolve_options(context.root());
|
||||
|
||||
let resolve_result = handle_resolve_error(
|
||||
resolve::resolve(context, request, resolve_options).as_raw_module_result(),
|
||||
Value::new(ReferenceType::EcmaScriptModules(
|
||||
EcmaScriptModulesReferenceSubType::Undefined,
|
||||
)),
|
||||
context,
|
||||
request,
|
||||
resolve_options,
|
||||
OptionIssueSource::none(),
|
||||
IssueSeverity::Error.cell(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(resolve_result)
|
||||
#[turbo_tasks::function]
|
||||
pub fn virtual_next_js_template_path(
|
||||
project_path: Vc<FileSystemPath>,
|
||||
path: String,
|
||||
) -> Vc<FileSystemPath> {
|
||||
get_next_package(project_path)
|
||||
.join("dist/esm".to_string())
|
||||
.join(path)
|
||||
}
|
||||
|
||||
pub async fn load_next_json<T: DeserializeOwned>(
|
||||
context: Vc<FileSystemPath>,
|
||||
path: &str,
|
||||
pub async fn load_next_js_templateon<T: DeserializeOwned>(
|
||||
project_path: Vc<FileSystemPath>,
|
||||
path: String,
|
||||
) -> Result<T> {
|
||||
let resolve_result = resolve_next_module(context, path).await?;
|
||||
let file_path = get_next_package(project_path).join(path);
|
||||
|
||||
let Some(metrics_asset) = *resolve_result.first_module().await? else {
|
||||
bail!("Expected to find module");
|
||||
};
|
||||
|
||||
let content = &*metrics_asset.content().file_content().await?;
|
||||
let content = &*file_path.read().await?;
|
||||
|
||||
let FileContent::Content(file) = content else {
|
||||
bail!("Expected file content for metrics data");
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
import type { LoaderTree } from '../../../../../server/lib/app-dir-module'
|
||||
|
||||
// @ts-ignore this need to be imported from next/dist to be external
|
||||
import * as module from 'next/dist/server/future/route-modules/app-page/module'
|
||||
import { RouteKind } from '../../../../../server/future/route-kind'
|
||||
import { AppPageRouteModule } from '../../../../../server/future/route-modules/app-page/module'
|
||||
|
||||
const AppPageRouteModule =
|
||||
module.AppPageRouteModule as unknown as typeof import('../../../../../server/future/route-modules/app-page/module').AppPageRouteModule
|
||||
|
||||
// These are injected by the loader afterwards.
|
||||
declare const tree: LoaderTree
|
||||
|
|
|
@ -1,14 +1,16 @@
|
|||
import '../../../../../server/node-polyfill-headers'
|
||||
|
||||
import {
|
||||
AppRouteRouteModule,
|
||||
type AppRouteRouteModuleOptions,
|
||||
} from '../../../../../server/future/route-modules/app-route/module'
|
||||
// @ts-ignore this need to be imported from next/dist to be external
|
||||
import * as module from 'next/dist/server/future/route-modules/app-route/module'
|
||||
import type { AppRouteRouteModuleOptions } from '../../../../../server/future/route-modules/app-route/module'
|
||||
import { RouteKind } from '../../../../../server/future/route-kind'
|
||||
|
||||
// @ts-expect-error - replaced by webpack/turbopack loader
|
||||
import * as userland from 'VAR_USERLAND'
|
||||
|
||||
const AppRouteRouteModule =
|
||||
module.AppRouteRouteModule as unknown as typeof import('../../../../../server/future/route-modules/app-route/module').AppRouteRouteModule
|
||||
|
||||
// These are injected by the loader afterwards. This is injected as a variable
|
||||
// instead of a replacement because this could also be `undefined` instead of
|
||||
// an empty string.
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
import { PagesAPIRouteModule } from '../../../../../server/future/route-modules/pages-api/module'
|
||||
// @ts-ignore this need to be imported from next/dist to be external
|
||||
import * as module from 'next/dist/server/future/route-modules/pages-api/module'
|
||||
import { RouteKind } from '../../../../../server/future/route-kind'
|
||||
import { hoist } from '../helpers'
|
||||
|
||||
const PagesAPIRouteModule =
|
||||
module.PagesAPIRouteModule as unknown as typeof import('../../../../../server/future/route-modules/pages-api/module').PagesAPIRouteModule
|
||||
|
||||
// Import the userland code.
|
||||
// @ts-expect-error - replaced by webpack/turbopack loader
|
||||
import * as userland from 'VAR_USERLAND'
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { PagesRouteModule } from '../../../../../server/future/route-modules/pages/module'
|
||||
// @ts-ignore this need to be imported from next/dist to be external
|
||||
import * as module from 'next/dist/server/future/route-modules/pages/module'
|
||||
import { RouteKind } from '../../../../../server/future/route-kind'
|
||||
import { hoist } from '../helpers'
|
||||
|
||||
|
@ -12,6 +13,9 @@ import App from 'VAR_MODULE_APP'
|
|||
// @ts-expect-error - replaced by webpack/turbopack loader
|
||||
import * as userland from 'VAR_USERLAND'
|
||||
|
||||
const PagesRouteModule =
|
||||
module.PagesRouteModule as unknown as typeof import('../../../../../server/future/route-modules/pages/module').PagesRouteModule
|
||||
|
||||
// Re-export the component (should be the default export).
|
||||
export default hoist(userland, 'default')
|
||||
|
||||
|
|
31
run-tests.js
31
run-tests.js
|
@ -12,6 +12,9 @@ const { createNextInstall } = require('./test/lib/create-next-install')
|
|||
const glob = promisify(_glob)
|
||||
const exec = promisify(execOrig)
|
||||
|
||||
const GROUP = process.env.CI ? '##[group]' : ''
|
||||
const ENDGROUP = process.env.CI ? '##[endgroup]' : ''
|
||||
|
||||
// Try to read an external array-based json to filter tests to be allowed / or disallowed.
|
||||
// If process.argv contains a test to be executed, this'll append it to the list.
|
||||
const externalTestsFilterLists = process.env.NEXT_EXTERNAL_TESTS_FILTERS
|
||||
|
@ -272,7 +275,9 @@ async function main() {
|
|||
return cleanUpAndExit(1)
|
||||
}
|
||||
|
||||
console.log('Running tests:', '\n', ...testNames.map((name) => `${name}\n`))
|
||||
console.log(`${GROUP}Running tests:
|
||||
${testNames.join('\n')}
|
||||
${ENDGROUP}`)
|
||||
|
||||
const hasIsolatedTests = testNames.some((test) => {
|
||||
return configuredTestTypes.some(
|
||||
|
@ -288,7 +293,7 @@ async function main() {
|
|||
// for isolated next tests: e2e, dev, prod we create
|
||||
// a starter Next.js install to re-use to speed up tests
|
||||
// to avoid having to run yarn each time
|
||||
console.log('Creating Next.js install for isolated tests')
|
||||
console.log(`${GROUP}Creating Next.js install for isolated tests`)
|
||||
const reactVersion = process.env.NEXT_TEST_REACT_VERSION || 'latest'
|
||||
const { installDir, pkgPaths, tmpRepoDir } = await createNextInstall({
|
||||
parentSpan: mockTrace(),
|
||||
|
@ -307,9 +312,11 @@ async function main() {
|
|||
process.env.NEXT_TEST_PKG_PATHS = JSON.stringify(serializedPkgPaths)
|
||||
process.env.NEXT_TEST_TEMP_REPO = tmpRepoDir
|
||||
process.env.NEXT_TEST_STARTER = installDir
|
||||
console.log(`${ENDGROUP}`)
|
||||
}
|
||||
|
||||
const sema = new Sema(concurrency, { capacity: testNames.length })
|
||||
const outputSema = new Sema(1, { capacity: testNames.length })
|
||||
const children = new Set()
|
||||
const jestPath = path.join(
|
||||
__dirname,
|
||||
|
@ -374,7 +381,7 @@ async function main() {
|
|||
if (hideOutput) {
|
||||
outputChunks.push({ type, chunk })
|
||||
} else {
|
||||
process.stderr.write(chunk)
|
||||
process.stdout.write(chunk)
|
||||
}
|
||||
}
|
||||
child.stdout.on('data', handleOutput('stdout'))
|
||||
|
@ -386,20 +393,22 @@ async function main() {
|
|||
children.delete(child)
|
||||
if (code !== 0 || signal !== null) {
|
||||
if (hideOutput) {
|
||||
await outputSema.acquire()
|
||||
process.stdout.write(`${GROUP}${test} output\n`)
|
||||
// limit out to last 64kb so that we don't
|
||||
// run out of log room in CI
|
||||
outputChunks.forEach(({ type, chunk }) => {
|
||||
if (type === 'stdout') {
|
||||
for (const { chunk } of outputChunks) {
|
||||
process.stdout.write(chunk)
|
||||
} else {
|
||||
process.stderr.write(chunk)
|
||||
}
|
||||
})
|
||||
process.stdout.write(`end of ${test} output\n${ENDGROUP}\n`)
|
||||
outputSema.release()
|
||||
}
|
||||
const err = new Error(
|
||||
code ? `failed with code: ${code}` : `failed with signal: ${signal}`
|
||||
)
|
||||
err.output = outputChunks.map((chunk) => chunk.toString()).join('')
|
||||
err.output = outputChunks
|
||||
.map(({ chunk }) => chunk.toString())
|
||||
.join('')
|
||||
|
||||
return reject(err)
|
||||
}
|
||||
|
@ -498,11 +507,15 @@ async function main() {
|
|||
if ((!passed || shouldContinueTestsOnError) && isTestJob) {
|
||||
try {
|
||||
const testsOutput = await fs.readFile(`${test}${RESULTS_EXT}`, 'utf8')
|
||||
await outputSema.acquire()
|
||||
if (GROUP) console.log(`${GROUP}Result as JSON for tooling`)
|
||||
console.log(
|
||||
`--test output start--`,
|
||||
testsOutput,
|
||||
`--test output end--`
|
||||
)
|
||||
if (ENDGROUP) console.log(ENDGROUP)
|
||||
outputSema.release()
|
||||
} catch (err) {
|
||||
console.log(`Failed to load test output`, err)
|
||||
}
|
||||
|
|
|
@ -5,7 +5,9 @@ import { describeVariants as describe } from 'next-test-utils'
|
|||
import { outdent } from 'outdent'
|
||||
|
||||
// TODO-APP: Investigate snapshot mismatch
|
||||
describe.each(['default', 'turbo'])('ReactRefreshLogBox app %s', () => {
|
||||
describe.each(['default', 'turbo', 'experimentalTurbo'])(
|
||||
'ReactRefreshLogBox app %s',
|
||||
() => {
|
||||
const { next } = nextTestSetup({
|
||||
files: new FileRef(path.join(__dirname, 'fixtures', 'default-template')),
|
||||
dependencies: {
|
||||
|
@ -143,4 +145,5 @@ describe.each(['default', 'turbo'])('ReactRefreshLogBox app %s', () => {
|
|||
|
||||
await cleanup()
|
||||
})
|
||||
})
|
||||
}
|
||||
)
|
||||
|
|
|
@ -5,7 +5,9 @@ import { check, describeVariants as describe } from 'next-test-utils'
|
|||
import path from 'path'
|
||||
import { outdent } from 'outdent'
|
||||
|
||||
describe.each(['default', 'turbo'])('ReactRefreshLogBox app %s', () => {
|
||||
describe.each(['default', 'turbo', 'experimentalTurbo'])(
|
||||
'ReactRefreshLogBox app %s',
|
||||
() => {
|
||||
const { next } = nextTestSetup({
|
||||
files: new FileRef(path.join(__dirname, 'fixtures', 'default-template')),
|
||||
dependencies: {
|
||||
|
@ -937,4 +939,5 @@ describe.each(['default', 'turbo'])('ReactRefreshLogBox app %s', () => {
|
|||
await cleanup()
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
)
|
||||
|
|
|
@ -5,7 +5,9 @@ import { check, describeVariants as describe } from 'next-test-utils'
|
|||
import path from 'path'
|
||||
import { outdent } from 'outdent'
|
||||
|
||||
describe.each(['default', 'turbo'])('Error recovery app %s', () => {
|
||||
describe.each(['default', 'turbo', 'experimentalTurbo'])(
|
||||
'Error recovery app %s',
|
||||
() => {
|
||||
const { next } = nextTestSetup({
|
||||
files: new FileRef(path.join(__dirname, 'fixtures', 'default-template')),
|
||||
dependencies: {
|
||||
|
@ -492,4 +494,5 @@ describe.each(['default', 'turbo'])('Error recovery app %s', () => {
|
|||
|
||||
await cleanup()
|
||||
})
|
||||
})
|
||||
}
|
||||
)
|
||||
|
|
|
@ -4,7 +4,9 @@ import { describeVariants as describe } from 'next-test-utils'
|
|||
import { outdent } from 'outdent'
|
||||
import path from 'path'
|
||||
|
||||
describe.each(['default', 'turbo'])('ReactRefreshLogBox %s', () => {
|
||||
describe.each(['default', 'turbo', 'experimentalTurbo'])(
|
||||
'ReactRefreshLogBox %s',
|
||||
() => {
|
||||
const { next } = nextTestSetup({
|
||||
files: new FileRef(path.join(__dirname, 'fixtures', 'default-template')),
|
||||
skipStart: true,
|
||||
|
@ -218,4 +220,5 @@ describe.each(['default', 'turbo'])('ReactRefreshLogBox %s', () => {
|
|||
expect(await session.hasRedbox(false)).toBe(false)
|
||||
await cleanup()
|
||||
})
|
||||
})
|
||||
}
|
||||
)
|
||||
|
|
|
@ -4,7 +4,9 @@ import { describeVariants as describe } from 'next-test-utils'
|
|||
import { outdent } from 'outdent'
|
||||
import path from 'path'
|
||||
|
||||
describe.each(['default', 'turbo'])('ReactRefreshLogBox %s', () => {
|
||||
describe.each(['default', 'turbo', 'experimentalTurbo'])(
|
||||
'ReactRefreshLogBox %s',
|
||||
() => {
|
||||
const { next } = nextTestSetup({
|
||||
files: new FileRef(path.join(__dirname, 'fixtures', 'default-template')),
|
||||
skipStart: true,
|
||||
|
@ -146,4 +148,5 @@ describe.each(['default', 'turbo'])('ReactRefreshLogBox %s', () => {
|
|||
|
||||
await cleanup()
|
||||
})
|
||||
})
|
||||
}
|
||||
)
|
||||
|
|
|
@ -5,7 +5,9 @@ import { describeVariants as describe } from 'next-test-utils'
|
|||
import path from 'path'
|
||||
import { outdent } from 'outdent'
|
||||
|
||||
describe.each(['default', 'turbo'])('ReactRefreshLogBox %s', () => {
|
||||
describe.each(['default', 'turbo', 'experimentalTurbo'])(
|
||||
'ReactRefreshLogBox %s',
|
||||
() => {
|
||||
const { next } = nextTestSetup({
|
||||
files: new FileRef(path.join(__dirname, 'fixtures', 'default-template')),
|
||||
skipStart: true,
|
||||
|
@ -722,4 +724,5 @@ describe.each(['default', 'turbo'])('ReactRefreshLogBox %s', () => {
|
|||
|
||||
await cleanup()
|
||||
})
|
||||
})
|
||||
}
|
||||
)
|
||||
|
|
|
@ -5,7 +5,9 @@ import { check, describeVariants as describe } from 'next-test-utils'
|
|||
import { outdent } from 'outdent'
|
||||
import path from 'path'
|
||||
|
||||
describe.each(['default', 'turbo'])('ReactRefreshLogBox %s', () => {
|
||||
describe.each(['default', 'turbo', 'experimentalTurbo'])(
|
||||
'ReactRefreshLogBox %s',
|
||||
() => {
|
||||
const { next } = nextTestSetup({
|
||||
files: new FileRef(path.join(__dirname, 'fixtures', 'default-template')),
|
||||
skipStart: true,
|
||||
|
@ -474,4 +476,5 @@ describe.each(['default', 'turbo'])('ReactRefreshLogBox %s', () => {
|
|||
|
||||
await cleanup()
|
||||
})
|
||||
})
|
||||
}
|
||||
)
|
||||
|
|
|
@ -26,7 +26,10 @@ import type { RequestInit, Response } from 'node-fetch'
|
|||
import type { NextServer } from 'next/dist/server/next'
|
||||
import type { BrowserInterface } from './browsers/base'
|
||||
|
||||
import { shouldRunTurboDevTest } from './turbo'
|
||||
import {
|
||||
shouldRunExperimentalTurboDevTest,
|
||||
shouldRunTurboDevTest,
|
||||
} from './turbo'
|
||||
|
||||
export { shouldRunTurboDevTest }
|
||||
|
||||
|
@ -327,6 +330,7 @@ export interface NextDevOptions {
|
|||
bootupMarker?: RegExp
|
||||
nextStart?: boolean
|
||||
turbo?: boolean
|
||||
experimentalTurbo?: boolean
|
||||
|
||||
stderr?: false
|
||||
stdout?: false
|
||||
|
@ -374,12 +378,19 @@ export function runNextCommandDev(
|
|||
const bootupMarkers = {
|
||||
dev: /compiled .*successfully/i,
|
||||
turbo: /started server/i,
|
||||
experimentalTurbo: /started server/i,
|
||||
start: /started server/i,
|
||||
}
|
||||
if (
|
||||
(opts.bootupMarker && opts.bootupMarker.test(message)) ||
|
||||
bootupMarkers[
|
||||
opts.nextStart || stdOut ? 'start' : opts?.turbo ? 'turbo' : 'dev'
|
||||
opts.nextStart || stdOut
|
||||
? 'start'
|
||||
: opts?.experimentalTurbo
|
||||
? 'experimentalTurbo'
|
||||
: opts?.turbo
|
||||
? 'turbo'
|
||||
: 'dev'
|
||||
].test(message)
|
||||
) {
|
||||
if (!didResolve) {
|
||||
|
@ -434,6 +445,7 @@ export function launchApp(
|
|||
) {
|
||||
const options = opts ?? {}
|
||||
const useTurbo = shouldRunTurboDevTest()
|
||||
const useExperimentalTurbo = shouldRunExperimentalTurboDevTest()
|
||||
|
||||
return runNextCommandDev(
|
||||
[useTurbo ? '--turbo' : undefined, dir, '-p', port as string].filter(
|
||||
|
@ -443,6 +455,7 @@ export function launchApp(
|
|||
{
|
||||
...options,
|
||||
turbo: useTurbo,
|
||||
experimentalTurbo: useExperimentalTurbo,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
@ -1008,23 +1021,30 @@ export function findAllTelemetryEvents(output: string, eventName: string) {
|
|||
return events.filter((e) => e.eventName === eventName).map((e) => e.payload)
|
||||
}
|
||||
|
||||
type TestVariants = 'default' | 'turbo'
|
||||
type TestVariants = 'default' | 'turbo' | 'experimentalTurbo'
|
||||
|
||||
// WEB-168: There are some differences / incompletes in turbopack implementation enforces jest requires to update
|
||||
// test snapshot when run against turbo. This fn returns describe, or describe.skip dependes on the running context
|
||||
// to avoid force-snapshot update per each runs until turbopack update includes all the changes.
|
||||
export function getSnapshotTestDescribe(variant: TestVariants) {
|
||||
const runningEnv = variant ?? 'default'
|
||||
if (runningEnv !== 'default' && runningEnv !== 'turbo') {
|
||||
if (
|
||||
runningEnv !== 'default' &&
|
||||
runningEnv !== 'turbo' &&
|
||||
runningEnv !== 'experimentalTurbo'
|
||||
) {
|
||||
throw new Error(
|
||||
`An invalid test env was passed: ${variant} (only "default" and "turbo" are valid options)`
|
||||
`An invalid test env was passed: ${variant} (only "default", "turbo" and "experimentalTurbo" are valid options)`
|
||||
)
|
||||
}
|
||||
|
||||
const shouldRunTurboDev = shouldRunTurboDevTest()
|
||||
const shouldRunExperimentalTurboDev = shouldRunExperimentalTurboDevTest()
|
||||
const shouldSkip =
|
||||
(runningEnv === 'turbo' && !shouldRunTurboDev) ||
|
||||
(runningEnv === 'default' && shouldRunTurboDev)
|
||||
(runningEnv === 'experimentalTurbo' && !shouldRunExperimentalTurboDev) ||
|
||||
(runningEnv === 'default' &&
|
||||
(shouldRunTurboDev || shouldRunExperimentalTurboDev))
|
||||
|
||||
return shouldSkip ? describe.skip : describe
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
let loggedTurbopack = false
|
||||
let loggedExperimentalTurbopack = false
|
||||
|
||||
/**
|
||||
* Utility function to determine if a given test case needs to run with --turbo.
|
||||
|
@ -24,3 +25,28 @@ export function shouldRunTurboDevTest(): boolean {
|
|||
|
||||
return shouldRunTurboDev
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function to determine if a given test case needs to run with --experimental-turbo.
|
||||
*
|
||||
* This is primarily for the gradual test enablement with latest turbopack upstream changes.
|
||||
*
|
||||
* Note: it could be possible to dynamically create test cases itself (createDevTest(): it.each([...])), but
|
||||
* it makes hard to conform with existing lint rules. Instead, starting off from manual fixture setup and
|
||||
* update test cases accordingly as turbopack changes enable more test cases.
|
||||
*/
|
||||
export function shouldRunExperimentalTurboDevTest(): boolean {
|
||||
if (!!process.env.TEST_WASM) {
|
||||
return false
|
||||
}
|
||||
|
||||
const shouldRunExperimentalTurboDev = !!process.env.EXPERIMENTAL_TURBOPACK
|
||||
if (shouldRunExperimentalTurboDev && !loggedExperimentalTurbopack) {
|
||||
require('console').log(
|
||||
`Running tests with experimental turbopack because environment variable EXPERIMENTAL_TURBOPACK is set`
|
||||
)
|
||||
loggedExperimentalTurbopack = true
|
||||
}
|
||||
|
||||
return shouldRunExperimentalTurboDev
|
||||
}
|
||||
|
|
9
test/turbopack-tests-manifest.js
Normal file
9
test/turbopack-tests-manifest.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
// Tests that are currently enabled with experimental Turbopack in CI.
|
||||
// Only tests that are actively testing against Turbopack should
|
||||
// be enabled here
|
||||
const enabledTests = [
|
||||
'test/development/api-cors-with-rewrite/index.test.ts',
|
||||
'test/integration/bigint/test/index.test.js',
|
||||
]
|
||||
|
||||
module.exports = { enabledTests }
|
Loading…
Reference in a new issue