Kudos for writing a descriptive userscript doc comment, with a version (I don't bother doing this with my userscripts, but probably should).
For small userscripts, particularly small ones written for yourself such as is the case here, code quality doesn't matter. The important thing is that the script works as intended and can be written easily.
Using a GPT is a pragmatic way to write such a script, but I'm guessing you didn't use a GPT here to make it more educational, which is fine.
So I'll review onward, artificially assuming that quality matters.
Here's an initial rewrite (comment is kept the same), followed by analysis:
for (const node of document.querySelectorAll("time")) { node.textContent = node.attributes.datetime.textContent; } const observer = new MutationObserver(changes => { for (const change of changes) { for (const node of change.addedNodes) { if (node.tagName === "TIME") { node.textContent = node.attributes.datetime.textContent; } } } }); observer.observe(document.body, { childList: true, subtree: true, });
Avoid XPath and document.getElementsByTagName
in favor of document.querySelector
and document.querySelectorAll
. XPath in particular is extremely cumbersome to work with. The virtue of XPath is that it's more powerful than CSS selectors, and can be used for rare cases when you need the power. But 99% of the time it's overkill for a simple tag selection like the one you're doing, which CSS selectors are perfectly suited for.
If document.querySelector
is used repeatedly, it's fine to alias it in small scripts where including jQuery would be excessive:
const $ = s => document.querySelector(s); const $$ = s => document.querySelectorAll(s);
Variable names should be clear--callback
and arr
are too generic in this context. Only use names like this when writing a function that can accept literally any callback or array. Here, handleMutation
and timeNodes
would be more appropriate.
It's OK to break out variables for conditions and arguments when doing so adds clarity. But more variables means more state to have to reason about. Here, you can inline single-use variables into the .observe()
call parameter list. This saves having to make the mental connection between callback
, config
and body
and the mutation observer that uses them. This is trivial here, but in more complex functions, reducing single-use state can lighten the cognitive load.
Notice that I did keep the observer
variable, which helps reduce the visual clutter of chaining .observe()
from the new MutationObserver
call. It's also common to want to disconnect observers, so the extra variable helps facilitate more operations on it in the future. New objects make sense to add to state, but parameters don't (usually).
Here are some further nitpicks, with the caveat that none of these matter much in userscripts:
- Use
for .. of
, .forEach
, .map
and .filter
instead of C-style counter-based for
loops. - Use
const
instead of let
except when you need to reassign a variable. - Always use
===
for comparisons, never ==
, avoiding type coercion footguns. - Prefer
.textContent
to .innerText
. - If you're exclusively setting
childList: true
in the mutation observer config, there's no need to explicitly check if( change.type == "childList") {
, which is the only possibility. - Use Prettier to format your code consistently.
- Remove all unused variables, like the
observer
callback parameter.
You mention:
I am wondering if I can make it more concise
As food for thought, I often write these sort of userscripts like this:
// ... left as is ... // @run-at document-start // ==/UserScript== (function poll() { requestAnimationFrame(poll); document.querySelectorAll("time") .forEach(e => e.textContent = e.attributes.datetime.textContent); })();
Advantages:
- Extremely easy to write and read, just one line once you're familiar with the IIFE/RAF loop boilerplate.
- Using
@run-at document-start
and a tight animation frame poll removes the "blink" effect that the mutation observer suffers from, where the wrong time format is shown for a split second before correction.
The only downside is that the script has to do more work. But computers are fast and the RAF is throttled way back when the tab is inactive, so I doubt there'll be any noticeable performance/battery impact.
Nowadays, GPTs can trivially generate a mutation observer, which demotivates the sloppier approach somewhat. Even so, I can still hammer out the above code into a console as fast as a GPT can, and the succinctness is aesthetically pleasing, so I still use it.
Furthermore, the CPU work can be avoided or mitigated by disconnecting the poll loop once it has modified the elements you want to modify, stopping it after a few seconds, or using a less frequent setInterval
. This gives you the best of all worlds, but is use-case specific.