From 974accc2c77f896acbde5e5563e4ac3af7aa3ab6 Mon Sep 17 00:00:00 2001 From: OJ Kwon <1210596+kwonoj@users.noreply.github.com> Date: Wed, 26 Jul 2023 10:49:34 -0700 Subject: [PATCH] feat(turbopack): embed build time info, emits next features telemetry event (#53028) ### What? closes WEB-1301. To collect some information inside of rust binary, embed it as build-time constant. It supersedes existing target triple embedding as well. --- Cargo.lock | 50 +++++++++ Cargo.toml | 1 + packages/next-swc/crates/napi/Cargo.toml | 3 + packages/next-swc/crates/napi/build.rs | 17 +-- packages/next-swc/crates/napi/src/lib.rs | 3 + .../crates/napi/src/next_api/project.rs | 4 + packages/next-swc/crates/napi/src/util.rs | 3 +- packages/next-swc/crates/next-api/Cargo.toml | 3 + packages/next-swc/crates/next-api/build.rs | 5 + packages/next-swc/crates/next-api/src/lib.rs | 3 + .../next-swc/crates/next-api/src/project.rs | 101 +++++++++++++++++- .../crates/next-core/src/next_config.rs | 55 +++++++++- .../crates/next-core/src/next_telemetry.rs | 42 ++++++++ packages/next/src/build/swc/index.ts | 12 +++ packages/next/src/cli/next-dev.ts | 4 + 15 files changed, 285 insertions(+), 21 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7562343b6c..4b628a242a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2640,6 +2640,12 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "616cde7c720bb2bb5824a224687d8f77bfd38922027f01d825cd7453be5099fb" +[[package]] +name = "is_debug" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06d198e9919d9822d5f7083ba8530e04de87841eaf21ead9af8f2304efd57c89" + [[package]] name = "isahc" version = "1.7.2" @@ -3378,6 +3384,7 @@ dependencies = [ "once_cell", "serde", "serde_json", + "shadow-rs", "tokio", "tracing", "tracing-subscriber", @@ -3548,6 +3555,7 @@ dependencies = [ "sentry", "serde", "serde_json", + "shadow-rs", "tracing", "tracing-chrome", "tracing-futures", @@ -3743,6 +3751,15 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "num_threads" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" +dependencies = [ + "libc", +] + [[package]] name = "objc" version = "0.2.7" @@ -5189,6 +5206,18 @@ dependencies = [ "digest", ] +[[package]] +name = "shadow-rs" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "970538704756fd0bb4ec8cb89f80674afb661e7c0fe716f9ba5be57717742300" +dependencies = [ + "const_format", + "is_debug", + "time 0.3.20", + "tzdb", +] + [[package]] name = "sharded-slab" version = "0.1.4" @@ -6870,6 +6899,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd0cbfecb4d19b5ea75bb31ad904eb5b9fa13f21079c3b92017ebdf4999a5890" dependencies = [ "itoa", + "libc", + "num_threads", "serde", "time-core", "time-macros 0.2.8", @@ -7983,6 +8014,25 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" +[[package]] +name = "tz-rs" +version = "0.6.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33851b15c848fad2cf4b105c6bb66eb9512b6f6c44a4b13f57c53c73c707e2b4" +dependencies = [ + "const_fn", +] + +[[package]] +name = "tzdb" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec758958f2fb5069cd7fae385be95cc8eceb8cdfd270c7d14de6034f0108d99e" +dependencies = [ + "iana-time-zone", + "tz-rs", +] + [[package]] name = "ucd-trie" version = "0.1.5" diff --git a/Cargo.toml b/Cargo.toml index ebe6853c3a..eaabda1f00 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -121,6 +121,7 @@ serde = { version = "1.0.152", features = ["derive"] } serde_json = "1.0.93" serde_qs = "0.11.0" serde_yaml = "0.9.17" +shadow-rs = { version = "0.23.0", default-features = false, features = ["tzdb"] } syn = "1.0.107" tempfile = "3.3.0" thiserror = "1.0.38" diff --git a/packages/next-swc/crates/napi/Cargo.toml b/packages/next-swc/crates/napi/Cargo.toml index 9c47caf113..b4ff4170cd 100644 --- a/packages/next-swc/crates/napi/Cargo.toml +++ b/packages/next-swc/crates/napi/Cargo.toml @@ -50,6 +50,7 @@ turbo-tasks = { workspace = true } once_cell = { workspace = true } serde = "1" serde_json = "1" +shadow-rs = { workspace = true } tracing = { workspace = true } tracing-futures = "0.2.5" tracing-subscriber = { workspace = true } @@ -84,6 +85,8 @@ sentry = { version = "0.27.0", default-features = false, features = [ napi-build = "2" serde = "1" serde_json = "1" +# It is not a mistake this dependency is specified in dep / build-dep both. +shadow-rs = { workspace = true } turbopack-binding = { workspace = true, features = [ "__turbo_tasks_build" ]} diff --git a/packages/next-swc/crates/napi/build.rs b/packages/next-swc/crates/napi/build.rs index cb93580b62..bb97d93c27 100644 --- a/packages/next-swc/crates/napi/build.rs +++ b/packages/next-swc/crates/napi/build.rs @@ -10,22 +10,15 @@ use turbopack_binding::turbo::tasks_build::generate_register; extern crate napi_build; fn main() { - // Emit current platform's target-triple into a text file to create static const - // in util.rs - let out_dir = env::var("OUT_DIR").expect("Outdir should exist"); - let dest_path = Path::new(&out_dir).join("triple.txt"); - let mut f = - BufWriter::new(File::create(dest_path).expect("Failed to create target triple text")); - write!( - f, - "{}", - env::var("TARGET").expect("Target should be specified") - ) - .expect("Failed to write target triple text"); + // Generates, stores build-time information as static values. + // There are some places relying on correct values for this (i.e telemetry), + // So failing build if this fails. + shadow_rs::new().expect("Should able to generate build time information"); // Emit current package.json's version field into a text file to create static // const in util.rs This is being used to set correct release version for // the sentry's crash reporter. + let out_dir = env::var("OUT_DIR").expect("Outdir should exist"); let pkg_file = File::open(Path::new("../../package.json")).expect("Should able to open package.json"); let json: serde_json::Value = serde_json::from_reader(pkg_file).unwrap(); diff --git a/packages/next-swc/crates/napi/src/lib.rs b/packages/next-swc/crates/napi/src/lib.rs index 0fd787188f..79ead3d3aa 100644 --- a/packages/next-swc/crates/napi/src/lib.rs +++ b/packages/next-swc/crates/napi/src/lib.rs @@ -58,6 +58,9 @@ pub mod turbopack; pub mod turbotrace; pub mod util; +// Declare build-time information variables generated in build.rs +shadow_rs::shadow!(build); + // don't use turbo malloc (`mimalloc`) on linux-musl-aarch64 because of the // compile error #[cfg(not(any( diff --git a/packages/next-swc/crates/napi/src/next_api/project.rs b/packages/next-swc/crates/napi/src/next_api/project.rs index 5050873ed7..ca8654fcf8 100644 --- a/packages/next-swc/crates/napi/src/next_api/project.rs +++ b/packages/next-swc/crates/napi/src/next_api/project.rs @@ -41,6 +41,9 @@ pub struct NapiProjectOptions { /// The contents of next.config.js, serialized to JSON. pub next_config: String, + /// The contents of ts/config read by load-jsconfig, serialized to JSON. + pub js_config: String, + /// A map of environment variables to use when compiling code. pub env: Vec, } @@ -58,6 +61,7 @@ impl From for ProjectOptions { project_path: val.project_path, watch: val.watch, next_config: val.next_config, + js_config: val.js_config, env: val .env .into_iter() diff --git a/packages/next-swc/crates/napi/src/util.rs b/packages/next-swc/crates/napi/src/util.rs index 2b5d33b7d7..67a1a63df1 100644 --- a/packages/next-swc/crates/napi/src/util.rs +++ b/packages/next-swc/crates/napi/src/util.rs @@ -41,13 +41,12 @@ use sentry::ClientOptions; use tracing_chrome::{ChromeLayerBuilder, FlushGuard}; use tracing_subscriber::{filter, prelude::*, util::SubscriberInitExt, Layer}; -static TARGET_TRIPLE: &str = include_str!(concat!(env!("OUT_DIR"), "/triple.txt")); #[allow(unused)] static PACKAGE_VERSION: &str = include_str!(concat!(env!("OUT_DIR"), "/package.txt")); #[napi] pub fn get_target_triple() -> String { - TARGET_TRIPLE.to_owned() + crate::build::BUILD_TARGET.to_string() } pub trait MapErr: Into> { diff --git a/packages/next-swc/crates/next-api/Cargo.toml b/packages/next-swc/crates/next-api/Cargo.toml index 70b8706cc0..27604dcaa5 100644 --- a/packages/next-swc/crates/next-api/Cargo.toml +++ b/packages/next-swc/crates/next-api/Cargo.toml @@ -23,6 +23,7 @@ next-core = { workspace = true } once_cell = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } +shadow-rs = { workspace = true } tokio = { workspace = true, features = ["full"] } turbopack-binding = { workspace = true, features = [ "__turbo_tasks_memory", @@ -42,6 +43,8 @@ tracing = { workspace = true } tracing-subscriber = { workspace = true, features = ["env-filter", "json"] } [build-dependencies] +# It is not a mistake this dependency is specified in dep / build-dep both. +shadow-rs = { workspace = true } turbopack-binding = { workspace = true, features = [ "__turbo_tasks_build" ]} \ No newline at end of file diff --git a/packages/next-swc/crates/next-api/build.rs b/packages/next-swc/crates/next-api/build.rs index 23e4bcb55d..5bc69ab443 100644 --- a/packages/next-swc/crates/next-api/build.rs +++ b/packages/next-swc/crates/next-api/build.rs @@ -1,5 +1,10 @@ use turbopack_binding::turbo::tasks_build::generate_register; fn main() { + // Generates, stores build-time information as static values. + // There are some places relying on correct values for this (i.e telemetry), + // So failing build if this fails. + shadow_rs::new().expect("Should able to generate build time information"); + generate_register(); } diff --git a/packages/next-swc/crates/next-api/src/lib.rs b/packages/next-swc/crates/next-api/src/lib.rs index 0aac4f947e..b4c5713fb3 100644 --- a/packages/next-swc/crates/next-api/src/lib.rs +++ b/packages/next-swc/crates/next-api/src/lib.rs @@ -8,6 +8,9 @@ mod pages; pub mod project; pub mod route; +// Declare build-time information variables generated in build.rs +shadow_rs::shadow!(build); + pub fn register() { next_core::register(); turbopack_binding::turbopack::build::register(); diff --git a/packages/next-swc/crates/next-api/src/project.rs b/packages/next-swc/crates/next-api/src/project.rs index 8b9463cc20..9a33648cfe 100644 --- a/packages/next-swc/crates/next-api/src/project.rs +++ b/packages/next-swc/crates/next-api/src/project.rs @@ -7,8 +7,9 @@ use next_core::{ get_edge_chunking_context, get_edge_compile_time_info, mode::NextMode, next_client::{get_client_chunking_context, get_client_compile_time_info}, - next_config::NextConfig, + next_config::{JsConfig, NextConfig}, next_server::{get_server_chunking_context, get_server_compile_time_info}, + next_telemetry::NextFeatureTelemetry, util::NextSourceConfig, }; use serde::{Deserialize, Serialize}; @@ -23,8 +24,8 @@ use turbopack_binding::{ turbopack::{ build::BuildChunkingContext, core::{ - chunk::ChunkingContext, compile_time_info::CompileTimeInfo, environment::ServerAddr, - PROJECT_FILESYSTEM_NAME, + chunk::ChunkingContext, compile_time_info::CompileTimeInfo, diagnostics::DiagnosticExt, + environment::ServerAddr, PROJECT_FILESYSTEM_NAME, }, dev::DevChunkingContext, ecmascript::chunk::EcmascriptChunkingContext, @@ -35,6 +36,7 @@ use turbopack_binding::{ use crate::{ app::{AppProject, OptionAppProject}, + build, entrypoints::Entrypoints, pages::PagesProject, route::{Endpoint, Route}, @@ -53,6 +55,9 @@ pub struct ProjectOptions { /// The contents of next.config.js, serialized to JSON. pub next_config: String, + /// The contents of ts/config read by load-jsconfig, serialized to JSON. + pub js_config: String, + /// A map of environment variables to use when compiling code. pub env: Vec<(String, String)>, @@ -92,12 +97,14 @@ impl ProjectContainer { let this = self.await?; let options = this.state.get(); let next_config = NextConfig::from_string(Vc::cell(options.next_config.clone())); + let js_config = JsConfig::from_string(Vc::cell(options.js_config.clone())); let env: Vc = Vc::cell(options.env.iter().cloned().collect()); Ok(Project { root_path: options.root_path.clone(), project_path: options.project_path.clone(), watch: options.watch, next_config, + js_config, env: Vc::upcast(env), browserslist_query: "last 1 Chrome versions, last 1 Firefox versions, last 1 Safari \ versions, last 1 Edge versions" @@ -128,6 +135,9 @@ pub struct Project { /// Next config. next_config: Vc, + /// Js/Tsconfig read by load-jsconfig + js_config: Vc, + /// A map of environment variables to use when compiling code. env: Vc>, @@ -225,6 +235,11 @@ impl Project { Ok(self.await?.next_config) } + #[turbo_tasks::function] + pub(super) async fn js_config(self: Vc) -> Result> { + Ok(self.await?.js_config) + } + #[turbo_tasks::function] pub(super) fn execution_context(self: Vc) -> Vc { let node_root = self.node_root(); @@ -305,6 +320,84 @@ impl Project { ) } + /// Emit a telemetry event corresponding to webpack configuration telemetry + /// (https://github.com/vercel/next.js/blob/9da305fe320b89ee2f8c3cfb7ecbf48856368913/packages/next/src/build/webpack-config.ts#L2516) + /// to detect which feature is enabled. + #[turbo_tasks::function] + async fn collect_project_feature_telemetry(self: Vc) -> Result> { + let emit_event = |feature_name: &str, enabled: bool| { + NextFeatureTelemetry::new(feature_name.to_string(), enabled) + .cell() + .emit(); + }; + + // First, emit an event for the binary target triple. + // This is different to webpack-config; when this is being called, + // it is always using SWC so we don't check swc here. + emit_event(build::BUILD_TARGET, true); + + // Go over jsconfig and report enabled features. + let compiler_options = self.js_config().compiler_options().await?; + let compiler_options = compiler_options.as_object(); + let experimental_decorators_enabled = compiler_options + .as_ref() + .and_then(|compiler_options| compiler_options.get("experimentalDecorators")) + .is_some(); + let jsx_import_source_enabled = compiler_options + .as_ref() + .and_then(|compiler_options| compiler_options.get("jsxImportSource")) + .is_some(); + + emit_event("swcExperimentalDecorators", experimental_decorators_enabled); + emit_event("swcImportSource", jsx_import_source_enabled); + + // Go over config and report enabled features. + // [TODO]: useSwcLoader is not being reported as it is not directly corresponds (it checks babel config existence) + // need to confirm what we'll do with turbopack. + let config = self.next_config(); + + emit_event("swcMinify", *config.swc_minify().await?); + emit_event( + "skipMiddlewareUrlNormalize", + *config.skip_middleware_url_normalize().await?, + ); + + emit_event( + "skipTrailingSlashRedirect", + *config.skip_trailing_slash_redirect().await?, + ); + + let config = &config.await?; + + emit_event("modularizeImports", config.modularize_imports.is_some()); + emit_event("transpilePackages", config.transpile_packages.is_some()); + emit_event("turbotrace", config.experimental.turbotrace.is_some()); + + // compiler options + let compiler_options = config.compiler.as_ref(); + let swc_relay_enabled = compiler_options.and_then(|c| c.relay.as_ref()).is_some(); + let styled_components_enabled = compiler_options + .map(|c| c.styled_components.is_some()) + .unwrap_or_default(); + let react_remove_properties_enabled = compiler_options + .and_then(|c| c.react_remove_properties) + .unwrap_or_default(); + let remove_console_enabled = compiler_options + .map(|c| c.remove_console.is_some()) + .unwrap_or_default(); + let emotion_enabled = compiler_options + .map(|c| c.emotion.is_some()) + .unwrap_or_default(); + + emit_event("swcRelay", swc_relay_enabled); + emit_event("swcStyledComponents", styled_components_enabled); + emit_event("swcReactRemoveProperties", react_remove_properties_enabled); + emit_event("swcRemoveConsole", remove_console_enabled); + emit_event("swcEmotion", emotion_enabled); + + Ok(unit()) + } + #[turbo_tasks::function] pub(super) fn ssr_chunking_context(self: Vc) -> Vc { self.server_chunking_context().with_layer("ssr".to_string()) @@ -349,6 +442,8 @@ impl Project { /// provided page_extensions). #[turbo_tasks::function] pub async fn entrypoints(self: Vc) -> Result> { + self.collect_project_feature_telemetry().await?; + let mut routes = IndexMap::new(); let app_project = self.app_project(); let pages_project = self.pages_project(); diff --git a/packages/next-swc/crates/next-core/src/next_config.rs b/packages/next-swc/crates/next-core/src/next_config.rs index ed37a914d2..9f38c2cabf 100644 --- a/packages/next-swc/crates/next-core/src/next_config.rs +++ b/packages/next-swc/crates/next-core/src/next_config.rs @@ -117,12 +117,14 @@ pub struct NextConfig { public_runtime_config: IndexMap, server_runtime_config: IndexMap, static_page_generation_timeout: f64, - swc_minify: bool, + swc_minify: Option, target: Option, trailing_slash: bool, typescript: TypeScriptConfig, use_file_system_public_routes: bool, webpack: Option, + skip_middleware_url_normalize: Option, + skip_trailing_slash_redirect: Option, } #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize, TraceRawVcs)] @@ -446,15 +448,13 @@ pub struct ExperimentalConfig { runtime: Option, scroll_restoration: Option, shared_pool: Option, - skip_middleware_url_normalize: Option, - skip_trailing_slash_redirect: Option, sri: Option, swc_file_reading: Option, swc_minify: Option, swc_minify_debug_options: Option, swc_trace_profiling: Option, transpile_packages: Option>, - turbotrace: Option, + pub turbotrace: Option, url_imports: Option, web_vitals_attribution: Option, worker_threads: Option, @@ -653,6 +653,25 @@ impl NextConfig { self.await?.sass_options.clone().unwrap_or_default(), )) } + + #[turbo_tasks::function] + pub async fn swc_minify(self: Vc) -> Result> { + Ok(Vc::cell(self.await?.swc_minify.unwrap_or(false))) + } + + #[turbo_tasks::function] + pub async fn skip_middleware_url_normalize(self: Vc) -> Result> { + Ok(Vc::cell( + self.await?.skip_middleware_url_normalize.unwrap_or(false), + )) + } + + #[turbo_tasks::function] + pub async fn skip_trailing_slash_redirect(self: Vc) -> Result> { + Ok(Vc::cell( + self.await?.skip_trailing_slash_redirect.unwrap_or(false), + )) + } } fn next_configs() -> Vc> { @@ -799,6 +818,34 @@ pub async fn has_next_config(context: Vc) -> Result> { ))) } +/// A subset of ts/jsconfig that next.js implicitly +/// interops with. +#[turbo_tasks::value(serialization = "custom", eq = "manual")] +#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct JsConfig { + compiler_options: Option, +} + +#[turbo_tasks::value_impl] +impl JsConfig { + #[turbo_tasks::function] + pub async fn from_string(string: Vc) -> Result> { + let string = string.await?; + let config: JsConfig = serde_json::from_str(&string) + .with_context(|| format!("failed to parse next.config.js: {}", string))?; + + Ok(config.cell()) + } + + #[turbo_tasks::function] + pub async fn compiler_options(self: Vc) -> Result> { + Ok(Vc::cell( + self.await?.compiler_options.clone().unwrap_or_default(), + )) + } +} + #[turbo_tasks::value] struct OutdatedConfigIssue { path: Vc, diff --git a/packages/next-swc/crates/next-core/src/next_telemetry.rs b/packages/next-swc/crates/next-core/src/next_telemetry.rs index ac2748286e..a92da18366 100644 --- a/packages/next-swc/crates/next-core/src/next_telemetry.rs +++ b/packages/next-swc/crates/next-core/src/next_telemetry.rs @@ -5,6 +5,48 @@ use turbopack_binding::{ turbopack::core::diagnostics::{Diagnostic, DiagnosticPayload}, }; +/// A struct represent telemetry event if certain feature of next.js +/// is enabled, such as next.config.swcMinify. +/// This is an equivalent representation of the following code: +/// https://github.com/vercel/next.js/blob/9da305fe320b89ee2f8c3cfb7ecbf48856368913/packages/next/src/build/webpack-config.ts#L2516 +#[turbo_tasks::value(shared)] +pub struct NextFeatureTelemetry { + pub event_name: String, + pub feature_name: String, + pub enabled: bool, +} + +impl NextFeatureTelemetry { + pub fn new(feature_name: String, enabled: bool) -> Self { + NextFeatureTelemetry { + event_name: "EVENT_BUILD_FEATURE_USAGE".to_string(), + feature_name, + enabled, + } + } +} + +#[turbo_tasks::value_impl] +impl Diagnostic for NextFeatureTelemetry { + #[turbo_tasks::function] + fn category(&self) -> Vc { + Vc::cell("NextFeatureTelemetry_category_tbd".to_string()) + } + + #[turbo_tasks::function] + fn name(&self) -> Vc { + Vc::cell(self.event_name.clone()) + } + + #[turbo_tasks::function] + fn payload(&self) -> Vc { + Vc::cell(HashMap::from([( + self.feature_name.clone(), + self.enabled.to_string(), + )])) + } +} + /// A struct represent telemetry event for the feature usage, /// referred as `importing` a certain module. (i.e importing @next/image) #[turbo_tasks::value(shared)] diff --git a/packages/next/src/build/swc/index.ts b/packages/next/src/build/swc/index.ts index 9bdbd5b32a..9f7a243414 100644 --- a/packages/next/src/build/swc/index.ts +++ b/packages/next/src/build/swc/index.ts @@ -340,6 +340,17 @@ interface ProjectOptions { */ nextConfig: NextConfigComplete + /** + * Jsconfig, or tsconfig contents. + * + * Next.js implicitly requires to read it to support few options + * https://nextjs.org/docs/architecture/nextjs-compiler#legacy-decorators + * https://nextjs.org/docs/architecture/nextjs-compiler#importsource + */ + jsConfig: { + compilerOptions: object + } + /** * A map of environment variables to use when compiling code. */ @@ -561,6 +572,7 @@ function bindingToApi(binding: any, _wasm: boolean) { return { ...options, nextConfig: await serializeNextConfig(options.nextConfig), + jsConfig: JSON.stringify(options.jsConfig ?? {}), env: Object.entries(options.env).map(([name, value]) => ({ name, value, diff --git a/packages/next/src/cli/next-dev.ts b/packages/next/src/cli/next-dev.ts index 34130379f2..c15a0e923a 100644 --- a/packages/next/src/cli/next-dev.ts +++ b/packages/next/src/cli/next-dev.ts @@ -226,6 +226,9 @@ const nextDev: CliCommand = async (argv) => { if (experimentalTurbo) { const { loadBindings } = require('../build/swc') as typeof import('../build/swc') + const { default: loadJsConfig } = + require('../build/load-jsconfig') as typeof import('../build/load-jsconfig') + const { jsConfig } = await loadJsConfig(dir, config) resetEnv() let bindings = await loadBindings() @@ -236,6 +239,7 @@ const nextDev: CliCommand = async (argv) => { projectPath: dir, rootPath: args['--root'] ?? findRootDir(dir) ?? dir, nextConfig: config, + jsConfig, env: { NEXT_PUBLIC_ENV_VAR: 'world', },