- Introduction to Writing Specifications with Bikeshed
- Related Resources
- CSS for domintro
- This document: Introduction to Writing Specifications with Bikeshed
- Source: bikeshed-intro/index.md at updates · dlaliberte/bikeshed-intro
- Issues: dlaliberte/bikeshed-intro/issues
As the Bikeshed Documentation says: "Bikeshed is a spec-generating tool that takes in lightly-decorated Markdown and spits out a full spec, with cross-spec autolinking, automatic generation of indexes/ToC/etc, and many other features."
This document is intended as a simple introduction to writing specifications of web APIs using Bikeshed. Very extensive documentation of Bikeshed is available, but it is a complex tool and there is a lot to learn before one can become productive.
So the goal here is to help beginners get started using Bikeshed in the simplest way possible. And then we provide some guidance for starting to write the first few revisions of a specification. We only cover the basics for many topics and refer to other documents for more complete details. The following topics are covered here:
- Installing Bikeshed, Running and Publishing
- A Strategy for Incremental Development of your specification
You can create a repository for your specification in a variety of ways. This guide will start with having you create a directory in your local work environment and set up the required tools. In a shell window, do the following, replacing "widgets" with the name of your spec:
mkdir widgets
cd widgets
If it is not already available on your development platform, Download and install Node.js. Node will also be used to help setup your development environment and can be used to run visual tests. This will also install the npx
command which will be used next to create the initial repository files.
Maybe run npm init
which will prompt you to provide some information about your project, such as the project name, version, description, entry point, etc. Then it will create a package.json
file.
- Install pnpm:
npm install pnpm
- Install
vnu-jar
:pnpm add vnu-jar
You should also install git, if it is not already available.
To easily create and initialize a new repository with content, first create a new directory (e.g. my-awesome-api
) and cd
into it. Then run the following:
npx wicg init "My Awesome API"
This command will ask you for meta-level details about your specification, which you can change later. You should select 'bikeshed' as the spec preprocesor so that it will generate an index.bs
file, and it will also create the following files as the initial content for your :
CONTRIBUTING.md
index.bs
-LICENSE.md
.pr-preview.json
README.md
- an explainer for your spec
The above npx
command created an initial index.bs
file. Alternatively, you can copy this Minimal template, or you can run bikeshed template > index.bs
to generate the same template. Here is what the bikeshed generated template looks like:
<pre class='metadata'>
Title: Your Spec Title
Shortname: your-spec
Level: 1
Status: w3c/UD
Group: WGNAMEORWHATEVER
URL: https://dlaliberte.github.io/bikeshed-examples/template.html
Editor: Your Name, Your Company http://example.com/your-company, [email protected], http://example.com/your-personal-website
Abstract: A short description of your spec, one or two sentences.
</pre>
Introduction {#intro}
=====================
Introduction here.
The Bikeshed Documentation: Installation provides details on several different ways for how to install and run Bikeshed. We recommend you install it on a local machine so you can run it multiple times as you make changes to the specification.
- Make sure you have Python 3.7 or later.
- Install Bikeshed with pip3, if you can.
pip3 install bikeshed
bikeshed update
- Alternatively, you can use pip or pipenv, but see details at: Bikeshed Documentation - Installing Bikeshed Itself
- You don't need to install "Bikeshed for Development", unless you are planning to do development on Bikeshed itself.
The index.bs
template created above will contain only a "metadata"
section and an Introduction, but when you run bikeshed spec index.bs
, the generated index.html
file will include the following sections (plus two added sections which are required in all specifications).
You can see what this looks like an an html page at Bikeshed spec template - using the default template.
-
About this specification:
- Title: From the metadata.
- This version: The URL you provide in metadata.
- Editor: The name and links you provide in metadata.
- Copyright: Generated for you.
-
Abstract: From the metadata.
-
Status of this document: From the metadata.
-
Table of Contents: Generated automatically from sections.
-
Introduction: This section provides an overview of the purpose and scope of the standard.
-
Security considerations: Add this section, which describes security issues related to the standard and provides guidance on how to mitigate them.
-
Privacy considerations: Add this section, which describes privacy issues related to the standard and provides guidance on how to address them.
-
Conformance: This section describes how to conform to the standard and specifies the requirements for conformance.
-
Document conventions: Generated content.
-
Conformant Algorithms: Generated content.
-
-
References:
- Normative References: This section lists the other standards and specifications that are referenced within the document and are required for implementation of the standard.
Assuming you will be using GitHub to develop and provide public access to your specification, you should first decide whether you will use an existing repo or create a new repo to serve as the "publishing source" repo for your specification. You'll need to be an admin for this source repo.
It is also convenient to set up a "GitHub Pages site" and actions which will automatically publish and serve the html file that is generated by Bikeshed after each change of your source file. To do that, make the following changes.
-
Follow instructions for Publishing with a custom GitHub Actions workflow. I.e. go to "Settings" > "Pages" to show "GitHub Pages"
-
Under "Build and deployment" > "Source", select "Deploy from a branch", if not already selected.
-
Under "Settings" > "Actions",
- Click "Set up a workflow yourself"
- Enter the name of the file (after
.github/workflows/
) tobuild.yml
orsome-other.yml
. - Use the following script, changing
index.bs
andindex.html
if your name is different. - For more sample workflow actions, see Spec Prod Documentation
- Enter the name of the file (after
- Click "Set up a workflow yourself"
name: Build
on:
pull_request: {}
push:
branches:
- main
permissions:
contents: write
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: w3c/spec-prod@v2
with:
TOOLCHAIN: bikeshed
SOURCE: index.bs
DESTINATION: index.html
GH_PAGES_BRANCH: gh-pages
BUILD_FAIL_ON: warning
- Optional Configurations
- Maybe turn on GitHub Apps - PR Preview for any repo that is not one of the blanket-installed orgs listed there. If you used the
npx
command above, the configuration file will be installed for you. - Visit https://github.com/settings/installations/262076.
- Under "Repository access", select either "All repositories" or use the "Select repositories" button to pick your new bikeshed repository.
- Click Save.
- Maybe turn on GitHub Apps - PR Preview for any repo that is not one of the blanket-installed orgs listed there. If you used the
Now you can repeat the following development cycle:
- Edit your spec source, e.g.
index.bs
, locally, run Bikeshed withbikeshed spec index.bs
and preview the resulting html in your browser. Fix any errors until satisfied. - Commit and sync updates to your spec source to a GitHub PR, which should then trigger the action that updates
index.html
in GitHub Pages.- If your repo is
github.com/owner/repo-name
, then your html file will be atowner.github.io/repo-name/index.html
.
- If your repo is
Bikeshed uses a Markdown variant called Bikeshed-flavored Markdown (BSMD). By default, Bikeshed recognizes all of the "block-level" Markdown constructs defined by CommonMark, except for indented code blocks. You can freely switch back and forth between Markdown and HTML as needed, but you should prefer to use Markdown where it is available.
In web browsers, a JavaScript function call with JS data parameters cause the browser to call an internal API function, typically implemented in C++. The result of the call, if any, is then returned as a JavaScript value. Web standards specify these function interfaces in WebIDL, and to further clarify the meaning of each function, we specify the algorithm for the function in terms of WebIDL and Infra.
WebIDL and Infra are complementary technologies. WebIDL is used to define the interfaces of web APIs, while Infra is used to define the algorithms of those interfaces with procedural steps.
JS types: boolean, number, array, object, ... Web IDL types: boolean, unsigned long, sequence, dictionary, ... Infra types: boolean, bytes (for strings), lists, ordered maps |
Once you have created an initial spec document, it might be easiest to follow the following process to incrementally refine your spec.
- Start with the template generated above, or copy a spec that is similar. See the example specs listed below.
- Add WebIDL for each function.
- Describe each WebIDL block with prose text.
- Add algorithms for each function.
If you have WebIDL specifications for your API code, that is a great place to start. Simply copy-paste a subset of the WebIDL that corresponds to the public API into an <xmp class="idl">
tag. We recommend using the <xmp>
tag rather than the <pre>
tag so that you will not need to HTML-escape &
and <
characters.
You will typically define your algorithm steps relative to an interface declared with WebIDL. Here is an example of an interface that is used in the following sections.
<xmp class="idl">
interface Foo {
attribute DOMString bar;
constructor(DOMString arg1);
long baz(DOMString arg1);
};
</xmp>
Immediately before or after each WebIDL block, it is important to include a short, non-normative description, or "domintro"
for each property defined. These descriptive blocks are especially important for algorithmic specifications which are otherwise difficult to read.
Here is the suggested markdown for a domintro
block, which Bikeshed will convert into an HTML definition list:
<div class="domintro">
: property
:: Brief summary of property
</div>
CSS for the domintro
class is defined below, which you should include in your spec.
Once you have some WebIDL declarations of functions and types of parameters, then you can define an algorithm for each function in terms of Web IDL along with Infra declarations for internal state. Use a <div class="algorithm">
container for your algorithm steps, so Bikeshed can add nice default styling to make the algorithms easier to read. For example:
<div algorithm="Foo-algorithm">
Attribute definitions, if any...
The algorithm steps are:
1. step-1
1. step-2
1. step-3
</div>
Note that the steps are all numbered 1.
, since markdown automatically increments the numbers for you, so you won't have to update numbers manually.
WebIDL definitions typically define one or more attributes that are associated with an instance object. By default these attributes refer to some internal state of the object, not directly observable by author-facing JS, but may be referenced by other spec algorithms.
For example, given this WebIDL:
<xmp class="idl">
interface Foo {
readonly attribute DOMString bar1;
attribute DOMString bar2;
};
</xmp>
[TODO: resolve what to say about "internal slots".]
This WebIDL implies that Foo
instances have internal slots for [[bar1]]
and [[bar2]]
.
When referencing an attribute in spec algorithms, you must refer to the internal slot, not the author-facing property, as those can be observed/intercepted by author code. You can use text like "...the {% raw %}{{Foo/bar1}}{% endraw %} internal slot
...". But current best practice is that instead of referring to internal slots, you should use language like this: "Foo
has an associated bar1
of type DOMString
".
The bar1
attribute is readonly
which means it only has a getter algorithm that could be defined like this:
<div algorithm="Foo.bar1">
The <dfn attribute for=Foo>bar1</dfn> [=getter steps=] are: ...
</div>
The bar2
attribute is read/write
so it has both a getter and a setter. Use something like the following markup to define these algorithms:
<div algorithm="Foo.bar2">
The <dfn attribute for=Foo>bar2</dfn> [=getter steps=] are:
1. Return [=this=]'s {% raw %}{{Foo/[[internalBar2]]}}{% endraw %} slot.
The {% raw %}{{Foo/bar2}}{% endraw %} [=setter steps=] are:
1. Set [=this=]'s {% raw %}{{Foo/[[internalBar2]]}}{% endraw %} slot to [=the given value=].
</div>
Note that within getter and setter algorithm steps, you implicitly have access to the instance, [=this=]
. Within setter steps for read/write
attributes, you also have access to [=the given value=]
, which is the value that the attribute is being set to.
If your attribute getter or setter needs to do something non-trivial, such as reacting to its state in ways that the WebIDL type system does not, you may need to explicitly define a name like: <dfn for=Foo>[[bar2]]</dfn>
(since Bikeshed doesn't auto-define the [[bar2]]
slot name for you yet).
All definitions (except dfn
definitions, by default) are automatically "exported", which means other specs can autolink to them.
If a definition is not exported, it is considered private, but you can still link to it with e.g. "<a spec: something>...</a>
".
Every method needs an algorithm defining it. Given an interface like:
<xmp class="idl">
interface Foo {
long baz(DOMString arg1);
};
</xmp>
Use markup like:
<div algorithm="Foo.baz()">
The <dfn method for=Foo>baz(DOMString |arg1|)</dfn> [=method steps=] are:
1. Do something to |arg1|.
1. If [=this's=] {% raw %}{{Foo/bar}}{% endraw %} attribute is null, [=throw=] a TypeError.
1. Otherwise, return 4.
</div>
By using the variable markup |arg1|
for an argument in the defining signature, you can refer to the argument within the method steps using the same notation, as shown in step 1 above.
Within all method algorithm steps, you implicitly have access to [=this=]
, the instance object being operated on.
If you don't define a constructor for a class, one gets created automatically for you that just throws. If you actually want your object to be constructable, it's very similar to methods. Given IDL like:
<xmp class="idl">
interface Foo {
constructor(DOMString arg1);
};
</xmp>
Use markup like:
<div algorithm="Foo()">
The <dfn constructor for=Foo lt="Foo(arg1)">new Foo(DOMString |arg1|)</dfn> [=constructor steps=] are:
...
</div>
(Bikeshed will make all this slightly easier in the future, see speced/bikeshed#2525.)
Defining a term, such as for a type, object, constant, concept, or a top-level entity, is usually as easy as wrapping a <dfn>
element around it. Bikeshed can then automatically link from each reference of a defined term to its definition. You can reference a definition by its name with [=name=]
, or you can specify a different display name with [=name|display name=]
. Here is an example of a simple definition and a couple different references to it.
The user agent has a <dfn>really useful object</dfn> that ...
...
1. If the [=really useful object=] is null, then …
2. If the [=really useful object|RUO=] is not null, then …
There are several convenient ways that Bikeshed will Autolink from use of a term to its definition. Here is a table of some of them (derived from a Bikeshed Cheat Sheet)
Definition Notation | Meaning |
---|---|
<dfn>foo</dfn> |
A definition for foo |
## heading {#link-name} |
Creates a new <h2> that is linkable by #link-name |
Link Notation | Meaning |
---|---|
[=foo=] |
Link to definition of foo |
{% raw %}{{foo}}{% endraw %} |
A reference to the IDL entry for foo |
[[foo]] |
A non-normative reference to the SpecRef entry foo |
[[!foo]] |
A normative reference to the SpecRef entry foo |
[[#foo]] |
A reference to the section in the local document named foo |
[[foo#bar]] |
A reference to section bar of spec foo . The spec must be part of Bikeshed's autolinking database. |
'foo' |
Link to a CSS property or descriptor named foo |
- Bikeshed spec template - using the default template.
- Common Sections of Specifications
- Standards for Different kinds of entities (TBD):
- CSS: Box Model Spec
- JS API: Example of a Widget Specification
- HTTP header
Domenic's guide to spec excellence - Docs (mostly tactical "how to start a new spec")
- Navigation API - (moving to the HTML Standard - new link forthcoming)
- Prioritized Task Scheduling
- Close Watcher API
-
Slides: How to read, write, and think about specs and video: Writing good specs
-
Writing Procedural Specs - A very helpful document, by Gary Kac, similar in purpose to this one.
-
Notes from a talk to WebML WG - Joshua Bell's high-level perspective on writing web specs and best practice tips for using Bikeshed.
<style>
/* domintro from https://resources.whatwg.org/standard.css */
dl.domintro {
position: relative;
color: green;
background: #DDFFDD;
margin: 2.5em 0 2em 0;
padding: 1.5em 1em 0.5em 2em;
}
dl.domintro dt, dl.domintro dt * {
color: black;
font-size: inherit;
}
dl.domintro dd {
margin: 0.5em 0 1em 2em; padding: 0;
}
dl.domintro dd p {
margin: 0.5em 0;
}
dl.domintro::before {
content: 'For web developers (non-normative)';
background: green;
color: white;
padding: 0.15em 0.25em;
font-style: normal;
position: absolute;
top: -0.8em;
left: -0.8em;
}
</style>