diff --git a/src/dashboard/Dashboard.js b/src/dashboard/Dashboard.js index c7c88da08..f5f43b014 100644 --- a/src/dashboard/Dashboard.js +++ b/src/dashboard/Dashboard.js @@ -12,12 +12,12 @@ import AppData from './AppData.react'; import AppsIndex from './Apps/AppsIndex.react'; import AppsManager from 'lib/AppsManager'; import Browser from './Data/Browser/Browser.react'; -import CloudCode from './Data/CloudCode/B4ACloudCode.react'; +// import CloudCode from './Data/CloudCode/B4ACloudCode.react'; import AppOverview from './Data/AppOverview/AppOverview.react'; import Config from './Data/Config/Config.react'; import FourOhFour from 'components/FourOhFour/FourOhFour.react'; import GeneralSettings from './Settings/GeneralSettings.react'; -import GraphQLConsole from './Data/ApiConsole/GraphQLConsole.react'; +// import GraphQLConsole from './Data/ApiConsole/GraphQLConsole.react'; // import HostingSettings from './Settings/HostingSettings.react'; import HubConnections from './Hub/HubConnections.react'; import IndexManager from './IndexManager/IndexManager.react' @@ -36,7 +36,7 @@ import { get } from 'lib/AJAX'; import { setBasePath } from 'lib/AJAX'; import ServerSettings from 'dashboard/ServerSettings/ServerSettings.react'; import { Helmet } from 'react-helmet'; -import Playground from './Data/Playground/Playground.react'; +// import Playground from './Data/Playground/Playground.react'; import axios from 'lib/axios'; // import moment from 'moment'; import B4aConnectPage from './B4aConnectPage/B4aConnectPage.react'; @@ -49,7 +49,7 @@ import PushDetails from './Push/PushDetails.react'; import PushIndex from './Push/PushIndex.react'; import PushNew from './Push/PushNew.react'; // import PushSettings from './Settings/PushSettings.react'; -import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import React, { useCallback, useEffect, useMemo, useState, Suspense, lazy } from 'react'; import RestConsole from './Data/ApiConsole/RestConsole.react'; // import SchemaOverview from './Data/Browser/SchemaOverview.react'; import SecuritySettings from './Settings/SecuritySettings.react'; @@ -66,6 +66,10 @@ import back4app2 from '../lib/back4app2'; import { initializeAmplitude } from 'lib/amplitudeEvents'; import { setUser as setSentryUser } from '@sentry/react'; +const LazyGraphQLConsole = lazy(() => import('./Data/ApiConsole/GraphQLConsole.react')); +const LazyPlayground = lazy(() => import('./Data/Playground/Playground.react')); +const LazyCloudCode = lazy(() => import('./Data/CloudCode/B4ACloudCode.react')); + const ShowSchemaOverview = false; //In progress features. Change false to true to work on this feature. // class Empty extends React.Component { @@ -143,6 +147,44 @@ const waitForScriptToLoad = async conditionFn => { throw new Error('Script not loaded yet!'); }; +const preloadMap = { + cloudCode: () => import('./Data/CloudCode/B4ACloudCode.react'), + graphqlConsole: () => import('./Data/ApiConsole/GraphQLConsole.react'), + playground: () => import('./Data/Playground/Playground.react'), +}; + +// Preload all routes with proper error handling and logging +const preloadRoute = async (routeName, preloadFn) => { + try { + await preloadFn(); + console.log(`Successfully preloaded route: ${routeName}`); + } catch (err) { + console.error(`Error preloading route ${routeName}:`, err); + } +}; + +// Preload all routes in parallel +const preloadAllRoutes = () => { + console.log('Preloading routes...'); + return Promise.all( + Object.entries(preloadMap).map(([routeName, preloadFn]) => + preloadRoute(routeName, preloadFn) + ) + ); +}; + +const LoadingComponent = () => ( +
+ +
+); + +const LazyComponentWrapper = ({ children }) => ( + }> + {children} + +); + class Dashboard extends React.Component { constructor(props) { super(); @@ -160,6 +202,11 @@ class Dashboard extends React.Component { } componentDidMount() { + // Start preloading routes immediately but don't block on it + preloadAllRoutes().finally(() => { + console.log('Route preloading complete'); + }); + get('/parse-dashboard-config.json').then(({ apps, newFeaturesInLatestVersion = [], user }) => { fetchHubUser().then(userDetail => { user.createdAt = userDetail.createdAt; @@ -383,8 +430,8 @@ class Dashboard extends React.Component { const ApiConsoleRoute = ( }> } /> - } /> - } /> + } /> + } /> } /> ); @@ -400,7 +447,7 @@ class Dashboard extends React.Component { } /> } /> - } /> + } /> } /> {JobsRoute} diff --git a/src/dashboard/Data/AppOverview/AppOverview.react.js b/src/dashboard/Data/AppOverview/AppOverview.react.js index fd479b2eb..c52482c82 100644 --- a/src/dashboard/Data/AppOverview/AppOverview.react.js +++ b/src/dashboard/Data/AppOverview/AppOverview.react.js @@ -6,7 +6,7 @@ * This source code is licensed under the license found in the LICENSE file in * the root directory of this source tree. */ -import React from 'react'; +import React, { Suspense, lazy } from 'react'; import DashboardView from 'dashboard/DashboardView.react'; import styles from 'dashboard/Data/AppOverview/AppOverview.scss'; import { withRouter } from 'lib/withRouter'; @@ -18,11 +18,12 @@ import AppSecurityCard from './AppSecurityCard.react'; import AppPerformanceCard from './AppPerformanceCard.react'; import AppLoadingText from './AppLoadingText.react'; import B4aTooltip from 'components/Tooltip/B4aTooltip.react'; -import ConnectAppModal from './ConnectAppModal.react'; +// import ConnectAppModal from './ConnectAppModal.react'; import OnboardingBoxes from './OnboardingBoxes.react'; import AccountManager from 'lib/AccountManager'; import { amplitudeLogEvent } from 'lib/amplitudeEvents'; +const LazyConnectAppModal = lazy(() => import('./ConnectAppModal.react')); @withRouter class AppOverview extends DashboardView { constructor() { @@ -73,6 +74,10 @@ class AppOverview extends DashboardView { this.loadCardInformation(); } + componentDidMount() { + import('./ConnectAppModal.react'); + } + copyText(copyText = '') { if (navigator) { navigator.clipboard.writeText(copyText); @@ -300,7 +305,9 @@ class AppOverview extends DashboardView { {this.state.showConnectAppModal && ( - this.setState({ showConnectAppModal: false })} /> + + this.setState({ showConnectAppModal: false })} /> + )} ); diff --git a/src/dashboard/index.js b/src/dashboard/index.js index 383973cae..c35bb04e5 100644 --- a/src/dashboard/index.js +++ b/src/dashboard/index.js @@ -9,14 +9,18 @@ import './instrument'; import 'core-js/stable'; import 'regenerator-runtime/runtime'; import Immutable from 'immutable'; -import installDevTools from 'immutable-devtools'; +// import installDevTools from 'immutable-devtools'; import React from 'react'; import ReactDOM from 'react-dom'; import Dashboard from './Dashboard'; require('stylesheets/fonts.scss'); require('graphiql/graphiql.min.css'); -installDevTools(Immutable); + +if (process.env.NODE_ENV !== 'production') { + const installDevTools = require('immutable-devtools'); + installDevTools(Immutable); +} const path = window.PARSE_DASHBOARD_PATH || '/'; ReactDOM.render(, document.getElementById('browser_mount')); diff --git a/webpack/homolog.config.js b/webpack/homolog.config.js index febe18273..1da74ac75 100644 --- a/webpack/homolog.config.js +++ b/webpack/homolog.config.js @@ -1,41 +1,69 @@ -const configuration = require('./publish.config.js'); +/* + * Copyright (c) 2016-present, Parse, LLC + * All rights reserved. + * + * This source code is licensed under the license found in the LICENSE file in + * the root directory of this source tree. + */ +const configuration = require('./base.config.js'); const webpack = require('webpack'); -const TerserPlugin = require('terser-webpack-plugin'); +const path = require('path'); +const HtmlWebpackPlugin = require('html-webpack-plugin'); +// const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; + +configuration.mode = 'production'; +configuration.entry = { + dashboard: './dashboard/index.js', +}; + +configuration.output.path = path.resolve('./Parse-Dashboard/public/bundles'); +configuration.output.filename = '[name].[chunkhash].js'; + +configuration.optimization = { + usedExports: true, + minimize: true, + concatenateModules: true, + sideEffects: true, + providedExports: true, + innerGraph: true, + splitChunks: { + chunks: 'all', + minSize: 20000, + maxAsyncRequests: 30, + maxInitialRequests: 30 + } +}; configuration.plugins.push( + new HtmlWebpackPlugin({ + template: '../Parse-Dashboard/index.ejs', + filename: path.resolve('./Parse-Dashboard/public/index.html') + }), new webpack.DefinePlugin({ 'process.env': { - 'NODE_ENV': JSON.stringify('homolog'), - 'SENTRY_ENV': JSON.stringify('homolog') + 'NODE_ENV': JSON.stringify('production'), + 'SENTRY_ENV': JSON.stringify('production') } }), - new webpack.SourceMapDevToolPlugin({ - filename: '[file].map', - include: /dashboard.*.*/ - }) + // new BundleAnalyzerPlugin() ); -configuration.optimization = { - splitChunks: { - cacheGroups: { - commons: { - test: /[\\/]node_modules[\\/]/, - name: 'vendors', - chunks: 'all' - } +configuration.module = configuration.module || {}; +configuration.module.rules = configuration.module.rules || []; + +// Ensure babel-loader is configured for tree shaking +const babelRule = configuration.module.rules.find(rule => rule.use && rule.use.includes('babel-loader')); +if (babelRule) { + babelRule.use = { + loader: 'babel-loader', + options: { + presets: [ + ['@babel/preset-env', { + modules: false // This is important for tree shaking + }] + ] } - }, - minimize: true, - minimizer: [ - new TerserPlugin({ - // cache: true, - parallel: true, - terserOptions: { - sourceMap: true, - mangle: false - } - }) - ] + }; } -module.exports = configuration; \ No newline at end of file +module.exports = configuration;