javascripttypescriptweb-componentlitunpkg

Using the Lit javascript module over a CDN without the Lit dependencies bundled in?


Is there a way to serve a the Lit element built from typescript without the Lit dependencies being bundled in with the web component?

For example we can serve the rollup bundle as described here from unpkg, but I was wondering if there is a way to load the Lit dependencies and the element separately over a CDN and still have it work.

So from the linked example instead of doing this:

  "main": "fs-gist.bundle.js",
  "module": "fs-gist.js",
  "types": "fs-gist.d.ts",
  "type": "module",

We could do:

  "main": "fs-gist.js",
  "module": "fs-gist.js",
  "types": "fs-gist.d.ts",
  "type": "module",

And then when we want to use the element, we would load the Lit dependencies first ... somehow ...

    <script
      type="module"
      src="https://unpkg.com/@lit/lit"></script>

    <script
      type="module"
      src="https://unpkg.com/@fireflysemantics/fs-gist"></script>

This way if we are loading multiple elements, they would all share the same version of lit.


Solution

  • In general, there are in total 3 options to achieve this:

    1. Use the traditional jQuery style CDN import where the entire library is exposed using some global object.
    2. Use the ESM mode.
    3. Use the ESM aware CDN like ESM.sh like Augustine mentioned in the comment.

    In first option, we rely on bundler like Webpack to exclude certain dependency by telling it that this import shall be available as a certain global variable. We do that often in React where react exposes React and ReactDOM as global variables. However, this first option is not possible with Lit as it only provides prebundled-ESM library. It means no global variable!

    The third option is a ready-to-use solution but it involves quite some number of changes on how you ship your application or library. This is the future of loading applications in browser. But, for large application, you may end up making hundreds of network requests if you end up using many external libraries (Each new import is as good as a network request).

    This is where second option comes in. In this way, you bundle some of the dependencies while relying on ESM loading for some dependencies. This also assumes that you are using some form of bundler or compiler.

    To externalize some library, you need to use:

    For this example, I am using Vite.

    Step 1 Create a ESM file (optionally with TypeScript):

    // main.ts
    
    import { LitElement } from 'https://lit';
    
    console.log(LitElement);
    

    The reason for using https:// is that when Vite looks at it, it automatically ignores these imports from the bundle. Using any other name will force Vite to bundle it. (Note: Vite doesn't yet understand import-maps).

    Step 2: Define import map

    In your HTML file which is the entry point for your application, configure the import map:

    <html lang="en">
      <head>
        <script type="importmap">
          {
            "imports": {
              "https://lit": "https://cdn.jsdelivr.net/gh/lit/dist@3.1.4/all/lit-all.min.js"
            }
          }
        </script>
      </head>
      <body>
        <div id="app"></div>
        <script type="module" src="./src/main2.js"></script>
      </body>
    </html>
    

    Here, whenever browser looks at https://lit, then it shall fetch the required dependency from the given CDN path. Note that, this file lit-all.min.js is pre-bundled by the Lit team and so in includes everything from lit-element, lit-html and @lit/reactive-element. You don't need to import them again.

    Step 3: Fix TypeScript

    {
      "compilerOptions": {
        "baseUrl": ".",
        "module": "ESNext",
        "moduleResolution": "Bundler",
    
        "paths": {
          "https://lit": ["./node_modules/lit/index.d.ts"],
        }
      }
    }
    

    With this configuration, autocomplete/intellisense should start working for you. If you need to achieve this in plain JS files too, then you need to enable checkJs flag as well.

    Step 4: Produce ESM output

    In case of Vite, ensure that your target output is set to either modules or esnext. If you are bundling a standalone library, it is probably already configured for you. Similar setting should be available for Webpack 5+.