vitejs / vite

Next generation frontend tooling. It's fast!
http://vite.dev
MIT License
69.12k stars 6.25k forks source link

Build executable CLI script in library mode. #18871

Open slavarazum opened 1 day ago

slavarazum commented 1 day ago

Description

It would be great to have an ability to build CLI script starting with a hashbang:

#!/usr/bin/env node

// parse arguments...

Many libraries offer CLI scripts; in fact, many libraries are CLI scripts. Vite has a useful library mode, and the ability to build a CLI companion alongside a browser-oriented library would be a valuable addition.

Suggested solution

As an example, it can be enabled using the entryCli library mode option.

// ...
    lib: {
      fileName: 'newlib',
      entry: resolve(__dirname, 'src/index.ts'),

      entryCli: resolve(__dirname, 'src/bin/cli.ts'),

      formats: ['es', 'cjs', 'umd', 'iife'],
      name: 'NewLib',
    },
// ...

Distribution target file might be fetched from bin package.json property:

 "bin": {
    "newlib": "dist/bin/newlib-cli.js"
  },

It also should be executable, tools like tsup demonstrate that.

Alternative

Currently, I haven't found a way to build an executable CLI script in library mode.

Additional context

No response

Validations

sapphi-red commented 1 day ago

If you put #!/usr/bin/env node in your entry point, Vite will keep that as-is. So you don't need to set any option.

It also should be executable

I think we can set +x for all output entrypoints with a hashbang.

slavarazum commented 10 hours ago

Yep, it may be executable by default for files with hashbangs. Additionally, the CLI distribution must be compiled in the correct format. I attempted to create a separate vite.cli.config.ts to build the CLI script.

import type { UserConfig } from 'vite'

import { fileURLToPath, URL } from 'node:url'
import dts from 'vite-plugin-dts'

export default {
  build: {
    lib: {
      fileName: 'library-cli',
      entry: fileURLToPath(new URL('src/bin/cli.ts', import.meta.url)),
      formats: ['es'],
    },

    outDir: 'dist/bin',

    sourcemap: true,
  },

  plugins: [
    dts({
      tsconfigPath: './tsconfig.cli.json',
      staticImport: true,
      entryRoot: 'src/bin',
      insertTypesEntry: true,
    }),
  ],

  resolve: {
    alias: {
      $: fileURLToPath(new URL('./src/bin', import.meta.url)),
    },
  },
} satisfies UserConfig

Reproduce example https://stackblitz.com/edit/vitejs-vite-5qfsan?file=vite.cli.config.ts

npm run build-cli
chmod +x ./dist/bin/library-cli.js
./dist/bin/library-cli.js

will throw:

[!CAUTION] Error: Class extends value undefined is not a constructor or null

the same for cjs format.

We can see how it works in comparison with tsup:

npm run build-cli-tsup
./dist/bin/library-cli.js