I'm trying to load Vue 3 components in a asynchronous way. I've found that there is a function called
defineAsyncComponent
which is supposed to be used as follows:
const GameUI = defineAsyncComponent(()=>import(filePath));
app.component("GameUI", GameUI);
filePath in this context is exactly: './components/GameUI/GameUI.element.vue'
Running the app like this leads to the following error:
Uncaught (in promise) Error: Cannot find module './components/GameUI/GameUI.element.vue'
at eval (eval at ./src lazy recursive)
But... if I change the filePath code to import the path as a string:
const GameUI = defineAsyncComponent(()=>import('./components/GameUI/GameUI.element.vue'));
The app works, it does find the component.
I don't want to use constant strings, because I have a lot of components and I want to load them asynchronously.
One of my main goals to achieve this, is to load the webapp by parts, component by component whenever they are needed, instead of loading them all on start.
I've also find that if I append a comment as follows:
const GameUI = defineAsyncComponent(()=>import(/* webpackChunkName: "GameUI" */ './components/GameUI/GameUI.element.vue'));
The JavaScript part for the GameUI component, should have it's own chunk.js file, but I always keep getting everything in a couple .js chunk files, which contradicts the async loading I want to achieve.
I'm using vue-cli-service, and my vue.config.js looks like this:
module.exports = {
productionSourceMap: false,
css: {
loaderOptions: {
sass: {
additionalData: process.env.NODE_ENV === 'production'
? '$baseURL:"/dist/";'
: '$baseURL:"/";'
}
}
},
publicPath: process.env.NODE_ENV === 'production'
? '/dist/'
: '/',
devServer: {
https: true,
port:"",
host:'website.com',
disableHostCheck: true,
cert: (fs.readFileSync('cert.pem')+""),
key: (fs.readFileSync('privkey.pem')+""),
ca: (fs.readFileSync('ca.pem')+""),
}
};
I've already tried multiple stuff I've found online, but they are not that much explanatory. I'm literally doing the same as some online articles I've found and cannot find the problem on my side.
The two main problems are:
Cannot load .vue files from variables, only full strings.
Cannot split the code into different .js files for every async loaded component.
The answer to the first question:
Because there have some limitations of async import
.
What you are doing cannot work because you are using a variable as value to defineAsyncComponent.
According to the limitations of async import, you cannot import your component usinig a variable. Instead what you can do is:
// If the component name to call is GameUI.element
const component = 'GameUI.element' // can be comed from anyting
const GameUI = defineAsyncComponent(()=>import(`./components/GameUI/${component}.vue`));
app.component("GameUI", GameUI);
Related
I'm quite new to Nuxtjs so I made a test project which purpose is merely the (of course) testing of Nuxtjs functionalities.
Currently I'm trying to create a simple custom module: afaik a module is basically a wrapper around a vou/js library/plugin, something like a high-level integration used to expose configurations on how the underlying library/plugin is imported and used in the Nuxt application.
So I'm trying with a simple module that declare some plain js classes that I'll use in my application, e.g. Order and Product, and that's what I came out with:
Directory structure
pages
the-page.vue
modules
classes
index.js
order.js
/modules/classes/index.js
const path = require('path')
export default function (moduleOptions) {
const { nuxt } = this
// add the debug plugin
this.addPlugin({
src: path.resolve(__dirname, 'order.js'),
})
}
/modules/classes/order.js
class Order {
constructor(id) {
this.id = id;
console.log('created order #' + this.id);
}
}
export {Order};
/nuxt.config.js
export default {
// ...
buildModules: [
// ...
'~/modules/classes'
],
// ...
}
/pages/the-page.vue
<script>
export default {
name: 'ThePage',
data () {
return {
}
},
methods: {
createOrder () {
const order = new Order(123)
}
}
}
</script>
The error
My defined class are still not imported in my pages:
/app/pages/the-page.vue
18:13 error 'order' is assigned a value but never used no-unused-vars
18:25 error 'Order' is not defined no-undef
Considerations
Probably I'm missing something about modules usage and/or implementation, but every tutorial I found starts with too complex scenarios, and since I'm at the beginning with Nuxtjs I need something easier to implement.
Ok, I found out that I was mistaken how NuxtJs modules are intended to work and was traying to do somenthing they are not intended for.
Nuxt modules cannot import js classes in every component of the application as I wanted to do, they just "add a property" to the main application instance that is made accessible through this.$<something>, like e.g. you can already do in simple Vue with the Vue Router or the Vuex store plugins that give access to the this.$router and this.$store properties.
NuxtJs modules just wrap simple plugins and expose configuration options to made.
Is there any way of building my svelte or react application in a way, that the three.js module (which I usually import using npm) will be declared as a script tag which will call the module from a CDN? I would like to keep the advantages of a framework but also be able to reduce my final bundle size, since most of my bundle contains three code.
Thank you for your wisdom
There are two ways to go about your goal of reducing bundle size:
Importing from a CDN (your suggestion)
Code-splitting
Importing from a CDN
To keep semantics of ESModules, you may simply replace your current three.js imports with a URL from an npm CDN, like unpkg:
Pros
Cons
No extra configuration needed
Slower to load, as browser needs to spin up new connections to access third-party CDN
Asynchronously
<script>
// App.svelte
import('https://unpkg.com/three#0.133.1/build/three.min.js').then(({ default: THREE }) => {
// your code here
});
</script>
Synchronously
Note: Importing like this blocks the rest of your script from loading while three.js is downloading, which defeats the purpose of the whole shebang. It's just here for completeness
<script>
// App.svelte
import { default as THREE } from 'https://unpkg.com/three#0.133.1/build/three.min.js';
// your code here
</script>
Code-splitting
This method takes advantage of the fact that you're already using a bundler (probably rollup, vite, or webpack). This answer will focus on rollup as it's the default used in svelte's examples.
Pros
Cons
Faster to load, as browser can use existing connections to access first-party resources
More complicated to get set up
Asynchronously
In your rollup.config.js file, ensure output.format is set to 'esm' & output.dir is set instead of output.file
// rollup.config.js
import svelte from 'rollup-plugin-svelte';
import resolve from '#rollup/plugin-node-resolve';
import commonjs from '#rollup/plugin-commonjs';
import postcss from 'rollup-plugin-postcss';
const production = !process.env.ROLLUP_WATCH;
export default {
input: 'src/index.js',
output: {
sourcemap: !production,
format: 'esm',
name: 'app',
dir: 'public',
},
plugins: {
// your plugins
svelte({
compilerOptions: {
dev: !production,
},
}),
postcss({
extract: 'bundle.css',
}),
resolve({
browser: true,
dedupe: ['svelte'],
}),
commonjs(),
}
}
<script>
// App.svelte
import('three').then(({ default: THREE }) => {
// your code here
});
</script>
Note: There is no synchronous way due to how code-splitting is evaluated at compile time. Plus it doesn't make much sense to do it like that anyways.
Yes, you can do the following:
In your "index.html" file, you can import the js file from a CDN as follow:
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
Then, in the file where you want to use it, which could be a React component for instance, you can do the following:
const THREE = window.THREE;
Which would replace your import statement, which would have been import * as THREE from "three"; or import THREE from "three";
We have a vanilla Vue/Vite setup and I'm receiving TypeError: Failed to fetch dynamically imported module on sentry logs.
It seems like the errors are correlated in time with new deployment to prod, although I don't have enough data to confirm. It doesn't happen on local and appears only on deployed code.
I've seen some similar questions for react's setups, but none with a satisfactory response.
I've also found a similar question regarding dynamically imported svgs, but our errors happen for full components.
The only place where we use dynamic imported components is on routing:
export const router = createRouter({
history: routerHistory,
strict: true,
routes: [
{
path: '/',
name: routes.homepage.name,
component: () => import('#/views/Home.vue'),
children: [
{
path: '/overview',
name: routes.overview.name,
component: () => import('#/views/Overview.vue'),
},
// other similar routes
],
},
],
});
Our deps versions:
"vue": "^3.0.9",
"vue-router": "^4.0.5",
"vite": "^2.0.5",
Any additional information on this issue and how to debug it would be much appreciated!
When you dynamically import a route/component, during build it creates a separate chunk. By default, chunk filenames are hashed according to their content – Overview.abc123.js. If you don't change the component code, the hash remains the same. If the component code changes, the hash changes too - Overview.32ab1c.js. This is great for caching.
Now this is what happens when you get this error:
You deploy the application
Your Home chunk has a link to /overview route, which would load Overview.abc123.js
Client visits your site
You make changes in your code, not necessarily to the Overview component itself, but maybe to some children components that Overview imports.
You deploy changes, and Overview is built with a different hash now - Overview.32ab1c.js
Client clicks on /overview link - gets the Failed to fetch dynamically imported module error, because Overview.abc123.js no longer exists
That is why the errors correlate with deployments. One way to fix it is to not use lazy loaded routes, but that's not a great solution when you have many heavy routes - it will make your main bundle large
In my case the error was caused by not adding .vue extension to module name.
import MyComponent from 'components/MyComponent'
It worked in webpack setup, but with Vite file extension is required:
import MyComponent from 'components/MyComponent.vue'
I had the exact same issue. In my case some routes worked and some didn't. The solution was relatively easy. I just restarted the dev server.
The accepted answer correctly explains when this error is triggered but does not really provide a good solution.
The way I fixed this is by using an error handler on the router. This error handler makes sure that when this error occurs (so thus when a new version of the app is deployed), the next route change triggers a hard reload of the page instead of dynamically loading the modules. The code looks like this:
router.onError((error, to) => {
if (error.message.includes('Failed to fetch dynamically imported module')) {
window.location = to.fullPath
}
})
Where router is your vue-router instance.
My situation was similar.
I found that my Quasar setup works fine on the initial page but not page that are loaded dynamically through an import('../pages/page.vue');.
Short response:
I replaced import('../pages/TestPage.vue') in the middle of the route file by import TestPage from '../pages/TestPage.vue' at the top.
More detailed response:
In my situation I don't expect to have much pages, a single bundle with no dynamic loading is fine with me.
The solution is to import statically every page I need.
In my routes.ts I import all the pages I need.
import IndexPage from '../pages/IndexPage.vue';
import TestPage from '../pages/TestPage.vue';
Then I serve them statically in my routes :
const routes: RouteRecordRaw[] = [
{
path: '/',
component: () => import('layouts/MainLayout.vue'),
children: [
{ path: 'test', component: () => TestPage },
{ path: '', component: () => IndexPage }
],
},
// Always leave this as last one,
// but you can also remove it
{
path: '/:catchAll(.*)*',
component: () => import('pages/ErrorNotFound.vue'),
},
];
I recently expriencied this. The error was caused by an empty href inside an a tag: <a href="" #click="goToRoute">. You can either remove the href or change the a tag to something else, ie. button. Let me know if this helps.
I had the same problem. I found that I had not started my project.
I currently have a large private NPM library which is being consumed by several other teams' apps across the business. At the moment the library is being published as one large single file (like the main lodash file) but this is causing application bundle size to be bloated as some of the applications don't need a large chunk of what is in the library.
So at the moment the apps are importing something like this
import { SomeReactComponent, someHelperFunction } from 'my-private-library';
What I want to achieve is the library published with individual modules similar to how Lodash, so the above would become:
import SomeReactComponent from 'my-private-library/lib/SomeReactComponent';
import someHelperFunction from 'my-private-library/lib/someHelperFunction';
I can get Webpack to output output the library in this format using multiple entry points, but what I can't get to work is getting Webpack to split out shared dependencies of each of those modules. So say the files look something like this:
src/SomeReactComponent.jsx
import React from 'react'
import SOME_CONST_STRING from '../constants';
const SomeReactComponent = () => {
return (
<div>You are using {SOME_CONST_STRING}</div>
);
}
export default SomeReactComponent;
src/someHelperFunction
import SOME_CONST_STRING from '../constants';
export default function someHelperFunction() {
return `This is just an example of ${SOME_CONST_STRING}`;
}
My Webpack is outputting the individual files, but it's not splitting out common code in a way that an app can consume the library. So notice above the SOME_CONST_STRING which is imported in each of the modules, Webpack is putting this code in both of the exported files.
My Webpack config looks a bit like this (removed other setting for brevity)
module.exports = {
entry: {
SomeReactComponent: './src/SomeReactComponent',
someHelperFunction: './src/someHelperFunction',
},
output: {
path: './lib',
library: 'MyPrivateLibrary'
libraryTarget: 'umd',
filename: '[name].js'
}
// removed other setting for brevity
}
I have tried using the splitChunks optimization setting like this
module.exports = {
entry: {
SomeReactComponent: './src/SomeReactComponent',
someHelperFunction: './src/someHelperFunction',
},
output: {
path: './lib',
library: 'MyPrivateLibrary'
libraryTarget: 'umd',
filename: '[name].js'
},
optimization: {
splitChunks: {
chunks: 'all',
},
},
// removed other setting for brevity
}
which does chunk the code, but when I try to use the library in an app after doing this I get errors along the lines of (ERROR in TypeError: __webpack_require__(...) is not a function).
My question is can anyone see what I'm doing wrong? Is what I'm trying to achieve even possible with Webpack? Are there any example out there (as I can't find any) on how to do this?
Apologies for the example code, as my library is private I'm not able to use real-code examples.
Did you get success to achieve what you were trying to achieve in above scenario. I am working on the same use case and was facing similiar issue. After diagnosing it i found it that when we define library then in parsed module webpack add this in this object as window.myWebpackJsonpMyPrivateLibrary in minified main chunk which is undefined. if you remove the library and libraryTarget from webpack then you will not face this issue.
In my case i faced another issue that required chunk(s) are not being loaded when this is used as install dependency in another project.
everyone.
I have a trivial doubt on making vue components.
I don't want to use browserify or webpack , cause I am working in django and it has most of it's templates in static files , although I read this , which does describe how to take in account both ( but that's for some other day ).
Problem :
I am making a single file component which I have to import and use, using my router but I can't, as the import just doesn't happen.
My Hello.vue
<template>
Some HTML code here.
</template>
<script>
module.exports = {
data() {
return {
coin : []
}
},
beforeRouteEnter (to, from, next) {
axios.get('my-django-rest-api-url')
.then(response => {
next(vm => {
vm.data = response.data
})
})
}
}
</script>
I have it in the index.html file itself , no other .js file,
<script>
import Hello from '#/components/Hello.vue'
Vue.use(VueRouter);
const dashboard = {template:'<p>This is the base template</p>'};
const profile = {
template: '#profile_template',
data () {
return {
profile_details: []
}
},
beforeRouteEnter (to, from, next) {
axios.get('my-api-url')
.then(response => {
next(vm => {
vm.profile_details = response.data
})
})
}
}
const router = new VueRouter({
routes: [
{ path: '/', component: dashboard },
{ path: '/profile', component: profile },
{ path: '/hello', component: Hello }
]
});
new Vue({
router : router,
}).$mount('#app');
</script>
What all I've tried :
1.<script src="../components/Hello.js" type="module"></script> and removing the import statement as suggested here
Replacing my Hello.js's code with this : export const Hello = { ...
Making a Hello.js file and importing it like this import Hello from '../components/Hello.js';
Error :
**Mozilla ( Quantum 57.0.4 64 bit ) ** : SyntaxError: import declarations may only appear at top level of a module
**Chrome ( 63.0.3239.108 (Official Build) (64-bit) ) ** :Uncaught SyntaxError: Unexpected identifier
P.S. : I have tried these in various combinations
Not a Vue.js guru, but here are a few perspectives that might help you.
Module loading is still not supported on modern browsers by default, and you'd need to set special flags in order to enable it (which the users of your app probably won't do).
If you insist on using import and export, you'd need Webpack. And most certainly Babel (or any other ES6 transpiler, e.g. Buble) as well.
If you prefer module.exports, then you'd need Browserify. It enables support for CommonJS in browser environments.
If neither is doable, then your best bet is defining Vue components in global scope. You can split them across separate files, and import each with a <script> individually. Definitely not the cleanest approach.
Single file components typically go inside of .vue files, but either way they require vue-loader which can be added and configured (again) with a bundler.
Last option is to just use an existing setup in place, if there is any (is there?). If you already have RequireJS, UMD, or something similar in place, adjust your components to fit that. Otherwise, use <script>s.
You are trying to do something which is not possible. Vue Single file components are not supported as raw component file by web browsers. The single file component is supposed to be compiled.
Please see this for more:
https://v2.vuejs.org/v2/guide/single-file-components.html
In Webpack, each file can be transformed by a “loader” before being included in the bundle, and Vue offers the vue-loader plugin to translate single-file (.vue) components.
A vue single file component is first "translated" (compiled) to pure javascript code which is use-able by browsers.