SoftTimur
SoftTimur

Reputation: 5540

Undertake operations upstream to Docusaurus and ensure the correct order of loading files

I'm taking over a website by V2 Docusaurus.

One particularity of our website is that we need to load office.js and css-vars-ponyfill.min.js, and do some operations in the very beginning. So the previous developer decided to use the following approach.

In every .mdx.md page, he wrapped the content by a component MainWrapper:

<MainWrapper>
    ... ...
    Real content
    ... ...
</MainWrapper>

MainWrapper/index.js is defined as follows

function MainWrapper(props) {
    return (<>
        <Head>
            <script defer 
                src="https://www.10studio.tech/lib/patches.js" 
                onload="(function(){console.log('patches.js fully loaded MainWrapper')}).call(this)" >
            </script>
        </Head>
        <CssvarsWrapper></CssvarsWrapper>
        <OfficejsWrapper></OfficejsWrapper>
        {props.children}
    </>)
}

export default MainWrapper;

CssvarsWrapper/index.js is defined as follows

function CssvarsWrapper(props) {
    return (<>
        <Head>
            <script defer 
                src="https://www.10studio.tech/lib/patches.js"
                onload="(function(){console.log('patches.js fully loaded in CssvarsWrapper')}).call(this)">
            </script>
            {console.log("CssvarsWrapper > index.js > CssvarsWrapper")}
            <script defer
                src="https://unpkg.com/css-vars-ponyfill@2/dist/css-vars-ponyfill.min.js"
                onload="(function(){console.log('css-vars-ponyfill.min.js fully loaded in CssvarsWrapper'); onCssVarsPonyfillLoad()}).call(this) ">
            </script>
        </Head>
        {props.children}
    </>)
}

OfficejsWrapper/index.js is defined as follows

function OfficeWrapper(props) {
    return (
        <>
            <Head>
                <script defer
                    src="https://www.10studio.tech/lib/patches.js"
                    onload="(function(){console.log('patches.js fully loaded in OfficeWrapper')}).call(this)">
                </script>
                {console.log("OfficejsWrapper > index.js > OfficeWrapper")}
                <script defer
                    src='https://appsforoffice.microsoft.com/lib/1/hosted/office.js'
                    onload="(function(){console.log('office.js fully loaded in OfficeWrapper'); onOfficejsLoad()}).call(this) ">
                </script>
            </Head>
            {props.children}
        </>
    )
}

lib/Patches.js contains real operations:

console.log("in patches")
... ...

function onCssVarsPonyfillLoad() {
    console.log("patches.js > onCssVarsPonyfillLoad()")
    cssVars({
        onlyLegacy: false,
        onComplete: function (cssText, styleElms, cssVariables, benchmark) {
        }
    });
}

function onOfficejsLoad() {
    Office.onReady(function () {
        console.log("office.js is ready.");
        patch();
    })
}

However, my test showed that this implementation cannot always respect a correct order of loading of files, regardless of the defer tag. For example, as the following screenshot shows, css-vars-ponyfill.min.js fully loaded in CssvarsWrapper and office.js fully loaded in OfficeWrapper were before patches.js fully loaded, as a consequence onCssVarsPonyfillLoad and onOfficejsLoad were not ready when they were called.

Actually, we should ensure that patches.js is always loaded before css-vars-ponyfill.min.js and office.js. Does anyone know how to ensure that?

Additionally, is this approach (i.e., wrapping a component around content of every page to do some operations upstream) correct?

enter image description here

Upvotes: 1

Views: 230

Answers (2)

Christos Lytras
Christos Lytras

Reputation: 37318

The way the previous developer decided to implement this loads scripts multiple times and particular it loads patches.js more than once.

I suggest you try to ditch this implementation and use Docusaurus scripts-array inside docusaurus.config.js to define these scripts and use defer on office.js and css-vars-ponyfill.min.js and define the onload script for each script there. That's the proper way to load external (and internal like patches.js) scripts in Docusaurus.

Set the scripts inside docusaurus.config.js:

module.exports = {
  ...
  scripts: [
    {
      src: '/lib/patches.js'
    },
    {
      src: 'https://appsforoffice.microsoft.com/lib/1/hosted/office.js',
      defer: true,
      onload: "(() => { console.log('office.js loaded'); onOfficejsLoad(); })()"
    },
    {
      src: 'https://unpkg.com/css-vars-ponyfill@2/dist/css-vars-ponyfill.min.js',
      defer: true,
      onload: "(() => { console.log('css-vars-ponyfill.min.js loaded'); onCssVarsPonyfillLoad(); })()"
    }
  ],
}

We use defer on these two scripts and that means that the scripts will be executed and loaded after the document has been parsed, but before firing DOMContentLoaded.

Trying this on a local Docusaurus project I got the scripts loading on the expected order every single time after clearing the cache:

in patches
office.js loaded
patches.js > onCssVarsPonyfillLoad()
css-vars-ponyfill.min.js loaded
patches.js > onCssVarsPonyfillLoad()
cssVars:onComplete
office.js is ready.

using this patches.js file:

console.log("in patches")

function onCssVarsPonyfillLoad() {
  console.log("patches.js > onCssVarsPonyfillLoad()");

  cssVars({
    onlyLegacy: false,
    onComplete: function (cssText, styleElms, cssVariables, benchmark) {
      console.log('cssVars:onComplete');
    }
  });
}

function onOfficejsLoad() {
  console.log("patches.js > onCssVarsPonyfillLoad()");

  Office.onReady(function () {
    console.log("office.js is ready.");
    // patch();
  })
}

Upvotes: 1

Saurabh Sharma
Saurabh Sharma

Reputation: 2472

TLDR;

You need to add to add defer to your <script> tag if you want your scripts to be executed in order.

You can read more about it here: https://www.w3schools.com/tags/att_script_defer.asp


A bit longer version

I want to highlight few things over your implementation though

You are working in a virtual DOM env, your components might mount/unmount depending over many use cases. I would never recommend any one loading files like this:

<script 
    src="/lib/patches.js" 
    onload="(function(){console.log('patches.js fully loaded 1')}).call(this)">
</script>

Instead I would recommend defining the methods in a single file and export it so I can just use any method I want to use within my app. This will not just kill the loading of file each time your component is mounted but also improve the efficiency and execution.

This approach is rather called modular approach. Check this out https://stackoverflow.com/a/49616670/1681154 and if you want to read more about modular approach, I found a good explanation here: https://javascript.info/import-export.

If you can't afford to break down the modules but rather want to use the approach you are currently using, you need to use defer to ensure script is executed in the same order it is defined.

Upvotes: 0

Related Questions