I recently ran into an issue when developing an Azure DevOps extension. I was using the azure-devops-extension-sdk package get data from the Azure DevOps project I was running the extension in. I used the package in multiple files but then encountered this error in my console:

The AzureDevOps SDK is already loaded. Only one version of this module can be loaded in a given document.

This is because, well, the SDK isn't particularly well programmed in my opinion. In the source code of the SDK, we see this:

const global = window as any;
if (global._AzureDevOpsSDKVersion) {
    console.error("The AzureDevOps SDK is already loaded. Only one version of this module can be loaded in a given document.");
}

global._AzureDevOpsSDKVersion = sdkVersion;

This means that we should actually only load the script once. Once for our whole contribution...

Solution

My solution was to create an SDKContext. That's the only place I reference the azure-devops-extension-sdk package. Then I just simply pass the reference to all the consuming components.

import * as SDK from 'azure-devops-extension-sdk';

type SDKContextProps = {
  SDK: typeof SDK;
};

export const SDKContext = React.createContext<SDKContextProps>({} as SDKContextProps);

export const SDKProvider: React.FC = (props) => {
  const context: SDKContextProps = {
    SDK
  };

  const initSDK = async () => {
    await SDK.init();
  };

  React.useEffect(() => {
    initSDK();
  }, []);

  return (
    <SDKContext.Provider value={context}>{props.children}</SDKContext.Provider>
  );
};

Now you wrap your component/app in the SDKProvider:

ReactDOM.render(
  <SDKProvider>
    <MyContribution />
  </SDKProvider>,
  document.getElementById('root')
);

And then you're able to consume the context wherever you like, happy days!

export const MyContribution = () => {
  const { SDK } = React.useContext(ApiContext);

  const getConfiguration = async () => {
    const data = await SDK.getConfiguration();
    // ...
  };

  // ...
};