Ich habe Shariff umgebaut. Mein Fork (eines Forks) nutzt ECMAScript statt CommonJS Module, verzichtet auf jQuery und reduziert die Buidpipeline massiv, sodass von npm install
nun 253 statt 1307 Abhängigkeiten installiert werden – von denen noch dazu über 200 nur für die Tests notwendig sind. Der eingesandte PR ist durch die Verkürzung der Abhängigkeitsliste deutlich rot, +5.576 stehen gegen −36.613 Codezeilen, auch bei der Kernlogik wurde nichts aufgebläht. Einschränkungen für die Nutzer sollte der Umbau keine haben.
Moment, worum geht es?
Shariff ist ein Javascriptprojekt, das vor einigen Jahren der Heiseverlag gestartet hatte. Es erstellt Buttons zum Teilen von URLs in sozialen Netzwerken so, dass nicht schon durch den Einbau der Buttons Leser von den Netzwerken verfolgt werden können. Mehrwert zu einfachen HTML-Buttons mit dem gleichen Vorteil sind die Zähler, die bei manchen der Netzwerken angezeigt werden können (und damals noch öfter konnten), die anzeigen wie oft ein Artikel bereits geteilt wurde.
Ein cooles Projekt, das damals international Aufmerksamkeit bekam. Leider schlief im Laufe der Zeit die Entwicklung etwas ein. Das dürfte mehrere Gründe haben, pure Spekulation: Die Buttons werden von Lesern sehr selten genutzt, Entwicklungszeit hier rechnet sich kaum; Netzwerke haben nach und nach das Abrufen der Zählerstandes immer öfter unmöglich gemacht und so den Mehrwert Shariffs zu simpleren Ansätzen reduziert; und Heise nutzt Shariff (soweit ich sehen konnte) nicht mehr auf den eigenen Webseiten.
Die Ankündigung von Shariff ist auf 2014 datiert. Technisch wurde das Projekt seitdem durchaus noch überarbeitet, aber es wurden nicht die Möglichkeiten genutzt, die 2025 solchen Projekten bietet: Durch die Weiterentwicklung der Browser sind viele Hilfsmittel von damals unnötig. Entfernt man sie, kann man die Buildpipelines ebenfalls entfernen oder reduzieren. Und kommt so im Idealfall wieder zu Javascriptprojekten, die direkt im Browser ausgeführt werden können, ohne die Entwicklung durch einen Buildschritt zu verkomplizieren.
Das ist allerdings keine überall geteilte Meinung. Nicht alle Entwickler teilen das Ziel der Simplifizierung oder werten ihre Vorteile gleich hoch, oft wird auch über Frameworks wie React oder durch die Nutzung von Typescript statt Javascript eine Buildpipeline notwendig. Shariff aber nutzte weder Typescript noch solche Frameworks, war daher in meinen Augen ein guter Kandidat für eine Abstraktion vermeidende Überarbeitung.
Ich war an Shariff interessiert, weil es für Serendipitys Socialplugin benutzt wurde. Anlass in die Entwicklung zu schauen war der Versuch, einen Mastodonbutton hinzufügen. Dabei stolperte ich über den aktiven Fork shariff-plus von Richard Fath. Dafür begann ich die Vereinfachung. Zwischenzeitlich entfernte ich Shariff zwar aus dem Socialplugin, aber die Modernisierung stellte ich kürzlich trotzdem fertig. Zu beachten ist, dass ich bei modernem Javascript durchaus Lücken habe, ich könnte also Details übersehen oder unnötig verkompliziert haben.
jQuery
Das Helferskript jQuery zu entfernen war gleichzeitig die einfachste Operation, aber ist auch die, über die am wahrscheinlichsten Bugs verursacht wurden. Einige fand ich bereits und das sollten die gröbsten gewesen sein, aber weitere kann ich nicht ganz ausschließen.
Mit jQuery werden manche Operationen einfacher, z.B. das Hinzufügen von Klassen zu einem DOM-Element. Früher war der große Vorteil auch das Überbügeln von Browserunterschieden, aber das ist heute eigentlich kein Thema mehr. Für ein Skript wie Shariff ist die jQuery-Abhängigkeit doof, weil wegen ihr die Webseitenbetreiber neben Shariff auch noch jQuery einbinden müssen, was die Webseitengröße erhöht.
Zum Entfernen musste die shariff.js überarbeitet werden, die aber nur knapp 300 Zeilen lang ist. In ihr gab es Codestellen wie diese:
var canonical = $('link[rel=canonical]').attr('href')
Das wurde dann jeweils mit Browsermethoden ersetzt, hier:
var canonical = document.querySelector('link[rel=canonical]')?.href
Davon waren manche Codeänderungen kniffliger, aber jQuery kann nichts was Browser nicht auch können, daher war alles mindestens theoretisch machbar.
Module
Bei den Modulen war die Änderung weniger mit Codelogik verbunden. Denn die vorher genutzten CommonJS-Module (CJS) und die neuen ESMs sind sich sehr ähnlich. Der Hauptunterschied ist, dass die ersteren von Browsern nicht verstanden werden. Um sie dann in ihnen zu nutzen müssen sie umgewandelt werden. Shariff nutzte CJS für die Funktionalität der Buttons, jeder Dienst war (und ist) sein eigenes Modul.
Vorher gab es eine services/index.js, die so aussah:
module.exports = {
buffer: require('./buffer'),
…
}
Daraus wurde:
export {default as buffer} from './buffer.js'
…
Bei den Modulen selbst änderte sich ihre Definition, von
'use strict'
module.exports = function (shariff) {
…
}
zu
'use strict'
export default function data(shariff) {
…
}
Der Import der Module in der shariff.js änderte sich ebenfalls, aus
const services = require('./services')
wurde
import * as services from './services/index.js';
Das sind alles syntaktisch simple Änderungen, die man mechanisch und ohne tieferes Verständnis ausführen konnte. Problematisch aber war, dass die Module manchmal auch ein URL-Modul einbauten, was in Browsern so nicht existiert:
var url = require('url')
…
var shareUrl = url.parse('https://twitter.com/intent/tweet', true)
Sowas musste dann umgebaut werden, hier:
var shareUrl = new URL('https://twitter.com/intent/tweet');
Glücklicherweise waren keine weiteren Nodeisms im Code verteilt.
Guter Effekt des Ganzen war, dass die shariff.js fortan direkt im Browser funktionierte, der konnte dann die Module laden ohne dass vorher ein Helferprogramm wie webpack den Code umformen musste. Optional ging das aber immer noch, so konnte rollup die shariff.complete.js weiterhin bauen, eine Datei mit dem Javascriptcode der äußeren shariff.js und der ganzen Module.
Pipeline
Und hier machte ich einen Fehler: Ich hörte auf. Wenn rollup doch die gebrauchte shariff.complete.js erstellen konnte, Browser ansonsten direkt die shariff.js verstanden, ich das mit Less gebaute CSS erstmal nicht abändern wollte, dann müsste es das gewesen sein, oder? Keineswegs.
In der package.json waren fünf Befehle definiert. Ich kann sie hier in Gänze zeigen:
"scripts": {
"dev": "webpack-dev-server --hot --inline --port 3000 --content-base demo --entry app=./demo/app.js",
"test": "eslint src && karma start --single-run",
"build": "rm -fr dist && webpack -p",
"build_zip": "7z a -tzip $BASE_NAME.zip ./dist/* && 7z a -ttar $BASE_NAME.tar ./dist/* && 7z a $BASE_NAME.tar.gz $BASE_NAME.tar",
"prepare": "husky install"
},
Von denen war nur build_zip
unbedenklich. Hauptproblem war webpack: Durch meine Änderungen an den Modulen funktionierte dessen Konfiguration nicht mehr. Und ich scheiterte selbst daran, die wieder hinzubiegen. Webpack aber, wie oben abzusehen, war zuständig für das Bauen des Javascript als auch des CSS, also zum Umwandeln der CJS-Module und der Lessdateien. Damit hatte meine Änderung beides blockiert.
Zweites Problem: Die Abhängigkeiten waren ziemlich veraltet, es hagelte deprecated-Warnungen. Problematisch gerade dann, wenn man auf modernen JS-Code umgestellt hat, der eher nur mit modernen Varianten dieser Helfer zusammenspielt, wenn überhaupt.
Außerdem waren noch Tests über, die jQuery testen wollten (was auch noch nicht als Abhängigkeit entfernt war), die auch noch ebenfalls auf das als deprecated markierte PhantomJS zurückgriffen, wobei auch die wenigen auf Shariff gerichteten Tests wegen den neuen Modulen zusätzlich noch etwas umgebaut werden mussten.
Genau das habe ich dann getan, alles umgebaut. Die Befehle sehen in meinem Fork nun so aus, was die Änderungen schonmal sichtbar macht:
"scripts": {
"dev": "lessc src/style/shariff-complete.less demo/shariff.complete.css && npx http-server -p 3000 demo/",
"test": "eslint src && mocha -r jsdom-global/register",
"build": "rm -fr dist && mkdir dist && lessc src/style/shariff-complete.less dist/shariff.complete.css && rollup src/js/shariff.js -o dist/shariff.complete.js",
"build_zip": "7z a -tzip $BASE_NAME.zip ./dist/* && 7z a -ttar $BASE_NAME.tar ./dist/* && 7z a $BASE_NAME.tar.gz $BASE_NAME.tar"
},
Die CSS-Datei wird also direkt von lessc
erstellt, die shariff.complete.js von rollup. Das eingebaute npx http-server
ersetzt webpack-dev-server
. Generell ist bei dev
weniger zu bauen, weil ich die Demodateien so umgebaut hatte, dass sie direkt auf die Quellcodedatei zeigen (die der Browser ja nun laden kann). Ganz entfernt hatte ich husky, da es kritische Warnungen wegen seiner Konfiguration schmiss und die Commits blockierte, damit fiel prepare
weg. Erstmal, das Projekt könnte sowas leicht wieder einbauen.
Bei den Tests ersetzte ich karma und PhantomJS mit mocha und jsdom, was dann leider einige Abhängigkeiten nach sich zog. Die direkte Abhängigkeitsliste ist aber nun hübsch klein. Vorher:
"dependencies": {
"@fortawesome/fontawesome-free": "^6.6.0",
"jquery": "^3.4.1"
},
"devDependencies": {
"autoprefixer": "^8.6.5",
"babel-core": "^6.26.3",
"babel-loader": "^7.1.5",
"babel-polyfill": "^6.26.0",
"babel-preset-env": "^1.7.0",
"css-loader": "^0.28.11",
"eslint": "^4.19.1",
"eslint-config-standard": "^11.0.0",
"eslint-plugin-import": "^2.17.3",
"eslint-plugin-node": "^6.0.1",
"eslint-plugin-promise": "^3.8.0",
"eslint-plugin-standard": "^3.1.0",
"extract-text-webpack-plugin": "^3.0.2",
"file-loader": "^1.1.11",
"husky": ">=6",
"karma": "^4.1.0",
"karma-chrome-launcher": "^2.2.0",
"karma-mocha": "^1.3.0",
"karma-mocha-reporter": "^2.2.5",
"karma-phantomjs-launcher": "^1.0.4",
"karma-webpack": "^2.0.13",
"less": "^3.9.0",
"less-loader": "^4.1.0",
"lint-staged": ">=10",
"mocha": "^5.2.0",
"postcss-loader": "^2.1.6",
"style-loader": "^0.23.1",
"url-loader": "^1.1.2",
"webpack": "^3.12.0",
"webpack-dev-server": "^2.11.5"
},
Jetzt:
"dependencies": {
"@fortawesome/fontawesome-free": "^6.7.2"
},
"devDependencies": {
"eslint": "^9.25.1",
"jsdom": "^17.0.0",
"jsdom-global": "^3.0.2",
"less": "^3.13.1",
"mocha": "^11.1.0",
"rollup": "^4.40.0"
},
Das Ausmaß der Änderungen zeigt aber auch, dass der vorherige Umbau nur mit einigem Aufwand zu integrieren war. Den vom Projekt zu verlangen anstatt das selbst zu stemmen konnte das Rückspielen der sonstigen Änderungen nur blockieren.
Das waren die Kernzüge meiner Umbauten an Shariff, neben notwendigen Bugfixes und sonstigen Details. Mein Ziel ist nicht, Shariff selbst zu übernehmen, sondern das ganze soll dem aktiven Fork shariff-plus zugutekommen – oder dem Ursprungsprojekt, wenn dort Interesse ist. Aber bisher ist nur ein PR für shariff-plus auf.
Es ist auch nicht zwingend, dass der PR akzeptiert wird. Divergenz vom Originalprojekt kann immer auch ein Fehler sein, wenn von dort doch noch Änderungen kommen wird dann alles schwieriger. Die Lösung mit den Symlinks für die Demo funktioniert noch dazu meines Wissens nicht direkt auf Windows, sondern braucht etwas Konfiguration. Die Erstellung einer minifizierten Quellcodedatei fehlt auch noch, sie müsste bei Bedarf nachgereicht werden.
Doch so oder so, für mich war das ein interessanter Umbau. Ich sah es auch als Test, ob meine Annahme richtig ist und man Buildpipelines tatsächlich weitgehend vermeiden kann, zumindest bei solchen Projekten. Oder ob ich irgendwelche Anforderungen übersehe, die das blockierten. Mein Fazit: Klar kann man sie vermeiden, und die Wartbarkeit des Projekts profitiert von der Änderung massiv. Der Effekt würde nur noch stärker werden, wenn man Less zugunsten von echtem CSS auch noch rauswirft.