Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Attempting Code Splitting, or reducing my bundle size, but not sure if I am using this package correctly #30

Open
andersacorn opened this issue May 10, 2023 · 1 comment

Comments

@andersacorn
Copy link

Hi,

I have a Meteor website that on a experimental branch to explore code splitting with SSR I have introduced this package

Previously I had used lazy and suspense from React to load pages in my routes file but SSR didn’t work, when I viewed the source of the page I just got my loading component, and I want to return all the html in the body for SEO.

After switching to npdev-react-loadable this seemed to reduce my bundle size, currently the bundle size is 2.7mb, it used to be 3.7mb but removing some packages and implementing react-loadable seems to help.

Currently on this experimental branch I have this client/index.js file

import React from 'react';
import { hydrateRoot } from 'react-dom/client';
import { Router } from 'react-router-dom';
import routes from '../both/routes';
import { preloadLoadables } from 'meteor/npdev:react-loadable';


preloadLoadables().then(() => {
	const container = document.getElementById('app');

	const html = hydrateRoot(
		container,
		<>
			<Router history={history}>
				<div>{routes}</div>
			</Router>
		</>,
	);
});

and this server/index.js file

import React from 'react';
import { renderToString } from 'react-dom/server';
import { onPageLoad } from 'meteor/server-render';
import { StaticRouter } from 'react-router';
import { Helmet } from 'react-helmet';
import { ServerStyleSheet } from 'styled-components';
import {
	LoadableCaptureProvider,
	preloadAllLoadables,
} from 'meteor/npdev:react-loadable';

preloadAllLoadables().then(() => {
	onPageLoad(async (sink) => {
		const context = {};
		const sheet = new ServerStyleSheet();
		const routes = (await import('../both/routes.js')).default;
		const loadableHandle = {};

		const App = (props) => (
			<StaticRouter location={props.location} context={context}>
				{routes}
			</StaticRouter>
		);

		const html = renderToString(
			<LoadableCaptureProvider handle={loadableHandle}>
				<App />
			</LoadableCaptureProvider>,
		);
		sink.renderIntoElementById('app', renderToString(html));
		sink.appendToBody(loadableHandle.toScriptTag());
		sink.appendToHead(sheet.getStyleTags());
		const helmet = Helmet.renderStatic();
		sink.appendToHead(helmet.meta.toString());
		sink.appendToHead(helmet.title.toString());
		sink.appendToHead(helmet.link.toString());
	});
});

Now I created a Loadable component at Loading/Loadable.js

import React from "react"
import {Loadable} from 'meteor/npdev:react-loadable';


const LoadableComponent = opts => Loadable({
	loading: () => <p>Loading</p>,
	...opts
});

export default LoadableComponent;

And in my routes.js I am loading my homepage component with the loadableComponent

const HomePage = LoadableComponent({
	loader: () => import('../../ui/containers/Pages/Homepage.container'),
})

and then in that same file calling it in the route

export default (
	<Switch>
		<Route exact name="index" path="/" component={HomePage} />
       ...

Now on that homePage component I have the following

import LoadableComponent from '../Loading/Loadable';

const PageWrapper = LoadableComponent({
	loader: () => import('../Global/PageWrapper'),
});

const StickyFooter = LoadableComponent({
	loader: () => import('../Global/StickyFooter'),
});

export default function HomePage(props) {
	const [state, setState] = React.useState({});
	let page = false;

	if (Meteor.isServer) {
		page = props.page;
	}
	if (Meteor.isClient) {
		React.useEffect(() => {
			if (props) {
				setState({ ...props });
			}
		}, [props]);

		page = state.page;
	}

	if (page) {
		return (
			<PageWrapper
				noMargin
				type={'page'}
				user={props.user}
				page={page}
			>
				<main
					itemType="https://schema.org/Organization"
					className="position-relative"
				>	
				</main>			
			</PageWrapper>
		);
	}
}

Now what is happening in this experimental branch is has a bunch of html markup like this

&amp;lt;div class=&amp;quot;sc-bCfvAP position-relative&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;sc-dPWrhe bciHYw page-wrapper-container&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;page-wrapper... more content missing

it seems to have html with character entities but it cuts off at some point and just shows … as seen at the end of this Gist html · GitHub

Is this a limitation of this package? to not output all the content into the body

Previously my two server/index.js and client/index.js files looked like this with normal imports and routes, now it is somehow working but is mixing readLoadable and npdev react Loadable packages. I didn’t set it up like this.

server

import React from 'react';
import { renderToString } from 'react-dom/server';
import { onPageLoad } from 'meteor/server-render';
import { StaticRouter } from 'react-router';
import { Helmet } from 'react-helmet';
import Loadable from 'react-loadable';
import { ServerStyleSheet } from "styled-components"

onPageLoad(async (sink) => {
	const context = {};
	const sheet = new ServerStyleSheet();
	const routes = (await import('../both/routes.js')).default;

	const App = props => (
		<StaticRouter location={props.location} context={context}>
			{routes}
		</StaticRouter>
	);

	const modules = [];
	// const html = renderToNodeStream((
	const html = renderToString((
		<Loadable.Capture report={(moduleName) => { modules.push(moduleName); }}>
			<App location={sink.request.url} />
		</Loadable.Capture>
	));


	sink.renderIntoElementById('app', html);
	sink.appendToHead(sheet.getStyleTags());
	const helmet = Helmet.renderStatic();
	sink.appendToHead(helmet.meta.toString());
	sink.appendToHead(helmet.title.toString());
	sink.appendToHead(helmet.link.toString());
});

client

import { onPageLoad } from 'meteor/server-render';
import React from 'react';
import { hydrateRoot } from 'react-dom/client';
import { Router } from 'react-router-dom';
import routes from '../both/routes';
import { preloadLoadables } from 'meteor/npdev:react-loadable';

preloadLoadables().then(() => {
	const container = document.getElementById('app');

	const html = hydrateRoot(
		container,
		<>
			<Router history={history}>
				<div>{routes}</div>
			</Router>
		</>,
	);
});

However the reason I am showing the current branch is because it does render the html in the source like so

gist.github.com
https://gist.github.com/andersacorn/081f48a4bc41d505be7c294c89b479f3

<body>
        <div id="app">
            <div class="sc-bCfvAP position-relative">
                <div class="sc-dPWrhe bciHYw page-wrapper-container">
                    <div class="page-wrapper">
                        <header class="sc-ksBlkl fyYoiM hero">
                            <div class=" hero--inner hero-home">
                                <div class="sc-kDvujY bEHiFJ mobile-nav">
                                    <button role="button" aria-controls="navMenu" style="display:none" class="accessibility-close">Close Nav</button>
                                </div>

I guess I have a few questions here.

  1. Why is the previous way of doing this working mixing react-loadable and npdev react loadable
  2. What should I do, choose the way I setup before or after experimental branch
  3. What would you recommend to tackle code splitting or reducing my bundles size and properly setting up my server and client app rendering.

Any help would be greatly appreciated.

@andersacorn
Copy link
Author

andersacorn commented May 10, 2023

For this missing content I had to do this. So I think that solves a large portion of the problem, also some other missing content was because I was nested loadable components which I guess doesn't work.

Finally I am wondering about the character entities instead of

tags and other element tags in the view source is it ok?

Solution

One of the first thing that was happening was Meteor.isClient was wrapping some components the used location Object and the window Object both only available on the client side. We were wrapping all of the SignUp Component and the Footer Component in the Meteor.isClient function which was preventing it from being rendered on the server and being passed to the view source html. I tell you web development is confusing.

For example we had to go from this

{Meteor.isClient && (
	<Waypoint
		scrollableAncestor={window}
		onEnter={() => setIsVisible(true)}
		onLeave={() => null}
	>...

→ To this


useEffect(() => {
    // Set isClient to true after the component is mounted on the client-side
    setIsClient(true);

    // Cleanup function to set isClient back to false when the component is unmounted
    return () => {
      setIsClient(false);
    };
  }, []); // Pass an empty dependency array to run this effect only on mount and unmount

return (	
	<Waypoint
		scrollableAncestor={isClient ? window : undefined}
		onEnter={() => setIsVisible(true)}
		onLeave={() => null}
	>...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant