jakejs / jake

JavaScript build tool, similar to Make or Rake. Built to work with Node.js.
http://jakejs.com
Apache License 2.0
1.96k stars 190 forks source link

Crash when running a ES6 Jakefile #415

Open laurentb-roy opened 1 year ago

laurentb-roy commented 1 year ago

Step to reproduce

  1. Create a new project, install jake
  2. Add the follow in package.json: "type": "module"
  3. Write the following Jakefile.js
import fs from 'fs';

task('default', () => {
    console.log('Task ran');
});
  1. Run jake: ./node_modules/.bin/jake
  2. The following error is displayed
jake aborted.
Error [ERR_REQUIRE_ESM]: require() of ES Module .../jakefile.js from .../node_modules/jake/lib/loader.js not supported.
Instead change the require of jakefile.js in .../node_modules/jake/lib/loader.js to a dynamic import() which is available in all CommonJS modules.
    at loadFile (.../node_modules/jake/lib/loader.js:67:20)
    at Loader.loadFile (.../node_modules/jake/lib/loader.js:141:7)
    at EventEmitter.Object.assign.run (.../node_modules/jake/lib/jake.js:315:37)
    at Object.<anonymous> (.../node_modules/jake/bin/cli.js:31:10)

Investigation

It seems the problem is that the Jakefile is loaded via the old, synchronous require() call. Offending line:

In Jake source code, lib/loader.js:66

  function loadFile(filePath) {
    let exported = require(filePath); // <------ Problem here
    for (let [key, value] of Object.entries(exported)) {
      let t;
      if (typeof value == 'function') {
        t = jake.task(key, value);
        t.description = '(Exported function)';
      }
    }
  }

Node.js does not support importing an ES6 module with this call, only commonjs module.

Potential fix

The fix is to use an asynchronous import() expression, which can import ES6 module from commonjs. However, that would making the function loadFile() and all its caller asynchronous, so its not a one line replacement.

Import expression: https://nodejs.org/dist/latest-v16.x/docs/api/esm.html#import-expressions

There is a little guide explaining the details here: https://techsparx.com/nodejs/esnext/esmodules-from-commonjs.html

Workaround

You can rename the Jakefile.js to Jakefile.cjs and rewrite it to use commonjs import

fs = require('fs');

task('default', () => {
    console.log('BUILD DONE');
});

You will need to specify the jakefile explicitely : ./node_modules/.bin/jake -f Jakefile.cjs

Of course, if you import any ES6 module from Jakefile.cjs, you will need to use import expression, which can be cumbersome to use.