Skip to content

Commit

Permalink
Merge pull request #43 from BrowserSync/html_support
Browse files Browse the repository at this point in the history
html_support
  • Loading branch information
shakyShane authored Nov 24, 2024
2 parents 2ad58c3 + 52f6222 commit 628fe41
Show file tree
Hide file tree
Showing 43 changed files with 944 additions and 149 deletions.
313 changes: 313 additions & 0 deletions Cargo.lock

Large diffs are not rendered by default.

23 changes: 23 additions & 0 deletions bslive.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<style>
p {
color: red;
}

abc-element {
color: green;
}

</style>

<abc-element></abc-element>

<script type="module">
class BSlive extends HTMLElement {
connectedCallback() {
this.innerHTML = "Hello worlds";
}
}

customElements.define("abc-element", BSlive);

</script>
2 changes: 2 additions & 0 deletions crates/bsnext_dto/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,7 @@ pub enum InputErrorDTO {
DirError(String),
YamlError(String),
MarkdownError(String),
HtmlError(String),
Io(String),
UnsupportedExtension(String),
MissingExtension(String),
Expand All @@ -305,6 +306,7 @@ impl From<&InputError> for InputErrorDTO {
e @ InputError::PortError(_) => InputErrorDTO::PortError(e.to_string()),
e @ InputError::DirError(_) => InputErrorDTO::DirError(e.to_string()),
e @ InputError::MarkdownError(_) => InputErrorDTO::MarkdownError(e.to_string()),
e @ InputError::HtmlError(_) => InputErrorDTO::HtmlError(e.to_string()),
e @ InputError::YamlError(_) => InputErrorDTO::YamlError(e.to_string()),
e @ InputError::Io(_) => InputErrorDTO::Io(e.to_string()),
e @ InputError::UnsupportedExtension(_) => {
Expand Down
1 change: 1 addition & 0 deletions crates/bsnext_example/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ edition = "2021"
[dependencies]
bsnext_input = { path = "../bsnext_input" }
bsnext_md = { path = "../bsnext_md" }
bsnext_html = { path = "../bsnext_html" }
clap = { workspace = true }
7 changes: 4 additions & 3 deletions crates/bsnext_example/src/md.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
use bsnext_input::server_config::ServerIdentity;
use bsnext_input::{InputSource, InputSourceKind};
use bsnext_md::md_to_input;
use bsnext_input::{InputCreation, InputSource, InputSourceKind};
use bsnext_md::md_fs::MdFs;

#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct MdExample;

impl InputSource for MdExample {
fn into_input(self, identity: Option<ServerIdentity>) -> InputSourceKind {
let input_str = include_str!("../../../examples/markdown/single.md");
let mut input = md_to_input(input_str).expect("example cannot fail?");
let mut input =
MdFs::from_input_str(input_str, &Default::default()).expect("example cannot fail?");
let server = input
.servers
.first_mut()
Expand Down
8 changes: 4 additions & 4 deletions crates/bsnext_example/src/playground.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
use bsnext_html::HtmlFs;
use bsnext_input::server_config::ServerIdentity;
use bsnext_input::{InputCreation, InputSource, InputSourceKind};
use bsnext_md::md_fs::MdFs;
use bsnext_input::{InputCreation, InputCtx, InputSource, InputSourceKind};

#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct PlaygroundExample;

impl InputSource for PlaygroundExample {
fn into_input(self, identity: Option<ServerIdentity>) -> InputSourceKind {
let input_str = include_str!("../../../examples/markdown/playground.md");
let mut input = MdFs::from_input_str(input_str).unwrap();
let input_str = include_str!("../../../examples/html/playground.html");
let mut input = HtmlFs::from_input_str(input_str, &InputCtx::default()).unwrap();

// update the server identity if it was provided
if let (Some(server), Some(identity)) = (input.servers.get_mut(0), identity) {
Expand Down
14 changes: 14 additions & 0 deletions crates/bsnext_html/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[package]
name = "bsnext_html"
version = "0.1.0"
edition = "2021"

[dependencies]
bsnext_input = { path = "../bsnext_input" }
unindent = "0.2.3"
indent = "0.1.1"
scraper = "0.21.0"

[dev-dependencies]
anyhow = { workspace = true }
insta = { workspace = true }
54 changes: 54 additions & 0 deletions crates/bsnext_html/src/html_writer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
use bsnext_input::playground::Playground;
use bsnext_input::server_config::ServerConfig;
use bsnext_input::{Input, InputWriter};

pub struct HtmlWriter;

impl InputWriter for HtmlWriter {
fn input_to_str(&self, input: &Input) -> String {
if input.servers.is_empty() {
todo!("html requires at least 1 server definition")
}
if input.servers.len() > 1 {
todo!("more than 1 server not supported yet")
}
let server = input.servers.first().expect("must access first");
let Some(playground) = &server.playground else {
todo!("only playground is supported in HTML for now")
};
let mut blocks: Vec<String> = vec![];
if let Some(css) = &playground.css {
blocks.push("<style>".into());
let indented = indent::indent_all_by(4, css);
blocks.push(indented);
blocks.push("</style>".into());
}
blocks.push(playground.html.clone());
if let Some(js) = &playground.js {
blocks.push("<script type=\"module\">".into());
let indented = indent::indent_all_by(4, js);
blocks.push(indented);
blocks.push("</script>".into());
}
blocks.join("\n")
}
}

#[test]
fn test_html_writer_for_playground() {
let css = r#"body {
background: red;
}"#;
let js = r#"console.log("hello world!")"#;
let playground = Playground {
html: "<p>Hello world</p>".to_string(),
js: Some(js.to_string()),
css: Some(css.to_string()),
};
let mut input = Input::default();
let mut server = ServerConfig::default();
server.playground = Some(playground);
input.servers.push(server);
let output = HtmlWriter.input_to_str(&input);
insta::assert_snapshot!(output);
}
96 changes: 96 additions & 0 deletions crates/bsnext_html/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
use bsnext_input::playground::Playground;
use bsnext_input::server_config::{ServerConfig, ServerIdentity};
use bsnext_input::{Input, InputCreation, InputCtx, InputError};
use std::fs::read_to_string;
use std::path::Path;

pub mod html_writer;

pub struct HtmlFs;

impl InputCreation for HtmlFs {
fn from_input_path<P: AsRef<Path>>(path: P, ctx: &InputCtx) -> Result<Input, Box<InputError>> {
let str = read_to_string(path).map_err(|e| Box::new(e.into()))?;
let input = playground_html_str_to_input(&str, ctx)
.map_err(|e| Box::new(InputError::HtmlError(e.to_string())))?;
Ok(input)
}

fn from_input_str<P: AsRef<str>>(content: P, ctx: &InputCtx) -> Result<Input, Box<InputError>> {
let input = playground_html_str_to_input(&content.as_ref(), ctx)
.map_err(|e| Box::new(InputError::HtmlError(e.to_string())))?;
Ok(input)
}
}

fn playground_html_str_to_input(html: &str, ctx: &InputCtx) -> Result<Input, Box<InputError>> {
use unindent::unindent;

// parse the HTML
let mut document = scraper::Html::parse_fragment(html);

let style = scraper::Selector::parse("style:first-of-type").unwrap();
let script = scraper::Selector::parse("script:first-of-type").unwrap();

let mut style_elems = document.select(&style);
let mut script_elems = document.select(&script);
let mut node_ids_to_remove = vec![];

// start an empty playground
let mut playground = Playground {
html: "".to_string(),
css: None,
js: None,
};

if let Some(style) = style_elems.next() {
node_ids_to_remove.push(style.id());
let t = style.text().nth(0).unwrap();
let unindented = unindent(t);
playground.css = Some(unindented);
}

if let Some(script) = script_elems.next() {
node_ids_to_remove.push(script.id());
let t = script.text().nth(0).unwrap();
let unindented = unindent(t);
playground.js = Some(unindented);
}

for node_id in node_ids_to_remove {
document.tree.get_mut(node_id).unwrap().detach();
}

// grab the HTML
let as_html = document.html();
let trimmed = as_html
.strip_prefix("<html>")
.unwrap()
.strip_suffix("</html>")
.unwrap();
let un_indented = unindent(trimmed);
playground.html = un_indented;

// Now start to build up the input
let mut input = Input::default();

// 1: first try prev
// 2: next try if 'port' was provided
// 3: finally, make one up
let iden = ctx
.first_id()
.or_else(|| ServerIdentity::from_port_or_named(ctx.port()).ok())
.unwrap_or_default();

// Create the server
let server = ServerConfig {
identity: iden,
playground: Some(playground),
..Default::default()
};

// Add it to the input
input.servers.push(server);

Ok(input)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
source: crates/bsnext_html/src/html_writer.rs
expression: output
---
<style>
body {
background: red;
}
</style>
<p>Hello world</p>
<script type="module">
console.log("hello world!")
</script>
82 changes: 82 additions & 0 deletions crates/bsnext_html/tests/html_playground.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
use bsnext_html::HtmlFs;
use bsnext_input::server_config::ServerIdentity;
use bsnext_input::{InputArgs, InputCreation, InputCtx};
use insta::assert_debug_snapshot;

const INPUT: &str = r#"
<style>
p {
color: red;
}
abc-shane {
color: red;
}
</style>
<main>
<h1>Test!</h1>
<abc-shane></abc-shane>
</main>
<script type="module">
class BSlive extends HTMLElement {
connectedCallback() {
this.innerHTML = "Hello world";
}
}
customElements.define("abc-shane", BSlive);
</script>"#;

#[test]
fn test_html_playground_content() -> anyhow::Result<()> {
let idens = vec![];
let ctx = InputCtx::new(&idens, None);
let as_input = HtmlFs::from_input_str(INPUT, &ctx)?;
let Some(server) = as_input.servers.get(0) else {
return Err(anyhow::anyhow!("no server"));
};
let routes = server.routes();
let html = routes.get(0).unwrap();
let js = routes.get(1).unwrap();
let css = routes.get(2).unwrap();
assert_debug_snapshot!(html.kind);
assert_debug_snapshot!(js.kind);
assert_debug_snapshot!(css.kind);
Ok(())
}
#[test]
fn test_html_playground_without_server_id() -> anyhow::Result<()> {
let idens = vec![];
let ctx = InputCtx::new(&idens, None);
let as_input = HtmlFs::from_input_str(INPUT, &ctx)?;
assert_eq!(as_input.servers.len(), 1);
let first = as_input.servers.get(0).unwrap();
let is_named = matches!(first.identity, ServerIdentity::Named { .. });
assert_eq!(is_named, true);
Ok(())
}
#[test]
fn test_html_playground_with_server_id() -> anyhow::Result<()> {
let ident = ServerIdentity::Address {
bind_address: String::from("127.0.0.1:8080"),
};
let ctx = InputCtx::new(&[ident.clone()], None);
let as_input = HtmlFs::from_input_str(INPUT, &ctx)?;

assert_eq!(as_input.servers.len(), 1);
let first = as_input.servers.get(0).unwrap();
assert_eq!(ident, first.identity);
Ok(())
}
#[test]
fn test_html_playground_with_port() -> anyhow::Result<()> {
let ident = ServerIdentity::Address {
bind_address: String::from("0.0.0.0:8080"),
};
let input_args = InputArgs { port: Some(8080) };
let ctx = InputCtx::new(&[], Some(input_args));
let as_input = HtmlFs::from_input_str(INPUT, &ctx)?;
let first = as_input.servers.get(0).unwrap();
assert_eq!(first.identity, ident);
Ok(())
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
source: crates/bsnext_html/tests/html_playground.rs
expression: js.kind
---
Raw(
Raw {
raw: "class BSlive extends HTMLElement {\n connectedCallback() {\n this.innerHTML = \"Hello world\";\n }\n}\n\ncustomElements.define(\"abc-shane\", BSlive);\n",
},
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
source: crates/bsnext_html/tests/html_playground.rs
expression: css.kind
---
Raw(
Raw {
raw: "p {\n color: red;\n}\n\nabc-shane {\n color: red;\n}\n",
},
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
source: crates/bsnext_html/tests/html_playground.rs
expression: html.kind
---
Raw(
Html {
html: "\n<main>\n <h1>Test!</h1>\n <abc-shane></abc-shane>\n</main>\n",
},
)
Loading

0 comments on commit 628fe41

Please sign in to comment.