Skip to content

Commit

Permalink
Collect packages are used and eliminated in getServerSideProps
Browse files Browse the repository at this point in the history
  • Loading branch information
Brooooooklyn committed Mar 17, 2022
1 parent 52e34ce commit 47a4b8e
Show file tree
Hide file tree
Showing 13 changed files with 124 additions and 22 deletions.
3 changes: 3 additions & 0 deletions examples/ssr-caching/pages/index.js
@@ -1,3 +1,5 @@
import React from 'react'

export default function Index({ time }) {
return (
<main>
Expand All @@ -12,6 +14,7 @@ export async function getServerSideProps({ req, res }) {
'Cache-Control',
'public, s-maxage=10, stale-while-revalidate=59'
)
res.setHeader('x-react-version', React.version)

return {
props: {
Expand Down
7 changes: 6 additions & 1 deletion packages/next-swc/crates/core/src/lib.rs
Expand Up @@ -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;
Expand Down Expand Up @@ -109,6 +110,7 @@ pub fn custom_before_pass<'a, C: Comments + 'a>(
file: Arc<SourceFile>,
opts: &'a TransformOptions,
comments: C,
eliminated_packages: Rc<RefCell<FxHashSet<String>>>,
) -> impl Fold + 'a {
#[cfg(target_arch = "wasm32")]
let relay_plugin = noop();
Expand Down Expand Up @@ -144,7 +146,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,
Expand Down
21 changes: 18 additions & 3 deletions 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::*;
Expand All @@ -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<RefCell<FxHashSet<String>>>) -> impl Fold {
Repeat::new(NextSsg {
state: Default::default(),
state: State {
eliminated_packages,
..Default::default()
},
in_lhs_of_var: false,
})
}
Expand All @@ -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<RefCell<FxHashSet<String>>>,
}

impl State {
Expand Down Expand Up @@ -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,
}

Expand Down Expand Up @@ -349,6 +358,12 @@ impl Fold for NextSsg {
| 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(local.sym.to_string());
}
tracing::trace!(
"Dropping import `{}{:?}` because it should be removed",
local.sym,
Expand Down
7 changes: 6 additions & 1 deletion packages/next-swc/crates/core/tests/errors.rs
Expand Up @@ -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,
);
}
2 changes: 1 addition & 1 deletion packages/next-swc/crates/core/tests/fixture.rs
Expand Up @@ -112,7 +112,7 @@ fn next_ssg_fixture(input: PathBuf) {
},
top_level_mark,
);
chain!(next_ssg(), jsx)
chain!(next_ssg(Default::default()), jsx)
},
&input,
&output,
Expand Down
8 changes: 7 additions & 1 deletion packages/next-swc/crates/core/tests/full.rs
Expand Up @@ -72,7 +72,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(),
) {
Expand Down
2 changes: 1 addition & 1 deletion packages/next-swc/crates/napi/src/bundle/mod.rs
Expand Up @@ -121,7 +121,7 @@ impl Task for BundleTask {
}

fn resolve(self, env: napi::Env, output: Self::Output) -> napi::Result<Self::JsValue> {
complete_output(&env, output)
complete_output(&env, output, Default::default())
}
}

Expand Down
22 changes: 20 additions & 2 deletions packages/next-swc/crates/napi/src/lib.rs
Expand Up @@ -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};
Expand Down Expand Up @@ -84,8 +85,25 @@ fn construct_compiler(ctx: CallContext) -> napi::Result<JsUndefined> {
ctx.env.get_undefined()
}

pub fn complete_output(env: &Env, output: TransformOutput) -> napi::Result<JsObject> {
env.to_js_value(&output)?.coerce_to_object()
pub fn complete_output(
env: &Env,
output: TransformOutput,
eliminated_packages: FxHashSet<String>,
) -> napi::Result<JsObject> {
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::<Vec<String>>(),
)?)?,
)?;
}
Ok(js_output)
}

pub type ArcCompiler = Arc<Compiler>;
4 changes: 2 additions & 2 deletions packages/next-swc/crates/napi/src/minify.rs
Expand Up @@ -85,7 +85,7 @@ impl Task for MinifyTask {
}

fn resolve(self, env: napi::Env, output: Self::Output) -> napi::Result<Self::JsValue> {
complete_output(&env, output)
complete_output(&env, output, Default::default())
}
}

Expand Down Expand Up @@ -113,5 +113,5 @@ pub fn minify_sync(cx: CallContext) -> napi::Result<JsObject> {
let output = try_with_handler(c.cm.clone(), true, |handler| c.minify(fm, handler, &opts))
.convert_err()?;

complete_output(cx.env, output)
complete_output(cx.env, output, Default::default())
}
30 changes: 24 additions & 6 deletions packages/next-swc/crates/napi/src/transform.rs
Expand Up @@ -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};
Expand All @@ -60,10 +63,11 @@ pub struct TransformTask {
}

impl Task for TransformTask {
type Output = TransformOutput;
type Output = (TransformOutput, FxHashSet<String>);
type JsValue = JsObject;

fn compute(&mut self) -> napi::Result<Self::Output> {
let eliminated_packages: Rc<RefCell<fxhash::FxHashSet<String>>> = Default::default();
let res = catch_unwind(AssertUnwindSafe(|| {
try_with_handler(self.c.cm.clone(), true, |handler| {
self.c.run(|| {
Expand Down Expand Up @@ -102,7 +106,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(),
)
})
Expand All @@ -117,16 +129,22 @@ 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),
)),
}
}

fn resolve(self, env: Env, result: Self::Output) -> napi::Result<Self::JsValue> {
complete_output(&env, result)
fn resolve(
self,
env: Env,
(output, eliminated_packages): Self::Output,
) -> napi::Result<Self::JsValue> {
complete_output(&env, output, eliminated_packages)
}
}

Expand Down Expand Up @@ -189,7 +207,7 @@ where
})
.convert_err()?;

complete_output(cx.env, output)
complete_output(cx.env, output, Default::default())
}

#[js_function(4)]
Expand Down
4 changes: 3 additions & 1 deletion packages/next-swc/crates/wasm/src/lib.rs
Expand Up @@ -55,7 +55,9 @@ pub fn transform_sync(s: &str, opts: JsValue) -> Result<JsValue, JsValue> {
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")?;
Expand Down
14 changes: 12 additions & 2 deletions packages/next/build/webpack/loaders/next-swc-loader.js
Expand Up @@ -88,7 +88,13 @@ async function loaderTransform(parentTrace, source, inputSourceMap) {
const swcSpan = parentTrace.traceChild('next-swc-transform')
return swcSpan.traceAsyncFn(() =>
transform(source, programmaticOptions).then((output) => {
return [output.code, output.map ? JSON.parse(output.map) : undefined]
return [
output.code,
output.map ? JSON.parse(output.map) : undefined,
output.eliminatedPackages
? JSON.parse(output.eliminatedPackages)
: undefined,
]
})
)
}
Expand Down Expand Up @@ -123,13 +129,17 @@ export function pitch() {

export default function swcLoader(inputSource, inputSourceMap) {
const loaderSpan = this.currentTraceSpan.traceChild('next-swc-loader')
const globalEliminatedPackages = this.eliminatedPackages
const callback = this.async()
loaderSpan
.traceAsyncFn(() =>
loaderTransform.call(this, loaderSpan, inputSource, inputSourceMap)
)
.then(
([transformedSource, outputSourceMap]) => {
([transformedSource, outputSourceMap, eliminatedPackages]) => {
if (eliminatedPackages) {
globalEliminatedPackages.push(...eliminatedPackages)
}
callback(null, transformedSource, outputSourceMap || inputSourceMap)
},
(err) => {
Expand Down
22 changes: 21 additions & 1 deletion 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'

type Feature =
| 'next/image'
Expand Down Expand Up @@ -54,6 +57,8 @@ const BUILD_FEATURES: Array<Feature> = [
'swcEmotion',
]

const ELIMINATED_PACKAGES: string[] = []

/**
* 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
Expand Down Expand Up @@ -83,6 +88,9 @@ export class TelemetryPlugin implements webpack.WebpackPluginInstance {
compiler.hooks.make.tapAsync(
TelemetryPlugin.name,
async (compilation: webpack.Compilation, callback: () => void) => {
compilation.hooks.afterCodeGeneration.tap(TelemetryPlugin.name, () => {
console.log(compilation.assets)
})
compilation.hooks.finishModules.tapAsync(
TelemetryPlugin.name,
async (modules: Iterable<Module>, modulesFinish: () => void) => {
Expand All @@ -105,11 +113,23 @@ export class TelemetryPlugin implements webpack.WebpackPluginInstance {
callback()
}
)
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(): { package: string }[] {
return Array.from(new Set(ELIMINATED_PACKAGES)).map((pkg) => ({
package: pkg,
}))
}
}

/**
Expand Down

0 comments on commit 47a4b8e

Please sign in to comment.