You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Laravel 10 Vite执行npm run build后部分文件内容丢失

Laravel 10 + Vite构建时global.js函数被移除的问题原因分析

问题描述

在Laravel 10项目中使用Vite构建工具时,执行npm run build命令后,部分脚本编译正常,但global.js文件仅保留Swal.mixin代码块,其余所有函数全部丢失。即使将该文件单独添加到vite.config.js的input列表中,问题依然存在。

vite.config.js 配置

import laravel from 'laravel-vite-plugin';
import vue from '@vitejs/plugin-vue';

export default defineConfig({
    plugins: [
        laravel({
            input: [
                'resources/sass/app.scss',
                'resources/css/app.css',
                'resources/js/app.js',

                'resources/js/global.js',

                'resources/js/addresses.js',
                'resources/js/global.js',
                'resources/js/afip/padron.js',
                'resources/js/categories/index.js',
                'resources/js/categories/selectize_input.js',
                'resources/js/categories/show.js',
                'resources/js/clients/create.js',
                'resources/js/clients/index.js',
                'resources/js/login/login.js',
                'resources/js/notifications/index.js',
                'resources/js/providers/create.js',
                'resources/js/providers/index.js',
                'resources/js/users/index.js',
            ],
            refresh: true,
        }),
        vue({
            template: {
                transformAssetUrls: {
                    base: null,
                    includeAbsolute: false,
                },
            },
        }),
    ],
    resolve: {
        alias: {
            vue: 'vue/dist/vue.esm-bundler.js',
        },
    },
    build: {
        minify: false,
    }
});

编译前的global.js

/**
 * SweetAlert2 boxes
 */
const defaultBox = Swal.mixin({
    allowOutsideClick: false,
    allowEscapeKey: true,
    allowEnterKey: true,
    customClass: {
        confirmButton: "btn-primary",
        cancelButton: "btn-secondary"
    },
    showCancelButton: false,
    showConfirmButton: false
});

function Box(message = '', title = 'aviso', type = 'success') {
    return defaultBox.mixin({
        title: title,
        html: message,
        icon: type
    })
}

function BoxInfo({message, action = function () {}, title = "Aviso", type = "success"}) {
    Box(message, title, type).fire({
        showConfirmButton: true,
        confirmButtonText: "Aceptar"
    }).then(function () {
        action();
    });
}

function BoxConfirm({ message, preConfirm = function () {}, type = "info", title = "Aviso",
                    confirmText = "Aceptar", cancelText = "Cancelar"})
{
    Box(message, title, type).fire({
        showConfirmButton: true,
        showCancelButton: true,
        confirmButtonText: confirmText,
        cancelButtonText: cancelText,
        allowOutsideClick: () => !Swal.isLoading(),
        showLoaderOnConfirm: true,
        preConfirm() {
            return preConfirm();
        }
    });
}

/**
 * Bootstrap 5 Toast creator
 *
 * @param message
 * @param background
 */
function CreateToast({ message, background = 'text-bg-primary'}) {
    let parent = document.createElement('div');
    parent.setAttribute('role', 'alert');
    parent.setAttribute('aria-live', 'assertive');
    parent.setAttribute('aria-atomic', 'true');
    parent.classList.add('toast', background);

    let child = document.createElement('div');
    child.classList.add('toast-body');

    let body = document.createElement('div');
    body.classList.add('d-flex', 'justify-content-between');

    let content = document.createElement('div');
    content.classList.add('toast-content');
    content.innerHTML = message;

    let button = document.createElement('button');
    button.setAttribute('type', 'button');
    button.classList.add('btn-close');
    button.setAttribute('data-bs-dismiss', 'toast');
    button.setAttribute('aria-label', 'Close');

    body.appendChild(content);
    body.appendChild(button);
    child.appendChild(body);
    parent.appendChild(child);

    parent.addEventListener('hidden.bs.toast', (event) => {
        event.currentTarget.remove();
    });

    document.getElementById('toast-container').appendChild(parent);

    let toast = new bootstrap.Toast(parent);
    toast.show();
}

/**
 * Table functions
 */
function SelectAll(elem) {
    const table = elem.closest('table');

    document.querySelectorAll("#select_all_icon span").forEach(span => {
        span.classList.toggle("d-block");
        span.classList.toggle("d-none");
    });

    table.querySelectorAll('input:not(#select_all)').forEach(input => {
        input.checked = elem.checked;
    });
}

function SelectAlternate(elem) {
    const table = elem.closest('table');

    table.querySelectorAll('input:not(#select_all)').forEach(input => {
        input.checked = !input.checked;
    });
}

function DeleteSelected(elem, route) {
    const   table = elem.closest('table'),
            selected = table.querySelectorAll('input:checked:not(#select_all)'),
            amount = selected.length;

    if (amount === 0) {
        CreateToast({
            message: '¡No hay items seleccionados!',
            background: 'text-bg-danger',
        });
        return;
    }

    BoxConfirm({
        type: 'warning',
        message: '¿Estás seguro de querer eliminar masivamente ' + amount + ' item(s)? <strong><u>Esto no se puede deshacer</u></strong>.',
        confirmText: 'Eliminar',
        preConfirm: () => {
            const formData = new FormData();

            selected.forEach(input => {
                formData.append(input.name, input.value);
            });

            return new Promise((resolve, reject) => {
                axios.post(route, formData)
                .then(response => {
                    if (response.status !== 200) {
                        throw new Error(response.statusText)
                    }
                    resolve(true);

                    $(selected).each(function () {
                        $(table).DataTable().row($(this).parents("tr")).remove().draw()
                    });

                    CreateToast({message: '¡' + amount + ' item(s) eliminado(s) correctamente', background: 'text-bg-success'});
                })
                .catch(error => {
                    let errorMessage = '';

                    const errors = error.response.data.errors;
                    for (const errorKey in errors) {
                        if (errors.hasOwnProperty(errorKey)) {
                            errorMessage = errors[errorKey][0];
                            break;
                        }
                    }

                    CreateToast({message: 'Error al eliminar multiples items: ' + errorMessage, background: 'text-bg-danger'});
                    resolve(false);
                });
            });
        }
    });
}

function DeleteSingle({ elem, route, name }) {
    const   row = $(elem).parents("tr"),
            table = $(elem).closest('table');

    BoxConfirm({
        type: 'warning',
        message: name + ' será eliminado.<br>¿Estás seguro?. <strong><u>Esto no se puede deshacer</u></strong>',
        preConfirm: () => {
            return new Promise((resolve, reject) => {
                axios.delete(route)
                .then(response => {
                    if (response.status !== 200) {
                        throw new Error(response.statusText);
                    }

                    resolve(true);
                    $(table).DataTable().row(row).remove().draw();

                    CreateToast({
                        message: '¡' + name + ' eliminado correctamente!',
                        background: 'text-bg-success'
                    });
                })
                .catch(error => {
                    CreateToast({
                        message: 'Error al eliminar: ' + error.response.data.error,
                        background: 'text-bg-danger'
                    });

                    resolve(false);
                });
            });
        }
    });
}

function DeleteSingleFromShow({ route, name }) {
    BoxConfirm({
        type: 'warning',
        message: name + ' será eliminado.<br>¿Estás seguro?. <strong><u>Esto no se puede deshacer</u></strong>',
        preConfirm: () => {
            window.location.replace(route);
        }
    });
}


/**
 * Other functions
 */
function ResetForm(form) {
    document.querySelector(form).reset();
}

编译后的global.js

Swal.mixin({
  allowOutsideClick: false,
  allowEscapeKey: true,
  allowEnterKey: true,
  customClass: {
    confirmButton: "btn-primary",
    cancelButton: "btn-secondary"
  },
  showCancelButton: false,
  showConfirmButton: false
});

问题原因

这是Vite的**tree-shaking(摇树优化)**机制导致的。Vite在生产构建时默认会移除未被引用的代码,以此减小打包体积。

你的global.js里的所有函数(比如Box、CreateToast、SelectAll等)都是在文件内部定义但没有被导出,同时也没有在当前文件内被调用的顶级函数;如果其他文件也没有通过导入的方式使用这些函数,Vite会判定这些代码是“未使用的死代码”,从而在构建时将它们移除。

唯一保留的Swal.mixin代码,是因为它是一个立即执行的表达式,Vite默认不会移除这种直接执行的代码(即使它的返回值defaultBox没有被后续使用)。

另外,你在vite.config.js的input里重复添加了global.js,但这不会改变tree-shaking的逻辑——只要代码没有被导出或引用,依然会被移除。

内容的提问来源于stack exchange,提问作者DEFALTUSER

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 04:54:57