evanw / esbuild

An extremely fast bundler for the web
https://esbuild.github.io/
MIT License
38.1k stars 1.15k forks source link

Import isn't getting imported with code splitting / chunks enabled #2208

Open taoeffect opened 2 years ago

taoeffect commented 2 years ago

We have a rather large project using esbuild 0.14.38 and the esbuild-sass-plugin, and when splitting is enabled using the following config, esbuild fails to import files when it should:

  const esbuildOptionBags = {
    // Native options that are shared between our esbuild tasks.
    default: {
      bundle: true,
      chunkNames: '[name]-[hash]-cached',
      define: {
        'process.env.BUILD': "'web'", // Required by Vuelidate.
        'process.env.CI': `'${CI}'`,
        'process.env.GI_VERSION': `'${GI_VERSION}'`,
        'process.env.LIGHTWEIGHT_CLIENT': `'${LIGHTWEIGHT_CLIENT}'`,
        'process.env.NODE_ENV': `'${NODE_ENV}'`,
        'process.env.VUEX_STRICT': VUEX_STRICT
      },
      external: ['crypto', '*.eot', '*.ttf', '*.woff', '*.woff2'],
      format: 'esm',
      incremental: true,
      loader: {
        '.eot': 'file',
        '.ttf': 'file',
        '.woff': 'file',
        '.woff2': 'file'
      },
      minifyIdentifiers: production,
      minifySyntax: production,
      minifyWhitespace: production,
      outdir: distJS,
      sourcemap: development,
      // Warning: split mode has still a few issues. See https://github.com/okTurtles/group-income/pull/1196
      splitting: !grunt.option('no-chunks'),
      watch: false // Not using esbuild's own watch mode since it involves polling.
    },
    // Native options used when building the main entry point.
    main: {
      assetNames: '../css/[name]',
      entryPoints: [`${srcDir}/main.js`]
    },
    // Native options used when building our service worker(s).
    serviceWorkers: {
      entryPoints: ['./frontend/controller/serviceworkers/primary.js']
    }
  }

Specifically, I have a fileB that looks like this:

import '~/shared/fileC.js'

I have a fileA that looks like this:

import '~/shared/fileC.js'
import '~/shared/fileB.js'

When fileA is loaded, fileB is loaded, but inside of it, it thinks that fileC isn't loaded.

The hack we've been forced to use is to import something from that file. In fileB:

import { something } from '~/shared/fileC.js'

console.debug('esbuild import hack:', something)

Then things work.

I'm not sure if this issue is related to #399 or not.

hyrious commented 2 years ago

I don't understand. Can you provide some example code to demonstrate what's wrong and what's your expected behavior?

e.g. repl link

taoeffect commented 2 years ago

I edited my comment to make it a little clearer what's happening.

Also, now the bug has disappeared. I changed something but I don't know what exactly caused it to disappear.

However, I've also found a commit where I can reproduce the behavior if you'd like to take a look at this esbuild-bug branch in our project.

To test you'll need to clone the project and use that branch, then npm i -g grunt-cli && npm install in the project folder. Then grunt dev and wait for it to say Local: http://localhost:3000. Then visit http://localhost:3000

To enable the hack/fix, in the file ~/frontend/model/contracts/chatroom.js, change line 6 from:

import '~/shared/domains/chelonia/chelonia.js'

To:

import { GIMessage } from '~/shared/domains/chelonia/chelonia.js'

And then uncomment lines 26-27:

// HACK: work around esbuild code splitting / chunking bug: https://github.com/evanw/esbuild/issues/399
// console.debug('esbuild import hack', GIMessage && '')

Then save the file and wait a few seconds, and refresh the page.

taoeffect commented 2 years ago

Technically the line import '~/shared/domains/chelonia/chelonia.js' is also a hack that shouldn't be there because it was imported earlier from a different file. So this is really 2 bugs and 2 hacks. But now (for unknown reasons) we're back down to 1 hack. :P

hyrious commented 2 years ago

Although I didn't successfully narrow down your project to a minimal reproduction, it looks really like 399 where common chunks may change their execution order. Your sbp requires the order to work. In which situation turning off code splitting is the right choice.

The correct way to solve this problem involves a big refactoring to the bundler to carefully capture side effects in each files and replay them in the correct order. That's exactly what parcel2's hoisting does (at the end of its hoisting doc, although not very obvious, it requires you to config splitting thresholds in package.json and manually add dummy codes to enlarge the file). Evan has thought of it but still lack of time to do this.

taoeffect commented 2 years ago

Thanks for looking into this @hyrious! I admit I don't really understand what you're saying, and it's confusing to me why esbuild can't simply build a graph of sorts of the order in which files are included and include them in that order. We definitely need code-splitting because of the size of the project (the site would take too long to load on a slow connection otherwise).

But anyway, just wanted to say thanks again for looking into this, hopefully Evan or someone else can figure this out. esbuild is a great project, we're mostly very happy with it, and we're very grateful for the speed improvement it gives.