Remove Prototype.js / Scriptaculous, replace with vanilla JS + compatibility shim - #5612
Open
mattdavenport wants to merge 30 commits into
Open
Conversation
Rewrote the migration doc from a planning document to an implementation status tracker. Documents all completed work (Phases 0-2, Waves 1-7), remaining tasks (~54 .phtml files, Phase 3 removal, ExtJS dependency, testing), architecture decisions (config flag, shim, deprecation wrapper), and the pattern replacement reference. Includes testing checklist and release timeline. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
21 additional admin template files rewritten to vanilla JS: system/config (edit, js, array, csp, synchronize), system/convert (process, run, wizard), system/email/template/edit, system/shipping (applicable_country, ups), system/variable/js, customer/tab/addresses, customer/validatevat, dashboard, downloadable, paypal config, promo, rating, report, store switcher, usa/dhl, weee, widget. ~13 files still contain Prototype patterns in deeply intertwined PHP-generated JS — these work with the compatibility shim. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Removes ~15,700 lines of legacy JavaScript libraries: Deleted files: - js/prototype/prototype.js (7,595 lines) - js/prototype/window.js, window_effects.js, window_ext.js - js/prototype/effects.js, tooltip.js, tooltip_manager.js - js/prototype/debug.js, extended_debug.js, window_readme.txt - js/scriptaculous/ (8 files: builder, controls, dragdrop, effects, scriptaculous, slider, sound, unittest) Kept in js/prototype/: - prototype-shim.js (compatibility layer for extensions) - prototype-deprecation.js (deprecation warnings) - validation.js (rewritten to vanilla JS in Wave 6) - windows/ directory (used by Dialog) Layout XML updates: - frontend/base/default/layout/page.xml — prototype → shim, removed scriptaculous - frontend/rwd/default/layout/page.xml — same - adminhtml/default/default/layout/main.xml — prototype + scriptaculous → shim only - adminhtml/default/default/layout/dataflow.xml — prototype → shim - frontend/*/layout/catalog.xml — removed scriptaculous.js reference Config change: - Default prototype_mode changed from "full" to "shim" - Sites can still set "full" to restore old behavior (but the files are gone, so only "shim" or "none" are meaningful now) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The Template.evaluate() method relies on gsub passing match groups
as an array (match[1], match[2], match[3]) to the replacement
callback, matching Prototype.js behavior. The previous implementation
passed native String.replace arguments directly, which broke
Template #{variable} interpolation.
Verified with 84-point smoke test suite covering: globals, Class.create
with inheritance, Object.extend, $H, $w, $A, Ajax, Event, Form,
Template, String/Array/Number extensions, Effects, Function extensions.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Ran the full Cypress suite against the shim and fixed all JS errors: - Ajax.Autocompleter: lightweight reimplementation for admin global search - document.on(): event delegation support (eventName, selector, handler) - Array.prototype.intersect: needed by configurable swatches - Element.store/retrieve: per-element data storage for input counters - Element.getHeight/getWidth/disable/enable: missing DOM helpers - Null guard in customer addresses setActiveItem (pre-existing bug) Cypress results: 117/122 pass (same 4 pre-existing failures as base branch) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove Prototype.js dependencies from menu.js, payment.js, product_options.js, telephone.js, weee.js, opcheckout.js, review.js, giftmessage.js, minicart.js, app.js and configurableswatches scripts. Adds stripScripts/evalScripts shims to prototype-shim.js. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace Prototype Class.create / $H / .each / .set/.unset / .toQueryString / .up/.down/.select / .setStyle in adminhtml phtml templates: store/switcher/enhanced, permissions/role_users_grid_js, permissions/user_roles_grid_js, importexport/import/form/before, bundle/edit/bundle, downloadable/samples, catalog/options/type/file - Rewrite JS modules: input-counter, ext-tree, browser, grid, tabs, tools, loader, product, rules, packaging, wysiwyg/widget - Add window.js (replaces Prototype-based varienWindow/Dialog) - Add sortable.js (replaces Scriptaculous Sortable.create) - Extend prototype-shim.js with missing APIs - Migrate category edit/tree phtml and widget/instance phtml - Fix PHP blocks: Review/Add, Review/Edit, Adminpass renderer Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…anilla
Introduces the Prototype.js compatibility switch and continues the removal:
- Core/Helper/Js: dev/js/prototype_mode config with isPrototypeMode{Full,Shim,None}
- Page/Html/Head: load Prototype / the shim according to the selected mode
- system config source Dev/Prototypemode
- js/prototype/prototype-shim: additional compatibility shims
- js/mage/adminhtml: loader, tabs, form, browser, window, variables,
product/composite/configure, sales, wysiwyg (widget + tinymce setup)
- admin login / forgot-password templates and catalog product view options JS
…ions Converts remaining adminhtml blocks/templates and varien/adminhtml JS off Prototype.js, and fixes several runtime regressions found while testing the admin and storefront: - Paypal admin System Config (Country/Hint/Location): port document.observe, $$, .each, Event.observe and friends to vanilla; fix an ASI hazard where the trailing IIFE was parsed as a call on the preceding function-expression assignment, leaving togglePaypalSolutionConfigureButton undefined - api2 permissions roles grid: grid.rows is now a native array, use forEach instead of Prototype's .each - js/varien/autocomplete.js: keep the polling observer running while the field has focus so typing past minChars shows suggestions without needing to blur and re-focus; stop observing on blur instead of on hide - js/mage/adminhtml/sales.js validateVat: guard the missing customer-group element so VAT validation works on guest orders - giftmessage inline (onepage checkout): re-execute injected <script> elements after innerHTML (restores the former Ajax.Updater evalScripts:true) so the gift-options section is hidden by default instead of always visible - Customer::validate(): only validate the password when it is actually being set (new customer, explicit change, or a provided value) so a logged-in customer is no longer redirected to account/edit on checkout; validate only the date part of dob to avoid a false "not a valid date" for stored datetimes Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Make 'full' (Prototype.js + Scriptaculous) the default JS loading mode instead of 'shim', so out-of-the-box behaviour matches the admin field comment and keeps legacy scripts working until a site opts into the lighter shim. Align config.xml, the Mage_Core_Helper_Js fallback and the system.xml comment, and correct the size figures in that comment to the real uncompressed values (~73KB shim vs ~377KB full). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add back the real Prototype.js + Scriptaculous library files and the Head block logic that swaps the compatibility shim for them when prototype_mode is 'full', with unit tests covering the full/shim/none selection. Also fix admin AJAX templates surfaced during the migration: send form_key/content-type on category refresh, show/hide the loader on category move and tree save, and handle a cancelled attribute-set group prompt. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Rewrite the Ext.lib.Event and Ext.lib.Ajax adapters in ext-tree.js to use native DOM events and XMLHttpRequest instead of Prototype's Event/Ajax/Form globals, so the admin tree widgets work under prototype_mode=none. Because loader.js could no longer inject isAjax/form_key via its Ajax.Request monkey-patch, Ext.lib.Ajax now calls the same _openMageAjaxUrl / _openMageInjectFormKey helpers itself (guarded), fixing the 302 on category tree node expand. Also fix latent gaps in this stripped ExtJS build that surfaced once the trees load without Prototype: - define the missing Ext.util.DelayedTask (inline node editing threw 'is not a constructor', e.g. renaming an attribute-set group); - make El.fly / El.get resilient to the beforeunload teardown of El._flyweights / El.cache (URL rewrite category click navigates via window.location, racing the node-select addClass -> 'reading _global'). Replace Prototype-isms in the tree/grid templates (Object.clone -> Object.assign, grid rows .each -> .forEach) and stop loading the now-dead extjs/fix-defer.js in none mode (it only reconciles Prototype's defer). Regenerate ext-tree.min.js. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Convert the last unguarded Prototype.js/Scriptaculous usages in USPS admin
config blocks and checkout templates to vanilla JS, so prototype_mode=none
no longer throws on these pages. Guarded dual-path code (loader.js, js.js,
salesrulejs.phtml) and the Prototype/Scriptaculous library files are left
untouched — they only run in full/shim mode.
- js/mage/adminhtml/usps_config.js: Event.observe / document.observe('dom:loaded')
-> addEventListener / DOMContentLoaded
- Usps/AbstractTestButton.php, Usps/Createdimensions.php: document.observe
-> DOMContentLoaded, new Ajax.Request -> fetch (POST + URLSearchParams),
preserving the result/resultDiv/button callback contract
- usa/checkout/address/verification.phtml: full <script> rewrite —
$() -> getElementById, .observe -> addEventListener, Ajax.Request -> fetch,
.evalJSON -> JSON.parse, .update -> innerHTML, new Element/.insert ->
createElement/appendChild, .show/.hide -> style.display,
.addClassName/.removeClassName -> classList, .each -> forEach,
Object.extend -> Object.assign
- catalog/product/list/upsell.phtml: $$() -> querySelectorAll (commented block)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two latent Prototype.js dependencies surfaced when running the Cypress
suite against a prototype_mode=none store.
1) "Template is not defined" — replace Prototype's Template class
(new Template(text, syntax).evaluate(data)) with a plain
String.replace of the {{key}} / #{key} placeholders:
- Adminhtml/Block/Cms/Page/Edit.php (saveAndContinueEdit)
- template/widget/instance/js.phtml (setSettings)
- template/weee/renderer/tax.phtml (add row)
- template/eav/attribute/options.phtml (base + openmage)
All other *.evaluate() call sites already use custom vanilla template
objects, not Prototype, so they are left untouched.
2) Swatch shows the wrong product image — in product-media.js
getSwatchImage() step 3, the original Prototype code used
`each(... return false)`, which does NOT break the loop (only
`throw $break` does), so it iterated all compatible products and the
LAST match won. The migration changed this to `some(... return true)`,
which breaks on the FIRST match, selecting a different (visually
identical but differently named) image. Restore the last-wins
behaviour with a non-breaking forEach.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ess Sonar/PHPStan/ECS/spellcheck findings CodeQL (23 high alerts): - prototype-shim.js: stripTags/stripScripts/extractScripts/evalScripts now use inert <template> parsing; align ScriptFragment with upstream Prototype - varien/js.js getInnerText: clone node + drop script/style instead of regex - uploader/instance.js: drop _XSSFilter regex, HTML-escape template vars and use textContent for progress/error messages - varien/form.js setDataToChild: build select/input via DOM APIs; extraChildParams parsed as attributes on an inert template - RegionUpdater (varien + adminhtml form.js): assign option.text directly - configure.js: collect popup scripts from DOM instead of regexing response - widget.js: create options container via createElement - tools.js: DOM-built image preview; setLocation now blocks non-HTTP(S) URLs - grid.js: route reload/export navigation through setLocation - opcheckout.js: _opcStripTags via template parsing - packaging.js: drop innerHTML regex fallback in _getElementText SonarCloud reliability (5 bugs): - varien/js.js: keep Autocomplete instance reference - loader.js: collapse identical if/else branches - telephone.js: regex group no longer matches empty string - file.phtml: label + onchange for delete checkbox (a11y) PHPStan: strict comparison in Menu.php, typed iterables in Prototypemode and Head, drop redundant array_values in HeadTest ECS: single-quote fixes in Bundle Attributes blocks Spellcheck: add migration doc terms to wordlist
Wrap code identifiers and file names in backticks (pyspelling ignores code spans) and add remaining prose terms to the wordlist. Verified locally with pyspelling against .github/spellcheck.yml.
- Rector: SimplifyQuoteEscapeRector on PR-touched inline-JS PHP blocks, constant spacing, drop deprecated ReflectionMethod::setAccessible in HeadTest (all from vendor/bin/rector process --config .rector.php) - file.phtml: escapeHtml() around the translated Delete label; clears Sonar phpsecurity:S5131 (translations are DB/CSV-sourced) and matches surrounding template style
Prototype shim (js/prototype/prototype-shim.js): - Ajax.Request: send GET parameters (fetch used the unmutated local url); route callback exceptions to onException instead of misreporting them as status-0 failures - Ajax.Updater: accept missing options, honor string insertion positions, update content at onComplete time (Prototype order), eval response scripts exactly once (previously twice with evalScripts:true, once without), and never re-run scripts already present in the target; clone options so PeriodicalUpdater cycles don't stack wrappers - Class.create: only inject $super when the override declares it as its first parameter; unconditional injection corrupted ordinary overrides - Form: getElements returns all controls so enable() can undo disable(); serialize/serializeElements follow the Prototype options contract (hash default, submit handling, file exclusion, multiselect arrays) - Element#setValue: Prototype checkbox/radio truthiness semantics and multi-select array support; explicit string coercion for options - stopObserving: support (name) and () overloads via an observe registry; add window.stopObserving; drop duplicate EP.select definition Vanilla rewrites: - checkout/review.js: treat non-2xx responses as failures instead of enabling Place Order on an error page; serialize repeated field names (billing[street][]) and multi-selects without dropping values - composite/configure.js: preserve all selected values of bundle multiselects across confirm/reopen; measure popup height after making it visible so centering works - opcheckout.js + downloadable.phtml: populate event.memo alongside detail so Prototype-era observers keep working in none mode - telephone.js: remove dead keypress bindings and empty handler PHP: - Js.php: normalize unknown prototype_mode values to full instead of silently degrading into an implicit shim-like asset state - Head.php: load prototype-deprecation.js after prototype.js in full mode (Phase 0 instrumentation was orphaned); cover both in HeadTest Docs: prototype-migration.md ext-tree.js rows updated (decoupled).
- New mode-aware spec asserts the emitted script loadout is consistent for whichever prototype_mode the site under test runs, and in shim mode exercises the prototype-shim.js API contract: Class.create $super gating, Ajax.Request GET parameters on the wire, Ajax.Updater single script evaluation, Form serialization of repeated names and multiselects, enable/disable round-trip, setValue checkbox and multiselect semantics, and the stopObserving bulk overload. - CI: run the full Cypress suite two more times with dev/js/prototype_mode overridden to shim and none via the environment config loader, in a separate job so the existing required check name is unchanged.
mattdavenport
force-pushed
the
feature/prototype-removal-compatibility-layer
branch
from
July 30, 2026 14:04
206838c to
dd887cc
Compare
|
empiricompany
approved these changes
Jul 30, 2026
Contributor
Author
|
@sreichel I know this is a big one, but could you take a look at this when you have some time? Thanks! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Summary
Migrates OpenMage off Prototype.js (1.7.3) + Scriptaculous (1.8.2) in favor of native browser JavaScript, controlled by a new
dev/js/prototype_modeconfig flag (full|shim|none).full— load Prototype/Scriptaculous (legacy behavior), plus theprototype-deprecation.jsinstrumentation wrapper (inert unless activated via?protodebug=1).shim— load a lightweight compatibility shim (prototype-shim.js) that re-implements the surface area third-party modules tend to rely on ($(),$$(),Element.*,Ajax.*,Class.create, etc.) without pollutingString/Arrayprototypes the same way.none— ship no Prototype layer at all. Core OpenMage JS has been rewritten to vanilla and verified to work without it.Default is
full, so the upgrade is fully backwards-compatible: existing stores keep the real Prototype/Scriptaculous libraries until they opt intoshimornone. Unknown/invalid config values are normalized tofull. The planned rollout (seedocs/prototype-migration.md) isfullnow →shimdefault in the next major → library removal in v22.0.Scope
js/varien/*,js/mage/*,js/mage/adminhtml/*)..phtmltemplates with inline JS converted.ext-tree.js(admin category tree + URL rewrites) decoupled from Prototype.js — works in all three modes.fullmode; physical removal (Phase 3) is deferred to v22.0 per the phase plan.docs/prototype-migration.md.Notes for reviewers
prototype_mode=shimandprototype_mode=none.Ajax.RequestGET parameters,Ajax.Updaterscript-eval/callback ordering,Class.create$supergating,Form.serialize/enable/disablecontracts,setValuecheckbox/multiselect semantics,stopObservingoverloads, plus checkout/bundle regressions (review.jsHTTP error handling, composite configure multiselect restore) were all fixed.upstream/mainimmediately before opening this PR; conflicts touchedjs/mage/adminhtml/form.js, two.phtmltemplates, and three upstream-deleted Authorize.net DirectPost files (deletions accepted).docs/prototype-migration.mdfor the phase-by-phase breakdown and remaining items.Test plan
dev/js/prototype_mode=full(default) — full guest checkout funnel (configurable product w/ swatches → cart → onepage → order placed), zero console errorsdev/js/prototype_mode=shim— same funnel, order placed,Prototype.Version === '1.7.3-shim', no Scriptaculous loadeddev/js/prototype_mode=none— same funnel, order placed,window.Prototype/window.$undefinedprototype_modefield; zero console errors in every modefull,shim, andnone(all green), plus a shim API contract specshim— cannot be tested exhaustively from core; this is exactly why the mode flag exists: stores with Prototype-dependent extensions stay onfull(the default) orshim, andnoneis opt-in. Extension authors are invited to test againstshim.ext-tree.jsdecoupled from Prototype)Class.createwith/without$super, Form serialize/enable/disable, setValue, stopObserving — covered by the in-browser contract spec running in CI shim mode🤖 Generated with Claude Code