Override configurable.js within the Configurable Products Module. The following code can be placed at around line 370 of configurable.js:
if (i == 0) {
this.options.values[attributeId] = options[i].id;
}
This is placed at the end of options loop so that the default option is set just after the first option is created for each attribute / select.
In the case that options were set in url the code for updating defaults was needed to run again to set the values correctly which can be achieved by adding below just outside of the loop:
if (window.location.href.indexOf('#') !== -1) {this._parseQueryParams(window.location.href.substr(window.location.href.indexOf('#') + 1));}
This is placed just after the for loop for options so that it's run just once at the end of the option creation.
So _fillSelect within configurable.js now should look like below:
_fillSelect: function (element) {
var attributeId = element.id.replace(/[a-z]*/, ''),
options = this._getAttributeOptions(attributeId),
prevConfig,
index = 1,
allowedProducts,
i,
j;
this._clearSelect(element);
element.options[0] = new Option('', '');
element.options[0].innerHTML = this.options.spConfig.chooseText;
prevConfig = false;
if (element.prevSetting) {
prevConfig = element.prevSetting.options[element.prevSetting.selectedIndex];
}
if (options) {
for (i = 0; i < options.length; i++) {
allowedProducts = [];
if (prevConfig) {
for (j = 0; j < options[i].products.length; j++) {
// prevConfig.config can be undefined
if (prevConfig.config &&
prevConfig.config.allowedProducts &&
prevConfig.config.allowedProducts.indexOf(options[i].products[j]) > -1) {
allowedProducts.push(options[i].products[j]);
}
}
} else {
allowedProducts = options[i].products.slice(0);
}
if (allowedProducts.length > 0) {
options[i].allowedProducts = allowedProducts;
element.options[index] = new Option(this._getOptionLabel(options[i]), options[i].id);
if (typeof options[i].price !== 'undefined') {
element.options[index].setAttribute('price', options[i].prices);
}
element.options[index].config = options[i];
index++;
}
//new code here - updates the value to first option
if (i == 0) {
this.options.values[attributeId] = options[i].id;
}
}
//new code here checks if configurations are set in url and resets them if needed
if (window.location.href.indexOf('#') !== -1) {this._parseQueryParams(window.location.href.substr(window.location.href.indexOf('#') + 1));}
}
},
Original file:
vendor/magento/module-configurable-product/view/frontend/web/js/configurable.js
Location when overridden in theme:
app/design/frontend/ThemeVendor/Theme/Magento_ConfigurableProduct/web/js/configurable.js