Publishing in JavaScript Ecosystem:

How to keep everyone happy?

Software Development Engineer

Trivikram Kamat

@trivikram

@trivikr

TRI

VIKRAM

.

What's my name again?

Reality Check

You cannot make everyone happy.

But you can try your best.

#CascadiaJS

#javascript #nodejs #npm

Why talk about publishing?

  • Browser: Chrome, Firefox, Edge, Safari, ...

  • Server: Node.js, Deno, Cloudflare Workers, Bun, ...

  • JS Native: React Native, NativeScript, Flutter, Ionic, ...

  • Module Formats: CJS, ESM, AMD, UMD, ...

  • Sizes: publish size, install size, bundle size

  • Types: Flow, TypeScript, ...

  • Debugging: sources, source map

  • Observability: OpenTelemetry, OpenTracing, OpenSensus

What are we going to cover?

  • Which criteria affects your package?

  • Evaluate WHY, HOW and WHAT of each criteria with some approximations based on data.

Note: Suggestions are based on assumption that you maintain a package on npm using package.json

Which criteria will affect you?

It depends!

on what/how of the problem solved by your package

and where/how do users want to use it.

Where/How do users want to use your package?

Ask them!

and if your package is popular, they WILL tell you.

You can also populate data in telemetry, if supported.

Publishing Criteria

WHY, HOW and WHAT of

Browser, Server, Native,

Module formats, Sizes,

Types, Debugging, Tracing

Criteria: Browser (why?)

JavaScript is THE language used for scripting

in the browsers.

WebAssembly allows non-JS code to run on browsers

But it's likely to be used for specific use cases - like compute-intensive apps

Criteria: Browser (how?)

Use browser field in package.json

"browser": "./browser/main.js",
"browser": {
  "module-a": false,
  "module-b": "./shims/module-b.js",
  "./server.js": "./shims/client.js",
}

Criteria: Browser (what?)

🪦 Internet Explorer has "retired" on June 15, 2022!

💪 Safari likely getting competitive!

Isn't JavaScript support fragmented on browsers?

Yes. But...

Criteria: Server (why?)

📈 Deno is being used in more products.

☁️ Cloudflare Workers is going Open Source.

🏆 Node.js is among the popular server runtimes.

🥟 Bun is going viral by prioritizing performance.

Criteria: Server (why?)

😎 Web-interoperable Runtimes Community Group

is collaborating on API interoperability of web platform APIs across runtimes.

Fragmentation existed for years on the browser.

Is it bad that now there are competing server runtimes?

NO.

Criteria: Node.js (how?)

Just add filepath in main entry of package.json

"main": "./dist/main.js",

Criteria: Deno (how?)

↔️ If most of your users are on Deno, you can publish your Deno module on npm using dnt.

🤝 Deno v1.25 added experimental npm support.

Criteria: Cloudflare Workers (how?)

👍 Any Node.js package that uses webpack or another polyfill bundler runs in Cloudflare Workers.

Criteria: Bun.js (how?)

❓Bun natively implements many Node.js and Web APIs.

As of August 2022, Bun is pre-alpha. The parent company is aiming stable release by Feb 2023, so look out for updates.

Criteria: Server (what?)

It depends!

Support the server runtimes your users want.

Criteria: JS Native (why?)

🏎 JavaScript Native performance trade-off have reduced significantly over the years.

⚠️ PWAs have limitations, like lack of push notifications.

You write JavaScript code, but the UI framework creates native apps.

Criteria: JS Native (how?)

📱 React Native parses code from .native.(js|ts) extension.

📱 NativeScript/Flutter/Ionic frameworks doesn't mention about platform specific JavaScript code.

Criteria: JS Native (what?)

It depends!

Support the native frameworks your users want.

Criteria: Module Formats (why?)

Modules are building blocks for software.

Module format is syntax used to define a module.

When JavaScript was getting popular in early 2000s, there was no standard module format. Multiple module formats were developed and used in parallel.

Criteria: Module Formats (why?)

⚠️ WARNING ⚠️

Conversations may get heated because of opinions when we talk about module systems.

Take suggestions with a grain of salt.

Criteria: Module Formats (why?)

ESM (ECMAScript): Official standard format.

CJS (CommonJS): Original module format in Node.js

AMD (Asynchronous Module Definition): was specifically made for frontend.

UMD (Universal Module Definition): was specifically made to work universally.

Criteria: Module Formats (how?)

If supporting single module system, add filepath in main entry.

"main": "./dist/main.js",

If supporting both CJS/ESM, use conditional exports.

"exports": {
  "import": "./dist/main.js",
  "require": "./dist/main.cjs"
}

Criteria: Module Formats (how?)

For supporting CJS/ESM, you can also use ESM module wrapper

// dist/index.cjs
exports.name = 'value';
// dist/index.mjs
import cjsModule from './index.cjs';
export const name = cjsModule.name;

This can be automated using gen-esm-wrapper.

Criteria: Module Formats (what?)

🔥🔥🔥 Ideally, you should only support ESM. 🔥🔥🔥

But that would break users on Node.js whose application code is in CommonJS format.

Be a good citizen of JavaScript ecosystem, and support both CJS/ESM if you have to support Node.js

Criteria: Sizes (why?)

Publish size: Size of the package being published.

Install size: Size of node_modules folder when your package is added as a dependency.

Bundle size: Size of the file generated when a bundler is used on the application code using your package.

Criteria: Sizes (why?)

If publish/install size is small, installs will be faster.

Your package will also have smaller carbon footprint.

Adding your package will not exceed size limits in resource constrained environments, like Serverless.

If bundle size is small, the load times for the final application will be faster.

Criteria: Reduce Publish Size (how?)

Write minimal code needed for solving the problem.

Use package.json "files" field to only publish required files, thus removing unnecessary files.

🔥 Consider moving TypeScript types to DefinitelyTyped. 🔥

If using TypeScript, publish sources and source maps in debug variant of your package using dist-tags.

Criteria: Reduce Install Size (how?)

Have minimal, or no dependencies!

Share tips for reducing publish/install size with packages you depend on.

Provide a variant of your package just for resource-constrained environment using a dist-tag.

@scope/package-name

@scope/package-name@node

: default variant - code for all platforms

: Node.js variant - Node.js specific code

Criteria: Reduce Bundle Size (how?)

Make your package tree shakeable (dead-code elimination).

Use named exports.

Publish ESM version of your package.

Make your package side effects free.

Split code into multiple smaller modules.

Criteria: Sizes (what?)

It depends!

It's always good to have small install/publish/bundle size

Criteria: Types (why?)

JavaScript is dynamically typed programming language.

Between 15% to 38% of the bugs can be prevented by types.

Types are checked and errors are spotted only at the runtime.

Using types, IDEs can provide code editing features like code completion and parameter information.

Criteria: Types (why?)

TypeScript is the most popular type system for JS.

ECMAScript proposal: Type Annotations

Criteria: Types (how?)

Either write your code in TypeScript or generate types from comments for your JavaScript code by running tsc.

The types can be linked in the "types" field in package.json

"types": "./dist/types.d.ts",

Or submit them to DefinitelyTyped to be published under @types organization on npm.

@types/your-package-name

Criteria: Types (what?)

It depends!

Your users will most likely need TypeScript types.

Criteria: Debugging (why?)

If your package code is in a language which compiles to JavaScript (like TypeScript), then you need to publish source code and source maps to help with debugging.

It helps your users to debug though readable code. It improves their understanding of your package, helping them unblock themselves or cut relevant bug reports or feature requests.

Criteria: Debugging (how?)

Enable generation of sourceMap files in your TypeScript Config

"sourceMap": true,
"inlineSourceMap": true,

OR

Include TypeScript source in your source maps

"inlineSources": true,
"sourceRoot": "src",

OR

Criteria: Debugging (what?)

If your package code is in a language which compiles to JavaScript, do provide sources and source maps.

If it cuts into your install/publish size quota, release a debug variant using `debug` dist-tag.

Criteria: Observability (why?)

Observability is the ability to measure a system's current state based on the data it generates, such as logs, metrics, and traces.

Observability helps developers build better, stable applications.

Back in mid-2010s, there were competing APIs like OpenTracing and OpenCensus. In 2019, they merged into OpenTelemetry.

Criteria: Observability (how?)

You can add out-of-the-box support for OTel, or add instrumentation in an external package.

import { trace } from '@opentelemetry/api';

const tracer = trace.getTracer("name", "0.1.0");
const span = tracer.startSpan("span-name");

// do some work

span.end();

Criteria: Observability (what?)

Add external instrumentation for your package, either in your npm scope or in Otel.

@opentelementry/instrumentation-pkg-name
@scope-name/opentelemetry-instrumentation

Conclusion (opinionated)

  • Browser: Support only modern browsers (ES6+)

  • Server: Node.js, with optional Deno/Cloudflare Workers support.

  • JS Native: It depends! ReactNative supports platform-specific code.

  • Module Formats: Dual package CJS+ESM, optional ESM only variant.

  • Sizes: Aim for smaller publish/install/bundle sizes.

  • Types: At least publish TypeScript types. Optionally use TypeScript.

  • Debugging: Provide sources, and source maps in debug variant.

  • Observability: OpenTelemetry with external instrumentation.

Thank you for listening!

Trivikram Kamat

@trivikram

@trivikr

Publishing in JavaScript Ecosystem: CascadiaJS 2022

By Trivikram Kamat

Publishing in JavaScript Ecosystem: CascadiaJS 2022

  • 564