mirror of
https://codeberg.org/demostf/frontend.git
synced 2026-06-03 18:24:12 +02:00
dynamic/static embeds
This commit is contained in:
parent
e5c9aeb7fe
commit
cb56c80555
22 changed files with 9068 additions and 665 deletions
3943
build/bundlers/Cargo.lock
generated
Normal file
3943
build/bundlers/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
29
build/bundlers/Cargo.toml
Normal file
29
build/bundlers/Cargo.toml
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
[package]
|
||||
name = "demostf-build-bundlers"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
lightningcss = { version = "1.0.0-alpha.40", features = ["browserslist", "visitor"] }
|
||||
base64 = "0.21.0"
|
||||
urlencoding = "2.1.2"
|
||||
swc = "0.259.6"
|
||||
swc_common = { version = "0.30.5", features = ["tty-emitter", "concurrent"], git = "https://github.com/icewind1991/swc", branch = "loader-browser-overwrite-old" }
|
||||
swc_core = { version = "0.74.6", git = "https://github.com/icewind1991/swc", branch = "loader-browser-overwrite-old" }
|
||||
#swc_bundler = "0.212.5"
|
||||
swc_bundler = { version = "0.212.5", git = "https://github.com/icewind1991/swc", branch = "loader-browser-overwrite-old" }
|
||||
#swc_ecma_loader = "0.42.5"
|
||||
swc_ecma_loader = { version = "0.42.5", git = "https://github.com/icewind1991/swc", branch = "loader-browser-overwrite-old", features = ["node", "cache"] }
|
||||
swc_ecma_ast = { version = "0.102.5", git = "https://github.com/icewind1991/swc", branch = "loader-browser-overwrite-old" }
|
||||
swc_atoms = { version = "0.4.43", git = "https://github.com/icewind1991/swc", branch = "loader-browser-overwrite-old" }
|
||||
swc_ecma_parser = { version = "0.132.6", features = ["typescript"], git = "https://github.com/icewind1991/swc", branch = "loader-browser-overwrite-old" }
|
||||
swc_ecma_codegen = { version = "0.137.6", git = "https://github.com/icewind1991/swc", branch = "loader-browser-overwrite-old" }
|
||||
swc_ecma_transforms_base = { version = "0.125.1", git = "https://github.com/icewind1991/swc", branch = "loader-browser-overwrite-old" }
|
||||
swc_ecma_transforms_typescript = { version = "0.175.4", git = "https://github.com/icewind1991/swc", branch = "loader-browser-overwrite-old" }
|
||||
swc_ecma_visit = { version = "0.88.5", git = "https://github.com/icewind1991/swc", branch = "loader-browser-overwrite-old" }
|
||||
anyhow = "1.0.70"
|
||||
jsx-dom-expressions = { version = "0.1", git = "https://github.com/icewind1991/swc-plugin-jsx-dom-expressions", branch = "loader-browser-overwrite-old" }
|
||||
#jsx-dom-expressions = { version = "0.1", path = "../../../../rust/swc-plugin-jsx-dom-expressions" }
|
||||
rand = "0.8.5"
|
||||
fnv = "1.0.7"
|
||||
|
||||
39
build/bundlers/src/lib.rs
Normal file
39
build/bundlers/src/lib.rs
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
mod script;
|
||||
mod style;
|
||||
|
||||
use fnv::FnvHasher;
|
||||
pub use script::bundle_script;
|
||||
use std::fs::read;
|
||||
use std::hash::{Hash, Hasher};
|
||||
pub use style::bundle_style;
|
||||
|
||||
pub fn bundle_raw(input: &str) -> Vec<u8> {
|
||||
read(input).expect("failed to read raw")
|
||||
}
|
||||
|
||||
fn guess_embed(path: &str) -> (&'static str, bool) {
|
||||
match path.split('.').last().unwrap() {
|
||||
"svg" => ("image/svg+xml", false),
|
||||
"png" => ("image/png", true),
|
||||
ext => panic!("no mimetype known for {ext}"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn hash(input: &[u8]) -> String {
|
||||
let mut hasher = FnvHasher::default();
|
||||
input.hash(&mut hasher);
|
||||
format!("{:x}", hasher.finish())
|
||||
}
|
||||
|
||||
pub fn guess_mime(path: &str) -> &'static str {
|
||||
if path.ends_with("svg") {
|
||||
return "image/svg+xml";
|
||||
} else if path.ends_with("png") {
|
||||
return "image/png";
|
||||
} else if path.ends_with("css") {
|
||||
return "text/css";
|
||||
} else if path.ends_with("js") {
|
||||
return "text/javascript";
|
||||
}
|
||||
return "text/plain";
|
||||
}
|
||||
192
build/bundlers/src/script.rs
Normal file
192
build/bundlers/src/script.rs
Normal file
|
|
@ -0,0 +1,192 @@
|
|||
use anyhow::Error;
|
||||
use jsx_dom_expressions::TransformVisitor;
|
||||
use std::collections::HashMap;
|
||||
use std::io::Write;
|
||||
use std::sync::Arc;
|
||||
use swc_atoms::js_word;
|
||||
use swc_bundler::{Bundler, Load, ModuleData, ModuleRecord};
|
||||
use swc_common::comments::NoopComments;
|
||||
use swc_common::sync::Lrc;
|
||||
use swc_common::{
|
||||
errors::{ColorConfig, Handler},
|
||||
FileName, Mark, SourceMap, Span, GLOBALS,
|
||||
};
|
||||
use swc_ecma_ast::*;
|
||||
use swc_ecma_codegen::text_writer::{omit_trailing_semi, JsWriter, WriteJs};
|
||||
use swc_ecma_codegen::Emitter;
|
||||
use swc_ecma_loader::resolvers::lru::CachingResolver;
|
||||
use swc_ecma_loader::resolvers::node::NodeModulesResolver;
|
||||
use swc_ecma_loader::TargetEnv;
|
||||
use swc_ecma_parser::{parse_file_as_module, Syntax, TsConfig};
|
||||
use swc_ecma_transforms_base::fixer::fixer;
|
||||
use swc_ecma_transforms_base::hygiene::hygiene;
|
||||
use swc_ecma_transforms_typescript::strip;
|
||||
use swc_ecma_visit::{as_folder, FoldWith};
|
||||
|
||||
pub fn bundle_script(script: &str) -> Vec<u8> {
|
||||
#[cfg(debug_assertions)]
|
||||
let minify = false;
|
||||
#[cfg(not(debug_assertions))]
|
||||
let minify = true;
|
||||
|
||||
GLOBALS.set(&Default::default(), || {
|
||||
let cm = Arc::<SourceMap>::default();
|
||||
|
||||
let globals = &Box::default();
|
||||
let mut bundler = Bundler::new(
|
||||
globals,
|
||||
cm.clone(),
|
||||
Loader { cm: cm.clone() },
|
||||
CachingResolver::new(
|
||||
4096,
|
||||
NodeModulesResolver::new(TargetEnv::Browser, Default::default(), true),
|
||||
),
|
||||
swc_bundler::Config {
|
||||
// disable_hygiene: !minify,
|
||||
// disable_dce: !minify,
|
||||
// disable_fixer: !minify,
|
||||
// disable_inliner: !minify,
|
||||
..Default::default()
|
||||
},
|
||||
Box::new(Hook),
|
||||
);
|
||||
let mut entries = HashMap::new();
|
||||
entries.insert(
|
||||
script.trim_end_matches(".js").to_string(),
|
||||
FileName::Real(script.into()),
|
||||
);
|
||||
let modules = bundler.bundle(entries).expect("failed to bundle");
|
||||
|
||||
let mut buf = vec![];
|
||||
for module in modules {
|
||||
write(minify, cm.clone(), &module.module, &mut buf);
|
||||
}
|
||||
buf
|
||||
})
|
||||
}
|
||||
|
||||
fn write<W: Write>(minify: bool, cm: Arc<SourceMap>, module: &Module, out: W) {
|
||||
let wr = JsWriter::new(cm.clone(), "\n", out, None);
|
||||
let mut emitter = Emitter {
|
||||
cfg: swc_ecma_codegen::Config {
|
||||
minify,
|
||||
..Default::default()
|
||||
},
|
||||
cm: cm.clone(),
|
||||
comments: None,
|
||||
wr: if minify {
|
||||
Box::new(omit_trailing_semi(wr)) as Box<dyn WriteJs>
|
||||
} else {
|
||||
Box::new(wr) as Box<dyn WriteJs>
|
||||
},
|
||||
};
|
||||
emitter.emit_module(module).unwrap();
|
||||
}
|
||||
|
||||
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(js_word!("url"), span)),
|
||||
value: Box::new(Expr::Lit(Lit::Str(Str {
|
||||
span,
|
||||
raw: None,
|
||||
value: file_name.into(),
|
||||
}))),
|
||||
},
|
||||
KeyValueProp {
|
||||
key: PropName::Ident(Ident::new(js_word!("main"), 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(js_word!("main"), 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::Typescript(TsConfig {
|
||||
tsx: true,
|
||||
..TsConfig::default()
|
||||
}),
|
||||
EsVersion::Es5,
|
||||
None,
|
||||
&mut vec![],
|
||||
)
|
||||
.unwrap_or_else(|err| {
|
||||
let handler =
|
||||
Handler::with_tty_emitter(ColorConfig::Always, false, false, Some(self.cm.clone()));
|
||||
err.into_diagnostic(&handler).emit();
|
||||
panic!("failed to parse")
|
||||
});
|
||||
|
||||
let top_level_mark = Mark::new();
|
||||
|
||||
let module = module
|
||||
.fold_with(&mut strip(top_level_mark))
|
||||
.fold_with(&mut as_folder(TransformVisitor::new(
|
||||
jsx_dom_expressions::config::Config {
|
||||
module_name: "solid-js/web".to_string(),
|
||||
builtins: vec![
|
||||
"For".into(),
|
||||
"Show".into(),
|
||||
"Switch".into(),
|
||||
"Match".into(),
|
||||
"Suspense".into(),
|
||||
"SuspenseList".into(),
|
||||
"Portal".into(),
|
||||
"Index".into(),
|
||||
"Dynamic".into(),
|
||||
"ErrorBoundary".into(),
|
||||
],
|
||||
..Default::default()
|
||||
},
|
||||
NoopComments,
|
||||
)))
|
||||
.fold_with(&mut hygiene())
|
||||
.fold_with(&mut fixer(None));
|
||||
|
||||
// if let FileName::Real(path) = &f {
|
||||
// let mut out = vec![];
|
||||
// write(false, self.cm.clone(), &module, &mut out);
|
||||
// let mut path = path.clone();
|
||||
// path.set_extension("c.js");
|
||||
// std::fs::write(path, out).unwrap();
|
||||
// }
|
||||
|
||||
Ok(ModuleData {
|
||||
fm,
|
||||
module,
|
||||
helpers: Default::default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
81
build/bundlers/src/style.rs
Normal file
81
build/bundlers/src/style.rs
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
use crate::guess_embed;
|
||||
use base64::engine::general_purpose::STANDARD;
|
||||
use base64::Engine;
|
||||
use lightningcss::bundler::{Bundler, FileProvider};
|
||||
use lightningcss::stylesheet::{MinifyOptions, ParserOptions, PrinterOptions};
|
||||
use lightningcss::targets::Browsers;
|
||||
use lightningcss::values::url::Url;
|
||||
use lightningcss::visit_types;
|
||||
use lightningcss::visitor::{Visit, VisitTypes, Visitor};
|
||||
use std::convert::Infallible;
|
||||
use std::fs::read;
|
||||
use std::path::Path;
|
||||
|
||||
pub fn bundle_style(style: &str) -> Vec<u8> {
|
||||
// todo build time?
|
||||
let fs = FileProvider::new();
|
||||
let mut bundler = Bundler::new(
|
||||
&fs,
|
||||
None,
|
||||
ParserOptions {
|
||||
nesting: true,
|
||||
..ParserOptions::default()
|
||||
},
|
||||
);
|
||||
let mut stylesheet = bundler
|
||||
.bundle(Path::new(style))
|
||||
.expect("failed to bundle css");
|
||||
let browsers =
|
||||
Browsers::from_browserslist(["last 2 versions"]).expect("failed to parse browserlist");
|
||||
stylesheet
|
||||
.minify(MinifyOptions {
|
||||
targets: browsers.clone(),
|
||||
..MinifyOptions::default()
|
||||
})
|
||||
.expect("failed to minify css");
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
let minify = false;
|
||||
#[cfg(not(debug_assertions))]
|
||||
let minify = true;
|
||||
|
||||
stylesheet.visit(&mut InlineUrlVisitor).unwrap();
|
||||
|
||||
stylesheet
|
||||
.to_css(PrinterOptions {
|
||||
targets: browsers,
|
||||
minify,
|
||||
..PrinterOptions::default()
|
||||
})
|
||||
.expect("failed to output css")
|
||||
.code
|
||||
.into_bytes()
|
||||
}
|
||||
|
||||
struct InlineUrlVisitor;
|
||||
|
||||
impl<'i> Visitor<'i> for InlineUrlVisitor {
|
||||
type Error = Infallible;
|
||||
|
||||
const TYPES: VisitTypes = visit_types!(URLS);
|
||||
|
||||
fn visit_url(&mut self, url: &mut Url<'i>) -> Result<(), Self::Error> {
|
||||
if let Some(path) = url.url.strip_prefix("inline://") {
|
||||
let content = read(path).unwrap_or_else(|e| {
|
||||
eprintln!("Failed to write inline file {path}: {e}");
|
||||
panic!("Failed to inline");
|
||||
});
|
||||
let (mime, encode) = guess_embed(path);
|
||||
|
||||
if encode {
|
||||
let encoded = STANDARD.encode(content);
|
||||
url.url = format!("data:{mime};base64,{encoded}").into();
|
||||
} else {
|
||||
let content = String::from_utf8(content).expect("invalid utf8");
|
||||
let encoded = urlencoding::encode(&content);
|
||||
url.url = format!("data:{mime},{encoded}").into();
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue