mirror of
https://github.com/MercuryWorkshop/scramjet.git
synced 2025-05-13 06:20:02 -04:00
source maps
This commit is contained in:
parent
36ebeb3e5c
commit
fac329b1dc
7 changed files with 144 additions and 14 deletions
|
@ -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"),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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 => "=",
|
||||
|
|
|
@ -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) {
|
||||
|
|
67
src/client/shared/sourcemaps.ts
Normal file
67
src/client/shared/sourcemaps.ts
Normal 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));
|
||||
},
|
||||
});
|
||||
}
|
|
@ -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
2
src/types.d.ts
vendored
|
@ -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;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue