Skip to content

Vite 8 production build miscompiles conditional var control flow in legacy plugin wrapper #22933

Description

@doberkofler

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:

npm install [email protected]
npm run build

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bug: upstreamBug in a dependency of Vitep3-minor-bugAn edge case that only affects very specific usage (priority)

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions