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},
};
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 std::fs::read_to_string;
use std::{
convert::TryFrom,
panic::{catch_unwind, AssertUnwindSafe},
@ -48,6 +49,8 @@ use swc_ecmascript::transforms::pass::noop;
pub enum Input {
/// Raw source code.
Source { src: String },
/// Get source code from filename in options
FromFilename,
}
pub struct TransformTask {
@ -63,18 +66,32 @@ impl Task for TransformTask {
fn compute(&mut self) -> napi::Result<Self::Output> {
let res = catch_unwind(AssertUnwindSafe(|| {
try_with_handler(self.c.cm.clone(), true, |handler| {
self.c.run(|| match &self.input {
Input::Source { src } => {
self.c.run(|| {
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 fm = self.c.cm.new_source_file(filename, src.to_string());
self.c.cm.new_source_file(filename, src.to_string())
}
Input::FromFilename => {
let filename = &options.swc.filename;
if filename.is_empty() {
bail!("no filename is provided via options");
}
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 before_pass = custom_before_pass(fm.clone(), &options);
@ -86,7 +103,6 @@ impl Task for TransformTask {
|_| before_pass,
|_| noop(),
)
}
})
})
}))
@ -115,22 +131,23 @@ impl Task for TransformTask {
/// returns `compiler, (src / path), options, plugin, callback`
pub fn schedule_transform<F>(cx: CallContext, op: F) -> napi::Result<JsObject>
where
F: FnOnce(&Arc<Compiler>, String, bool, String) -> TransformTask,
F: FnOnce(&Arc<Compiler>, Input, bool, String) -> TransformTask,
{
let c = get_compiler(&cx);
let unknown_src = cx.get::<JsUnknown>(0)?;
let src = match unknown_src.get_type()? {
napi::ValueType::String => napi::Result::Ok(
JsString::try_from(unknown_src)?
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(
String::from_utf8_lossy(JsBuffer::try_from(unknown_src)?.into_value()?.as_ref())
}),
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(),
@ -175,14 +192,10 @@ where
#[js_function(4)]
pub fn transform(cx: CallContext) -> napi::Result<JsObject> {
schedule_transform(cx, |c, src, _, options| {
let input = Input::Source { src };
TransformTask {
schedule_transform(cx, |c, input, _, options| TransformTask {
c: c.clone(),
input,
options,
}
})
}

View file

@ -62,7 +62,10 @@ function loadNative() {
if (bindings) {
return {
transform(src, options) {
const isModule = typeof src !== 'string' && !Buffer.isBuffer(src)
const isModule =
typeof src !== undefined &&
typeof src !== 'string' &&
!Buffer.isBuffer(src)
options = options || {}
if (options?.jsc?.parser) {
@ -77,7 +80,16 @@ function loadNative() {
},
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 || {}
if (options?.jsc?.parser) {

View file

@ -28,6 +28,7 @@ DEALINGS IN THE SOFTWARE.
import { transform } from '../../swc'
import { getLoaderSWCOptions } from '../../swc/options'
import { isAbsolute } from 'path'
async function loaderTransform(parentTrace, source, inputSourceMap) {
// 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) {
const loaderSpan = this.currentTraceSpan.traceChild('next-swc-loader')
const callback = this.async()