Skip to content

[Question] Convert Rollup build script to esbuild #4147

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
erosman opened this issue Apr 17, 2025 · 4 comments
Open

[Question] Convert Rollup build script to esbuild #4147

erosman opened this issue Apr 17, 2025 · 4 comments

Comments

@erosman
Copy link

erosman commented Apr 17, 2025

I am a novice and trying to convert eslint-linter-browserify Rollup build script to esbuild by trial & error.

However, it runs into errors.

✘ [ERROR] Could not resolve "node:path"

    ../node_modules/eslint/lib/eslint/legacy-eslint.js:13:21:
      13 │ const path = require("node:path");
         ╵                      ~~~~~~~~~~~

  The package "node:path" wasn't found on the file system but is built into
  node. Are you trying to bundle for node? You can use "platform: 'node'" to do
  that, which will remove this error.

After adding platform: 'node, esbuild generates the build without error but the resulted file, when imported into the browser, has the following error:

Uncaught Error: Dynamic require of "node:path" is not supported
linter.js:1:196

Any help will be greatly appreciated.

build.js
// @ts-check

const esbuild = require('esbuild');
const path = require('path');
const fs = require('fs');

build({
  entryPoints: ['../node_modules/eslint/lib/linter/linter.js'],
  bundle: true,
  minify: true,
  format: 'esm',
  // platform: 'node',
  outdir: path.join(__dirname, 'eslint')
});

build({
  entryPoints: ['linter.js'],
  bundle: true,
  minify: true,
  format: 'esm',
  // platform: 'node',
  outdir: path.join(__dirname, 'eslint'),
  loader: {
    '.ttf': 'file'
  }
});

/**
 * @param {import ('esbuild').BuildOptions} opts
 */
function build(opts) {
  esbuild.build(opts).then((result) => {
    if (result.errors.length > 0) {
      console.error(result.errors);
    }
    if (result.warnings.length > 0) {
      console.error(result.warnings);
    }
  });
}

/**
 * Remove a directory and all its contents.
 * @param {string} _dirPath
 * @param {(filename: string) => boolean} [keep]
 */
function removeDir(_dirPath, keep) {
  if (typeof keep === 'undefined') {
    keep = () => false;
  }
  const dirPath = path.join(__dirname, _dirPath);
  if (!fs.existsSync(dirPath)) {
    return;
  }
  rmDir(dirPath, _dirPath);
  console.log(`Deleted ${_dirPath}`);

  /**
   * @param {string} dirPath
   * @param {string} relativeDirPath
   * @returns {boolean}
   */
  function rmDir(dirPath, relativeDirPath) {
    let keepsFiles = false;
    const entries = fs.readdirSync(dirPath);
    for (const entry of entries) {
      const filePath = path.join(dirPath, entry);
      const relativeFilePath = path.join(relativeDirPath, entry);
      if (keep(relativeFilePath)) {
        keepsFiles = true;
        continue;
      }
      if (fs.statSync(filePath).isFile()) {
        fs.unlinkSync(filePath);
      } else {
        keepsFiles = rmDir(filePath, relativeFilePath) || keepsFiles;
      }
    }
    if (!keepsFiles) {
      fs.rmdirSync(dirPath);
    }
    return keepsFiles;
  }
}
@hyrious
Copy link

hyrious commented Apr 18, 2025

The main challenge to do that is to replicate the behavior of rollup-plugin-polyfill-node in that repo. Luckily eslint is written with requiring quite a few node builtin modules so we do not have to polyfill all of them. Here's the script I tried to make that work:

// run-esbuild.mjs
import * as esbuild from 'esbuild'
import { getModules } from './node_modules/rollup-plugin-polyfill-node/dist/es/modules.js'

await esbuild.build({
  entryPoints: ['index.js'],
  bundle: true,
  format: 'esm',
  outfile: 'linter.mjs',
  plugins: [polyfill()],
  banner: { js: 'if (!global) { var global = globalThis || window; }' },
  inject: ['./inject.js'],
  logLevel: 'info'
}).catch(() => process.exit(1))

await esbuild.build({
  entryPoints: ['example/script.js'],
  bundle: true,
  outfile: 'example/bundle.js',
  logLevel: 'info'
}).catch(() => process.exit(1))

function polyfill() {
  const mods = getModules()
  return {
    name: 'polyfill',
    setup(build) {
      // Not all node builtins are captured here, just work for the example case.
      build.onResolve({ filter: /^node:|^(process|_inherits)$/ }, args => {
        const name = args.path.replace('node:', '')
        if (mods.has(name)) {
          return { namespace: 'polyfill', path: name, sideEffects: false }
        }
      })
      build.onLoad({ namespace: 'polyfill', filter: /(?:)/ }, args => {
        return { contents: mods.get(args.path) }
      })
    }
  }
}

// inject.js
import process from 'node:process'

export { process }

Save the run-esbuild.mjs and inject.js in that repo's root folder and run node run-esbuild.mjs will make it work (you may have to run npm install in both root and example folder first).


I saw your issue when running the browserified eslint inside a web extension doesn't work UziTech/eslint-linter-browserify#519. The error message says the CSP forbids running any untrusted code (via eval() or Function()). However it seems eslint is depending ajv to validate eslint configurations by matching them to a JSON schema, where ajv uses Function() to compile json schemas into js functions. So all you can do is try finding a place where evaluating untrusted code is permitted (like a web pakge without CSP).

Anyway, this is not a problem with esbuild.

@erosman
Copy link
Author

erosman commented Apr 18, 2025

Thank you for taking time to look into this issue.

I managed to get it built with your script, although as you have mentioned, it doesn’t solve the Ajv issue.

If I may ask, what is the purpose of

await esbuild.build({
  entryPoints: ['example/script.js'],
  bundle: true,
  outfile: 'example/bundle.js',
  logLevel: 'info'
}).catch(() => process.exit(1))

I get ...

✘ [ERROR] Could not resolve "example/script.js"

@hyrious
Copy link

hyrious commented Apr 18, 2025

@erosman It is used to bundle the e2e test in that repo: https://github.com/UziTech/eslint-linter-browserify/blob/master/example/script.js. Since I'm using a relative path, the build script must be executed (i.e. with cwd) in the root folder of the repo to make it find the correct file.

@erosman
Copy link
Author

erosman commented Apr 24, 2025

@hyrious Do you have any ideas about #4140?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants