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 {
|
Config {
|
||||||
prefix: get_str(config, "prefix"),
|
prefix: get_str(config, "prefix"),
|
||||||
encode: create_encode_function(get_obj(codec, "encode")),
|
encode: create_encode_function(get_obj(codec, "encode")),
|
||||||
|
|
||||||
wrapfn: get_str(config, "wrapfn"),
|
wrapfn: get_str(config, "wrapfn"),
|
||||||
importfn: get_str(config, "importfn"),
|
importfn: get_str(config, "importfn"),
|
||||||
rewritefn: get_str(config, "rewritefn"),
|
rewritefn: get_str(config, "rewritefn"),
|
||||||
metafn: get_str(config, "metafn"),
|
metafn: get_str(config, "metafn"),
|
||||||
setrealmfn: get_str(config, "setrealmfn"),
|
setrealmfn: get_str(config, "setrealmfn"),
|
||||||
|
pushsourcemapfn: get_str(config, "pushsourcemapfn"),
|
||||||
|
|
||||||
|
do_sourcemaps: get_bool(flags, "sourcemaps"),
|
||||||
capture_errors: get_bool(flags, "captureErrors"),
|
capture_errors: get_bool(flags, "captureErrors"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -120,7 +120,9 @@ fn dorewrite(source_text: &str) -> String {
|
||||||
rewritefn: "$rewrite".to_string(),
|
rewritefn: "$rewrite".to_string(),
|
||||||
metafn: "$meta".to_string(),
|
metafn: "$meta".to_string(),
|
||||||
setrealmfn: "$setrealm".to_string(),
|
setrealmfn: "$setrealm".to_string(),
|
||||||
|
pushsourcemapfn: "$pushsourcemap".to_string(),
|
||||||
capture_errors: true,
|
capture_errors: true,
|
||||||
|
do_sourcemaps: true,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.as_slice(),
|
.as_slice(),
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
use core::str;
|
||||||
|
|
||||||
use oxc_allocator::Allocator;
|
use oxc_allocator::Allocator;
|
||||||
use oxc_ast::{
|
use oxc_ast::{
|
||||||
ast::{
|
ast::{
|
||||||
|
@ -37,14 +39,17 @@ struct Rewriter {
|
||||||
|
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
pub prefix: String,
|
pub prefix: String,
|
||||||
|
|
||||||
pub wrapfn: String,
|
pub wrapfn: String,
|
||||||
pub importfn: String,
|
pub importfn: String,
|
||||||
pub rewritefn: String,
|
pub rewritefn: String,
|
||||||
pub setrealmfn: String,
|
pub setrealmfn: String,
|
||||||
pub metafn: String,
|
pub metafn: String,
|
||||||
pub encode: EncodeFn,
|
pub pushsourcemapfn: String,
|
||||||
|
|
||||||
|
pub encode: EncodeFn,
|
||||||
pub capture_errors: bool,
|
pub capture_errors: bool,
|
||||||
|
pub do_sourcemaps: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Rewriter {
|
impl Rewriter {
|
||||||
|
@ -85,22 +90,22 @@ impl Rewriter {
|
||||||
|
|
||||||
impl<'a> Visit<'a> for Rewriter {
|
impl<'a> Visit<'a> for Rewriter {
|
||||||
fn visit_identifier_reference(&mut self, it: &IdentifierReference<'a>) {
|
fn visit_identifier_reference(&mut self, it: &IdentifierReference<'a>) {
|
||||||
if self.config.capture_errors {
|
// 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 {
|
self.jschanges.push(JsChange::GenericChange {
|
||||||
span: it.span,
|
span: it.span,
|
||||||
text: format!(
|
text: format!("{}({})", self.config.wrapfn, it.name),
|
||||||
"{}({}, 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
|
// 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 size_estimate = (original_len as i32 + difference) as usize;
|
||||||
let mut buffer: Vec<u8> = Vec::with_capacity(size_estimate);
|
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;
|
let mut offset = 0;
|
||||||
for change in ast_pass.jschanges {
|
for change in ast_pass.jschanges {
|
||||||
match &change {
|
match &change {
|
||||||
|
@ -462,6 +473,19 @@ pub fn rewrite(js: &str, url: Url, config: Config) -> Vec<u8> {
|
||||||
let start = span.start as usize;
|
let start = span.start as usize;
|
||||||
let end = span.end 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(unsafe { js.get_unchecked(offset..start) }.as_bytes());
|
||||||
|
|
||||||
buffer.extend_from_slice(text.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());
|
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
|
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 {
|
fn fmt_op(op: AssignmentOperator) -> &'static str {
|
||||||
match op {
|
match op {
|
||||||
AssignmentOperator::Assign => "=",
|
AssignmentOperator::Assign => "=",
|
||||||
|
|
|
@ -4,7 +4,8 @@ import { rewriteJs } from "../../shared";
|
||||||
function rewriteFunction(ctx: ProxyCtx, client: ScramjetClient) {
|
function rewriteFunction(ctx: ProxyCtx, client: ScramjetClient) {
|
||||||
const stringifiedFunction = ctx.call().toString();
|
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) {
|
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",
|
rewritefn: "$scramjet$rewrite",
|
||||||
metafn: "$scramjet$meta",
|
metafn: "$scramjet$meta",
|
||||||
setrealmfn: "$scramjet$setrealm",
|
setrealmfn: "$scramjet$setrealm",
|
||||||
|
pushsourcemapfn: "$scramjet$pushsourcemap",
|
||||||
wasm: "/scramjet.wasm.js",
|
wasm: "/scramjet.wasm.js",
|
||||||
shared: "/scramjet.shared.js",
|
shared: "/scramjet.shared.js",
|
||||||
worker: "/scramjet.worker.js",
|
worker: "/scramjet.worker.js",
|
||||||
|
@ -29,6 +30,7 @@ export class ScramjetController {
|
||||||
serviceworkers: false,
|
serviceworkers: false,
|
||||||
naiiveRewriter: false,
|
naiiveRewriter: false,
|
||||||
captureErrors: 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;
|
serviceworkers: boolean;
|
||||||
naiiveRewriter: boolean;
|
naiiveRewriter: boolean;
|
||||||
captureErrors: boolean;
|
captureErrors: boolean;
|
||||||
|
sourcemaps: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
interface ScramjetConfig {
|
interface ScramjetConfig {
|
||||||
|
@ -34,6 +35,7 @@ interface ScramjetConfig {
|
||||||
rewritefn: string;
|
rewritefn: string;
|
||||||
metafn: string;
|
metafn: string;
|
||||||
setrealmfn: string;
|
setrealmfn: string;
|
||||||
|
pushsourcemapfn: string;
|
||||||
wasm: string;
|
wasm: string;
|
||||||
shared: string;
|
shared: string;
|
||||||
worker: string;
|
worker: string;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue