Publishing in JavaScript Ecosystem:
How to keep everyone happy?
Software Development Engineer
Trivikram Kamat
@trivikram
@trivikr
Reality Check
You cannot make everyone happy.
But you can try your best.
#OpenJSWorld22
#javascript #nodejs #npm
Why talk about publishing?
-
Browser: Chrome, Firefox, Edge, Safari, ...
-
Server: Node.js, Deno, Cloudflare Workers, ...
-
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 will affect 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 is "retiring" 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.
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 has experimental Node compatibility mode.
Criteria: Cloudflare Workers (how?)
ð Any Node.js package that uses webpack or another polyfill bundler runs in Cloudflare Workers.
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.
ðĨ If using TypeScript, move 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 or suffix.
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 small 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 definitely 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 or suffix.
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 native, with support for Deno and Cloudflare Workers
-
JS Native: It depends! ReactNative supports platform-specific code
-
Module Formats: Dual packaging CJS+ESM, optional ESM only variant
-
Sizes: It depends! Aim for smaller publish/install/bundle sizes.
-
Types: At least publish TypeScript types. May code in TypeScript.
-
Debugging: Provide sources, and source maps in debug variant.
-
Observability: OpenTelemetry with instrumentation package.
Thank you for listening!
Trivikram Kamat
@trivikram
@trivikr
Publishing in JavaScript Ecosystem: OpenJS World 2022
By Trivikram Kamat
Publishing in JavaScript Ecosystem: OpenJS World 2022
- 555