Upgrade next-swc-napi to NAPI-RS v2 (#40094)

Build succeed https://github.com/vercel/next.js/actions/runs/3243091429
This commit is contained in:
LongYinan 2022-10-14 04:07:14 +08:00 committed by GitHub
parent d8395e4eeb
commit e505e7545b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 223 additions and 511 deletions

View file

@ -7,7 +7,7 @@ on:
name: Build, test, and deploy
env:
NAPI_CLI_VERSION: 2.7.0
NAPI_CLI_VERSION: 2.12.0
TURBO_VERSION: 1.3.2-canary.1
RUST_TOOLCHAIN: nightly-2022-09-23
PNPM_VERSION: 7.3.0

View file

@ -5,7 +5,7 @@ on:
name: Generate Pull Request Stats
env:
NAPI_CLI_VERSION: 2.7.0
NAPI_CLI_VERSION: 2.12.0
TURBO_VERSION: 1.3.2-canary.1
RUST_TOOLCHAIN: nightly-2022-09-23
PNPM_VERSION: 7.3.0
@ -84,7 +84,7 @@ jobs:
# since the repo's dependencies aren't installed we need
# to install napi globally
- run: npm i -g @napi-rs/cli@2.7.0
- run: npm i -g @napi-rs/cli@${NAPI_CLI_VERSION}
- run: npm i -g turbo@${TURBO_VERSION} pnpm@${PNPM_VERSION}
- name: Build

View file

@ -1525,38 +1525,60 @@ checksum = "7843ec2de400bcbc6a6328c958dc38e5359da6e93e72e37bc5246bf1ae776389"
[[package]]
name = "napi"
version = "1.8.0"
version = "2.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5586ff59e18f42d41f68139a8ca72ef1dbcc243ec62c5696e6383169a8a05a4"
checksum = "bace9a4026eaa6631804e2ff9030c47beb0483fbb12dc17950fe1530c4961f84"
dependencies = [
"bitflags",
"ctor",
"napi-sys",
"once_cell",
"serde",
"serde_json",
"winapi",
"thread_local",
]
[[package]]
name = "napi-build"
version = "1.2.1"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebd4419172727423cf30351406c54f6cc1b354a2cfb4f1dba3e6cd07f6d5522b"
checksum = "882a73d9ef23e8dc2ebbffb6a6ae2ef467c0f18ac10711e4cc59c5485d41df0e"
[[package]]
name = "napi-derive"
version = "1.1.2"
version = "2.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ee880798e942fc785e2e234544b9db578019a1d7676f45dad7f38d432ab0fe4"
checksum = "39f3d8b02ef355898ea98f69082d9a183c8701c836942c2daf3e92364e88a0fa"
dependencies = [
"convert_case",
"napi-derive-backend",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "napi-sys"
version = "1.1.2"
name = "napi-derive-backend"
version = "1.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67cf20e0081fea04e044aa4adf74cfea8ddc0324eec2894b1c700f4cafc72a56"
checksum = "6c35640513eb442fcbd1653a1c112fb6b2cc12b54d82f9c141f5859c721cab36"
dependencies = [
"convert_case",
"once_cell",
"proc-macro2",
"quote",
"regex",
"syn",
]
[[package]]
name = "napi-sys"
version = "2.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "529671ebfae679f2ce9630b62dd53c72c56b3eb8b2c852e7e2fa91704ff93d67"
dependencies = [
"libloading",
]
[[package]]
name = "native-tls"
@ -2955,6 +2977,8 @@ dependencies = [
"indexmap",
"json_comments",
"lru",
"napi",
"napi-derive",
"once_cell",
"parking_lot",
"pathdiff",
@ -3143,6 +3167,7 @@ dependencies = [
"swc_ecma_utils",
"swc_ecma_visit",
"swc_node_base",
"swc_nodejs_common",
"swc_plugin_proxy",
"swc_plugin_runner",
"swc_trace_macro",
@ -3828,6 +3853,21 @@ dependencies = [
"swc_common",
]
[[package]]
name = "swc_nodejs_common"
version = "0.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a61f704db0d5bbf239312cf2bfdd47ad7c497e407726deff80be2ea4021719c4"
dependencies = [
"anyhow",
"napi",
"serde",
"serde_json",
"swc_node_base",
"tracing",
"tracing-subscriber",
]
[[package]]
name = "swc_plugin_proxy"
version = "0.22.6"

View file

@ -4,9 +4,6 @@ name = "next-swc"
version = "0.0.0"
publish = false
[lib]
crate-type = ["cdylib", "rlib"]
[features]
plugin = [
"swc_core/plugin_transform_host_native"

View file

@ -24,8 +24,8 @@ sentry_rustls = ["_sentry_rustls"]
anyhow = "1.0"
backtrace = "0.3"
fxhash = "0.2.1"
napi = {version = "1", features = ["serde-json"]}
napi-derive = "1"
napi = {version = "2", default-features = false, features = ["napi3", "serde-json"]}
napi-derive = "2"
next-swc = {version = "0.0.0", path = "../core"}
once_cell = "1.13.0"
serde = "1"
@ -33,6 +33,7 @@ serde_json = "1"
swc_core = { features = [
"allocator_node",
"base_concurrent", # concurrent?
"base_node",
"common_concurrent",
"ecma_ast",
"ecma_loader_node",
@ -48,7 +49,7 @@ swc_core = { features = [
"ecma_transforms_react",
"ecma_transforms_typescript",
"ecma_utils",
"ecma_visit"
"ecma_visit",
], version = "0.32.8" }
tracing = { version = "0.1.32", features = ["release_max_level_info"] }
tracing-futures = "0.2.5"
@ -68,6 +69,6 @@ _sentry_rustls = { package = "sentry", version = "0.27.0", default-features = fa
], optional = true }
[build-dependencies]
napi-build = "1"
napi-build = "2"
serde = "1"
serde_json = "1"
serde_json = "1"

View file

@ -1,212 +0,0 @@
use crate::{
complete_output, get_compiler,
util::{CtxtExt, MapErr},
};
use anyhow::{anyhow, bail, Context, Error};
use napi::{CallContext, JsObject, Task};
use once_cell::sync::Lazy;
use serde::Deserialize;
use std::{collections::HashMap, path::PathBuf, sync::Arc};
use swc_core::{
base::{config::SourceMapsConfig, try_with_handler, TransformOutput},
bundler::{Bundler, ModuleData, ModuleRecord},
common::{
collections::AHashMap,
errors::{ColorConfig, Handler},
BytePos, FileName, Globals, SourceMap, Span, GLOBALS,
},
ecma::ast::*,
ecma::atoms::JsWord,
ecma::loader::{
resolvers::{lru::CachingResolver, node::NodeModulesResolver},
TargetEnv, NODE_BUILTINS,
},
ecma::parser::{lexer::Lexer, EsConfig, Parser, StringInput, Syntax},
ecma::visit::{noop_visit_type, Visit, VisitWith},
};
#[js_function(1)]
pub fn bundle(cx: CallContext) -> napi::Result<JsObject> {
let option = cx.get_buffer_as_string(0)?;
let task = BundleTask {
c: get_compiler(&cx),
config: option,
};
cx.env.spawn(task).map(|t| t.promise_object())
}
#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
struct BundleOption {
entry: PathBuf,
}
struct BundleTask {
c: Arc<swc_core::base::Compiler>,
config: String,
}
impl Task for BundleTask {
type Output = TransformOutput;
type JsValue = JsObject;
fn compute(&mut self) -> napi::Result<Self::Output> {
let option: BundleOption = crate::util::deserialize_json(&self.config).convert_err()?;
try_with_handler(
self.c.cm.clone(),
swc_core::base::HandlerOpts {
color: ColorConfig::Never,
skip_filename: true,
},
|handler| {
let builtins = NODE_BUILTINS
.iter()
.copied()
.map(JsWord::from)
.collect::<Vec<_>>();
let globals = Globals::default();
let comments = self.c.comments().clone();
//
let mut bundler = Bundler::new(
&globals,
self.c.cm.clone(),
CustomLoader {
cm: self.c.cm.clone(),
handler,
},
make_resolver(),
swc_core::bundler::Config {
require: true,
disable_inliner: false,
external_modules: builtins,
module: swc_core::bundler::ModuleType::Es,
..Default::default()
},
Box::new(CustomHook),
);
GLOBALS.set(&globals, || {
let mut entries = HashMap::default();
let path: PathBuf = option.entry;
let path = path
.canonicalize()
.context("failed to canonicalize entry file")?;
entries.insert("main".to_string(), FileName::Real(path));
let outputs = bundler.bundle(entries)?;
let output = outputs.into_iter().next().ok_or_else(|| {
anyhow!("swc_bundler::Bundle::bundle returned empty result")
})?;
let source_map_names = {
let mut v = SourceMapIdentCollector {
names: Default::default(),
};
output.module.visit_with(&mut v);
v.names
};
let code = self.c.print(
&output.module,
None,
None,
true,
EsVersion::Es5,
SourceMapsConfig::Bool(true),
&source_map_names,
None,
false,
Some(&comments),
true,
false,
)?;
Ok(code)
})
},
)
.convert_err()
}
fn resolve(self, env: napi::Env, output: Self::Output) -> napi::Result<Self::JsValue> {
complete_output(&env, output, Default::default())
}
}
type Resolver = Arc<CachingResolver<NodeModulesResolver>>;
fn make_resolver() -> Resolver {
static CACHE: Lazy<Resolver> = Lazy::new(|| {
// TODO: Make target env and alias configurable
let r = NodeModulesResolver::new(TargetEnv::Node, Default::default(), true);
let r = CachingResolver::new(256, r);
Arc::new(r)
});
(*CACHE).clone()
}
struct CustomLoader<'a> {
handler: &'a Handler,
cm: Arc<SourceMap>,
}
impl swc_core::bundler::Load for CustomLoader<'_> {
fn load(&self, f: &FileName) -> Result<ModuleData, Error> {
let fm = match f {
FileName::Real(path) => self.cm.load_file(path)?,
_ => unreachable!(),
};
let lexer = Lexer::new(
Syntax::Es(EsConfig {
..Default::default()
}),
EsVersion::Es2020,
StringInput::from(&*fm),
None,
);
let mut parser = Parser::new_from(lexer);
let module = parser.parse_module().map_err(|err| {
err.into_diagnostic(self.handler).emit();
anyhow!("failed to parse")
})?;
Ok(ModuleData {
fm,
module,
helpers: Default::default(),
})
}
}
struct CustomHook;
impl swc_core::bundler::Hook for CustomHook {
fn get_import_meta_props(
&self,
_span: Span,
_module_record: &ModuleRecord,
) -> Result<Vec<KeyValueProp>, Error> {
bail!("`import.meta` is not supported yet")
}
}
pub struct SourceMapIdentCollector {
names: AHashMap<BytePos, JsWord>,
}
impl Visit for SourceMapIdentCollector {
noop_visit_type!();
fn visit_ident(&mut self, ident: &Ident) {
self.names.insert(ident.span.lo, ident.sym.clone());
}
}

View file

@ -34,20 +34,20 @@ extern crate napi_derive;
/// Explicit extern crate to use allocator.
extern crate swc_core;
use std::{env, panic::set_hook, sync::Arc};
use backtrace::Backtrace;
use fxhash::FxHashSet;
use napi::{CallContext, Env, JsObject, JsUndefined};
use std::{env, panic::set_hook, sync::Arc};
use napi::bindgen_prelude::*;
use swc_core::{
base::{Compiler, TransformOutput},
common::{sync::Lazy, FilePathMapping, SourceMap},
};
mod bundle;
mod minify;
mod parse;
mod transform;
mod util;
pub mod minify;
pub mod parse;
pub mod transform;
pub mod util;
static COMPILER: Lazy<Arc<Compiler>> = Lazy::new(|| {
let cm = Arc::new(SourceMap::new(FilePathMapping::empty()));
@ -55,53 +55,26 @@ static COMPILER: Lazy<Arc<Compiler>> = Lazy::new(|| {
Arc::new(Compiler::new(cm))
});
#[module_exports]
fn init(mut exports: JsObject) -> napi::Result<()> {
#[napi::module_init]
fn init() {
if cfg!(debug_assertions) || env::var("SWC_DEBUG").unwrap_or_default() == "1" {
set_hook(Box::new(|panic_info| {
let backtrace = Backtrace::new();
println!("Panic: {:?}\nBacktrace: {:?}", panic_info, backtrace);
}));
}
exports.create_named_method("bundle", bundle::bundle)?;
exports.create_named_method("transform", transform::transform)?;
exports.create_named_method("transformSync", transform::transform_sync)?;
exports.create_named_method("minify", minify::minify)?;
exports.create_named_method("minifySync", minify::minify_sync)?;
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)?;
exports.create_named_method("initCrashReporter", util::init_crash_reporter)?;
exports.create_named_method("teardownCrashReporter", util::teardown_crash_reporter)?;
Ok(())
}
fn get_compiler(_ctx: &CallContext) -> Arc<Compiler> {
#[inline]
fn get_compiler() -> Arc<Compiler> {
COMPILER.clone()
}
#[js_function]
fn construct_compiler(ctx: CallContext) -> napi::Result<JsUndefined> {
// TODO: Assign swc::Compiler
ctx.env.get_undefined()
}
pub fn complete_output(
env: &Env,
output: TransformOutput,
eliminated_packages: FxHashSet<String>,
) -> napi::Result<JsObject> {
) -> napi::Result<Object> {
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 {
@ -110,9 +83,7 @@ pub fn complete_output(
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>>(),
)?)?,
env.create_string_from_std(serde_json::to_string(&eliminated_packages)?)?,
)?;
}
Ok(js_output)

View file

@ -25,20 +25,19 @@ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
*/
use crate::{
complete_output, get_compiler,
util::{CtxtExt, MapErr},
};
use fxhash::FxHashMap;
use napi::{CallContext, JsObject, Task};
use serde::Deserialize;
use std::sync::Arc;
use fxhash::FxHashMap;
use napi::bindgen_prelude::*;
use serde::Deserialize;
use swc_core::{
base::{config::JsMinifyOptions, try_with_handler, TransformOutput},
common::{errors::ColorConfig, sync::Lrc, FileName, SourceFile, SourceMap, GLOBALS},
};
struct MinifyTask {
use crate::{get_compiler, util::MapErr};
pub struct MinifyTask {
c: Arc<swc_core::base::Compiler>,
code: MinifyTarget,
opts: swc_core::base::config::JsMinifyOptions,
@ -72,10 +71,11 @@ impl MinifyTarget {
}
}
#[napi]
impl Task for MinifyTask {
type Output = TransformOutput;
type JsValue = JsObject;
type JsValue = TransformOutput;
fn compute(&mut self) -> napi::Result<Self::Output> {
try_with_handler(
@ -101,33 +101,37 @@ impl Task for MinifyTask {
.convert_err()
}
fn resolve(self, env: napi::Env, output: Self::Output) -> napi::Result<Self::JsValue> {
complete_output(&env, output, Default::default())
fn resolve(&mut self, _: napi::Env, output: Self::Output) -> napi::Result<Self::JsValue> {
Ok(output)
}
}
#[js_function(2)]
pub fn minify(cx: CallContext) -> napi::Result<JsObject> {
let code = cx.get_deserialized(0)?;
let opts = cx.get_deserialized(1)?;
#[napi]
pub fn minify(
input: Buffer,
opts: Buffer,
signal: Option<AbortSignal>,
) -> napi::Result<AsyncTask<MinifyTask>> {
let code = serde_json::from_slice(&input)?;
let opts = serde_json::from_slice(&opts)?;
let c = get_compiler(&cx);
let c = get_compiler();
let task = MinifyTask { c, code, opts };
cx.env.spawn(task).map(|t| t.promise_object())
Ok(AsyncTask::with_optional_signal(task, signal))
}
#[js_function(2)]
pub fn minify_sync(cx: CallContext) -> napi::Result<JsObject> {
let code: MinifyTarget = cx.get_deserialized(0)?;
let opts = cx.get_deserialized(1)?;
#[napi]
pub fn minify_sync(input: Buffer, opts: Buffer) -> napi::Result<TransformOutput> {
let code: MinifyTarget = serde_json::from_slice(&input)?;
let opts = serde_json::from_slice(&opts)?;
let c = get_compiler(&cx);
let c = get_compiler();
let fm = code.to_file(c.cm.clone());
let output = try_with_handler(
try_with_handler(
c.cm.clone(),
swc_core::base::HandlerOpts {
color: ColorConfig::Never,
@ -135,7 +139,5 @@ pub fn minify_sync(cx: CallContext) -> napi::Result<JsObject> {
},
|handler| GLOBALS.set(&Default::default(), || c.minify(fm, handler, &opts)),
)
.convert_err()?;
complete_output(cx.env, output, Default::default())
.convert_err()
}

View file

@ -1,7 +1,7 @@
use crate::util::{deserialize_json, CtxtExt, MapErr};
use anyhow::Context as _;
use napi::{CallContext, Either, Env, JsObject, JsString, JsUndefined, Task};
use std::sync::Arc;
use anyhow::Context as _;
use napi::bindgen_prelude::*;
use swc_core::{
base::{config::ParseOptions, try_with_handler},
common::{
@ -9,26 +9,25 @@ use swc_core::{
},
};
use crate::util::MapErr;
pub struct ParseTask {
pub filename: FileName,
pub src: String,
pub options: String,
}
pub fn complete_parse(env: &Env, ast_json: String) -> napi::Result<JsString> {
env.create_string_from_std(ast_json)
pub options: Buffer,
}
#[napi]
impl Task for ParseTask {
type Output = String;
type JsValue = JsString;
type JsValue = String;
fn compute(&mut self) -> napi::Result<Self::Output> {
GLOBALS.set(&Default::default(), || {
let c =
swc_core::base::Compiler::new(Arc::new(SourceMap::new(FilePathMapping::empty())));
let options: ParseOptions = deserialize_json(&self.options).convert_err()?;
let options: ParseOptions = serde_json::from_slice(self.options.as_ref())?;
let comments = c.comments().clone();
let comments: Option<&dyn Comments> = if options.comments {
Some(&comments)
@ -64,27 +63,29 @@ impl Task for ParseTask {
})
}
fn resolve(self, env: Env, result: Self::Output) -> napi::Result<Self::JsValue> {
complete_parse(&env, result)
fn resolve(&mut self, _env: Env, result: Self::Output) -> napi::Result<Self::JsValue> {
Ok(result)
}
}
#[js_function(3)]
pub fn parse(ctx: CallContext) -> napi::Result<JsObject> {
let src = ctx.get::<JsString>(0)?.into_utf8()?.as_str()?.to_string();
let options = ctx.get_buffer_as_string(1)?;
let filename = ctx.get::<Either<JsString, JsUndefined>>(2)?;
let filename = if let Either::A(value) = filename {
FileName::Real(value.into_utf8()?.as_str()?.to_owned().into())
#[napi]
pub fn parse(
src: String,
options: Buffer,
filename: Option<String>,
signal: Option<AbortSignal>,
) -> AsyncTask<ParseTask> {
let filename = if let Some(value) = filename {
FileName::Real(value.into())
} else {
FileName::Anon
};
ctx.env
.spawn(ParseTask {
AsyncTask::with_optional_signal(
ParseTask {
filename,
src,
options,
})
.map(|t| t.promise_object())
},
signal,
)
}

View file

@ -26,28 +26,26 @@ IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
*/
use crate::{
complete_output, get_compiler,
util::{deserialize_json, CtxtExt, MapErr},
};
use anyhow::{anyhow, bail, Context as _};
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 anyhow::{anyhow, bail, Context as _};
use fxhash::FxHashSet;
use napi::bindgen_prelude::*;
use next_swc::{custom_before_pass, TransformOptions};
use swc_core::{
base::{try_with_handler, Compiler, TransformOutput},
common::{errors::ColorConfig, FileName, GLOBALS},
ecma::transforms::base::pass::noop,
};
use crate::{complete_output, get_compiler, util::MapErr};
/// Input to transform
#[derive(Debug)]
pub enum Input {
@ -60,12 +58,12 @@ pub enum Input {
pub struct TransformTask {
pub c: Arc<Compiler>,
pub input: Input,
pub options: String,
pub options: Buffer,
}
impl Task for TransformTask {
type Output = (TransformOutput, FxHashSet<String>);
type JsValue = JsObject;
type JsValue = Object;
fn compute(&mut self) -> napi::Result<Self::Output> {
GLOBALS.set(&Default::default(), || {
@ -79,7 +77,7 @@ impl Task for TransformTask {
},
|handler| {
self.c.run(|| {
let options: TransformOptions = deserialize_json(&self.options)?;
let options: TransformOptions = serde_json::from_slice(&self.options)?;
let fm = match &self.input {
Input::Source { src } => {
let filename = if options.swc.filename.is_empty() {
@ -150,7 +148,7 @@ impl Task for TransformTask {
}
fn resolve(
self,
&mut self,
env: Env,
(output, eliminated_packages): Self::Output,
) -> napi::Result<Self::JsValue> {
@ -158,58 +156,48 @@ impl Task for TransformTask {
}
}
/// returns `compiler, (src / path), options, plugin, callback`
pub fn schedule_transform<F>(cx: &CallContext, op: F) -> napi::Result<TransformTask>
where
F: FnOnce(&Arc<Compiler>, Input, bool, String) -> TransformTask,
{
let c = get_compiler(cx);
#[napi]
pub fn transform(
src: Either3<String, Buffer, Undefined>,
_is_module: bool,
options: Buffer,
signal: Option<AbortSignal>,
) -> napi::Result<AsyncTask<TransformTask>> {
let c = get_compiler();
let unknown_src = cx.get::<JsUnknown>(0)?;
let src = match unknown_src.get_type()? {
napi::ValueType::String => napi::Result::Ok(Input::Source {
src: JsString::try_from(unknown_src)?
.into_utf8()?
.as_str()?
.to_owned(),
}),
napi::ValueType::Object => napi::Result::Ok(Input::Source {
src: String::from_utf8_lossy(JsBuffer::try_from(unknown_src)?.into_value()?.as_ref())
.to_string(),
}),
napi::ValueType::Undefined => napi::Result::Ok(Input::FromFilename),
_ => Err(napi::Error::new(
Status::GenericFailure,
"first argument must be a String or Buffer".to_string(),
)),
}?;
let is_module = cx.get::<JsBoolean>(1)?;
let options = cx.get_buffer_as_string(2)?;
let input = match src {
Either3::A(src) => Input::Source { src },
Either3::B(src) => Input::Source {
src: String::from_utf8_lossy(&src).to_string(),
},
Either3::C(_) => Input::FromFilename,
};
Ok(op(&c, src, is_module.get_value()?, options))
let task = TransformTask { c, input, options };
Ok(AsyncTask::with_optional_signal(task, signal))
}
#[js_function(4)]
pub fn transform(cx: CallContext) -> napi::Result<JsObject> {
let task = schedule_transform(&cx, |c, input, _, options| TransformTask {
c: c.clone(),
input,
options,
})?;
cx.env.spawn(task).map(|handle| handle.promise_object())
}
#[napi]
pub fn transform_sync(
env: Env,
src: Either3<String, Buffer, Undefined>,
_is_module: bool,
options: Buffer,
) -> napi::Result<Object> {
let c = get_compiler();
#[js_function(4)]
pub fn transform_sync(cx: CallContext) -> napi::Result<JsObject> {
let mut task = schedule_transform(&cx, |c, input, _, options| TransformTask {
c: c.clone(),
input,
options,
})?;
let input = match src {
Either3::A(src) => Input::Source { src },
Either3::B(src) => Input::Source {
src: String::from_utf8_lossy(&src).to_string(),
},
Either3::C(_) => Input::FromFilename,
};
let mut task = TransformTask { c, input, options };
let output = task.compute()?;
task.resolve(*cx.env, output)
task.resolve(env, output)
}
#[test]
fn test_deser() {
const JSON_STR: &str = r#"{"jsc":{"parser":{"syntax":"ecmascript","dynamicImport":true,"jsx":true},"transform":{"react":{"runtime":"automatic","pragma":"React.createElement","pragmaFrag":"React.Fragment","throwIfNamespace":true,"development":false,"useBuiltins":true}},"target":"es5"},"filename":"/Users/timneutkens/projects/next.js/packages/next/dist/client/next.js","sourceMaps":false,"sourceFileName":"/Users/timneutkens/projects/next.js/packages/next/dist/client/next.js"}"#;

View file

@ -26,10 +26,12 @@ IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
*/
use anyhow::{anyhow, Context, Error};
use napi::{CallContext, Env, JsBuffer, JsExternal, JsString, JsUndefined, JsUnknown, Status};
use serde::de::DeserializeOwned;
use std::{any::type_name, cell::RefCell, convert::TryFrom, env, path::PathBuf};
use std::{cell::RefCell, env, path::PathBuf};
use anyhow::anyhow;
use napi::bindgen_prelude::{External, Status};
#[cfg(feature = "crash-report")]
use sentry::{init, types::Dsn, ClientInitGuard, ClientOptions};
use tracing_chrome::{ChromeLayerBuilder, FlushGuard};
use tracing_subscriber::{filter, prelude::*, util::SubscriberInitExt, Layer};
@ -37,9 +39,9 @@ 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"));
#[contextless_function]
pub fn get_target_triple(env: Env) -> napi::ContextlessResult<JsString> {
env.create_string(TARGET_TRIPLE).map(Some)
#[napi]
pub fn get_target_triple() -> String {
TARGET_TRIPLE.to_owned()
}
pub trait MapErr<T>: Into<Result<T, anyhow::Error>> {
@ -51,63 +53,13 @@ pub trait MapErr<T>: Into<Result<T, anyhow::Error>> {
impl<T> MapErr<T> for Result<T, anyhow::Error> {}
pub trait CtxtExt {
fn get_buffer_as_string(&self, index: usize) -> napi::Result<String>;
/// Currently this uses JsBuffer
fn get_deserialized<T>(&self, index: usize) -> napi::Result<T>
where
T: DeserializeOwned;
}
impl CtxtExt for CallContext<'_> {
fn get_buffer_as_string(&self, index: usize) -> napi::Result<String> {
let buffer = self.get::<JsBuffer>(index)?.into_value()?;
Ok(String::from_utf8_lossy(buffer.as_ref()).to_string())
}
fn get_deserialized<T>(&self, index: usize) -> napi::Result<T>
where
T: DeserializeOwned,
{
let buffer = self.get::<JsBuffer>(index)?.into_value()?;
let v = serde_json::from_slice(&buffer)
.with_context(|| {
format!(
"Failed to deserialize argument at `{}` as {}\nJSON: {}",
index,
type_name::<T>(),
String::from_utf8_lossy(&buffer)
)
})
.convert_err()?;
Ok(v)
}
}
pub(crate) fn deserialize_json<T>(s: &str) -> Result<T, Error>
where
T: DeserializeOwned,
{
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,
};
#[napi]
pub fn init_custom_trace_subscriber(
trace_out_file_path: Option<String>,
) -> napi::Result<External<RefCell<Option<FlushGuard>>>> {
let trace_out_file_path = trace_out_file_path.map(PathBuf::from);
let mut layer = ChromeLayerBuilder::new().include_args(true);
if let Some(trace_out_file) = trace_out_file_path {
@ -129,57 +81,47 @@ pub fn init_custom_trace_subscriber(cx: CallContext) -> napi::Result<JsExternal>
.expect("Failed to register tracing subscriber");
let guard_cell = RefCell::new(Some(guard));
cx.env.create_external(guard_cell, None)
Ok(External::new(guard_cell))
}
/// 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)?;
#[napi]
pub fn teardown_trace_subscriber(guard_external: External<RefCell<Option<FlushGuard>>>) {
let guard_cell = &*guard_external;
if let Some(guard) = guard_cell.take() {
drop(guard);
}
cx.env.get_undefined()
}
#[cfg(any(
target_arch = "wasm32",
#[cfg(all(
all(target_os = "windows", target_arch = "aarch64"),
not(all(feature = "sentry_native_tls", feature = "sentry_rustls"))
feature = "crash-report"
))]
#[js_function(1)]
pub fn init_crash_reporter(cx: CallContext) -> napi::Result<JsExternal> {
#[napi]
pub fn init_crash_reporter() -> External<RefCell<Option<usize>>> {
let guard: Option<usize> = None;
let guard_cell = RefCell::new(guard);
cx.env.create_external(guard_cell, None)
External::new(guard_cell)
}
/// Initialize crash reporter to collect unexpected native next-swc crashes.
#[cfg(all(
not(target_arch = "wasm32"),
not(all(target_os = "windows", target_arch = "aarch64")),
any(feature = "sentry_native_tls", feature = "sentry_rustls")
feature = "crash-report"
))]
#[js_function(1)]
pub fn init_crash_reporter(cx: CallContext) -> napi::Result<JsExternal> {
#[napi]
pub fn init_crash_reporter() -> External<RefCell<Option<ClientInitGuard>>> {
use std::{borrow::Cow, str::FromStr};
// Attempts to follow https://nextjs.org/telemetry's debug behavior.
// However, this is techinically not identical to the behavior of the telemetry
// itself as sentry's debug option does not provides full payuload output.
let debug = env::var("NEXT_TELEMETRY_DEBUG").map_or_else(|_| false, |v| v == "1");
let guard = {
#[cfg(feature = "sentry_native_tls")]
use _sentry_native_tls::{init, types::Dsn, ClientOptions};
#[cfg(feature = "sentry_rustls")]
use _sentry_rustls::{init, types::Dsn, ClientOptions};
use std::{borrow::Cow, str::FromStr};
let dsn = if debug {
None
} else {
@ -201,42 +143,34 @@ pub fn init_crash_reporter(cx: CallContext) -> napi::Result<JsExternal> {
};
let guard_cell = RefCell::new(guard);
cx.env.create_external(guard_cell, None)
External::new(guard_cell)
}
#[cfg(any(
target_arch = "wasm32",
#[cfg(all(
all(target_os = "windows", target_arch = "aarch64"),
not(all(feature = "sentry_native_tls", feature = "sentry_rustls"))
feature = "crash-report"
))]
#[js_function(1)]
pub fn teardown_crash_reporter(cx: CallContext) -> napi::Result<JsUndefined> {
cx.env.get_undefined()
#[napi]
pub fn teardown_crash_reporter(guard_external: External<RefCell<Option<usize>>>) {
let guard_cell = &*guard_external;
if let Some(guard) = guard_cell.take() {
drop(guard);
}
}
/// Trying to drop crash reporter guard if exists. This is the way to hold
/// guards to not to be dropped immediately after crash reporter is initialized
/// in napi context.
#[cfg(all(
not(target_arch = "wasm32"),
not(all(target_os = "windows", target_arch = "aarch64")),
any(feature = "sentry_native_tls", feature = "sentry_rustls")
feature = "crash-report"
))]
#[js_function(1)]
pub fn teardown_crash_reporter(cx: CallContext) -> napi::Result<JsUndefined> {
#[cfg(feature = "sentry_native_tls")]
use _sentry_native_tls::ClientInitGuard;
#[cfg(feature = "sentry_rustls")]
use _sentry_rustls::ClientInitGuard;
let guard_external = cx.get::<JsExternal>(0)?;
let guard_cell = &*cx
.env
.get_value_external::<RefCell<Option<ClientInitGuard>>>(&guard_external)?;
#[napi]
pub fn teardown_crash_reporter(guard_external: External<RefCell<Option<ClientInitGuard>>>) {
let guard_cell = &*guard_external;
if let Some(guard) = guard_cell.take() {
drop(guard);
}
cx.env.get_undefined()
}

View file

@ -3,8 +3,8 @@
"version": "12.3.2-canary.27",
"private": true,
"scripts": {
"build-native": "napi build --platform -p next-swc-napi --cargo-name next_swc_napi native --features plugin",
"build-native-no-plugin": "napi build --platform -p next-swc-napi --cargo-name next_swc_napi native",
"build-native": "napi build --platform -p next-swc-napi --cargo-name next_swc_napi --features plugin --js false native",
"build-native-no-plugin": "napi build --platform -p next-swc-napi --cargo-name next_swc_napi --js false native",
"build-wasm": "wasm-pack build crates/wasm --scope=next"
},
"napi": {
@ -26,6 +26,6 @@
}
},
"devDependencies": {
"@napi-rs/cli": "2.7.0"
"@napi-rs/cli": "2.12.0"
}
}

View file

@ -3,7 +3,6 @@ export function transform(src: string, options?: any): Promise<any>
export function transformSync(src: string, options?: any): any
export function minify(src: string, options: any): Promise<string>
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

View file

@ -325,10 +325,6 @@ function loadNative() {
return bindings.minifySync(toBuffer(src), toBuffer(options ?? {}))
},
bundle(options) {
return bindings.bundle(toBuffer(options))
},
parse(src, options) {
return bindings.parse(src, toBuffer(options ?? {}))
},
@ -373,11 +369,6 @@ export function minifySync(src, options) {
return bindings.minifySync(src, options)
}
export async function bundle(options) {
let bindings = loadBindingsSync()
return bindings.bundle(toBuffer(options))
}
export async function parse(src, options) {
let bindings = await loadBindings()
let parserOptions = getParserOptions(options)

View file

@ -122,7 +122,7 @@
"@babel/types": "7.18.0",
"@edge-runtime/primitives": "1.1.0-beta.36",
"@hapi/accept": "5.0.2",
"@napi-rs/cli": "2.7.0",
"@napi-rs/cli": "2.12.0",
"@napi-rs/triples": "1.1.0",
"@next/polyfill-module": "12.3.2-canary.27",
"@next/polyfill-nomodule": "12.3.2-canary.27",

View file

@ -463,7 +463,7 @@ importers:
'@babel/types': 7.18.0
'@edge-runtime/primitives': 1.1.0-beta.36
'@hapi/accept': 5.0.2
'@napi-rs/cli': 2.7.0
'@napi-rs/cli': 2.12.0
'@napi-rs/triples': 1.1.0
'@next/env': 12.3.2-canary.27
'@next/polyfill-module': 12.3.2-canary.27
@ -665,7 +665,7 @@ importers:
'@babel/types': 7.18.0
'@edge-runtime/primitives': 1.1.0-beta.36
'@hapi/accept': 5.0.2
'@napi-rs/cli': 2.7.0
'@napi-rs/cli': 2.12.0
'@napi-rs/triples': 1.1.0
'@next/polyfill-module': link:../next-polyfill-module
'@next/polyfill-nomodule': link:../next-polyfill-nomodule
@ -898,9 +898,9 @@ importers:
packages/next-swc:
specifiers:
'@napi-rs/cli': 2.7.0
'@napi-rs/cli': 2.12.0
devDependencies:
'@napi-rs/cli': 2.7.0
'@napi-rs/cli': 2.12.0
packages/react-dev-overlay:
specifiers:
@ -5886,10 +5886,10 @@ packages:
glob-to-regexp: 0.3.0
dev: true
/@napi-rs/cli/2.7.0:
/@napi-rs/cli/2.12.0:
resolution:
{
integrity: sha512-bQb+r9/xW8LFRbpEN7A/4fX8LnEWbI0JzyOzXGDpO+cI8dXRxX7OPySOpzT2nBgP1brA2Ydkw/t9lyxLN9TlxQ==,
integrity: sha512-DWx9jDpun9JqDBypiXKjcYMm7gEnh83bry7b6UkItpmVE3w3tNrj91fOEPKDbFQZ7EULfFt+aQBbqtUHq5oNzQ==,
}
engines: { node: '>= 10' }
hasBin: true