WeAreGenki/minna-ui

View on GitHub
utils/build-component/src/index.ts

Summary

Maintainability
B
5 hrs
Test Coverage
/**
 * Minna UI component compiler.
 */

/* eslint-disable @typescript-eslint/camelcase */

import { preprocess } from '@minna-ui/preprocess';
import buble from '@rollup/plugin-buble';
import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import { basename } from 'path';
import * as rollup from 'rollup';
// @ts-ignore - Don't care about types in simple lib
import svelte from 'rollup-plugin-svelte';
import { terser } from 'rollup-plugin-terser';

interface CssResult {
  code: string;
  map?: string;
}

interface RollupSvelteCss extends CssResult {
  write(path: string): void;
}

interface BuildComponentResult {
  cjs: {
    bundle: rollup.RollupBuild;
    result: rollup.RollupOutput;
  };
  css: CssResult;
  element: {
    bundle: rollup.RollupBuild;
    result: rollup.RollupOutput;
  };
  esm: {
    bundle: rollup.RollupBuild;
    result: rollup.RollupOutput;
  };
}

const terserOpts = {
  compress: {
    drop_console: false,
    drop_debugger: true,
    keep_fargs: false,
    negate_iife: false,
    passes: 4,
    pure_funcs: ['Object.freeze'],
    pure_getters: true,
    side_effects: true,
  },
  mangle: true,
  output: {
    comments: /^!|[@#]__PURE__/,
    wrap_iife: true,
  },
  sourcemap: true,
  warnings: true,
};

/**
 * Run component build process.
 *
 * @param env - Node `process.env`.
 */
export async function run(
  env: NodeJS.ProcessEnv,
): Promise<BuildComponentResult> {
  const pkgBrowser = env.npm_package_browser;
  const pkgHomepage = env.npm_package_homepage;
  const pkgMain = env.npm_package_main;
  const pkgModule = env.npm_package_module;
  const pkgName = env.npm_package_name;
  const pkgStyle = env.npm_package_style;
  const pkgSvelte = env.npm_package_svelte;
  const pkgVersion = env.npm_package_version;

  if (!pkgBrowser) throw new Error('package.json#browser is not defined');
  if (!pkgMain) throw new Error('packge.json#main is not defined');
  if (!pkgStyle) throw new Error('packge.json#style is not defined');
  if (!pkgSvelte) throw new Error('package.json#svelte is not defined');

  process.env.NODE_ENV = env.NODE_ENV || 'production';
  const name = basename(pkgSvelte, '.svelte');

  const banner = `/*!
 * ${pkgName} v${pkgVersion} (${pkgHomepage})
 * Copyright ${new Date().getFullYear()} We Are Genki
 * Apache 2.0 license - https://github.com/WeAreGenki/minna-ui/blob/master/LICENCE
 */`;

  try {
    let resolveCss: (value: CssResult) => void;
    const resultCss: Promise<CssResult> = new Promise((res) => {
      resolveCss = res;
    });

    const bundleCjs = await rollup.rollup({
      input: pkgSvelte,
      plugins: [
        svelte({
          css(css: RollupSvelteCss) {
            // eslint-disable-next-line no-param-reassign
            css.code = `${banner}\n${css.code}`;
            resolveCss({
              code: css.code,
              map: css.map,
            });
            css.write(pkgStyle);
          },
          preprocess,
        }),
        resolve(),
        commonjs(),
        buble({
          transforms: {
            dangerousForOf: true,
          },
        }),
        terser(terserOpts),
      ],
    });

    const bundleElement = await rollup.rollup({
      input: pkgSvelte,
      plugins: [
        svelte({
          customElement: true,
          preprocess,
          // FIXME: Make this customisable or use a new technique to name tags
          tag: pkgName!.replace('@minna-ui/', 'minna-'),
        }),
        resolve(),
        commonjs(),
        buble({
          transforms: {
            classes: false,
            dangerousForOf: true,
          },
        }),
        terser(terserOpts),
      ],
    });

    const bundleEsm = await rollup.rollup({
      // Allow Svelte to be tree-shaken when users import multiple components
      external: ['svelte', 'svelte/internal'],
      input: pkgSvelte,
      plugins: [
        svelte({
          css: false,
          preprocess,
        }),
        resolve(),
        commonjs(),
      ],
    });

    const resultCjs = bundleCjs.write({
      banner,
      file: pkgMain,
      format: 'iife',
      name,
      sourcemap: true,
    });

    const resultElement = bundleElement.write({
      banner,
      file: pkgBrowser,
      format: 'iife',
      name,
      sourcemap: true,
    });

    const resultEsm = bundleEsm.write({
      banner,
      file: pkgModule,
      format: 'esm',
      name,
      sourcemap: true,
    });

    // await here to capture any errors
    const results = {
      cjs: {
        bundle: bundleCjs,
        result: await resultCjs,
      },
      css: await resultCss,
      element: {
        bundle: bundleElement,
        result: await resultElement,
      },
      esm: {
        bundle: bundleEsm,
        result: await resultEsm,
      },
    };

    return results;
  } catch (err) {
    // eslint-disable-next-line no-console
    console.error('[build-component]', err);

    // we always want internal builds to fail on error
    process.exitCode = 2;
    throw err;
  }
}