GithubHelp home page GithubHelp logo

axodotdev / axohtml Goto Github PK

View Code? Open in Web Editor NEW
108.0 1.0 10.0 312 KB

๐Ÿ‘ฉโ€๐Ÿ’ป type-checked JSX for Rust

License: Mozilla Public License 2.0

Rust 98.55% Shell 1.45%
html jsx rust

axohtml's Introduction

axohtml

Github Actions Rust crates.io License: MPL 2.0

This crate provides the html! macro for building fully type checked HTML documents inside your Rust code using roughly JSX compatible syntax.

This crate is a fork of the great Bodil Stokke's typed-html crate. Opted for a fork instead of maintainership because not currently intending to use or maintain the Wasm compatibility (for now).

Quick Preview

let mut doc: DOMTree<String> = html!(
    <html>
        <head>
            <title>"Hello Axo"</title>
            <meta name=Metadata::Author content="Axo Developer Co."/>
        </head>
        <body>
            <h1>">o_o<"</h1>
            <p class="official">
                "The tool company for tool companies"
            </p>
            { (0..3).map(|_| html!(
                <p class="emphasis">
                    ">o_o<"
                </p>
            )) }
            <p class="citation-needed">
                "Every company should be a developer experience company."
            </p>
        </body>
    </html>
);
let doc_str = doc.to_string();

Syntax

This macro largely follows JSX syntax, but with some differences:

  • Text nodes must be quoted, because there's only so much Rust's tokeniser can handle outside string literals. So, instead of <p>Hello</p>, you need to write <p>"Hello"</p>. (The parser will throw an error asking you to do this if you forget.)
  • Element attributes will accept simple Rust expressions, but the parser has its limits, as it's not a full Rust parser. You can use literals, variables, dotted properties, type constructors and single function or method calls. If you use something the parser isn't currently capable of handling, it will complain. You can put braces or parentheses around the expression if the parser doesn't understand it. You can use any Rust code inside a brace or parenthesis block.

Valid HTML5

The macro will only accept valid HTML5 tags, with no tags or attributes marked experimental or obsolete. If it won't accept something you want it to accept, we can discuss it over a pull request (experimental tags and attributes, in particular, are mostly omitted just for brevity, and you're welcome to implement them).

The structure validation is simplistic by necessity, as it defers to the type system: a few elements will have one or more required children, and any element which accepts children will have a restriction on the type of the children, usually a broad group as defined by the HTML spec. Many elements have restrictions on children of children, or require a particular ordering of optional elements, which isn't currently validated.

Attribute Values

Brace blocks in the attribute value position should return the expected type for the attribute. The type checker will complain if you return an unsupported type. You can also use literals or a few simple Rust expressions as attribute values (see the Syntax section above).

The html! macro will add an .into() call to the value expression, so that you can use any type that has an Into<A> trait defined for the actual attribute type A.

As a special case, if you use a string literal, the macro will instead use the FromStr<A> trait to try and parse the string literal into the expected type. This is extremely useful for eg. CSS classes, letting you type class="css-class-1 css-class-2" instead of going to the trouble of constructing a SpacedSet<Class>. The big caveat for this: currently, the macro is not able to validate the string at compile time, and the conversion will panic at runtime if the string is invalid.

Example

let classList: SpacedSet<Class> = ["foo", "bar", "baz"].try_into()?;
html!(
    <div>
        <div class="foo bar baz" />         // parses a string literal
        <div class=["foo", "bar", "baz"] /> // uses From<[&str, &str, &str]>
        <div class=classList />             // uses a variable in scope
        <div class={                        // evaluates a code block
            SpacedSet::try_from(["foo", "bar", "baz"])?
        } />
    </div>
)

Generated Nodes

Brace blocks in the child node position are expected to return an IntoIterator of DOMTrees. You can return single elements or text nodes, as they both implement IntoIterator for themselves. The macro will consume this iterator at runtime and insert the generated nodes as children in the expected position.

Example

html!(
    <ul>
        { (1..=5).map(|i| html!(
            <li>{ text!("{}", i) }</li>
        )) }
    </ul>
)

Rendering

You have two options for actually producing something useful from the DOM tree that comes out of the macro.

Render to a string

The DOM tree data structure implements Display, so you can call to_string() on it to render it to a String. If you plan to do this, the type of the tree should be DOMTree<String> to ensure you're not using any event handlers that can't be printed.

let doc: DOMTree<String> = html!(
    <p>"Hello Axo"</p>
);
let doc_str = doc.to_string();
assert_eq!("<p>Hello Axo</p>", doc_str);

Render to a virtual DOM

The DOM tree structure also implements a method called vnode(), which renders the tree to a tree of VNodes, which is a mirror of the generated tree with every attribute value rendered into Strings. You can walk this virtual DOM tree and pass it on to your favourite virtual DOM system.

License

This software is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.

Copyright 2018 Bodil Stokke, 2022 Axo Developer Co.

axohtml's People

Contributors

adrianheine avatar ashleygwilliams avatar bodil avatar chrysn avatar ckampfe avatar cuviper avatar dependabot-preview[bot] avatar dependabot-support avatar derekdreery avatar dgellow avatar dtolnay avatar egeriis avatar iamcodemaker avatar jonathankingston avatar lunchhunter avatar meteor-lsw avatar saravieira avatar tethyssvensson avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar

axohtml's Issues

update deps

it's been a second since the deps on this package were updated and the lalrpop version this uses is about to be EOL on future versions of stable rust. related to axodotdev/oranda#222

warning: the following packages contain code that will be rejected by a future version of Rust: lalrpop v0.19.8
note: to see what the problems were, use the option `--future-incompat-report`, or run `cargo report future-incompatibilities --id 1`

Support for preconnect link relationship for Google Fonts

Google Fonts asks you to include two preconnect links when using one of their fonts. The link tags when copied from the Google Fonts site look like this:

<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Roboto&display=swap" rel="stylesheet">

The rel attribute of links is parsed with a LinkType enum that is missing a few valid values. This LinkType enum is also used for a tags so a straight copy/paste into the enum might not be enough because not all types are supported in both tags!

Header elements (h1-h6) cannot be placed inside <summary>

The spec does say it's for Phrasing contents (which excludes header elements), but then allows them anyway.

Content model:
Phrasing content, optionally intermixed with heading content.

https://html.spec.whatwg.org/multipage/interactive-elements.html#the-summary-element

https://stackoverflow.com/questions/41227862/is-it-semantically-correct-to-use-h2-tag-inside-summary-tag

So the compile error for this code, does not follow the spec

let x: DOMTree<String> = html! {
    <summary>
      <h4>"test"</h4>
    </summary>
};
error[E0277]: the trait bound `h4<_>: axohtml::elements::PhrasingContent<_>` is not satisfied
  --> src/main.rs:9:30
   |
9  |       let x: DOMTree<String> = html! {
   |  ______________________________^
10 | |         <summary>
11 | |           <h4>"test"</h4>
12 | |         </summary>
13 | |     };
   | |_____^ the trait `axohtml::elements::PhrasingContent<_>` is not implemented for `h4<_>`
   |

This feels gnarly to carve out an exception in our type system, so I understand if this isn't high priority. It's not high priority for me either

allow http_equiv attribute in meta tags

looks like #23 was incomplete, we should add this and do a patch version, 0.4.1

---- site::it_builds_the_site stdout ----
ERROR: <meta http_equiv="Permissions-Policy"> failed to parse attribute value: Matching variant not found
ERROR: rebuild with nightly to print source location
thread 'site::it_builds_the_site' panicked at 'failed to parse string literal', src/site/head.rs:59:9

Events? Events!

Currently, this package does not appear to handle events. Right now that doesn't matter โ€” generated Oranda pages do not need fancy event listeners. What will we do when we want things, like interactive widgets?

It seems doing a straight inline event registration on the HTML attribute is not great. (See: https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events#inline_event_handlers_%E2%80%94_dont_use_these)

Will we choose to feed vnode trees into the vDOM library of our choice? Find a way to register and dispatch events ourselves (https://the-guild.dev/blog/react-dom-event-handling-system)? Confine most interactive bits to the Axo dashboard (Sunfish) and add a little bit of script for the rest? Is this a case for Web Components?! We don't know, but we should think about it!

add doc tests

per #29 i think it'd be useful to make sure we are testing not only the library but the docs :)

Allow colons in class names

When using class names that contain colons (:), I get the following error:

thread 'tokio-runtime-worker' panicked at 'called `Result::unwrap()` on an `Err` value: "class name can only contain alphanumerics, dash, dot and underscore"'

This is coming from this check.

Now, I did not find anything in the HTML spec that actually forbids colons (actually, it allows everything but whitespace). At least colons are quite frequently used for pseudo classes in tailwind (e.g. hover:bg-purple-100).

So I propose to allow colons for classes (and also ids for good measure, it uses the same check). If you agree, I'll open a pull request, should be easy to change.

UPDATE: I changed it in my fork, works like a charm. I'll open a PR if you want.

Input elements "name" attribute should allow arbitrary strings instead of only ID

It's been a long time since I did any html, but I remember being able to use names such as foo[0].bar or foo[] as input field names, so that a web framework could reconstruct a nested structure from query/form parameters. It seems this is not currently supported, and the attribute is defined as being of type ID.

Using such a name leads to a runtime panic such as

ERROR: <input name="foobar[]"> failed to parse attribute value: ID can only contain alphanumerics, dash, dot and underscore

The whatwg html spec does not seem to restrict the contents of the name attribute:

Other than isindex, any non-empty value for name is allowed

Fix clippy warnings on tests

introduced in axodotdev/axohtml-fork#1 - clippy tests fail due to some annoying to fix warnings in tests. should fix or add config for clippy to skip.

Latest Version Fails to compile

Latest version fails to compile:

error[E0615]: attempted to take value of method `line` on type `proc_macro::Span`
   --> C:\Users\drunk\.cargo\registry\src\index.crates.io-6f17d22bba15001f\axohtml-macros-0.5.0\src\html.rs:203:53
    |
203 | ...                   span.unstable().start().line,
    |                                               ^^^^ method, not a field
    |
help: use parentheses to call the method
    |
203 |                             span.unstable().start().line(),
    |                                                         ++

error[E0615]: attempted to take value of method `column` on type `proc_macro::Span`
   --> C:\Users\drunk\.cargo\registry\src\index.crates.io-6f17d22bba15001f\axohtml-macros-0.5.0\src\html.rs:204:53
    |
204 | ...                   span.unstable().start().column
    |                                               ^^^^^^ method, not a field
    |
help: use parentheses to call the method
    |
204 |                             span.unstable().start().column()
    |                                                           ++

For more information about this error, try `rustc --explain E0615`.
error: could not compile `axohtml-macros` (lib) due to 2 previous errors

For some reason these lines seem to be faulty. However after looking here, I can't seem to figure out why. I have tried cleaning and rebuilding.

Rustc:

rustc 1.73.0-nightly (da6b55cc5 2023-07-17)
binary: rustc
commit-hash: da6b55cc5eaf76ed6acb7dc2f7d611e32af7c9a7
commit-date: 2023-07-17
host: x86_64-pc-windows-msvc
release: 1.73.0-nightly
LLVM version: 16.0.5

Custom Renderers (Terminal UI)

Awesome project! Currently in Rust it's not the most fun or expressive to write terminal UI.

In NodeJS a project called https://github.com/vadimdemedes/ink was created to use JSX and render it to a terminal UI instead.

So I'd love to feature request that axohtml have an extension/plug-in to render output for terminal UI creation!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.