Mastro 👨‍🍳 Docs

Search results

Blog Community GitHub   Stoat Chat   Discord   Bluesky

Install NPM packages without a build system

Instead of writing all code yourself, it’s sometimes useful to install packages from NPM (or JSR). Make sure to take a bit of time to evaluate a package before adding it as a dependency to your project. Code quality, size, security, and long-term maintenance outlook varies a lot between different packages. npmx is a nice browser for NPM that highlights some of these aspects.

Client-side and server-side JavaScript is separated on purpose in Mastro, because the former needs to be downloaded to the browser of your users, while the latter runs in a runtime like Deno or Node.js, which is an environment you control directly.

On the server

As an example, you might want to use the markdown-it package from NPM, instead of the @mastrojs/markdown package to generate HTML from your markdown files on the server.

To install the package, use your package manager as follows:

deno add npm:markdown-it
Copied!

which will add this line to your deno.json file:

deno.json
{
  "name": "...",
  "imports": {
    "markdown-it": "npm:markdown-it@14.1.0"
  }
}
Copied!
pnpm add markdown-it
Copied!

which will not only change the package.json file, but also run pnpm install.

package.json
{
  "name": "...",
  "dependencies": {
    "markdown-it": "markdown-it@14.1.0"
  }
}
Copied!
bun add markdown-it
Copied!

which will not only change the package.json file, but also run bun install.

package.json
{
  "name": "...",
  "dependencies": {
    "markdown-it": "markdown-it@14.1.0"
  }
}
Copied!

When generating a static site with the Mastro VS Code for the Web extension, Mastro will look for an import_map.json file to resolve the markdown-it specifier. Using esm.sh:

import_map.json
{
  "imports": {
    "markdown-it": "https://esm.sh/markdown-it@14.1.0"
  }
}
Copied!

Then you’ll be able to import the package in any server-side JavaScript file:

import markdownIt from "markdown-it";
Copied!

On the client

It’s important to remember that the server’s deno.json or package.json file (the one in the section above) will not be loaded into the browser – and unless you use a bundler, neither will any packages it contains. This is a good, because packages intended to be run on the server are often very big and would slow down your website if all your website visitors had to download them.

Installing client-side packages without a build system

We’ll look at setting up a bundler with Mastro later. But as long as you don’t need one for your own files, it’s simplest to use pre-bundled versions of third-party packages – either by using a CDN or self-hosting (aka vendoring).

CDN

If the library you want to add advertises a CDN URL in their installation instructions (usually a URL starting with https://cdn.jsdelivr.net or https://unpkg.com), then you should use that. If they’re only advertising an NPM package, you can use the esm.sh CDN, which has a built-in bundler. To enable that, add ?bundle at the end of the esm.sh URL:

<!doctype html>
<html>
  <head>
    <script type="importmap">
    {
      "imports": {
        "markdown-it": "https://esm.sh/markdown-it@14.1.0?bundle"
      }
    }
    </script>
Copied!

Self-hosting / vendoring

If you want to self-host your client-side dependencies instead of relying on a third-party CDN, you can download pre-bundled files and add them to your project repository. To get hold of the right files, you either:

  1. Download the files from a CDN or project release page: Find the right URL (see above) and open it in your browser. Then select File -> Save Page As in the menu.

  2. Or find the files in a dist/ folder or similar right inside the NPM package (for the exact location, open the pacakge.json of the library and look at the exports fields). You can find the files inside any NPM package in the Code tab on npmjs.com or npmx.dev. For the markdown-it example, this would be the dist/markdown-it.min.js file on this page. However, this approach won’t work if the files in the dist/ folder don’t include transitive dependencies (packages this package depends on). In that case, it’s best to use approach one above.

After downloading the file, add it somewhere in your Mastro project’s routes/ folder, e.g. routes/vendor/markdown-it/markdown-it.min.js. You can choose the exact folder names and structure, but vendor is a common name when “vendoring” – i.e. copying another project’s code into your project. Then you can load it like:

<!doctype html>
<html>
  <head>
    <script type="importmap">
    {
      "imports": {
        "markdown-it": "/vendor/markdown-it/markdown-it.min.js"
      }
    }
    </script>
Copied!

Instead of downloading the files manually, you can also use a script like unpm or nudeps to do so.

Import maps

You may be wondering what the <script type="importmap"> in the above examples is. It’s a browser-native way to assign a centralized identifier to a JavaScript module URL. While you could also use the URL of any ESM module as part of an import statement, if you’ll use the library in more than one file, it’s best to centralize it in an import map. Then, there’s only one place to change the version number, when the time comes to update it.

You can see a full example of a client-side importmap in the guide.