Vue3 how to use a dynamic path with defineAsyncComponnet - vuejs3

I am trying to add retry functionality to defineAsyncComponent so I created a helper function
const MAX_TRY = 3;
const WAIT_TIME = 1000;
async function loadAsyncComponent(componentPath: string, tryCount = 1) {
if (tryCount > MAX_TRY) return Promise.reject();
return new Promise((resolve, reject) => {
const path = componentPath.replace('.vue', '').replace('#/modules/', '');
// https://github.com/rollup/plugins/tree/master/packages/dynamic-import-vars#limitations
import(`../../../modules/${path}.vue`).then(resolve).catch((error) => {
console.error('loading async component failed : ', error);
captureException(error);
wait(WAIT_TIME).then(() => {
loadAsyncComponent(componentPath, ++tryCount)
.then(resolve)
.catch(reject);
});
});
});
}
export default function customAsyncComponent<T>(componentPath: string): T {
return defineAsyncComponent({
// the loader function
loader: () => loadAsyncComponent(componentPath) as any,
// A component to use while the async component is loading
loadingComponent: Loading,
// Delay before showing the loading component. Default: 200ms.
delay: 200,
// A component to use if the load fails
errorComponent: AsyncLoadFailed,
// The error component will be displayed if a timeout is
// provided and exceeded. Default: Infinity.
timeout: 10000,
});
}
This works in local DEV environment but when I build and deployed to prod it broke Error: Unknown variable dynamic import: ../../../modules/room/components/room/Room.vue
It seems that these dynamic imports are not included in the final build.
Found this answer https://stackoverflow.com/a/72479802/1055015
Tryied these
rollupOptions: {
external: [path.resolve(__dirname, 'src/modules/room/components/room/Room.vue')],
},
rollupOptions: {
external: ['#/modules/room/components/room/Room.vue')],
},
After vite build The dynamically imported files are not generated in the dist folder
Any ideas ?
UPDATE: I found a workaround using glob-import testing it now

Solution 2:
The first solution left me with hundred's of small files , every single vue,ts,css file loading separately (~175),
I needed to preload them for better UX but and it started making 429 errors (too many request) on my server
So I made some changes on my custom loader and manually listed the files that needed to be preloaded
const PRELOAD_LIST = [
() => import('#/modules/X.vue'),
];
async function loadAsyncComponent(componentLoader: any, tryCount = 1) {
return new Promise((resolve, reject) => {
componentLoader() // <--------------- this part mostly
.then(resolve)
.catch((error) => {
console.error('loading async component failed : ', error);
captureException(error);
if (tryCount >= MAX_TRY) reject(error);
else {
wait(WAIT_TIME).then(() => {
loadAsyncComponent(componentLoader, ++tryCount)
.then(resolve)
.catch(reject);
});
}
});
});
}
const X = customAsyncComponent(() => import('#/modules/X.vue'));
Solution 1:
For now I used this approach :
const MODULES = import.meta.glob('../../../modules/**/*.vue');
this generates a key/value object list, the keys are the file path's and the values are import statements with static path
// code produced by vite
const MODULES = {
'../../../modules/dir/foo.vue': () => import('./modules/dir/foo.vue'),
'../../../modules/dir/bar.vue': () => import('./modules/dir/bar.vue')
}
Then you can just do MODULES[myPath]() to load the file/module.
This works but it also generates extra code for the modules at I don't really need to, so I am still looking for a better solution.

Related

combineLatest is not getting called

I have the following code
I am new to rjxs programming so wondering what can be the root cause. I am trying to do a certain task and first combine all latest result, then subscribe to assign results. But combineLatest is not working seems like. What can be the cause ?
const downloadUrls$: any = Array.from(event.target.files).map((file: any) => {
const fileName = ...
const path = ...
const fileRef = ....
const task: any = ...
return task.snapshotChanges().pipe(
filter(snap => snap.state === firebase.storage.TaskState.SUCCESS),
switchMap(() => {
return fileRef.getDownloadURL().pipe(
map((url: string) => ({
name: fileName,
filepath: url,
relativePath: path,
}))
);
})
);
});
combineLatest(...downloadUrls$).subscribe((urls: any) => {
console.log(hi); // not printing
});
The reason behind that combinelatest doesnt fire is te reference of the subject.
You should change to this version. So removing the ... before the downloadUrls$.
combineLatest(downloadUrls$).subscribe((urls: any) => {
console.log(hi); // not printing
});
Combinelatest is for merging two or more observables. Is there any specific reason using combinelatest with one observable? Otherwise you can just change to simpler version like
downloadUrls$.subscribe((urls: any) => {
console.log(hi); // not printing
});
The latest versions of cobineLateste() receive an array as a parameter:
combineLatest(downloadUrls$).subscribe((urls: any) => {
console.log(hi); // not printing
});

GatsbyJS with Firebase - WebpackError: ReferenceError: IDBIndex is not defined

I'm received error with gatsby develop. It's very similar to this one: https://github.com/firebase/firebase-js-sdk/issues/2222, but I'm received error with gatsby develop, not with gatsby build. I did a lot of research but I can't find working solution.
At first I had a problem with gatsby build, like in this post: https://github.com/firebase/firebase-js-sdk/issues/2222, but I resolved it with custom onCreateWebpackConfig(you can find it below).
Stack:
- Gatsby
- Firebase(error probably with that)
- Redux
I'm also delete .cache and node_modules and install everything again, but it didn't work.
Error:
There was an error compiling the html.js component for the development server.
See our docs page on debugging HTML builds for help https://gatsby.dev/debug-html ReferenceError: IDBIndex is not defined
]);
86 |
> 87 | proxyRequestMethods(Index, '_index', IDBIndex, [
| ^
88 | 'get',
89 | 'getKey',
90 | 'getAll',
WebpackError: ReferenceError: IDBIndex is not defined
- idb.mjs:87 Module../node_modules/idb/lib/idb.mjs
node_modules/idb/lib/idb.mjs:87:1
- index.esm.js:1 Module../node_modules/#firebase/installations/dist/index.esm.js
node_modules/#firebase/installations/dist/index.esm.js:1:1
- index.esm.js:1 Module../node_modules/#firebase/analytics/dist/index.esm.js
node_modules/#firebase/analytics/dist/index.esm.js:1:1
- index.esm.js:1 Module../node_modules/firebase/analytics/dist/index.esm.js
node_modules/firebase/analytics/dist/index.esm.js:1:1
- index.ts:1 Module../src/firebase/index.ts
src/firebase/index.ts:1:1
- index.esm.js:32 emit
node_modules/#firebase/analytics/dist/index.esm.js:32:1
My gatsby-node file:
exports.onCreateWebpackConfig = ({ stage, actions, getConfig }) => {
if (stage === 'build-html') {
actions.setWebpackConfig({
externals: getConfig().externals.concat(function(context, request, callback) {
const regex = /^#?firebase(\/(.+))?/;
if (regex.test(request)) {
return callback(null, `umd ${request}`);
}
callback();
}),
});
}
};
My firebase dependencies:
"#firebase/firestore-types": "^1.10.1",
"firebase": "^7.13.1",
"firebase-admin": "^8.10.0",
"firebase-functions": "^3.5.0",
"firebase-tools": "^7.16.1",
Firebase index file:
import firebase from 'firebase/app';
import 'firebase/firestore';
import 'firebase/auth';
import 'firebase/storage';
import 'firebase/analytics';
const firebaseConfig = {...};
firebase.initializeApp(firebaseConfig);
export const firestore = firebase.firestore();
export const auth = firebase.auth();
export const storage = firebase.storage();
Project repo: https://github.com/olafsulich/Projecty
Post on Github issues: https://github.com/firebase/firebase-js-sdk/issues/2946
Thanks in advance.
The following snippet will only work on build environment because of your condition (stage === 'build-html'):
exports.onCreateWebpackConfig = ({ stage, actions, getConfig }) => {
if (stage === 'build-html') {
actions.setWebpackConfig({
externals: getConfig().externals.concat(function(context, request, callback) {
const regex = /^#?firebase(\/(.+))?/;
if (regex.test(request)) {
return callback(null, `umd ${request}`);
}
callback();
}),
});
}
};
Remove it and use it like this:
exports.onCreateWebpackConfig = ({ stage, actions, getConfig }) => {
actions.setWebpackConfig({
externals: getConfig().externals.concat(function(context, request, callback) {
const regex = /^#?firebase(\/(.+))?/;
if (regex.test(request)) {
return callback(null, `umd ${request}`);
}
callback();
}),
});
};
Thank's a lot! It's working only on gatbsy develop, but now when I
want to build project, I get an error - TypeError: Cannot read
property 'concat' of undefined. You know how to solve it?
Regarding the new issue, you can follow a workaround in this topic, This is a common error in third-party modules in Gatsby when they try to reach a DOM element (usually window) that is not already defined when the app builds. So, you need to wait until window is defined. You can achieve this in two ways:
Instancing your firebase with a condition like this:
import firebase from '#firebase/app';
import '#firebase/auth';
import '#firebase/firestore';
import '#firebase/functions';
const config = {
... firebase config here
};
let instance;
export default function getFirebase() {
if (typeof window !== 'undefined') {
if (instance) return instance;
instance = firebase.initializeApp(config);
return instance;
}
return null;
}
Note the if (typeof window !== 'undefined') statement
By ignoring firebase module in you webpack configuration like shows their docs. In your gatsby-node.js:
exports.onCreateWebpackConfig = ({ stage, loaders, actions }) => {
if (stage === "build-html") {
actions.setWebpackConfig({
module: {
rules: [
{
test: /bad-module/,
use: loaders.null(),
},
],
},
})
}
}
Replace bad module for firebase (or the package/folder name in node_modules). Leave the slashes since test is a regular expression rule
This snippet replaces your previous one that seems to throw an error in concat() function.
For those who wants to try the concat() resolution, this will be helpful too:
exports.onCreateWebpackConfig = ({ stage, actions, getConfig }) => {
if (stage === 'build-html') {
actions.setWebpackConfig({
externals: getConfig().externals.concat((context, request, callback) => {
const regex = /^#?firebase(\/(.+))?/
// exclude firebase products from being bundled, so they will be loaded using require() at runtime.
if (regex.test(request)) {
return callback(null, `commonjs ${request}`) // <- use commonjs!
}
callback()
}),
})
}
}
Solved this problem!!
I'm using "gatsby": "^3.10.2", "firebase": "9.0.0-beta.6".
firebase needs to be set externals as commonjs.
gatsby-node.js:
exports.onCreateWebpackConfig = ({ stage, actions, getConfig }) => {
if (stage === 'build-html') {
actions.setWebpackConfig({
externals: getConfig().externals.concat((context, request, callback) => {
const regex = /^#?firebase(\/(.+))?/
// exclude firebase products from being bundled, so they will be loaded using require() at runtime.
if (regex.test(request)) {
return callback(null, `commonjs ${request}`) // <- use commonjs!
}
callback()
}),
})
}
}
Please try this setting.

Change layout according to wordpress page template NuxtJs

I want to get change layout according to wordpress page template (i get data with rest api)
API Data
{
"id": 47,
"template": "test.php", // need vue template according this
}
export default {
validate ({ params }) {
return !isNaN(+params.id)
},
async asyncData ({ params, error }) {
return fetch('http://wordpress.local/wp-json/wp/v2/'+params.postType+'/'+params.id)
.then(response => response.json())
.then(res => {
return { users: res }
})
},
layout () {
return 'blog' // Change here according to wordpress page template
},
}
I found a way to pass something from middleware to store which you can use inside the layout function. Here is the basic example I put together.
middleware/get-layout.js I simulate an async call here, could also be result of axios.post() for example
export default async (ctx) => {
return new Promise((resolve, reject) => {
// you can also access ctx.params here
ctx.store.commit('setLayout', 'new');
resolve();
});
}
store/index.js nothing crazy here
export const state = () => ({
layout: ''
})
export const mutations = {
setLayout(state, layout) {
state.layout = layout;
}
}
Middleware can either be registered globally for every route in nuxt.config.js or only for pages where you need this logic.
Finally using it in page component layout property:
layout(ctx) {
return ctx.store.state.layout;
}
I tested it with new.vue inside layout folder.

await response of image upload before continue function

So I am working on a upload function for multiple images in an array. After a lot of struggling I have finally got my upload function to work and the images are showing up in the Firebase Database. However I have yet to find out a working way to make sure my upload function completes before continuing.
Below is the part were I am calling the upload function and try to store the response in uploadurl, the uploadurl variable is later used in the dispatch function to store the url with other data.
try {
uploadurl = await uploadImages()
address = await getAddress(selectedLocation)
console.log(uploadurl)
if (!uploadurl.lenght) {
Alert.alert('Upload error', 'Something went wrong uploading the photo, plase try again', [
{ text: 'Okay' }
]);
setIsLoading(true);
return;
}
dispatch(
So the image upload function is below. This works to the point that the images are uploaded, however the .then call to get the DownloadURL is not started correctly and the .then images also is not working.
uploadImages = () => {
const provider = firebase.database().ref(`providers/${uid}`);
let imagesArray = [];
try {
Promise.all(photos)
.then(photoarray => {
console.log('all responses are resolved succesfully')
for (let photo of photoarray) {
let file = photo.data;
const path = "Img_" + uuid.v4();
const ref = firebase
.storage()
.ref(`/${uid}/${path}`);
var metadata = {
contentType: 'image/jpeg',
};
ref.putString(file, 'base64', metadata).then(() => {
ref
.getDownloadURL()
.then(images => {
imagesArray.push({
uri: images
});
console.log("Out-imgArray", imagesArray);
})
})
};
return imagesArray
})
} catch (e) {
console.error(e);
}
};
So I want to return the imagesArray, AFTER, all the photos are uploaded. So the imagesArray is then set as uploadURL in the first function? After all images URL are set in imagesArray and passed to uploadURL, only then my dispatch function to upload the rest of the data should continue. How can I make sure this is happening as expected?
I have changed this so many times now because I keep getting send to different ways of doing this that I am completely at a loss how to continue now :(
Most of your uploadImages() code was correct, however in many places you didn't return the promise from each asynchronous action.
Quick sidestep: Handling many promises
When working with lots of asynchronous tasks based on an array, it is advised to map() the array to an array of Promises rather than use a for loop. This allows you to build an array of promises that can be fed to Promise.all() without the need to initialise and push to another array.
let arrayOfPromises = someArray.map((entry) => {
// do something with 'entry'
return somePromiseRelatedToEntry();
});
Promise.all(arrayOfPromises)
.then((resultsOfPromises) => {
console.log('All promises resolved successfully');
})
.catch((err) => {
// an error in one of the promises occurred
console.error(err);
})
The above snippet will fail if any of the contained promises fail. To silently ignore individual errors or defer them to handle later, you just add a catch() inside the mapped array step.
let arrayOfPromises = someArray.map((entry) => {
// do something with 'entry'
return somePromiseRelatedToEntry()
.catch(err => ({hasError: true, error: err})); // silently ignore errors for processing later
});
Updated uploadImages() code
Updating your code with these changes, gives the following result:
uploadImages = () => {
const provider = firebase.database().ref(`providers/${uid}`);
// CHANGED: removed 'let imagesArray = [];', no longer needed
return Promise.all(photos) // CHANGED: return the promise chain
.then(photoarray => {
console.log('all responses are resolved successfully');
// take each photo, upload it and then return it's download URL
return Promise.all(photoarray.map((photo) => { // CHANGED: used Promise.all(someArray.map(...)) idiom
let file = photo.data;
const path = "Img_" + uuid.v4();
const storageRef = firebase // CHANGED: renamed 'ref' to 'storageRef'
.storage()
.ref(`/${uid}/${path}`);
let metadata = {
contentType: 'image/jpeg',
};
// upload current photo and get it's download URL
return storageRef.putString(file, 'base64', metadata) // CHANGED: return the promise chain
.then(() => {
console.log(`${path} was uploaded successfully.`);
return storageRef.getDownloadURL() // CHANGED: return the promise chain
.then(fileUrl => ({uri: fileUrl}));
});
}));
})
.then((imagesArray) => { // These lines can
console.log("Out-imgArray: ", imagesArray) // safely be removed.
return imagesArray; // They are just
}) // for logging.
.catch((err) => {
console.error(err);
});
};

UI not update (have to click somewhere) & router not work in Angular2-Meteor

I cannot update the UI immediately after subscribing the data from database, I have to click somewhere
Also if I use the router to go to another page, it does not work
#Component({
selector: 'foo-component',
template: `
{{foo}}
`
})
export class FooComponent extends MeteorComponent {
foo: string ;
constructor() {
super();
this.subscribe('messages', () => {
// I cannot change foo value immediately, I have to click somewhere
this.foo = 'foo';
// Also if I use the router to go to another page, it does not work
// this.router.navigate(['Home']);
});
}
}
How to solve this?
Note the new angular2-meteor version autoBind is set to true by default. So you probably won't meet this issue again.
But you still need use NgZone in Accounts.changePassword or other similar Accounts.foo() functions.
This problem is because that part of code run out of Angular 2 zone, you need run it inside of zone to update UI immediately or use router to go to another page.
Where do these problems usually happen?
Most time you don't do this. So when do you need this? Usually in callback of Meteor functions:
this.autorun(() => {
// here you need
});
this.subscribe('messages', () => {
// here you need
});
this.call('newMessage', (error, result) => {
// here you need
});
Accounts.changePassword(currentPassword, newPassword, error => {
// here you need
});
How to solve?
Take
this.call('newMessage', (error, result) => {
this.router.navigate(['Home']);
});
for example, you need change to:
import { NgZone } from '#angular/core';
constructor(private zone: NgZone) {}
this.call('newMessage', (error, result) => {
this.zone.run(() => {
this.router.navigate(['Home']);
});
});
Is there a clean way?
Luckily, Angular2-Meteor helps you do this dirty work.
Check Angular2-Meteor API document, there is an autoBind parameter for this.subscribe and this.autorun.
So now you don't need use this.zone.run, instead you can just add a true:
this.autorun(() => {
// your code
}, true);
this.subscribe('messages', () => {
// your code
}, true);

Resources