read file in swc when no loaders follow the next-swc-loader (#31682)

Fixes #31685


## Bug

- [ ] Related issues linked using `fixes #number`
- [ ] Integration tests added
- [ ] Errors have helpful link attached, see `contributing.md`

## Feature

- [ ] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR.
- [ ] Related issues linked using `fixes #number`
- [ ] Integration tests added
- [ ] Documentation added
- [ ] Telemetry added. In case of a feature if it's used or not.
- [ ] Errors have helpful link attached, see `contributing.md`

## Documentation / Examples

- [ ] Make sure the linting passes by running `yarn lint`


Co-authored-by: Tim Neutkens <6324199+timneutkens@users.noreply.github.com>
This commit is contained in:
Tobias Koppers 2021-11-25 15:00:14 +01:00 committed by GitHub
parent b71f9d9475
commit a4159321b2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 87 additions and 39 deletions

View file

@ -31,8 +31,9 @@ use crate::{
util::{deserialize_json, CtxtExt, MapErr}, util::{deserialize_json, CtxtExt, MapErr},
}; };
use next_swc::{custom_before_pass, TransformOptions}; use next_swc::{custom_before_pass, TransformOptions};
use anyhow::{anyhow, Context as _, Error}; use anyhow::{anyhow, bail, Context as _, Error};
use napi::{CallContext, Env, JsBoolean, JsBuffer, JsObject, JsString, JsUnknown, Status, Task}; use napi::{CallContext, Env, JsBoolean, JsBuffer, JsObject, JsString, JsUnknown, Status, Task};
use std::fs::read_to_string;
use std::{ use std::{
convert::TryFrom, convert::TryFrom,
panic::{catch_unwind, AssertUnwindSafe}, panic::{catch_unwind, AssertUnwindSafe},
@ -48,6 +49,8 @@ use swc_ecmascript::transforms::pass::noop;
pub enum Input { pub enum Input {
/// Raw source code. /// Raw source code.
Source { src: String }, Source { src: String },
/// Get source code from filename in options
FromFilename,
} }
pub struct TransformTask { pub struct TransformTask {
@ -63,30 +66,43 @@ impl Task for TransformTask {
fn compute(&mut self) -> napi::Result<Self::Output> { fn compute(&mut self) -> napi::Result<Self::Output> {
let res = catch_unwind(AssertUnwindSafe(|| { let res = catch_unwind(AssertUnwindSafe(|| {
try_with_handler(self.c.cm.clone(), true, |handler| { try_with_handler(self.c.cm.clone(), true, |handler| {
self.c.run(|| match &self.input { self.c.run(|| {
Input::Source { src } => { let options: TransformOptions = deserialize_json(&self.options)?;
let options: TransformOptions = deserialize_json(&self.options)?; let fm = match &self.input {
Input::Source { src } => {
let filename = if options.swc.filename.is_empty() {
FileName::Anon
} else {
FileName::Real(options.swc.filename.clone().into())
};
let filename = if options.swc.filename.is_empty() { self.c.cm.new_source_file(filename, src.to_string())
FileName::Anon }
} else { Input::FromFilename => {
FileName::Real(options.swc.filename.clone().into()) let filename = &options.swc.filename;
}; if filename.is_empty() {
bail!("no filename is provided via options");
}
let fm = self.c.cm.new_source_file(filename, src.to_string()); self.c.cm.new_source_file(
FileName::Real(filename.into()),
read_to_string(filename).with_context(|| {
format!("Failed to read source code from {}", filename)
})?,
)
}
};
let options = options.patch(&fm);
let options = options.patch(&fm); let before_pass = custom_before_pass(fm.clone(), &options);
self.c.process_js_with_custom_pass(
let before_pass = custom_before_pass(fm.clone(), &options); fm.clone(),
self.c.process_js_with_custom_pass( None,
fm.clone(), &handler,
None, &options.swc,
&handler, |_| before_pass,
&options.swc, |_| noop(),
|_| before_pass, )
|_| noop(),
)
}
}) })
}) })
})) }))
@ -115,22 +131,23 @@ impl Task for TransformTask {
/// returns `compiler, (src / path), options, plugin, callback` /// returns `compiler, (src / path), options, plugin, callback`
pub fn schedule_transform<F>(cx: CallContext, op: F) -> napi::Result<JsObject> pub fn schedule_transform<F>(cx: CallContext, op: F) -> napi::Result<JsObject>
where where
F: FnOnce(&Arc<Compiler>, String, bool, String) -> TransformTask, F: FnOnce(&Arc<Compiler>, Input, bool, String) -> TransformTask,
{ {
let c = get_compiler(&cx); let c = get_compiler(&cx);
let unknown_src = cx.get::<JsUnknown>(0)?; let unknown_src = cx.get::<JsUnknown>(0)?;
let src = match unknown_src.get_type()? { let src = match unknown_src.get_type()? {
napi::ValueType::String => napi::Result::Ok( napi::ValueType::String => napi::Result::Ok(Input::Source {
JsString::try_from(unknown_src)? src: JsString::try_from(unknown_src)?
.into_utf8()? .into_utf8()?
.as_str()? .as_str()?
.to_owned(), .to_owned(),
), }),
napi::ValueType::Object => napi::Result::Ok( napi::ValueType::Object => napi::Result::Ok(Input::Source {
String::from_utf8_lossy(JsBuffer::try_from(unknown_src)?.into_value()?.as_ref()) src: String::from_utf8_lossy(JsBuffer::try_from(unknown_src)?.into_value()?.as_ref())
.to_string(), .to_string(),
), }),
napi::ValueType::Undefined => napi::Result::Ok(Input::FromFilename),
_ => Err(napi::Error::new( _ => Err(napi::Error::new(
Status::GenericFailure, Status::GenericFailure,
"first argument must be a String or Buffer".to_string(), "first argument must be a String or Buffer".to_string(),
@ -175,14 +192,10 @@ where
#[js_function(4)] #[js_function(4)]
pub fn transform(cx: CallContext) -> napi::Result<JsObject> { pub fn transform(cx: CallContext) -> napi::Result<JsObject> {
schedule_transform(cx, |c, src, _, options| { schedule_transform(cx, |c, input, _, options| TransformTask {
let input = Input::Source { src }; c: c.clone(),
input,
TransformTask { options,
c: c.clone(),
input,
options,
}
}) })
} }

View file

@ -62,7 +62,10 @@ function loadNative() {
if (bindings) { if (bindings) {
return { return {
transform(src, options) { transform(src, options) {
const isModule = typeof src !== 'string' && !Buffer.isBuffer(src) const isModule =
typeof src !== undefined &&
typeof src !== 'string' &&
!Buffer.isBuffer(src)
options = options || {} options = options || {}
if (options?.jsc?.parser) { if (options?.jsc?.parser) {
@ -77,7 +80,16 @@ function loadNative() {
}, },
transformSync(src, options) { transformSync(src, options) {
const isModule = typeof src !== 'string' && !Buffer.isBuffer(src) if (typeof src === undefined) {
throw new Error(
"transformSync doesn't implement reading the file from filesystem"
)
} else if (Buffer.isBuffer(src)) {
throw new Error(
"transformSync doesn't implement taking the source code as Buffer"
)
}
const isModule = typeof src !== 'string'
options = options || {} options = options || {}
if (options?.jsc?.parser) { if (options?.jsc?.parser) {

View file

@ -28,6 +28,7 @@ DEALINGS IN THE SOFTWARE.
import { transform } from '../../swc' import { transform } from '../../swc'
import { getLoaderSWCOptions } from '../../swc/options' import { getLoaderSWCOptions } from '../../swc/options'
import { isAbsolute } from 'path'
async function loaderTransform(parentTrace, source, inputSourceMap) { async function loaderTransform(parentTrace, source, inputSourceMap) {
// Make the loader async // Make the loader async
@ -92,6 +93,28 @@ async function loaderTransform(parentTrace, source, inputSourceMap) {
) )
} }
export function pitch() {
if (
this.loaders.length - 1 === this.loaderIndex &&
isAbsolute(this.resourcePath)
) {
const loaderSpan = this.currentTraceSpan.traceChild('next-swc-loader')
const callback = this.async()
loaderSpan
.traceAsyncFn(() => loaderTransform.call(this, loaderSpan))
.then(
([transformedSource, outputSourceMap]) => {
this.addDependency(this.resourcePath)
callback(null, transformedSource, outputSourceMap)
},
(err) => {
this.addDependency(this.resourcePath)
callback(err)
}
)
}
}
export default function swcLoader(inputSource, inputSourceMap) { export default function swcLoader(inputSource, inputSourceMap) {
const loaderSpan = this.currentTraceSpan.traceChild('next-swc-loader') const loaderSpan = this.currentTraceSpan.traceChild('next-swc-loader')
const callback = this.async() const callback = this.async()