I'm working on a project with nuxt.js and I want to implement the atomic design methodology
so I currently import the components like this
import ButtonStyled from '#/components/atoms/ButtonStyled.vue'
import TextLead from '#/components/atoms/TextLead.vue'
import InputSearch from '#/components/atoms/InputSearch.vue'
but I need to import like this
import {
ButtonStyled,
TextLead,
InputSearch
} from '#/components/atoms'
the closer I got was that,
/atoms/index.js
const req = require.context('./', true, /\.vue$/)
const modules = {}
req.keys().forEach(fileName => {
const componentName = fileName.replace(/^.+\/([^/]+)\.vue/, '$1')
modules[componentName] = req(fileName).default
})
export const { ButtonStyled, TextLead } = modules
but I'm still defining the export variable names statically, I need to define dynamics based on the components inside the folder
NOTE: I can not use
export default modules
if I use the above code snippet I will not be able to import the way I need it, which is:
import { ButtonStyled } from "#/components/atoms"
require.context is a quite obscure function in Webpack, you will have issues while running unit tests. But, to solve your problem; You will need to import the index.js file in the main.js of your project.
This is how I do it:
_globals.js
// Globally register all base components prefixed with _base for convenience, because they
// will be used very frequently. Components are registered using the
// PascalCased version of their file name.
import Vue from 'vue'
import upperFirst from 'lodash/upperFirst'
import camelCase from 'lodash/camelCase'
const requireComponent = require.context('.', true, /_base-[\w-]+\.vue$/)
requireComponent.keys().forEach(fileName => {
const componentConfig = requireComponent(fileName)
const componentName = upperFirst(
camelCase(fileName.replace(/^\.\/_base/, '').replace(/\.\w+$/, ''))
)
Vue.component(componentName, componentConfig.default || componentConfig)
})
components/index.js
//...
import './_globals'
//...
main.js
//...
import './components' // This imports in the index.js
//...
This way your components loaded in with require.context() gets registered as a vue component and made globally available. I advice to only use global components with components that will be used a lot. Do not load a component globally if you intend to use it only one time.
You can find a working example here -> https://github.com/IlyasDeckers/vuetiful/tree/master/src
To get your unit tests working with jest, you will need to mock require.context(). This was a true pain, but can be achieved easily by using babel-plugin-transform-require-context
I try to use your way to do that, and known you have make a mistake at module.exports
module.exports can not use import , i think you may can do like this
at atoms/index.js
const req = require.context("./", true, /\.vue$/);
const atoms = {};
req.keys().forEach(fileName => {
const componentName = fileName.replace(/^.+\/([^/]+)\.vue/, "$1");
atoms[componentName] = req(fileName).default;
});
export default atoms;
at where to use
import k from "#/components/atoms/index.js";
export default {
components: {
test1: k.test1,
test2: k.test2
}
};
or index.js
import test1 from "./test1.vue";
import test2 from "./test2.vue";
export { test1, test2 };
and where to use like this
import {test1,test2} from "#/components/atoms/index.js";
export default {
components: {
test1,
test2
}
};
I created a library that does all this for me, maybe it helps other people.
named-exports
Related
I am working on my first Vue project. I'm used to React and vanilla js, but just getting my head around a few concepts in Vue here.
In particular, importing state and action props from a Pinia store, and seemingly having to import those multiple times in a single Vue component (something I don't need to do in React).
In this example, I am importing a simple count value, and an increment function, and trying to use these in a few different places:
<script setup>
// I import everything initially in setup, which works fine,
// and these props (currentCount and incrementCount)
// can be used in my template:
import { storeToRefs } from 'pinia';
import { useStore } from '#/stores/store';
const { currentCount } = storeToRefs(useStore());
const { incrementCount } = useStore();
</script>
<template>
<main>
Current count: {{ currentCount }}
<button #click="incrementCount">Increment</button>
</main>
</template>
<script>
// I can't use store values from setup here.
// This doesn't work:
// console.log(currentCount);
// I also can't import store values here.
// I get the following error:
// "getActivePinia was called with no active Pinia"
// const { currentCount } = storeToRefs(useStore());
export default {
mounted() {
// I have to import store values here for them to work:
const { currentCount } = storeToRefs(useStore());
console.log(currentCount);
},
watch: {
// weirdly, this reference to watching "currentCount" works:
currentCount() {
// I also have to import store values here for them to work:
const { currentCount } = storeToRefs(useStore());
console.log(currentCount);
},
},
};
</script>
As you can see, if I want to use store values in my template, on mount, and in a watcher (whereby I'd use React's useEffect hook) I am having to import the store props 3 times in total.
Is this correct / normal? Is there a simpler way to achieve what I'm doing, where I only import props once? I want to be sure I haven't missed something and am not doing something in an unusual way.
Thanks for any help and advice!
Pinia was designed with Composition API in mind.
So its intended usage is inside setup() function, where you'd only import it once.
To use it outside of a setup() function, you have two main routes:
inside components, you can just return it from setup() and it becomes available in any hook/method/getter. Either as this.store or spread:
import { useStore } from '#/store'
import { toRefs } from 'vue'
// or from '#vue/composition-api' in Vue2
export default {
setup: () => ({ ...toRefs(useStore()) })
}
/* this makes every state prop, getter or action directly available
on current component instance. In your case, `this.currentCount`.
Obviously, you can also make the entire store available as `this.someStore`:
setup: () => ({ someStore: useSomeStore() })
// now you can use `this.someStore` anywhere
*/
a more general approach is to export the pinia instance (returned by createPinia()), from main.(js|ts), import it where you need the store and then call useStore() passing the pinia instance as an argument.
This can be done anywhere, even outside of components.
Generic example:
import { pinia } from 'main.js'
import { useSomeStore } from '#/store'
const someStore = useSomeStore(pinia);
I should probably also mention the mapState helper provided by pinia. It allows you to select only a few of the keys exposed to current instance. Example:
import { mapState } from 'pinia'
// ...
computed: {
...mapState(useSomeStore, [ 'currentCount'])
}
// Now `this.currentCount` is available
Note: mapState is weirdly named, as it allows you to access more than just state props (also getters and actions). It was named mapState to match the similar helper from vuex.
An even more general approach is to add your store as global, using the plugin registration API in Vue2:
import { useSomeStore } from '#/store';
import { createPinia } from 'pinia';
const pinia = createPinia();
const someStorePlugin = {
install(Vue, options) {
Vue.prototype.someStore = useSomeStore(options.pinia)
}
};
Vue.use(someStorePlugin, { pinia });
new Vue({ pinia });
After this, every single component of your Vue instance will have this.someStore available on it, without you needing to import it.
Note: I haven't tested adding a store in globals (and I definitely advise against it - you should avoid globals), but i expect it to work.
If you want to combine pinia stores with the options API, one way to do it is to use the setup() function inside the options to call useStore:
<script>
import { useStore } from '#/stores/store';
export default {
setup() {
const store = useStore();
return {store}
},
watch: {
store.currentBrightness(newVal, oldVal){
// your code
}
},
methods: {
// inside methods use this.store
},
mounted() {
console.log(this.store.currentCount);
}
}
</script>
Some might consider this as a unwanted mix of composition and options API, but in my view it is a quite good solution for pinia stores.
Nechoj, has the most straightforward answer. Also if you have multiple stores you can always import the stores as necessary into a parent component then use inject just add some parts. For example I have a route data that is called via an api, I don't need it everywhere all the time so i call it in a parent then use inject to use those routes in a drop down that might be a great grandchild component. I don't need that whole utils store just the routes.
index page:
import { useUtilsStore } from "src/stores/utilsStore";
const passengerRoutes = computed(() => utilsStore.getPassengerRoutes);
provide("passengerRoutes", passengerRoutes);
grandchild component:
const compRoutes = inject("passengerRoutes");
Anytime I have a long list of items to import in javascript my natural intention is to export those items in a separate file, require that file in my main javascript file, then if they need to be loaded into Vue or some other process, loop through them and load them dynamically.
I'm just getting started with VueORM, and ORM Models need to be loaded into the VueORM instance like so:
import Foo from '#/models/foo'
import Bar from '#/models/bar'
database.register(Foo)
database.register(Bar)
My app will have dozens of models so I'd like to export those from a single file as I mentioned, then loop through them and register them.
// main.js
import * as models from '#/models'
Object.keys(models).forEach(key => {
database.register(model[key])
})
In my models I'm exporting each as a function like so:
// models/index.js
export const Foo = () => import('#models/foo')
export const Bar = () => import('#models/bar')
This normally works fine, but in VuexORM the models are not being loaded.
Can someone please advise on a better method to load these Models from a separate file?
Vuex ORM does not handle dynamic imports. Rather than export them as dynamic imports, simply export them using the ordinary ESM syntax?
The following assumes your models are default exports.
// models/index.js
export { default as Foo } from '#/models/foo'
export { default as Bar } from '#/models/bar'
Subsequently, your main.js can aggregate the modules:
// main.js
import * as models from '#/models'
Object.values(models).forEach(model => database.register(model))
If you have Vite you can now do this. Taken from Quasar app.
//store/OrmModelPlugin.js
import { Database, install } from '#vuex-orm/core'
export default function () {
//Require all js files in our models directory
const database = new Database()
const modelFileContext = import.meta.globEager('src/models/*.js')
Object.values(modelFileContext).forEach(modelImport => {
const model = modelImport.default
database.register(model)
})
return install(database)
}
//store/index.js
import { store } from 'quasar/wrappers'
import { createStore } from 'vuex'
import OrmModelPlugin from './OrmModelPlugin'
export default store(function () {
const Store = createStore({
plugins: [OrmModelPlugin()],
})
return Store
})
I have a file that is similar to this:
const COLORS = {
PRIMARY_COLOR: 'red',
SECONDARY_COLOR: 'green'
};
const APP = {
APP_COLOR: GRAY_DARK,
APP_FONT_SIZE: FONT_SIZE_NORMAL,
APP_FONT_WEIGHT: FONT_WEIGHT_NORMAL,
APP_SEPARATOR_COLOR: GRAY_LIGHT
};
export default {
...COLORS,
...APP
};
The issue is when I'm trying to destructure that object from another file, I get undefined values?
import theme, { PRIMARY_COLOR } from '../../../themes/default';
printing the theme object works fine
but printing PRIMARY_COLOR gets undefined
Any help?
The {} syntax in imports is for "named" imports and is not destructuring.
Just do this:
import theme from '../../../themes/default';
const { PRIMARY_COLOR } = theme;
To understand the difference, you first need to know the way they are exported.
In case of React, the export goes something like this
const Component = ...
...
...
export Component;
This becomes available under React.Component and you can import it like import { Component } from 'react';
The way these look under the microscope is:
default.Component
...
While everything else is just under the default object.
If you do a quick console.log of theme, you'll understand what I mean.
I hope this makes sense.
Let's go a little in depth.
Suppose you have the following bit of code:
const a = {
test: 'hello',
};
const b = {
foo: 'bar',
}
export default a;
Now, let's import that
import * as theme from './test.js'
When we do a console.log( theme ) we get
{ default: { test: 'hello' } }
What does this show? It means that the export object of a file contains a default property which is automatically loaded into memory when we do something like import theme from 'test'. However, if you have more than one export, the compiler gives you the option to pick and choose, but at the same time, provides you with a default object just for fall back.
In your case, you have exported everything under the default. When you do import theme from './theme' all works fine. However, when you do { PRIMARY_COLOR }... it tries to find something which was exported like
export PRIMARY_COLOR...
I hope this makes it clear! :)
I am trying to import a single function to my Vue component. I've created a separated js file for my function:
randomId.js:
exports.randomId = () => //My function ...
In my Vue component, I've imported the Random js:
let randomId = require('../functions/randomId');
randomId();
but Webpack throws an error of "randomId is not a function".
I tried to import the file using import syntax, but the error remains.
import randomId from '../functions/randomId';
Should I use some other methods for importing single functions? I'm relatively new to Webpack and JS6.
Change your function module to properly use ES6 export:
export function randomId() { /*My function ...*/ }
And then use ES6 named import:
import { randomId } from '../functions/randomId';
If you want to use CommonJS, then in the file with your randomId function do the following:
function randomId() {
...
}
module.exports = randomId;
And then the let randomId = require('../functions/randomId'); in your Vue component will work.
I'm using React, and I have something like this in my code:
renderDetails.js:
export default renderDetails = (details) => {
// function logic removed for brevity
}
Then, in the same folder, I have another source file from where I want to import it, and I do something like this:
businessDetails.js:
import renderDetails from './renderDetails';
// rest removed for brevity
But, I get an error message pointing to my renderDetails.js file and says: "rederDetails is not defined". Any ideas what the problem might be and how to fix it?
The problem is that even though you are exporting the component as default you are giving it a name which is not defined
You can either do
export default (details) => {
}
or
const renderDetails = (details) => {
}
export default renderDetails;
One more thing, when you are trying to render components, make sure that their name starts with a Uppercase character.
try that way.
functions.jsx
export function renderDetails(details) => {
// function logic removed for brevity
}
then import like,
import { renderDetails } from './functions';
P.S.
./ is for if both files a re in same namespace/folder.
You can also write them like this:
export const exampleFunctionOne = () => {}
export const exampleFunctionTwo = () => {}
Then import the individual functions you require from said file like this:
import { exampleFunctionOne, exampleFunctionTwo } from './exampleFile';
I like this approach when wanting to combine similar functions into one file such as validators or filters making them easy to import and use across your application.