diff --git a/packages/next-swc/crates/core/src/lib.rs b/packages/next-swc/crates/core/src/lib.rs index 9ca3d33a8ee55b6..781313ee3e19db0 100644 --- a/packages/next-swc/crates/core/src/lib.rs +++ b/packages/next-swc/crates/core/src/lib.rs @@ -31,6 +31,7 @@ DEALINGS IN THE SOFTWARE. use auto_cjs::contains_cjs; use either::Either; +use fxhash::FxHashSet; use serde::Deserialize; use std::cell::RefCell; use std::rc::Rc; @@ -113,6 +114,7 @@ pub fn custom_before_pass<'a, C: Comments + 'a>( file: Arc, opts: &'a TransformOptions, comments: C, + eliminated_packages: Rc>>, ) -> impl Fold + 'a { #[cfg(target_arch = "wasm32")] let relay_plugin = noop(); @@ -148,7 +150,10 @@ pub fn custom_before_pass<'a, C: Comments + 'a>( Either::Right(noop()) } }, - Optional::new(next_ssg::next_ssg(), !opts.disable_next_ssg), + Optional::new( + next_ssg::next_ssg(eliminated_packages), + !opts.disable_next_ssg + ), amp_attributes::amp_attributes(), next_dynamic::next_dynamic( opts.is_development, diff --git a/packages/next-swc/crates/core/src/next_ssg.rs b/packages/next-swc/crates/core/src/next_ssg.rs index a25e885abf6576a..6745ca2984248e1 100644 --- a/packages/next-swc/crates/core/src/next_ssg.rs +++ b/packages/next-swc/crates/core/src/next_ssg.rs @@ -1,6 +1,8 @@ use easy_error::{bail, Error}; use fxhash::FxHashSet; +use std::cell::RefCell; use std::mem::take; +use std::rc::Rc; use swc_common::pass::{Repeat, Repeated}; use swc_common::DUMMY_SP; use swc_ecmascript::ast::*; @@ -12,9 +14,12 @@ use swc_ecmascript::{ }; /// Note: This paths requires running `resolver` **before** running this. -pub fn next_ssg() -> impl Fold { +pub fn next_ssg(eliminated_packages: Rc>>) -> impl Fold { Repeat::new(NextSsg { - state: Default::default(), + state: State { + eliminated_packages, + ..Default::default() + }, in_lhs_of_var: false, }) } @@ -41,6 +46,10 @@ struct State { done: bool, should_run_again: bool, + + /// Track the import packages which are eliminated in the + /// `getServerSideProps` + pub eliminated_packages: Rc>>, } impl State { @@ -288,7 +297,7 @@ impl Fold for Analyzer<'_> { /// Actual implementation of the transform. struct NextSsg { - state: State, + pub state: State, in_lhs_of_var: bool, } @@ -344,11 +353,19 @@ impl Fold for NextSsg { return i; } + let import_src = &i.src.value; + i.specifiers.retain(|s| match s { ImportSpecifier::Named(ImportNamedSpecifier { local, .. }) | ImportSpecifier::Default(ImportDefaultSpecifier { local, .. }) | ImportSpecifier::Namespace(ImportStarAsSpecifier { local, .. }) => { if self.should_remove(local.to_id()) { + if self.state.is_server_props { + self.state + .eliminated_packages + .borrow_mut() + .insert(import_src.to_string()); + } tracing::trace!( "Dropping import `{}{:?}` because it should be removed", local.sym, diff --git a/packages/next-swc/crates/core/tests/errors.rs b/packages/next-swc/crates/core/tests/errors.rs index 35ce78e252de2ca..3176182ef27f2e8 100644 --- a/packages/next-swc/crates/core/tests/errors.rs +++ b/packages/next-swc/crates/core/tests/errors.rs @@ -63,5 +63,10 @@ fn styled_jsx_errors(input: PathBuf) { #[fixture("tests/errors/next-ssg/**/input.js")] fn next_ssg_errors(input: PathBuf) { let output = input.parent().unwrap().join("output.js"); - test_fixture_allowing_error(syntax(), &|_tr| next_ssg(), &input, &output); + test_fixture_allowing_error( + syntax(), + &|_tr| next_ssg(Default::default()), + &input, + &output, + ); } diff --git a/packages/next-swc/crates/core/tests/fixture.rs b/packages/next-swc/crates/core/tests/fixture.rs index 9a7d8b7e5e56aef..8db78ec56865343 100644 --- a/packages/next-swc/crates/core/tests/fixture.rs +++ b/packages/next-swc/crates/core/tests/fixture.rs @@ -113,7 +113,7 @@ fn next_ssg_fixture(input: PathBuf) { }, top_level_mark, ); - chain!(next_ssg(), jsx) + chain!(next_ssg(Default::default()), jsx) }, &input, &output, diff --git a/packages/next-swc/crates/core/tests/full.rs b/packages/next-swc/crates/core/tests/full.rs index 12817af23a1e971..5ca7a97dc51767f 100644 --- a/packages/next-swc/crates/core/tests/full.rs +++ b/packages/next-swc/crates/core/tests/full.rs @@ -73,7 +73,13 @@ fn test(input: &Path, minify: bool) { &handler, &options.swc, |_, comments| { - custom_before_pass(cm.clone(), fm.clone(), &options, comments.clone()) + custom_before_pass( + cm.clone(), + fm.clone(), + &options, + comments.clone(), + Default::default(), + ) }, |_, _| noop(), ) { diff --git a/packages/next-swc/crates/napi/src/bundle/mod.rs b/packages/next-swc/crates/napi/src/bundle/mod.rs index b89f8df3a243135..8a63de96256131d 100644 --- a/packages/next-swc/crates/napi/src/bundle/mod.rs +++ b/packages/next-swc/crates/napi/src/bundle/mod.rs @@ -132,7 +132,7 @@ impl Task for BundleTask { } fn resolve(self, env: napi::Env, output: Self::Output) -> napi::Result { - complete_output(&env, output) + complete_output(&env, output, Default::default()) } } diff --git a/packages/next-swc/crates/napi/src/lib.rs b/packages/next-swc/crates/napi/src/lib.rs index 87955d3768ba32b..477108aabefb573 100644 --- a/packages/next-swc/crates/napi/src/lib.rs +++ b/packages/next-swc/crates/napi/src/lib.rs @@ -35,6 +35,7 @@ extern crate napi_derive; extern crate swc_node_base; use backtrace::Backtrace; +use fxhash::FxHashSet; use napi::{CallContext, Env, JsObject, JsUndefined}; use std::{env, panic::set_hook, sync::Arc}; use swc::{Compiler, TransformOutput}; @@ -86,8 +87,25 @@ fn construct_compiler(ctx: CallContext) -> napi::Result { ctx.env.get_undefined() } -pub fn complete_output(env: &Env, output: TransformOutput) -> napi::Result { - env.to_js_value(&output)?.coerce_to_object() +pub fn complete_output( + env: &Env, + output: TransformOutput, + eliminated_packages: FxHashSet, +) -> napi::Result { + let mut js_output = env.create_object()?; + js_output.set_named_property("code", env.create_string_from_std(output.code)?)?; + if let Some(map) = output.map { + js_output.set_named_property("map", env.create_string_from_std(map)?)?; + } + if !eliminated_packages.is_empty() { + js_output.set_named_property( + "eliminatedPackages", + env.create_string_from_std(serde_json::to_string( + &eliminated_packages.into_iter().collect::>(), + )?)?, + )?; + } + Ok(js_output) } pub type ArcCompiler = Arc; diff --git a/packages/next-swc/crates/napi/src/minify.rs b/packages/next-swc/crates/napi/src/minify.rs index 56f4790b762f447..40b0545f4bfc3f2 100644 --- a/packages/next-swc/crates/napi/src/minify.rs +++ b/packages/next-swc/crates/napi/src/minify.rs @@ -92,7 +92,7 @@ impl Task for MinifyTask { } fn resolve(self, env: napi::Env, output: Self::Output) -> napi::Result { - complete_output(&env, output) + complete_output(&env, output, Default::default()) } } @@ -127,5 +127,5 @@ pub fn minify_sync(cx: CallContext) -> napi::Result { ) .convert_err()?; - complete_output(cx.env, output) + complete_output(cx.env, output, Default::default()) } diff --git a/packages/next-swc/crates/napi/src/transform.rs b/packages/next-swc/crates/napi/src/transform.rs index 5f5f46d255ce80f..aeb0a34756748e9 100644 --- a/packages/next-swc/crates/napi/src/transform.rs +++ b/packages/next-swc/crates/napi/src/transform.rs @@ -31,12 +31,15 @@ use crate::{ util::{deserialize_json, CtxtExt, MapErr}, }; use anyhow::{anyhow, bail, Context as _, Error}; +use fxhash::FxHashSet; use napi::{CallContext, Env, JsBoolean, JsBuffer, JsObject, JsString, JsUnknown, Status, Task}; use next_swc::{custom_before_pass, TransformOptions}; use std::fs::read_to_string; use std::{ + cell::RefCell, convert::TryFrom, panic::{catch_unwind, AssertUnwindSafe}, + rc::Rc, sync::Arc, }; use swc::{try_with_handler, Compiler, TransformOutput}; @@ -60,10 +63,11 @@ pub struct TransformTask { } impl Task for TransformTask { - type Output = TransformOutput; + type Output = (TransformOutput, FxHashSet); type JsValue = JsObject; fn compute(&mut self) -> napi::Result { + let eliminated_packages: Rc>> = Default::default(); let res = catch_unwind(AssertUnwindSafe(|| { try_with_handler( self.c.cm.clone(), @@ -108,7 +112,15 @@ impl Task for TransformTask { None, handler, &options.swc, - |_, comments| custom_before_pass(cm, file, &options, comments.clone()), + |_, comments| { + custom_before_pass( + cm, + file, + &options, + comments.clone(), + eliminated_packages.clone(), + ) + }, |_, _| noop(), ) }) @@ -124,7 +136,9 @@ impl Task for TransformTask { }); match res { - Ok(res) => res.convert_err(), + Ok(res) => res + .map(|o| (o, eliminated_packages.replace(Default::default()))) + .convert_err(), Err(err) => Err(napi::Error::new( Status::GenericFailure, format!("{:?}", err), @@ -132,8 +146,12 @@ impl Task for TransformTask { } } - fn resolve(self, env: Env, result: Self::Output) -> napi::Result { - complete_output(&env, result) + fn resolve( + self, + env: Env, + (output, eliminated_packages): Self::Output, + ) -> napi::Result { + complete_output(&env, output, eliminated_packages) } } @@ -203,7 +221,7 @@ where ) .convert_err()?; - complete_output(cx.env, output) + complete_output(cx.env, output, Default::default()) } #[js_function(4)] diff --git a/packages/next-swc/crates/wasm/src/lib.rs b/packages/next-swc/crates/wasm/src/lib.rs index 44803981a1fb1f1..71269c3482202fc 100644 --- a/packages/next-swc/crates/wasm/src/lib.rs +++ b/packages/next-swc/crates/wasm/src/lib.rs @@ -68,7 +68,9 @@ pub fn transform_sync(s: &str, opts: JsValue) -> Result { None, handler, &opts.swc, - |_, comments| custom_before_pass(cm, file, &opts, comments.clone()), + |_, comments| { + custom_before_pass(cm, file, &opts, comments.clone(), Default::default()) + }, |_, _| noop(), ) .context("failed to process js file")?; diff --git a/packages/next/build/index.ts b/packages/next/build/index.ts index ee7666d33949563..6932e9ab92db818 100644 --- a/packages/next/build/index.ts +++ b/packages/next/build/index.ts @@ -73,6 +73,7 @@ import { eventTypeCheckCompleted, EVENT_BUILD_FEATURE_USAGE, EventBuildFeatureUsage, + eventPackageUsedInGetServerSideProps, } from '../telemetry/events' import { Telemetry } from '../telemetry/storage' import { CompilerResult, runCompiler } from './compiler' @@ -1961,6 +1962,7 @@ export default async function build( if (telemetryPlugin) { const events = eventBuildFeatureUsage(telemetryPlugin) telemetry.record(events) + telemetry.record(eventPackageUsedInGetServerSideProps(telemetryPlugin)) } if (ssgPages.size > 0) { diff --git a/packages/next/build/webpack/loaders/next-swc-loader.js b/packages/next/build/webpack/loaders/next-swc-loader.js index 70e596102ed7e30..f027c09b6e12cb0 100644 --- a/packages/next/build/webpack/loaders/next-swc-loader.js +++ b/packages/next/build/webpack/loaders/next-swc-loader.js @@ -88,6 +88,11 @@ async function loaderTransform(parentTrace, source, inputSourceMap) { const swcSpan = parentTrace.traceChild('next-swc-transform') return swcSpan.traceAsyncFn(() => transform(source, programmaticOptions).then((output) => { + if (output.eliminatedPackages && this.eliminatedPackages) { + for (const pkg of JSON.parse(output.eliminatedPackages)) { + this.eliminatedPackages.add(pkg) + } + } return [output.code, output.map ? JSON.parse(output.map) : undefined] }) ) diff --git a/packages/next/build/webpack/plugins/telemetry-plugin.ts b/packages/next/build/webpack/plugins/telemetry-plugin.ts index 5aa5eaab7dff539..8bc73c20240e8d3 100644 --- a/packages/next/build/webpack/plugins/telemetry-plugin.ts +++ b/packages/next/build/webpack/plugins/telemetry-plugin.ts @@ -1,4 +1,7 @@ -import type { webpack5 as webpack } from 'next/dist/compiled/webpack/webpack' +import { + NormalModule, + webpack5 as webpack, +} from 'next/dist/compiled/webpack/webpack' /** * List of target triples next-swc native binary supports. @@ -86,6 +89,8 @@ const BUILD_FEATURES: Array = [ 'swc/target/aarch64-pc-windows-msvc', ] +const ELIMINATED_PACKAGES = new Set() + /** * Plugin that queries the ModuleGraph to look for modules that correspond to * certain features (e.g. next/image and next/script) and record how many times @@ -137,11 +142,23 @@ export class TelemetryPlugin implements webpack.WebpackPluginInstance { callback() } ) + if (compiler.options.mode === 'production' && !compiler.watchMode) { + compiler.hooks.compilation.tap(TelemetryPlugin.name, (compilation) => { + const moduleHooks = NormalModule.getCompilationHooks(compilation) + moduleHooks.loader.tap(TelemetryPlugin.name, (loaderContext: any) => { + loaderContext.eliminatedPackages = ELIMINATED_PACKAGES + }) + }) + } } usages(): FeatureUsage[] { return [...this.usageTracker.values()] } + + packagesUsedInServerSideProps(): string[] { + return Array.from(ELIMINATED_PACKAGES) + } } /** diff --git a/packages/next/telemetry/events/build.ts b/packages/next/telemetry/events/build.ts index 2a260b06039464a..c8548f0942f98aa 100644 --- a/packages/next/telemetry/events/build.ts +++ b/packages/next/telemetry/events/build.ts @@ -160,3 +160,21 @@ export function eventBuildFeatureUsage( }, })) } + +export const EVENT_NAME_PACKAGE_USED_IN_GET_SERVER_SIDE_PROPS = + 'NEXT_PACKAGE_USED_IN_GET_SERVER_SIDE_PROPS' + +export type EventPackageUsedInGetServerSideProps = { + package: string +} + +export function eventPackageUsedInGetServerSideProps( + telemetryPlugin: TelemetryPlugin +): Array<{ eventName: string; payload: EventPackageUsedInGetServerSideProps }> { + return telemetryPlugin.packagesUsedInServerSideProps().map((packageName) => ({ + eventName: EVENT_NAME_PACKAGE_USED_IN_GET_SERVER_SIDE_PROPS, + payload: { + package: packageName, + }, + })) +}