Skip to content

three.js over cdn using svelte or react

Is there any way of building my svelte or react application in a way, that the three.js module (which I usually import using npm) will be declared as a script tag which will call the module from a CDN? I would like to keep the advantages of a framework but also be able to reduce my final bundle size, since most of my bundle contains three code.

Thank you for your wisdom

Answer

There are two ways to go about your goal of reducing bundle size:

  1. Importing from a CDN (your suggestion)
  2. Code-splitting

Importing from a CDN

To keep semantics of ESModules, you may simply replace your current three.js imports with a URL from an npm CDN, like unpkg:

Pros Cons
No extra configuration needed Slower to load, as browser needs to spin up new connections to access third-party CDN

Asynchronously

<script>
  // App.svelte

  import('https://unpkg.com/[email protected]/build/three.min.js').then(({ default: THREE }) => {
    // your code here
  });
</script>

Synchronously

Note: Importing like this blocks the rest of your script from loading while three.js is downloading, which defeats the purpose of the whole shebang. It’s just here for completeness

<script>
  // App.svelte

  import { default as THREE } from 'https://unpkg.com/[email protected]/build/three.min.js';

  // your code here
</script>

Code-splitting

This method takes advantage of the fact that you’re already using a bundler (probably rollup, vite, or webpack). This answer will focus on rollup as it’s the default used in svelte‘s examples.

Pros Cons
Faster to load, as browser can use existing connections to access first-party resources More complicated to get set up

Asynchronously

In your rollup.config.js file, ensure output.format is set to 'esm' & output.dir is set instead of output.file

// rollup.config.js

import svelte from 'rollup-plugin-svelte';
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import postcss from 'rollup-plugin-postcss';

const production = !process.env.ROLLUP_WATCH;

export default {
  input: 'src/index.js',
  output: {
    sourcemap: !production,
    format: 'esm',
    name: 'app',
    dir: 'public',
  },
  plugins: {
    // your plugins

    svelte({
      compilerOptions: {
        dev: !production,
      },
    }),

    postcss({
      extract: 'bundle.css',
    }),

    resolve({
      browser: true,
      dedupe: ['svelte'],
    }),

    commonjs(),
  }
}
<script>
  // App.svelte

  import('three').then(({ default: THREE }) => {
    // your code here
  });
</script>

Note: There is no synchronous way due to how code-splitting is evaluated at compile time. Plus it doesn’t make much sense to do it like that anyways.