Dynamic import splits code but doesn't lazy load - javascript

I want to introduce lazy loading to Vue Router, so that some parts of the code will be loaded only on demand.
I'm following the official documentation for Lazy Loading in Vue Router:
https://router.vuejs.org/en/advanced/lazy-loading.html
So for a test I've changed how the Vault module is imported in my router file:
import Vue from 'vue';
import Router from 'vue-router';
// Containers
import Full from '#/containers/Full';
// Views
// TODO: views should be imported dynamically
import Dashboard from '#/views/Dashboard';
const Vault = () => import('#/views/Vault');
import Page404 from '#/views/Page404';
import Page500 from '#/views/Page500';
import Login from '#/views/Login';
Vue.use(Router);
export default new Router({
routes: [
{
path: '/',
redirect: '/dashboard',
name: 'VENE',
component: Full,
children: [
{
path: 'dashboard',
name: 'dashboard',
component: Dashboard
},
{
path: 'vault',
name: 'vault',
component: Vault
},
],
},
{
path: '/login',
name: 'Login',
component: Login,
},
{
path: '/404',
name: 'Page404',
component: Page404,
},
{
path: '/500',
name: 'Page500',
component: Page500,
},
],
});
All fine, however, when I open the app for the first time, the extracted bundle which was supposed to be lazy loaded, is loaded up front:
When I go to that view using router it appears in Dev Tools Network Tab again, but says it's loaded from the disk, so the bundle is clearly loaded on first page load, which is totally against the idea of lazy loading.

This is occurring for a couple reasons. I should say, you've set everything up correctly for lazy-loading the Vault component. One tip, I've found it helpful to add the webpack chunk name to the dynamic import:
const Vault = () => import(/* webpackChunkName: "vault" */ '#/views/Vault')
This would then show up in your network tab named with the chunkName "vault"
First, I'm guessing that you're using #vue-cli looking at your file structure and /src alias. Depending on the options you select when creating your project, #vue-cli uses a webpack config for progressive web apps that prefetches all resources. While the browser has mechanisms for prioritizing these downloads, I've found that some of the prefetching appears to block other resources. The benefit of prefetching is for browsers that don't support service-workers, you use idle browser time to put resources in the browser cache that the user will probably eventually use. When the user does need that resource, it is already cached and ready to go.
Second, you do have options for disabling the prefetch plugin. #vue-cli provides escape hatches for overriding the default config. Simply edit or add vue.config.js to the root of your project.
courtesy #LinusBorg
// vue.config.js
chainWebpack: (config) => {
// A, remove the plugin
config.plugins.delete('prefetch')
// or:
// B. Alter settings:
config.plugin('prefetch').tap(options => {
options.fileBlackList.push([/myasyncRoute(.)+?\.js$/])
return options
})
}
-- Be sure to only use either option A or option B; not both. --
Source: https://github.com/vuejs/vue-cli/issues/979
I've used option A with success, but you should definitely benchmark the results yourself and go with the option that best serves your users and application.
I appreciate the configurability of #vue-cli for these and many scenarios. It's definitely worth exploring to write the application you want, rather than coercing your app to the config.

Related

"TypeError: Failed to fetch dynamically imported module" on Vue/Vite vanilla setup

We have a vanilla Vue/Vite setup and I'm receiving TypeError: Failed to fetch dynamically imported module on sentry logs.
It seems like the errors are correlated in time with new deployment to prod, although I don't have enough data to confirm. It doesn't happen on local and appears only on deployed code.
I've seen some similar questions for react's setups, but none with a satisfactory response.
I've also found a similar question regarding dynamically imported svgs, but our errors happen for full components.
The only place where we use dynamic imported components is on routing:
export const router = createRouter({
history: routerHistory,
strict: true,
routes: [
{
path: '/',
name: routes.homepage.name,
component: () => import('#/views/Home.vue'),
children: [
{
path: '/overview',
name: routes.overview.name,
component: () => import('#/views/Overview.vue'),
},
// other similar routes
],
},
],
});
Our deps versions:
"vue": "^3.0.9",
"vue-router": "^4.0.5",
"vite": "^2.0.5",
Any additional information on this issue and how to debug it would be much appreciated!
When you dynamically import a route/component, during build it creates a separate chunk. By default, chunk filenames are hashed according to their content – Overview.abc123.js. If you don't change the component code, the hash remains the same. If the component code changes, the hash changes too - Overview.32ab1c.js. This is great for caching.
Now this is what happens when you get this error:
You deploy the application
Your Home chunk has a link to /overview route, which would load Overview.abc123.js
Client visits your site
You make changes in your code, not necessarily to the Overview component itself, but maybe to some children components that Overview imports.
You deploy changes, and Overview is built with a different hash now - Overview.32ab1c.js
Client clicks on /overview link - gets the Failed to fetch dynamically imported module error, because Overview.abc123.js no longer exists
That is why the errors correlate with deployments. One way to fix it is to not use lazy loaded routes, but that's not a great solution when you have many heavy routes - it will make your main bundle large
In my case the error was caused by not adding .vue extension to module name.
import MyComponent from 'components/MyComponent'
It worked in webpack setup, but with Vite file extension is required:
import MyComponent from 'components/MyComponent.vue'
I had the exact same issue. In my case some routes worked and some didn't. The solution was relatively easy. I just restarted the dev server.
The accepted answer correctly explains when this error is triggered but does not really provide a good solution.
The way I fixed this is by using an error handler on the router. This error handler makes sure that when this error occurs (so thus when a new version of the app is deployed), the next route change triggers a hard reload of the page instead of dynamically loading the modules. The code looks like this:
router.onError((error, to) => {
if (error.message.includes('Failed to fetch dynamically imported module')) {
window.location = to.fullPath
}
})
Where router is your vue-router instance.
My situation was similar.
I found that my Quasar setup works fine on the initial page but not page that are loaded dynamically through an import('../pages/page.vue');.
Short response:
I replaced import('../pages/TestPage.vue') in the middle of the route file by import TestPage from '../pages/TestPage.vue' at the top.
More detailed response:
In my situation I don't expect to have much pages, a single bundle with no dynamic loading is fine with me.
The solution is to import statically every page I need.
In my routes.ts I import all the pages I need.
import IndexPage from '../pages/IndexPage.vue';
import TestPage from '../pages/TestPage.vue';
Then I serve them statically in my routes :
const routes: RouteRecordRaw[] = [
{
path: '/',
component: () => import('layouts/MainLayout.vue'),
children: [
{ path: 'test', component: () => TestPage },
{ path: '', component: () => IndexPage }
],
},
// Always leave this as last one,
// but you can also remove it
{
path: '/:catchAll(.*)*',
component: () => import('pages/ErrorNotFound.vue'),
},
];
I recently expriencied this. The error was caused by an empty href inside an a tag: <a href="" #click="goToRoute">. You can either remove the href or change the a tag to something else, ie. button. Let me know if this helps.
I had the same problem. I found that I had not started my project.

Angular 8 loads javascript from wrong path after reload on lazy loaded module

when I reload the page when I'm on lazy loaded module the app breaks as it tries to import js files from the wrong path. you can see my routing configuration:
app routing module:
{
path: 'accommodations',
canActivate: [AuthGuard],
loadChildren: () => import('./accommodation/accommodation.module').then(m => m.AccommodationModule)
}
accommodation routing module:
const routes: Routes = [
{
path: ':id',
component: AccommodationDetailsComponent
}
];
when I'm on route http://localhost:4200/accommodations/1 for example and I reload the page, browser tries to import js files from http://localhost:4200/accommodations and shows 404 error.
for example, it tries to import runtime js from http://localhost:4200/accommodations/runtime.js
I didnt' find the problem itself, but i found that if I use useHash:true the error goes away

Angular 5: How to load multiple custom child components for a screen?

Background
I'm writing an enterprise application in Angular 5. One of the large screens I'm working on has many tabs and child components. A majority of clients will get the same version of the screen, but a client or two want custom changes under a couple of tabs.
Question
How do I dynamically load custom components for those couple of tabs where the client wants changes? I know I could use async lazy module loading to load a whole new screen specific for that client, but it seems it would be more efficient to only swap out the child components that have changes rather than the whole screen.
Async loading appears to be the way to go as you can use canLoad to not load unnecessary code for each client. At the child level, if you have multiple components at the same child level to swap, you could try to use named router outlets with async modules for the child components. However, I ran into a bug with async loading and the named router outlets.
Anyone have any other ideas?
Code
Here is the code for the routes I tried when exploring the above idea. The core screen is the parent level screen, async loaded.
export const routes: Routes = [
{
path: '',
component: CoreScreenComponent,
children: [
{
path: 'x-core',
loadChildren: "app/components/core-component-x/core-component-x.module#CoreComponentXComponent",
outlet: 'x'
},
{
path: 'x-client',
loadChildren: "app/components/client-component-x/client-component-x.module#ClientComponentXComponent",
outlet: 'x'
},
{
path: 'y-core',
loadChildren: "app/components/core-component-y/core-component-y.module#CoreComponentYComponent",
outlet: 'y'
},
{
path: 'y-client',
loadChildren: "app/components/client-component-y/client-component-y.module#ClientComponentyComponent",
outlet: 'y'
},
]
}];

React Router v3 address bar not updating correctly when clicking link quickly

My application uses react-router v3. We use WebPack to asynchronously load chunks on a route change (via dynamic import statements).
Under certain conditions, clicking <Link/>s too quickly will cause the address bar to not update (perhaps due to clicking faster than the WebPack chunk can resolve?).
The issue appears sporadically and is correlated to high network latency. Higher network latency (such as 3g) increases the chance of the issue arising.
Example route config:
{
component: App,
indexRoute: controlsRoute,
childRoutes: [
{
path: "app/account",
getComponent(_discard: void, cb: Function) {
import("./account/index") // WebPack dynamic import
.then(module => cb(undefined, module.Account))
.catch((e: object) => cb(undefined, crashPage(e)));
}
}
]
}
<link/> usage:
<Link to="/app/account" onClick={props.close("accountMenuOpen")}>
{t("Account Settings")}
</Link>
Are there any common mistakes that cause React Router to lose track of state when loading WebPack chunks asynchronously?

Extend the React Router of exist application on fly

Is there any way to extend react-router of one application which is already hosted on fly? I want to inject additional routes on the click of a link which allows me to inject the script or allows to include my javascript.
Eventually I am looking for two different react applications which has one build and deployment cycle, but interrelated to each other.
Ex. there is the abc.com in which on click of a link(i.e. abc.com/nepage) the entire page is getting reloaded with same content [i.e. say header footer] which is maintained by different team all to gather and they have there one build and deployment cycle.
I want the application to be with SPA even if we have different build and deployment process.
This was achieved using Backbone with help of Backbone.Router.extend, where on click of link the default router for the new page was overridden with all new set of routers and which use to full the supporting files from the path mentioned for the specific router's
With PlainRoutes, child routes can be loaded on-demand (when the user enters the route) and resolved asynchronously. Having that in mind, you can use Webpack chunks to split the code corresponding to theses routes in diferente files. Going even further, you can have multiple entrypoints on Webpack, making users load only the part of the application that affects the current page.
Sample app:
index.js:
import React from 'react'
import ReactDOM from 'react-dom'
import { Router, browserHistory } from 'react-router'
const App = ({ children }) => {
<div>
<nav>Your navigation header</nav>
{ children }
<footer>Your app footer</footer>
</div>
}
const HomePage = () => <p>Welcome!</p>
const routes = {
path: '/',
component: App,
indexRoute: { component: HomePage },
getChildRoutes (partialNextState, cb) {
require.ensure([], (require) => {
cb(null, [
require('./routes/about'),
require('./routes/blog'),
require('./routes/contact'),
])
})
}
}
ReactDOM.render(
<Router history={ browserHistory } routes={ routes } />,
document.getElementById('container')
)
routes/about.js:
import React from 'react'
const About = () => <p>About page</p>
export default {
path: 'about',
component: About
}
Other routes could be similar to the about route as shown above.

Categories