Reputation: 1620
I recently learnt about lazy loading components and started using it. Now I am trying to prefetch the lazy loaded components as well as vue-router
routes. But using the chrome devtools I found that lazy loaded chunks are only loaded when we actually navigate to the lazy loaded route (in case of a vue-router route) or when the v-if
evaluates to true
and the component is rendered (in case of a lazy loaded component).
I have also tried using the webpackPrefetch: true
magic string in the router as well as component import statement but doing that does not seem to make any difference.
Project structure:
Master-Detail layout
router config:
import Vue from "vue";
import Router from "vue-router";
Vue.use(Router);
var routes = [
{
path: "/DetailPage",
component: () => import(/* webpackChunkName: "Detail-chunk" */ "AppModules/views/MyModuleName/DetailPage.vue")
},
{
path: "/MasterPage",
component: () => import("AppModules/views/MyModuleName/MasterPage.vue")
}
]
export const router = new Router({
routes: routes,
stringifyQuery(query) {
// encrypt query string here
}
});
export default router;
Master view:
<template>
<div @click="navigate">
Some text
</div>
</template>
<script>
export default {
name: "MasterPage",
methods: {
navigate() {
this.$router.push({
path: "/DetailPage",
query: {},
});
},
},
};
</script>
Details page:
<template>
<div>
<my-component v-if="showComponent" />
<div @click="showComponent = true">Show Component</div>
</div>
</template>
<script>
const MyComponent = () => import(/* webpackChunkName: "MyComponent-chunk" */ "AppCore/components/AppElements/Helpers/MyComponent");
export default {
name: "DetailPage",
components: {
MyComponent,
},
data() {
return {
showComponent: false
}
}
};
</script>
vue.js.config file:
const path = require("path");
const BundleAnalyzerPlugin = require("webpack-bundle-analyzer")
.BundleAnalyzerPlugin;
module.exports = {
publicPath: "some-url",
outputDir: "./some/path",
chainWebpack: webapckConfig => {
webapckConfig.plugin("html").tap(() => {
return [
{
inject: true,
filename: "index.html",
template: "./public/index.html"
}
];
});
},
productionSourceMap: true,
configureWebpack: {
plugins: [
new BundleAnalyzerPlugin({
analyzerMode: "server",
generateStatsFile: false,
statsOptions: {
excludeModules: "node_modules"
}
})
],
output: {
filename: "some file name",
libraryTarget: "window"
},
module: {
rules: [
{
test: /\.(woff|woff2)(\?v=\d+\.\d+\.\d+)?$/,
use: [
{
loader: "url-loader",
options: {
limit: 50000,
fallback: "file-loader",
outputPath: "/assets/fonts",
name: "[name].[ext]?hash=[hash]"
}
}
]
}
]
},
resolve: {
alias: {
vue$: process.env.NODE_ENV == 'production' ? 'vue/dist/vue.min.js' : 'vue/dist/vue.js',
AppCore: path.resolve(__dirname, "..", "..", "AppCoreLite"),
AppModules: path.resolve(__dirname, "..", "..", "AppModulesLite")
}
}
}
};
Both the async route and component do get split into separate chunks but these chunks are not prefetched.
When I navigate to the master view, I dont see Detail-chunk.[hash].js
in the network tab. It gets requested only when the navigate
method in the master page is executed (this the correct lazy load behaviour without prefetch).
Now when I am on the details page, MyComponent-chunk.[hash].js
is only requested when the showComponent
becomes true
(on click of a button)
I've also read at a few places that vue-cli
v3 does has prefetch functionality enabled by default and webpack magic string is not needed. I also tried that by removing the webpackPrefetch
comment but it made no difference.
I did vue-cli-service inspect
and found that prefetch plugin is indeed present in the webpack config:
/* config.plugin('preload') */
new PreloadPlugin(
{
rel: 'preload',
include: 'initial',
fileBlacklist: [
/\.map$/,
/hot-update\.js$/
]
}
),
/* config.plugin('prefetch') */
new PreloadPlugin(
{
rel: 'prefetch',
include: 'asyncChunks'
}
),
UPDATE: I tried removing the prefetch webpack plugin using config.plugins.delete('prefetch');
and then using the webpack magic comment: /* webpackPrefetch: true */
but it made no difference.
How do I implement prefetch functionality?
Upvotes: 7
Views: 10688
Reputation: 1106
since vue-router-prefetch
didn't work for me i ended up doing it manually.
Vue 3 Example - all routes are iterated on page load and async components are loaded
const router = createRouter({
history: createWebHistory(),
routes: [{
path: '/',
component: HomeView
}, {
path: '/about',
component: () => import('./views/AboutView.vue')
}]
});
async function preloadAsyncRoutes() {
// iterate all routes and if the component is async - prefetch it!
for (const route of router.getRoutes()) {
if (!route.components) continue;
// most routes have just a "default" component unless named views are used - iterate all entries just in case
for (const componentOrImporter of Object.values(route.components)) {
if (typeof componentOrImporter === 'function') {
try {
// prefetch the component and wait until it finishes before moving to the next one
await componentOrImporter();
} catch (err) {
// ignore failing requests
}
}
}
}
}
window.addEventListener('load', preloadAsyncRoutes);
Upvotes: 1
Reputation: 3297
I'm working on a mobile app. and wanted to load some components dynamically while showing the splash screen.
@Thomas's answer is a good solution (a Prefetch component), but it doesn't load the component in the shadow dom, and Doesn't pass Vetur validation (each component must have its template)
Here's my code:
main.vue
<template>
<loader />
</template>
<script>
import Loader from './Loader'
const Prefetch = () => import('./Prefetch')
export default {
name: 'Main',
components: {
Loader,
Prefetch
}
}
</script>
Prevetch.vue
<template>
<div id="prefetch">
<lazy-comp-a />
<lazy-comp-b />
</div>
</template>
<script>
import Vue from 'vue'
import LazyCompA from './LazyCompA'
import LazyCompB from './LazyCompB'
Vue.use(LazyCompA)
Vue.use(LazyCompB)
export default {
components: {
LazyCompA,
LazyCompB
}
}
</script>
<style lang="scss" scoped>
#prefetch {
display: none !important;
}
</style>
The loader component is loaded & rendered, then the Prefetch component can load anything dynamically.
Upvotes: 0
Reputation: 11
I solved this by creating a simple prefetch component that loads after a custom amount of time.
Prefetch.vue
<script>
import LazyComp1 from "./LazyComp1.vue";
import LazyComp2 from "./LazyComp2.vue";
export default {
components:{
LazyComp1,
LazyComp2,
}
}
</script>
App.vue
<template>
<Prefech v-if="loadPrefetch"></Prefech>
</template>
<script>
export default {
components: {
Prefech: () => import("./Prefetch");
},
data() {
return {
loadPrefetch: false
}
},
mounted() {
setTimeout(() => {
this.loadPrefetch = true;
}, 1000);
}
}
</script>
Upvotes: 1
Reputation: 6058
You need to implement vue-router-prefetch package for your need. Here is a working demo.
Note: From the working demo, you can notice from console.log
that only page 2
is prefetched by the QuickLink
component imported from vue-router-prefetch
Code :
import Vue from "vue";
import Router from "vue-router";
import RoutePrefetch from "vue-router-prefetch";
Vue.use(Router);
Vue.use(RoutePrefetch, {
componentName: "QuickLink"
});
const SiteNav = {
template: `<div>
<ul>
<li>
<router-link to="/page/1">page 1</router-link>
</li>
<li>
<quick-link to="/page/2">page 2</quick-link>
</li>
<li>
<router-link to="/page/3">page 3</router-link>
</li>
</ul>
</div>`
};
const createPage = (id) => async() => {
console.log(`fetching page ${id}`);
return {
template: `<div>
<h1>page {id}</h1>
<SiteNav />
</div>`,
components: {
SiteNav
}
};
};
const routers = new Router({
mode: "history",
routes: [{
path: "/",
component: {
template: `<div>
<h1>hi</h1>
<SiteNav />
</div>`,
components: {
SiteNav
}
}
}]
});
for (let i = 1; i <= 3; i++) {
routers.addRoutes([{
path: `/page/${i + 1}`,
component: createPage(i + 1)
}]);
}
export default routers;
Upvotes: 0
Reputation: 24661
Lazy loaded components are meant to be loaded only when user clicks the route. If you want to load component before it, just don't use lazy loading.
vue-router
will load components to memory and swap the content of the tag dynamically even if you will use normally loaded component.
Upvotes: 0