How to use composable inside a component from the same npm module - javascript

I want to create a npm plugin that contains all the elements that I often use in my Nuxt projects.
I followed the instructions here : https://v3.nuxtjs.org/guide/going-further/modules/ and there : https://www.youtube.com/watch?v=7MBOOxUg1yM but I'm still experiencing an issue with the composables.
I added this in the setup function of my module.js :
const runtimeDir = fileURLToPath(new URL('./runtime', import.meta.url));
addImports({
name: 'useTheme',
as: 'useTheme',
from: resolve(runtimeDir, 'composables')
});
await addComponentsDir({
path: resolve(runtimeDir, 'components'),
prefix: 'my-project'
});
And here's my composables.js :
import { useState } from '#app';
export const useTheme = () => useState('theme', () => 'dark');
And when I try to use the useTheme in my ThemeToggle.vue, I have an error "useTheme is not defined"
<script setup>
const theme = useTheme();
</script>
I tried to add an import like this :
import { useTheme } from '#app';
But it throw me an other error __vite_ssr_import_0__.useTheme is not a function
Any idea ?

Related

Vue 3: Add global method via Plugin not working

I'm trying to build a method that can be used inside any template to automatically build local image urls.
The issue I'm facing is that when I try to build a plugin that adds a global property, it's not working!
Plugin code
// src/plugins/urlbuilder.js
export default {
install: (app) => {
app.config.globalProperties.buildImageUrl = imageName => require('#/assets/images/' + imageName);
}
}
Main.js file
// src/main.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import urlbuilder from './plugins/urlbuilder.js'
createApp(App).use(router).use(urlbuilder).mount('#app')
Home view where I render the image
// src/views/Home.vue
<template>
<img :src="buildImageUrl('myimage.jpg')">
</template>
and I'm getting this error in my the dev console:
Uncaught (in promise) TypeError: _ctx.buildImageUrl is not a function
at Proxy.render (cjs.js?!./node_modules/babel-loader/lib/index.js!./node_modules/vue-loader-v16/dist/templateLoader.js?!./node_modules/cache-loader/dist/cjs.js?!./node_modules/vue-loader-v16/dist/index.js?!./src/views/Home.vue?vue&type=template&id=fae5bece&scoped=true:57)
at renderComponentRoot (runtime-core.esm-bundler.js:922)
at ReactiveEffect.componentUpdateFn [as fn] (runtime-core.esm-bundler.js:4667)
at ReactiveEffect.run (reactivity.esm-bundler.js:195)
at setupRenderEffect (runtime-core.esm-bundler.js:4793)
at mountComponent (runtime-core.esm-bundler.js:4576)
at processComponent (runtime-core.esm-bundler.js:4534)
at patch (runtime-core.esm-bundler.js:4138)
at ReactiveEffect.componentUpdateFn [as fn] (runtime-core.esm-bundler.js:4744)
at ReactiveEffect.run (reactivity.esm-bundler.js:195)
Note: This works when I add purely a global property, but I read the best way to do this was via plugin.
It works when I do this:
app = createApp(App)
app.config.globalProperties.buildImageUrl = imageName => require('#/assets/images/' + imageName)
app.use(router).mount('#app')
What am I doing wrong?
A better way would be to use provide and inject
import urlbuilder from './plugins/urlbuilder.js'
app.provide('$urlbuilder', urlbuilder);
Read more about provide and inject
You should get global properties by this:
const instance = getCurrentInstance()
const globalProperties = instance.appContext.config.globalProperties
console.log(globalProperties)
Recommand use provide and inject.
Or use a hook:
useGlobalProps.ts
import { getCurrentInstance } from 'vue'
import type { ComponentInternalInstance } from 'vue'
function useGlobalProps() {
const { appContext } = getCurrentInstance() as ComponentInternalInstance
const globalProps = appContext.config.globalProperties
return { ...globalProps }
}
export default useGlobalProps
use it in component:
import useGlobalProps from '#/hooks/useGlobalProps'
const { testFn, globalFn } = useGlobalProps()
testFn()
globalFn('global function in main.js')
Register globalFn in main.js
app.config.globalProperties.globalFn = function testGlobal(name: string) {
console.log(name)
}
Register testFn by plugin:
myPlugin.js
export default function (app: App<HTMLElement>) {
app.config.globalProperties.testFn = () => {
console.log('install global properties')
}
return app
}
use plugin in main.js
import myPlugin from './myPlugin.js'
// ...
app.use(myPlugin)

How to use Vue 3 Meta with Vue.js 3?

It seems that Vue Meta has been upgraded to handle Vue.js 3 with a new npm package called vue-3-meta
Before Vue.js 3, it was easy to use vue-meta by adding it to the Vue instance:
import Vue from 'vue'
import VueMeta from 'vue-meta'
Vue.use(VueMeta, {
// optional pluginOptions
refreshOnceOnNavigation: true
})
However in Vue.js 3, there is no Vue instance; and instead you create the app by running createApp like such:
const app = createApp(App);
const router = createVueRouter();
app.use(router);
// need to make app use Vue-Meta here
I cannot find any documentation for vue-3-meta. import VueMeta from 'vue-meta' no longer works.
How do I import the vue-3-meta plugin properly and use it with app like in prior versions?
Disclaimer: vue-meta v3 is still in alpha!
This was the minimal implementation I needed to get started:
Update vue-meta to v3 (in package.json)
- "vue-meta": "^2.4.0",
+ "vue-meta": "^3.0.0-alpha.7",
...or with yarn:
yarn add vue-meta#alpha
Add metaManager to Vue app
import { createMetaManager } from 'vue-meta'
const app = createApp(App)
.use(router)
.use(store)
.use(createMetaManager()) // add this line
await router.isReady()
app.mount('#app')
Add <metainfo> to App.vue <template> (this is also where I set a "title template")
<template>
<metainfo>
<template v-slot:title="{ content }">{{ content ? `${content} | SITE_NAME` : `SITE_NAME` }}</template>
</metainfo>
<header />
<router-view />
<footer />
</template>
Set default meta in App.vue <script>
Vue 3 vanilla:
import { useMeta } from 'vue-meta'
export default {
setup () {
useMeta({
title: '',
htmlAttrs: { lang: 'en', amp: true }
})
}
}
or with vue-class-component:
import { setup, Vue } from 'vue-class-component'
import { useMeta } from 'vue-meta'
export default class App extends Vue {
meta = setup(() => useMeta({
title: '',
htmlAttrs: { lang: 'en', amp: true }
})
}
Override meta in each component
Vue 3 vanilla:
import { useMeta } from 'vue-meta'
export default {
setup () {
useMeta({ title: 'Some Page' })
}
}
or with vue-class-component:
import { computed } from '#vue/runtime-core'
import { setup, Vue } from 'vue-class-component'
import { useMeta } from 'vue-meta'
export default class SomePage extends Vue {
meta = setup(() => useMeta(
computed(() => ({ title: this.something?.field ?? 'Default' })))
)
}
See also:
"Quick Usage" (vue-meta next branch)
Vue Router Example (vue-meta next branch)
In addition to the previous answers, I also needed to add a transpileDependency in my vue.config.js, as I was using vue-cli:
module.exports = {
transpileDependencies: ['vue-meta']
}
Else, I would get the error:
error in ./node_modules/vue-meta/dist/vue-meta.esm-browser.min.js
Module parse failed: Unexpected token (8:7170)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
Thanks to this thread for pointing me to this: https://stackoverflow.com/a/65844988/3433137
metaManager is a MetaManager instance created from createMetaManager() of vue-meta.
Based on the Vue 3 + Vue Router example for vue-meta, here's an example usage:
import { createApp } from 'vue'
import { createMetaManager, defaultConfig, resolveOption, useMeta } from 'vue-meta'
const decisionMaker5000000 = resolveOption((prevValue, context) => {
const { uid = 0 } = context.vm || {}
if (!prevValue || prevValue < uid) {
return uid
}
})
const metaManager = createMetaManager({
...defaultConfig,
esi: {
group: true,
namespaced: true,
attributes: ['src', 'test', 'text']
}
}, decisionMaker5000000)
useMeta(
{
og: {
something: 'test'
}
},
metaManager
)
createApp(App).use(metaManager).mount('#app')

Import all files in a directory at once [duplicate]

With ES6, I can import several exports from a file like this:
import {ThingA, ThingB, ThingC} from 'lib/things';
However, I like the organization of having one module per file. I end up with imports like this:
import ThingA from 'lib/things/ThingA';
import ThingB from 'lib/things/ThingB';
import ThingC from 'lib/things/ThingC';
I would love to be able to do this:
import {ThingA, ThingB, ThingC} from 'lib/things/*';
or something similar, with the understood convention that each file contains one default export, and each module is named the same as its file.
Is this possible?
I don't think this is possible, but afaik the resolution of module names is up to module loaders so there might a loader implementation that does support this.
Until then, you could use an intermediate "module file" at lib/things/index.js that just contains
export * from 'ThingA';
export * from 'ThingB';
export * from 'ThingC';
and it would allow you to do
import {ThingA, ThingB, ThingC} from 'lib/things';
Just a variation on the theme already provided in the answer, but how about this:
In a Thing,
export default function ThingA () {}
In things/index.js,
export {default as ThingA} from './ThingA'
export {default as ThingB} from './ThingB'
export {default as ThingC} from './ThingC'
Then to consume all the things elsewhere,
import * as things from './things'
things.ThingA()
Or to consume just some of things,
import {ThingA,ThingB} from './things'
The current answers suggest a workaround but it's bugged me why this doesn't exist, so I've created a babel plugin which does this.
Install it using:
npm i --save-dev babel-plugin-wildcard
then add it to your .babelrc with:
{
"plugins": ["wildcard"]
}
see the repo for detailed install info
This allows you to do this:
import * as Things from './lib/things';
// Do whatever you want with these :D
Things.ThingA;
Things.ThingB;
Things.ThingC;
again, the repo contains further information on what exactly it does, but doing it this way avoids creating index.js files and also happens at compile-time to avoid doing readdirs at runtime.
Also with a newer version you can do exactly like your example:
import { ThingsA, ThingsB, ThingsC } from './lib/things/*';
works the same as the above.
You now can use async import():
import fs = require('fs');
and then:
fs.readdir('./someDir', (err, files) => {
files.forEach(file => {
const module = import('./' + file).then(m =>
m.callSomeMethod();
);
// or const module = await import('file')
});
});
Great gugly muglys! This was harder than it needed to be.
Export one flat default
This is a great opportunity to use spread (... in { ...Matters, ...Contacts } below:
// imports/collections/Matters.js
export default { // default export
hello: 'World',
something: 'important',
};
// imports/collections/Contacts.js
export default { // default export
hello: 'Moon',
email: 'hello#example.com',
};
// imports/collections/index.js
import Matters from './Matters'; // import default export as var 'Matters'
import Contacts from './Contacts';
export default { // default export
...Matters, // spread Matters, overwriting previous properties
...Contacts, // spread Contacts, overwriting previosu properties
};
// imports/test.js
import collections from './collections'; // import default export as 'collections'
console.log(collections);
Then, to run babel compiled code from the command line (from project root /):
$ npm install --save-dev #babel/core #babel/cli #babel/preset-env #babel/node
(trimmed)
$ npx babel-node --presets #babel/preset-env imports/test.js
{ hello: 'Moon',
something: 'important',
email: 'hello#example.com' }
Export one tree-like default
If you'd prefer to not overwrite properties, change:
// imports/collections/index.js
import Matters from './Matters'; // import default as 'Matters'
import Contacts from './Contacts';
export default { // export default
Matters,
Contacts,
};
And the output will be:
$ npx babel-node --presets #babel/preset-env imports/test.js
{ Matters: { hello: 'World', something: 'important' },
Contacts: { hello: 'Moon', email: 'hello#example.com' } }
Export multiple named exports w/ no default
If you're dedicated to DRY, the syntax on the imports changes as well:
// imports/collections/index.js
// export default as named export 'Matters'
export { default as Matters } from './Matters';
export { default as Contacts } from './Contacts';
This creates 2 named exports w/ no default export. Then change:
// imports/test.js
import { Matters, Contacts } from './collections';
console.log(Matters, Contacts);
And the output:
$ npx babel-node --presets #babel/preset-env imports/test.js
{ hello: 'World', something: 'important' } { hello: 'Moon', email: 'hello#example.com' }
Import all named exports
// imports/collections/index.js
// export default as named export 'Matters'
export { default as Matters } from './Matters';
export { default as Contacts } from './Contacts';
// imports/test.js
// Import all named exports as 'collections'
import * as collections from './collections';
console.log(collections); // interesting output
console.log(collections.Matters, collections.Contacts);
Notice the destructuring import { Matters, Contacts } from './collections'; in the previous example.
$ npx babel-node --presets #babel/preset-env imports/test.js
{ Matters: [Getter], Contacts: [Getter] }
{ hello: 'World', something: 'important' } { hello: 'Moon', email: 'hello#example.com' }
In practice
Given these source files:
/myLib/thingA.js
/myLib/thingB.js
/myLib/thingC.js
Creating a /myLib/index.js to bundle up all the files defeats the purpose of import/export. It would be easier to make everything global in the first place, than to make everything global via import/export via index.js "wrapper files".
If you want a particular file, import thingA from './myLib/thingA'; in your own projects.
Creating a "wrapper file" with exports for the module only makes sense if you're packaging for npm or on a multi-year multi-team project.
Made it this far? See the docs for more details.
Also, yay for Stackoverflow finally supporting three `s as code fence markup.
Similar to the accepted answer but it allows you to scale without the need of adding a new module to the index file each time you create one:
./modules/moduleA.js
export const example = 'example';
export const anotherExample = 'anotherExample';
./modules/index.js
// require all modules on the path and with the pattern defined
const req = require.context('./', true, /.js$/);
const modules = req.keys().map(req);
// export all modules
module.exports = modules;
./example.js
import { example, anotherExample } from './modules'
If you are using webpack. This imports files automatically and exports as api namespace.
So no need to update on every file addition.
import camelCase from "lodash-es";
const requireModule = require.context("./", false, /\.js$/); //
const api = {};
requireModule.keys().forEach(fileName => {
if (fileName === "./index.js") return;
const moduleName = camelCase(fileName.replace(/(\.\/|\.js)/g, ""));
api[moduleName] = {
...requireModule(fileName).default
};
});
export default api;
For Typescript users;
import { camelCase } from "lodash-es"
const requireModule = require.context("./folderName", false, /\.ts$/)
interface LooseObject {
[key: string]: any
}
const api: LooseObject = {}
requireModule.keys().forEach(fileName => {
if (fileName === "./index.ts") return
const moduleName = camelCase(fileName.replace(/(\.\/|\.ts)/g, ""))
api[moduleName] = {
...requireModule(fileName).default,
}
})
export default api
I've used them a few times (in particular for building massive objects splitting the data over many files (e.g. AST nodes)), in order to build them I made a tiny script (which I've just added to npm so everyone else can use it).
Usage (currently you'll need to use babel to use the export file):
$ npm install -g folder-module
$ folder-module my-cool-module/
Generates a file containing:
export {default as foo} from "./module/foo.js"
export {default as default} from "./module/default.js"
export {default as bar} from "./module/bar.js"
...etc
Then you can just consume the file:
import * as myCoolModule from "my-cool-module.js"
myCoolModule.foo()
Just an other approach to #Bergi's answer
// lib/things/index.js
import ThingA from './ThingA';
import ThingB from './ThingB';
import ThingC from './ThingC';
export default {
ThingA,
ThingB,
ThingC
}
Uses
import {ThingA, ThingB, ThingC} from './lib/things';
Nodejs ? Do like this:
Create a folder with index.js, in index file, add this:
var GET = require('./GET');
var IS = require('./IS');
var PARSE = require('./PARSE');
module.exports = { ...GET, ...IS, ...PARSE};
And, in file GET.js, or IS.js export as normal:
module.exports = { /* something as you like */}
ANd now, you need only including index.js like:
const Helper = require('./YourFolder');
Helper will include all of function in YourFolder.
Good day!
This is not exactly what you asked for but, with this method I can Iterate throught componentsList in my other files and use function such as componentsList.map(...) which I find pretty usefull !
import StepOne from './StepOne';
import StepTwo from './StepTwo';
import StepThree from './StepThree';
import StepFour from './StepFour';
import StepFive from './StepFive';
import StepSix from './StepSix';
import StepSeven from './StepSeven';
import StepEight from './StepEight';
const componentsList= () => [
{ component: StepOne(), key: 'step1' },
{ component: StepTwo(), key: 'step2' },
{ component: StepThree(), key: 'step3' },
{ component: StepFour(), key: 'step4' },
{ component: StepFive(), key: 'step5' },
{ component: StepSix(), key: 'step6' },
{ component: StepSeven(), key: 'step7' },
{ component: StepEight(), key: 'step8' }
];
export default componentsList;
You can use require as well:
const moduleHolder = []
function loadModules(path) {
let stat = fs.lstatSync(path)
if (stat.isDirectory()) {
// we have a directory: do a tree walk
const files = fs.readdirSync(path)
let f,
l = files.length
for (var i = 0; i < l; i++) {
f = pathModule.join(path, files[i])
loadModules(f)
}
} else {
// we have a file: load it
var controller = require(path)
moduleHolder.push(controller)
}
}
Then use your moduleHolder with dynamically loaded controllers:
loadModules(DIR)
for (const controller of moduleHolder) {
controller(app, db)
}
I was able to take from user atilkan's approach and modify it a bit:
For Typescript users;
require.context('#/folder/with/modules', false, /\.ts$/).keys().forEach((fileName => {
import('#/folder/with/modules' + fileName).then((mod) => {
(window as any)[fileName] = mod[fileName];
const module = new (window as any)[fileName]();
// use module
});
}));
if you don't export default in A, B, C but just export {} then it's possible to do so
// things/A.js
export function A() {}
// things/B.js
export function B() {}
// things/C.js
export function C() {}
// foo.js
import * as Foo from ./thing
Foo.A()
Foo.B()
Foo.C()

How to construct js object in React

I have some problems with React and Advertisement.
Wanna use 'Coupang' Advertisement, but they support the script library only.
I can add it to 'index.html' in the public directory, but cannot customize the location.
here is the code,
<script src="https://ads-partners.coupang.com/g.js"></script>
<script>
new PartnersCoupang.G({"id":23232,"subId":null});
</script>
It's a dynamic advertisement.
How can I add it to the React functional component??
MB this will be helpful.
useScript will help you add script to your code dynamically.
and you custom hook (just create PartnersCoupang);
const usePartnersCoupang = () => {
const const [loaded, error] = useScript('https://ads-partners.coupang.com/g.js');
React.useEffect(() => {
if (loaded) {
new PartnersCoupang.G({"id":23232,"subId":null});
}
}, [loaded]);
React.useEffect(() => {
if (error) {
console.error('PartnersCoupang Failed.');
}
}, [error]);
}
Actually, you should eject from your create-react-app project by this command:
$ yarn eject
or:
$ npm run eject
Then you can see a folder that name is config, in this folder you can see all configuration of your project, especially your Webpack configs, then you should add your external library to the webpack as external key on the configuration of Webpack:
// webpack.config.js
...
module.exports = {
...
externals: {
PartnersCoupang: '[path-to]/g.js',
},
...
};
Then in your component import it easily:
import React, { Component } from 'react';
import PartnersCoupang from 'PartnersCoupang';
class YourComponent extends Component {
componentDidMount() {
new PartnersCoupang.G({"id":23232,"subId":null});
}
}

Error in Vue i18n with TypeScript: "Property '$t' does not exist on type 'VueConstructor'. " . How can I fix it?

In project some common function are in separate .ts files.
How can I use i18 in that cases:
// for i18n
import Vue from 'vue'
declare module 'vue/types/vue' {
interface VueConstructor {
$t: any
}
}
declare module 'vue/types/options' {
interface ComponentOptions<V extends Vue> {
t?: any
}
}
(()=>{
const test = Vue.$t('auth.title');
console.log( test )
})()
Return an error:
Property '$t' does not exist on type 'VueConstructor<Vue>"
How can I fix it?
we can achieve the same like below
Step 1: create a separate index.ts file inside a i18n folder (you can do it your own way - root level or any where in your app)
i18n/index.ts
import Vue from 'vue';
import VueI18n from 'vue-i18n';
// register i18n module
Vue.use(VueI18n);
const i18n = new VueI18n({
locale: 'nb-NO', //if you need get the browser language use following "window.navigator.language"
fallbackLocale: 'en',
messages: {en, no},
silentTranslationWarn: true
})
const translate = (key: string) => {
if (!key) {
return '';
}
return i18n.t(key);
};
export { i18n, translate}; //export above method
Step 2: make sure to use(import) above in main.ts
main.ts
import { i18n } from '#/i18n';
new Vue({ i18n, render: h => h(app) }).$mount('#app')
after above configuration we should be able to use translation in any place that we want in our application
Step 3: How to use it in .ts and .vue files
// first import it into the file
import { translate, i18n } from '#/i18n';
//this is how we can use translation inside a html if we need
<template>
<h1>{{'sample text' | translate}}</h1>
</template>
//this is how we can use translation inside a .ts or .vue files
<script lang='ts'>
//normal scenario
testFunc(){
let test = `${translate('sample text')}`;
console.log(test );
}
//in your case it should be like below
(()=>{
const test = `${translate('auth.title')}`;
console.log( test )
})()
</script>
I hope that this will help you to resolve your issue.

Categories