userscripts icon indicating copy to clipboard operation
userscripts copied to clipboard

Proposal: new and better `UserStyle` metadata block

Open ACTCD opened this issue 8 months ago • 2 comments

I know I've raised:

  • #711

But I could still see the value of writing CSS code directly instead of wrapping it in .user.js:

  • Better syntax highlighting
  • CSS linting
  • Simpler and more straightforward
  • Directly utilize the relevant extension APIs

From my knowledge, some of the issues currently faced by UserStyle ecology:

  • Lack of true standardization and widespread adoption
  • The @match *://*/* conflict in metadata comment
  • Deprecated @-moz-document syntax

I hope it does:

  • Based on .css files instead of individual apps internal format
  • Use extension API registration only once for each single file
  • No complex splitting and parsing and acts like a normal css file (or userjs)
  • Accepted by most common editors with no errors
  • Proper syntax highlighting and markdown rendering (Like the following example)
  • The UserStyle metadata easy to write and parse within the file
  • Prefer match patterns over other matching rules (Safer and API friendly)
  • Not confusing and conflicting with various existing implementations

Evolution:

  • Using CSS comments becomes impractical due to the @match *://*/* conflict.
  • Using the @metadata liked At-rules is prone to linting errors
  • So a fairly straightforward idea would be to utilize the existing CSS syntax
  • It is quite compatible and meets all of the above considerations
  • This metadata ruleset will be parsed and stripped by the manager
  • Use Custom properties for metadata names to avoid linting errors

The following is a simple sample, for further improvement and standardization:

Existing one in the userscripts:

/* ==UserStyle==
@name        NewStyle-aefwe6zy
@description This is your new file, start writing code
@match       <all_urls>
@match       *://*/*
@match       *://*.foo.bar/*
==/UserStyle== */

body { color: red; }

New proposed:

userstyle-metadata {
	--name: "NewStyle-aefwe6zy";
	--description: "This is your new file, start writing code";
	--match: "<all_urls>";
	--match-2: "*://*/*";
	--match-3: "*://*.foo.bar/*";
	--version: "1.0.0";
	--download-url: "https://foo.bar/foo.user.css";
}

body { color: red; }

Footnotes: To avoid lint error like Unexpected duplicate "--match" (declaration-block-no-duplicate-custom-properties), metadata such as @match which can be declared repeatedly, can have any trailing suffix and will eventually be parsed as an array by the manager.

ACTCD avatar Jun 19 '25 04:06 ACTCD

For example, this is a simple parser implementation in the browser runtime:

const cssText = `
userstyle-metadata {
	--name: "NewStyle-aefwe6zy";
	--description: "This is your new file, start writing code";
	--match: "<all_urls>";
	--match-2: "*://*/*";
	--match-3: "*://*.foo.bar/*";
	--version: "1.0.0";
	--download-url: "https://foo.bar/foo{}}.user.css";
}
body { color: red; }
h1 { color: red; }`;

async function parseUserStyleText(cssText) {
	const stylesheet = new CSSStyleSheet();
	await stylesheet.replace(cssText);
	const cssTexts = [];
	let metadataRule;
	for (const rule of stylesheet.cssRules) {
		if (rule.selectorText === "userstyle-metadata") {
			metadataRule = rule;
			continue;
		}
		cssTexts.push(rule.cssText);
	}
	if (!metadataRule) throw Error("metadata not found");
	const mKeys = ["name", "description", "version", "download-url"];
	const metadata = {
		match: [],
	};
	for (const key of metadataRule.style) {
		const value = metadataRule.style
			.getPropertyValue(key)
			.replace(/^["']+|["']+$/g, "");
		if (key.startsWith("--match")) {
			metadata.match.push(value);
		}
		const mKey = key.slice(2);
		if (mKeys.includes(mKey)) {
			metadata[mKey] = value;
		}
	}
	return { metadata, css: cssTexts.join("\n") };
}

const result = await parseUserStyleText(cssText);

console.log(result);

{
  "metadata": {
    "match": [
      "<all_urls>",
      "*://*/*",
      "*://*.foo.bar/*"
    ],
    "name": "NewStyle-aefwe6zy",
    "description": "This is your new file, start writing code",
    "version": "1.0.0",
    "download-url": "https://foo.bar/foo{}}.user.css"
  },
  "css": "body { color: red; }\nh1 { color: red; }"
}

ACTCD avatar Jun 19 '25 22:06 ACTCD

And it could also be expanded to:

userstyle-metadata {
	--name: "NewStyle-aefwe6zy";
	--description: "This is your new file, start writing code";
	--match: "<all_urls>";
	--match-2: "*://*/*";
	--match-3: "*://*.foo.bar/*";
	--version: "1.0.0";
	--download-url: "https://foo.bar/foo.user.css";
}

body {
	--meta-uuid: "ac0d3cf8-d542-4830-abc9-94d851b6ff51";
	--meta-name: "change color 1";
	--meta-desc: "change the body color...";
	color: red;
}

h1 {
	--meta-uuid: "c393fdb0";
	--meta-name: "change color 2";
	color: red;
}

These additional metadata (will stripped by the manager), tracking the rulesets, and building a user interface to facilitate disabling some rulesets.

It's much easier than using comments and building a jsdoc-like parser.

ACTCD avatar Jun 22 '25 02:06 ACTCD