feat(next-swc): introduce experimental tracing support for swc (#35803)
* feat(next-swc/napi): expose initcustomtracesubscriber * feat(next/config): enable experimental swcTrace * feat(trace): add trace for emotion transform * refactor(swc): use .next for the default trace output * refactor(swc): teardown subscriber via drop * refactor(swc/trace): simplify config * refactor(swc/trace): adjust teardown
This commit is contained in:
parent
3692a5ecdb
commit
837e0a6af8
13 changed files with 189 additions and 8 deletions
53
packages/next-swc/Cargo.lock
generated
53
packages/next-swc/Cargo.lock
generated
|
@ -717,6 +717,12 @@ dependencies = [
|
|||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "json"
|
||||
version = "0.12.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "078e285eafdfb6c4b434e0d31e8cfcb5115b651496faca5749b88fafd4f23bfd"
|
||||
|
||||
[[package]]
|
||||
name = "json_comments"
|
||||
version = "0.2.1"
|
||||
|
@ -1043,6 +1049,10 @@ dependencies = [
|
|||
"swc_ecma_loader",
|
||||
"swc_ecmascript",
|
||||
"swc_node_base",
|
||||
"tracing",
|
||||
"tracing-chrome",
|
||||
"tracing-futures",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1303,6 +1313,26 @@ dependencies = [
|
|||
"siphasher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project"
|
||||
version = "1.0.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "58ad3879ad3baf4e44784bc6a718a8698867bb991f8ce24d1bcbe2cfb4c3a75e"
|
||||
dependencies = [
|
||||
"pin-project-internal",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-internal"
|
||||
version = "1.0.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "744b6f092ba29c3650faf274db506afd39944f48420f6c86b17cfe0ee1cb36bb"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.8"
|
||||
|
@ -2555,7 +2585,9 @@ dependencies = [
|
|||
"swc_common",
|
||||
"swc_ecma_transforms_testing",
|
||||
"swc_ecmascript",
|
||||
"swc_trace_macro",
|
||||
"testing",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2851,6 +2883,17 @@ dependencies = [
|
|||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-chrome"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fcb13184244c7cd22758b79e7c993c515ad67a8e730edcb7e05fe7bcabb283c7"
|
||||
dependencies = [
|
||||
"json",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-core"
|
||||
version = "0.1.26"
|
||||
|
@ -2861,6 +2904,16 @@ dependencies = [
|
|||
"valuable",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-futures"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2"
|
||||
dependencies = [
|
||||
"pin-project",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-log"
|
||||
version = "0.1.2"
|
||||
|
|
|
@ -27,8 +27,7 @@ swc_common = { version = "0.17.25", features = ["concurrent", "sourcemap"] }
|
|||
swc_ecma_loader = { version = "0.29.1", features = ["node", "lru"] }
|
||||
swc_ecmascript = { version = "0.150.0", features = ["codegen", "minifier", "optimization", "parser", "react", "transforms", "typescript", "utils", "visit"] }
|
||||
swc_cached = "0.1.1"
|
||||
tracing = { version = "0.1.32", features = ["release_max_level_off"] }
|
||||
|
||||
tracing = { version = "0.1.32", features = ["release_max_level_info"] }
|
||||
|
||||
[dev-dependencies]
|
||||
swc_ecma_transforms_testing = "0.82.0"
|
||||
|
|
|
@ -21,6 +21,8 @@ sourcemap = "6.0.1"
|
|||
swc_atoms = "0.2.11"
|
||||
swc_common = { version = "0.17.25", features = ["concurrent", "sourcemap"] }
|
||||
swc_ecmascript = { version = "0.150.0", features = ["codegen", "utils", "visit"] }
|
||||
swc_trace_macro = "0.1.1"
|
||||
tracing = { version = "0.1.32", features = ["release_max_level_info"] }
|
||||
|
||||
[dev-dependencies]
|
||||
swc_ecma_transforms_testing = "0.82.0"
|
||||
|
|
|
@ -25,6 +25,7 @@ use swc_ecmascript::{
|
|||
codegen::util::SourceMapperExt,
|
||||
visit::{Fold, FoldWith},
|
||||
};
|
||||
use swc_trace_macro::swc_trace;
|
||||
|
||||
mod hash;
|
||||
|
||||
|
@ -164,6 +165,7 @@ pub struct EmotionTransformer<C: Comments> {
|
|||
in_jsx_element: bool,
|
||||
}
|
||||
|
||||
#[swc_trace]
|
||||
impl<C: Comments> EmotionTransformer<C> {
|
||||
pub fn new(options: EmotionOptions, path: &Path, cm: Arc<SourceMap>, comments: C) -> Self {
|
||||
EmotionTransformer {
|
||||
|
|
|
@ -24,6 +24,11 @@ swc_common = { version = "0.17.25", features = ["concurrent", "sourcemap"] }
|
|||
swc_ecma_loader = { version = "0.29.1", features = ["node", "lru"] }
|
||||
swc_ecmascript = { version = "0.150.0", features = ["codegen", "minifier", "optimization", "parser", "react", "transforms", "typescript", "utils", "visit"] }
|
||||
swc_node_base = "0.5.2"
|
||||
tracing = { version = "0.1.32", features = ["release_max_level_info"] }
|
||||
tracing-futures = "0.2.5"
|
||||
tracing-subscriber = "0.3.9"
|
||||
|
||||
tracing-chrome = "0.5.0"
|
||||
|
||||
[build-dependencies]
|
||||
napi-build = "1"
|
||||
|
|
|
@ -73,6 +73,11 @@ fn init(mut exports: JsObject) -> napi::Result<()> {
|
|||
exports.create_named_method("parse", parse::parse)?;
|
||||
|
||||
exports.create_named_method("getTargetTriple", util::get_target_triple)?;
|
||||
exports.create_named_method(
|
||||
"initCustomTraceSubscriber",
|
||||
util::init_custom_trace_subscriber,
|
||||
)?;
|
||||
exports.create_named_method("teardownTraceSubscriber", util::teardown_trace_subscriber)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -26,13 +26,14 @@ IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|||
DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
use anyhow::{Context, Error};
|
||||
use napi::{CallContext, Env, JsBuffer, JsString, Status};
|
||||
use anyhow::{anyhow, Context, Error};
|
||||
use napi::{CallContext, Env, JsBuffer, JsExternal, JsString, JsUndefined, JsUnknown, Status};
|
||||
use serde::de::DeserializeOwned;
|
||||
use std::any::type_name;
|
||||
use std::{any::type_name, cell::RefCell, convert::TryFrom, path::PathBuf};
|
||||
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"));
|
||||
|
||||
#[contextless_function]
|
||||
pub fn get_target_triple(env: Env) -> napi::ContextlessResult<JsString> {
|
||||
env.create_string(TARGET_TRIPLE).map(Some)
|
||||
|
@ -89,3 +90,57 @@ where
|
|||
serde_json::from_str(s)
|
||||
.with_context(|| format!("failed to deserialize as {}\nJSON: {}", type_name::<T>(), s))
|
||||
}
|
||||
|
||||
/// Initialize tracing subscriber to emit traces. This configures subscribers
|
||||
/// for Trace Event Format (https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/preview).
|
||||
#[js_function(1)]
|
||||
pub fn init_custom_trace_subscriber(cx: CallContext) -> napi::Result<JsExternal> {
|
||||
let optional_trace_out_file_path = cx.get::<JsUnknown>(0)?;
|
||||
let trace_out_file_path = match optional_trace_out_file_path.get_type()? {
|
||||
napi::ValueType::String => Some(PathBuf::from(
|
||||
JsString::try_from(optional_trace_out_file_path)?
|
||||
.into_utf8()?
|
||||
.as_str()?
|
||||
.to_owned(),
|
||||
)),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
let mut layer = ChromeLayerBuilder::new().include_args(true);
|
||||
if let Some(trace_out_file) = trace_out_file_path {
|
||||
let dir = trace_out_file
|
||||
.parent()
|
||||
.ok_or_else(|| anyhow!("Not able to find path to the trace output"))
|
||||
.convert_err()?;
|
||||
std::fs::create_dir_all(dir)?;
|
||||
|
||||
layer = layer.file(trace_out_file);
|
||||
}
|
||||
|
||||
let (chrome_layer, guard) = layer.build();
|
||||
tracing_subscriber::registry()
|
||||
.with(chrome_layer.with_filter(filter::filter_fn(|metadata| {
|
||||
!metadata.target().contains("cranelift") && !metadata.name().contains("log ")
|
||||
})))
|
||||
.try_init()
|
||||
.expect("Failed to register tracing subscriber");
|
||||
|
||||
let guard_cell = RefCell::new(Some(guard));
|
||||
cx.env.create_external(guard_cell, None)
|
||||
}
|
||||
|
||||
/// Teardown currently running tracing subscriber to flush out remaining traces.
|
||||
/// This should be called when parent node.js process exits, otherwise generated
|
||||
/// trace may drop traces in the buffer.
|
||||
#[js_function(1)]
|
||||
pub fn teardown_trace_subscriber(cx: CallContext) -> napi::Result<JsUndefined> {
|
||||
let guard_external = cx.get::<JsExternal>(0)?;
|
||||
let guard_cell = &*cx
|
||||
.env
|
||||
.get_value_external::<RefCell<Option<FlushGuard>>>(&guard_external)?;
|
||||
|
||||
if let Some(guard) = guard_cell.take() {
|
||||
drop(guard);
|
||||
}
|
||||
cx.env.get_undefined()
|
||||
}
|
||||
|
|
|
@ -113,7 +113,7 @@ import { TelemetryPlugin } from './webpack/plugins/telemetry-plugin'
|
|||
import { MiddlewareManifest } from './webpack/plugins/middleware-plugin'
|
||||
import { recursiveCopy } from '../lib/recursive-copy'
|
||||
import { recursiveReadDir } from '../lib/recursive-readdir'
|
||||
import { lockfilePatchPromise } from './swc'
|
||||
import { lockfilePatchPromise, teardownTraceSubscriber } from './swc'
|
||||
|
||||
export type SsgRoute = {
|
||||
initialRevalidateSeconds: number | false
|
||||
|
@ -2212,6 +2212,7 @@ export default async function build(
|
|||
|
||||
// Ensure all traces are flushed before finishing the command
|
||||
await flushAllTraces()
|
||||
teardownTraceSubscriber()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import createStore from 'next/dist/compiled/unistore'
|
||||
import stripAnsi from 'next/dist/compiled/strip-ansi'
|
||||
import { flushAllTraces } from '../../trace'
|
||||
|
||||
import { teardownTraceSubscriber } from '../swc'
|
||||
import * as Log from './log'
|
||||
|
||||
export type OutputState =
|
||||
|
@ -91,6 +91,7 @@ store.subscribe((state) => {
|
|||
|
||||
// Ensure traces are flushed after each compile in development mode
|
||||
flushAllTraces()
|
||||
teardownTraceSubscriber()
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -117,6 +118,7 @@ store.subscribe((state) => {
|
|||
Log.warn(state.warnings.join('\n\n'))
|
||||
// Ensure traces are flushed after each compile in development mode
|
||||
flushAllTraces()
|
||||
teardownTraceSubscriber()
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -132,4 +134,5 @@ store.subscribe((state) => {
|
|||
)
|
||||
// Ensure traces are flushed after each compile in development mode
|
||||
flushAllTraces()
|
||||
teardownTraceSubscriber()
|
||||
})
|
||||
|
|
2
packages/next/build/swc/index.d.ts
vendored
2
packages/next/build/swc/index.d.ts
vendored
|
@ -6,3 +6,5 @@ export function minifySync(src: string, options: any): string
|
|||
export function bundle(options: any): Promise<any>
|
||||
export function parse(src: string, options: any): any
|
||||
export const lockfilePatchPromise: { cur?: Promise<void> }
|
||||
export function initCustomTraceSubscriber(traceFileName?: string): void
|
||||
export function teardownTraceSubscriber(): void
|
||||
|
|
|
@ -17,6 +17,7 @@ let nativeBindings
|
|||
let wasmBindings
|
||||
let downloadWasmPromise
|
||||
let pendingBindings
|
||||
let swcTraceFlushGuard
|
||||
export const lockfilePatchPromise = {}
|
||||
|
||||
async function loadBindings() {
|
||||
|
@ -261,6 +262,8 @@ function loadNative() {
|
|||
},
|
||||
|
||||
getTargetTriple: bindings.getTargetTriple,
|
||||
initCustomTraceSubscriber: bindings.initCustomTraceSubscriber,
|
||||
teardownTraceSubscriber: bindings.teardownTraceSubscriber,
|
||||
}
|
||||
return nativeBindings
|
||||
}
|
||||
|
@ -320,3 +323,43 @@ export function getBinaryMetadata() {
|
|||
target: bindings?.getTargetTriple?.(),
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize trace subscriber to emit traces.
|
||||
*
|
||||
*/
|
||||
export const initCustomTraceSubscriber = (() => {
|
||||
return (filename) => {
|
||||
if (!swcTraceFlushGuard) {
|
||||
// Wasm binary doesn't support trace emission
|
||||
let bindings = loadNative()
|
||||
swcTraceFlushGuard = bindings.initCustomTraceSubscriber(filename)
|
||||
}
|
||||
}
|
||||
})()
|
||||
|
||||
/**
|
||||
* Teardown swc's trace subscriber if there's an initialized flush guard exists.
|
||||
*
|
||||
* This is workaround to amend behavior with process.exit
|
||||
* (https://github.com/vercel/next.js/blob/4db8c49cc31e4fc182391fae6903fb5ef4e8c66e/packages/next/bin/next.ts#L134=)
|
||||
* seems preventing napi's cleanup hook execution (https://github.com/swc-project/swc/blob/main/crates/node/src/util.rs#L48-L51=),
|
||||
*
|
||||
* instead parent process manually drops guard when process gets signal to exit.
|
||||
*/
|
||||
export const teardownTraceSubscriber = (() => {
|
||||
let flushed = false
|
||||
return () => {
|
||||
if (!flushed) {
|
||||
flushed = true
|
||||
try {
|
||||
let bindings = loadNative()
|
||||
if (swcTraceFlushGuard) {
|
||||
bindings.teardownTraceSubscriber(swcTraceFlushGuard)
|
||||
}
|
||||
} catch (e) {
|
||||
// Suppress exceptions, this fn allows to fail to load native bindings
|
||||
}
|
||||
}
|
||||
}
|
||||
})()
|
||||
|
|
|
@ -436,6 +436,16 @@ export default async function getBaseWebpackConfig(
|
|||
}
|
||||
|
||||
const getBabelOrSwcLoader = () => {
|
||||
if (useSWCLoader && config?.experimental?.swcTraceProfiling) {
|
||||
// This will init subscribers once only in a single process lifecycle,
|
||||
// even though it can be called multiple times.
|
||||
// Subscriber need to be initialized _before_ any actual swc's call (transform, etcs)
|
||||
// to collect correct trace spans when they are called.
|
||||
require('./swc')?.initCustomTraceSubscriber?.(
|
||||
path.join(distDir, `swc-trace-profile-${Date.now()}.json`)
|
||||
)
|
||||
}
|
||||
|
||||
return useSWCLoader
|
||||
? {
|
||||
loader: 'next-swc-loader',
|
||||
|
|
|
@ -132,6 +132,7 @@ export interface ExperimentalConfig {
|
|||
skipDefaultConversion?: boolean
|
||||
}
|
||||
>
|
||||
swcTraceProfiling?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Loading…
Reference in a new issue