npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

passing-notes-ui

v1.6.0

Published

A middleware for delivering code to the browser during development

Downloads

482

Readme

passing-notes-ui

npm CI Status dependencies Status devDependencies Status

A middleware for delivering code to the browser during development

It leverages native support for ES modules to avoid the overhead in writing large bundles. npm packages are compiled to separate, standalone ESM files and cached by the browser. Application code is built and bundled incrementally.

It uses esbuild under the hood and has limited support for non-standard JavaScript features.

Usage

Install passing-notes-ui by running:

yarn add passing-notes-ui

Given a directory (say, ./ui) containing HTML, CSS, and JS files, we provide several ways to serve them over HTTP.

To quickly start a server that does nothing aside from serving those files:

yarn serve-ui ./ui

To add this functionality as a middleware to an existing app:

import {compose, Logger} from 'passing-notes'
import serveUi from 'passing-notes-ui'

const logger = new Logger()

export default compose(
  serveUi({path: './ui', logger}),
  () => () => ({status: 404})
)

serveUi will compile any JavaScript (.js) files requested by the browser, bundling project source code into a single file. Any npm packages imported via bare specifiers (e.g. 'react') are externalized and bundled separately and ultimately imported via HTTP:

import React from '/npm/react'

JavaScript, CSS, and other files from npm packages can be requested directly via URL:

<link href="/npm/the-new-css-reset/css/reset.css" rel="stylesheet">

Currently, serveUi compiles as needed on each request. In the future, it may instead compile only when files change.

Virtual Files

Optionally, "virtual files" can be specified.

import {compose, Logger} from 'passing-notes'
import serveUi from 'passing-notes-ui'

const logger = new Logger()

export default compose(
  serveUi({
    logger,
    path: './ui',
    files: {
      'index.html': `
        <!doctype html>
        <script type="module" src="/index.js"></script>
      `,
      'index.js': `
        import text from './text.js'
        document.body.textContent = text
      `
    }
  }),
  () => () => ({status: 404})
)

These virtual files are compiled and served as if they were written directly to the file system at the given paths.

Code Splitting

Code splitting is accomplished by having the browser import from different entry points. A bundle is created for each entry point.

If both bundles end up importing the same file, the code in that file is duplicated into both bundles.

To prevent such duplication, "boundaries" can be defined. Bundles will never include files that cross a boundary, leaving them to be imported via HTTP at runtime.

Here's an intended example use case:

import {compose, Logger} from 'passing-notes'
import serveUi from 'passing-notes-ui'

const logger = new Logger()

export default compose(
  serveUi({
    path: './ui',
    boundaries: ['./ui/lib/*'],
    logger
  }),
  () => () => ({status: 404})
)

If the main entry point for the app is at ./ui/index.js and that file imports ./ui/lib/one/index.js and ./ui/lib/two/index.js, three bundles will be created:

  • A bundle including ./ui/lib/one/index.js and any files it imports from within ./ui/lib/one/
  • A bundle including ./ui/lib/two/index.js and any files it imports from within ./ui/lib/two/
  • A bundle including ./ui/index.js and any files it imports that are outside of ./ui/lib

Note that files within ./ui/lib/one/ should only import files from within ./ui/lib/one/. If they import files in outer directories, additional bundles will be created.

These boundaries should correspond to actual boundaries within the codebase where imports that cross are strictly controlled.

Automated Testing

Internally, caches and resources are setup to speed up compilation, which may prevent the process from exiting, especially in automated test. There is a teardown function that will clean up these caches and resources:

import test from 'ava'
import {compose, Logger, startServer, stopServer} from 'passing-notes'
import serveUi, {teardown} from 'passing-notes-ui'

test('serving a UI', async (t) => {
  const logger = new Logger()

  const server = await startServer(
    {port: 10_000},
    compose(
      serveUi({
        logger,
        path: './ui',
        files: {
          'index.html': `
            <!doctype html>
            <script type="module" src="/index.js"></script>
          `,
          'index.js': `
            document.body.textContent = 'Hello World!'
          `
        }
      }),
      () => () => ({status: 404})
    )
  )

  t.teardown(async () => {
    await stopServer(server)
    await teardown('./ui')
  })

  t.pass()
})

teardown() can optionally take in a directory path, only removing resources related to that directory and its descendents.