Dynamic Vue Import from a subfolder - javascript

I am trying to import all .vue files in a certain subfolder into another component. I know about Global registration of Base components but this does not seem to help me here.
Let's say I have default Vue component (not the main one) with something like this:
export default {
components: {
red: () => import('./panes/red'),
},
data() {
return {
current: false,
};
},
and my file structure is something like:
/src
- main.js
- main.vue
-- components/
-- panes/
--- /red.vue
--- /green.vue
--- /blue.vue
-- SubMain.vue
I am trying to dynamically create the components object for the SubMain.vue folder.
So I try something like this in SubMain.vue:
import myComponents from './src/components/panes';
...
components: Object.keys(myComponents).reduce((obj, name) => {
return Object.assign(obj, { [name]: () => import(myComponents[name]) })
}, {}),
But eslint is giving me errors about Missing file extension and Unable to resolve path to module on the myComponents variable. Yes, I double checked my path and tried other paths. Nothing. I am using the Vue CLI 3 project.

If you're using Webpack, you can use require.context:
import _kebabCase from lodash/kebabCase
const components = require.context('src/panes', true)
components.keys().map(component => {
// turn './ComponentName.vue' into 'component-name'
const componentName = _kebabCase(
component.replace(/^\.\//, '').replace(/\.vue$/, '')
)
// register new component globally
Vue.component(componentName, components(component))
})

I don't think you can import multiple components that way without an index file that aggregates the component import/exports for you. See Import multiple components in vue using ES6 syntax doesn't work

Related

ReferenceError: navigator is not defined (mxgraph with next.js) [duplicate]

Trying to create an xterm react component in Next.js I got stuck as I'm not able to get over an error message I've never got before.
I'm trying to import a npm client-side module called xterm, but if I add the import line the application crashes.
import { Terminal } from 'xterm'
The error reads Server Error... ReferenceError: self is not defined
and then shows this chunk of code as Source
module.exports = require("xterm");
According to some research I did, this has to do with Webpack and could be helped if something like this was done:
output: {
globalObject: 'this'
}
Would you know how to fix this?
The error occurs because the library requires Web APIs to work, which are not available when Next.js pre-renders the page on the server-side.
In your case, xterm tries to access the window object which is not present on the server. To fix it, you have to dynamically import xterm so it only gets loaded on the client-side.
There are a couple of ways to achieve this in Next.js.
#1 Using dynamic import()
Move the import to your component's useEffect, then dynamically import the library and add your logic there.
useEffect(() => {
const initTerminal = async () => {
const { Terminal } = await import('xterm')
const term = new Terminal()
// Add logic with `term`
}
initTerminal()
}, [])
#2 Using next/dynamic with ssr: false
Create a component where you add the xterm logic.
// components/terminal-component
import { Terminal } from 'xterm'
function TerminalComponent() {
const term = new Terminal()
// Add logic around `term`
return <></>
}
export default TerminalComponent
Then dynamically import that component when using it.
import dynamic from 'next/dynamic'
const TerminalComponent = dynamic(() => import('<path-to>/components/terminal-component'), {
ssr: false
})
As an alternative, you could add the logic directly when dynamically importing the library with next/dynamic to avoid having an extra file for it.
import dynamic from 'next/dynamic'
const Terminal = dynamic(
{
loader: () => import('xterm').then((mod) => mod.Terminal),
render: (props, Terminal) => {
const term = new Terminal()
// Add logic with `term`
return <></>
}
},
{
ssr: false
}
)

Dynamic export of variables ES5 to ES6

I'm working on a vue/nuxt.js project and would like to apply the atomic design methodology, i would like to import the components in a clustered and smarter way.
currently
import ButtonStyled from '#/components/atoms/ButtonStyled.vue'
import TextLead from '#/components/atoms/TextLead.vue'
import InputSearch from '#/components/atoms/InputSearch.vue'
How I wish
import {
ButtonStyled,
TextLead,
InputSearch
} from '#/components/atoms'
Solution?
index.js in folder of atoms
it works perfectly (ES5)
// ES5 works 👍
const req = require.context('.', false, /\.vue$/)
req.keys().forEach(fileName => {
const componentName = fileName.replace(/^.+\/([^/]+)\.vue/, '$1')
module.exports[componentName] = req(fileName).default
})
// ES6 does not work 👎
// ERROR: Module build failed, 'import' and 'export' may only appear at the top level
const req = require.context('.', false, /\.vue$/)
req.keys().forEach(fileName => {
const componentName = fileName.replace(/^.+\/([^/]+)\.vue/, '$1')
export const [componentName] = req(fileName).default
})
nuxt use ES6
NOTE: I can not export an object because I can not use import {ButtonStyled} or I will have to de-structure the object after importing it
I need to export so that I can use
import { ButtonStyled } from '#/components/atoms'
I need to export name of each component in the folder
Any advice, information or suggestions will be appreciated.
Thanks in advance.
Well first of all you need to be careful when making use of import/export on EM6, since now you can't export anywhere outside of the top level scope of the js file and the general treatment of it is different than in EM5.
Now with the problem. I see you are exporting the components from inside of a ForEach loop/function and that works totally fine in EM5 but with EM6 It's different, and at least I see two ways you can solve the problem if you aren't expecting the number of components to grow dinamically:
Call a function that returns the component and export it, do it for each component. Should look something like this:
./componentsFile.js
exports.component1 = () => { /*code...*/ return component }
exports.component2 = () => { /*code...*/ return component }
./renderingFile.js
import { component1, component2 } from './componentsFile.js'
/* use the components that you get from the return... */
The other way is to build an object which fields are the components. And destructure it when you are importing.
./componentsFile.js
const component1 = /*Create the component*/
const component2 = /*Create the component*/
exports.object = {
component1,
component2,}
./renderingFile.js
import { component1, component2 } from './componentsFile.js'
/*Use the components...*/
I think you can get the idea with this two ways.
I created a library that solved this problem for me, makes exports named from a directory and listens to the creation, rename and exlclusion of the modules and updates the index.js that does the export.
Maybe it helps other people.
named-exports

import and export all .vue files from the folder

I'm working on a VueJS project.
I have a folder with several single file components and I need to export all in one index.js
so I have this code
import a from './a.vue'
import b from './b.vue'
export {
a,
b
}
but I'd like to do this dynamically, so I do not have to change this index.js every time I create a new component
someone to help?
From this article, you could try this:
const requireModule = require.context(".", false, /\.vue$/); //extract vue files inside modules folder
const modules = {};
requireModule.keys().forEach(fileName => {
const moduleName = fileName.replace(/(\.\/|\.vue)/g, ""); //
modules[moduleName] = requireModule(fileName).default;
});
export default modules;
I created a library that does all this work, follow the link
named-exports

Only dynamically import module once rather than on every component instance in React

I have a react component that conditionally imports a module. The reason is if the module is imported normally at the top of the file, this breaks another project's webpack build that depends on this component because it cannot process the imported module.
I have solved this issue with es6 dynamic imports - however, the problem now is that every instance of the component re-imports the module. If I have 100 components on 1 page, it'll import that module 100 times, making it horribly inefficient and slowing page load times.
What would be the proper way to only import it once, then have the rest of all component instances reference that 1 dynamically imported module?
Here's my component:
import React from "react"
export default class Link extends React.Component {
state = {
gatsbyLink: null
}
...
componentDidMount() {
if (GLOBAL_FLAG) {
import("gatsby").then(({ Link }) => {
this.setState({
gatsbyLink: Link
})
})
}
}
render() {
const { gatsbyLink } = this.state;
const GatsbyLink = gatsbyLink ? gatsbyLink : "";
...
return (<GatsbyLink {...}>...</GatsbyLink>)
}
}
I'm not sure dynamically imported modules are cached. If they aren't you could export a promise that resolves a dynamically imported gatsby if the flag is set.
You will still have to import and call it in every component where you use gatsby, but it will not dynamically import the module every time.
const conditionallyResolveGatsby = () => {
// You could reject as well
return GLOBAL_FLAG ? import('gatsby') : Promise.resolve();
};
export default conditionallyResolveGatsby();
You could also try to use require.

Webpack Dependency Management and Vue.js async component loading

I am trying to achieve Vue.js dynamic async component registration. This video gave me code that works perfectly fine, but it loads all modules even if they are not used.
import Vue from 'vue'
import upperFirst from 'lodash/upperFirst'
import camelCase from 'lodash/camelCase'
// Require in a base component context
const requireComponent = require.context(
'../components/base', false, /base-[\w-]+\.vue$/,
)
requireComponent.keys().forEach(fileName => {
// Get component config
const componentConfig = requireComponent(fileName)
// Get PascalCase name of component
const componentName = upperFirst(
camelCase(fileName.replace(/^\.\//,
'').replace(/\.\w+$/,
'')),
)
// Register component globally
Vue.component(componentName, componentConfig.default || componentConfig)
})
What I tried to achieve was to create async components instead. Like so
import Vue from 'vue'
import upperFirst from 'lodash/upperFirst'
import camelCase from 'lodash/camelCase'
// Require in a base component context
const requireComponent = require.context(
'../components/base', false, /base-[\w-]+\.vue$/,
)
requireComponent.keys().forEach(fileName => {
const componentPath = fileName.replace('./', '../components/base/');
// Get PascalCase name of component
const componentName = upperFirst(
camelCase(fileName.replace(/^\.\//,
'').replace(/\.\w+$/,
'')),
)
// Register component globally
Vue.component(componentName, () => import(componentPath))
})
If the case of the code above vue throws an error
vue.esm.js:591 [Vue warn]: Failed to resolve async component: function () {
return __webpack_require__("./lib lazy recursive")(componentPath);
}
Reason: Error: Cannot find module '../components/base/base-button.vue'
If I manually write down Vue.component('BaseButton', () => import('../components/base/base-button.vue'))
it works without problem but when I try to do that dynamically it fails. Is it possible to do such async component registration if so how?
This doesn't work either:
const button = '../components/base/base-button.vue'
Vue.component('BaseButton', () => import(button))
only if I literally right the string into import function.
import cannot be used when the module path is fully dynamic. See the docs:
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.
You didn't specify the mode argument for require.context which defaults to "sync"; this means all modules matched will be loaded straight away. You want to use "lazy" which generates a lazy-loadable chunk for each module.
Untested, but I imagine it'll be something like this:
const context = require.context('../components/base', false, /base-[\w-]+\.vue$/, 'lazy');
context.keys().forEach(fileName => {
const componentPath = fileName.replace('./', '../components/base/');
// Get PascalCase name of component
const componentName = upperFirst(
camelCase(fileName.replace(/^\.\//,
'').replace(/\.\w+$/,
'')),
);
// Register component globally
Vue.component(componentName, () => context(fileName));
});

Categories

Resources