Prevent specific .vue components from being bundled - vue.js

Problem: In my vue-cli 4 app, I'd like to have build: script, which generates production bundle, with specific .vue-components not being included in some cases. In other cases they should be included. Also, these components are stored in app itself - not in external library.
I've tried: to import .vue-components dynamically - let's say I have an array:
const customPreset = ['WidgetFirst', 'WidgetSecond', ...]
And empty object:
const widgets = {}
So I've tried to make something like this:
customPreset.forEach(v => { Object.assign(widgets, { [v]: () => import('./' + v + '.vue') }) })
export default widgets
Changing customPreset to some other array would allow to import another set of components...
But that doesn't work, because import() cannot operate with expressions.
So, what could be done to include various .vue-components into production bundle in various cases? Maybe it could be achieved through tweaking vue.config.js?

What you are looking for is lazy loaded components. In Vue they are available at multiple points.
In vue-router - you can import components per route, to load only when they are needed:
This is how to define an async component that will be automatically code-split by webpack:
const Foo = () => import('./Foo.vue')
You can also group components in the same chunk:
const Foo = () => import(/* webpackChunkName: "group-foo" */ './Foo.vue')
const Bar = () => import(/* webpackChunkName: "group-foo" */ './Bar.vue')
const Baz = () => import(/* webpackChunkName: "group-foo" */ './Baz.vue')
Second option is Dynamic/Async components, which can be used in .vue files like this:
Vue.component(
'async-webpack-example',
// The `import` function returns a Promise.
() => import('./my-async-component')
)
It even support loading state, straight from the box:
const AsyncComponent = () => ({
// The component to load (should be a Promise)
component: import('./MyComponent.vue'),
// A component to use while the async component is loading
loading: LoadingComponent,
// A component to use if the load fails
error: ErrorComponent,
// Delay before showing the loading component. Default: 200ms.
delay: 200,
// The error component will be displayed if a timeout is
// provided and exceeded. Default: Infinity.
timeout: 3000
})

Related

Lazy loading & loading states with vue-router, vite & vuejs 2.x

I'm migrating an old project from vue-cli to vite. I followed the migration guide and everything worked great, but there's something it's not working, or at least, not as intended, when I was using vue-cli I tried to implement the loading states as shown in their documentation but then I saw the following pull request explaining how to achieve the wanted behavior (with the downside of losing navigation guards on those routes).
Now, after migrating I noticed that neither the loading/error components are rendered at all, even setting a timeout really small, however, I see in the networking tab that my components are being loaded, but never rendered.
Do you have any suggestions of why might this occur?.
// This is the lazyLoadView that was working before migrating to vite.
function lazyLoadView(AsyncView) {
const AsyncHandler = () => ({
component: AsyncView,
// A component to use while the component is loading.
loading: import("./components/loaders/loader.vue").default,
// A fallback component in case the timeout is exceeded
// when loading the component.
error: import("./components/loaders/error.vue").default,
// Delay before showing the loading component.
// Default: 200 (milliseconds).
delay: 1,
// Time before giving up trying to load the component.
// Default: Infinity (milliseconds).
timeout: 2,
});
return Promise.resolve({
functional: true,
render(h, { data, children }) {
// Transparently pass any props or children
// to the view component.
return h(AsyncHandler, data, children);
},
});
}
And the routes I have:
const routes = [
{
path: "/foo/",
component: () => lazyLoadView(import("./components/bar.vue")),
}
]
Let me know if you find why might this be happening.
So I figured out:
Looks like the loading & error components were also lazy loaded, they were skipped. So continued trying to obtain the main one until shown (that's why didn't render the loading besides of only showing a message).
So in order to fix it I had to import them at the top and include them in the lazyLoadView like this:
//These two imports at the top
import loaderComp from "./components/loaders/loader.vue";
import errorComp from "./components/loaders/error.vue";
function lazyLoadView(AsyncView) {
const AsyncHandler = () => ({
component: AsyncView,
// A component to use while the component is loading.
// must NOT be lazy-loaded
loading: loaderComp,
// A fallback component in case the timeout is exceeded
// when loading the component.
// must NOT be lazy-loaded
error: errorComp,
// Delay before showing the loading component.
// Default: 200 (milliseconds).
delay: 1,
// Time before giving up trying to load the component.
// Default: Infinity (milliseconds).
timeout: 2,
});
return Promise.resolve({
functional: true,
render(h, { data, children }) {
// Transparently pass any props or children
// to the view component.
return h(AsyncHandler, data, children);
},
});
}

nuxtServerInit in Modules mode does not run

I'm using nuxt.js in Universal mode and i'm trying to run nuxtServerInit() in one of my store actions, however it is not running.
In the docs it says...
If you are using the Modules mode of the Vuex store, only the primary
module (in store/index.js) will receive this action. You'll need to
chain your module actions from there.
However I don't quite understand what you need to do in order to make this work. Also if you need to use the primary module, then why would you need to use the module mode at all? Then you should just need to use the Classic mode.
store/posts.js
export const state = () => ({
loadedPosts:[]
});
export const getters = {
get(state){
return state
}
};
export const mutations = {
setPosts(state, payload){
state.loadedPosts = payload;
}
};
export const actions = {
async nuxtServerInit ({commit}){
const {data} = await axios.get('somedata')
console.log('I cannot see this comment');
commit('setPosts', data.results)
}
};
As it says in the documentation, you really need to have that function in the index.js file inside the store folder, which will not work otherwise.
here's an example of a production NuxtJs universal application with the ./store/index.js only with that file, you can easily call other stores methods by prefixing them with the file name, as the example shows auth/setAuth
I just faced the problem that nuxtServerInit was not called. The reason was in nuxt.config.js where ssr was set to false.
nuxt.config.js
export default {
// Disable server-side rendering: https://go.nuxtjs.dev/ssr-mode
// ssr: false, <-- Remove or out comment the line
/*
** Nuxt target
** See https://nuxtjs.org/api/configuration-target
*/
target: 'server',
// Global page headers: https://go.nuxtjs.dev/config-head
head: {
}
}

Laravel Mix lazy load components from vue files

im trying to load components from files rather than defining them within the app.js, but I also want to lazy load them, so trying to mix the 2 together.
So a lazy loaded component definiton would look like so:
Vue.component(
'carousel',
() => import(
/* webpackChunkName: "carousel" */
'./components/carousel.vue'
)
);
And registering the components using the files is like so:
const files = require.context('./', true, /\.vue$/i);
files.keys().map(key => Vue.component(key.split('/').pop().split('.')[0], files(key).default));
How can I combine this?
My current attempt is as follows, but of course I have missed out the webpackChunkName as no idea how to do that:
const files = require.context('./', true, /\.vue$/i);
files.keys().map(key => Vue.component(key.split('/').pop().split('.')[0], () => import(files(key)) ));
This doesn't work however, I just get an error saying:
WARNING in ./resources/js/app.js 9:11-29
Critical dependency: the request of a dependency is an expression
# multi ./resources/js/app.js ./resources/sass/index.sass
Ended up using the below code. I think after looking at it, it is similar to what Excalibaard posted, but I couldn't get that to work for me:
const files = require.context('./components', true, /\.vue$/i, 'lazy');
files.keys().map(key => {
const name = key.split('/').pop().split('.')[0];
Vue.component(name, () => import(/* webpackChunkName: "[request]" */'./components/' + key.slice(2)));
});

Dynamically choose compontent in Nuxt Router

I would like to render the same component for all languages. The translation will be done inside the component. The url and component relationship should look like so:
pseudocode:
browserurl: "/blogpost_1"
nuxtcomponent: "blogpost_1"
browserurl: "/en/blogpost_1"
nuxtcomponent: "blogpost_1"
browserurl: "/randompage"
nuxtcomponent: "randompage"
browserurl: "/en/randompage"
nuxtcomponent: "randompage"
My approach was to do the following, unfortunately i cant find a way to access pathMatch.
router: {
extendRoutes(routes, resolve){
routes.push({
path: 'en/*',
component: resolve(__dirname, '/' + somehow.access.pathMatch )
})
}
}
I don't think you can resolve component dynamically. component: expects component instance or Promise. Once the promise resolves, result is cached. I can imagine solution using navigation guards and switching components "by hand" but that would require to import views by hand etc.
So your best bet is to rewrite paths generated by nuxt to include optional path segment (from /about to /:lang(en|jp)?/about so it accepts paths like /en/about) - your component then receives lang parameter which will be empty for /about otherwise it will contain language....
Define available translations in meta
Rewrite the paths of pages with translations
In your page:
<script>
export default {
meta: {
translations: ['en', 'jp']
}
}
</script>
In Nuxt config (pseudo - not tested)
router: {
extendRoutes(routes, resolve) {
const routesWithTranslation = routes.filter(i => i.meta.translations && i.meta.translations.length > 0);
routesWithTranslation.forEach(route => {
const segment = `/:lang(${route.meta.translations.join("|")})?`
route.path = segment + route.path
})
}

How to import all Vue components from a folder?

I am trying to load all my vue's components automatically from a folder,
which is working fine if I don't use vue "Async Components".
Once I try to use Async Components with import .. I get this error:
10:11-36 Critical dependency: the request of a dependency is an expression
My code that load all components, which generate this error:
const ComponentContext = require.context('./', true, /\.vue$/i);
ComponentContext.keys().forEach((componentFilePath) => {
const componentName = componentFilePath.split('/').pop().split('.')[0];
Vue.component(componentName, () => import(componentFilePath));
});
How to fix this ? or is there is any other way to accomplish this?
Ok, I needed to add 'lazy' in:
const ComponentContext = require.context('./', true, /\.vue$/i, 'lazy');
and then:
Vue.component(componentName, () => ComponentContext(componentFilePath));
I had to merge the question with an answer here to get this final solution:
const ComponentContext = require.context('./', true, /\.vue$/i, 'lazy');
ComponentContext.keys().forEach((componentFilePath) => {
const componentName = componentFilePath.split('/').pop().split('.')[0];
Vue.component(componentName, () => ComponentContext(componentFilePath));
});
The 'lazy' third param was added to require.context(), and () => import() was changed to () => ComponentContext().
I can see the bundles in the Network tab of the dev tools pane, and I don't see the bundles when I navigate to a page that doesn't render any of the auto-loaded components.
Therefore, I am reasonably-certain the above code is autoloading and dynamic importing. I will also confirm that in my project, I am using:
require.context('~/components/common', true, /\.vue$/i, 'lazy')
Note where mine is different at ~/components/common compared to ./. Your project needs will likely be different. In mine, the ~ is a Webpack alias for /resources/js, so my full path would be ./resources/js/components/common. The rest of the code above is an algorithm, and can remain untouched.
Instead of
Vue.component(componentName, () => import(componentFilePath));
Try
Vue.component(componentName, ComponentContext(componentFilePath));
Or
Vue.component(componentName, ComponentContext(componentFilePath).default);
not sure about the default part.