0

I have spfx extension Application customizer which has very long loading time takes like 4.37 s, I was wondering how to improve the loading time.

Edit- I tried loading the react component in domElement but same result

enter image description here

.ts

    import * as React from 'react';
import * as ReactDom from 'react-dom';
import { sp } from "@pnp/sp";
import { override } from '@microsoft/decorators';
import * as $ from 'jquery';
import { Log, SPEventArgs } from '@microsoft/sp-core-library';
import styles from "./NavigationDeApplicationCustomizer.module.scss";
import {
  PlaceholderContent,
  PlaceholderName,
  BaseApplicationCustomizer
} from '@microsoft/sp-application-base';
import GlobalNav from "./Components/GlobalNav";
import { IGlobalNavProps } from "./Components/IGlobalNavProps";
import ReactFooter, { IReactFooterProps } from "./ReactFooter";
import { IGobalNavState } from "./Components/IGlobalNavState";
import { Dialog } from '@microsoft/sp-dialog';
import { SPHttpClient, SPHttpClientConfiguration, SPHttpClientResponse, ODataVersion, ISPHttpClientConfiguration } from '@microsoft/sp-http';

import * as strings from 'NavigationDeApplicationCustomizerStrings';

import * as ReactDOM from 'react-dom';

const LOG_SOURCE: string = 'NavigationDeApplicationCustomizer';



/**
 * If your command set uses the ClientSideComponentProperties JSON input,
 * it will be deserialized into the BaseExtension.properties object.
 * You can define an interface to describe it.
 */
export interface INavigationDeApplicationCustomizerProperties {
  // This is an example; replace with your own property
  termGroupId: string;
  termSetId: string;
  Bottom: string;
}


/** A Custom Action which can be run during execution of a Client Side Application */
export default class NavigationDeApplicationCustomizer
  extends BaseApplicationCustomizer<INavigationDeApplicationCustomizerProperties> {
    private static headerPlaceholder: PlaceholderContent;
    private _topPlaceholder: PlaceholderContent | undefined;
    private _bottomPlaceholder: PlaceholderContent | undefined;

    @override
    public onInit(): Promise<void> {
      Log.info(LOG_SOURCE, `Initialized ${strings.Title}`);
      localStorage.clear();
     super.onInit().then(_ => {
        console.log("super oninit called");
        sp.setup({
          spfxContext: this.context,
          defaultCachingTimeoutSeconds: 30,
          globalCacheDisable: false
        });
      }).then((_) => {
        console.log("Sp Initilize");
        this.context.placeholderProvider.changedEvent.add(this, this._renderPlaceHolders);
        this.context.application.navigatedEvent.add(this, () => {
          Log.info(LOG_SOURCE, 'Navigated event');
          this._renderPlaceHolders();
        });
    
        this._renderPlaceHolders();
      });
  
      return Promise.resolve();
    }
    
    private _renderPlaceHolders(): void {
      console.log("NavigationDeApplicationCustomizer._renderPlaceHolders()");
      console.log(
        "Available placeholders: ",
        this.context.placeholderProvider.placeholderNames
          .map(name => PlaceholderName[name])
          .join(", ")
      );

      // Handling the top placeholder
    if (!this._topPlaceholder) {
      this._topPlaceholder = this.context.placeholderProvider.tryCreateContent(
        PlaceholderName.Top,
        { onDispose: this._onDispose }
      );

      // The extension should not assume that the expected placeholder is available.
      if (!this._topPlaceholder) {
        console.error("The expected placeholder (Top) was not found.");
        return;
      }
      if (this.properties) {
        // Add refrence of react component to this file.
       /* const element: React.ReactElement<IGlobalNavProps> = React.createElement(
          GlobalNav,
          {
            termGroupId: this.properties.termGroupId,
            termSetId: this.properties.termSetId,
          }
        );
        ReactDom.render(element, this._topPlaceholder.domElement);*/
        
      
      if (this._topPlaceholder.domElement) {
        this._topPlaceholder.domElement.innerHTML = ``;
        const element: React.ReactElement<IGlobalNavProps> = React.createElement(
          GlobalNav,
          {
            termGroupId: this.properties.termGroupId,
            termSetId: this.properties.termSetId,
          }
        );
        ReactDom.render(element, this._topPlaceholder.domElement);
        
     
      }
   } 
} 
  // Handling the bottom placeholder
  if (!this._bottomPlaceholder) {  
    this._bottomPlaceholder =  
      this.context.placeholderProvider.tryCreateContent(  
        PlaceholderName.Bottom,  
        { onDispose: this._onDispose });  
    
    // The extension should not assume that the expected placeholder is available.  
    if (!this._bottomPlaceholder) {  
      console.error('The expected placeholder (Bottom) was not found.');  
      return;  
    }  
   
    const elem: React.ReactElement<IReactFooterProps> = React.createElement(ReactFooter);  
    ReactDOM.render(elem, this._bottomPlaceholder.domElement); 
  }
 //  if (this._bottomPlaceholder.domElement) {
 //     this._bottomPlaceholder.domElement.innerHTML = `
    //  <div class="${styles.app}">
      
  //    <span class="${styles.fTab}">Open Footer</span>
    //<footer>
    //</footer> 
 // </div>`;
 //    }

} 
  private _onDispose(): void {
    console.log('[NavigationDeApplicationCustomizer._onDispose] Disposed custom top and bottom placeholders.');
  } 
   
}

.tsx

    import * as React from 'react';
import { IGlobalNavProps } from "./IGlobalNavProps";
import { IGobalNavState } from "./IGlobalNavState";
import { Log } from '@microsoft/sp-core-library';
import { sp } from "@pnp/sp";
import "@pnp/sp/taxonomy";
import { dateAdd, PnPClientStorage } from "@pnp/common";
import { IOrderedTermInfo } from '@pnp/sp/taxonomy';

const myKey: string = "navigationElements";
import { IContextualMenuItem, 
 } from 'office-ui-fabric-react/lib/ContextualMenu';

import { ContextualMenuItemType, IButtonStyles, Icon, IconButton, IIconProps } from '@fluentui/react';
import { createTheme, ITheme } from 'office-ui-fabric-react/lib/Styling';
import { CommandBar, ICommandBarStyleProps } from "@fluentui/react/lib/CommandBar";
import styles from "../NavigationDeApplicationCustomizer.module.scss";



const theme: ITheme = createTheme({
    semanticColors: {
        bodyBackground: "#27348a",
        bodyText: "#fff"
        
    }
});
const CommandBarProps: ICommandBarStyleProps = {
    theme: theme,
    
};

const buttonStyle: IButtonStyles = {
    root: {
        '&:hover': {
            backgroundColor: "darken(someColor, 0.3) !important",
        },
        backgroundColor: "#27348a",
        color: "#fff",
        justifyContent:"center !important",
        letterSpacing: "1px",
        
        
    },
    menuIcon: {
        color: "#fff",
        justifyContent: "center !important",
        letterSpacing:"1px",
        display:"flex"
    }
};



export default class GlobalNav extends React.Component<IGlobalNavProps, IGobalNavState> {

private store = new PnPClientStorage();
    constructor(props: IGlobalNavProps) {
        super(props);
        this.state = {
            
            loading: false,
            terms: []
        };
    }


    public componentDidMount() {
        this.setState({}, async () => {
            // this portion is responsible for getting terms from term store
            const cachedTermInfo = await this.store.local.getOrPut(myKey, () => {
                return sp.termStore.groups.getById(this.props.termGroupId).sets.getById(this.props.termSetId).getAllChildrenAsOrderedTree({ retrieveProperties: true });
            }, dateAdd(new Date(), "minute", 10));
            if (cachedTermInfo.length > 0) {
                console.log(cachedTermInfo);
                this.setState({ terms: cachedTermInfo });
            }
        });
    }

    private menuItems(menuItem: any, itemType: ContextualMenuItemType) {
        return ({
            
            key: menuItem.id,
            name: menuItem.defaultLabel,
            itemType: itemType,
            href: menuItem.children.length == 0 ?
                ((menuItem.localProperties != undefined && menuItem.localProperties[0].properties !== undefined && menuItem.localProperties[0].properties.length > 0) ?
                    menuItem.localProperties[0].properties.filter(x => x.key == "_Sys_Nav_SimpleLinkUrl")[0].value !== undefined ? menuItem.localProperties[0].properties.filter(x => x.key == "_Sys_Nav_SimpleLinkUrl")[0].value : null
                    : null)
                : null,
            subMenuProps: menuItem.children.length > 0 ?
                { items: menuItem.children.map((i) => { return (this.menuItems(i, ContextualMenuItemType.Normal)); }) }
                : null,
            isSubMenu: itemType != ContextualMenuItemType.Header,
            buttonStyles: buttonStyle
        });
    }
    
    public render(): React.ReactElement<IGlobalNavProps> {
        
        var commandBarItems: any[] = [];
        if (this.state.terms.length > 0) {
            commandBarItems = this.state.terms.map((i) => {
                return (this.menuItems(i, ContextualMenuItemType.Header));
            });
        }
        return (
            <>
                {
                    this.state.terms.length > 0 &&
                    
                    <div className={`ms-bgColor-neutralLighter ms-fontColor-white ${styles.app}`}>
                    <div className={`ms-bgColor-neutralLighter ms-fontColor-white ${styles.top}`}>
                    
                            <img src="https://transparent.png" className={styles.headerlogo}/>
                        <CommandBar
                            className={styles.commandBar}
                            {...CommandBarProps}
                            items={commandBarItems}
                           
                        />
                         </div>
                    </div>    
                }
            </>
        );
    }
}
4
  • That isn't the load time. That's the build time. Paul's answer about using spfx fast serve is the best way to address that. Though, there's probably some architecture issues that are leading to the slowdown - especially if this is only an app customizer solution. If you are also having slow load times, I recommend opening a new question and providing details about your architecture (packages, imports, framework, what you're doing in the init, etc.) May 16 at 12:37
  • I have updated the question with the code.
    – striker92
    May 17 at 8:44
  • What does the line require('myScript'); do?
    – Paul Lucas
    May 18 at 2:01
  • @PaulLucas Nothing I have removed myscript
    – striker92
    May 19 at 8:07

1 Answer 1

2

Have a look at spfx-fast-serve, which can speed up your build times significantly during development.

https://github.com/s-KaiNet/spfx-fast-serve

With regards to troubleshooting loading times, you would want to try to distinguish between length of time taken to load the built javascript files over the internet vs the time taken for the browser to initialize and run the module.

Your browser dev tools (Ctrl+Shift+i) will be useful to looking at these issues, along with the SharePoint Developer Dashboard (Ctrl+F12)

See also here for info on the developer dashboard

https://learn.microsoft.com/en-us/sharepoint/dev/spfx/use-developer-dashboard

4
  • But what about when It is deployed ? I have the same issue when deployed
    – striker92
    May 16 at 6:36
  • 1
    If it takes 4.5s to load when you have deployed it to the app catalog there is something in your code that is taking a long time, can you share the code? May 16 at 6:43
  • @RobertLindgren I have updated the main question with the code.
    – striker92
    May 17 at 8:41
  • @striker92 I've updated my answer
    – Paul Lucas
    May 18 at 1:56

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.