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.
Related
I'm looking for a way to use a component in a dynamic way:
const getDynamicComponent = (componentName) => dynamic(() => import(${componentName}), {
ssr: false,
loading: () => <p>Loading...</p>,
});
const Test = () => {
const router = useRouter();
const { component } = router.query;
const DynamicComponent = getDynamicComponent(component);
return <DynamicComponent />
}
Obiovusly if I specify a folder name there like components/${componentName} it searches ALL components.. and not the one specified in the variable.
I know the documentation states explicitly that template strings are not an option for dynamic imports, but I would like to know how I would be able to import a specific component from node_modules, without importing all node_modules folder 🙂
Only static strings are permitted?
I have a dynamic route file as [componentId].js which should import its component from node_modules based on the route name.. any ideas?
Should I try to configure babel in a specific way to make this work?
Thanks!
PS: I know it was asked here, but those answers are not quite correct as the whole folder is trying to get imported, not just the component specified.
I am building a vue plugin called vue-scan-field. At the previous version (0.1.2) the plugin only supported vuetify. Now I want to also add quasar to this plugin. So what I have decided to do is use dynamic imports for the components. For example the quasar file looks like this:
export default {
components: {
scan_text_field: () => import('quasar/src/components/input/QInput'),
scan_textarea: () => import('quasar/src/components/input/QInput'),
scan_checkbox: () => import('quasar/src/components/checkbox/QCheckbox'),
scan_select: () => import('quasar/src/components/select/QSelect')
},
...
}
And I also have this for vuetify:
export default {
components: {
scan_text_field: () => import('vuetify/lib/components/VTextField'),
scan_textarea: () => import('vuetify/lib/components/VTextarea'),
scan_checkbox: () => import('vuetify/lib/components/VCheckbox/VCheckbox'),
scan_select: () => import('vuetify/lib/components/VAutocomplete'),
},
...
}
Now download the module from npm and run my quasar project I get these errors:
These dependencies were not found:
* vuetify/lib/components in ./node_modules/vue-scan-field/dist/vue-scan-field.esm.js
* vuetify/lib/components/VAutocomplete in ./node_modules/vue-scan-field/dist/vue-scan-field.esm.js
* vuetify/lib/components/VCheckbox/VCheckbox in ./node_modules/vue-scan-field/dist/vue-scan-field.esm.js
* vuetify/lib/components/VTextField in ./node_modules/vue-scan-field/dist/vue-scan-field.esm.js
* vuetify/lib/components/VTextarea in ./node_modules/vue-scan-field/dist/vue-scan-field.esm.js
Now this can be because maybe my compiler imports the components on build. I don't know if I can safely disable this function (I don't think so). Is there any way to fix this?
NPM Package: https://www.npmjs.com/package/vue-scan-field
Github page: https://github.com/jessielaf/vue-scan-field
Dynamic components in Webpack (and in general) work because bundler at build time prepares js files (with component code) which are at runtime loaded (or not ...depending on logic) by the app. Bundler can not execute your code and evaluate the logic which decides which component should be loaded. It must prepare the budle for all possible outcomes.
So yes, of course your bundler looks for and bundle all the files even they are used in dynamic import.
I do not know exactly how to set up Rollup correctly but I'm pretty sure you don't want to include any Vuetify/Quasar code as part of your library. Such code would be duplicate and possibly from different version of the framework the user of your library is already using! So instead of using string configuration (framework: 'vuetify'), leave it to your users to pass the component mapping (and maybe add sample to your docs)...
// dynamic
Vue.use(ScanField, { components: {
scan_text_field: () => import('quasar/src/components/input/QInput'),
scan_textarea: () => import('quasar/src/components/input/QInput'),
scan_checkbox: () => import('quasar/src/components/checkbox/QCheckbox'),
scan_select: () => import('quasar/src/components/select/QSelect'),
scan_date: () => null
}})
// static
import QInput from 'quasar/src/components/input/QInput'
Vue.use(ScanField, { components: {
scan_text_field: QInput,
...
}})
Alternative solution is to use normal (not dynamic) imports, but configure Rollup so that both Vuetify and Quasar are treated as peerDependencies (dependency your lib requires but the user is responsible to add/install it into his project) - see the docs
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)));
});
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 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/