This page is for people writing a MediaWiki extension or skin that has to work inside a Wikven build. It assumes you know how to write one for a live wiki; what follows is only what a static export changes. Development describes how Wikven itself is built, which is a different question.

What a build is

A Wikven build boots MediaWiki once, on the command line, renders every page to a file, and exits. The reader is then served plain files by a host that runs no PHP.

Two consequences run through everything below:

  • Your code runs at build time, not at view time. There is no load.php, no action API and no database once the site is deployed. Anything your extension would normally do in response to a request has to happen during the build, or ship as static JavaScript that needs no server.
  • The build is not a request. MW_ENTRY_POINT is cli, there is no WebRequest worth reading, and a hook of yours that assumes a user, a session or a title from the URL will not find one. Wikven's own hooks guard on MW_ENTRY_POINT === 'cli' for this reason.

Getting your extension into a build

A site lists what it wants in its .wikven.yml. Extensions bundled with the image are named and nothing else is needed; anything else is fetched at build time from a source declared under WikvenRepositories — a tarball, a Git repository, or a Composer package.

extensions:
  - MyExtension
config:
  MyExtensionGreeting: Hello
WikvenRepositories:
  MyExtension:
    repository: https://github.com/example/mediawiki-extensions-MyExtension.git
    reference: v1.2.3

Your settings need no support from Wikven. The site's config map is applied through MediaWiki's own settings loader, so any key your extension.json declares under config can be set there, and reaches you as the global you already read.

Pin a reference rather than tracking a branch. A build of a given revision of a site should fetch the code that revision was tested against, and the docs site pins every fetched component for that reason.

Local URLs: the one contract to read

This is the part that catches extension authors, because the code is correct on a live wiki and wrong only here.

Wikven makes page URLs document-relative. Title::getLocalURL() answers ./Page.html, and because a page whose title has a slash is exported into a real directory, a page one level down needs ../Page.html to mean the same thing. maintenance/rename.php applies that correction per page, by the page's own depth.

It corrects two places:

  • the page's markup — href, src, srcset and CSS url();
  • the page's own JavaScript config, the RLCONF object, so a local URL you export as a config var is corrected the way an href is.

It cannot correct a third: a local URL you bake into a ResourceLoader module's bundle. One bundle file serves every page at every depth, so there is no depth to correct it by, and nothing will fix it for you. If your module ships a URL, anchor it yourself against a value that is site-root-anchored by construction rather than against the document.

The instance this was found through: SifterSearch baked the search results page's URL into its module config, which meant a reader who searched from a page one directory down was sent to a page that does not exist. It was fixed on the extension's side, by resolving that URL against the bundle path the site already declares.

Your code runs once per skin, in parallel

A site with three skins renders three times. Each pass is a separate process with its own MediaWiki boot, its own copy of the database, and its own output directory, and passes run beside each other. Three rules follow:

  • Be a reader of the database, not a writer. The content is final before the passes start. A pass that writes contends with the others for the same file, and writes something the next pass would have written identically anyway.
  • Write only inside your pass's own output directory ($wgWikvenHtmlDirectory). Note the trap: the main skin renders into the export root, which is the parent of every other skin's directory, so "everything below my output directory" is not the same set as "my pages" for that pass.
  • Do not assume you are alone. Anything you create outside your own directory can be raced by another pass.

Reproducibility

Two builds of the same source must produce byte-identical output, and CI asserts it by baking the docs site twice and diffing the results. The build already freezes the clock, pins page_touched, strips the request and render ids MediaWiki stamps into each page, and runs the job queue in a fixed order to get there.

So do not put into rendered output:

  • a timestamp, a duration, or anything else read from the clock;
  • a random value, a request id, a hostname, a process id, or an absolute path from the build machine;
  • anything ordered by the filesystem — sort a directory listing before you write from it.

If your extension has a cache keyed on any of the above, key it on content instead.

Jobs

Enqueue jobs as you normally would. The build drains the queue before any skin renders, one job type at a time in name order, so a job of yours has run by the time pages are rendered.

One thing to know if your job is expensive and gets enqueued per revision: the build holds the search index job back to the very end of the queue, because it was otherwise re-running on every pass of the queue and throwing away all but the last result. A job of the same shape wants the same treatment.

What Wikven already removes

Do not re-add UI that needs a live wiki — Wikven has taken it out on purpose:

  • the edit and history UI, and the sidebar's wiki-only links;
  • the search box, unless a static search index is providing results;
  • the history action, which renders nothing.

Footer links to edit, history and source are pointed at the URLs the site configures (WikvenEditUrl and friends), so if your extension adds a link of that kind, take the same route rather than linking a Special: page the export does not contain.

Checking your work

Build a site that uses your extension and look at the output, rather than trusting a live wiki to agree:

docker run --rm \
  -v "$(pwd)/docs:/workspace/src" \
  -v "$(pwd)/dist:/workspace/dist" \
  wikven

Worth checking specifically:

  • open a page at depth — one whose title contains a slash — and follow the links your extension renders, since a URL bug shows up nowhere else;
  • build twice into different directories and diff -r them, which catches an unstable value before CI does;
  • build with more than one skin configured, so a pass that reaches outside its own output is caught.

See also

  • Configuration — every setting a site can name, including WikvenRepositories.
  • Development — the build pipeline, step by step.
  • JavaScript — how scripts and gadgets are bundled for a static host.