Study Docusaurus Code: Static Assets
Published: 2023-10-31 03:32:45

[Docusaurus](https://docusaurus.io/docs) is an open-source static-site generator that builds a single-page application with fast client-side navigation, leveraging the full power of React to make sites interactive. I started learning from the Docusaurus source code a few days ago and would like to share my findings in this blog about [Static Assets](https://docusaurus.io/docs/static-assets).

## Static Assets

Static assets are the non-code files that are directly copied to the build output. They include images, stylesheets, favicons, fonts, etc. By default, as the name implies, it is the `static` folder that used to store these file.

## Docusaurus implementation 

There are three ways showed in the Docusaurus document for the static assets.

1. 
```tsx
import DocusaurusImageUrl from '@site/static/img/docusaurus.png';

<img src={DocusaurusImageUrl} />;
```

2. 
```tsx
<img src={require('@site/static/img/docusaurus.png').default} />
```

3.
```tsx
import useBaseUrl from '@docusaurus/useBaseUrl';

<img src={useBaseUrl('/img/docusaurus.png')} />;
```

The first one is using the import() method which is a dynamic import statement introduced in ECMAScript and is commonly used in modern JavaScript for loading modules on-demand. The second one is using the require() function which is core part of the Node.js runtime. The third one is introduced by Docusaurus. Let's look at it.

### `useBaseUrl` function

We can find how `useBaseUrl` function implemented in the source code `docusaurus/packages/docusaurus/src/client/exports/useBaseUrl.ts`

```tsx
/**
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

import {useCallback} from 'react';
import useDocusaurusContext from './useDocusaurusContext';
import {hasProtocol} from './isInternalUrl';
import type {BaseUrlOptions, BaseUrlUtils} from '@docusaurus/useBaseUrl';

function addBaseUrl(
  siteUrl: string,
  baseUrl: string,
  url: string,
  {forcePrependBaseUrl = false, absolute = false}: BaseUrlOptions = {},
): string {
  // It never makes sense to add base url to a local anchor url, or one with a
  // protocol
  if (!url || url.startsWith('#') || hasProtocol(url)) {
    return url;
  }

  if (forcePrependBaseUrl) {
    return baseUrl + url.replace(/^\//, '');
  }

  // /baseUrl -> /baseUrl/
  // https://github.com/facebook/docusaurus/issues/6315
  if (url === baseUrl.replace(/\/$/, '')) {
    return baseUrl;
  }

  // We should avoid adding the baseurl twice if it's already there
  const shouldAddBaseUrl = !url.startsWith(baseUrl);

  const basePath = shouldAddBaseUrl ? baseUrl + url.replace(/^\//, '') : url;

  return absolute ? siteUrl + basePath : basePath;
}

export function useBaseUrlUtils(): BaseUrlUtils {
  const {
    siteConfig: {baseUrl, url: siteUrl},
  } = useDocusaurusContext();

  const withBaseUrl = useCallback(
    (url: string, options?: BaseUrlOptions) =>
      addBaseUrl(siteUrl, baseUrl, url, options),
    [siteUrl, baseUrl],
  );

  return {
    withBaseUrl,
  };
}

export default function useBaseUrl(
  url: string,
  options: BaseUrlOptions = {},
): string {
  const {withBaseUrl} = useBaseUrlUtils();
  return withBaseUrl(url, options);
}

```

The `useBaseUrlUtils` function provides a utility to use the `addBaseUrl` function with the site's `baseUrl` and `siteUrl`. It returns an object with a `withBaseUrl` function that can be used to generate absolute or relative URLs. The `useDocusaurusContext` function to access Docusaurus site configuration, `hasProtocol` to check if a URL has a protocol (like "http" or "https"), and types related to `BaseUrlOptions` and `BaseUrlUtils`. The main `useBaseUrl` function takes a `url` and optional `options` and delegates the work to the `withBaseUrl` function provided by `useBaseUrlUtils`.

### Other insights from the code
1. local anchor url
```tsx
  // It never makes sense to add base url to a local anchor url, or one with a
  // protocol
  if (!url || url.startsWith('#') || hasProtocol(url)) {
    return url;
  }
```
I realized I had been neglecting this special case


### Unit test
we can see the unit test code from `docusaurus/packages/docusaurus/src/client/exports/__tests__/useBaseUrl.test.tsx`

```tsx
/**
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

import React from 'react';
import {renderHook} from '@testing-library/react-hooks';
import useBaseUrl, {useBaseUrlUtils} from '../useBaseUrl';
import {Context} from '../../docusaurusContext';
import type {DocusaurusContext} from '@docusaurus/types';
import type {BaseUrlOptions} from '@docusaurus/useBaseUrl';

const forcePrepend = {forcePrependBaseUrl: true};

describe('useBaseUrl', () => {
  const createUseBaseUrlMock =
    (context: DocusaurusContext) => (url: string, options?: BaseUrlOptions) =>
      renderHook(() => useBaseUrl(url, options), {
        wrapper: ({children}) => (
          <Context.Provider value={context}>{children}</Context.Provider>
        ),
      }).result.current;
  it('works with empty base URL', () => {
    const mockUseBaseUrl = createUseBaseUrlMock({
      siteConfig: {
        baseUrl: '/',
        url: 'https://docusaurus.io',
      },
    } as DocusaurusContext);

    expect(mockUseBaseUrl('hello')).toBe('/hello');
    expect(mockUseBaseUrl('/hello')).toBe('/hello');
    expect(mockUseBaseUrl('hello/')).toBe('/hello/');
    expect(mockUseBaseUrl('/hello/')).toBe('/hello/');
    expect(mockUseBaseUrl('hello/foo')).toBe('/hello/foo');
    expect(mockUseBaseUrl('/hello/foo')).toBe('/hello/foo');
    expect(mockUseBaseUrl('hello/foo/')).toBe('/hello/foo/');
    expect(mockUseBaseUrl('/hello/foo/')).toBe('/hello/foo/');
    expect(mockUseBaseUrl('https://github.com')).toBe('https://github.com');
    expect(mockUseBaseUrl('//reactjs.org')).toBe('//reactjs.org');
    expect(mockUseBaseUrl('//reactjs.org', forcePrepend)).toBe('//reactjs.org');
    expect(mockUseBaseUrl('https://site.com', forcePrepend)).toBe(
      'https://site.com',
    );
    expect(mockUseBaseUrl('/hello/foo', {absolute: true})).toBe(
      'https://docusaurus.io/hello/foo',
    );
    expect(mockUseBaseUrl('#hello')).toBe('#hello');
  });

  it('works with non-empty base URL', () => {
    const mockUseBaseUrl = createUseBaseUrlMock({
      siteConfig: {
        baseUrl: '/docusaurus/',
        url: 'https://docusaurus.io',
      },
    } as DocusaurusContext);

    expect(mockUseBaseUrl('')).toBe('');
    expect(mockUseBaseUrl('hello')).toBe('/docusaurus/hello');
    expect(mockUseBaseUrl('/hello')).toBe('/docusaurus/hello');
    expect(mockUseBaseUrl('hello/')).toBe('/docusaurus/hello/');
    expect(mockUseBaseUrl('/hello/')).toBe('/docusaurus/hello/');
    expect(mockUseBaseUrl('hello/foo')).toBe('/docusaurus/hello/foo');
    expect(mockUseBaseUrl('/hello/foo')).toBe('/docusaurus/hello/foo');
    expect(mockUseBaseUrl('hello/foo/')).toBe('/docusaurus/hello/foo/');
    expect(mockUseBaseUrl('/hello/foo/')).toBe('/docusaurus/hello/foo/');
    expect(mockUseBaseUrl('https://github.com')).toBe('https://github.com');
    expect(mockUseBaseUrl('//reactjs.org')).toBe('//reactjs.org');
    expect(mockUseBaseUrl('//reactjs.org', forcePrepend)).toBe('//reactjs.org');
    expect(mockUseBaseUrl('/hello', forcePrepend)).toBe('/docusaurus/hello');
    expect(mockUseBaseUrl('https://site.com', forcePrepend)).toBe(
      'https://site.com',
    );
    expect(mockUseBaseUrl('/hello/foo', {absolute: true})).toBe(
      'https://docusaurus.io/docusaurus/hello/foo',
    );
    expect(mockUseBaseUrl('/docusaurus')).toBe('/docusaurus/');
    expect(mockUseBaseUrl('/docusaurus/')).toBe('/docusaurus/');
    expect(mockUseBaseUrl('/docusaurus/hello')).toBe('/docusaurus/hello');
    expect(mockUseBaseUrl('#hello')).toBe('#hello');
  });
});

describe('useBaseUrlUtils().withBaseUrl()', () => {
  const mockUseBaseUrlUtils = (context: DocusaurusContext) =>
    renderHook(() => useBaseUrlUtils(), {
      wrapper: ({children}) => (
        <Context.Provider value={context}>{children}</Context.Provider>
      ),
    }).result.current;
  it('empty base URL', () => {
    const {withBaseUrl} = mockUseBaseUrlUtils({
      siteConfig: {
        baseUrl: '/',
        url: 'https://docusaurus.io',
      },
    } as DocusaurusContext);

    expect(withBaseUrl('hello')).toBe('/hello');
    expect(withBaseUrl('/hello')).toBe('/hello');
    expect(withBaseUrl('hello/')).toBe('/hello/');
    expect(withBaseUrl('/hello/')).toBe('/hello/');
    expect(withBaseUrl('hello/foo')).toBe('/hello/foo');
    expect(withBaseUrl('/hello/foo')).toBe('/hello/foo');
    expect(withBaseUrl('hello/foo/')).toBe('/hello/foo/');
    expect(withBaseUrl('/hello/foo/')).toBe('/hello/foo/');
    expect(withBaseUrl('https://github.com')).toBe('https://github.com');
    expect(withBaseUrl('//reactjs.org')).toBe('//reactjs.org');
    expect(withBaseUrl('//reactjs.org', forcePrepend)).toBe('//reactjs.org');
    expect(withBaseUrl('https://site.com', forcePrepend)).toBe(
      'https://site.com',
    );
    expect(withBaseUrl('/hello/foo', {absolute: true})).toBe(
      'https://docusaurus.io/hello/foo',
    );
    expect(withBaseUrl('#hello')).toBe('#hello');
  });

  it('non-empty base URL', () => {
    const {withBaseUrl} = mockUseBaseUrlUtils({
      siteConfig: {
        baseUrl: '/docusaurus/',
        url: 'https://docusaurus.io',
      },
    } as DocusaurusContext);

    expect(withBaseUrl('hello')).toBe('/docusaurus/hello');
    expect(withBaseUrl('/hello')).toBe('/docusaurus/hello');
    expect(withBaseUrl('hello/')).toBe('/docusaurus/hello/');
    expect(withBaseUrl('/hello/')).toBe('/docusaurus/hello/');
    expect(withBaseUrl('hello/foo')).toBe('/docusaurus/hello/foo');
    expect(withBaseUrl('/hello/foo')).toBe('/docusaurus/hello/foo');
    expect(withBaseUrl('hello/foo/')).toBe('/docusaurus/hello/foo/');
    expect(withBaseUrl('/hello/foo/')).toBe('/docusaurus/hello/foo/');
    expect(withBaseUrl('https://github.com')).toBe('https://github.com');
    expect(withBaseUrl('//reactjs.org')).toBe('//reactjs.org');
    expect(withBaseUrl('//reactjs.org', forcePrepend)).toBe('//reactjs.org');
    expect(withBaseUrl('https://site.com', forcePrepend)).toBe(
      'https://site.com',
    );
    expect(withBaseUrl('/hello/foo', {absolute: true})).toBe(
      'https://docusaurus.io/docusaurus/hello/foo',
    );
    expect(withBaseUrl('/docusaurus')).toBe('/docusaurus/');
    expect(withBaseUrl('/docusaurus/')).toBe('/docusaurus/');
    expect(withBaseUrl('/docusaurus/hello')).toBe('/docusaurus/hello');
    expect(withBaseUrl('#hello')).toBe('#hello');
  });
});

```

From the test code, I find the well written test covered  lots of cases. For me, it serves as an excellent TDD example to emulate. Such as the handling double slashes and the local anchor url situations.

```tsx
    expect(withBaseUrl('/hello')).toBe('/docusaurus/hello');
```

```tsx
    expect(withBaseUrl('//reactjs.org')).toBe('//reactjs.org');
```

```tsx
    expect(withBaseUrl('#hello')).toBe('#hello');
```

## Conclusion 
I've gained a deeper understanding of the inner workings of Docusaurus's `useBaseUrl` function. The comprehensive unit tests have underscored the reliability of the codebase. The special cases have proven to be very helpful to me in the development of similar features.