source maps

This commit is contained in:
velzie 2024-09-01 19:37:33 -04:00
parent 36ebeb3e5c
commit fac329b1dc
No known key found for this signature in database
GPG key ID: 048413F95F0DDE1F
7 changed files with 144 additions and 14 deletions

View file

@ -53,12 +53,15 @@ fn get_config(scramjet: &Object) -> Config {
Config {
prefix: get_str(config, "prefix"),
encode: create_encode_function(get_obj(codec, "encode")),
wrapfn: get_str(config, "wrapfn"),
importfn: get_str(config, "importfn"),
rewritefn: get_str(config, "rewritefn"),
metafn: get_str(config, "metafn"),
setrealmfn: get_str(config, "setrealmfn"),
pushsourcemapfn: get_str(config, "pushsourcemapfn"),
do_sourcemaps: get_bool(flags, "sourcemaps"),
capture_errors: get_bool(flags, "captureErrors"),
}
}

View file

@ -120,7 +120,9 @@ fn dorewrite(source_text: &str) -> String {
rewritefn: "$rewrite".to_string(),
metafn: "$meta".to_string(),
setrealmfn: "$setrealm".to_string(),
pushsourcemapfn: "$pushsourcemap".to_string(),
capture_errors: true,
do_sourcemaps: true,
},
)
.as_slice(),

View file

@ -1,3 +1,5 @@
use core::str;
use oxc_allocator::Allocator;
use oxc_ast::{
ast::{
@ -37,14 +39,17 @@ struct Rewriter {
pub struct Config {
pub prefix: String,
pub wrapfn: String,
pub importfn: String,
pub rewritefn: String,
pub setrealmfn: String,
pub metafn: String,
pub encode: EncodeFn,
pub pushsourcemapfn: String,
pub encode: EncodeFn,
pub capture_errors: bool,
pub do_sourcemaps: bool,
}
impl Rewriter {
@ -85,22 +90,22 @@ impl Rewriter {
impl<'a> Visit<'a> for Rewriter {
fn visit_identifier_reference(&mut self, it: &IdentifierReference<'a>) {
if self.config.capture_errors {
self.jschanges.push(JsChange::GenericChange {
span: it.span,
text: format!(
"{}({}, typeof arguments != 'undefined' && arguments)",
self.config.wrapfn, it.name
),
});
} else {
// if self.config.capture_errors {
// self.jschanges.push(JsChange::GenericChange {
// span: it.span,
// text: format!(
// "{}({}, typeof arguments != 'undefined' && arguments)",
// self.config.wrapfn, it.name
// ),
// });
// } else {
if UNSAFE_GLOBALS.contains(&it.name.to_string().as_str()) {
self.jschanges.push(JsChange::GenericChange {
span: it.span,
text: format!("{}({})", self.config.wrapfn, it.name),
});
}
}
// }
}
// we need to rewrite `new Something` to `new (wrapfn(Something))` instead of `new wrapfn(Something)`, that's why there's weird extra code here
@ -455,6 +460,12 @@ pub fn rewrite(js: &str, url: Url, config: Config) -> Vec<u8> {
let size_estimate = (original_len as i32 + difference) as usize;
let mut buffer: Vec<u8> = Vec::with_capacity(size_estimate);
let mut sourcemap: Vec<u8> = Vec::new();
if ast_pass.config.do_sourcemaps {
sourcemap.reserve(size_estimate * 2);
sourcemap.extend_from_slice(&format!("{}([", ast_pass.config.pushsourcemapfn).as_bytes());
}
let mut offset = 0;
for change in ast_pass.jschanges {
match &change {
@ -462,6 +473,19 @@ pub fn rewrite(js: &str, url: Url, config: Config) -> Vec<u8> {
let start = span.start as usize;
let end = span.end as usize;
if ast_pass.config.do_sourcemaps {
let spliced = &js[start..end];
sourcemap.extend_from_slice(
format!(
"[\"{}\",{},{}],",
json_escape_string(spliced),
start,
start + text.len()
)
.as_bytes(),
);
}
buffer.extend_from_slice(unsafe { js.get_unchecked(offset..start) }.as_bytes());
buffer.extend_from_slice(text.as_bytes());
@ -500,9 +524,38 @@ pub fn rewrite(js: &str, url: Url, config: Config) -> Vec<u8> {
}
buffer.extend_from_slice(js[offset..].as_bytes());
if ast_pass.config.do_sourcemaps {
sourcemap.extend_from_slice(b"],");
sourcemap.extend_from_slice(b"\"");
sourcemap
.extend_from_slice(json_escape_string(str::from_utf8(&buffer).unwrap()).as_bytes());
sourcemap.extend_from_slice(b"\");\n");
sourcemap.extend_from_slice(&buffer);
return sourcemap;
}
buffer
}
fn json_escape_string(s: &str) -> String {
let mut out = String::with_capacity(s.len());
for c in s.chars() {
match c {
'"' => out.push_str("\\\""),
'\\' => out.push_str("\\\\"),
'\x08' => out.push_str("\\b"),
'\x0C' => out.push_str("\\f"),
'\n' => out.push_str("\\n"),
'\r' => out.push_str("\\r"),
'\t' => out.push_str("\\t"),
_ => out.push(c),
}
}
out
}
fn fmt_op(op: AssignmentOperator) -> &'static str {
match op {
AssignmentOperator::Assign => "=",

View file

@ -4,7 +4,8 @@ import { rewriteJs } from "../../shared";
function rewriteFunction(ctx: ProxyCtx, client: ScramjetClient) {
const stringifiedFunction = ctx.call().toString();
ctx.return(ctx.fn(`return ${rewriteJs(stringifiedFunction, client.meta)}`)());
let content = rewriteJs(`return ${stringifiedFunction}`, client.meta);
ctx.return(ctx.fn(content)());
}
export default function (client: ScramjetClient, self: Self) {

View file

@ -0,0 +1,67 @@
import { ScramjetClient } from "../client";
let sourcemaps: {
source: string;
map: [string, number, number][];
}[] = [];
export const enabled = self.$scramjet.config.flags.sourcemaps;
export default function (client: ScramjetClient, self: Self) {
// every script will push a sourcemap
Object.defineProperty(self, "$scramjet$pushsourcemap", {
value: (map, source) => {
sourcemaps.push({ map, source });
},
enumerable: false,
writable: false,
configurable: false,
});
// when we rewrite javascript it will make function.toString leak internals
// this can lead to double rewrites which is bad
client.Proxy("Function.prototype.toString", {
apply(ctx) {
const stringified = ctx.fn.call(ctx.this);
let newString = "";
// find the sourcemap, brute force, just check every file until the body of the function shows up
// it doesnt matter if there's multiple with the same content because it will be the same function
const sourcemap = sourcemaps.find(({ source }) =>
source.includes(stringified)
);
// i don't know what cases this would happen under, but it does
if (!sourcemap) return ctx.return(stringified);
const { source, map } = sourcemap;
// first we need to find the character # where the function starts relative to the *transformed* source
let starting = source.indexOf(stringified);
const beforeFunctionRewrites = map.filter(
([str, start, end]) => start < starting
);
// map the offsets of the original source to the transformed source
for (const [str, start, end] of beforeFunctionRewrites) {
starting -= end - start - str.length;
}
const relevantRewrites = map.filter(
([str, start, end]) =>
start >= starting && end <= starting + stringified.length
);
let i = 0;
let offset = 0;
for (const [str, start, end] of relevantRewrites) {
// ooh i should really document this before i forget how it works
newString += stringified.slice(i, start - starting + offset);
newString += str;
offset += end - start - str.length;
i = start - starting + offset + str.length;
}
return ctx.return(newString + stringified.slice(i));
},
});
}

View file

@ -19,6 +19,7 @@ export class ScramjetController {
rewritefn: "$scramjet$rewrite",
metafn: "$scramjet$meta",
setrealmfn: "$scramjet$setrealm",
pushsourcemapfn: "$scramjet$pushsourcemap",
wasm: "/scramjet.wasm.js",
shared: "/scramjet.shared.js",
worker: "/scramjet.worker.js",
@ -29,6 +30,7 @@ export class ScramjetController {
serviceworkers: false,
naiiveRewriter: false,
captureErrors: false,
sourcemaps: true,
},
};

2
src/types.d.ts vendored
View file

@ -23,6 +23,7 @@ type ScramjetFlags = {
serviceworkers: boolean;
naiiveRewriter: boolean;
captureErrors: boolean;
sourcemaps: boolean;
};
interface ScramjetConfig {
@ -34,6 +35,7 @@ interface ScramjetConfig {
rewritefn: string;
metafn: string;
setrealmfn: string;
pushsourcemapfn: string;
wasm: string;
shared: string;
worker: string;