rollup

 

// rollup.config.ts
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import typescript from '@rollup/plugin-typescript';
import postcss from 'rollup-plugin-postcss';
import peerDepsExternal from 'rollup-plugin-peer-deps-external';
import image from '@rollup/plugin-image';
import json from '@rollup/plugin-json';
import { terser } from 'rollup-plugin-terser';
import { getFiles } from '../../scripts/build-utils';

/**
 *  Generate rollup config by module
 * @param {string} module module type to build, cjs or esm
 * @return {any} rollup config
 */
function getConfig(module) {
  const outputDir = module === 'cjs' ? 'dist' : `dist/${module}`;
  const config = {
    input: ['src/index.ts', ...getFiles('./src', ['.ts', '.tsx'], ['.test.tsx'])],
    output: [
      {
        dir: outputDir,
        format: module,
        exports: 'named',
        preserveModules: true,
        preserveModulesRoot: 'src',
        sourcemap: false
      }
    ],
    // the npm publish script will ignore the node_modules in the build files, and we need 'tslib`, so add it to external
    external: ['clsx', 'tslib'],
    plugins: [
      peerDepsExternal({
        includeDependencies: true
      }),
      resolve(),
      commonjs(),
      typescript({
        tsconfig: './tsconfig.build.json',
        declarationDir: outputDir
      }),
      json(),
      postcss(),
      image(),
      terser()
    ]
  };
  return config;
}

const configs = ['cjs', 'esm'].map((module) => getConfig(module));

export default configs;
// tsconfig.build.json
{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    "declaration": true,
    "rootDir": "src"
  },
  "include": ["./src/**/*.ts*"],
  "exclude": ["src/**/*.test.ts*"]
}
// tsconfig.json

{
  "extends": "../../tsconfig.json"
}

// ../../tsconfig.json
{
  "compilerOptions": {
    "module": "esnext",
    "target": "es5",
    "lib": ["es2020", "dom"],
    "sourceMap": true,
    "jsx": "react-jsx",
    "esModuleInterop": true,
    "strict": true,
    "noEmit": true,
    "moduleResolution": "node",
    "allowSyntheticDefaultImports": true,
    "forceConsistentCasingInFileNames": true,
    "allowJs": true,
    "noImplicitAny": false,
    "resolveJsonModule": true
  },
  "exclude": ["dist", "node_modules", "scripts"],
  "types": ["typePatches"]
}
// build-utils.js

const { readdirSync, lstatSync } = require('fs');

export const getFiles = (entry, extensions = [], excludeExtensions = []) => {
  let fileNames = [];
  const dirs = readdirSync(entry);

  dirs.forEach((dir) => {
    const path = `${entry}/${dir}`;

    if (lstatSync(path).isDirectory()) {
      fileNames = [...fileNames, ...getFiles(path, extensions, excludeExtensions)];

      return;
    }

    if (!excludeExtensions.some((exclude) => dir.endsWith(exclude)) && extensions.some((ext) => dir.endsWith(ext))) {
      fileNames.push(path);
    }
  });

  return fileNames;
};


// frank-build.js


/* eslint-disable no-console */
const { resolve, join, basename } = require('path');
const { readFile, writeFile, copy } = require('fs-extra');
const { readdirSync } = require('fs');
const packagePath = process.cwd();
const buildFolder = './dist';
const distPath = join(packagePath, buildFolder);
const ignoreFolders = ['node_modules', 'util', 'common', 'cjs', 'locales', '__mocks__'];
const getFolders = (source) =>
  readdirSync(source, { withFileTypes: true })
    .filter((dirent) => dirent.isDirectory())
    .map((dirent) => dirent.name)
    .filter((name) => ignoreFolders.indexOf(name) == -1);

const writeJson = (targetPath, obj) => writeFile(targetPath, JSON.stringify(obj, null, 2), 'utf8');

async function createPackageFile() {
  const packageData = await readFile(resolve(packagePath, './package.json'), 'utf8');
  const { scripts, devDependencies, ...packageOthers } = JSON.parse(packageData);
  const newPackageData = {
    ...packageOthers,
    sideEffects: false,
    module: './esm/index.js',
    main: './index.js',
    types: './index.d.ts'
  };

  const targetPath = resolve(distPath, './package.json');
  await writeJson(targetPath, newPackageData);
  console.log(`Created package.json in ${targetPath}`);
}

async function createModulePackages() {
  getFolders(buildFolder).map(async (folder) => {
    const packageData = {
      sideEffects: false,
      module: `../esm/${folder}/index.js`,
      main: './index.js',
      types: './index.d.ts'
    };
    const targetPath = resolve(distPath, `./${folder}/package.json`);
    await writeJson(targetPath, packageData);
    console.log(`Created Module package.json in ${targetPath}`);
  });
}

async function includeFileInBuild(file) {
  const sourcePath = resolve(packagePath, file);
  const targetPath = resolve(distPath, basename(file));
  await copy(sourcePath, targetPath);
  console.log(`Copied ${sourcePath} to ${targetPath}`);
}

async function run() {
  try {
    await createModulePackages();
    await createPackageFile();
    await includeFileInBuild('./README.md');
  } catch (err) {
    console.error(err);
    process.exit(1);
  }
}

run();
// package.json
{
  "name": "@joseph/components",
  "version": "1.0.0",
  "main": "dist/index.js",
  "module": "dist/esm/index.js",
  "types": "dist/index.d.ts",
  "sideEffects": false,
  "scripts": {
    "build": "rimraf dist && rollup -c && yarn postbuild",
    "postbuild": "node ../../scripts/frank-build.js",
    "publish": "npm publish ./dist",
    "prestart": "rimraf dist"
  }
}