I am working on Laravel Inertia project.
app.js on local is 4MB
When I load the same file on live server then it becomes 13-15MB.
due to which page load time increases to 10-15 seconds.
I push file after compressing it by npm run prod
Local Response time
Live Response time
Everything is working great on local and live except file size issue.
app.js
require('./bootstrap');
// Import modules...
import { createApp, h } from 'vue';
import mitt from 'mitt';
const emitter = mitt();
import { App as InertiaApp, plugin as InertiaPlugin } from '#inertiajs/inertia-vue3';
import { InertiaProgress } from '#inertiajs/progress';
const el = document.getElementById('app');
const app = createApp({
render: () =>
h(InertiaApp, {
initialPage: JSON.parse(el.dataset.page),
resolveComponent: (name) =>
require(`./Pages/${name}`).default,
}),
})
.mixin({ methods: { route } })
.use(InertiaPlugin)
});
app.config.globalProperties.emitter = emitter;
app.mount(el);
InertiaProgress.init({ color: '#124664',showSpinner:false });
webpack.config.js
const path = require('path');
module.exports = {
resolve: {
alias: {
'#': path.resolve('resources/js'),
},
},
};
webpack.mix.js
const mix = require('laravel-mix');
mix.js('resources/js/app.js', 'public/js')
.vue()
.postCss('resources/css/app.css', 'public/css/tailwind_css', [
require('postcss-import'),
// require('tailwindcss'),
require('autoprefixer'),
])
.webpackConfig(require('./webpack.config'));
if (mix.inProduction()) {
mix.version();
}
app.js is added in main blade in head tag file like this
<script src="{{ mix('js/app.js') }}" defer></script>
The problem is probably because you haven't configured Code Splitting correctly, which is causing all your files to be bundled into a single huge file.
You need to replace the synchronous require statement in you app.js with a dynamic import:
render: () =>
h(InertiaApp, {
initialPage: JSON.parse(el.dataset.page),
resolveComponent: (name) =>
require(`./Pages/${name}`).default, // This line is bundling all your files together
}),
})
render: () =>
h(InertiaApp, {
initialPage: JSON.parse(el.dataset.page),
resolveComponent: (name) =>
import(`./Pages/${name}`), // Now, each page will have its respective file
}),
})
Laravel mix will probably handle the tooling need for Webpack to handle dynamic imports, but in case you're having troubles, take a look at the docs which explains what you'll need in details.
Related
I have a website written in Laravel and InertiaJS (VueJS).
It has more than 60 pages.
InertiaJS stores all pages and components in these three files:
/js/manifest.js
/js/vendor.js
/js/app.js
The problem is the size of these files (Specially app.js) are getting so huge! The app.js is about 5MB.
I removed useless plugins and libraries, Also i refactored my code and it's all clean.
When i was only using Laravel for web development, I could load each page javascript and css files in it's own page. So speed of the page was pretty good. But when i migrated to VueJS it loads app.js and styles.css at once! It also extracts all styles in one file which is not ideal.
Also i use CDN, gzip compression, SSR and they are not helping too much to have better performance.
I want extract all components and pages into different javascript/css files and load them in their own pages when needed.
How can i do that ?
Here's your app.js using code splitting. See if it'll reduce your load size:
import Vue from 'vue';
const App = () => import('./App.vue');
const Link = () => import('#inertiajs/inertia-vue/Link');
const plugin = () => import('#inertiajs/inertia-vue/plugin');
const BootstrapVue = () => import('bootstrap-vue/dist/bootstrap-vue.esm');
const IconsPlugin = () => import('bootstrap-vue/dist/bootstrap-vue-icons.esm');
const Ziggy = () => import('ziggy-js');
const InertiaProgress = () => import('#inertiajs/progress');
Vue.component('inertia-link', Link)
Vue.use(BootstrapVue);
Vue.use(IconsPlugin);
Vue.use(plugin)
InertiaProgress.init()
Vue.mixin({
methods: {
route: (name, params, absolute) => Ziggy().then(ziggy => ziggy.route(name, params, absolute)),
},
});
const app = document.getElementById('app')
if (app.dataset.page) {
new Vue({
render: h => h(App, {
props: {
initialPage: JSON.parse(app.dataset.page),
resolveComponent: name => import(`./Pages/${name}.vue`).then(m => m.default),
},
})
}).$mount(app)
} else {
const app = new Vue({
el: '#app'
});
}
See this: Inertia doc
Are you loading all the pages at once?
I think you can try loading components only if required. We can use lazy loading and reduce app.js. Ref: https://router.vuejs.org/guide/advanced/lazy-loading.html#with-webpack
render: h => h(App, {
props: {
initialPage: JSON.parse(app.dataset.page),
resolveComponent: name => require(`./Pages/${name}`).default,
},
})
}).$mount(app)
This is my app.js file.
require('./bootstrap');
import {App, Link, plugin} from '#inertiajs/inertia-vue'
import Vue from 'vue'
import {BootstrapVue, IconsPlugin} from 'bootstrap-vue'
import route from 'ziggy-js';
import {Ziggy} from 'ziggy-js';
import {InertiaProgress} from '#inertiajs/progress'
Vue.component('inertia-link', Link)
Vue.use(BootstrapVue);
Vue.use(IconsPlugin);
Vue.use(plugin)
InertiaProgress.init()
Vue.mixin({
methods: {
route: (name, params, absolute) => route(name, params, absolute, Ziggy),
},
});
const app = document.getElementById('app')
if (app.dataset.page) {
new Vue({
render: h => h(App, {
props: {
initialPage: JSON.parse(app.dataset.page),
resolveComponent: name => require(`./Pages/${name}`).default,
},
})
}).$mount(app)
} else {
const app = new Vue({
el: '#app'
});
}
In the previous way of setting up inertia in a laravel app, I could tweak the resolve property in the `createInertiaApp function from:
{
...,
resolve: name => import("./Pages/${name}"),
...
}
To
{
...,
resolve: name => {
const page = require("./Pages/${name}").default
if(!page.layout) {
page.layout = DefaultLayoutFile
}
},
...
}
To allow me manually pass a default layout file to be used in pages.
But with Vite becoming the default asset bundler and according to the docs, I must use a resolvePageComponent function which takes in import.meta.glob as a second argument to instruct Vite which files to bundle.
Problem here is the import gets returned from this resolvePageComponent so I cannot access the default object like I normally will from a require function.
So I have not been able to attach a default layout file to imported pages.
Has anyone been able to find a workaround for this?
Assuming you imported your default layout file like this (remember to not use # in imports anymore as only relative paths work for static analysis reasons):
import DefaultLayoutFile from './Layouts/DefaultLayoutFile.vue'
You can use the following code to get default layout working with Inertia and Vite:
resolve: (name) => {
const page = resolvePageComponent(
`./Pages/${name}.vue`,
import.meta.glob("./Pages/**/*.vue")
);
page.then((module) => {
module.default.layout = module.default.layout || DefaultLayoutFile;
});
return page;
},
[UPDATE 2022-08-01]
Since this is still receiving views, I though it would be useful to show how to get the # working in imports in Vite.
Require path below your imports in vite.config.js
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
import vue from '#vitejs/plugin-vue';
const path = require('path')
And then add resolve into your config below:
export default defineConfig({
resolve:{
alias:{
'#' : path.resolve(__dirname, './src')
},
},
})
Now # will point to your Laravel root and you can import components dynamically from anywhere:
For example import LayoutTop from '#/Layouts/LayoutTop.vue'
will now point to
/resources/js/Layouts/LayoutTop.vue
Remember that Vite needs the .vue extension when importing Vue SFC files.
The async await version of the accepted answer
resolve: async name => {
const page = await resolvePageComponent(`./Pages/${name}.vue`, import.meta.glob("./Pages/**/*.vue"));
page.default.layout ??= DefaultLayoutFile;
return page;
},
This worked from me using the vite with inertia preset
import { createApp, h } from 'vue'
import { createInertiaApp } from '#inertiajs/inertia-vue3'
import { resolvePageComponent } from 'vite-plugin-laravel/inertia'
import DefaultLayout from '#/views/layouts/default.vue'
import SimpleLayout from '#/views/layouts/simple.vue'
import UserLayout from '#/views/layouts/user.vue'
createInertiaApp({
resolve: (name) => {
const page = resolvePageComponent(
name,
import.meta.glob('../views/pages/**/*.vue'),
DefaultLayout
);
page.then((module) => {
if (name.startsWith('auth.')) module.layout = SimpleLayout;
if (name.startsWith('user.')) module.layout = [DefaultLayout, UserLayout];
});
return page
},
setup({ el, app, props, plugin }) {
createApp({ render: () => h(app, props) })
.use(plugin)
.mount(el)
},
})
I have searched and searched and can't seem to find a solution to my problem.
I have taken the following steps:
\\Webpack.mix.js
const mix = require('laravel-mix');
const path = require('path');
mix.webpackConfig({
resolve: {
alias: {
ziggy: path.resolve('vendor/tightenco/ziggy/src/js/route.js'),
},
},
});
mix.js('resources/js/app.js', 'public/js').vue()
.postCss('resources/css/app.css', 'public/css', [
//
]);
mix.browserSync('127.0.0.1:8008');
And then in my app.js I have
\\app.js
require('./bootstrap');
import Vue from 'vue'
// Ziggy start here
import route from 'ziggy';
import { Ziggy } from './ziggy';
Vue.mixin({
methods: {
route: (name, params, absolute, config = Ziggy) => route(name, params, absolute, config),
},
});
// ziggy end here
import VendorLoginForm from './vue/components/vendors/auth/VendorLoginForm'
import VendorRegisterForm from './vue/components/vendors/auth/VendorRegisterForm'
import EditVendorProfile from './vue/components/vendors/profile/EditVendorProfile'
import { ValidationProvider, ValidationObserver, extend } from 'vee-validate/dist/vee-validate.full.esm';
extend("password", {
message: "{_field_} must be at least 10 characters long, contain one uppercase, one lowercase, and one number.",
validate: value => {
const strong_password = new RegExp(
"^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.{10,})"
); // regex to check our strong password
return strong_password.test(value); //Test the regex. Test function returns a true or false value.
}
});
import './scripts/validators'
Vue.component('ValidationProvider', ValidationProvider);
Vue.component('ValidationObserver', ValidationObserver);
Vue.component('extend', extend);
const app = new Vue({
el: '#vue-app',
components: { VendorLoginForm, VendorRegisterForm, EditVendorProfile }
});
I have ran the php artisan command to generate the ziggy.js and it seems to be working fine when I Ctrl+click it.
However when I got into my component to try to use route as below
mounted() {
console.log(this.route('home.page'))
},
I get the error:
app.js:51783 TypeError: Cannot call a class as a function
at _classCallCheck (app.js:3612)
at Route (app.js:3630)
at VueComponent.route (app.js:3345)
at VueComponent.mounted (app.cbfbd1ac1b8410f1ce58.hot-update.js:172)
at invokeWithErrorHandling (app.js:51749)
at callHook (app.js:54109)
at Object.insert (app.js:53032)
at invokeInsertHook (app.js:56234)
at VueComponent.patch [as __patch__] (app.js:56453)
at VueComponent.Vue._update (app.js:53838)
this was a nightmare when I was working with Ziggy at first time but I resolved my issue by
after creating ziggy.js in resources folder php artisan ziggy:generate
edit my webpack.mix.js file
const mix = require('laravel-mix');
const path = require('path');
/*
|--------------------------------------------------------------------------
| Mix Asset Management
|--------------------------------------------------------------------------
|
| Mix provides a clean, fluent API for defining some Webpack build steps
| for your Laravel application. By default, we are compiling the Sass
| file for the application as well as bundling up all the JS files.
|
*/
mix.js('resources/js/app.js', 'public/js')
.vue()
.sass('resources/sass/app.scss', 'public/css');
mix.webpackConfig({
resolve: {
alias: {
ziggy: path.resolve('vendor/tightenco/ziggy/dist'),//Note we point to dist folder
},
},
});
then in App.js
import route from 'ziggy';
import {
Ziggy
} from './ziggy';
Vue.mixin({
methods: {
route: (name, params, absolute) => route(name, params, absolute, Ziggy)
}
});
then you can use route in your components
console.log(route("home"));
I am trying to integrate Highcharts with app.js in Laravel 8 with Vue and Inertia. I am trying to figure out how to pass HighchartsVue. I am trying to pass it to the use function for the createApp. However, I can't access it in the templates.
App.js
require("./bootstrap");
// Import modules...
import { createApp, h } from "vue";
import HighCharts from ""
import {
App as InertiaApp,
plugin as InertiaPlugin,
} from "#inertiajs/inertia-vue3";
const el = document.getElementById("app");
createApp({
render: () =>
h(InertiaApp, {
initialPage: JSON.parse(el.dataset.page),
resolveComponent: (name) => require(`./Pages/${name}`).default,
}),
})
.mixin({ methods: { route } })
.use(InertiaPlugin)
.mount(el);
My template that tries to access the Vue Template. I haven't included the entire template here.
Vue Template
<div>
<highcharts :options="indexOptions"></highcharts>
</div>
For a global registration:
After you have installed "highcharts-vue" using:
npm install highcharts-vue
Register it globally as a plugin in your app.js with:
import HighchartsVue from 'highcharts-vue'
Next register it as a plugin in your vue object with:
Vue.use(HighchartsVue)
Please see the documentation here for more detailed instructions (and how to register it locally in the component).
After installing, your app.js would look something like this:
require("./bootstrap");
// Import modules...
import { createApp, h } from "vue";
import HighchartsVue from 'highcharts-vue'
import {
App as InertiaApp,
plugin as InertiaPlugin,
} from "#inertiajs/inertia-vue3";
Vue.use(HighchartsVue);
const el = document.getElementById("app");
createApp({
render: () =>
h(InertiaApp, {
initialPage: JSON.parse(el.dataset.page),
resolveComponent: (name) => require(`./Pages/${name}`).default,
}),
})
.mixin({ methods: { route } })
.use(InertiaPlugin)
.mount(el);
I would like to use nwidart/laravel-modules and inertia together. The problem I'm having is that my Index.vue that is in my Users module isn't showing up and there is no errors.
When I have this line of code in my app.js
resolveComponent: (name) => require(`../../Modules/Users/Resources/assets/Pages/${name}`).default
then my Index.vue shows up, but when I try to loop through it so that I can make it dynamic nothing shows up and there is no errors
This is what I have in my app.js at the moment
require('./bootstrap');
require('moment');
import Vue from 'vue';
import { InertiaApp } from '#inertiajs/inertia-vue';
import { InertiaForm } from 'laravel-jetstream';
import PortalVue from 'portal-vue';
Vue.mixin({ methods: { route } });
Vue.use(InertiaApp);
Vue.use(InertiaForm);
Vue.use(PortalVue);
const app = document.getElementById('app');
const modulesJson = require("../../modules_statuses.json");
var modulesObject = Object.keys(modulesJson);
new Vue({
render: (h) =>
h(InertiaApp, {
props: {
initialPage: JSON.parse(app.dataset.page),
resolveComponent: (name) => {
// LOOP IS HERE
for(let i = 0; i < modulesObject.length; i++){
require('../../Modules/'+modulesObject[i]+'/Resources/assets/Pages/'+name).default
}
}
},
}),
}).$mount(app);
Write the path of a file in your module when you want to run it through inertia. Then put a condition in the app.js file that if it is sent through the module, this code will be executed, otherwise it will return to the desired pages in the folder pages.
Like the following code:
Route::get('test',function () {
return \Inertia\Inertia::render('modules/Faq/resources/js/Pages/Admin/Index');
});
createInertiaApp({
title: (title) => `${title} - ${appName}`,
resolve: (name) => name.toString().indexOf('modules') > -1 ? require(`../../${name}.vue`) : require(`./Pages/${name}.vue`),
});