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

@chocolateboy/uncommonjs

v3.2.1

Published

A minimum viable shim for module.exports

Downloads

365

Readme

UnCommonJS

Build Status NPM Version

NAME

UnCommonJS - a minimum viable shim for module.exports

FEATURES

  • module.exports
  • exports
  • pluggable require
  • supports live exports (ESM emulation)
  • tiny (< 700 B minified + gzipped)
  • no dependencies
  • fully typed (TypeScript)
  • CDN builds - jsDelivr, unpkg

INSTALLATION

$ npm install @chocolateboy/uncommonjs

USAGE

// ==UserScript==
// @name          My Userscript
// @description   A userscript which uses some CommonJS modules
// @include       https://www.example.com/*
// @require       https://unpkg.com/@chocolateboy/[email protected]
// @require       https://cdn.jsdelivr.net/npm/[email protected]
// @require       https://cdn.jsdelivr.net/npm/[email protected]
// ==/UserScript==

console.log(module.exported) // { once: ..., sha1: ..., sha256: ..., ... }
console.log(exports === module.exports) // true

const { once, sha256: encrypt } = exports

// ...

DESCRIPTION

UnCommonJS is a tiny library which exposes a module.exports global (and exports alias) which behaves like the CommonJS built-in. It can be used to gather exports in environments which don't otherwise support CommonJS.

Names are deduplicated, so that e.g. if multiple modules export the same name or assign multiple values to module.exports, each export is given a distinct name.

This shim is useful in very constrained environments in which it's not possible (usually for political or policy reasons) to use transpilers or bundlers to integrate third-party modules.

Why?

Userscripts

I mainly use it to work around NPM modules that don't have UMD builds when I want to use one in a userscript (and as a way to import dependencies that are available as UMD bundles without polluting window).

For example, let's say I want to use the following modules, which are available on NPM but don't have UMD builds:

Since both of these modules are simple, small, and standalone — i.e. they don't use require — I can use UnCommonJS to expose module.exports and exports globals which they can attach their exports to. I can then pull these exported values (functions in this case) into a userscript simply by extracting them from the module.exports/exports object:

// ==UserScript==
// @name     My Userscript
// @require  https://unpkg.com/@chocolateboy/[email protected]
// @require  https://cdn.jsdelivr.net/npm/[email protected]
// @require  https://cdn.jsdelivr.net/npm/[email protected]
// ==/UserScript==

const { once, sha256: encrypt } = module.exports

// ...

ESM-only environments

It can also be used to add support for (dependency-free) NPM modules to environments which only support ESM such as Deno and QuickJS.

$ deno

Deno
exit using ctrl+d or close()

> import 'https://unpkg.com/@chocolateboy/[email protected]'
> import 'https://unpkg.com/[email protected]'

> exports.parse('Hi, **this** _is_ [Markdown](#markdown)!')
'<p>Hi, <strong>this</strong> <em>is</em> <a href="#markdown" >Markdown</a>!</p>'

Why not?

This is a hack to get CommonJS modules working in constrained environments such as userscripts when no other option is available. It shouldn't be used in situations or environments where sane solutions are available.

TYPES

The following types are referenced in the descriptions below.

type Exports = Record<PropertyKey, any>
type Require = (id: string) => any

type Module = {
    get exports (): Exports;
    set exports (value: any);
    readonly exported: Exports;
    require: Require;
}

type Environment = {
    module: Module;
    exports: Exports;
    require: Require;
}

type Options = {
    require?: Require;
}

GLOBALS

When the shim is loaded, module, exports and require are defined as global variables if they're not defined already. Unless noted, they should have the same behavior as the corresponding values in Node.js and other CommonJS environments.

import '@chocolateboy/uncommonjs/polyfill'

module.exports = 42
console.log(module.exported) // { "default": 42 }

The API can be imported without being automatically registered via the module's main file, e.g.:

import cjs from '@chocolateboy/uncommonjs'

const env = cjs() // { module: ..., exports: ..., require: ... }
Object.assign(globalThis, env)

exports

An alias for module.exports.

module

An object which contains the following properties:

module.exported

module.exports (and its exports alias) is implemented as a thin wrapper (an ES6 Proxy) around the actual exports which transparently handles name deduplication.

Most of the time this distinction doesn't matter, but it can crop up when logging/debugging — e.g. when dumping the exported values with console.log — since some environments display the Proxy's internals, rather than its target. This can make it hard to see what's actually available. The module.exported property solves this by exposing a (read-only) view of the underlying object.

console.log(module.exports)
// Proxy { <target>: {…}, <handler>: {…} }

console.log(module.exported)
// Object { once: ..., sha1: ..., sha256: ..., ... }

module.exports

An object (dictionary) of exported values which can be assigned to by name, e.g.:

module.exports.foo = function foo () { ... }
module.exports.bar = function () { ... }

exports is an alias for module.exports, so named assignments to exports are identical to named assignments to module.exports.

The first time a named export is assigned, it is given the specified name. Subsequent assignments to the same name with the same value are ignored. If different values are assigned to the same name, they are assigned unique names by appending numeric suffixes, e.g.: foo, foo_1, foo_2 etc.

In addition to named exports, default exports can be assigned directly to module.exports. Note: unlike named exports, which can be assigned to exports, default exports only work by assignment to module.exports.

If a named function is assigned to module.exports, it is equivalent to a named export, e.g.:

module.exports = function foo () { }

is equivalent to:

module.exports.foo = function foo () { }

If the assigned value is an anonymous function or a non-function, it is assigned the name default. As with named exports, default assignments with the same value are ignored and default assignments with different values are assigned distinct names by appending a numeric suffix, e.g. default, default_1, default_2 etc.

If a plain object is assigned to module.exports, its properties are assigned by name (the object's own, enumerable string keys) in addition to the default export, e.g.:

const props = { foo, bar }
module.exports = props

is equivalent to:

module.exports.foo = foo
module.exports.bar = bar
module.exports.default = props

module.require

An alias for the require export. Can be assigned a new require implementation which is used whenever the exported require function is called.

const mods = {
    'is-even':     (value => value % 2 === 0),
    'is-odd':      (value => value % 2 === 1),
    'is-thirteen': (value => value === 13),
}

module.require = id => {
    return mods[id] || throw new Error(...)
}

require

A function which takes a module ID (string) and returns the value exported by the module.

The default implementation is a stub which raises an exception which includes the name of the required module. It can be overridden by assigning to module.require.

EXPORTS

default

Type: (options?: Options) => Environment

import cjs from '@chocolateboy/uncommonjs'

const mods = {
    'is-even': (value => value % 2 === 0),
    'is-odd':  (value => value % 2 === 1),
}

const myRequire = id => mods[id] || throw new Error(...)
const env = cjs({ require: myRequire })

Object.assign(globalThis, env)

A function which generates a new CommonJS environment, i.e. an object containing CommonJS-compatible module, exports and require properties.

Takes an optional options object supporting the following options:

require

Type: Require

A require implementation which is delegated to by the exported require function. If not supplied, it defaults to a function which raises an exception with the supplied module ID.

CAVEATS

  • By default, require is defined but not implemented (it throws an exception): check the required modules to ensure they don't use it
  • __filename and __dirname are not supported
  • Pin the versions of the required modules to avoid being caught out if they update their dependencies
  • Unless a compatible require has been defined, load UMD bundles which use require before this shim, otherwise it will mislead them into thinking it's a real CommonJS environment

Scope

Care may need to be taken when extracting values from the exports object into variables if a CommonJS module (also) defines those variables at its top level, e.g.:

function foo () { ... }

module.exports = foo

For example, while most userscript engines execute userscripts in a (nested) scope distinct from that of the @requires, at least one popular engine doesn't. This means that the following won't work portably:

// ==UserScript==
// @name          Non-Portable Userscript
// @include       *
// @require       https://unpkg.com/@chocolateboy/[email protected]
// @require       https://cdn.jsdelivr.net/npm/[email protected]
// ==/UserScript==

const { get } = exports // SyntaxError

get(obj, path)

Because the userscript is evaluated in the same scope as the CommonJS module, the name is already defined and the declaration results in an error:

Uncaught SyntaxError: redeclaration of function `get`

The same issue can occur in other contexts, e.g. if the shim is being used on a webpage.

The solution is to wrap the code (or the parts of the code which assign properties of exports to local variables) in a nested scope, e.g. a block or IIFE:

(function () {
    const { get } = exports // OK

    get(obj, path)
})()

Alternatively, it may be simpler to access the export by its qualified name, e.g.:

exports.get(obj, path) // OK

DEVELOPMENT

NPM Scripts

The following NPM scripts are available:

  • build - compile the library for testing and save to the target directory
  • build:doc - update the table-of-contents (TOC) in the README
  • build:release - compile the library for release and save to the target directory
  • clean - remove the target directory and its contents
  • rebuild - clean the target directory and recompile the library
  • test - recompile the library and run the test suite
  • test:run - run the test suite
  • typecheck - sanity check the library's type definitions

COMPATIBILITY

  • any environment with ES6 support
  • in strict mode, globals are assigned to globalThis, which may need to be polyfilled

SEE ALSO

VERSION

3.2.1

AUTHOR

chocolateboy

COPYRIGHT AND LICENSE

Copyright © 2020 by chocolateboy.

This is free software; you can redistribute it and/or modify it under the terms of the MIT license.