update initial router url when running inside ifra

2020-04-05 08:41发布

I'm currently rendering Vue apps inside object tags (iframe could work too) of a container/master Vue app. First I setup a fileserver serving that container or the requested sub-app to render inside the div.

For the sake of simplicity I will only show the required routing of my Node/Express server

// serve the sub-app on demand
router.get('/subApps/:appName', function(req, res) {
    res.sendFile(path.resolve(__dirname, `../apps/${req.params.appName}/index.html`);
});

// always render the app container if no sub-app was requested
router.get('*', function(req, res) {
    res.sendFile(path.resolve(__dirname, '../base/index.html'));
});

My app container / master Vue app is inside the view file rendered on /apps/:appName, requests that sub-app and wraps it into object tags

  document.getElementById("customAppContainer").innerHTML = `<object style="width: 100%; height:100%;" data="http://localhost:3000/subApps/${appKey}"></object>`;

This approach works fine but the rendered sub-app uses the startup url http://localhost:3000/subApps/app-one although it should use the url http://localhost:3000/apps/app-one. When the sub-app loads the router instance I have to change the startup url from the iframe url to the browser url (parent).

I thought about fixing that with history.replaceState

const router = new Router({ ... });

router.afterEach((to, from) => {
    localStorage.subAppRouteUpdate = to.path;
});

if (window.location !== window.parent.location) {
    const browserUrl = top.location.href;
    history.replaceState(null, null, browserUrl);
}

export default router;

Why do I want to do this? The App.vue of the master app should append the sub-app route to the browser url. With this approach it's possible to update the browser url while navigating inside the sub-app. I achieve this by storing the sub-app url to the local storage and listen for local storage changes at the master app side. So the App.vue file uses this code

<script>
export default {
  created() {
    window.addEventListener("storage", () => {
      const fullRoute = this.$router.currentRoute.fullPath;
      const routeSegments = fullRoute.split("/");
      const appsIndex = routeSegments.indexOf("apps");
      const newBaseUrlSegments = routeSegments.slice(0, appsIndex + 2);
      const newBaseUrl = newBaseUrlSegments.join("/");
      const subAppRoute = localStorage.subAppRouteUpdate;
      const updatedUrl = newBaseUrl.concat(subAppRoute);
      history.replaceState(null, null, updatedUrl);
    });
  }
};
</script>

to enable a routing while using IFrames. It almost works, this is what I get

enter image description here

Unfortunately it happens that when calling / of the sub-app the browser url gets updated to

http://localhost:3000/apps/app-one/apps/app-one

although I'm expecting

http://localhost:3000/apps/app-one/


Reproduction:

I created a repository for reproduction / testing. Does someone know what might be wrong or how to fix that url updating?


Update:

I think the error occurs because in the router.js of the subApp I'm firing this code

if (window.location !== window.parent.location) {
    const browserUrl = top.location.href;
    history.replaceState(null, null, browserUrl);
}

router.afterEach((to, from) => {
    console.log({ urlToAppend: to.path });
    localStorage.subAppRouteUpdate = to.path;
});

The replaceState function will update the IFrame url from /subApps/app-one to the correct browser url /apps/app-one. Unfortunately this will trigger the afterEach event and to.path results in /apps/app-one although it should be /.

If the url would be /apps/app-one/users/create the after each event should trigger with /users/create of course.

But I didn't figured out how to fix this first triggered event.

1条回答
虎瘦雄心在
2楼-- · 2020-04-05 09:15

It may be a bit hacky solution but it works for me. Just check that current url path is not equal to to.path in case if event is triggered twice

router.afterEach((to, from) => {
    console.log({ urlToAppend: to.path });
    if (router.currentRoute.path !== to.path) {
        localStorage.subAppRouteUpdate = to.path;
    }
});

UPDATE

In base/src/App.vue in storage event listener before concatenating sub route to base route you don't check for the possible duplicates. This fix should help

window.addEventListener("storage", () => {
      const fullRoute = this.$router.currentRoute.fullPath;
      const routeSegments = fullRoute.split("/");
      const appsIndex = routeSegments.indexOf("apps");
      const newBaseUrlSegments = routeSegments.slice(0, appsIndex + 2);
      const newBaseUrl = newBaseUrlSegments.join("/");
      const subAppRoute = localStorage.subAppRouteUpdate;
      if (subAppRoute.startsWith(newBaseUrl)) {
        history.replaceState(null, null, newBaseUrl);
      } else {
        const updatedUrl = newBaseUrl.concat(subAppRoute);
        history.replaceState(null, null, updatedUrl);
      }
    });

And router.afterEach should look like this to navigate to subRoutes which are defined within app-one router:

router.afterEach((to, from) => {
        const newPath = '/' + to.path.split('/').pop();
        const matchingRoutes = router.options.routes.filter(r => r.path === newPath);
        const isPathInRoutes = matchingRoutes.length > 0;
        if (router.currentRoute.path !== newPath && isPathInRoutes) {
            router.push(newPath);
            localStorage.subAppRouteUpdate = newPath;
        } else {
            localStorage.subAppRouteUpdate = to.path;
        }
});

If you want page one to be rendered by default when user goes to http://localhost:3000/apps/app-one you could check whether last part of the entered url is equal to sub apps base route(/app-one) and if it does navigate to default page route(/):

router.afterEach((to, from) => {
    let newPath = '/' + to.path.split('/').pop();
    const matchingRoutes = router.options.routes.filter(r => r.path === newPath);
    const isPathInRoutes = matchingRoutes.length > 0;
    if (newPath === router.history.base || !isPathInRoutes) {
        newPath = '/';
    }
    if (router.currentRoute.path !== newPath) {
            router.push(newPath);
            localStorage.subAppRouteUpdate = newPath;
    } else {
            localStorage.subAppRouteUpdate = to.path;
    }
});
查看更多
登录 后发表回答