Skip to content
anhtai9710 edited this page Aug 9, 2024 · 58 revisions

As a first step, clone the repository:

git clone [email protected]:speced/respec.git

Developing ReSpec requires Node.js v18.14+ and pnpm v8+. You can "install" pnpm with corepack as:

corepack enable
corepack prepare --activate # run this from repository root

and install the needed dependencies:

pnpm install

Now you can start the local development servers:

pnpm start --browser Chrome

Note: You can use Firefox and ChromeCanary in the above.

That will start up "Karma" and a local http server for you.

Open the url given (usually http://127.0.0.1:8000). And go to "examples".

Usually "basic.html" is a good place to start hacking from.

ReSpec's architecture

ReSpec is an application that runs mostly synchronous bits of JS after a Document loads. These JavaScript fragments are referred to as "plugins". When a bunch of plugins are combined together, they create a "profile".

Generally, a "profile" is only created for a real-world organization or company. So, for instance, the W3C's profile, located at profiles/w3c.js, loads the following plugins (not the full list, just for illustrative purposes):

  • core/base-runner
  • core/include-config,
  • core/style,
  • w3c/style,
  • core/markdown,
  • w3c/headers,
  • ...

What each plugin above does doesn't matter, though you can deduce what each does by the name. What matters is that ordering is important - and we mix together W3C plugins and "core" plugins. And that it's these plugins coming together that form a profile, in this case the "W3C profile". Each of the plugins are run only once - and the thing that runs them is the core/base-runner plugin.

See profile/w3c.js for the actual details of how the profile is set up. But it's essentially:

  1. Load the profile + all the plugins (but don't "run" them yet!).
  2. Wait for the document's "DOMContentLoaded" event to fire.
  3. Once DOM is ready, run each plugin in order, waiting for each plugin to finish.

Core Base runner (core/base-runner.js)

The first and most important plugin (core/base-runner), is actually the "brains" of ReSpec: it is the thing that "runs" all other plugins in order.

Before any plugins are run, however, it adds the following property to the document object:

// The following only resolves once all plugins have run
// and ReSpec has finished doing whatever it needs to do.
document.respec.ready;

After that, the Base Runner starts looping over an array of given plugins: literally just a call to a .runAll(arrayOfPlugins) method. For each plugin, it waits until a plugin has finished doing its work before continuing to the next plugin. It does this by calling the run() function exported from a plugin, and awaiting for that function to finish. Some plugins may export a Plugin class with a run() method instead.

Once all the plugins have "run", ReSpec resolves the respec.ready promise on the Document object.

document.respec.ready.then(() => {
  console.log("ReSpec has finished processing this document");
});

This is potentially useful for scripts that depend on ReSpec's output. They can wait for the promise to settle before doing their own work.

Alternatively, if you really need to run things immediately before or after ReSpec runs the plugins, you can define preProcess or postProcess properties on the configuration object. See preProcess and postProcess more details and for examples.

Plugins

Plugins are simple ES6 modules that live in the "src/" folder. They have two parts: A synchronous initialization, and an optionally exported run() function that is called asynchronously.

A plugin looks like this:

// import other things you need
import utils from "core/utils";

// This part runs synchronously and an indeterminate order.
// do any plugin specific setup here. Note, the document
// can be in an unstable state here - so don't manipulate
// the DOM here!

// Optionally, export "run" function
// See below for description of arguments.
export async function run(conf) {
  if ("something" in conf || document.querySelector("#important-element")) {
    await someAsyncTask();
  }
}

async function someAsyncTask() {
  // Your code here
}

The exported run method SHOULD have arguments (conf):

  • conf: is the ReSpec configuration object (window.respecConfig) - which the user defined. Be careful not to modify this object.

Warning users of errors

If you are creating a plugin that needs to show warnings to a user, you can use the showWarning utility.

import { showWarning, showError } from "./core/utils.js";
export async function run(conf) {
  if (!"something" in conf) {
    showWarning("Some error message", "plugin-name");
    // You can pass additional details like a `hint` how to fix, list of `elements` that caused the issue etc.
    // See showWarning and showError in core/utils.js
  }
}

These messages will be picked up by ReSpec's UI (the "pill"), and displayed to the end-user. You should only "error" on things that the user needs to fix to successfully publish their document. Likewise, only warn on things the user SHOULD fix.

IMPORTANT: Don't show JavaScript errors to the user - as they won't be able to fix these, and the minified JS output will make these messages really unhelpful!

pnpm start

The start script in package.json contains the commands useful during development. It runs a static HTTP server, watches files for change and re-build the profile, and run unit tests.

Built-in HTTP server

You can launch a built in HTTP server during development by simply typing: pnpm start. If you wish not to run tests and other parts of start script, you can alternatively run pnpm run server.

Testing

ReSpec's unit tests are written using Jasmine and run on Karma. To start the testing server:

pnpm start --browser Firefox

You can run test in different browsers by setting browsers value above to any of: Firefox, FirefoxHeadless, Chrome, ChromeHeadless, Safari. Same can be set using the BROWSERS environment variable:

BROWSERS="ChromeHeadless Firefox" pnpm start

For debugging purposes, you can click on the Debug button when the tests start in the browser - this will allow you to see the tests summary in browser itself as well as allow you to re-run any particular test.

Please refer to Jasmine documentation regarding focused specs (fdescribe(), fit()) to see how to run only specific tests when running pnpm run karma. This will save you a lot of time and pain.

You can also select individual tests by filtering those which match a particular pattern:

pnpm start --grep="SEO"

If you want to run all tests whose description includes "SEO".

Interactive mode

You can also run start in "interactive" mode. This gives you more control over when tests are run and, by default, turns off automatic file watching.

pnpm start --interactive

This is useful for more advanced debugging sessions, and can be combined with --grep to test just what you want, when you want.

Testing without opening browser window

You can also run tests without opening a full browser window. Test results will be visible in your terminal.

pnpm start --browser FirefoxHeadless
# or use ChromeHeadless

Look at the help dialog when you run pnpm start for more options.

Custom profiles

If you are a company, standards consortium, or government entity, you might want to consider maintaining your own ReSpec profile. That allows you have your own content templates, load whatever plugins you need, and generally keep control over how ReSpec runs.

To create a custom profile:

  1. Make a copy of "profiles/w3c.js", but rename it "YOUR-PROFILE-NAME.js".
  2. Open "YOUR-PROFILE-NAME.js", and remove, add, etc. any plugins you want.
  3. run: node ./tools/builder.js YOUR-PROFILE-NAME. That will generate a bundle in the build directory.

If the profile is popular, then please send a pull request to the main repository and we can host as part of the main project.

Working with your new profile

In examples/, make a copy of "basic.html" and point the <script> tag at your new profile. Now run:

pnpm start --profile YOUR_PROFILE_NAME --browser Chrome

That will start a web server, so you can now load up http://localhost:8000/examples and have play with your custom profile.

Testing your profile

If you are writing custom Jasmine tests, simply place them into tests/spec/YOUR-PROFILE-NAME/. And then run:

pnpm start --interactive --profile=YOUR-PROFILE-NAME --browser Chrome

If you prefer to use a different browser, that's ok too.

Guides

Configuration options

W3C Configuration options

Linter rules

Internal properties

Handled by ReSpec for you.

Special <section> IDs

HTML elements

Custom Elements

WebIDL

HTML attributes

CSS Classes

Special properties

Clone this wiki locally