From 31e20de05ced8d60835028d03e301decf9c89f60 Mon Sep 17 00:00:00 2001
From: Vincent Balat
Date: Sat, 6 Jun 2026 12:15:15 +0200
Subject: [PATCH 1/4] doc: odoc manual + API refs for wodoc (dev)
Convert the wikicreole manual (docs/manual-wiki/*.wiki) to odoc .mld
(intro, functors, ppx, jsx) + a curated docs/api.mld from docs/indexdoc,
declared in package tyxml via docs/dune (documentation). Convert the
<>/<> references in the .mli/.ml doc comments to native
odoc references. First step of the wodoc doc migration.
---
docs/api.mld | 30 +++++++++
docs/dune | 2 +
docs/functors.mld | 114 ++++++++++++++++++++++++++++++++++
docs/intro.mld | 153 ++++++++++++++++++++++++++++++++++++++++++++++
docs/jsx.mld | 106 ++++++++++++++++++++++++++++++++
docs/ppx.mld | 118 +++++++++++++++++++++++++++++++++++
implem/tyxml.ml | 2 +-
lib/html_f.mli | 2 +-
lib/html_sigs.mli | 2 +-
lib/svg_f.mli | 2 +-
lib/svg_sigs.mli | 2 +-
ppx/tyxml_ppx.mli | 2 +-
12 files changed, 529 insertions(+), 6 deletions(-)
create mode 100644 docs/api.mld
create mode 100644 docs/dune
create mode 100644 docs/functors.mld
create mode 100644 docs/intro.mld
create mode 100644 docs/jsx.mld
create mode 100644 docs/ppx.mld
diff --git a/docs/api.mld b/docs/api.mld
new file mode 100644
index 000000000..b2aba60be
--- /dev/null
+++ b/docs/api.mld
@@ -0,0 +1,30 @@
+{0 API reference}
+
+{2 Main modules }
+
+{!modules:
+Tyxml
+Tyxml.Html
+Tyxml.Svg
+}
+
+{2 Types and signatures}
+
+{!modules:
+Html_sigs
+Html_types
+Svg_sigs
+Svg_types
+}
+
+{2 TyXML Functors }
+
+{!modules:
+Html_f
+Svg_f
+Xml_sigs
+Xml_wrap
+Xml_iter
+Xml_print
+Xml_stream
+}
diff --git a/docs/dune b/docs/dune
new file mode 100644
index 000000000..71b0c739c
--- /dev/null
+++ b/docs/dune
@@ -0,0 +1,2 @@
+(documentation
+ (package tyxml))
diff --git a/docs/functors.mld b/docs/functors.mld
new file mode 100644
index 000000000..5f243e154
--- /dev/null
+++ b/docs/functors.mld
@@ -0,0 +1,114 @@
+{0 The functorial interface}
+
+TyXML provides a functorial interface to implement HTML and SVG on top
+of any XML implementation. This is used heavily by Eliom to implement
+the [F] and [D] modules, providing respectively a functional and
+a DOM version of the HTML implementation.
+
+
+{1:make The [Make] functors}
+
+These interfaces are available in the modules
+{!Html_f} and {!Svg_f}.
+They provide a functor [Make] which takes a concrete implementation
+of Xml following {!Xml_sigs.T}. A default
+implementation is provided by the {!Tyxml.Xml} module. The
+functor {!Html_f.Make} also needs an Svg
+implementation that one can obtain, for example, with the functor
+{!Svg_f.Make}. The [Xml] always needs to provide a
+module [W] of type {!Xml_wrap.T}, along with types
+{@ocaml[ type 'a wrap = 'a W.t ]} and
+{@ocaml[ type 'a list_wrap = 'a W.tlist ]}.
+The purpose of the [Wrap] module is explained in the next section.
+
+
+{1:wrap Wrapping up the nodes}
+
+The module {!Xml_sigs.T.W} allows to wrap Xml elements
+in a monad ['a t]. A good example of application is the [R]
+modules with reactive nodes in Eliom. Here is the simplified signature
+of the [div] element:
+{@ocaml[R.div : 'a elt list t -> div elt]}
+[t] will wrap the input of every [Xml] node and be
+integrated in the resulting node.
+
+The [W] module needs to implement operations over the type of nodes,
+and provides a special type for lists of nodes. The [W] module
+additionally provides a type [(-'a, 'b) ft] for (wrapped) functions,
+whose purpose is explained in the next section. In most cases, it is
+sufficient to define:
+{@ocaml[ (-'a, 'b) ft = 'a -> 'b]}
+
+An identity wrapper, {!Xml_wrap.NoWrap}, is
+provided. {!Xml_wrap.NoWrap} can be used to apply the
+functor without wrapping the elements.
+
+
+{1:wrapped_functions The [Make_with_wrapped_functions] functors}
+
+The [Make_with_wrapped_functions] functors (available in the modules
+{!Html_f} and {!Svg_f}) differ from
+the [Make] functors by requiring an additional argument [C] (of
+type {!Html_f.Wrapped_functions} and
+{!Svg_f.Wrapped_functions} respectively).
+
+The [C] functor argument defines a type [(-'a, 'b) ft], and a
+collection of [ft] values. For applying the functor, the type
+constraint
+{@ocaml[ ('a, 'b) Xml.W.ft = ('a, 'b) C.ft]}
+needs to be satisfied. The [ft] values are wrapped functions that
+the functor uses internally to operate on wrapped elements.
+
+The motivation of providing the [Make_with_wrapped_functions] is as
+follows. Certain monads ['a t] can only be operated upon by wrapped
+functions, and not by plain OCaml functions. The wrapped functions
+cannot be produced internally by TyXML, and thus have to be provided
+to the functor. Our intended application is with Eliom shared (i.e.,
+client-server) signals. Such signals can only be operated upon with
+Eliom shared functions.
+
+The [Make] functors simply apply the [Make_with_wrapped_functions]
+functors with TyXML-provided [Wrapped_functions] modules. The latter
+modules do not wrap the functions, i.e., they satisfy:
+{@ocaml[(-'a, 'b) ft = 'a -> 'b]}.
+
+
+{1:sig Exporting the correct signature}
+
+In order to help export the correct signature after a functor
+application, two signature functors are provided:
+{!Svg_sigs.Make} and {!Html_sigs.Make}.
+
+As an example of use, let us look at the module {!Tyxml.Svg}.
+Here is the definition of the module:
+{@ocaml[module M = Svg_f.Make(Tyxml_xml)]}
+In this case, the declaration in the interface file should look like
+this: {@ocaml[module M : Svg_sigs.Make(Tyxml_xml).T]}
+
+The signature functor {!Svg_sigs.Make} contains only a
+signature [T], which is equal to {!Svg_sigs.T}, but
+exports various equalities with the module [Xml].
+
+You should {b always} use a signature functor to give the type of a
+module produced by a functor application. It will ensure that exactly
+the right type equalities are exported and will naturally keep track
+of changes in TyXML.
+
+There are some important notes about these signature functors:
+- [module M : Svg_sigs.Make(Tyxml_xml).T] doesn't mean that [M.Xml]
+ is a submodule of [Xml]. It only means that the types [uri],
+ [event_handler], [mouse_event_handler],
+ [keyboard_event_handler], [touch_event_handler],
+ [attrib] and [elt] are the
+ same in both modules. This is useful when not exporting the exact
+ module that was used in the functor, but another (smaller and
+ simpler) module. This is the case in {!Js_of_ocaml_tyxml.Tyxml_js.R},
+ for example.
+
+- {!Html_f} and {!Svg_f} functors
+ export two additional equalities, [+'a elt = Xml.elt] and
+ [+'a attrib = Xml.attrib]. These equalities {b should never be
+ exported in a public interface}. Exporting them would break HTML
+ typing by allowing to build invalid HTML trees. These equalities
+ are useful internally, for example in Eliom they are used to make
+ [F.elt], [D.elt] and [R.elt] equals.
diff --git a/docs/intro.mld b/docs/intro.mld
new file mode 100644
index 000000000..d7067a412
--- /dev/null
+++ b/docs/intro.mld
@@ -0,0 +1,153 @@
+{0 TyXML}
+
+Tyxml is a library for building statically correct HTML and SVG documents.
+
+{@ocaml[
+open Tyxml
+let to_ocaml = Html.(a ~a:[a_href "ocaml.org"] [txt "OCaml!"])
+]}
+
+{1:usage Using TyXML}
+
+{2 Standalone Use}
+
+To use TyXML in standalone manner, simply install the [tyxml] OPAM package, link the [tyxml] ocamlfind library and open {!Tyxml}.
+
+{2 Use with another library}
+
+TyXML combinators can be used in conjunction with other libraries. Please consult the relevant document. For example,
+{{:../../eliom/latest/clientserver-html.html}Eliom}
+and
+{{!Js_of_ocaml_tyxml.Tyxml_js}Js_of_ocaml}.
+
+{2 Use with the PPX}
+
+TyXML can also be used with the standard HTML syntax, using {{!page-"ppx"}the PPX syntax extension}:
+{@ocaml[
+open Tyxml
+let%html to_ocaml = "OCaml!"
+]}
+
+This syntax is available both while using TyXML standalone, or with another library. In order to do so, install the [tyxml-ppx] OPAM package and link the [tyxml.ppx] [ocamlfind] library.
+
+{1:example Examples and documentation}
+
+For standalone use, examples are available in the {{:https://github.com/ocsigen/tyxml/tree/master/examples}examples} directory. The entry point of the documentation is in the {!Tyxml} module.
+
+{1:creation Creating documents with TyXML}
+
+This section assumes you have at your disposal an [Html] or [Svg] module, as instructed in {{!page-"".usage}the previous section}.
+
+The documentation for TyXML combinators is provided in {!Html_sigs.T} and {!Svg_sigs.T} and is common to all instances of [Html] and [Svg].
+
+The first thing to understand about TyXML is that for most intents and purposes, it is exactly like HTML. As such, the {{:https://developer.mozilla.org/en-US/docs/Web/HTML/Element}HTML reference} is still very useful. For each HTML element or attribute, there is a combinator implementing it. The main differences are that you can use OCaml to manipulate the elements and that invalid markup produces a type error.
+
+In this tutorial, we will build the {{:https://github.com/ocsigen/tyxml/tree/master/examples/mini_website}Mini website}.
+If you prefer the native HTML syntax, you can also use the {{!page-"ppx"}PPX syntax extension} and consult the {{:https://github.com/ocsigen/tyxml/tree/master/examples/mini_website_ppx}PPX mini website}.
+
+Let us start by building the content of our website. For text, we use the [txt] combinator. In traditional Web fashion, we put everything in a [div].
+{@ocaml[
+let mycontent =
+ div [
+ txt "This is a fabulous content." ;
+ ]
+]}
+
+The variable [mycontent] is of type [[> `Div] Html.elt].
+As we can see, the fact that this is a [div] is reflected in the type. HTML elements are of type {{!Html_sigs.T.elt}elt} and have a combinator of the same name, except when it's a reserved OCaml keyword (such as [object_]).
+
+Our content is fabulous, but for the sake of CSS styling (and still in true Web fashion) we want to add a [class] to it.
+{@ocaml[
+let mycontent =
+ div ~a:[a_class ["content"]] [
+ txt "This is a fabulous content." ;
+ ]
+]}
+
+The {{!Html_sigs.T.a_class}a_class} creates a new [class] attribute of type [[> `Class] attrib]. Similarly to elements, the kind of attribute is reflected in the {{!Html_sigs.T.attrib}attrib} type.
+We use the optional argument [~a] to pass the list of attributes. This optional argument is available on all element combinators.
+
+In order to add a title to our fabulous content, we use the {{!Html_sigs.T.a}h1} combinator.
+{@ocaml[
+let mycontent =
+ div ~a:[a_class ["content"]] [
+ h1 [txt "A fabulous title"] ;
+ txt "This is a fabulous content." ;
+ ]
+]}
+
+Naturally, [div] accepts several children. In TyXML vocabulary, this is a {{!Html_sigs.T.star}star} combinator.
+There are also
+{{!Html_sigs.T.unary}unary} and
+{{!Html_sigs.T.nullary}nullary} combinators,
+which accept, respectively, one child and zero children.
+
+{{!Html_sigs.T.title}title} is an example of a [unary]
+combinator.
+{@ocaml[
+let mytitle = title (txt "A Fabulous Web Page")
+]}
+
+{3:type-errors Interlude about type errors}
+
+However, what would happen if we were to try to put {b bold} text in our title? This is not specification-compliant! Let's try it.
+{@ocaml[
+let mytitle = title (b [txt "A Bold Web Page"])
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+Error: This expression has type ([> Html_types.b ] as 'a) elt
+ but an expression was expected of type
+ ([< Html_types.title_content_fun ] as 'b) elt = 'b elt
+ Type 'a = [> `B ] is not compatible with type 'b = [< `PCDATA ]
+ The second variant type does not allow tag(s) `B
+]}
+
+As expected, this code does not type-check!
+The type checker is unfortunately a bit unclear about the source of the error.
+
+It tells us that the given expression has type [[> b] elt]
+(indeed, it is produced by a [b] combinator)
+but an expression is expected of type [[< title_content_fun] elt]
+(which means that is is used as content for a [title] element).
+It then tells us that, since [[> b] = [> `B]] and
+[[< title_content_fun] = [< `PCDATA ]],
+[`B] is not allowed inside a [title].
+
+In order to get reasonable type errors with TyXML, The [-short-paths] option should always be used when invoking OCaml.
+
+{3:finishing Finishing up the webpage}
+
+To finish our webpage, we use {{!Html_sigs.T.body}body}, {{!Html_sigs.T.head}head} and {{!Html_sigs.T.html}html}.
+The last two combinators have special types due to their specific constraints: {{!Html_sigs.T.head}head} requires only one [title] child, and {{!Html_sigs.T.html}html} requires exactly two children: [head] and [body].
+
+{@ocaml[
+let mypage =
+ html
+ (head mytitle [])
+ (body [mycontent])
+]}
+
+If you are using {{:../../eliom/latest/clientserver-html.html}Eliom}
+or {{!Js_of_ocaml_tyxml.Tyxml_js}Js_of_ocaml},
+this is the end of TyXML's territory. However, for standalone use, we now need to print our document as an HTML file. The standalone implementation comes with a printer, {!Tyxml.Html.pp}, that we can use to print files:
+
+{@ocaml[
+let () =
+ let file = open_out "index.html" in
+ let fmt = Format.formatter_of_out_channel file in
+ pp () fmt mypage;
+ close_out file
+]}
+
+You could also print directly to a string:
+{@ocaml[
+let s = Format.asprintf "%a" (Html.pp ()) mypage
+]}
+
+Well done, you know have a very minimal (but fabulous) website! Once again, the implementation can be found {{:https://github.com/ocsigen/tyxml/tree/master/examples/mini_website}here}.
+
+Other examples are available in the {{:https://github.com/ocsigen/tyxml/tree/master/examples/}examples} directory.
+
+{1:custom Using your own underlying implementation}
+
+You can use TyXML with any underlying implementation. In order to do so, TyXML provides a set of functors.
+Please consult {{!page-"functors"}the relevant manual}.
diff --git a/docs/jsx.mld b/docs/jsx.mld
new file mode 100644
index 000000000..a8187a4f8
--- /dev/null
+++ b/docs/jsx.mld
@@ -0,0 +1,106 @@
+{0 TyXML JSX syntax}
+
+TyXML provides a ppx which allows use Reason's convenient JSX syntax
+while still enjoying the benefits and type-safety of TyXML.
+It is available as the [tyxml-jsx] package:
+{[
+opam install tyxml-jsx
+]}
+
+{1:syntax Syntax}
+
+This syntax should be familiar to most Reason users:
+
+{@ocaml[
+# open Tyxml;
+
+# let intro = "Hello!" ;
+let intro: Tyxml_html.elt([> Html_types.span ]) =
+ Hello!;
+
+# let svgpath = ;
+let svgpath: Tyxml_svg.elt([> Svg_types.path ]) =
+ ;
+]}
+
+The quotations will use the [Html] and [Svg] module that are available in scope, but it can also be disambiguated manually by prefixing the namespace.
+
+We can also build list of elements:
+
+{@ocaml[
+# let nice_picture = <>
];
+]}
+
+Finally, we can insert custom elements:
+{@ocaml[
+# module Page = {
+ let createElement = (~title, ~children, ()) => {
+
+
+ {Html.txt(title)}
+
+
+ ...children
+
+ };
+};
+module Page:
+ {
+ let createElement:
+ (~title: string,
+ ~children: list(Html.elt([< Html_types.object__content_fun ])),
+ unit) => Html.doc;
+ };
+
+# let this_title = "Weeee";
+# let page =
+
+ ...content
+ ;
+let page: Html.doc = ....
+]}
+
+In the previous examples, we used the textual implementation of tyxml (module [Tyxml]) in the [tyxml] library), but it works equally well
+with other implementations:
+- The DOM tree with the [js_of_ocaml-tyxml] library. Either the {!Js_of_ocaml_tyxml.Tyxml_js} or the reactive
+version in {!Js_of_ocaml_tyxml.Tyxml_js.R}.
+- The [virtual_dom] library with the module [Virtual_dom.Tyxml].
+
+In each case, simply open the module and the JSX syntax will use it.
+
+{1:unsafe "Unsafe" attributes}
+
+Some Javascript libraries and frameworks depend upon HTML markup that includes non-standard
+attributes, which tyxml would usually reject. When constructing elements using tyxml's API directly,
+the available workaround is to use the {{!Html_sigs.T.Unsafe}Unsafe} constructors.
+The same relaxed semantics can be had when using JSX by prefixing non-standard attribute names
+with a leading underscore.
+
+So, while this will fail:
+{@ocaml[
+let button =
+]}
+
+This will not:
+{@ocaml[
+let button =
+]}
+
+Such underscore-prefixed attributes are presumed to be strings, and are constructed using e.g.
+{{!Html_sigs.T.Unsafe.string_attrib}Unsafe.string_attrib}.
+
+{1:tips Tips}
+
+It can sometime be necessary to disable the JSX syntax. For that purpose, simply use the toogle `[@tyxml.jsx false]` to turn the JSX on and off.
diff --git a/docs/ppx.mld b/docs/ppx.mld
new file mode 100644
index 000000000..6f5979219
--- /dev/null
+++ b/docs/ppx.mld
@@ -0,0 +1,118 @@
+{0 TyXML Ppx syntax extension}
+
+TyXML provides a ppx to allow writing HTML and SVG documents
+in the standard syntax while still enjoying the benefits and type-safety of TyXML.
+It is available as the [tyxml-ppx] package:
+{[
+opam install tyxml-ppx
+]}
+
+{1:syntax Syntax}
+
+The ppx provides two quotations, [html] and [svg] (and two aliases, [tyxml.html] and [tyxml.svg]).
+
+{@ocaml[
+open Tyxml ;;
+
+let content = [%html{|
some content
|}] ;;
+val content : [> Html_types.div ] Html.elt
+
+let svgpath = [%svg{||}] ;;
+val svgpath : [> Svg_types.path ] Svg.elt
+]}
+
+The quotations will use the [Html] and [Svg] module that are available in scope. To use another module name, you can qualify the quotation:
+
+{@ocaml[
+let content = [%html.F {|
some content
|}]
+val content : [> Html_types.div ] F.elt
+]}
+
+A quotation containing multiple elements will automatically produce a list of elements:
+
+{@ocaml[
+let my_text = [%html
+ {|This is an HTML formated content.|}]
+val my_text : [> `B | `I | `PCDATA ] Html.elt list
+]}
+
+The produced elements can be used inside usual TyXML combinators:
+{@ocaml[
+let my_span = Html.(span ~a:[a_class ["mytext"]] my_text)
+val my_span : [> Html_types.span ] Html.elt
+]}
+
+It is also possible to use tyxml elements inside quotations using [antiquotations]:
+
+{@ocaml[
+let my_paragraphs =
+ [%html "
"[my_span]"
more content
"]
+val my_paragraphs : [> Html_types.p ] Html.elt list
+]}
+
+Note here that since [p] expects a list of children (it's a {{!Html_sigs.T.star}star} element), the antiquotation must be of type list, hence the use of [[], []].
+
+It is also possible to use antiquotations for attributes.
+{@ocaml[
+let my_id = "thediv"
+let my_div = [%html ""]
+val my_div : [> Html_types.div ] Html.elt
+]}
+
+{1:unsafe "Unsafe" attributes}
+
+Some Javascript libraries and frameworks depend upon HTML markup that includes non-standard
+attributes, which tyxml would usually reject. When constructing elements using tyxml's API directly,
+the available workaround is to use the {{!Html_sigs.T.Unsafe}Unsafe} constructors.
+The same relaxed semantics can be had when using the ppx by prefixing non-standard attribute names
+with a leading underscore.
+
+So, while this will fail:
+{@ocaml[
+[%html {||}]
+]}
+
+This will not:
+{@ocaml[
+[%html {||}]
+]}
+
+Such underscore-prefixed attributes are presumed to be strings, and are constructed using e.g.
+{{!Html_sigs.T.Unsafe.string_attrib}Unsafe.string_attrib}.
+
+{1:let Let notation}
+
+It is also possible to use the ppx with the [let] notation:
+{@ocaml[
+let%html content = {|
some content
|} ;;
+val content : [> Html_types.div ] Html.elt
+]}
+
+All the capabilities provided by the ppx are still available with this form. Additionally, the modifiers [and] or [rec] are available. It is also possible to create functions:
+{@ocaml[
+let%html make_content id = "
some content
" ;;
+val make_content : string -> [> Html_types.div ] Html.elt
+]}
+
+{1:notes Notes}
+
+{2 Locations}
+
+Due to the code transformations done by the ppx, proper locations are difficult to provide.
+Please report examples of badly located code on {{:https://github.com/ocsigen/tyxml/issues}the bug tracker}.
+
+{2 Composability}
+
+Due to various reasons, some [HTML] can not be composed properly using the ppx. For example, this will result in an error:
+
+{@ocaml[
+let my_title = [%html "The title"]
+let my_head = [%html ""my_title""]
+]}
+
+You can, however, inline the title element inside the head element:
+
+{@ocaml[
+let my_title = [%html "The title"]
+let my_head = [%html ""my_title""]
+]}
diff --git a/implem/tyxml.ml b/implem/tyxml.ml
index 2468bb31a..1e6b6618b 100644
--- a/implem/tyxml.ml
+++ b/implem/tyxml.ml
@@ -3,7 +3,7 @@
This is the natural implementation of the TyXML combinators
based on an XML data-structure.
{%
- Other implementations are available, see <> for details. %}
+ Other implementations are available, see {{!page-"intro"}the manual} for details. %}
*)
(** Typesafe constructors and printers for HTML documents.
diff --git a/lib/html_f.mli b/lib/html_f.mli
index 156e5be3a..f0a17a05f 100644
--- a/lib/html_f.mli
+++ b/lib/html_f.mli
@@ -21,7 +21,7 @@
(** Typesafe constructors for HTML documents (Functorial interface)
- {% See <>. %}
+ {% See {{!page-"functors"}the manual of the functorial interface}. %}
*)
(** Create a new implementation of [HTML], using the given underlying [Xml]
diff --git a/lib/html_sigs.mli b/lib/html_sigs.mli
index b357c74e8..7abf97737 100644
--- a/lib/html_sigs.mli
+++ b/lib/html_sigs.mli
@@ -1184,7 +1184,7 @@ module type NoWrap = T with module Xml.W = Xml_wrap.NoWrap
(** {2 Signature functors}
- {% See <>. %} *)
+ {% See {{!page-"functors"}the manual of the functorial interface}. %} *)
(** Signature functor for {!Html_f.Make}. *)
module Make
diff --git a/lib/svg_f.mli b/lib/svg_f.mli
index 6ca0a3949..882591792 100644
--- a/lib/svg_f.mli
+++ b/lib/svg_f.mli
@@ -23,7 +23,7 @@
and the interface is very low level and do not take deeply into account
the needs of SVG elements.
- {% See <>. %}
+ {% See {{!page-"functors"}the manual of the functorial interface}. %}
*)
(*
diff --git a/lib/svg_sigs.mli b/lib/svg_sigs.mli
index 8b523a3a9..4fb026ae2 100644
--- a/lib/svg_sigs.mli
+++ b/lib/svg_sigs.mli
@@ -1053,7 +1053,7 @@ module type NoWrap = T with module Xml.W = Xml_wrap.NoWrap
(** {2 Signature functors}
- See {% <> %}. *)
+ See {{!page-"functors"}the manual of the functorial interface}. *)
(** Signature functor for {!Svg_f.Make}. *)
module Make (Xml : Xml_sigs.T) : sig
diff --git a/ppx/tyxml_ppx.mli b/ppx/tyxml_ppx.mli
index 867e1b2e8..e5414d896 100644
--- a/ppx/tyxml_ppx.mli
+++ b/ppx/tyxml_ppx.mli
@@ -21,7 +21,7 @@
This is the documentation for the internal ppx library.
{% Documentation for the ppx itself is available
- <>. %}
+ {{!page-"ppx"}here}. %}
*)
type lang = Html | Svg
From 089faa56355909e0ee2d2a97a306ae2ebc1e8564 Mon Sep 17 00:00:00 2001
From: Vincent Balat
Date: Sun, 7 Jun 2026 13:16:23 +0200
Subject: [PATCH 2/4] doc: build the themed site with wodoc (doc/wodoc + CI on
master); retire wikidoc
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Add a declarative doc/wodoc config + doc/ README and a CI (doc.yml) that builds &
deploys via `wodoc build`. The CI triggers ONLY on master (push -> dev) plus a
manual dispatch for releases (e.g. ref=latest-mli-odoc, label=4.6.0, set_latest;
the version-independent doc sources are overlaid from master) — so pushing a
branch never deploys, and the site still shows dev + the released version. Drop
the legacy ocamldoc/wikidoc machinery (docs/Makefile, docs/indexdoc,
docs/manual-wiki/, the Makefile wikidoc target).
---
.github/workflows/doc.yml | 96 +++++++++++++++++++++
Makefile | 24 +-----
doc/README.md | 56 ++++++++++++
doc/wodoc | 21 +++++
docs/Makefile | 38 --------
docs/indexdoc | 32 -------
docs/manual-wiki/functors.wiki | 114 ------------------------
docs/manual-wiki/intro.wiki | 153 ---------------------------------
docs/manual-wiki/jsx.wiki | 106 -----------------------
docs/manual-wiki/menu.wiki | 6 --
docs/manual-wiki/ppx.wiki | 118 -------------------------
11 files changed, 175 insertions(+), 589 deletions(-)
create mode 100644 .github/workflows/doc.yml
create mode 100644 doc/README.md
create mode 100644 doc/wodoc
delete mode 100644 docs/Makefile
delete mode 100644 docs/indexdoc
delete mode 100644 docs/manual-wiki/functors.wiki
delete mode 100644 docs/manual-wiki/intro.wiki
delete mode 100644 docs/manual-wiki/jsx.wiki
delete mode 100644 docs/manual-wiki/menu.wiki
delete mode 100644 docs/manual-wiki/ppx.wiki
diff --git a/.github/workflows/doc.yml b/.github/workflows/doc.yml
new file mode 100644
index 000000000..a1fcb6c1a
--- /dev/null
+++ b/.github/workflows/doc.yml
@@ -0,0 +1,96 @@
+# Build the TyXML documentation (manual + API) with odoc, theme it with the
+# Ocsigen chrome (wodoc), and publish it on the project's gh-pages branch, served
+# at https://ocsigen.org/tyxml/. See doc/README.md for the full picture.
+#
+# - push to master -> rebuild and deploy the "dev" docs.
+# - manual dispatch -> build any version and optionally make it "latest" (e.g.
+# ref = latest-mli-odoc, label = 4.6.0, set_latest = true).
+# The doc sources are version-independent, so they are
+# overlaid from master for a ref that predates the migration.
+#
+# Historical version directories already on gh-pages are PRESERVED: each run only
+# replaces its own
more content
"]
-val my_paragraphs : [> Html_types.p ] Html.elt list
->>
-
-Note here that since ##p## expects a list of children (it's a <> element), the antiquotation must be of type list, hence the use of ##[##, ##]##.
-
-It is also possible to use antiquotations for attributes.
-<"]
-val my_div : [> Html_types.div ] Html.elt
->>
-
-==@@id="unsafe"@@ "Unsafe" attributes
-
-Some Javascript libraries and frameworks depend upon HTML markup that includes non-standard
-attributes, which tyxml would usually reject. When constructing elements using tyxml's API directly,
-the available workaround is to use the <> constructors.
-The same relaxed semantics can be had when using the ppx by prefixing non-standard attribute names
-with a leading underscore.
-
-So, while this will fail:
-<Click Me|}]
->>
-
-This will not:
-<Click Me|}]
->>
-
-Such underscore-prefixed attributes are presumed to be strings, and are constructed using e.g.
-<>.
-
-==@@id="let"@@ Let notation
-
-It is also possible to use the ppx with the ##let## notation:
-<some content|} ;;
-val content : [> Html_types.div ] Html.elt
->>
-
-All the capabilities provided by the ppx are still available with this form. Additionally, the modifiers ##and## or ##rec## are available. It is also possible to create functions:
-<some content" ;;
-val make_content : string -> [> Html_types.div ] Html.elt
->>
-
-==@@id="notes"@@ Notes
-
-=== Locations ===
-
-Due to the code transformations done by the ppx, proper locations are difficult to provide.
-Please report examples of badly located code on [[https://github.com/ocsigen/tyxml/issues|the bug tracker]].
-
-=== Composability ===
-
-Due to various reasons, some ##HTML## can not be composed properly using the ppx. For example, this will result in an error:
-
-<The title"]
-let my_head = [%html ""my_title""]
->>
-
-You can, however, inline the title element inside the head element:
-
-<"my_title""]
->>
From 5b055288f8320d674d248a2577481f34da76fc09 Mon Sep 17 00:00:00 2001
From: Vincent Balat
Date: Sun, 7 Jun 2026 14:03:54 +0200
Subject: [PATCH 3/4] doc(ci): deploy only the dev docs, only from master
Drop the workflow_dispatch release path (label/ref/set_latest), the
overlay-from-master step and the repoint-latest step. Stable versions are
built by hand and committed to gh-pages at release time, so CI now only
rebuilds and deploys the dev docs on a push to master. README updated to match.
---
.github/workflows/doc.yml | 62 +++++++--------------------------------
doc/README.md | 16 +++++-----
2 files changed, 17 insertions(+), 61 deletions(-)
diff --git a/.github/workflows/doc.yml b/.github/workflows/doc.yml
index a1fcb6c1a..628ab497c 100644
--- a/.github/workflows/doc.yml
+++ b/.github/workflows/doc.yml
@@ -2,33 +2,16 @@
# Ocsigen chrome (wodoc), and publish it on the project's gh-pages branch, served
# at https://ocsigen.org/tyxml/. See doc/README.md for the full picture.
#
-# - push to master -> rebuild and deploy the "dev" docs.
-# - manual dispatch -> build any version and optionally make it "latest" (e.g.
-# ref = latest-mli-odoc, label = 4.6.0, set_latest = true).
-# The doc sources are version-independent, so they are
-# overlaid from master for a ref that predates the migration.
-#
-# Historical version directories already on gh-pages are PRESERVED: each run only
-# replaces its own / directory.
+# CI deploys ONLY the "dev" docs and ONLY from master. Stable versions are built
+# by hand and committed to gh-pages at release time, so there is no dispatch /
+# release path here. Each run replaces only the dev/ directory; the other version
+# directories already on gh-pages are PRESERVED.
name: Documentation
on:
push:
branches: [master]
- workflow_dispatch:
- inputs:
- label:
- description: "Version label / output directory (e.g. dev, 4.6.0)"
- required: true
- default: dev
- ref:
- description: "Git ref to build (branch or tag). Defaults to the label."
- required: false
- default: ""
- set_latest:
- description: "Also repoint 'latest' at this build"
- type: boolean
- default: false
+ workflow_dispatch: {}
jobs:
build-deploy:
@@ -36,22 +19,9 @@ jobs:
runs-on: ubuntu-latest
permissions:
contents: write
- env:
- LABEL: ${{ github.event.inputs.label || 'dev' }}
steps:
- name: Checkout
uses: actions/checkout@v4
- with:
- ref: ${{ github.event.inputs.ref || github.event.inputs.label || github.sha }}
- fetch-depth: 0
-
- # A ref that predates this migration has no doc/ infra; the doc sources are
- # version-independent, so overlay them from master.
- - name: Overlay doc sources from master (pre-migration refs)
- if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.ref != '' && github.event.inputs.ref != 'master' }}
- run: |
- git fetch origin master
- git checkout origin/master -- doc docs
- name: Set up OCaml
uses: ocaml/setup-ocaml@v3
@@ -66,31 +36,19 @@ jobs:
opam pin add -n wodoc https://github.com/ocsigen/wodoc.git
opam install wodoc
- - name: Build documentation
+ - name: Build documentation (dev)
# wodoc build runs `dune build @doc --profile release`, assembles the
# themed site from doc/wodoc, and fetches the shared menu from its single
# canonical copy in ocsigen.github.io.
run: >-
opam exec -- wodoc build --config doc/wodoc
--menu https://ocsigen.org/wodoc/menu.html
- --out "$PWD/_doc-site/$LABEL" --label "$LABEL"
+ --out "$PWD/_doc-site/dev" --label dev
- - name: Deploy to gh-pages (replace only this version, keep the rest)
+ - name: Deploy to gh-pages (replace only dev/, keep the rest)
uses: JamesIves/github-pages-deploy-action@v4
with:
branch: gh-pages
- folder: _doc-site/${{ env.LABEL }}
- target-folder: ${{ env.LABEL }}
+ folder: _doc-site/dev
+ target-folder: dev
clean: true
-
- - name: Repoint 'latest' (releases)
- if: ${{ github.event.inputs.set_latest == 'true' }}
- run: |
- tmp="$(mktemp -d)"
- git clone --depth 1 --branch gh-pages \
- "https://x-access-token:${{ github.token }}@github.com/${{ github.repository }}.git" "$tmp"
- ln -sfn "$LABEL" "$tmp/latest"
- git -C "$tmp" add latest
- git -C "$tmp" -c user.email=ci@ocsigen.org -c user.name="Ocsigen CI" \
- commit -m "doc: latest -> $LABEL" || true
- git -C "$tmp" push
diff --git a/doc/README.md b/doc/README.md
index 384db504c..8c2a7e77a 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -42,15 +42,13 @@ offline; `wodoc build` then prints the exact command to serve it.
[`.github/workflows/doc.yml`](../.github/workflows/doc.yml) builds and publishes
to the project's **`gh-pages`** branch (served at `ocsigen.org/tyxml/`). The CI
-triggers **only on `master`** (and manual dispatch), so pushing a feature/doc
-branch never deploys:
+triggers **only on `master`**, so pushing a feature/doc branch never deploys:
-- **push to `master`** → rebuilds and deploys the **`dev`** docs
+- **push to `master`** → rebuilds and deploys the **`dev`** docs only
(`ocsigen.org/tyxml/dev/`).
-- **manual run** (Actions → *Documentation* → *Run workflow*) → builds any version
- and optionally makes it `latest`. For the current release: *label* = `4.6.0`,
- *ref* = `latest-mli-odoc` (the released API with its odoc doc-comments), and tick
- *set_latest*. The version-independent doc sources are overlaid from `master`.
-Each run replaces only its own `/` directory; the other version directories
-already on `gh-pages` are preserved.
+Stable versions (e.g. `4.6.0`, built from the released API at `latest-mli-odoc`)
+are **not** built by CI: they are generated by hand and committed to `gh-pages`,
+with the `latest` symlink repointed, at release time. Each CI run replaces only
+the `dev/` directory; the other version directories already on `gh-pages` are
+preserved.
From 51be39cde9bbb6ab887a742af43b9a69a0e2df0c Mon Sep 17 00:00:00 2001
From: Vincent Balat
Date: Mon, 8 Jun 2026 16:27:10 +0200
Subject: [PATCH 4/4] doc(ci): drop redundant 'opam install odoc' (odoc is a
with-doc dep)
opam install . --deps-only --with-doc already pulls odoc, so the explicit
install was a no-op. Spotted by @raphael-proust in review (ocsigen/lwt#1109).
---
.github/workflows/doc.yml | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/.github/workflows/doc.yml b/.github/workflows/doc.yml
index 628ab497c..b3bdbdeea 100644
--- a/.github/workflows/doc.yml
+++ b/.github/workflows/doc.yml
@@ -30,8 +30,7 @@ jobs:
- name: Install dependencies
run: |
- opam install . --deps-only --with-doc
- opam install odoc
+ opam install . --deps-only --with-doc # odoc is a with-doc dep
# wodoc: the odoc driver that themes the pages with the Ocsigen chrome.
opam pin add -n wodoc https://github.com/ocsigen/wodoc.git
opam install wodoc