Describe the bug
Vite 8 production build emits JavaScript with changed control flow for a legacy jQuery-plugin-style function that uses var declarations inside an if branch.
The source code conditionally passes either defaults or normalized options to a constructor, and conditionally calls a method only for string command calls. Vite 8 emits code that always passes defaults and always calls the method.
This is a runtime-breaking semantic change. In our application, the runtime failure started after upgrading from [email protected] to [email protected]. A reduced case shows the underlying miscompile is already reproducible with [email protected] and remains reproducible with [email protected]. The same reduced source emits correctly with [email protected].
This originally surfaced in a vendored navgoco jQuery plugin after upgrading a Vite 8 project from 8.1.0 to 8.1.4. The app failed with TypeError: $.cookie is not a function because the generated code ignored the normalized options object where save had been disabled. The 8.1.4 upgrade likely made the pre-existing miscompile observable through changed chunking/dependency output, but the reduced example demonstrates a standalone semantic miscompile.
Reproduction
Minimal source:
const $ = (value) => value;
$.fn = {};
$.extend = Object.assign;
install($);
window.result = $.fn.navgoco;
function install($) {
$.fn.navgoco = function(options) {
if (typeof options === 'string' && options.charAt(0) !== '_' && options !== 'init') {
var callback = true,
args = Array.prototype.slice.call(arguments, 1);
} else {
options = $.extend({}, $.fn.navgoco.defaults, options || {});
if (!$.cookie) {
options.save = false;
}
}
return this.each(function(idx) {
var $this = $(this),
obj = $this.data('navgoco');
if (!obj) {
obj = new Plugin(this, callback ? $.fn.navgoco.defaults : options, idx);
$this.data('navgoco', obj);
}
if (callback) {
obj[options].apply(obj, args);
}
});
};
}
function Plugin(el, options, idx) {
this.el = el;
this.options = options;
this.idx = idx;
}
Config:
import {defineConfig} from 'vite';
export default defineConfig({
build: {
minify: false,
rollupOptions: {
input: {
main: './src/main.js',
},
output: {
entryFileNames: '[name].js',
},
},
},
});
Commands:
Actual behavior
With [email protected], dist/main.js contains:
if (!obj) {
obj = new Plugin(this, $.fn.navgoco.defaults, idx);
$this.data("navgoco", obj);
}
obj[options].apply(obj, args);
This is not equivalent to the source.
The ternary callback ? $.fn.navgoco.defaults : options was reduced to $.fn.navgoco.defaults, and the if (callback) guard was removed.
Expected behavior
The generated output should preserve the source control flow:
if (!obj) {
obj = new Plugin(this, callback ? $.fn.navgoco.defaults : options, idx);
$this.data("navgoco", obj);
}
if (callback) {
obj[options].apply(obj, args);
}
This is what [email protected] emits for the same reduced source.
Version comparison
System Info
System:
OS: macOS
Node: 26.1+
Package manager:
npm: 11.13+
Additional context
The real-world source was vendor/navgoco/jquery.navgoco.js:
if (!obj) {
obj = new Plugin(this, callback ? $.fn.navgoco.defaults : options, idx);
$this.data('navgoco', obj);
}
if (callback) {
obj[options].apply(obj, args);
}
The generated Vite 8 output changed it to:
if (!obj) {
obj = new Plugin(this, $.fn.navgoco.defaults, idx);
$this.data("navgoco", obj);
}
obj[options].apply(obj, args);
Because this is a semantic miscompile in production output, it is hard for application code to defend against except by rewriting the legacy source pattern.
Describe the bug
Vite 8 production build emits JavaScript with changed control flow for a legacy jQuery-plugin-style function that uses
vardeclarations inside anifbranch.The source code conditionally passes either defaults or normalized options to a constructor, and conditionally calls a method only for string command calls. Vite 8 emits code that always passes defaults and always calls the method.
This is a runtime-breaking semantic change. In our application, the runtime failure started after upgrading from
[email protected]to[email protected]. A reduced case shows the underlying miscompile is already reproducible with[email protected]and remains reproducible with[email protected]. The same reduced source emits correctly with[email protected].This originally surfaced in a vendored
navgocojQuery plugin after upgrading a Vite 8 project from8.1.0to8.1.4. The app failed withTypeError: $.cookie is not a functionbecause the generated code ignored the normalizedoptionsobject wheresavehad been disabled. The8.1.4upgrade likely made the pre-existing miscompile observable through changed chunking/dependency output, but the reduced example demonstrates a standalone semantic miscompile.Reproduction
Minimal source:
Config:
Commands:
Actual behavior
With
[email protected],dist/main.jscontains:This is not equivalent to the source.
The ternary
callback ? $.fn.navgoco.defaults : optionswas reduced to$.fn.navgoco.defaults, and theif (callback)guard was removed.Expected behavior
The generated output should preserve the source control flow:
This is what
[email protected]emits for the same reduced source.Version comparison
[email protected]->[email protected][email protected]emits correct code[email protected]emits incorrect code[email protected]emits incorrect codeSystem Info
Additional context
The real-world source was
vendor/navgoco/jquery.navgoco.js:The generated Vite 8 output changed it to:
Because this is a semantic miscompile in production output, it is hard for application code to defend against except by rewriting the legacy source pattern.