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