Laravel Mix lazy load components from vue files - vue.js

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)));
});

Related

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.

Vue.js routing not working by using parameter as a route

I'm trying to use parameter only as a route as I need URL like localhost:8080/road. But, it's not working properly.
My code:
{
path: "/:id",
name: "id",
component: Blog
},
Whenever I enter a url like localhost:8080/dashboard or any other URL, it uses the Blog component. How can I solve it?
If your goal is to cut down on lines of code in your router definition, you could optimise this somewhat with an array of component names mapped to route definitions. For example
const components = ["Blog", "Road", "Dashboard"]
// creates kebab-cased slugs from Pascal cased component names
const toSlug = component =>
component.replace(/(?<=\w)[A-Z]/g, c => `-${c}`).toLowerCase()
const routes = components.map(name => ({
name,
path: toSlug(name),
component: () => import(`#/components/${name}.vue`)
})
Using static parts in the import path like #/components/ and .vue help Webpack optimise bundling.

Prevent specific .vue components from being bundled

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
})

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.

Is there a smart function to import dynamic components automatically?

How can I dynamically import dynamic components? I found a few examples, but they seem like a workaround. I couldn't find a clear explanation for that.
I import them one by one like that:
Vue.component('account', () => import('../components/Account')
Vue.component('settings', () => import('../components/Settings')
// etc… one for each component
and my main component is like that:
<component :is="componentName" :data="myData"/>
data: () => ({
componentName: 'account'
})
Is there a way to make a smart method for the first code?
Try this, adapted from the Laravel source:
// replace './' with the relative path to your components
const files = require.context('./', true, /\.vue$/i)
files.keys().map(key => Vue.component(key.split('/').pop().split('.')[0], () => import(files(key))))
I found the solution here.
I need dynamic imports on Webpack.
https://webpack.js.org/guides/code-splitting/