taylorc93
taylorc93

Reputation: 3716

Output an executable file with webpack

I'm currently writing a node CLI tool and using webpack to bundle all of my assets. The entry point for this application is the js file where I actually parse process.argv and run a command (For reference, I'm using tj/commander). This way, once the bundling is complete, I can enter ./<outputFile> and it will run my application. The entry file looks like this:

import cli from './cli';

cli.parse(process.argv);

// If nothing was supplied
if (!process.argv.slice(2).length) {
  cli.outputHelp();
}

The bundling works fine but I can't get webpack to output the file as an executable. Once I run chmod +x <outputFile>, everything works perfectly. Is there a way that I can tell webpack what permissions to grant an output file?

Upvotes: 7

Views: 6521

Answers (5)

cdauth
cdauth

Reputation: 7558

This is how I did it with Webpack 5:

import { promises as fs } from 'fs';

plugins: [
    new webpack.BannerPlugin({
        banner: '#!/usr/bin/env node',
        raw: true,
        entryOnly: true
    }),
    function() {
        this.hooks.done.tapPromise('Make executable', async () => {
            await fs.chmod(`${__dirname}/dist/app.js`, '755');
        });
    }
]

Upvotes: 1

elboletaire
elboletaire

Reputation: 5367

I'm surprised no one said a thing about webpack's BannerPlugin. I do something similar than @oklas, but using BannerPlugin to add the specific node shebang:

{
  plugins: [
    new webpack.BannerPlugin({
      banner: '#!/usr/bin/env node',
      raw: true,
    }),
  ],
}

Then I simply add the execution permissions just adding chmod to my package.json file:

"scripts": {
  "build": "webpack && chmod +x dist/mycommand"
}

Anyway, if you'd like to just use webpack you can use the WebpackShellPlugin, as said by oklas (note that using this forces you to add a new dependency, that's why I avoid using this approach):

const WebpackShellPlugin = require('webpack-shell-plugin')
{
  // [...]
  plugins: [
    new WebpackShellPlugin({
      onBuildEnd:['chmod +x dist/mycommand'],
    }),
  ],
}

If you want to avoid including WebpackShellPlugin as a dependency, you can try to define a custom plugin based on fs, as said by @taylorc93

Upvotes: 9

alfonsodev
alfonsodev

Reputation: 2754

You'll need to append #!/usr/bin/env node on top of the file.
I ended up with this webpack plugin using shelljs

plugins: [
  // ...plugins,
  function () {
      this.plugin('done', () => {
      shell
        .echo('#!/usr/bin/env node\n')
        .cat(`${__dirname}/build/outputfile.js`)
        .to(`${__dirname}/commandname`)
      shell.chmod(755, `${__dirname}/commandname`)
    })
  },
]

Upvotes: 3

oklas
oklas

Reputation: 8220

One simple way is to use npm. Do you have an package.json in your project? Add "build": "webpack && chmod +x outputFile" to the scripts section of your package.json and build your project by running npm run build.

Another way is to add one of these solutions to your webpack.config.js:

Whatever you choose, you'll need to add this piece of code:

var chmod = require('chmod');
chmod("outputFile", 500);

Upvotes: 6

taylorc93
taylorc93

Reputation: 3716

Although @oklas's solution worked perfectly for me, I really wanted to try and keep all of this within webpack. I realized after a little more thought that this could all be done by a very simple plugin:

plugins: [
  // ...plugins,

  function() {
    this.plugin('done', () => {
      fs.chmodSync('bin/program-name.js', '755');

      // When the webpack output doesn't have a .js extension, minification fails :(
      fs.renameSync('bin/program-name.js', 'bin/program-name');
    })
  },
]

Use whichever way suits your needs!

Upvotes: 2

Related Questions