evanw / esbuild

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

[bug] generate tons of unnecessary js files when using react-syntax-highlighter #1836

Open baurine opened 2 years ago

baurine commented 2 years ago

Reproduce steps

  1. Initial a react project by npx esbuild-create-react-app my-app, choose TypeScript template. esbuild-create-react-app

  2. Install react-syntax-highlighter by yarn add react-syntax-highlighter

  3. Test it in src/App.tsx:

    import { Light as SyntaxHighlighter } from 'react-syntax-highlighter'
    import sql from 'react-syntax-highlighter/dist/esm/languages/hljs/sql'
    import darkTheme from 'react-syntax-highlighter/dist/esm/styles/hljs/atom-one-dark'
    
    SyntaxHighlighter.registerLanguage('sql', sql)
    
    export default function App() {
      return (
        <div>
          <SyntaxHighlighter
            language='sql'
            style={darkTheme}
            customStyle={{
              background: 'none',
              padding: 0,
              overflowX: 'hidden'
            }}
          >
            {'SELECT COUNT(*) from test;'}
          </SyntaxHighlighter>
        </div>
      )
    }
  4. Build it by enabling splitting, esm format:

    build({ ..., splitting: true, format: 'esm' })

    works fine:

    image

  5. But in the output dir, it generates more than 1 thousand unnecessary js files.

    $ ls -alh
    ...
    -rw-r--r-- 1 bao bao 5.8K 12月  7 23:01 xml-B2ETNLYP.js
    -rw-r--r-- 1 bao bao 9.2K 12月  7 23:01 xml-B2ETNLYP.js.map
    -rw-r--r-- 1 bao bao  166 12月  7 23:01 xml-doc-HP46ILPO.js
    -rw-r--r-- 1 bao bao   93 12月  7 23:01 xml-doc-HP46ILPO.js.map
    -rw-r--r-- 1 bao bao  157 12月  7 23:01 xojo-SWGGOJ4T.js
    -rw-r--r-- 1 bao bao   93 12月  7 23:01 xojo-SWGGOJ4T.js.map
    -rw-r--r-- 1 bao bao 7.3K 12月  7 23:01 xquery-K2TFH4NY.js
    -rw-r--r-- 1 bao bao 9.6K 12月  7 23:01 xquery-K2TFH4NY.js.map
    -rw-r--r-- 1 bao bao  163 12月  7 23:01 xquery-YN3HSIRP.js
    -rw-r--r-- 1 bao bao   93 12月  7 23:01 xquery-YN3HSIRP.js.map
    -rw-r--r-- 1 bao bao  157 12月  7 23:01 yaml-BMW3GS7H.js
    -rw-r--r-- 1 bao bao   93 12月  7 23:01 yaml-BMW3GS7H.js.map
    -rw-r--r-- 1 bao bao 3.9K 12月  7 23:01 yaml-O7ZZ6HCD.js
    -rw-r--r-- 1 bao bao 6.8K 12月  7 23:01 yaml-O7ZZ6HCD.js.map
    -rw-r--r-- 1 bao bao  157 12月  7 23:01 yang-NXGAW5PK.js
    -rw-r--r-- 1 bao bao   93 12月  7 23:01 yang-NXGAW5PK.js.map
    -rw-r--r-- 1 bao bao 3.2K 12月  7 23:01 zephir-V53UVJEH.js
    -rw-r--r-- 1 bao bao 5.1K 12月  7 23:01 zephir-V53UVJEH.js.map
    -rw-r--r-- 1 bao bao  154 12月  7 23:01 zig-KOYKB6KL.js
    -rw-r--r-- 1 bao bao   93 12月  7 23:01 zig-KOYKB6KL.js.map
    
    $ ls -alh | wc -l
    1343

    Demo code: https://github.com/miscs-test/esbuild-demo/commit/d0754086a3224e6d8c240dbe1fd432b4ab26a908

Workaround

Replace the following one line code:

- import { Light as SyntaxHighlighter } from 'react-syntax-highlighter'
+ import SyntaxHighlighter from 'react-syntax-highlighter/dist/esm/light'

The unnecessary js files are disappeared.

$ ls -alh
total 2.5M
drwxrwxr-x 2 bao bao 4.0K 12月  7 23:17 .
drwxrwxr-x 8 bao bao 4.0K 12月  7 23:17 ..
-rw-rw-r-- 1 bao bao 3.8K 12月  7 23:17 favicon.ico
-rw-rw-r-- 1 bao bao  114 12月  7 23:17 index.css
-rw-rw-r-- 1 bao bao   93 12月  7 23:17 index.css.map
-rw-rw-r-- 1 bao bao  872 12月  7 23:17 index.html
-rw-rw-r-- 1 bao bao 948K 12月  7 23:17 index.js
-rw-rw-r-- 1 bao bao 1.5M 12月  7 23:17 index.js.map
-rw-rw-r-- 1 bao bao 5.3K 12月  7 23:17 logo192.png
-rw-rw-r-- 1 bao bao 9.5K 12月  7 23:17 logo512.png
-rw-rw-r-- 1 bao bao  493 12月  7 23:17 manifest.json
-rw-rw-r-- 1 bao bao   67 12月  7 23:17 robots.txt

$ ls -alh | wc -l
13

Demo code: https://github.com/miscs-test/esbuild-demo/commit/349d1d359e1e3aca0791ee7c10d0c15c20976836

Reason

node_modules/react-syntax-highlighter/dist/esm/index.js:

export { default } from './default-highlight';
export { default as LightAsync } from './light-async';
export { default as Light } from './light';
export { default as PrismAsyncLight } from './prism-async-light';
export { default as PrismAsync } from './prism-async';
export { default as PrismLight } from './prism-light';
export { default as Prism } from './prism';
export { default as createElement } from './create-element';

It exports both sync and async API, for example, Light and LightAsync. The LightAsync finally dynamic imports tons of js files that are related to the kinds of languages highlighting.

Although we only import the sync API, import { Light as SyntaxHighlighter } from 'react-syntax-highlighter', esbuild compile the async API as well.

Not sure whether this behavior is expected or not.

evanw commented 2 years ago

This is the way the bundler currently works, so in that sense it's expected. Tree shaking happens after source traversal, and dynamic imports become additional entry points during source traversal. Perhaps dynamic entry points could be treated as a special tree-shakeable form of entry-like source file but this would involve overhauling the current linking algorithm.