0.1.0-alpha.4 • Published 5 months ago

swc-plugin-global-esm v0.1.0-alpha.4

Weekly downloads
-
License
MIT
Repository
github
Last release
5 months ago

swc-plugin-global-esm

!WARNING This plugin is for custom module system to implement Hot Module Replacement(HMR) in some bundlers that don't support it.

Installationeac

npm install swc-plugin-global-esm
# or yarn
yarn add swc-plugin-global-esm

Usage

Inject runtime script to top of bundle.

import 'swc-plugin-global-esm/runtime';

// Now you can use global module API (global.__modules)

and add plugin to your swc options.

import { transform } from '@swc/core';

await transform(code, {
  jsc: {
    experimental: {
      plugins: [
        // Add plugin here.
        ['swc-plugin-global-esm', {
          /**
           * Convert import statements to custom module system and remove export statements.
           *
           * Defaults to `false`.
           */
          runtimeModule: true,
          /**
           * Actual module path aliases (resolved module path)
           *
           * Defaults to none.
           */
          importPaths: {
            "<import source>": "actual module path",
            // eg. react
            "react": "node_modules/react/cjs/react.development.js",
          },
        }],
      ],
    },
    /**
     * You should disable external helpers when `runtimeModule` is `true`
     * because external helper import statements will be added after plugin transformation.
     */
    externalHelpers: false,
  },
});

Preview

Before

import React, { useState, useEffect } from 'react';
import { Container, Section, Button, Text } from '@app/components';
import { useCustomHook } from '@app/hooks';
import * as app from '@app/core';

export function MyComponent (): JSX.Element {
  // ...
}

// anonymous class
export default class {}

After

// with `runtimeModule: true`
const React = global.__modules.import("react").default;
const useState = global.__modules.import("react").useState;
const useEffect = global.__modules.import("react").useEffect;
const Container = global.__modules.import("@app/components").Container;
const Section = global.__modules.import("@app/components").Section;
const Button = global.__modules.import("@app/components").Button;
const Text = global.__modules.import("@app/components").Text;
const useCustomHook = global.__modules.import("@app/hooks").useCustomHook;
const app = global.__modules.import("@app/core");

function MyComponent () {
  // ...
}

const __export_default = class {}

global.__modules.export("<module-file-name>", {
  default: __export_default,
  MyComponent
});

Use Cases

import fs from 'node:fs/promises';
import path from 'node:path';
import * as esbuild from 'esbuild';
import { transform } from '@swc/core';

const ROOT = path.resolve('.');

const context = await esbuild.context({
  // ...,
  sourceRoot: ROOT,
  metafile: true,
  inject: ['swc-plugin-global-esm/runtime'],
  plugins: [
    // ...,
    {
      name: 'store-metadata-plugin',
      setup(build) {
        build.onEnd((result) => {
          /**
           * Store metafile data to memory for read it later.
           * 
           * # Metafile
           *
           * ```js
           * {
           *   inputs: {
           *     'src/index.ts': {
           *       bytes: 100,
           *       imports: [
           *         {
           *           kind: '...',
           *           // Import path in source code
           *           original: 'react',
           *           // Resolved path by esbuild (actual module path)
           *           path: 'node_modules/react/cjs/react.development.js',
           *           external: false,
           *         },
           *         ...
           *       ],
           *     },
           *     ...
           *   },
           *   outputs: {...}
           * }
           * ```
           */
          store.set('metafile', result.metafile);
        });
      },
    },
  ],
});
await context.rebuild();

// eg. file system watcher
watcher.addEventListener(async ({ path }) => {
  /**
   * Get import paths from esbuild's metafile data.
   *
   * # Return value
   *
   * ```js
   * {
   *   'react': 'node_modules/react/cjs/react.development.js',
   *   'src/components/Button': 'src/components/Button.tsx',
   *   ...
   * }
   * ```
   */
  const getImportPathsFromMetafile = (filepath: string) => {
    const metafile = store.get('metafile');
    return metafile?.inputs[filepath]?.imports?.reduce((prev, curr) => ({
      ...prev,
      [curr.original]: curr.path
    }), {}) ?? {};
  };

  const strippedPath = path.replace(ROOT, '').substring(1);
  const rawCode = await fs.readFile(path, 'utf-8');
  const transformedCode = await transform(rawCode, {
    filename: strippedPath,
    jsc: {
      experimental: {
        plugins: [
          ['swc-plugin-global-esm', {
            runtimeModule: true,
            importPaths: getImportPathsFromMetafile(strippedPath),
          }],
        ],
      },
      externalHelpers: false,
    },
  });
  
  // eg. send HMR update message to clients via websocket.
  sendHMRUpdateMessage(path, transformedCode);
});

License

MIT

0.1.0-alpha.4

5 months ago

0.1.0-alpha.3

5 months ago

0.1.0-alpha.2

6 months ago

0.1.0-alpha.1

6 months ago

0.1.0-alpha.0

6 months ago