Page MenuHomePhabricator

Kiwix: how to properly retrieve page JS/CSS modules / have something which renders properly on mobile/desktop/tablet in 2025?
Open, Needs TriagePublic

Description

I'm asking this question in the frame of mwoffliner scraper from Kiwix, creating offline versions of Wikimedia wikis but also from "random" Mediawiki instances.

Sorry for the long text, but I'm trying to be a bit exhaustive / precise.

Currently, we use following one of the following endpoints / methods to retrieve article HTML:

We also have some future plans. At some point in time, we plan to investigate:

Side-note: should we prefer ActionParse endpoints

With all but (5) we need to make a separate call to ActionParse retrieve the list of JS/CSS modules which are needed. This means two HTTP queries for every article to retrieve (more on that later).

This also means that we use endpoints which work either only on Wikimedia instances, or on standalone Mediawiki instances. Obviously, this means branching logic is the codebase which can only leads to some trouble / more maintenance effort.

Solution (5) seems to have the promise that it will work on all situations with only one HTTP query. Quite compelling.

Is there any drawback I have to keep in mind?

Experiments shown below shows that there is more JS embedded, and this JS is doing some nasty jquery side-loading, not working when offline. Am I wrong?

Render properly (on mobile screens)

Other struggle we have is that we need an HTML which renders properly, especially on mobile screens. And ideally well on desktop / tablets as well. Our primary target is definitely mobile, but I find it clearly sad to not be at a point where we can have responsive CSS working quite good at all resolutions in 2025 (meaning at least not too much degradation when you move out of your primary target, I still like the mobile-first approach).

To achieve this goal, we probably have multiple approach possible. I won't go into details of how mwoffliner currently solves this problem for now because it is still way too blurry for me, and it might well be more the consequence of multiple years of maintenance than something totally correctly architected.

I will rather go into details of what I've found in every endpoints. See Tests done below.

Note that I've use only one sample page for all endpoints, meaning it might not represent the complexities of all possible situations.

The conclusion to these tests is that mostly only Wikimedia mobile API returns sufficient JS and CSS to render properly, especially on mobile screen. But even desktop is kinda ugly on all other APIs.

So here is my second question: what am I doing wrong? Is there any "global" CSS / JS which must be loaded on all pages and can be retrieved through APIs as well?

How are we supposed to do it well on "Random" Mediawiki instances where we do not have any REST mobile API?

What about templates?

What about skins?

What about gadgets? Do you confirm they are never present in the HTML sent by these APIs?

Retrieve JS/CSS modules for a given page

As can be seen in the tests below, it looks like in most cases we have "broad" information about the JS/CSS modules needed for given page in HTML <head> section. However, when calling Mediawiki ActionParse API, the list of modules is bigger, and it includes a distinction between modules and modulescripts (what is that for?) and the additional jsconfigvars. Does it means that "broad" information of HTML <head> section in insufficient in many cases?$

mwoffliner currently systematimatically retrieves the list of modules from ActionParse API, hence the two HTTP queries I mentionned before. If this is confirmed to be necessary, should we at least pass parsoid=1 to make the call a bit faster / more optimized or will it have other side effect because the HTML is not retrieved from Parsoid?

 Tests done

You can find all the HTML/JS/CSS I've built manually to test these pages here:

Wikimedia desktop API

https://hu.wikipedia.org/api/rest_v1/page/html/Brian_May

In this API, the rendered HTML contains these interesting properties in <head>:

<meta property="mw:generalModules" content="ext.cite.ux-enhancements|mediawiki.page.media|ext.tmh.player">
<meta property="mw:moduleStyles" content="ext.cite.parsoid.styles|ext.cite.styles|ext.tmh.player.styles">
<link rel="stylesheet" href="/w/load.php?lang=hu&amp;modules=ext.cite.parsoid.styles%7Cext.cite.styles%7Cext.tmh.player.styles%7Cmediawiki.skinning.content.parsoid%7Cmediawiki.skinning.interface%7Csite.styles&amp;only=styles&amp;skin=vector" />

Rendering all this locally causes JS issues due missing mw object + not responsive at all + many things including quick-facts are not properly positioned at all.

Wikimedia mobile API

https://hu.wikipedia.org/api/rest_v1/page/mobile-html/Brian_May

In this API, the rendered HTML contains these interesting properties in <head>:

<meta property="mw:generalModules" content="ext.cite.ux-enhancements|mediawiki.page.media|ext.tmh.player" />
<meta property="mw:moduleStyles" content="ext.cite.parsoid.styles|ext.cite.styles|ext.tmh.player.styles" />
<link rel="stylesheet" href="//meta.wikimedia.org/api/rest_v1/data/css/mobile/base" />
<link rel="stylesheet" href="//hu.wikipedia.org/api/rest_v1/data/css/mobile/site" />
<link rel="stylesheet" href="//meta.wikimedia.org/api/rest_v1/data/css/mobile/pcs" />
<script src="//meta.wikimedia.org/api/rest_v1/data/javascript/mobile/pcs"></script>

Rendering all this locally works pretty well.

Mediawiki Visual Editor

https://www.appropedia.org/w/api.php?action=visualeditor&mobileformat=html&format=json&paction=parse&formatversion=2&page=Main_Page_for_Kiwix

In this API, the rendered HTML contains these interesting properties in <head>:

<meta property="mw:generalModules" content="ext.smw.styles">
<meta property="mw:moduleStyles" content="mediawiki.page.gallery.styles">
<link rel="stylesheet" href="/w/load.php?lang=en&amp;modules=mediawiki.page.gallery.styles%7Cmediawiki.skinning.content.parsoid%7Cmediawiki.skinning.interface%7Csite.styles&amp;only=styles&amp;skin=vector">

Rendering all this locally causes JS issues due missing mw object + not responsive at all + many things including quick-facts are not properly positioned at all.

Mediawiki REST API

https://www.appropedia.org/w/rest.php/v1/page/Main_Page_for_Kiwix/html

In this API, the rendered HTML contains these interesting properties in <head>:

<meta property="mw:generalModules" content="ext.smw.styles">
<meta property="mw:moduleStyles" content="mediawiki.page.gallery.styles">
<link rel="stylesheet" href="/w/load.php?lang=en&amp;modules=mediawiki.page.gallery.styles%7Cmediawiki.skinning.content.parsoid%7Cmediawiki.skinning.interface%7Csite.styles&amp;only=styles&amp;skin=vector">

Rendering all this locally causes JS issues due missing mw object + not responsive at all + many things including quick-facts are not properly positioned at all.

Mediawiki ActionParse API

https://hu.wikipedia.org/w/api.php?action=parse&format=json&prop=modules%7Cjsconfigvars%7Cheadhtml%7Ctext&parsoid=1&page=Brian_May

In this API, the rendered HTML contains these interesting properties in <head>:

<script>document.documentElement.className="client-js vector-feature-language-in-header-enabled vector-feature-language-in-main-page-header-disabled vector-feature-page-tools-pinned-enabled vector-feature-toc-pinned-clientpref-1 vector-feature-main-menu-pinned-enabled vector-feature-limited-width-clientpref-1 vector-feature-limited-width-content-enabled vector-feature-custom-font-size-clientpref-1 vector-feature-appearance-pinned-clientpref-1 vector-feature-night-mode-enabled skin-theme-clientpref-day vector-sticky-header-enabled";RLCONF={"wgBreakFrames":true,"wgSeparatorTransformTable":[",\\t."," \\t,"],"wgDigitTransformTable":["",""],"wgDefaultDateFormat":"ymd","wgMonthNames":["","január","február","március","április","május","június","július","augusztus","szeptember","október","november","december"],"wgRequestId":"35b37723-fe3b-4a0b-8aae-afd7d19ed2a9","wgCanonicalNamespace":"","wgCanonicalSpecialPageName":false,"wgNamespaceNumber":0,"wgPageName":"Brian_May","wgTitle":"Brian May","wgCurRevisionId":27765432,"wgRevisionId":0,"wgArticleId":19638,"wgIsArticle":true,"wgIsRedirect":false,"wgAction":"view","wgUserName":"B-root","wgUserGroups":["*","user","autoconfirmed"],"wgCategories":["Mágikus ISBN-linkeket használó lapok","Wikipédia-szócikkek VIAF-azonosítóval","Wikipédia-szócikkek LCCN-azonosítóval","Wikipédia-szócikkek ISNI-azonosítóval","Wikipédia-szócikkek ORCID-azonosítóval","Wikipédia-szócikkek GND-azonosítóval","Wikipédia-szócikkek BNF-azonosítóval","Wikipédia-szócikkek SBN-azonosítóval","Wikipédia-szócikkek KKT-azonosítóval","Wikipédia-szócikkek BIBSYS-azonosítóval","1947-ben született személyek","Londoniak","Élő személyek","Queen","Brit rockgitárosok","Brit zeneszerzők","A Brit Birodalom Rendjével kitüntetett személyek","Minden idők 100 legjobb gitárosa (Rolling Stone magazin)"],"wgPageViewLanguage":"hu","wgPageContentLanguage":"hu","wgPageContentModel":"wikitext","wgRelevantPageName":"Brian_May","wgRelevantArticleId":19638,"wgUserId":587166,"wgUserIsTemp":false,"wgUserEditCount":0,"wgUserRegistration":1740993008000,"wgUserFirstRegistration":1426538644000,"wgIsProbablyEditable":true,"wgRelevantPageIsProbablyEditable":true,"wgRestrictionEdit":[],"wgRestrictionMove":[],"wgNoticeProject":"wikipedia","wgNoticeUserData":{"registration":"20250303091008"},"wgCiteReferencePreviewsActive":true,"wgFlaggedRevsParams":{"tags":{"accuracy":{"levels":2}}},"wgStableRevisionId":27765432,"wgGlobalGroups":[],"wgMediaViewerOnClick":true,"wgMediaViewerEnabledByDefault":true,"wgPopupsFlags":0,"wgVisualEditor":{"pageLanguageCode":"hu","pageLanguageDir":"ltr","pageVariantFallbacks":"hu"},"wgMFDisplayWikibaseDescriptions":{"search":true,"watchlist":true,"tagline":true,"nearby":true},"wgWMESchemaEditAttemptStepOversample":false,"wgWMEPageLength":70000,"wgULSAcceptLanguageList":[],"wgULSBabelLanguages":[]};\nRLSTATE={"site.styles":"ready","user.styles":"ready","user":"ready","user.options":"loading","ext.cite.parsoid.styles":"ready","ext.cite.styles":"ready","ext.tmh.player.styles":"ready","mediawiki.skinning.content.parsoid":"ready"};RLPAGEMODULES=["ext.cite.ux-enhancements","mediawiki.page.media","ext.tmh.player"];</script>
<script>(RLQ=window.RLQ||[]).push(function(){mw.loader.impl(function(){return["user.options@12s5i",function($,jQuery,require,module){mw.user.tokens.set({"patrolToken":"1c9f22ae41c4db237009124605551f6167cffed7+\\\\","watchToken":"6cb81619d736e98b9541dac4f7e59b6367cffed7+\\\\","csrfToken":"f32aa4ef3c021aa4b0af3f2ea7a1671e67cffed7+\\\\"});mw.user.options.set({"discussiontools-autotopicsub":1,"echo-subscriptions-email-edit-user-talk":1,"echo-subscriptions-web-reverted":false,"echo-subscriptions-email-article-linked":true,"echo-subscriptions-web-article-linked":true,"echo-subscriptions-email-mention":true,"echo-subscriptions-email-dt-subscription":true});\n}];});});</script>
<link rel="stylesheet" href="/w/load.php?lang=hu&amp;modules=ext.cite.parsoid.styles%7Cext.cite.styles%7Cext.tmh.player.styles%7Cmediawiki.skinning.content.parsoid&amp;only=styles&amp;skin=vector-2022" />
<script async="" src="/w/load.php?lang=hu&amp;modules=startup&amp;only=scripts&amp;raw=1&amp;skin=vector-2022"></script>
<link rel="stylesheet" href="/w/load.php?lang=hu&amp;modules=site.styles&amp;only=styles&amp;skin=vector-2022" />

And following JSON details:

"modules": [
    "ext.cite.ux-enhancements",
    "mediawiki.page.media",
    "ext.tmh.player",
    "user",
    "user.options"
],
"modulescripts": [],
"modulestyles": [
    "ext.cite.parsoid.styles",
    "ext.cite.styles",
    "ext.tmh.player.styles",
    "mediawiki.skinning.content.parsoid",
    "site.styles",
    "noscript",
    "user.styles"
],
"jsconfigvars": {}

Rendering all this locally causes JS issues due to dynamic loading of jquery module + missing mw object + not responsive at all + many things including quick-facts are not properly positioned at all.

Wikimedia Enterprise Dumps

In the dump of March 2025 (20250301), the 'article_body.html' property containing rendered HTML has these interesting properties in HTML <head>:

<meta property="mw:generalModules" content="ext.cite.ux-enhancements|mediawiki.page.media|ext.tmh.player" />
<meta property="mw:moduleStyles" content="ext.cite.parsoid.styles|ext.cite.styles|ext.tmh.player.styles" />
<link rel="stylesheet" href="/w/load.php?lang=hu&amp;modules=ext.cite.parsoid.styles%7Cext.cite.styles%7Cext.tmh.player.styles%7Cmediawiki.skinning.content.parsoid%7Cmediawiki.skinning.interface%7Csite.styles&amp;only=styles&amp;skin=vector" />

Rendering all this locally causes JS issues due missing mw object + not responsive at all + many things including quick-facts are not properly positioned at all.

Wikimedia new(ish) REST API

https://api.wikimedia.org/core/v1/wikipedia/hu/page/Brian_May/with_html

In this API, the rendered HTML contains these interesting properties in <head>:

<meta property="mw:generalModules" content="ext.cite.ux-enhancements|mediawiki.page.media|ext.tmh.player" />
<meta property="mw:moduleStyles" content="ext.cite.parsoid.styles|ext.cite.styles|ext.tmh.player.styles" />
<link rel="stylesheet" href="/w/load.php?lang=hu&amp;modules=ext.cite.parsoid.styles%7Cext.cite.styles%7Cext.tmh.player.styles%7Cmediawiki.skinning.content.parsoid%7Cmediawiki.skinning.interface%7Csite.styles&amp;only=styles&amp;skin=vector" />

Rendering all this locally causes JS issues due missing mw object + not responsive at all + many things including quick-facts are not properly positioned at all.

Event Timeline

Short answer: action=parse&parsoid=1 should give you all the information you need in a single API call. You will be getting "read view" Parsoid output, which has a few differences from "edit mode" Parsoid output -- like section edit links are added, although you can probably suppress those with the proper argument to the action API.

The set of modules is probably larger from the action=parse API because it is "more correct". The information embedded in the Parsoid <head> was ported from the earlier Parsoid/JS implementation and it wouldn't surprise me if it is missing some modules which are added by extensions, core methods like OutputPage::addHelpLink, etc. You should generally not use it, if you can use information from the action API instead. You can use the usearticle=1 flag added by T373212, and if you look at the source code in mediawiki-core:includes/api/ApiParse you can also set skin/subtitle/headhtml/mobileformat to ensure that all of the appropriate hooks are invoked to make the output the same as it would be for desktop or mobile read views.

(The 'mobileformat' option might also be your answer to getting "mobile format" HTML.)

"ModuleStyles" are CSS-only modules. "ModuleScripts" are script-only modules (deprecated since 1.32, T188689). The differences are mostly historic, I believe. You don't want to use the resource loader invocation included by Parsoid because you don't want to download and cache a bunch of different combinations of modules. Instead for ZIM bundle purposes you want to download each module (JS, CSS) separately and insert an appropriate load statement for each. That way you can store just module A and module B, not also try to store "A+B" as a separate item.

Thanks a lot for all this insight! I begin to get a better landscape.

Digging a bit deeper, I being to understand that in addition to the modules returned by ActionParse, I also need to load startup to have the core mw object defined. Is that correct? Is there other modules (scripts or styles) which I need to load?

includes/ResourceLoader/ResourceLoader.php contains:

		// Special module that always exists
		$this->register( 'startup', [ 'class' => StartUpModule::class ] );

so I think that's the only 'special' one.
includes/ResourceLoader/ClientHtml::getHeadHtml seems to give the load order for the different modules:

		// Inline RLQ: Embedded modules
		if ( $data['embed']['general'] ) {
			$chunks[] = $this->getLoad(
				$data['embed']['general'],
				Module::TYPE_COMBINED
			);
		}

		// External stylesheets (only=styles)
		if ( $data['styles'] ) {
			$chunks[] = $this->getLoad(
				$data['styles'],
				Module::TYPE_STYLES
			);
		}

		// Inline stylesheets (embedded only=styles)
		if ( $data['embed']['styles'] ) {
			$chunks[] = $this->getLoad(
				$data['embed']['styles'],
				Module::TYPE_STYLES
			);
		}

Thanks !

Digging a bit deeper, ActionParse gives me only the HTML head and the parsed body.

But in fact, I realize by experimenting that there is some HTML in-between, with usually important styles attached to it.

For instance, on https://hu.wikipedia.org/wiki/Brian_May?useskin=vector-2022, we have all these divs which are placed by the skin:

<div class="mw-page-container">
	<div class="mw-page-container-inner">
		<div class="mw-content-container">
			<main id="content" class="mw-body">
				<div id="bodyContent" class="vector-body ve-init-mw-desktopArticleTarget-targetContainer" aria-labelledby="firstHeading" data-mw-ve-target-container="">
					<div id="mw-content-text" class="mw-body-content">

And some of these divs have very important styles attached:

@media screen {
  .vector-body {
    font-size:var(--font-size-medium);
    line-height:var(--line-height-medium)
  }
}

Where can we find / download this HTML which seems to come from the skin and is in-between the head/body opening and the parsed output from Parsoid ?

Aside that, there is pretty important stuff which goes in this "in-between" HTML : page title, subtitle (which in some wikis like wikivoyage includes a full banner with links), breadcrumbs sometimes, ... Again, where can we find this HTML?

Are both stuff we could retrieve with ActionParse API as well?

This is generated by the 'skin' and in some sense it is the responsibility of Kiwix to generate an appropriate skin for your purposes. There is the useskin option to action=parse, and probably you want to look at the api and fallback skins, which should provide a good basis for applying kiwix-specific styling.

OK, thanks for confirming this.

The problem I have with the idea of developing our own skin, is that Mediawiki owners (the foundation but also standalone Mediawiki owners) have a default skin which fits their usage. And wiki users are used to this skin. If we develop a single (or few) custom Kiwix skin, then it means that all wikis will render the same, and this is probably quite deceptive to most users which just want a 1-to-1 archive of their wiki. Or maybe it is a compromise we need to accept, at least on the short term. Do you confirm there is no real solution to retrieve skin automatically? Maybe "reimplementing" skins by just adding the necessary HTML mentioned above is the another solution, since skins are not expected to much change.

Another topic, I've found some CSS modules which seem to not be referenced in the API response: infobox, redirect and wikiMenuStyles; not sure they are all needed, but it is obvious at least the first one is mandatory. Do you know where I can find this list of additional modules so I do not have to guess them?

Could you give a specific title which contains each of those CSS modules? They might be coming from the skin or from the TemplateStyles extension. I don't know anything about them.

I do not find exact example again, of course.

On https://hu.wikipedia.org/wiki/Brian_May, it looks like styles from https://hu.wikipedia.org/w/load.php?lang=hu&modules=ext.gadget.infobox%2CwikiMenuStyles&only=styles are required to properly render the infobox.

Does it means that since all extensions / gadgets could add their own CSS/JS, and since the corresponding HTML is present in the rendered HTML, we have to load CSS/JS from all extensions / gadgets?

OK, so I've went a bit further into implementing this "ActionParse" approach.

I confirm the issue around "missing" ext.gadget.infobox,wikiMenuStyles styles module on hu.wikipedia.org ; issue is not present on en.wikipedia.org

On both projects, I have other issues:

  • Many Uncaught TypeError: mw.hook is not a function ; looks like the declaration of this hook is part of mediawiki.base scripts module ; but it looks like this scripts module need to be called from the Resource Loader ? (might be wrong, but at least adding the script is not sufficient to have mw.hook defined)
  • At least one Uncaught ReferenceError: jQuery is not defined in site.js on hu.wikipedia.org
  • At least one Uncaught TypeError: mw.loader.using is not a function in site.js on en.wikipedia.org

And in both cases (might be related or not), the navbox "Articles and topics related to xxx" is not collapsed and does not have required "Show/Hide" button.

I feel a bit lost on how to move this forward. Not sure this is really related to ActionParse, but it needs to be solved anyway.

Note that all "other issues" mentioned above + the navbox issue is "old", like some were already there with Wikimedia Desktop, and some where even already a problem 1 year ago when still using the former / deprecated mobile endpoint.

ssastry subscribed.

We had some sync gmeet calls about this in October with a bunch of teams, and I am going to untag CTT (work in progress) for now till there is more clarity about work that needs to be done on our end.