Skip to content
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

Default exports not handled correctly when external=true and module is dynamically imported (node16) #3737

Open
ksiondag opened this issue Apr 26, 2024 · 0 comments

Comments

@ksiondag
Copy link

ksiondag commented Apr 26, 2024

I was trying an idea mentioned here: #944

Have you considered doing this with an esbuild plugin and bundle: true? The plugin could mark all imported paths as external but have the side effect of kicking off additional builds (as long as the path hasn't been seen before). That should let you experiment with doing this.

I found that it worked for my purposes, but that dynamic importing did not work when using default exports.

Example to reproduce:

$ node --version
v16.20.2

./package.json:

{
  "dependencies": {
    "esbuild": "^0.20.2"
  }
}

./src/a.js:

const dynamicLoadBug = async () => {
    const b = await import('./b');
    b.example();
    b.default();
};

dynamicLoadBug();

./src/b.js:

export const example = () => {
    console.log('Hello, World!');
};

export default example;

./script/build.ts:

const esbuild = require('esbuild');

const OUTPUT_DIR = './dist';

const entryPoints = [
  './src/a.js',
];

const alreadyBuilt = [...entryPoints];

const externalizeRelativeImports = {
  name: 'allIndexFilesExternal',
  setup(_build) {
    _build.onResolve({filter: /^\./}, async (args) => {
      if (args.kind === 'entry-point') {
        return null;
      }
      const absoluteFilePath = `${args.resolveDir}/${args.path}.js`;
      const resolveResult = {
        path: `./${args.path}.js`,
        external: true,
      };
      if (!alreadyBuilt.includes(absoluteFilePath)) {
        alreadyBuilt.push(absoluteFilePath);
        await _build.esbuild.build({
          ..._build.initialOptions,
          entryPoints: [absoluteFilePath],
        });
      }
      return resolveResult;
    });
  },
};

const plugins = [
  externalizeRelativeImports,
];

const options = {
  entryPoints,
  bundle: true,
  outbase: './',
  outdir: OUTPUT_DIR,
  platform: 'node',
  target: 'node16',
  plugins,
};

async function build() {
  await esbuild.build(options);
}

build();

With this plugin of mine, I get this result:

$ node scripts/build.js
$ node dist/src/a.js   
Hello, World!
/path/to/esbuild-external-dynamic-load-bug/dist/src/a.js:28
  b.default();
           ^

TypeError: b.default is not a function
    at dynamicLoadBug (/path/to/esbuild-external-dynamic-load-bug/dist/src/a.js:28:12)

But without using the plugin, I get:

$ node dist/src/a.js   
Hello, World!
Hello, World!

The code causing the error looks like:

var dynamicLoadBug = async () => {
  const b = await import("././b.js");
  b.example();
  b.default();
};

And if I manually change it to:

var dynamicLoadBug = async () => {
  const b = (await import("././b.js")).default;
  b.example();
  b.default();
};

I get the same output as the normal, non-plugin setup.

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

1 participant