Signed-off-by: Sean Cross <sean@xobs.io>
This commit is contained in:
Sean Cross 2023-11-20 18:03:18 +08:00
commit 07dc3f87ed
9 changed files with 3156 additions and 0 deletions

38
.gitattributes vendored Normal file
View File

@ -0,0 +1,38 @@
# Various C code
*.h text eol=lf
*.c text eol=lf
*.s text eol=lf
*.S text eol=lf
# Rust code
*.rs text eol=lf
*.toml text eol=lf
*.lock text eol=lf
# Misc scripting files
Makefile text eol=lf
*.mk text eol=lf
*.sh text eol=lf
*.ps1 text eol=crlf
*.py text eol=lf
# Human-readable files
*.md eol=lf
README.* text eol=lf
LICENSE text eol=lf
*.txt text eol=lf
# Binary files
*.dfu binary
*.png binary
*.jpg binary
*.bin binary
*.elf binary
# Git configuration files
.gitignore text eol=lf
.gitattributes text eol=lf
# System description files
*.svd eol=lf

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target

2725
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

27
Cargo.toml Normal file
View File

@ -0,0 +1,27 @@
[package]
name = "frogr"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
anyhow = "1"
heatshrink = "0.2"
minify-html = "0.11.1"
swc = "0.269"
swc_bundler = "0.222.57"
swc_common = { version = "0.33", features = [
"ahash",
"sourcemap",
"parking_lot",
] }
swc_ecma_ast = "0.110.10"
swc_ecma_codegen = "0.146.30"
swc_ecma_loader = "0.45.10"
swc_ecma_minifier = "0.189.59"
swc_ecma_parser = "0.141.24"
swc_ecma_transforms_base = "0.134.40"
swc_ecma_visit = "0.96.10"

9
src/bin.rs Normal file
View File

@ -0,0 +1,9 @@
use std::io::Read;
pub fn process<T: AsRef<std::path::Path> + ?Sized>(
path: &T,
) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
let mut code = vec![];
std::fs::File::open(path)?.read_to_end(&mut code)?;
Ok(code)
}

17
src/html.rs Normal file
View File

@ -0,0 +1,17 @@
use minify_html::{minify, Cfg};
use std::io::Read;
pub fn process<T: AsRef<std::path::Path> + ?Sized>(path: &T) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
let mut code = vec![];
std::fs::File::open(path)?.read_to_end(&mut code)?;
let cfg = Cfg {
keep_comments: false,
minify_js: true,
minify_css: true,
minify_css_level_1: true,
minify_css_level_2: true,
..Default::default()
};
Ok(minify(&code, &cfg))
}

199
src/js.rs Normal file
View File

@ -0,0 +1,199 @@
#![allow(clippy::needless_update)]
// /// Use memory allocator
// extern crate swc_malloc;
use std::{collections::HashMap, path::Path};
use anyhow::Error;
use swc_bundler::{Bundle, Bundler, Load, ModuleData, ModuleRecord};
use swc_common::{sync::Lrc, FileName, Mark, SourceMap, Span, GLOBALS};
use swc_ecma_ast::*;
use swc_ecma_codegen::{
text_writer::{omit_trailing_semi, JsWriter, WriteJs},
Emitter,
};
use swc_ecma_loader::{
resolvers::{lru::CachingResolver, node::NodeModulesResolver},
TargetEnv,
};
use swc_ecma_minifier::option::{
CompressOptions, ExtraOptions, MangleOptions, MinifyOptions, TopLevelOptions,
};
use swc_ecma_parser::{parse_file_as_module, Syntax};
use swc_ecma_transforms_base::fixer::fixer;
use swc_ecma_visit::VisitMutWith;
fn stringify(
cm: Lrc<SourceMap>,
modules: Vec<Bundle>,
) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
if modules.len() != 1 {
return Err("Expected exactly one bundle".into());
}
let code = {
let mut buf = vec![];
{
let wr = JsWriter::new(cm.clone(), "\n", &mut buf, None);
let mut emitter = Emitter {
cfg: swc_ecma_codegen::Config::default().with_minify(true),
cm: cm.clone(),
comments: None,
wr: Box::new(omit_trailing_semi(wr)) as Box<dyn WriteJs>,
};
emitter.emit_module(&modules[0].module).unwrap();
}
buf
};
// println!("Created output.js ({}kb)", code.len() / 1024);
// fs::write("output.js", &code).unwrap();
// }
Ok(code)
}
pub fn process<T: AsRef<Path> + ?Sized>(entry: &T) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
let mut entries = HashMap::new();
entries.insert(
"main".to_string(),
FileName::Real(entry.as_ref().to_owned()),
);
let inline = true;
let minify = true;
let cm = std::sync::Arc::<SourceMap>::default();
let globals = Box::leak(Box::default());
let mut bundler = Bundler::new(
globals,
cm.clone(),
Loader { cm: cm.clone() },
CachingResolver::new(
4096,
NodeModulesResolver::new(TargetEnv::Node, Default::default(), true),
),
swc_bundler::Config {
require: false,
disable_inliner: !inline,
external_modules: Default::default(),
disable_fixer: minify,
disable_hygiene: minify,
disable_dce: false,
module: Default::default(),
},
Box::new(Hook),
);
let mut modules = bundler.bundle(entries)?;
modules = modules
.into_iter()
.map(|mut b| {
GLOBALS.set(globals, || {
b.module = swc_ecma_minifier::optimize(
b.module.into(),
cm.clone(),
None,
None,
&MinifyOptions {
compress: Some(CompressOptions {
top_level: Some(TopLevelOptions { functions: true }),
..Default::default()
}),
mangle: Some(MangleOptions {
top_level: Some(true),
..Default::default()
}),
..Default::default()
},
&ExtraOptions {
unresolved_mark: Mark::new(),
top_level_mark: Mark::new(),
},
)
.expect_module();
b.module.visit_mut_with(&mut fixer(None));
b
})
})
.collect();
stringify(cm, modules)
// Ok("".to_string())
}
struct Hook;
impl swc_bundler::Hook for Hook {
fn get_import_meta_props(
&self,
span: Span,
module_record: &ModuleRecord,
) -> Result<Vec<KeyValueProp>, Error> {
let file_name = module_record.file_name.to_string();
Ok(vec![
KeyValueProp {
key: PropName::Ident(Ident::new("url".into(), span)),
value: Box::new(Expr::Lit(Lit::Str(Str {
span,
raw: None,
value: file_name.into(),
}))),
},
KeyValueProp {
key: PropName::Ident(Ident::new("main".into(), span)),
value: Box::new(if module_record.is_entry {
Expr::Member(MemberExpr {
span,
obj: Box::new(Expr::MetaProp(MetaPropExpr {
span,
kind: MetaPropKind::ImportMeta,
})),
prop: MemberProp::Ident(Ident::new("main".into(), span)),
})
} else {
Expr::Lit(Lit::Bool(Bool { span, value: false }))
}),
},
])
}
}
pub struct Loader {
pub cm: Lrc<SourceMap>,
}
impl Load for Loader {
fn load(&self, f: &FileName) -> Result<ModuleData, Error> {
let fm = match f {
FileName::Real(path) => self.cm.load_file(path)?,
_ => unreachable!(),
};
let module = parse_file_as_module(
&fm,
Syntax::Es(Default::default()),
EsVersion::Es2020,
None,
&mut vec![],
)
.expect("failed to parse");
// .unwrap_or_else(|err| {
// let handler =
// Handler::with_emitter(ColorConfig::Always, false, self.cm.clone());
// err.into_diagnostic(&handler).emit();
// panic!("failed to parse")
// });
Ok(ModuleData {
fm,
module,
helpers: Default::default(),
})
}
}

92
src/main.oxc.rs Normal file
View File

@ -0,0 +1,92 @@
use std::fs;
use std::path::Path;
use oxc::allocator::Allocator;
use oxc::codegen::{Codegen, CodegenOptions};
use oxc::minifier::{Minifier, MinifierOptions};
use oxc::parser::Parser;
use oxc::semantic::SemanticBuilder;
use oxc::span::SourceType;
use oxc::transformer::{
ReactJsxOptions, ReactJsxRuntime, TransformOptions, TransformTarget, Transformer,
};
fn transform_js(
source_text: &str,
source_type: SourceType,
) -> Result<String, Box<dyn std::error::Error>> {
let allocator = Allocator::default();
let ret = Parser::new(&allocator, source_text, source_type).parse();
if !ret.errors.is_empty() {
for error in ret.errors {
let error = error.with_source_code(source_text.to_owned());
println!("{error:?}");
}
return Err("Parse error".into());
}
let codegen_options = CodegenOptions;
let printed = Codegen::<false>::new(source_text.len(), codegen_options).build(&ret.program);
println!("Original:\n");
println!("{printed}\n");
let semantic = SemanticBuilder::new(source_text, source_type)
.with_trivias(ret.trivias)
.build(&ret.program)
.semantic;
let program = allocator.alloc(ret.program);
let transform_options = TransformOptions {
target: TransformTarget::ES5,
react_jsx: Some(ReactJsxOptions {
runtime: ReactJsxRuntime::Automatic,
..ReactJsxOptions::default()
}),
..TransformOptions::default()
};
Transformer::new(&allocator, source_type, semantic, transform_options).build(program);
Ok(Codegen::<false>::new(source_text.len(), codegen_options).build(program))
}
fn minify(source_text: &str, source_type: SourceType) -> String {
let allocator = Allocator::default();
let program = Parser::new(&allocator, source_text, source_type)
.parse()
.program;
let program = allocator.alloc(program);
let options = MinifierOptions {
mangle: true,
..MinifierOptions::default()
};
Minifier::new(options).build(&allocator, program);
Codegen::<true>::new(source_text.len(), CodegenOptions).build(program)
}
fn process<T: AsRef<Path>>(path: T) -> Result<(), Box<dyn std::error::Error>> {
let p = path.as_ref();
let source_text = fs::read_to_string(p).unwrap_or_else(|_| panic!("{} not found", p.display()));
let source_type = SourceType::from_path(p).unwrap();
println!("Source type: {:?}", source_type);
if source_type.is_javascript() {
let printed = transform_js(&source_text, source_type)?;
println!("{printed}");
// let printed = minify(&source_text, source_type);
// println!("{printed}");
}
// if twice {
// let printed = minify(&printed, source_type, mangle, whitespace);
// println!("{printed}");
// }
Ok(())
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
process("index.js")
}

48
src/main.rs Normal file
View File

@ -0,0 +1,48 @@
use std::path::Path;
use std::io::Write;
mod bin;
mod html;
mod js;
fn process_file<T: AsRef<Path>>(path: &T) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
let p = path.as_ref();
match p.extension().map(|x| x.to_str()).unwrap_or(None) {
Some("css") => html::process(p),
Some("js") => js::process(p),
Some("html") => html::process(p),
Some("ico") => bin::process(p),
Some(x) => Err(format!("Unknown extension: {}", x).into()),
None => Err("No extension".into()),
}
}
fn process_dir<T: AsRef<Path>, U: AsRef<Path>>(
input: T,
output: U,
) -> Result<(), Box<dyn std::error::Error>> {
let input_dir = std::fs::read_dir(input)?;
for entry in input_dir {
let entry = entry?;
let path = entry.path();
let mut output_name = output.as_ref().to_owned();
output_name.push(path.file_name().unwrap());
if path.is_dir() {
if !output_name.exists() {
std::fs::create_dir(&output)?;
}
process_dir(path, &output_name)?;
} else {
let contents = process_file(&path)?;
let mut f = std::fs::File::create(output_name)?;
f.write_all(&contents)?;
}
}
Ok(())
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
process_dir("input", "output")?;
Ok(())
// process(&["input/code.js", "input/index.html"])
}