Skip to content

Instantly share code, notes, and snippets.

@cmorten
Last active January 24, 2021 00:07
Show Gist options
  • Save cmorten/fdffbd9ce521b6436f5fcbce429e752d to your computer and use it in GitHub Desktop.
Save cmorten/fdffbd9ce521b6436f5fcbce429e752d to your computer and use it in GitHub Desktop.
Rollup Browser Dist - "Uncaught TypeError: Cannot read property 'split' of undefined"

Steps to reproduce

This instructions are for reproducing in Deno. Please note that the same code snippet will also reproduce the issue in any modern browser which supports ESM, namely via <script type="module">.

  1. Install Deno following the installation instructions for your platform;

  2. Execute deno run https://gist.githubusercontent.com/cmorten/fdffbd9ce521b6436f5fcbce429e752d/raw/d20036022c30ea8011def64a741aa82155c2afff/mod.ts to run the mod.ts code in this gist.

  3. Observe that the rollup browser distribution throws the error:

    error: Uncaught TypeError: Cannot read property 'split' of undefined
        at Et (https://unpkg.com/rollup@2.38.0/dist/es/rollup.browser.js:11:65715)
        at ja (https://unpkg.com/rollup@2.38.0/dist/es/rollup.browser.js:11:325076)
        at async Qa.loadEntryModule (https://unpkg.com/rollup@2.38.0/dist/es/rollup.browser.js:11:334442)
        at async Promise.all (index 0)

Notes

  • The error is thrown in https://github.com/rollup/rollup/blob/master/browser/path.ts#L61 where there is an attempt to call .split() on the output of paths.shift().

  • The above code has made use of the non-null assertion operator to insist that the output of paths.shift() is non-null and non-undefined, noting that it is a possibility given [].shift() === undefined.

  • We can find the transpiled version of this resolve() function code in https://unpkg.com/rollup@2.38.0/dist/es/rollup.browser.js on line 11 column 65715 which is prettified below for ease:

    function Et(...e) {
      let t = e.shift().split(/[/\\]/);
    
      return (
        e.forEach((e) => {
          if (dt(e)) t = e.split(/[/\\]/);
          else {
            const s = e.split(/[/\\]/);
            for (; "." === s[0] || ".." === s[0]; ) {
              ".." === s.shift() && t.pop();
            }
            t.push.apply(t, s);
          }
        }),
        t.join("/")
      );
    }

    We can see that the result of let resolvedParts = paths.shift()!.split(/[/\\]/); is let t = e.shift().split(/[/\\]/); with no protection against the output of paths.shift() (equiv. e.shift()) being undefined.

  • In https://github.com/rollup/rollup/blob/master/src/utils/resolveId.ts#L31 we observe Rollup's logic for handling the resolution of module ids. Specifically, if plugins return null and either the importer is undefined, the source is an absolute path or the source starts with . (relative path) then Rollup uses some resolve() logic to derive a module id.

  • In this logic, if the importer is undefined then the code calls resolve() without any arguments. In NodeJS this is valid and is the equivalent of calling resolve("./") effectively returning the current working directory.

  • However, in the browser implementation of Rollup we have seen above that the arguments passed to resolve() are used in paths.shift()!.split(/[/\\]/). If no arguments are provided then paths === [] and therefore paths.shift() === undefined, resulting in the error we have seen above as .split() cannot be called on the value of undefined.

  • This will occur for the browser distribution for entry modules and potentially a few other use-cases for external modules.

  • The expected behaviour should be a deliberate and descriptive Rollup thrown error (e.g. entry module does not exist), not an Uncaught TypeError.

// @deno-types="https://unpkg.com/rollup@2.38.0/dist/rollup.d.ts"
import {
rollup,
} from "https://unpkg.com/rollup@2.38.0/dist/es/rollup.browser.js";
const options = {
input: "src/mod.ts",
};
rollup(options);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment