I am currently trying to implement lazy loading of routes in VueJS3 and Laravel. I'm using vue-router to implement my routes.
Unfortunately, whenever I click on a link, nothing happens and after 30 seconds, I see a timeout error pop up in my browser console. If I replace lazy loading of the routes with a direct import, everything runs fine.
I would appreciate any hints as to what might be going on here. I've had the problem for a couple of weeks now. All other posts and suggestions to clear browser cache or set the public path in webpack.config.js have not worked for me. I have also replaced laravel-mix with webpack and the problem remains.
Pre-conditions:
Webpack 5.69.1
Webpack-cli ^4.9.2
Vue ^3.2.31
Vue-router ^4.0.12
Laravel 7.2
Steps to reproduce
Run npm run production
Run php artisan serve
Open browser
Enter desired URL in browser: http://127.0.0.1:8000/login
Click on "Register" link after login page opens.
Expected behavior
Npm runs successfully manifest.json is updated. Webpack generates all chunks in dists folder.
Laravel server starts
Website is reacheable over http://127.0.0.1:8000
Login page opens component configured in the route. Login.js is downloaded by the browser.
Browser downloads register.js dynamically. Registration page is displayed
Observed behavior
Npm runs successfully manifest.json is updated. Webpack generates all chunks in dists folder.
Laravel server starts
Website is reacheable over http://127.0.0.1:8000
Login page opens component configured in the route. Login.js is downloaded by the browser.
register.js chunk is never downloaded. Registration page is never displayed. After 30 seconds timeout, error is printed in browser console.
ChunkLoadError: Loading chunk register failed.
(timeout: http://127.0.0.1:8000/dist/register.d3e633a9a1aea3ebf47b.js)
at Object.__webpack_require__.f.j (main.34a1a3da92d476b41479.js:4051:29)
at main.34a1a3da92d476b41479.js:3905:40
at Array.reduce (<anonymous>)
at Function.__webpack_require__.e (main.34a1a3da92d476b41479.js:3904:67)
at component (routes.js:35:55)
at extractComponentsGuards (vue-router.esm-bundler.js:2037:40)
at eval (vue-router.esm-bundler.js:3156:22)
webpack.config.js
const path = require('path');
const {VueLoaderPlugin} = require('vue-loader');
const { WebpackManifestPlugin } = require('webpack-manifest-plugin');
const options = {basePath:'/dist/',fileName:'../mix-manifest.json',publicPath:'dist/'};
const webpack = require('webpack');
module.exports = {
mode: 'development',
entry: './resources/js/main.js',
output: {
clean: true,
filename: "[name].[chunkhash].js",
publicPath: './dist/',
path: path.resolve(__dirname, 'public/dist'),
chunkLoadTimeout: 30000,
},
resolve:{
alias: {
'#': path.resolve(__dirname,'resources/js'),
},
extensions: ['.js', '.vue', '.json']
},
module: {
rules: [
{
test: /\.vue$/,
use: 'vue-loader'
},
{
test: /\.css$/,
use: [
'vue-style-loader',
'css-loader'
]
}
]
},
plugins: [
new VueLoaderPlugin(),
new WebpackManifestPlugin(options),
new webpack.DefinePlugin({
__VUE_OPTIONS_API__: false,
__VUE_PROD_DEVTOOLS__: false,
}),
]
};
routes.js
const routes = [
{
path: "/",
component: DefaultLayout,
children: [
{
path: "/publicidade",
name: "publicidade",
component: () => import (/* webpackChunkName: "publicity" */ '../pages/Publicidade')
},
{
path: "/login",
name: "login",
component: () => import(/* webpackChunkName: "login" */ '../pages/login.vue')
},
{
path: "/cadastro",
name: "cadastro",
component: () => import(/* webpackChunkName: "register" , webpackPrefetch: true */ '../pages/Register.vue')
},
{
path: "perfil",
name: "perfil",
component: () => import('../pages/Profile')
},
],
},
{ path: "/:catchAll(.*)", component: NotFoundPage }
];
I had the same problem in vue, I solved it using a method similar to the method Polymer uses to import the lazy loading resources.
Example:
setTimeout(() => {
for (let i in routes[0].children ){
routes[0].children[i].component();
}
}, 1000)
After a couple of weeks of head banging I was able to locate the problem by stepping through the Webpack code starting at the Webpack.require call (See my error log above).
My issue was that my chunks were being blocked by the Osano GPDR Consent service, which I use in my website to satisfy EU privacy laws.
As soon as I removed the osano script tag from the header of my site, everything just worked.
<script src="https://cmp.osano.com/AzZcqMSBLrjhQ2PZ1/4e3118ce-1798-4f27-93be-f6c9e99f1537/osano.js"></script>
Since GPDR Consent is a must here in Europe, I had to add a rule to the Dashboard of my Osano account in order to allow scripts from 127.0.0.1:8080 domain.
Related
I'm in the process of turning a, what was called, "PWA" into an actual PWA. I have successfully gotten a serviceworker attached to the application and now I wanna know how to precache the entire application so that internet connection is "never" needed to open the application if it has already been cached in the browser.
To my mind the StaleWhileRevalidate strategy SHOULD be the best option here, but I have been bested by, either, my own ability to read documentation or workbox-webpack's horrible attempt at documentation (it's most likely the former, rather than the latter)
I have my webpack.common.js file which is my general webpack config entry point for all types of builds.
'use strict';
const helpers = require('./helpers');
const path = require('path');
const VueLoaderPlugin = require('vue-loader/lib/plugin')
const WorkboxPlugin = require('workbox-webpack-plugin')
module.exports = {
entry: {
polyfill: '#babel/polyfill',
main: path.resolve(__dirname, '../src/main.js'),
vendor: path.resolve(__dirname, '../src/vendor.js')
},
module: {
rules: [{
test: /\.(vue|Vue)$/,
loader: 'vue-loader',
include: [helpers.root('src')]
},
{
test: /\.html$/,
use: [
{
loader: 'html-loader',
}
]
},
{
test: /\.ico$/,
use: {
loader: 'file-loader',
options: {
name: '[name].[hash].[ext]',
outputPath: 'assets/img/icons'
}
}
},
{
test: /\.svg$/,
use: [{
loader: 'svg-sprite-loader',
options: {
spriteFilename: 'sprites.svg',
runtimeCompat: true
}
},
{
loader: 'svgo-loader',
options: {
removeTitle: true,
removeUselessStrokeAndFill: true
}
}
]
}
]
},
plugins: [
new VueLoaderPlugin(),
new WorkboxPlugin.GenerateSW({
exclude: ['./static/1pixel.png'],
maximumFileSizeToCacheInBytes: 6291456,
clientsClaim: true,
skipWaiting: true,
runtimeCaching: [
{
handler: 'CacheFirst',
urlPattern: /\.(?:js|css|html)$/,
options: {
cacheName: 'static-assets-cache',
cacheableResponse: {
statuses: [200]
}
}
}
]
})
]
};
so far I have gotten to the point where I can exclude an image file from the cache, but that's about as far as I have gotten. I want to achieve the following:
I open the application WITH internet connection. Get everything loaded, cached and ready to go. I then close the tab the application was loaded in, but my phone in my pocket and go about my business. I then later want to open the application again, but now WITHOUT internet connection. This should be possible since the application should live in the cache. However. I can see in my applications cache all of my fonts, image assets, polyfill and stores (js) have been cached, but not the index.html file. And I guess that is why my application refuses to load after a tab in the browser is disposed.
TL;DR - How do I precache the index.html so that the application will load even after a browsertab has been disposed or the browser timed out the session that my PWA lives in?
My current solution was to add:
{
handler: 'CacheFirst',
urlPattern: './index.html',
options: {
cacheName: 'index-assets-cache',
cacheableResponse: {
statuses: [200]
}
}
}
To my runtimeCaching object. I also fixed my manifest.json config to comply with the standards defined by Chrome and Safari. This way I can install the PWA as an app on the phone, which fixes my problem when the app is installed on the phone.
The problem still persists in a normal browser on a phone. After login and load of all files and precaching I cannot close the tab and open it again from an offline state. So this is not a complete answer.
I've just started to learn Vue but I simply can't set up enviroment for my container.
I use Cloud9 and I have to assign my host for serving Vue app according to this link.
Unfortunately, I can't find vue.config.js file to do this.
Also there is no path indication in Vue docs.
"if it's present in your project root..." but what if not? Whatever, go use React? :)
Vue version: 3.1.1
See the documentation of Vue CLI:
vue.config.js is an optional config file that will be automatically
loaded by #vue/cli-service if it's present in your project root (next
to package.json). You can also use the vue field in package.json, but
do note in that case you will be limited to JSON-compatible values
only.
The file should export an object containing options:
// vue.config.js
module.exports = {
// options...
}
So just create the file by yourself. It is completely optional.
It's simple just create file vue.config.js in ROOT folder of project.
Its very important file. it's on top of vue project.
People usualy use more than one page in old fasion style.
vue.config.js is right place to define main dependency pages.
I used to create main sigle-page app (pwa) but i also need
some other pages. Like error pages or google verify for example.
You can change dev server port , sourceMap enable/disable or PWA configuration...
module.exports = {
pages: {
'index': {
entry: './src/main.ts',
template: 'public/index.html',
title: 'Welcome to my vue generator project',
chunks: ['chunk-vendors', 'chunk-common', 'index']
},
'bad': {
entry: './src/error-instance.ts',
template: 'public/bad.html',
title: 'Error page',
chunks: ['chunk-vendors', 'chunk-common', 'index']
},
/* Disabled - Only one time
'googleVerify': {
entry: './src/error-instance.ts',
template: 'public/somelink.html',
title: 'Error page',
chunks: ['chunk-vendors', 'chunk-common', 'index']
},
*/
},
devServer: {
'port': 3000
},
css: {
sourceMap: false
},
pwa: {
name: 'My App',
themeColor: '#4DBA87',
msTileColor: '#000000',
appleMobileWebAppCapable: 'yes',
appleMobileWebAppStatusBarStyle: 'black',
},
}
For this config here's main instance file:
import Vue from 'vue'
import App from './App.vue'
import store from './store'
Vue.config.productionTip = false
new Vue({
store,
render: h => h(App, {
props: { error: 'You can not search for assets...' }
}),
}).$mount('#error')
if you used vue-cli this is in your root like this
your project
node_module
public
src
...
vue.config.js
if you dont have it,
your project is not vue-cli, it is vit-vue ,
and your config file name is vit.config. vue
I'm having trouble figuring out how to have multiple pages in a Vue CLI project. Right now I have my home page with a few components and I want to create another page but I do not know how to do that. Am I supposed to create multiple html files where the index.html by default is? In a simple file structure with css js img folder and html files as pages I know that creating another html file means making another page. But I don't understand how this works with Vue CLI project.
I saw stuff like vue-router and "pages" in Vue documentation but I do not understand them very well. What are my alternatives? Is there a guide that explains that in detail, because I wasn't able to find any, let alone detailed. Would be very happy if you could help! Thank you!
First: always read the official documentation. With Vue you can build a SPA, and a MPA is also no problem. Just follow the guides:
https://cli.vuejs.org/
https://cli.vuejs.org/guide/html-and-static-assets.html#building-a-multi-page-app
https://cli.vuejs.org/config/#pages
You should create a new project with Vue CLI 3. Once you've created your project set it to be manually configured. Make sure you don't choose the SPA option. Vue will then create a nice "start" project using a MPA approach. After that, just repeat the config on vue.config.js.
Updated #1
It seems that some updates on Vue Cli, have changed the way to build a MPA app, so:
Create a new application vue create test
Choose Manual configuration
The boilerplate created will be for a SPA. So make the following changes:
Create a folder under src named pages (optional)
Into this folder create your own pages: Home, About, etc.
Copy and paste the App.vue and main.js from src, into your new folders - Home, etc.
Format the App.vue into this folders, to your liking.
Create a vue.config.js and set it like this: https://cli.vuejs.org/config/#pages
Below, I have three images demonstrating this:
First: a fresh new app
Second: this same app, with the changes I made above
Third: the vue.config.js from this app
You don't need to create the pages folder, this is just to get the idea.
Link to GitHub: Building a MPA App
EDIT: Vue has this built-in. Skip to the bottom for more.
Original answer:
There are two ways to interpret your question, and therefore to answer it.
The first interpretation is: "how can I support routing to different pages within the same single-page app, e.g. localhost:8080/about and localhost:8080/report etc?". The answer to this is to use the router. It's reasonably straightforward and works well.
The second interpretation is: "my app is complex, and I have multiple single-page applications, e.g. one app for the 'website' part, one app for consumers to log in and do work, one app for admins, etc - how can vue do this, without making three entirely separate repositories?"
The answer to the latter is a single repository with multiple single-page apps. This demo looks like exactly what you're after:
https://github.com/Plortinus/vue-multiple-pages/
Look in particular at: https://github.com/Plortinus/vue-multiple-pages/blob/master/vue.config.js
Updated answer:
It turns out that vuejs has the idea of multiple top-level pages built-in. I mean, it makes sense - it's going to be really common, despite what many incorrect answers are saying about "no, it's for single page apps"!
You want the pages option in the vue.config.js file:
https://cli.vuejs.org/config/#pages
If your project doesn't have that file in the root directory, create it and vuejs will discover it.
There is a long and a short way to define each page. I used the short form here:
module.exports = {
pages: {
index: 'src/pages/index/main.ts',
experiment: 'src/pages/experiment/main.ts'
}
}
You don't have to put your work under "pages". It could be "/src/apps/index/index.ts" or whatever.
After moving code around and changing some imports from:
import HelloWorld from './components/HelloWorld'
to
import HelloWorld from '#/components/HelloWorld'
The app works - but the "experiment" app in my repo had to be loaded like this:
http://localhost:8080/experiment.html
Pretty ugly, and even worse because it uses the router which resulted in URLs like:
http://localhost:8080/experiment.html/about
Ugh.
Fortunately, this stackoverflow answer solved it. Update the vue.config.js file to include devServer options (make sure this is at the top level of the exported object:
devServer: {
historyApiFallback: {
rewrites: [
{ from: /\/index/, to: '/index.html' },
{ from: /\/experiment/, to: '/experiment.html' }
]
}
}
Then also modify the router.ts file to append the extra path (in my case "experiment/":
export default new Router({
mode: 'history',
base: process.env.BASE_URL + 'experiment/',
...
Then URLs resolve nicely, e.g.: http://localhost:8080/experiment/about
This may not be relevant to the question, but bear with me, maybe my answer can help someone.
I use webpack+vue, and I have figured out how to build multiple pages applications. Here my webpack.config.js:
const path = require('path');
const fs = require('fs')
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const VueLoaderPlugin = require('vue-loader/lib/plugin');
const TerserPlugin = require('terser-webpack-plugin');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
module.exports = {
entry: {
app: './src/app.js',
mgmt: ['./src/modules/mgmt/mgmt.js'],
login: './src/modules/login/login.js'
},
output: {
path: path.resolve(__dirname, 'dist'),
// publicPath: '/ahezime/',
filename: (chunkData) => {
console.log('chuckData.chunk.name => ', chunkData.chunk.name)
return chunkData.chunk.name === 'app' ? './[name].bundle.js' : './[name]/[name].bundle.js';
}
},
optimization: {
minimizer: [
new TerserPlugin(),
new OptimizeCSSAssetsPlugin({})
]
},
plugins: [
new MiniCssExtractPlugin({
filename: "[name].css",
chunkFilename: "[id].css"
}),
new CleanWebpackPlugin(['dist']),
new VueLoaderPlugin(),
new HtmlWebpackPlugin({
title: 'app',
template: './src/app.html',
// inject: false,
chunks: ['app'],
filename: './index.html'
}),
new HtmlWebpackPlugin({
title: 'mgmt',
template: './src/modules/mgmt/mgmt.html',
// inject: false,
chunks: ['mgmt'],
filename: './mgmt/index.html'
}),
new HtmlWebpackPlugin({
title: 'login',
template: './src/modules/login/login.html',
// inject: false,
chunks: ['login'],
filename: './login/index.html'
})
],
module: {
rules: [
{
test: /\.m?js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
presets: ['#babel/preset-env'],
plugins: ['#babel/plugin-proposal-object-rest-spread']
}
}
}
],
rules: [
{
test: /\.vue$/,
exclude: /node_modules/,
loader: 'vue-loader'
},
{
test: /\.css$/,
use: [
'vue-style-loader',
'style-loader',
'css-loader',
'sass-loader'
]
},
{
test: /\.scss?$/,
use: ['style-loader', 'css-loader', 'sass-loader']
},
{
test: /\.(png|svg|jpg|gif)$/,
use: [
'file-loader'
]
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/,
use: [
'file-loader'
]
}
]
}
};
And here's my directory structure:
https://i.stack.imgur.com/uFvKx.png
And you can jump pages:
<template>
<div>
<h1>App</h1>
<div>
Please click me, and let take you into the login page!!!
</div>
<span>Before computed: {{ message }} </span>
<br>
<span>Afer computed: {{ computedMessage() }} </span>
</div>
</template>
<script>
export default {
data() {
return {
message: 'Hello World!'
}
},
computed: {
reversedMessage: function() {
return this.message.split('').reverse().join('')
}
},
methods: {
computedMessage: function() {
return this.message.split('').reverse().join('')
}
}
}
</script>
Note pointing users to what should be the accepted answer
At the moment of posting my initial answer I wasn't aware of the possibility of actually building MPAs in VueJS. My answer doesn't address the question asked therefore I will recommend to take a look at the answer provided by PJ.Wanderson bellow which should be the accepted answer
Inital Answer
Vue.js projects are SPAs(single page applications). You only have one .html file in the entire project which is the index.html file you mentioned. The "pages" you want to create, in vue.js are referred to as components. They will be plugged into the index.html file and rendered in the browser. A vue.js component comprises 3 parts:
<template>
</template>
<script>
export default {
}
</script>
<style>
</style>
Template: it contains all the html your page should display (this is where you put the html of your pages)
Script: it contains all JavaScript code that will be executed on the page/component
Style: it contains the CSS that will style that specific component/page
You can check this tutorial out for a quick-start Vue.js 2 Quickstart Tutorial 2017
It explains vue.js project structure and how the various files relate to each other
I have...
Back-End
Node.js/express server which serves files when requests are made to certain routes.
Front-End
React pages that makes calls to the back-end requesting data to populate the pages.
I'm using react-router v4 on the front end. Whenever I navigate to a route that is NOT at the exact path AND reload the page, I get a 404 error. I understand why this isn't working; the browser makes a request to a path handled by react-router, and since it doesn't find that route on the server, I get 404.
I'm seeking for a solution to this problem.
// BrowserRouter imported as Router
<Router>
<Route exact path='/main' component={Main} />
<Route path='/sub1' component={SubOne} />
<Route path='/sub2' component={SubTwo} />
</Router>
When I go to /main in the browser, <Main /> is rendered. Say that inside <Main />, there are links to /sub1 and /sub2 respectively. I click on /sub2. Component and page content renders without fail. Then I refresh page, either by accident or intentionally (say component Sub2 lifts state up to Main).
How do I avoid getting 404 after this refresh? How do I get the page/component where "I was" rendered after a refresh if I'm using React-Router?
I had the same issue you're having about 2 months ago. I had very little knowledge about server-side rendering with React. I got confused on the general concept of it. However, I didn't want to use the create-react-app cli. I wanted to use my own boilerplate. Upon doing research, I found out that I had to configure my webpack to handle my 404 reloading fallbacks.
Here is my current webpack setup:
Please note, only pay attention to the historyApiFallback: true that allows you to refresh your page without throwing a 404 error if you're using v4. In addition, i forgot to mention that this requires webpack-dev-server to work.
const webpack = require('webpack');
const path = require('path');
const nodeExternals = require('webpack-node-externals');
const HtmlWebPackPlugin = require('html-webpack-plugin');
var browserConfig = {
devServer: {
historyApiFallback: true,
proxy: {
"/api": "http://localhost:3012"
}
},
entry: ['babel-polyfill', __dirname + '/src/index.js'],
output: {
path: path.resolve(__dirname + '/public'),
filename: 'bundle.js',
publicPath: '/'
},
module: {
rules: [
{
test: /\.jsx?$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
query: {
presets: ['react', 'env', 'stage-0']
}
}
}
]
},
plugins: [
new HtmlWebPackPlugin({
template: './public/index.html',
})
]
}
var serverConfig = {
target: 'node',
externals: [nodeExternals()],
entry: __dirname + '/server/main.js',
output: {
path: path.resolve(__dirname + '/public'),
filename: 'server.js',
publicPath: '/'
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
query: {
presets: ['react', 'env', 'stage-0']
}
}
}
]
}
}
module.exports = [browserConfig, serverConfig]
The reason you got 404 is refreshing the page poses network roundtrip to hit the server, while react-router is a client-side routing library, it doesn't handle server-side routing.
when the user hits the refresh button or is directly accessing a page other than the landing page, e.g. /help or /help/online as the web server bypasses the index file to locate the file at this location. As your application is a SPA, the web server will fail trying to retrieve the file and return a 404 - Not Found message to the user.
Since you're using an Express server, connect-history-api-fallback(Express middleware) can be used to proxy all received requests(including unknown ones, /sub2 in your case) through your index page. Then reboot your SPA and route to /sub2 on the client by react router.
If you're using webpack-dev-server for local development, it will be as simple as turning on devServer.historyApiFallback.
my solution is using content-history-api-fallback module
first: import history from 'connect-history-api-fallback'
then:
app.use(history({
rewrites:[
{from: /^\/api\/.*$/, to: function(context){
return context.parsedUrl.pathname;
}},
{from: /\/.*/, to: '/'}
]
}))
app.get('/', function(req, res, next){
res.render('index');
})
app.use('/api', api);
Use the "Redirect" directive to map your sub paths to actual paths
https://reacttraining.com/react-router/web/example/no-match
Just add the following "function" to your server application's app.js file:
app.get('*', (req, res) => {
res.sendFile(path.join(__dirname+'/client/build/index.html'));
});
Replace the "client" with the name of the directory of you client application. This will act as a "catchball" for any requests that don't match the ones that you have defined. It worked for me.
For more info please watch the following tutorial: https://www.youtube.com/watch?v=xwovj4-eM3Y
I followed the steps on the website 'https://angular.io/guide/aot-compiler' to adapt an Angular 2 app to use AoT compilation and everything seemed to work as expected until the rollup step, which in theory cleans up the code of unused components.
Leaving the 'rollup-config.js' file the same as in the instructions, when I run 'node_modules/.bin/rollup -c rollup-config.js' I get the following error:
[!] Error: 'ToastModule' is not exported by node_modules/ng2-toastr/ng2-toastr.js
I've googled it, and I came across a solution that pointed to add a 'namedExports' field to the 'commonjs' parameter. The resulting 'rollup-config.js' is:
import nodeResolve from 'rollup-plugin-node-resolve';
import commonjs from 'rollup-plugin-commonjs';
import uglify from 'rollup-plugin-uglify';
export default {
entry: 'src/main.js',
dest: 'src/build.js',
sourceMap: false,
format: 'iife',
onwarn: function(warning) {
if(warning.code === 'THIS_IS_UNDEFINED') { return; }
console.warn(warning.message);
},
plugins: [
nodeResolve({jsnext: true, module: true}),
commonjs({
include: 'node_modules/**',
namedExports: {
'node_modules/ng2-toastr/ng2-toastr': [ 'ToastModule' ]
}
}),
uglify()
]
}
Now, the script generates the 'build.js' file and I can load it through the index.html file, but the component 'ng2-toastr' is not working (the component shows a 'toast' like message, appearing on one side of the screen and disappearing after a few seconds).
Did I miss something? Thanks in advance,