massive speedup in sourcemaps.ts

This commit is contained in:
velzie 2024-09-02 14:20:45 -04:00
parent 7fa94bd9d3
commit 5d71685997
No known key found for this signature in database
GPG key ID: 048413F95F0DDE1F
4 changed files with 78 additions and 36 deletions

4
rewriter/Cargo.lock generated
View file

@ -346,8 +346,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"js-sys",
"libc", "libc",
"wasi", "wasi",
"wasm-bindgen",
] ]
[[package]] [[package]]
@ -980,6 +982,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"boa_engine", "boa_engine",
"console_error_panic_hook", "console_error_panic_hook",
"getrandom",
"js-sys", "js-sys",
"obfstr", "obfstr",
"oxc_allocator", "oxc_allocator",
@ -987,6 +990,7 @@ dependencies = [
"oxc_parser", "oxc_parser",
"oxc_span", "oxc_span",
"oxc_syntax", "oxc_syntax",
"rand",
"serde", "serde",
"serde-wasm-bindgen", "serde-wasm-bindgen",
"url", "url",

View file

@ -24,6 +24,7 @@ panic = "abort"
[dependencies] [dependencies]
console_error_panic_hook = "0.1.7" console_error_panic_hook = "0.1.7"
getrandom = { version = "0.2.15", features = ["js"] }
js-sys = "0.3.69" js-sys = "0.3.69"
obfstr = "0.4.3" obfstr = "0.4.3"
oxc_allocator = "0.20.0" oxc_allocator = "0.20.0"
@ -31,6 +32,7 @@ oxc_ast = { version = "0.20.0", features = ["serialize"]}
oxc_parser = { version = "0.20.0" } oxc_parser = { version = "0.20.0" }
oxc_span = "0.20.0" oxc_span = "0.20.0"
oxc_syntax = "0.20.0" oxc_syntax = "0.20.0"
rand = "0.8.5"
serde = "1.0.204" serde = "1.0.204"
serde-wasm-bindgen = "0.6.5" serde-wasm-bindgen = "0.6.5"
url = "2.5.2" url = "2.5.2"

View file

@ -1,4 +1,5 @@
use core::str; use core::str;
use std::str::from_utf8;
use oxc_allocator::Allocator; use oxc_allocator::Allocator;
use oxc_ast::{ use oxc_ast::{
@ -19,8 +20,8 @@ enum JsChange {
span: Span, span: Span,
text: String, text: String,
}, },
DebugInject { SourceTag {
span: Span, tagstart: u32,
}, },
Assignment { Assignment {
name: String, name: String,
@ -254,6 +255,16 @@ impl<'a> Visit<'a> for Rewriter {
walk::walk_object_expression(self, it); walk::walk_object_expression(self, it);
} }
fn visit_function_body(&mut self, it: &oxc_ast::ast::FunctionBody<'a>) {
// tag function for use in sourcemaps
if self.config.do_sourcemaps {
self.jschanges.push(JsChange::SourceTag {
tagstart: it.span.start,
});
}
walk::walk_function_body(self, it);
}
fn visit_return_statement(&mut self, it: &oxc_ast::ast::ReturnStatement<'a>) { fn visit_return_statement(&mut self, it: &oxc_ast::ast::ReturnStatement<'a>) {
// if let Some(arg) = &it.argument { // if let Some(arg) = &it.argument {
// self.jschanges.push(JsChange::GenericChange { // self.jschanges.push(JsChange::GenericChange {
@ -391,6 +402,19 @@ const UNSAFE_GLOBALS: [&str; 9] = [
"eval", "eval",
]; ];
fn random_string() -> String {
use rand::{distributions::Alphanumeric, thread_rng, Rng};
from_utf8(
&thread_rng()
.sample_iter(&Alphanumeric)
.take(10)
.collect::<Vec<u8>>(),
)
.unwrap()
.to_string()
}
pub fn rewrite(js: &str, url: Url, config: Config) -> Vec<u8> { pub fn rewrite(js: &str, url: Url, config: Config) -> Vec<u8> {
let allocator = Allocator::default(); let allocator = Allocator::default();
let source_type = SourceType::default(); let source_type = SourceType::default();
@ -406,6 +430,8 @@ pub fn rewrite(js: &str, url: Url, config: Config) -> Vec<u8> {
// dbg!(&program); // dbg!(&program);
let sourcetag = random_string();
let mut ast_pass = Rewriter { let mut ast_pass = Rewriter {
jschanges: Vec::new(), jschanges: Vec::new(),
base: url, base: url,
@ -424,7 +450,7 @@ pub fn rewrite(js: &str, url: Url, config: Config) -> Vec<u8> {
rhsspan: _, rhsspan: _,
op: _, op: _,
} => entirespan.start, } => entirespan.start,
JsChange::DebugInject { span } => span.start, JsChange::SourceTag { tagstart } => *tagstart,
}; };
let b = match b { let b = match b {
JsChange::GenericChange { span, text: _ } => span.start, JsChange::GenericChange { span, text: _ } => span.start,
@ -434,7 +460,7 @@ pub fn rewrite(js: &str, url: Url, config: Config) -> Vec<u8> {
rhsspan: _, rhsspan: _,
op: _, op: _,
} => entirespan.start, } => entirespan.start,
JsChange::DebugInject { span } => span.start, JsChange::SourceTag { tagstart } => *tagstart,
}; };
a.cmp(&b) a.cmp(&b)
}); });
@ -514,11 +540,14 @@ pub fn rewrite(js: &str, url: Url, config: Config) -> Vec<u8> {
offset = entirespan.end as usize; offset = entirespan.end as usize;
} }
JsChange::DebugInject { span } => { JsChange::SourceTag { tagstart } => {
let start = span.start as usize; let start = *tagstart as usize;
buffer.extend_from_slice(unsafe { js.get_unchecked(offset..start) }.as_bytes()); buffer.extend_from_slice(unsafe { js.get_unchecked(offset..start) }.as_bytes());
offset = span.end as usize; let inject = format!("/*scramtag {} {}*/", start, sourcetag);
buffer.extend_from_slice(inject.as_bytes());
offset = start;
} }
} }
} }
@ -527,8 +556,7 @@ pub fn rewrite(js: &str, url: Url, config: Config) -> Vec<u8> {
if ast_pass.config.do_sourcemaps { if ast_pass.config.do_sourcemaps {
sourcemap.extend_from_slice(b"],"); sourcemap.extend_from_slice(b"],");
sourcemap.extend_from_slice(b"\""); sourcemap.extend_from_slice(b"\"");
sourcemap sourcemap.extend_from_slice(&sourcetag.as_bytes());
.extend_from_slice(json_escape_string(str::from_utf8(&buffer).unwrap()).as_bytes());
sourcemap.extend_from_slice(b"\");\n"); sourcemap.extend_from_slice(b"\");\n");
sourcemap.extend_from_slice(&buffer); sourcemap.extend_from_slice(&buffer);

View file

@ -1,17 +1,17 @@
import { ScramjetClient } from "../client"; import { ScramjetClient } from "../client";
const sourcemaps: { type Mapping = [string, number, number];
source: string;
map: [string, number, number][]; const sourcemaps: Record<string, Mapping[]> = {};
}[] = [];
export const enabled = () => self.$scramjet.config.flags.sourcemaps; export const enabled = () => self.$scramjet.config.flags.sourcemaps;
let t = 0;
export default function (client: ScramjetClient, self: Self) { export default function (client: ScramjetClient, self: Self) {
// every script will push a sourcemap // every script will push a sourcemap
Object.defineProperty(self, "$scramjet$pushsourcemap", { Object.defineProperty(self, "$scramjet$pushsourcemap", {
value: (map, source) => { value: (maps: Mapping[], tag: string) => {
sourcemaps.push({ map, source }); sourcemaps[tag] = maps;
}, },
enumerable: false, enumerable: false,
writable: false, writable: false,
@ -22,44 +22,52 @@ export default function (client: ScramjetClient, self: Self) {
// this can lead to double rewrites which is bad // this can lead to double rewrites which is bad
client.Proxy("Function.prototype.toString", { client.Proxy("Function.prototype.toString", {
apply(ctx) { apply(ctx) {
const stringified = ctx.fn.call(ctx.this); let stringified: string = ctx.fn.call(ctx.this);
let newString = ""; let newString = "";
// find the sourcemap, brute force, just check every file until the body of the function shows up // every function rewritten will have a scramtag comment
// it doesnt matter if there's multiple with the same content because it will be the same function // it will look like this:
const sourcemap = sourcemaps.find(({ source }) => // function name() /*scramtag [index] [tag] */ { ... }
source.includes(stringified) const scramtag_ident = "/*scramtag ";
const scramtagstart = stringified.indexOf(scramtag_ident);
if (scramtagstart === -1) return ctx.return(stringified); // it's either a native function or something stolen from scramjet itself
// [index] holds the index of the first character in the scramtag (/)
const abstagindex = parseInt(
stringified
.substring(scramtagstart + scramtag_ident.length)
.split(" ")[0]
); );
// i don't know what cases this would happen under, but it does // subtracting that from the index of the scramtag gives us the starting index of the function relative to the entire file
if (!sourcemap) return ctx.return(stringified); let absindex = abstagindex - scramtagstart;
const { source, map } = sourcemap;
// first we need to find the character # where the function starts relative to the *transformed* source const scramtagend = stringified.indexOf("*/", scramtagstart);
let starting = source.indexOf(stringified); const tag = stringified
.substring(scramtagstart + scramtag_ident.length, scramtagend)
.split(" ")[1];
const beforeFunctionRewrites = map.filter( // delete the scramtag now that we're done with it
([str, start, end]) => start < starting stringified =
); stringified.slice(0, scramtagstart) +
stringified.slice(scramtagend + 2);
// map the offsets of the original source to the transformed source const maps = sourcemaps[tag];
for (const [str, start, end] of beforeFunctionRewrites) {
starting -= end - start - str.length;
}
const relevantRewrites = map.filter( const relevantRewrites = maps.filter(
([str, start, end]) => ([str, start, end]) =>
start >= starting && end <= starting + stringified.length start >= absindex && end <= absindex + stringified.length
); );
let i = 0; let i = 0;
let offset = 0; let offset = 0;
for (const [str, start, end] of relevantRewrites) { for (const [str, start, end] of relevantRewrites) {
// ooh i should really document this before i forget how it works // ooh i should really document this before i forget how it works
newString += stringified.slice(i, start - starting + offset); newString += stringified.slice(i, start - absindex + offset);
newString += str; newString += str;
offset += end - start - str.length; offset += end - start - str.length;
i = start - starting + offset + str.length; i = start - absindex + offset + str.length;
} }
return ctx.return(newString + stringified.slice(i)); return ctx.return(newString + stringified.slice(i));