@adeira/monorepo-npm-publisher

2.0.0 • Public • Published

This package prepares our public NPM packages to be published. It can automatically find these packages, transpile them based on our Babel configuration, copy Flow versions of the files and automatically publish it to NPM (in CI). It publishes only packages with new version and it ignores old or current versions.

This publisher uses @adeira/babel-preset-adeira behind the scenes to transpile JS (and JS-ESM) and Flow files.

Please note: changelogs are not responsibility of this package. You should write them manually for your users.

Installation

yarn add --dev @adeira/monorepo-npm-publisher

This package is intended to be run by CI server.

Usage

import path from 'path';
import os from 'os';
import publish from '@adeira/monorepo-npm-publisher';

(async () => {
  await publish({
    // Run in a "dry" mode (without publishing to NPM)?
    dryRun: true,

    // Where to store transpiled code before it's being published.
    buildCache: path.join(
      os.tmpdir(),
      'com.adeira.TODO_YOUR_PROJECT.npm', // change please
      '.build',
    ),

    // Workspaces to publish on NPM. It takes into account only
    // packages with public visibility set in `package.json`.
    workspaces: new Set(['@adeira/js', '@adeira/fetch', '@adeira/relay', '@adeira/eslint-config']),

    npmAuthToken: '*** TODO ***', // see: https://www.npmjs.com/settings/<USERNAME>/tokens
    reactRuntime: 'classic', // or `automatic` if you want to use the new JSX transform. 'automatic' is the default option. see https://reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html
  });
})();

This NPM publisher automatically takes .npmignore (or .gitignore) files into account. Read this info for more details: https://docs.npmjs.com/misc/developers#keeping-files-out-of-your-package

BEGIN-ADEIRA-UNIVERSE-INTERNAL, END-ADEIRA-UNIVERSE-INTERNAL

In rare scenarios, you might need to skip some part of the source code when publishing to NPM. It can be done like so:

// BEGIN-ADEIRA-UNIVERSE-INTERNAL
require('@babel/register')({
  ignore: [/node_modules\/(?!@adeira)/],
  rootMode: 'upward',
});
// END-ADEIRA-UNIVERSE-INTERNAL

The code block between BEGIN-* and END-* will be removed. Please try to use it sporadically - typical example would be to wrap a code which is required only for Adeira Universe monorepo functioning. It's worth mentioning that we are actively looking for better and systematic solutions to this problem.

Behind the scenes explanation

Let's have a look at our JS project and what happens when you run this publisher. Before:

src/js
├── LICENSE
├── README.md
├── package.json
└── src
    ├── __tests__
    │   ├── invariant.test.js
    │   ├── isObject.test.js
    │   ├── sprintf.test.js
    │   └── warning.test.js
    ├── index.js
    ├── invariant.js
    ├── isObject.js
    ├── sprintf.js
    └── warning.js

2 directories, 12 files

After:

com.adeira.universe.npm/.build/js
├── LICENSE
├── README.md
├── package.json
└── src
    ├── index.js
    ├── index.js.flow
    ├── index.mjs
    ├── invariant.js
    ├── invariant.js.flow
    ├── invariant.mjs
    ├── isObject.js
    ├── isObject.js.flow
    ├── isObject.mjs
    ├── sprintf.js
    ├── sprintf.js.flow
    ├── sprintf.mjs
    ├── warning.js
    ├── warning.js.flow
    └── warning.mjs

1 directory, 18 files

As you can see all the important files are still in the final bundle but tests are missing. It's because they are excluded in .npmignore file. Every JS file is distributed in multiple variants. JS files are transpiled so they can be used basically everywhere:

'use strict';

var _interopRequireDefault = require('@babel/runtime/helpers/interopRequireDefault');

Object.defineProperty(exports, '__esModule', {
  value: true,
});
exports.default = isObject;

var _typeof2 = _interopRequireDefault(require('@babel/runtime/helpers/typeof'));

function isObject(value) {
  return (0, _typeof2.default)(value) === 'object' && value !== null && !Array.isArray(value);
}

Flow files contain more or less raw code with Flow definitions (it's not 1:1 to original, we also apply some transformations):

// @flow strict

export default function isObject(value: mixed): boolean %checks {
  return typeof value === 'object' && value !== null && !Array.isArray(value);
}

You can read more about Flow Declaration Files here. And MJS files contain JS version for modern environments (essentially JS version but with ES6 modules support):

import _typeof from '@babel/runtime/helpers/esm/typeof';
export default function isObject(value) {
  return _typeof(value) === 'object' && value !== null && !Array.isArray(value);
}

One last change is happening: NPM publisher modifies package.json file so it contains correct module field pointing to MJS file variants. Please note: ES6 modules can be disabled completely when you specify "module": false in your package.json:

{
  "name": "withDisabledES6",
  "version": "0.0.0",
  "main": "src/index.js",
  "module": false,
  "dependencies": {
    "//": "none"
  }
}

This is handy when your code is not ready for ES6 modules yet.

Note on ES6 modules

The following text describes the current situation up to this date. Seems like some parts of the JS ecosystem are not well documented and they may change in future.

Usually tools use CJS requires but some of them also support ES6 imports. This is how it works in Next.js for example:

const webpackResolveConfig = {
  // Disable .mjs for node_modules bundling
  extensions: isServer
    ? ['.js', '.mjs', '.jsx', '.json', '.wasm']
    : ['.mjs', '.js', '.jsx', '.json', '.wasm'],
  mainFields: isServer ? ['main', 'module'] : ['browser', 'module', 'main'],
  // ...
};

Notice the ordering of supported extensions. Therefore Next.js actually uses different systems for FE and BE. We distribute both versions: JS and MJS files. Different situation is when package uses MJS and this package has dependency on different package which also uses MJS. This subpackage must be configured properly in package.json. The only possible solution is currently this:

{
  "name": "@adeira/js",
  "module": "src/index.mjs",
  "main": "src/index"
}

Notice that field main cannot have file extension! There is currently not many resources explaining why but it's further explained in this Babel issue. This extension cannot be present even when you don't have module field but you are distributing MJS files anyway.

More resources:

Readme

Keywords

none

Package Sidebar

Install

npm i @adeira/monorepo-npm-publisher

Weekly Downloads

110

Version

2.0.0

License

MIT

Unpacked Size

64.3 kB

Total Files

36

Last publish

Collaborators

  • martin.zlamal
  • adeira-npm-bot