wip
Signed-off-by: Sean Cross <sean@xobs.io>
This commit is contained in:
commit
07dc3f87ed
38
.gitattributes
vendored
Normal file
38
.gitattributes
vendored
Normal 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
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/target
|
2725
Cargo.lock
generated
Normal file
2725
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
27
Cargo.toml
Normal file
27
Cargo.toml
Normal 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
9
src/bin.rs
Normal 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
17
src/html.rs
Normal 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
199
src/js.rs
Normal 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
92
src/main.oxc.rs
Normal 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
48
src/main.rs
Normal 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"])
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user