{"id":15658357,"url":"https://github.com/florianrappl/bundler-comparison","last_synced_at":"2025-05-05T16:44:06.152Z","repository":{"id":44340664,"uuid":"226804321","full_name":"FlorianRappl/bundler-comparison","owner":"FlorianRappl","description":"Comparison of web resource bundlers. :package:","archived":false,"fork":false,"pushed_at":"2020-03-20T16:59:45.000Z","size":359,"stargazers_count":22,"open_issues_count":0,"forks_count":1,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-03-30T22:32:01.736Z","etag":null,"topics":["article","bundler","comparison","frontend","resources","sample","web"],"latest_commit_sha":null,"homepage":"https://blog.bitsrc.io/choosing-the-right-javascript-bundler-in-2020-f9b1eae0d12b","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/FlorianRappl.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null},"funding":{"github":["FlorianRappl"],"custom":["https://www.paypal.me/FlorianRappl"]}},"created_at":"2019-12-09T06:50:31.000Z","updated_at":"2024-11-14T14:12:12.000Z","dependencies_parsed_at":"2022-08-29T17:51:44.637Z","dependency_job_id":null,"html_url":"https://github.com/FlorianRappl/bundler-comparison","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/FlorianRappl%2Fbundler-comparison","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/FlorianRappl%2Fbundler-comparison/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/FlorianRappl%2Fbundler-comparison/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/FlorianRappl%2Fbundler-comparison/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/FlorianRappl","download_url":"https://codeload.github.com/FlorianRappl/bundler-comparison/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252535256,"owners_count":21763922,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["article","bundler","comparison","frontend","resources","sample","web"],"created_at":"2024-10-03T13:12:04.654Z","updated_at":"2025-05-05T16:44:06.134Z","avatar_url":"https://github.com/FlorianRappl.png","language":"TypeScript","funding_links":["https://github.com/sponsors/FlorianRappl","https://www.paypal.me/FlorianRappl"],"categories":[],"sub_categories":[],"readme":"# Bundler Comparison\n\nFor the comparison we use a super small project consisting of some dependencies:\n\n- TypeScript\n- SASS\n- React with React DOM\n\nBy default all project folders have thus been set up using the following command:\n\n```sh\nnpm i react react-dom @types/react @types/react-dom typescript sass @types/node\n```\n\nWe installed all the typings for these dependencies, too. The requirements for the app bundling are:\n\n- We load one component lazy (bundle splitting)\n- We have 1 HTML file\n- We have 1 CSS file\n- We have 1 image (JPG) file\n\nWe should obtain 5 files (1 html, 2 js, 1 css, 1 jpg).\n\nOur key metrics are:\n\n- The number of packages have been installed for bundling\n- The ease of setup (how many packages had to be installed manually, how large is the configuration)\n- The time of the 1st build and subsequent builds\n- How hard is a dev mode to establish\n- What is the bundle size (minimum for the side-bundle, full for the main bundle)\n- How many changes to our original source code (available in `src` have been necessary)\n\n## Results\n\nNow before going into details, these are the results.\n\n|                               | Webpack   | Parcel   | Rollup    | Browserify | FuseBox    | Brunch    |\n|-------------------------------|-----------|----------|-----------|------------|------------|-----------|\n| Released                      | 2011      | 2017     | 2014      | 2010       | 2016       | 2011      |\n| Current Version               | 4\\.41\\.3  | 1\\.12\\.4 | 1\\.27\\.13 | 16\\.5\\.0   | 3\\.7\\.1    | 3\\.0\\.0   |\n| GitHub Stars                  | 52,300    | 34,100   | 17,200    | 13,000     | 3,900      | 6,600     |\n| Collaborators                 | 573       | 215      | 200       | 182        | 152        | 122       |\n| Configuration                 | JS Module | Custom*  | JS Module | JS Script  | JS Script  | JS Module |\n| CLI Tooling                   | Separate  | Yes      | Yes       | Yes        | No         | Yes       |\n| Weekly Downloads              | 8,807,158 | 101,988  | 1,005,643 | 675,444    | 9,303      | 11,048    |\n| Open Issues                   | 374       | 810      | 152       | 310        | 54         | 143       |\n| Packages Added                | 9         | 1        | 8         | 3          | 3          | 4         |\n| Packages Installed            | 457       | 743      | 229       | 272        | 505        | 404       |\n| Configuration Size \\[LoC\\]    | 40        | 0        | 50        | 56         | 23         | 14        |\n| Bundle Size \\[kB\\]            | 130       | 132      | 132       | 130        | 132        | 138       |\n| Bundle Splitting              | Yes       | Yes      | Yes       | No         | Yes        | No        |\n| Speed / 1\\. Run\\[s\\]          | 5         | 7        | 7         | 7          | 5          | 6         |\n| Speed / Subsequent Runs \\[s\\] | 4         | 2        | 2         | 7          | 4          | 6         |\n| Required Changes              | 2         | 0        | 3         | 3          | 3          | 4         |\n| Difficulty                    | Average   | Simple   | Difficult | Average    | Average    | Average   |\n| Flexibility                   | High      | Average  | Average   | High       | Average    | Low       |\n\nwhere *) Parcel uses standard configurations of all the tools, e.g., `.babelrc` for Babel, `.browserlist` etc.\n\n**Summary**: Depending on what you want to do there is a bundler for your choice.\n\n## Browserify\n\n### Installation\n\nThe installation only requires three packages.\n\n```sh\nnpm i browserify tsify tinyify --save-dev\n```\n\nThis results in about 272 new packages.\n\n```plain\n+ browserify@16.5.0\n+ tsify@4.0.1\n+ tinyify@2.5.2\nadded 272 packages from 242 contributors and audited 1888 packages in 39.221s\nfound 0 vulnerabilities\n```\n\n### Setup\n\nWe had to write about a 56 LOC configuration file.\n\n```js\nconst browserify = require('browserify');\nconst tsify = require('tsify');\nconst tinyify = require('tinyify');\nconst fs = require('fs');\nconst path = require('path');\nconst sass = require('sass');\n\nconst source = path.resolve(__dirname, 'src');\nconst target = path.resolve(__dirname, 'dist');\n\nif (!fs.existsSync(target)) {\n  fs.mkdirSync(target);\n}\n\nfunction compileCss() {\n  const outFile = path.resolve(target, 'style.css');\n\n  sass.render(\n    {\n      file: path.resolve(source, 'style.scss'),\n      outFile,\n      outputStyle: 'compressed',\n    },\n    (err, result) =\u003e {\n      if (err) {\n        console.errror(err);\n      } else {\n        fs.writeFile(outFile, result.css, err =\u003e err \u0026\u0026 console.error(err));\n      }\n    },\n  );\n}\n\nfunction compileJs() {\n  browserify()\n    .add(path.resolve(source, 'app.tsx'))\n    .plugin(tsify, { noImplicitAny: true })\n    .plugin(tinyify, {\n      env: {\n        NODE_ENV: 'production',\n      },\n    })\n    .bundle()\n    .on('error', console.error)\n    .pipe(fs.createWriteStream(path.resolve(target, 'app.js')));\n}\n\nfunction copyRest() {\n  ['index.html', 'smiley.jpg'].forEach(file =\u003e {\n    fs.copyFile(path.resolve(source, file), path.resolve(target, file), err =\u003e err \u0026\u0026 console.error(err));\n  });\n}\n\ncompileCss();\ncompileJs();\ncopyRest();\n```\n\n### Modifications\n\nSince the HTML file could not be used as any kind of entry we had to already prepare it for the output.\n\n```diff\n\u003c!DOCTYPE html\u003e\n\u003chtml lang=\"en\"\u003e\n\u003chead\u003e\n\u003cmeta charset=\"UTF-8\"\u003e\n\u003cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"\u003e\n\u003cmeta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\"\u003e\n\u003ctitle\u003eBundler Comparison\u003c/title\u003e\n-\u003clink rel=\"stylesheet\" href=\"style.scss\"\u003e\n+\u003clink rel=\"stylesheet\" href=\"style.css\"\u003e\n\u003c/head\u003e\n\u003cbody\u003e\n\u003ch1\u003eTest Page\u003c/h1\u003e\n\u003cdiv id=\"app\"\u003e\u003c/div\u003e\n-\u003cscript src=\"app.tsx\"\u003e\u003c/script\u003e\n+\u003cscript src=\"app.js\"\u003e\u003c/script\u003e\n\u003c/body\u003e\n\u003c/html\u003e\n```\n\nAdditionally, there is right now no package to deal with PNG files. The closest we have is `imgurify`, however, we do not want the image to be base64 encoded in the bundle. We want the image to be standalone (referenced).\n\n```diff\n        \u003cp\u003e\n-          \u003cimg src={require('smiley.jpg')} alt=\"A classic smiley\" /\u003e\n+          \u003cimg src=\"smiley.jpg\" alt=\"A classic smiley\" /\u003e\n        \u003c/p\u003e\n```\n\n### Running\n\nRunning is done via one command:\n\n```sh\nnode bundle.js\n```\n\n### Results\n\nThe initial build took about 7s and resulted in a 130 kB bundle.\n\n```plain\nreal    0m6.640s\nuser    0m8.813s\nsys     0m1.078s\n```\n\nThe stylesheet was minified to 83 bytes, the additional bundle was **not created**.\n\nSubsequent runs are exactly the same.\n\n## Brunch\n\n### Installation\n\nThe installation only requires four packages.\n\n```sh\nnpm i brunch typescript-brunch terser-brunch sass-brunch  --save-dev\n```\n\nThis results in about 404 new packages.\n\n```plain\n+ sass-brunch@2.10.8\n+ typescript-brunch@2.3.0\n+ terser-brunch@4.0.0\n+ brunch@3.0.0\nadded 404 packages from 260 contributors and audited 1661 packages in 57.415s\nfound 2 low severity vulnerabilities\n```\n\n### Setup\n\nWe had to write about a 14 LOC configuration file.\n\n```js\nmodule.exports = {\n  paths: {\n    public: 'dist',\n    watched: ['src'],\n  },\n  files: {\n    javascripts: {\n      joinTo: 'app.js',\n    },\n    stylesheets: {\n      joinTo: 'style.css',\n    },\n  },\n};\n```\n\n### Modifications\n\nWe had to remove the lazy loading as `import` function calls are not supported (not even to \"hard-wire\" the content).\n\nFurthermore, `require` calls are not picked up. So we can spare this kind of sharade and just reference the file.\n\n```diff\nimport * as React from 'react';\nimport { render } from 'react-dom';\n+import Page from './Page';\n\n-const Page = React.lazy(() =\u003e import('./Page'));\n\nconst App = () =\u003e {\n  const [showPage, setShowPage] = React.useState(false);\n\n  return (\n    \u003cReact.Suspense fallback={\u003cb\u003eLoading ...\u003c/b\u003e}\u003e\n      \u003cdiv className=\"main-content\"\u003e\n        \u003ch2\u003eLet's talk about smileys\u003c/h2\u003e\n        \u003cp\u003eMore about smileys can be found here ...\u003c/p\u003e\n        \u003cp\u003e\n-          \u003cimg src={require('./smiley.jpg')} alt=\"A classic smiley\" /\u003e\n+          \u003cimg src=\"./smiley.jpg\" alt=\"A classic smiley\" /\u003e\n        \u003c/p\u003e\n        \u003cp\u003e\n          \u003cbutton onClick={() =\u003e setShowPage(!showPage)}\u003eToggle page\u003c/button\u003e\n        \u003c/p\u003e\n      \u003c/div\u003e\n      {showPage \u0026\u0026 \u003cPage /\u003e}\n    \u003c/React.Suspense\u003e\n  );\n};\n\nrender(\u003cApp /\u003e, document.querySelector('#app'));\n```\n\nThe \"static\" files `index.html` and `smiley.jpg` had to be moved into a subfolder called \"assets\", which was automatically used for copying static files into the target.\n\n### Running\n\nRunning is done via one command:\n\n```sh\nbrunch build --production\n```\n\n### Results\n\nThe initial build took about 6s and resulted in a 138 kB bundle.\n\n```plain\n\n11:33:43 - info: compiling\n11:33:43 - info: compiled 16 files into 2 files, copied index.html in 5.2 sec\n\nreal    0m5.837s\nuser    0m6.859s\nsys     0m1.000s\n```\n\nThe stylesheet was minified to 84 bytes, the additional bundle was **not created**.\n\nSubsequent runs are exactly the same.\n\n## FuseBox\n\n### Installation\n\nThe installation only requires three packages.\n\n```sh\nnpm i fuse-box node-sass terser --save-dev\n```\n\nThis results in about 505 new packages.\n\n```plain\n+ node-sass@4.13.0\n+ terser@4.4.3\n+ fuse-box@3.7.1\nadded 505 packages from 351 contributors and audited 2018 packages in 42.65s\nfound 1 low severity vulnerability\n```\n\n### Setup\n\nWe had to write about a 23 LOC configuration file.\n\n```js\nconst { FuseBox, SassPlugin, CSSPlugin, WebIndexPlugin, QuantumPlugin, CopyPlugin } = require('fuse-box');\n\nconst fuse = FuseBox.init({\n  homeDir: 'src',\n  target: 'browser',\n  output: 'dist/$name.js',\n  plugins: [\n    WebIndexPlugin({ template: 'src/index.html' }),\n    [SassPlugin(), CSSPlugin({ outFile: _ =\u003e 'dist/style.css' })],\n    CopyPlugin({ files: ['*.jpg'], dest: '' }),\n    QuantumPlugin({\n      css: true,\n      bakeApiIntoBundle: true,\n      uglify: true,\n    }),\n  ],\n});\n\nfuse\n  .bundle('app')\n  .instructions(' \u003e app.tsx');\n\nfuse.run();\n```\n\n### Modifications\n\nWe had to reference the SASS file in the beginning for our application.\n\n```tsx\nimport './style.scss';\n```\n\nThe HTML template was slightly changed.\n\n```diff\n\u003c!DOCTYPE html\u003e\n\u003chtml lang=\"en\"\u003e\n\u003chead\u003e\n\u003cmeta charset=\"UTF-8\"\u003e\n\u003cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"\u003e\n\u003cmeta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\"\u003e\n\u003ctitle\u003eBundler Comparison\u003c/title\u003e\n-\u003clink rel=\"stylesheet\" href=\"style.scss\"\u003e\n+\u003clink rel=\"stylesheet\" href=\"style.css\"\u003e\n\u003c/head\u003e\n\u003cbody\u003e\n\u003ch1\u003eTest Page\u003c/h1\u003e\n\u003cdiv id=\"app\"\u003e\u003c/div\u003e\n-\u003cscript src=\"./app.tsx\"\u003e\u003c/script\u003e\n+$bundles\n\u003c/body\u003e\n\u003c/html\u003e\n```\n\nThe HTML template had to be referenced in the Fuse loader script.\n\n### Running\n\nRunning is done via one command:\n\n```sh\nnode fuse.js\n```\n\n### Results\n\nThe initial build took about 5s and resulted in a 132 kB bundle.\n\n```plain\n--- FuseBox 3.7.1 ---\n  → Typescript config file:  /tsconfig.json\n  → Applying automatic alias based on baseUrl in tsconfig.json\n  → \n        Page =\u003e \"~/Page\"\n        app =\u003e \"~/app\"\n  → Typescript script target: ES2015\n\n--------------------------\nBundle \"app\" \n\n    Page.jsx\n    app.jsx\n    smiley.jpg\n    style.scss\n└──  (4 files,  2 kB) default\n└── fuse-box-css 1.3 kB (1 files)\n└── object-assign 2.2 kB (1 files)\n└── process 3.3 kB (1 files)\n└── prop-types 4.4 kB (2 files)\n└── react-dom 1 MB (3 files)\n└── react 79.9 kB (3 files)\n└── scheduler 43.3 kB (6 files)\nsize: 1.2 MB in 1s 189ms\n\n  -------------- \n\nLaunching quantum core\n  → Generating abstraction, this may take a while\n  → Abstraction generated\n  → Process bundle app\n  → Process package default \n  →   Files: 4 \n  → Process package fuse-box-css \n  →   Files: 1 \n  → Process package object-assign \n  →   Files: 1 \n  → Process package process \n  →   Files: 1 \n  → Process package prop-types \n  →   Files: 2 \n  → Process package react-dom \n  →   Files: 3 \n  → Process package react \n  →   Files: 3 \n  → Process package scheduler \n  →   Files: 6 \n  → Create split bundle 9d21301d with entry point default/Page.jsx\n  → QuantumBit: Adding default/Page.jsx to 9d21301d\n  → Render bundle app\n  → Render bundle 9d21301d\n  → Uglifying app...\n  → Using terser because the target is greater than ES5 or es6 option is set\n  → Done uglifying app\n  size:  129.3 kB, 41.2 kB (gzipped)\n  → Uglifying 9d21301d...\n  → Using terser because the target is greater than ES5 or es6 option is set\n  → Done uglifying 9d21301d\n  size:  1.6 kB, 844 Bytes (gzipped)\n\nreal    0m5.409s\nuser    0m5.422s\nsys     0m1.172s\n```\n\nThe stylesheet was *not* minified and remained at 142 bytes, the additional bundle came at 1604 bytes.\n\nSubsequent builds seem to be slightly faster with 4s.\n\n```plain\n--- FuseBox 3.7.1 ---\n\n--------------------------\nBundle \"app\" \n\n    Page.jsx\n    app.jsx\n    smiley.jpg\n    style.scss\n└──  (4 files,  2 kB) default\n└── fuse-box-css@0.0.1 1.3 kB (0 files)\n└── object-assign@4.1.1 2.2 kB (0 files)\n└── process@0.0.0 3.3 kB (0 files)\n└── prop-types@15.7.2 4.4 kB (0 files)\n└── react-dom@16.12.0 1 MB (0 files)\n└── react@16.12.0 79.9 kB (0 files)\n└── scheduler@0.18.0 43.3 kB (0 files)\nsize: 1.2 MB in 117ms\n\n  -------------- \n\nLaunching quantum core\n  → Generating abstraction, this may take a while\n  → Abstraction generated\n  → Process bundle app\n  → Process package default \n  →   Files: 4 \n  → Process package fuse-box-css \n  →   Files: 1 \n  → Process package object-assign \n  →   Files: 1 \n  → Process package process \n  →   Files: 1 \n  → Process package prop-types \n  →   Files: 2 \n  → Process package react-dom \n  →   Files: 3 \n  → Process package react \n  →   Files: 3 \n  → Process package scheduler \n  →   Files: 6 \n  → Create split bundle 9d21301d with entry point default/Page.jsx\n  → QuantumBit: Adding default/Page.jsx to 9d21301d\n  → Render bundle app\n  → Render bundle 9d21301d\n  → Uglifying app...\n  → Using terser because the target is greater than ES5 or es6 option is set\n  → Done uglifying app\n  size:  129.3 kB, 41.2 kB (gzipped)\n  → Uglifying 9d21301d...\n  → Using terser because the target is greater than ES5 or es6 option is set\n  → Done uglifying 9d21301d\n  size:  1.6 kB, 844 Bytes (gzipped)\n\nreal    0m4.350s\nuser    0m4.469s\nsys     0m1.141s\n```\n\n## Parcel\n\n### Installation\n\nThe installation only requires a single package.\n\n```sh\nnpm i parcel-bundler --save-dev\n```\n\nThis results in about 743 new packages.\n\n```plain\n+ parcel-bundler@1.12.4\nadded 743 packages from 533 contributors and audited 8421 packages in 91.127s\nfound 0 vulnerabilities\n```\n\n### Setup\n\nNothing had to be changed. Everything could run immediately without any configuration.\n\n### Modifications\n\nThe original source code was left unchanged.\n\n### Running\n\nRunning is done via one command:\n\n```sh\nparcel build src/index.html\n```\n\n### Results\n\nThe initial build took about 7s and resulted in a 132 kB bundle.\n\n```plain\n✨  Built in 5.84s.\n\ndist/app.9baf3188.js.map        271.5 KB    125ms\ndist/app.9baf3188.js           132.24 KB    5.81s\ndist/smiley.b199c00b.jpg        99.38 KB    666ms\ndist/Page.cb038bbe.js            2.04 KB    1.29s\ndist/Page.cb038bbe.js.map        1.11 KB      5ms\ndist/index.html                    374 B    358ms\ndist/style.cb6d7d54.css.map        304 B      5ms\ndist/style.cb6d7d54.css            130 B    4.95s\n\nreal    0m7.293s\nuser    0m11.828s\nsys     0m4.563s\n```\n\nThe stylesheet was minified to 130 bytes, the additional bundle came at 2 kB.\n\nSubsequent builds took about 2s.\n\n```plain\n✨  Built in 734ms.\n\ndist/app.9baf3188.js.map        271.5 KB    152ms\ndist/app.9baf3188.js           132.24 KB    478ms\ndist/smiley.b199c00b.jpg        99.38 KB     48ms\ndist/Page.cb038bbe.js            2.04 KB     25ms\ndist/Page.cb038bbe.js.map        1.11 KB     14ms\ndist/index.html                    374 B     16ms\ndist/style.cb6d7d54.css.map        304 B     14ms\ndist/style.cb6d7d54.css            130 B     21ms\n\nreal    0m2.201s\nuser    0m1.359s\nsys     0m1.766s\n```\n\n## rollup.js\n\n### Installation\n\nThe installation requires 8 packages.\n\n```sh\nnpm i rollup rollup-plugin-typescript2 rollup-plugin-scss @rollup/plugin-html @rollup/plugin-image @rollup/plugin-node-resolve rollup-plugin-commonjs rollup-plugin-terser --save-dev\n```\n\nThis results in about 229 new packages.\n\n```plain\n+ rollup-plugin-commonjs@10.1.0\n+ rollup-plugin-terser@5.1.2\n+ rollup-plugin-scss@1.0.2\n+ rollup@1.27.9\n+ rollup-plugin-typescript2@0.25.3\n+ @rollup/plugin-node-resolve@6.0.0\n+ @rollup/plugin-image@2.0.0\n+ @rollup/plugin-html@0.1.0\nadded 229 packages from 161 contributors and audited 657 packages in 13.473s\nfound 0 vulnerabilities\n```\n\n### Setup\n\nWe had to write about a 50 LOC configuration file.\n\n```js\nimport typescript from 'rollup-plugin-typescript2';\nimport scss from 'rollup-plugin-scss';\nimport image from '@rollup/plugin-image';\nimport html from '@rollup/plugin-html';\nimport nodeResolve from '@rollup/plugin-node-resolve';\nimport commonjs from 'rollup-plugin-commonjs';\nimport { terser } from 'rollup-plugin-terser';\nimport { readFileSync } from 'fs';\nimport { resolve } from 'path';\n\nconst content = readFileSync(resolve(__dirname, 'src/index.html'), 'utf8');\nconst cssName = 'main.css';\nconst dist = 'dist';\n\nexport default {\n  input: [resolve(__dirname, 'src/app.tsx'), resolve(__dirname, 'src/style.scss')],\n  output: {\n    format: 'amd',\n    dir: dist,\n  },\n  plugins: [\n    nodeResolve(),\n    typescript({\n      objectHashIgnoreUnknownHack: true,\n    }),\n    image({\n      dom: true,\n    }),\n    scss({\n      output: `${dist}/${cssName}`,\n    }),\n    commonjs({\n      sourceMap: false,\n      namedExports: { react: ['createElement', 'Component', 'lazy', 'Fragment', 'useState'], 'react-dom': ['render'] },\n    }),\n    terser(),\n    html({\n      fileName: 'index.html',\n      template: ({ files, publicPath }) =\u003e {\n        const { js = [{ fileName: 'missing.js' }] } = files;\n        return content\n          .replace(\n            '\u003clink rel=\"stylesheet\" href=\"style.scss\"\u003e',\n            `\u003clink rel=\"stylesheet\" href=\"${publicPath}${cssName}\"\u003e`,\n          )\n          .replace('\u003cscript src=\"app.tsx\"\u003e\u003c/script\u003e', `\u003cscript src=\"${publicPath}${js[0].fileName}\"\u003e\u003c/script\u003e`);\n      },\n    }),\n  ],\n};\n```\n\n### Modifications\n\nWe had to add an additional TypeScript declaration file to import standard images:\n\n```ts\ndeclare module '*.jpg';\n```\n\nFurthermore, we are not allowed the mix the classic `require` with `import`. So we had to change the *app.tsx* file.\n\n```diff\nimport * as React from 'react';\nimport { render } from 'react-dom';\n+import smiley from './smiley.jpg';\n\nconst Page = React.lazy(() =\u003e import('./Page'));\n\nconst App = () =\u003e {\n  const [showPage, setShowPage] = React.useState(false);\n\n  return (\n    \u003c\u003e\n      \u003cdiv className=\"main-content\"\u003e\n        \u003ch2\u003eLet's talk about smileys\u003c/h2\u003e\n        \u003cp\u003eMore about smileys can be found here ...\u003c/p\u003e\n        \u003cp\u003e\n+          \u003cimg src={smiley} alt=\"A classic smiley\" /\u003e\n-          \u003cimg src={require('./smiley.jpg')} alt=\"A classic smiley\" /\u003e\n        \u003c/p\u003e\n        \u003cp\u003e\n          \u003cbutton onClick={() =\u003e setShowPage(!showPage)}\u003eToggle page\u003c/button\u003e\n        \u003c/p\u003e\n      \u003c/div\u003e\n      {showPage \u0026\u0026 \u003cPage /\u003e}\n    \u003c/\u003e\n  );\n};\n\nrender(\u003cApp /\u003e, document.querySelector('#app'));\n```\n\n### Running\n\nRunning is done via one command:\n\n```sh\nrollup -c rollup.config.js --environment BUILD:production\n```\n\n### Results\n\nThe initial build took about 7s and resulted in a 132 kB bundle.\n\n```plain\n\n```\n\nThe stylesheet was minified to 130 bytes, the additional bundle came at 2 kB.\n\nSubsequent builds took about 2s.\n\n```plain\n\n```\n\n## Webpack\n\n### Installation\n\nThe installation requires 9 packages.\n\n```sh\nnpm install webpack webpack-cli ts-loader sass-loader css-loader html-loader file-loader extract-loader mini-css-extract-plugin --save-dev\n```\n\nThis results in about 457 new packages.\n\n```plain\n+ html-loader@0.5.5\n+ extract-loader@3.1.0\n+ css-loader@3.2.1\n+ mini-css-extract-plugin@0.8.0\n+ file-loader@5.0.2\n+ sass-loader@8.0.0\n+ ts-loader@6.2.1\n+ webpack-cli@3.3.10\n+ webpack@4.41.2\nadded 456 packages from 240 contributors and audited 5598 packages in 37.111s\nfound 0 vulnerabilities\n```\n\n### Setup\n\nA new `webpack.config.js` with 40 LOC was required.\n\n```js\nconst path = require('path');\nconst MiniCssExtractPlugin = require('mini-css-extract-plugin');\n\nmodule.exports = {\n  entry: ['./src/app.tsx', './src/index.html', './src/style.scss'],\n  module: {\n    rules: [\n      {\n        test: /\\.(png|jpe?g|gif)$/i,\n        loader: 'file-loader',\n      },\n      {\n        test: /\\.html$/,\n        use: ['file-loader?name=[name].[ext]', 'extract-loader', 'html-loader'],\n      },\n      {\n        test: /\\.s[ac]ss$/i,\n        use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader'],\n      },\n      {\n        test: /\\.tsx?$/,\n        use: 'ts-loader',\n        exclude: /node_modules/,\n      },\n    ],\n  },\n  plugins: [\n    new MiniCssExtractPlugin({\n      filename: '[name].css',\n      chunkFilename: '[id].css',\n    }),\n  ],\n  resolve: {\n    extensions: ['.tsx', '.ts', '.js'],\n  },\n  output: {\n    filename: 'index.js',\n    path: path.resolve(__dirname, 'dist'),\n  },\n};\n\n```\n\n### Modifications\n\nIn order to have the script and sheet correctly referenced we have to change the `index.html` to the *final* names instead of their entry points.\n\n```diff\n\u003c!DOCTYPE html\u003e\n\u003chtml lang=\"en\"\u003e\n\u003chead\u003e\n\u003cmeta charset=\"UTF-8\"\u003e\n\u003cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"\u003e\n\u003cmeta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\"\u003e\n\u003ctitle\u003eBundler Comparison\u003c/title\u003e\n-\u003clink rel=\"stylesheet\" href=\"style.scss\"\u003e\n+\u003clink rel=\"stylesheet\" href=\"main.css\"\u003e\n\u003c/head\u003e\n\u003cbody\u003e\n\u003ch1\u003eTest Page\u003c/h1\u003e\n\u003cdiv id=\"app\"\u003e\u003c/div\u003e\n-\u003cscript src=\"app.tsx\"\u003e\u003c/script\u003e\n+\u003cscript src=\"index.js\"\u003e\u003c/script\u003e\n\u003c/body\u003e\n\u003c/html\u003e\n```\n\n### Running\n\nRunning is done via one command:\n\n```sh\nwebpack --mode production\n```\n\n### Results\n\nThe initial build took about 5s and resulted in a 130 kB bundle.\n\n```plain\nHash: bdd8a76b5baaf9b51608\nVersion: webpack 4.41.2\nTime: 4178ms\nBuilt at: 12/09/2019 4:51:36 AM\n                               Asset       Size  Chunks             Chunk Names\n                          1.index.js  386 bytes       1  [emitted]\ne69134e93d4d91d02d0b6864a4b9f3a3.jpg   99.4 KiB          [emitted]\n                          index.html  366 bytes          [emitted]\n                            index.js    130 KiB       0  [emitted]  main\n                            main.css   83 bytes       0  [emitted]  main\nEntrypoint main = main.css index.js\n [3] multi ./src/app.tsx ./src/index.html ./src/style.scss 52 bytes {0} [built]\n [4] ./src/app.tsx 904 bytes {0} [built]\n [9] ./src/smiley.jpg 80 bytes {0} [built]\n[10] ./src/index.html 54 bytes {0} [built]\n[11] ./src/style.scss 39 bytes {0} [built]\n[12] ./src/Page.tsx 358 bytes {1} [built]\n    + 8 hidden modules\nChild mini-css-extract-plugin node_modules/css-loader/dist/cjs.js!node_modules/sass-loader/dist/cjs.js!src/style.scss:\n    Entrypoint mini-css-extract-plugin = *\n    [0] ./node_modules/css-loader/dist/cjs.js!./node_modules/sass-loader/dist/cjs.js!./src/style.scss 220 bytes {0} [built]\n        + 1 hidden module\n\nreal    0m5.825s\nuser    0m6.000s\nsys     0m1.688s\n```\n\nThe stylesheet was minified to 83 bytes, the additional bundle came at 386 bytes.\n\nSubsequent builds seem to be slightly faster with 4s.\n\n```plain\nHash: bdd8a76b5baaf9b51608\nVersion: webpack 4.41.2\nTime: 2635ms\nBuilt at: 12/09/2019 4:57:20 AM\n                               Asset       Size  Chunks             Chunk Names\n                          1.index.js  386 bytes       1  [emitted]\ne69134e93d4d91d02d0b6864a4b9f3a3.jpg   99.4 KiB          [emitted]\n                          index.html  366 bytes          [emitted]\n                            index.js    130 KiB       0  [emitted]  main\n                            main.css   83 bytes       0  [emitted]  main\nEntrypoint main = main.css index.js\n [3] multi ./src/app.tsx ./src/index.html ./src/style.scss 52 bytes {0} [built]\n [4] ./src/app.tsx 904 bytes {0} [built]\n [9] ./src/smiley.jpg 80 bytes {0} [built]\n[10] ./src/index.html 54 bytes {0} [built]\n[11] ./src/style.scss 39 bytes {0} [built]\n[12] ./src/Page.tsx 358 bytes {1} [built]\n    + 8 hidden modules\nChild mini-css-extract-plugin node_modules/css-loader/dist/cjs.js!node_modules/sass-loader/dist/cjs.js!src/style.scss:\n    Entrypoint mini-css-extract-plugin = *\n    [0] ./node_modules/css-loader/dist/cjs.js!./node_modules/sass-loader/dist/cjs.js!./src/style.scss 220 bytes {0} [built]\n        + 1 hidden module\n\nreal    0m4.310s\nuser    0m4.141s\nsys     0m1.594s\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fflorianrappl%2Fbundler-comparison","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fflorianrappl%2Fbundler-comparison","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fflorianrappl%2Fbundler-comparison/lists"}