vue and webpack doesn't do lazy loading in components? - vue.js

The lazy component in vue/webpack seem to be wrong or I miss confuse about the terms.
To do lazy loading of component I use the keyword import and webpack should split this component to sepeate bundle, and when I need to load this component webpack should take care of it and load the component.
but in fact, webpack does make sperate file, but it loaded anyway when the application is running. which is unexpected.
For example I just create a simple vue application (using the cli) and browse to localhost:8080/ and the about page should be loaded (by default) using the import keyword.
const routes = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/about',
name: 'About',
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
}
]
So This is by design? I load every time the file I do not need right now (the page about). and if I have 200 pages, when I'll fetch 200 files I dont need. how that ends up? that did I save here by using the import key?
In vuetify for example they uses import key, but the files are loaded anyway and not by demand.

You can also avoid component prefetch using one of the webpack "magic" comments (https://webpack.js.org/guides/code-splitting/#prefetchingpreloading-modules), eg.
components: {
MyComponent: () => import(/* webpackPrefetch: false */ './MyComponent.vue')
}
Feel free to read more about this Vue optimization below:
https://vueschool.io/articles/vuejs-tutorials/lazy-loading-individual-vue-components-and-prefetching/

If you're referring to the default app template from Vue CLI, then you're actually observing the prefetch of the About page, intended to reduce load times for pages the user will likely visit.
If you want to avoid this performance optimization, use this Vue config:
// vue.config.js
module.exports = {
chainWebpack: config => {
config.plugins.delete('prefetch')
config.plugins.delete('preload')
}
}
For troubleshooting reference, Chrome's Network panel includes an Initiator column, which provides a clickable link to the source file that triggered the network call. In this case of the about.js, the source file looks like this:

try using vue-lazyload maybe it can help and for <script> tags you can try async defer it helps in website speed optimizations

Related

Is there any way to have dynamic routes/components in vuejs router?

Hi beautiful Vuejs developers out there!
I have a little problem with routing many Vue components/pages dynamically. In this scenario I am using nested routes to have a couple of routes for my layout components and hundreds of child routes for my pages and as you can imagine I'll have to type many child routes statically or manually, and then add more when I need more child routes in the future code changes but I need a solution to simplify/solve this problem with more efficient/better way like adding those routes from what user types after the layout in the url... here is my example code code:
const routes: RouteRecordRaw[] = [
{
{
path: '/student',
component: () => import('layouts/StudentLayout.vue'),
children: [
{
path: 'dashboard',
component: () => import('pages/student/Dashboard.vue'),
},
{
path: 'profile',
component: () => import('pages/student/Profile.vue'),
},
],
},
}
As you see in this code I have a layout named Student and it has two children but I'll have to type manually hundreds of child routes for this layout and other layouts is there any way to dynamically set up those routes with what users enter after the layout name like /student/dashboard or /layout/page and match it with a component name? I mean like params in Angular, can I use the param value itself inside the router to say?
{
path: ':pagename',
component: (pagename) => import('pages/student/' + pagename + '.vue'),
},
let me know if there is an efficient way to solve this problem.
Thanks in advance!
I would, personally, not use this, or advise such an approach, nor have I done it, but this idea came to me when I read your question:
My best guess would be to have a handler component which renders a component dynamically based on a route parameter (say you have /:subpage as a child to /student and the handler component is set to that route), and an exception handler around that to show a 404 page when the user types in an inexistent/unsupported route.
For example, I would dynamically import the component by the route parameter into a predefined let (e.g. let SubpageComponent; outside the try catch block), have a try catch block around the dynamic import assignment for the respective error where catch would set the variable to a 404 page. Then I would add the SubpageComponent into the data() of the component doing the rendering of the route.
Edit
I've written out come code that, maybe, makes sense.
It's based on https://v2.vuejs.org/v2/guide/components.html#Dynamic-Components
your routes definition, changed
const routes: RouteRecordRaw[] = [
{
path: '/student',
component: () => import('layouts/StudentLayout.vue'),
children: [
{
path: '/:subpage',
component: () => import('pages/student/SubpageRenderer.vue'),
props: true,
},
],
},
]
SubpageRenderer.vue
<script>
export default {
props: ['subpage'],
data() {
return {
currentSubpage: () => import(`./${subpage}.vue`)
}
}
}
</script>
<template>
<component :is="currentSubpage"></component>
</template>
Instead of using the currentSubpage import, you can also use the subpage route prop to bind :is if subpage is the name of a registered component.
Since this would get only "dashboard" from the route, you'd need some namespacing, like "student-dashboard" with the help of template literals. You could make currentSubpage into a template literal that creates the student-${subpage} name.
I'd probably recommend importing the options object of the component designated by the subpage route parameter instead of registering all the components - if you're registering them, you might as well use vue-router the usual way :)
Also, I only think this could work! It should be tested out, and perhaps casing should be kept in mind, and maybe the Layout suffix as well (subpage will probably be all lowercase, and you'll probably have the components named in PascalCase). After uppercasing the first letter, this could also obviously lead to both /student/Dashboard and /student/dashboard being valid routes

Nuxt: how to explicitly name JS chunks?

I'm using Nuxt in static site generation mode. One requirement in my project is to deploy only certain routes, each with their respective assets.
But Nuxt gives chunks random names like 925446d.js.
So I created a manual router.js and specified chunk names while importing my components:
component: () => import(/* webpackChunkName: "about" */ '~/pages/about.vue').then(m => m.default || m)
But Nuxt doesn't take my chunk names into account and continues to give chunks random names, making it super difficult to single out which chunk goes with which route.
Any suggestion?
If you want to be able to identify chunks by name, Nuxt actually has a built-in mechanism do to that, using the property filename of the build config.
You can add the following in your nuxt.config.js file:
{
// ...
build: { filenames: { chunk: () => '[name].js' } }
}
Some additional options do exist, see documentation.

Component dynamic import doesn't work from router.js

With Nuxt.js, in my router.js I'm trying to import my route component thus:
{
path: '/',
component: () => import(/* webpackChunkName: "index" */ '~/pages/index.vue')
}
I get this error:
render function or template not defined in component: anonymous
I came across someone else's Nuxt.js project where they add the following at the end, and with this it works:
{
path: '/',
component: () => import(/* webpackChunkName: "index" */ '~/pages/index.vue').then(m => m.default || m)
}
The returned object of my import() looks like this:
In a another Vue.js, non-Nuxt project, the same kind of import() looks like that:
In both cases, the component is nested in some "default" object. But with Nuxt.js it seems you must import that object explicitly, whereas with regular Vue.js you don't have to specify that.
Why?
Regarding why the module is nested inside a { default } object: it's because Nuxt uses ES6 style modules. ES6 style modules are written the following:
export default {
// some object
};
Default is not the only property you can have. More syntax.
Regarding why vue-router are working without the .default, it's because the code is more versatile than the code used by Nuxt. Nuxt already ships it's own builder and always uses ES6 modules.
On the other hand, vue-router does not know about how it will be built. So to be easier to use, vue-router automatically checks if the module is ES6 or not.
const resolve = once(resolvedDef => {
if (isESModule(resolvedDef)) {
resolvedDef = resolvedDef.default
}
// ...
}
Source: code of vue-router.
As you may already know, it's very uncommon to use a router.js file in a Nuxt project, because Nuxt already has its own routing system which splits your code into chunks for each page. Documentation here.

Lazy Route gets prefetched, ignoring webpack magic comments (Vue)

I need to develop an application with server side authentication with a login view. If I want to use Vue Router to dynamically switch between login and index (the protected view), I need to avoid login view downloading (prefetching) index before succesful authentication, because if not, server will answer with the login page to the index prefetching request.
I'm trying to achieve this in the original Vue Router example that has two routes. Home and about. The first one is included and the second one is lazy loaded (but prefetched) which would be the protected page in the real application.
In order to avoid prefetching I have tried all the webpack magic comments I have found, but the prefetching is still hapenning.
Here is the code:
import Vue from "vue"
import VueRouter from "vue-router"
import Home from "../views/Home.vue"
Vue.use(VueRouter);
const routes = [
{
path: "/",
name: "Home",
component: Home
},
{
path: "/about",
name: "About",
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () =>
import(/* webpackChunkName: "about"*/ /* webpackMode: "lazy" */ /* webpackPrefetch: false */ /* webpackPreload: false */ "../views/About.vue")
}
];
const router = new VueRouter({
routes
});
export default router;
And here the result:
I don't want to disable the feature from the general webpack configuration because I want it for the rest of the application links. I want to disable it only for this link.
How I should configure the router to achieve it?
Thanks for your time,
H25E
There is a discussion on Github which offers some tips relevant to your situation.
// vue.config.js
module.exports = {
chainWebpack: config => {
config.plugin('prefetch').tap(options => {
options.fileBlackList.push([/MyChunkName(.*)\.js$/]);
return options;
});
}
};
Vue-CLI by default automatically prefetches all dynamic imports - so you have to add a blacklist.
The magic comments for Webpack (webpackPrefetch and probably webpackPreload too) accept either true or a number (index) - but do not accept false argument.

Vue Lazy load refactor

I am trying to lazy load a lot of components in my app, I want to show a loading spinner while any of them is loading, and the same for error; so there is a lot of duplication.
export default c => ({
component: import(`${c}`),
loading: loadingComponent,
timeout: 3000
})
I am trying to refactor this into a single function and using it like that
import lazyload from './lazyload';
Collection: lazyload("./Collection.vue")
But webpack is not extracting the component as it normally does, I know that I am missing something.
You need to be creating an async component factory (meaning function). Also the import module cannot be fully dynamic, there needs to be some prefix to the module path otherwise it could match literally any module and webpack needs to know which subset of modules it could possibly match at runtime to include them in the build.
Fully dynamic statements, such as import(foo), will fail because webpack requires at least some file location information. This is because foo could potentially be any path to any file in your system or project. The import() must contain at least some information about where the module is located, so bundling can be limited to a specific directory or set of files.
I've made some adjustments to your code (untested):
lazyload.js
export default c => () => ({
component: import(`./components/${c}`),
loading: loadingComponent,
timeout: 3000
})
Example usage
import lazyload from './lazyload'
export default {
components: {
Collection: lazyload('collection.vue')
}
}
A better implementation, in my opinion, would be to avoid creating the dynamic import. I prefer webpack to know for certain which modules are definitely needed at build time instead of bundling together a subset of modules inside a directory.
lazyload.js
export default componentFn => () => ({
component: componentFn(),
loading: loadingComponent,
timeout: 3000
})
Example usage
import lazyload from './lazyload'
export default {
components: {
Collection: lazyload(() => import('./collection.vue'))
}
}
Now lazyload has no dependency on any specific component directory and can be used with any component.