DEV Community

Cover image for Building tools to create full stack Deno applications
Jeroen Peeters
Jeroen Peeters

Posted on • Updated on

Building tools to create full stack Deno applications

The following article is a consolidation of lessons learned while developing and maintaining denopack, a toolset for bundling/minifying code with Deno.

While deno bundle covers most cases where a bundler is needed, popular use cases like tree shaking and minification are rapidly becoming must haves.
This was the base motivation for denopack's development: providing a bundling experience for devs that don't want to use Node and NPM - let alone manage node_modules - while working on a Deno project.

Bundler and minifier

There were several routes to provide a bundling API. Initially, the two most prominent options were to either write it yourself or build on top of deno bundle.

But the Node ecosystem already has incredibly powerful bundlers, which is why the goal shifted to trying to use Rollup - the bundler with which I personally had the most enjoyable experience - in Deno. There were several paths to achieve this:

  1. hope the maintainer(s) also publish to Deno with Denoify
  2. port the package to Deno and maintain it yourself
  3. use the browser version of the package (if it exists)

Out of all these options the last one ultimately made the most sense. The maintainers of Rollup don't include a Denoified version, and porting the package also meant manual maintenance, a headache in and of itself if you're not familiar with the internals.

Using the browser version also meant that CLI and file system access (loading and writing files) had to be manually added, which spawned the denopack CLI and various base plugins to read from cache, disk and remote.

When it came to the topic of minification, it was clear from the get go that we would be using Terser. And here the choice as well was to go with the browser build. The browser version of Terser injects the dependency into globalThis instead of exporting it, so some minor hacky re-export had to be done:

import "https://unpkg.com/source-map@0.7.3/dist/source-map.js";
import "https://unpkg.com/terser@4.8.0/dist/bundle.min.js";

import { AST_Node, MinifyOptions, MinifyOutput } from "./terser.d.ts";

export const minify: (
  files: string | string[] | { [file: string]: string } | AST_Node,
  options?: MinifyOptions
) => MinifyOutput = (globalThis as any).Terser.minify;
Enter fullscreen mode Exit fullscreen mode

Lessons learned

  • Starting from an existing bundler proved to be the right path, since this allowed to potentially use all existing plugins that have already been battle tested.

  • Browser versions are neat little packages that are generally very easy to drop in and use, but do come with major tradeoffs. In many cases it's still the better choice, compared to manually rewriting a package (and keeping that up to date).
    To combat any further tradeoffs and performance cuts, we're making an effort to provide a solution that can automatically polyfill/rewrite key pieces of code to support proper Deno + ESM syntax.

Compiler API's, lockfiles and watch mode

The next step in the process was to handle Typescript support. Luckily, Deno exposes compiling, transpiling and bundling as API's inside of the Deno object (albeit currently hidden behind the --unstable flag).

Both Deno.compile as Deno.transpile made it into separate plugins. In hindsight a necessary choice as certain scenario's require a different approach.

"In thought" courtesy of https://undraw.co

The biggest issue, currently, is the fact that Deno.compile uses a runtime cache that neither denopack or the user has control over... yet. This means that for now straight up compiling doesn't blend well with a watch mode (using Rollup's caching feature), but it does excel in the abstraction of manual file resolution and cache loading.

Deno.transpile, on the other hand, takes the cake when it comes to efficient single file handling. Compile doesn't recognize CSS assets since it actually tries to resolve it as JS/TS code, while Transpile essentially just emits type stripped code. Transpile also transpiles JSX syntax in case you're also working with React.

The additions of lockfile support and incremental compilation/watch mode marked the end of denopack's first major chapter. Watch mode is still considered beta, not just because of the aforementioned incompatibility with Compile, but there's also no access to Rollup's native watcher API, making it less reliable for situations with external assets like CSS and HTML.

Lessons learned

A tl;dr for Deno.compile vs Deno.transpile:

  • use Compile when you're working on a backend application or SPA with no external assets and you don't require watch support. If you're writing f.e. a React SPA, you'd be tied to external styles or a CSS-in-JS solution (I would heavily recommend checking out Otion).
  • use Transpile when you want to quickly iterate on an idea, or you're building a frontend application that needs access to assets like CSS, SVG's, ... in JS/TS context.

Access to non-browser functionality of Rollup is now becoming a must have for future chapters of denopack. This will probably be the most efficient way to allow for bundle manipulation and proper watch/incremental support.

The next chapter: focus on frontend

"React" courtesy of https://undraw.co

Before deciding where to take denopack next, I challenged myself into using it as a static site generator for the documentation site.
This was the first time effort was put into loading/emitting assets, and it very much felt like a smooth experience. The results can be found in the /docs section on Github (link down below).

Ultimately, this meant we would shift focus to a field where I personally am most comfortable: front end. The addition of HTML, CSS and dev server plugins greatly increased options and DX for webapp developers, and made it a breeze to reimplement the create-react-app starter with denopack, which can be found here.

We're currently exploring the addition of PostCSS, Babel, Vue, Svelte and many more. Further down on the menu: the addition of create-xyz behaviour for various frontend (and backend!) frameworks available for Deno, as well as SSR support similar to Next.js and Gatsby.

The goal is to be as inclusive as possible, and to make denopack a one stop shop for our users.

Find denopack

💻 https://denopack.mod.land
🐙 https://github.com/denofn/denopack
🦕 https://deno.land/x/denopack
🥚 https://nest.land/package/denopack

Top comments (0)