Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vite hmr doesn't work with error: ReferenceError: Cannot access '_page_0' before initialization with unplugin-auto-import #132

Open
xiangnanscu opened this issue Mar 1, 2023 · 12 comments · May be fixed by #157
Assignees
Labels
🐞 bug this isn't working as expected

Comments

@xiangnanscu
Copy link

When you modify template, the hmr doesn't work with error: ReferenceError: Cannot access '_page_0' before initialization. you must press F5 to refresh the page.

<script setup>
const route = useRoute();
</script>
<template>
  <div>foo</div>
</template>

But if you manually import useRoute, this problem doesn't exist

<script setup>
import { useRoute } from "vue-router";
const route = useRoute();
</script>
<template>
  <div>foo</div>
</template>

router.ts

import { createRouter, createWebHashHistory } from "vue-router";
import { routes } from "vue-router/auto/routes";

const router = createRouter({
  history: createWebHashHistory(import.meta.env.BASE_URL),
  routes: [...routes],
});
export default router;

vite.config.ts

import { readFileSync } from "fs";
import { fileURLToPath, URL } from "node:url";
import { defineConfig } from "vite";
import VueRouter from "unplugin-vue-router/vite";
import {
  VueRouterAutoImports,
  getPascalCaseRouteName,
} from "unplugin-vue-router";
import vue from "@vitejs/plugin-vue";
import vueJsx from "@vitejs/plugin-vue-jsx";
import AutoImport from "unplugin-auto-import/vite";
import Components from "unplugin-vue-components/vite";
import { AntDesignVueResolver } from "unplugin-vue-components/resolvers";
import VitePluginOss from "vite-plugin-oss";
import * as dotenv from "dotenv";
const dotenvExpand = require("dotenv-expand");

const { parsed: exposedEnvs } = dotenvExpand.expand({
  ...dotenv.config({
    override: false,
    path: ".env",
  }),
  ignoreProcessEnv: true,
});

const env = process.env;
for (const key of Object.keys(env).sort()) {
  // console.log(key, env[key]);
}
const VITE_PROXY_PREFIX = process.env.VITE_PROXY_PREFIX || "/toXodel";
const VITE_PROXY_PREFIX_REGEX = new RegExp("^" + VITE_PROXY_PREFIX);
const VITE_APP_NAME = process.env.VITE_APP_NAME;
const PROD_URL = `https:${env.ALIOSS_URL}${VITE_APP_NAME}/`;

const baseUrl = env.NODE_ENV == "production" ? PROD_URL : "/";

const plugins = [
  Components({
    dirs: ["./components"],
    extensions: ["vue"],
    dts: "./unplugin/components.d.ts",
    resolvers: [AntDesignVueResolver()],
  }),
  VueRouter({
    // Folder(s) to scan for vue components and generate routes. Can be a string, or
    // an object, or an array of those.
    routesFolder: ["src/views", "views"],

    // allowed extensions to be considered as routes
    extensions: [".vue"],

    // list of glob files to exclude from the routes generation
    // e.g. ['**/__*'] will exclude all files starting with `__`
    // e.g. ['**/__*/**/*'] will exclude all files within folders starting with `__`
    exclude: [],

    // Path for the generated types. Defaults to `./typed-router.d.ts` if typescript
    // is installed. Can be disabled by passing `false`.
    dts: "./unplugin/typed-router.d.ts",

    // Override the name generation of routes. unplugin-vue-router exports two versions:
    // `getFileBasedRouteName()` (the default) and `getPascalCaseRouteName()`. Import any
    // of them within your `vite.config.ts` file.
    getRouteName: getPascalCaseRouteName,

    // Customizes the default langage for `<route>` blocks
    // json5 is just a more permissive version of json
    routeBlockLang: "json5",

    // Change the import mode of page components. Can be 'async', 'sync', or a function with the following signature:
    // (filepath: string) => 'async' | 'sync'
    importMode: "sync",
  }),
  vue(),
  vueJsx({
    // https://github.com/vuejs/babel-plugin-jsx
    // https://github.com/vitejs/vite/tree/main/packages/plugin-vue-jsx
  }),
  AutoImport({
    //https://github.com/antfu/unplugin-auto-import#configuration
    eslintrc: {
      enabled: true, // Default `false`
      filepath: "./unplugin/.eslintrc-auto-import.json", // Default `./.eslintrc-auto-import.json`
      globalsPropValue: true, // Default `true`, (true | false | 'readonly' | 'readable' | 'writable' | 'writeable')
    },
    imports: [
      "vue",
      "pinia",
      // "vue-router", // comment this if using VueRouterAutoImports
      VueRouterAutoImports,
      { "vue-router/auto": ["useLink"] },
      "@vueuse/core",
      {
        // "@vueuse/core": [
        //   // named imports
        //   "useMouse", // import { useMouse } from '@vueuse/core',
        //   // "useFetch",
        //   // ["useFetch", "useMyFetch"], // import { useFetch as useMyFetch } from '@vueuse/core',
        // ],
        axios: [
          // default imports
          ["default", "axios"], // import { default as axios } from 'axios',
        ],
      },
    ],
    dts: "./unplugin/auto-imports.d.ts",
    vueTemplate: true,
    include: [
      /\.[tj]sx?$/, // .ts, .tsx, .js, .jsx
      /\.vue$/,
      /\.vue\?vue/, // .vue
    ],
    dirs: [
      "./components", // only root modules
      "./composables", // only root modules
      "./stores/**", // all nested modules
    ],
  }),
  {
    // this plugin handles ?b64 tags
    name: "vite-b64-plugin",
    transform(code, id) {
      if (!id.match(/\?b64$/)) return;
      // console.log(id, code);
      const path = id.replace(/\?b64/, "");
      const data = readFileSync(path, "base64");
      return `export default '${data}'`;
    },
  },
];
if (process.env.NODE_ENV == "production") {
  plugins.push(
    VitePluginOss({
      from: "./dist/**", // 上传那个文件或文件夹
      dist: `/${VITE_APP_NAME}`, // 需要上传到oss上的给定文件目录
      region: env.ALIOSS_REGION,
      accessKeyId: env.ALIOSS_KEY,
      accessKeySecret: env.ALIOSS_SECRET,
      bucket: env.ALIOSS_BUCKET,
      // test: true, // 测试,可以在进行测试看上传路径是否正确, 打开后只会显示上传路径并不会真正上传。默认false
      // 因为文件标识符 "\"  和 "/" 的区别 不进行 setOssPath配置,上传的文件夹就会拼到文件名上, 丢失了文件目录,所以需要对setOssPath 配置。
      setOssPath: (filePath) => {
        const index = filePath.lastIndexOf("dist");
        const Path = filePath.substring(index + 4, filePath.length);
        return Path.replace(/\\/g, "/");
      },
    })
  );
}
const proxyServer = `http://${env.NGINX_server_name}:${env.NGINX_listen}`;
export default defineConfig({
  base: baseUrl,
  define: {
    "process.env": exposedEnvs,
  },
  plugins,
  optimizeDeps: {
    include: ["vue"],
  },
  resolve: {
    alias: {
      "~/": fileURLToPath(new URL("./src", import.meta.url)) + "/",
    },
  },
  css: {
    preprocessorOptions: {
      less: {
        javascriptEnabled: true,
      },
    },
  },
  server: {
    // https://vitejs.dev/config/server-options.html#server-proxy
    // https://github.com/http-party/node-http-proxy#options
    port: Number(env.VITE_APP_PORT) || 5173,
    strictPort: true,
    proxy: {
      [VITE_PROXY_PREFIX]: {
        target: proxyServer,
        changeOrigin: true,
        rewrite: (path) => path.replace(VITE_PROXY_PREFIX_REGEX, ""),
      },
    },
  },
});
@posva
Copy link
Owner

posva commented Mar 1, 2023

Do you have a reproduction without the vite-plugin-oss and ant?

@xiangnanscu
Copy link
Author

xiangnanscu commented Mar 2, 2023

@posva click here, click Test and modify src/views/Test.vue's template.

@posva
Copy link
Owner

posva commented Mar 2, 2023

The link seems private

@xiangnanscu
Copy link
Author

@posva sorry, plz refer this https://github.com/xiangnanscu/v

@posva
Copy link
Owner

posva commented Mar 6, 2023

When using sync mode there seems to be (I think) a circular dependency somewhere creating the error you see when also using useRoute() with auto import (from vue-router/auto). Similar to issues reported at vitejs/vite#3033

Using async (the default) doesn't show this problem. Note that in your repro you are also importing the routes and calling vue-router createRouter() but you should do this instead (as shown in docs):

import { createRouter, createWebHashHistory } from 'vue-router/auto'

const router = createRouter({
  history: createWebHashHistory(import.meta.env.BASE_URL),
  extendRoutes(routes) {
    return routes
  }
})
export default router

Unfortunately, this doesn't fix the problem. I recommend you to use async mode in the meantime or to toggle it for dev builds with importMode: process.env.NODE_ENV === 'production' ? 'sync' : 'async' or similar.

Contribution welcome!

@xenolithviktor
Copy link

@posva
Any possibility we could fix this soon? Since 0.8 this has become a bigger problem for us since we are now forced to import from 'vue-router/auto' to get typing on useRoute. This forces us to use 'async' in development, leading to inconsistent application behavior between dev and production.

I'd be happy to make another PR, but I need to know which path to take.

Here are some different ways to solve it:

  1. Use Promise.resolve like in my previous PR. A bit hacky, but it only runs in dev and keeps HMR happy. Not a breaking change.
  2. Turn routes in 'vue-router/auto/routes' into a function instead of a static array. Maybe also a bit hacky? This solves the HMR problem, but the circular import is still there I guess. Not a breaking change.
  3. Moving createRouter into its own sub-module, like 'vue-router/auto/config' or similar. AFAIK the only reason we import routes into 'vue-router/auto' is for createRouter. If we move this to its own sub-module that is not imported in pages we would get rid of the circular dependency entirely. This is probably the cleanest approach, but would be a breaking change as everyone would have to update their import where they set up the router.

@posva
Copy link
Owner

posva commented Mar 2, 2024

@xenolithviktor I need to investigate more. For the moment, I recommend you to use any of the versions with pnpm patch or patch-package.

I will focus on fixing this and #5 at some point, but I need to focus on other features and bugs now.

@xenolithviktor
Copy link

@posva
I understand. When you do get the time, I'll be happy to assist in any way I can.

Previously, the typed-routes.d.ts would declare typings for the 'vue-router' module which made it possible to import from there as a workaround. Would it be possible to get that back somehow with an extra d.ts file? If so, what would we put in it? I tried copying the declaration from a previous version of unplugin-vue-router but it does not seem to work..

Patching should be a last resort 😅

@roos-robert
Copy link

@posva Any updates regarding this? @xenolithviktor has put forward he is happy to help with a solution and just need your blessing on which way to move forward with a PR.

Copy link
Owner

posva commented Mar 12, 2024

You don't need my blessing in any way 😅
This is open source, feel free to work on it and submit a PR. I'm busy with other matters that affect more users, so using async mode or a local patch is the preferred way until a proper fix is found

@xenolithviktor
Copy link

Here is a little workaround that patches the routes array to be a function using a Vite-plugin. (Add to vite config plugins)

{
  // Hacky workaround until HMR in sync mode is fixed, see: https://github.com/posva/unplugin-vue-router/issues/132
  name: 'unplugin-vue-router-hmr-workaround',
  apply: 'serve',
  transform(code, id) {
    if (id === 'virtual:vue-router/auto') {
      return {
        code: code.replace('extendRoutes(routes) : routes', 'extendRoutes(routes()) : routes()')
      }
    }
    if (id === 'virtual:vue-router/auto-routes') {
      return {
        code: code.replace('export const routes = [', 'export const routes = () => [')
      }
    }
  }
}

This only runs on serve and makes HMR happy by using a function instead of defining the routes array directly in the module, which removes the cyclic dependency error in Vite.

Note that this will only work when using createRouter from vue-router/auto, and not if importing the routes directly from vue-router/auto-routes.

In the future, in my opinion, it would probably be best to remove the custom createRouter (and the import of vue-router/auto-routes) from vue-router/auto completely, and instead have the users import the routes manually when they set up the router.
We don't want vue-router/auto-routes to be imported on every page that uses things like useRouter() or useRoute() as this is what introduces the cyclic problem.

@ferferga
Copy link

ferferga commented Mar 20, 2024

@xenolithviktor Rollup's still report CIRCULAR_DEPENDENCY warnings, both with your PR and with your plugin workaround (HMR is working fine for me, it's just production where I'm having issues), so none of them are the real solution to this issue.

I've been investigating further and I'm also onboard that the best solution (albeit it's a minor inconvinient, but just for the first-time setup) is to force users to import the routes manually, like in the layout example in README.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
🐞 bug this isn't working as expected
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants