A preview of DocC's support for combined documentation

Hello,

I'm happy to let you know that there's now enough pieces of combined documentation of multiple targets in place across Swift-DocC, Swift-DocC Render, and the Swift-DocC Plugin to enable you to try out an early, and still experimental, version of this feature for the use-case of hosting combined documentation for a Swift package together.

I've created a tiny Swift package example that hosts its combined documentation on GitHub Pages as a preview of what's supported today with the latests Swift-DocC Plugin version (1.4.2) and the Swift-DocC and Swift-DocC Render in the latest main Swift development toolchain. Below is a screenshot of one of those pages:

A few things you may notice on this page:

  • The navigation hierarchy on the left-hand side displays the hierarchies for both targets in this package. This means that Quick Navigation—activated via the / key/button—can navigate to pages in both targets.
  • The top of the navigation hierarchy, displays the name of this Swift package. Clicking on this element takes you to a minimal synthesized landing page for these targets. The same page can be reached at /documentation.
  • The content of this page (from the "Outer" target) contains authored symbol links to pages from the "Inner" target. These symbol links are written with a leading slash followed by the module name and the rest of the path to the symbol. For example: /Inner/InnerClass.
  • The "Inherits From" and "Conforms To" symbol relationships are automatically turned into links to those symbols (from the other target). If you navigate to someProperty or someFunction() you may also notice that the InnerValue elements in their declarations is a link to that page.

Try this yourself

Building combined documentation for multiple targets with the Swift-DocC Plugin is easy. When you add the new --enable-experimental-combined-documentation flag to your existing swift package generate-documentation call and specify one or more targets using --target <target-name> or --product <product-name> , the plugin will build all the specified targets, configure them to support linking to their target dependencies' documentation, and output a documentation archive with their combined documentation.

If you're using a main Swift development toolchain you'll get a combined navigation hierarchy and a minimal synthesized landing page, like in the example above. You can download a Swift toolchain for your platform and follow the instructions on that page or use the Swiftly CLI to install a Swift development toolchain. You can use the development toolchain to build documentation with the Swift-DocC plugin by passing the toolchain path to the swift package CLI:

swift package                                   \
  --toolchain /path/to/the-toolchain            \
  generate-documentation                        \
  --enable-experimental-combined-documentation  \
  ...

You can also use a Swift 6.0 toolchain but if you do, each target will get a separate navigation hierarchy and you won't have any landing page. You can still write links from targets to their dependencies and symbol relationships are still automatically turned into links. Note: the Swift 6.0 toolchain won't receive any further updates related to combined documentation.

We'd love for you to try this on your Swift packages and give us your feedback on what's already there, suggest enhancements, and help us find bugs. Note that this feature is experimental and may receive breaking changes.

One feature that's mentioned in the combined documentation of multiple targets thread which isn't available yet is the ability to write your own landing page and package-level articles (that aren't about to any particular target). We have some ideas for how we want to do this but we want to discuss those ideas—in a separate, future Forums thread—and wanted to let you try out what's already available before that.

17 Likes

I have trouble getting some backreferences to work.

I made these changes to this example project, and then compiled the web documentation.

jaanus@jk-mbp ~/D/combined-documentation-example (main)> git diff
diff --git a/Sources/Inner/Inner.swift b/Sources/Inner/Inner.swift
index 2741ffe..4724b64 100644
--- a/Sources/Inner/Inner.swift
+++ b/Sources/Inner/Inner.swift
@@ -23,6 +23,8 @@
 public protocol InnerProtocol {}
 
 /// A class in the inner target.
+///
+/// See also OuterClass: ``/Outer/OuterClass``
 open class InnerClass {}
 
 /// A public value in inner target.
diff --git a/Sources/Outer/Outer.docc/Outer.md b/Sources/Outer/Outer.docc/Outer.md
index 765cd01..9eb594b 100644
--- a/Sources/Outer/Outer.docc/Outer.md
+++ b/Sources/Outer/Outer.docc/Outer.md
@@ -1,3 +1,5 @@
 # ``Outer``
 
 The outer module of this combined-documentation example.
+
+See also the Inner class: ``/Inner/InnerClass``

The change to Outer is rendered as expected:

However, the change to InnerClass is not rendered as expected:

Could this be because Outer has Inner defined as a dependency in Package.swift, but not vice versa? So the Inner documentation generation does not have access to Outer symbols, and thus won’t render links to those.

Correct. Each target's documentation can only link to its direct dependencies' documentation. This is described in the original pitch post and there's also a section that talks about what some of the tradeoffs would be of allowing bidirectional links and how that creates an inconsistent experience if one of the targets can be imported individually.

Note that you wouldn't be able to configure the "Inner" target to depend on the "Outer" target because the code doesn't allow for cyclic dependencies (not documentation specific).

1 Like

We've yet to see many practical use cases for linking to specific symbols "up" the documentation hierarchy (for example inferred from the target's source dependency hierarchy). A few examples have been brought up but we felt that there were better ways to write that documentation which didn't involve bidirectional links. For example:

  • The inner target that wants to point to an example implementation for conforming to a protocol.

    Here the conforming type's documentation would repeat the protocol's documentation. It would likely be more helpful to the reader to include specific implementation examples as inline code blocks or link directly to the relevant source code of the conforming type, rather than its documentation. Because that wouldn't link to documentation, it would be a regular https link and not a symbol link in DocC.

  • A package's top-level documentation wants to point to a list of consumers.

    This documentation is most likely to only link to the top-level pages of the consuming packages, not individual pages in their documentation. In this case, cloning and building the consuming packages documentation for a single top-level link may not be worth it compared to a regular https link.

  • Documentation about how to use an outer symbol and an inner symbol together.

    If this documentation is a bit higher level and/or covers more than a single symbol in either target, that could be an indication that it's better suited for an article than as documentation for any specific symbol. That article could exist in:

    • the outer target
    • the inner target
    • package-level documentation

    An article in the inner target has the same problems with bidirectional links as a symbol in the inner target would. However, both the outer target and the package-level documentation are able to link to symbol in both targets. Either the outer target or the package-level documentation is also likely a better conceptual place for this documentation.

    If a consumer of the package is able to import only the inner target, it's also a bit odd if that target has documentation about API that's not available to the consumer.

    On the other hand, if this documentation is very specific to a symbol in either the outer target or the inner target, that could be an indication that it's better suited as documentation for that specific symbol, rather than an article.

    If it's specific to the outer API, then the documentation can link to documentation for both targets. If, however, it's specific to the inner API, then it can't link to the outer API.

    One key question to ask here is if a consumer can import only the inner target. If they can, then the documentation would probably be more helpful to them if it focused on how the consumer's own code needs to interact with the inner API, drawing from the outer API's implementation as code examples (the first example use case)

  • Packages with one or more example projects

    Currently, the preferred way for packages with dedicated example projects to document those example projects is to write a single article that describe what that example project showcases, any specific configuration or setup needed to run it, and possibly describe some of its implementation. This article would use a @PageKind directive to change its style from an "article" to a "sample code page" and a @CallToAction button to give readers a prominent button to check out that example project.

    In this case the article is a part of the target's documentation so links to the target's pages are local. In this case there are no other pages specific to the example project to link to.

    We can see a possible future use case for larger example projects that have many pages of documentation about the example itself, maybe even documentation about its individual symbols. However, we consider this use case to be slightly different from linking between other targets in a package because the example project is not "API" for the consumer to use. This has a couple of implications:

    • It's would "pollute" the navigation hierarchy and features like Quick Navigation if symbols from the example project displayed alongside the API documentation.
    • The example project is likely to link to many pages in the target/package documentation but the only page that has a reason to link to specific symbols in the example project's documentation is the example project's "sample code page" with the call-to-action.

    This means that there are additional aspects about combined documentation—other than unidirectional links—that isn't a good for example projects.

    We don't have any specific plans for large example projects with their own documentation hierarchies, but if we were to consider it in the future it's possible that we would treat it like it as a special case with multiple unique behaviors that don't apply to "general" combined documentation.

2 Likes