|
// ==UserScript== |
|
// @name Require.js Warning |
|
// @namespace https://www.controlaltdelete.dev/ |
|
// @author Michiel Gerritsen |
|
// @version 1.0 |
|
// @description Warn about require.js usage |
|
// @match *://*/* |
|
// @run-at document-start |
|
// @grant none |
|
// ==/UserScript== |
|
|
|
(function() { |
|
// Private variables in closure |
|
let cssInjected = false; |
|
const attemptedModules = new Set(); |
|
let requireCallCount = 0; |
|
|
|
// Store any existing require/define functions |
|
const originalRequire = window.require; |
|
const originalDefine = window.define; |
|
|
|
// Create a function that returns undefined for detection attempts |
|
function createProxyFunction(name, implementation) { |
|
return function(...args) { |
|
// Check if this is likely a detection attempt |
|
const stack = new Error().stack || ''; |
|
const isDetectionAttempt = stack.includes('eval') || |
|
stack.includes('Function(') || |
|
args.length === 0; |
|
|
|
if (isDetectionAttempt) { |
|
// Make it appear as if require/define don't exist |
|
return undefined; |
|
} |
|
|
|
// Otherwise run the warning implementation |
|
return implementation.apply(this, args); |
|
}; |
|
} |
|
|
|
// Private functions in closure |
|
function injectCSS() { |
|
if (cssInjected) return; |
|
cssInjected = true; |
|
|
|
const style = document.createElement('style'); |
|
style.textContent = ` |
|
.require-warning-banner { |
|
background-color: #dc3545; |
|
color: white; |
|
padding: 16px; |
|
margin: 8px 0; |
|
border-radius: 4px; |
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; |
|
font-size: 16px; |
|
line-height: 1.5; |
|
font-weight: bold; |
|
text-align: center; |
|
} |
|
.require-warning { |
|
background-color: #fff3cd; |
|
color: #856404; |
|
padding: 12px; |
|
margin: 8px 0; |
|
border: 1px solid #ffeeba; |
|
border-radius: 4px; |
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; |
|
font-size: 14px; |
|
line-height: 1.5; |
|
} |
|
.require-warning-stack { |
|
margin-top: 8px; |
|
font-family: monospace; |
|
font-size: 12px; |
|
color: #666; |
|
white-space: pre-wrap; |
|
} |
|
.require-warning-stack a { |
|
color: #0366d6; |
|
text-decoration: none; |
|
} |
|
.require-warning-stack a:hover { |
|
text-decoration: underline; |
|
}`; |
|
document.head.appendChild(style); |
|
} |
|
|
|
function createBanner() { |
|
const banner = document.createElement('div'); |
|
banner.className = 'require-warning-banner'; |
|
banner.innerHTML = `Require.js is still being called! (<span class="require-call-count">${requireCallCount}</span> times) If you want to update your site to Hyvä you should migrate this javascript to Alpine.js`; |
|
|
|
const maincontent = document.querySelector('#maincontent'); |
|
if (maincontent) { |
|
maincontent.insertBefore(banner, maincontent.firstChild); |
|
} |
|
} |
|
|
|
function updateCallCount() { |
|
const countElement = document.querySelector('.require-call-count'); |
|
if (countElement) { |
|
countElement.textContent = requireCallCount; |
|
} |
|
} |
|
|
|
function getStackTrace() { |
|
const error = new Error(); |
|
return error.stack |
|
?.split('\n') |
|
.filter(line => !line.includes('at window.require')) |
|
.slice(3) // Skip Error, getStackTrace, and warnAboutModule |
|
.map(line => line.trim()) |
|
.join('\n'); |
|
} |
|
|
|
function createWarning(message, stackTrace) { |
|
injectCSS(); |
|
|
|
// Only create banner once when first warning appears |
|
if (!document.querySelector('.require-warning-banner')) { |
|
createBanner(); |
|
} |
|
|
|
const warning = document.createElement('div'); |
|
warning.className = 'require-warning'; |
|
|
|
const messageDiv = document.createElement('div'); |
|
messageDiv.textContent = message; |
|
warning.appendChild(messageDiv); |
|
|
|
if (stackTrace) { |
|
const stackDiv = document.createElement('div'); |
|
stackDiv.className = 'require-warning-stack'; |
|
const formattedStack = stackTrace; |
|
stackDiv.innerHTML = formattedStack; |
|
warning.appendChild(stackDiv); |
|
} |
|
|
|
const maincontent = document.querySelector('#maincontent'); |
|
if (maincontent) { |
|
maincontent.insertBefore(warning, maincontent.querySelector('.require-warning-banner').nextSibling); |
|
} |
|
} |
|
|
|
function warnAboutModule(modules) { |
|
const stackTrace = getStackTrace(); |
|
|
|
if (typeof modules === 'string') { |
|
if (attemptedModules.has(modules)) return; |
|
attemptedModules.add(modules); |
|
createWarning( |
|
`Warning: Attempted to load "${modules}" via require.js, which is no longer supported.`, |
|
stackTrace |
|
); |
|
} else if (Array.isArray(modules)) { |
|
const newModules = modules.filter(m => !attemptedModules.has(m)); |
|
if (newModules.length === 0) return; |
|
|
|
newModules.forEach(m => attemptedModules.add(m)); |
|
createWarning( |
|
`Warning: Attempted to load modules via require.js, which is no longer supported:\n` + |
|
newModules.map(m => `• ${m}`).join('\n'), |
|
stackTrace |
|
); |
|
} |
|
} |
|
|
|
function logConsoleWarning() { |
|
// Only log once |
|
if (window._requireWarningLogged) return; |
|
window._requireWarningLogged = true; |
|
|
|
console.warn( |
|
'%c⚠️ Require.js Usage Detected! ⚠️\n' + |
|
'%cIf you want to update your site to Hyvä you should migrate this javascript to Alpine.js', |
|
'font-size: 20px; font-weight: bold; color: #dc3545;', |
|
'font-size: 16px; color: #856404;' |
|
); |
|
} |
|
|
|
// Main require implementation |
|
const requireImplementation = function(modules, callback) { |
|
requireCallCount++; |
|
const originalError = new Error(`Require.js usage detected: ${Array.isArray(modules) ? modules.join(', ') : modules}`); |
|
|
|
logConsoleWarning(); |
|
console.warn( |
|
'%cRequire.js module(s) requested: %c' + (Array.isArray(modules) ? modules.join(', ') : modules) + |
|
'\n%cTotal calls: %c' + requireCallCount, |
|
'color: #856404; font-weight: bold;', |
|
'color: #dc3545; font-weight: bold;', |
|
'color: #856404; font-weight: bold;', |
|
'color: #dc3545; font-weight: bold;', |
|
'\n', |
|
originalError |
|
); |
|
|
|
warnAboutModule(modules); |
|
updateCallCount(); |
|
|
|
if (typeof callback === 'function') { |
|
const mockModules = Array.isArray(modules) |
|
? modules.map(() => ({})) |
|
: [{}]; |
|
callback.apply(null, mockModules); |
|
} |
|
}; |
|
|
|
// Main define implementation |
|
const defineImplementation = function(name, deps, callback) { |
|
requireCallCount++; |
|
const originalError = new Error('Define call detected' + |
|
(typeof name === 'string' ? `: ${name}` : '')); |
|
|
|
logConsoleWarning(); |
|
console.warn( |
|
'%cRequire.js define called: %c' + (typeof name === 'string' ? name : 'anonymous module') + |
|
'\n%cTotal calls: %c' + requireCallCount, |
|
'color: #856404; font-weight: bold;', |
|
'color: #dc3545; font-weight: bold;', |
|
'color: #856404; font-weight: bold;', |
|
'color: #dc3545; font-weight: bold;', |
|
'\n', |
|
originalError |
|
); |
|
|
|
if (Array.isArray(name)) { |
|
deps = name; |
|
name = undefined; |
|
} |
|
if (Array.isArray(deps)) { |
|
warnAboutModule(deps); |
|
} |
|
updateCallCount(); |
|
}; |
|
|
|
// Only set up our proxies if require/define don't already exist |
|
if (!originalRequire) { |
|
window.require = createProxyFunction('require', requireImplementation); |
|
} |
|
|
|
if (!originalDefine) { |
|
window.define = createProxyFunction('define', defineImplementation); |
|
// Still maintain AMD compatibility for actual usage attempts |
|
window.define.amd = {}; |
|
} |
|
})(); |