4

I'm developing a website using vue.js 3 and vite. When i run on development mode, it works fine. Then i built the website using yarn build and run it with yarn preview, but the app shows blank page without any errors appear.

Here is my code :

package.json :

  "name": "web-app-new",
  "version": "0.0.0",
  "scripts": {
    "dev": "vite --port 5000 --host",
    "build": "vite build",
    "preview": "vite preview --port 4173",
    "test:unit": "vitest --environment jsdom"
  },
  "dependencies": {
    "axios": "^0.27.2",
    "crypto-js": "^4.1.1",
    "firebase": "^8.10.1",
    "jwt-decode": "^3.1.2",
    "maska": "^1.5.0",
    "mdi-vue": "^3.0.13",
    "moment": "^2.29.4",
    "pinia": "^2.0.16",
    "uuid": "^8.3.2",
    "v-calendar": "^3.0.0-alpha.8",
    "v-viewer": "^3.0.10",
    "vue": "^3.2.39",
    "vue-router": "^4.1.2",
    "vue3-cookies": "^1.0.6",
    "vuefire": "^2.2.5"
  },
  "devDependencies": {
    "@vitejs/plugin-vue": "^3.0.1",
    "@vitejs/plugin-vue-jsx": "^2.0.0",
    "@vue/test-utils": "^2.0.2",
    "autoprefixer": "^10.4.8",
    "jsdom": "^20.0.0",
    "postcss": "^8.4.16",
    "tailwindcss": "^3.1.8",
    "vite": "^3.0.3",
    "vitest": "^0.18.1"
  }
}

vite.config.js :


import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import vueJsx from "@vitejs/plugin-vue-jsx";

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue(), vueJsx()],
  build: {
    /** If you set esmExternals to true, this plugins assumes that 
      all external dependencies are ES modules */

    commonjsOptions: {
      esmExternals: true,
    },
  },
  resolve: {
    alias: {
      "@": fileURLToPath(new URL("./src", import.meta.url)),
    },
  },
});

main.js :

import { createApp, markRaw } from "vue";
import { createPinia } from "pinia";
import mdiVue from "mdi-vue/v3";
import * as mdijs from "@mdi/js";

import App from "./App.vue";
import router from "./router";

import "./assets/main.css";
import "v-calendar/dist/style.css";
import utils from "./plugins/utils";
import external from "./plugins/utils.external";
// import { firestorePlugin } from "vuefire";

try {
  const app = createApp(App);

  const components = import.meta.globEager([
    "./components/*.vue",
    "./components/Atoms/*.vue",
    "./components/Atoms/Image/*.vue",
    "./components/Atoms/Button/*.vue",
    "./components/Atoms/Input/*.vue",
    "./components/Atoms/Tabs/*.vue",
    "./components/Molecules/*.vue",
    "./components/Molecules/Modal/*.vue",
    "./components/Molecules/Transition/*.vue",
    "./components/Organism/*.vue",
    "./components/Organism/Absensi/*.vue",
    "./components/Organism/Pengaturan/*.vue",
  ]);

  Object.entries(components).forEach(([path, definition]) => {
    // components/Atoms/Container.vue become => AtomsContainer
    const componentName = path.replace(/(.vue|\/|\.|components|index)/g, "");
    // Register component on this Vue instance
    // console.log(`  ${componentName} loaded`);
    app.component(componentName, definition.default);
  });
  // @plugins

  //@pinia
  const pinia = createPinia();
  pinia.use(({ store }) => {
    store.$router = markRaw(router);
    store.$app = app;
    store.$globalProperties = app.config.globalProperties;
  });
  app.use(pinia);

  //@others
  // app.use(firestorePlugin, {});
  app.use(mdiVue, {
    icons: mdijs,
  });
  app.use(utils, {});
  app.use(external, {});

  app.use(router);
  app.mount("#app");
} catch (error) {
  console.log(error);
}

router.js :

import { createRouter, createWebHistory } from "vue-router";
import { useStorage } from "@/composables/storage";
import { useLoadingStore } from "@/stores/loading";
import { useUserStore } from "@/stores/user";
import { useClientStore } from "@/stores/client";
import { useWorkerStore } from "@/stores/worker";

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    {
      redirect: "/login",
    },
    {
      path: "/login",
      name: "login",
      component: import("@/views/Test.vue"),
      // meta: {
      //   layout: "Auth",
      // },
    },
    {
      path: "/daftar",
      name: "daftar",
      redirect: "/daftar",
      component: () => import("@/components/Atoms/NestedWrapper.vue"),
      children: [
        {
          path: "/daftar",
          name: "daftar",
          component: import("@/views/Daftar/index.vue"),
          meta: {
            icon: "history",
            // layout: "Auth",
          },
        },
        {
          path: "/daftar/akun",
          name: "daftar akun",
          redirect: "/daftar/akun",
          component: () => import("@/components/Atoms/NestedWrapper.vue"),
          children: [
            {
              path: "/daftar/akun",
              name: "daftar akun",
              component: import("@/views/Daftar/Akun/index.vue"),
              meta: {
                icon: "account",
                layout: "Plain",
                validate: ({ next, userStore }) => {
                  ((!userStore.$state?.form?.idNumber ||
                    !userStore.$state?.form?.email) &&
                    next("/daftar")) ||
                    next();
                },
              },
            },
            {
              path: "/daftar/akun/buat",
              name: "buat akun baru",
              component: import("@/views/Daftar/Akun/Buat.vue"),
              meta: {
                icon: "account",
                layout: "Plain",
                validate: ({ next, userStore }) => {
                  ((!userStore.$state?.form?.idNumber ||
                    !userStore.$state?.form?.email) &&
                    next("/daftar")) ||
                    next();
                },
              },
            },
          ],
          meta: {},
        },
        {
          path: "/daftar/perusahaan",
          name: "daftar perusahaan",
          redirect: "/daftar/perusahaan",
          component: () => import("@/components/Atoms/NestedWrapper.vue"),
          children: [
            {
              path: "/daftar/perusahaan",
              name: "daftar perusahaan",
              component: import("@/views/Daftar/Perusahaan/index.vue"),
              meta: {
                icon: "history",
                layout: "Plain",
              },
            },
            {
              path: "/daftar/perusahaan/akun",
              name: "daftar akun perusahaan",
              component: import("@/views/Daftar/Perusahaan/Akun.vue"),
              meta: {
                icon: "account",
                layout: "Plain",
                validate: ({ next, workerStore, clientStore }) => {
                  (!clientStore.$state?.form && next("/daftar")) || next();
                },
              },
            },
          ],
          meta: {},
        },
        {
          path: "/daftar/pekerja",
          name: "daftar pekerja",
          redirect: "/daftar/pekerja",
          component: () => import("@/components/Atoms/NestedWrapper.vue"),
          children: [
            {
              path: "/daftar/pekerja",
              name: "daftar pekerja",
              component: import("@/views/Daftar/Pekerja/index.vue"),
              meta: {
                icon: "history",
                layout: "Plain",
                validate: ({ next, workerStore, clientStore }) => {
                  (!workerStore.$state?.form?.idNumber && next("/daftar")) ||
                    next();
                },
              },
            },
            {
              path: "/daftar/pekerja/akun",
              name: "daftar akun pekerja",
              component: import("@/views/Daftar/Pekerja/Akun.vue"),
              meta: {
                icon: "account",
                layout: "Plain",
                validate: ({ next, workerStore, clientStore }) => {
                  ((!workerStore.$state?.form ||
                    !workerStore.$state?.form?.idNumber) &&
                    next("/daftar")) ||
                    next();
                },
              },
            },
          ],
          meta: {},
        },
      ],
      meta: {
        // layout: "Auth",
      },
    },
    {
      path: "/app",
      redirect: "/app/beranda",
    },
    {
      path: "/app/beranda",
      name: "beranda",
      component: import("@/views/Beranda.vue"),
      meta: {
        layout: "Auth",
      },
    },
    {
      path: "/app/404",
      name: "404",
      component: () => import("@/views/404.vue"),
      meta: {
        // type: [roles.All],
        layout: "Auth",
        hidden: true,
      },
    },
    {
      path: "/app/:pathMatch(.*)*",
      redirect: "/app/wrong",
    },
  ],
});

router.beforeEach(async (to, from, next) => {
  const loadingStore = useLoadingStore();
  const userStore = useUserStore();
  const clientStore = useClientStore();
  const workerStore = useWorkerStore();
  loadingStore.setLoading({
    skeleton: true,
  });
  if (to?.meta?.layout !== from?.meta?.layout) {
    loadingStore.setLoading({
      layout: true,
    });
    await new Promise((res) => setTimeout(() => res(true), 1000));
  }
  if (to?.path !== from?.path) {
    loadingStore.setLoading({
      global: true,
    });
  }
  try {
    if (useStorage("credentials")?.refresh) {
      await userStore.statusUser();
    }
    if (userStore.isLoggedIn && !to.fullPath.includes("app")) {
      return next({ path: "/app" });
    }
    if (!userStore.isLoggedIn && to.fullPath.includes("app")) {
      return next({ path: "/login" });
    }
    if (to.meta?.validate && typeof to.meta?.validate === "function") {
      return to.meta?.validate({
        to,
        from,
        next,
        userStore,
        clientStore,
        workerStore,
      });
    } else {
      next();
    }
  } catch (error) {
    return Promise.reject(error);
  }
});

router.afterEach(async (to, from, failure) => {
  const loadingStore = useLoadingStore();
  const userStore = useUserStore();
  try {
    // if (!userStore.isLoggedIn) {
    //   router.push("/");
    // }
  } catch (error) {
    return Promise.reject(error);
  } finally {
    loadingStore.clearLoading();
  }
});

export default router;

App.vue :

import Layout from "@/layouts/index.vue";
import { onMounted } from "vue";
import { RouterLink, RouterView } from "vue-router";
import { useLoadingStore } from "./stores/loading";

onMounted(() => {
  document.documentElement.style.scrollBehavior = "smooth";
  document.documentElement.style.overflow = "auto";
  useLoadingStore().setLoading({ layout: true });
});
</script>

<template>
  <div class="min-h-screen">
    <atoms-loading />
    <atoms-alert />
    <atoms-transition />
    <!-- <atoms-swipe /> -->
    <molecules-modal />
    <!-- <router-view /> -->
    <Layout />
  </div>
</template>

Im also using layouts, here is my layouts/index.vue :

import AppLayoutDefault from "./Default.vue";
import ErrorLayout from "./Error.vue";
import { markRaw, onErrorCaptured, onMounted, ref, watch } from "vue";
import { useRoute, useRouter } from "vue-router";

const layout = ref();
const route = useRoute();

watch(
  () => route.meta?.layout || undefined,
  async (metaLayout) => {
    try {
      const component =
        metaLayout && (await import(/* @vite-ignore */ `./${metaLayout}.vue`));
      layout.value = markRaw(component?.default || AppLayoutDefault);
    } catch (e) {
      layout.value = markRaw(ErrorLayout);
    }
  },
  { immediate: true }
);

onErrorCaptured(() => {
  // layout.value = markRaw(ErrorLayout);
});
</script>

<template>
  <component :is="layout"> <router-view /> </component>
</template>

And this is the preview on development mode :

enter image description here

but on production mode :

enter image description here

I would appriciate some help. Thanks a lot

Nta
  • 79
  • 6
  • Do you have any errors in the console? Can you explain why you set `esmExternals` in the build section of the vite.config (I guess that's where your build breaks)? – Stefano Nepa Sep 28 '22 at 14:02
  • @StefanoNepa the console is clear, there is nothing appear and i guess ```esmExternals``` came from default project generator ([create-vue](https://github.com/vuejs/create-vue) as mentioned here [https://vuejs.org/guide/quick-start.html#creating-a-vue-application:~:text=%3E%20npm%20init%20vue%40latest](https://vuejs.org/guide/quick-start.html#creating-a-vue-application:~:text=%3E%20npm%20init%20vue%40latest)) – Nta Sep 28 '22 at 14:40
  • it seems only layouts files (from above screenshots) work. I've tried this [#72005194](https://stackoverflow.com/questions/72005194/vue-3-vite-built-application-shows-blank-page) but only makes the layouts view gone – Nta Sep 28 '22 at 14:51
  • so, no errors, but what does the page source look like? i.e. the HTML the browser receives – Jaromanda X Sep 29 '22 at 00:27
  • I am facing the same issue. Any solution yet? – Muhammad Siddiqui Nov 22 '22 at 11:47
  • @MuhammadSiddiqui i just post the answer – Nta Nov 23 '22 at 09:59

2 Answers2

1

Sorry, i forgot to answer the issue. Just adding async/await into the import function like below :

{
      path: "/login",
      name: "login",
      // add async/await 
      component: async () => await import("@/views/Test.vue"),
      // meta: {
      //   layout: "Auth",
      // },
},

This refer to https://v3-migration.vuejs.org/breaking-changes/async-components.html#_3-x-syntax

Nta
  • 79
  • 6
0

I faced the same issue with a vanilla js app created with npm create vite@latest client --template vanilla My project folder did not have vite.config.js. Simply adding this file with default configuration (shown below) did the trick for me.

// vite.config.js
export default {
    // config options
  }
codefun
  • 316
  • 2
  • 6