misc/class
lib/jquery_pnotify, lib/moment, lib/lodash, misc/notification, site/engine, misc/social
if( $.browser.msie && $.browser.version <= 8 ) include('lib/respond'); $._social.__cfg = {"init":[{"service":"basic"},{"fb_app_id":"1997094873850041","service":"fb"},{"vk_app_id":"2978320","service":"vk"},{"service":"twi"}],"like":[{"service":"fb"},{"service":"vk"},{"via":"","channel":"","hash_tag":"","service":"twi"}]}; window._SiteEngine = new classes.SiteEngine( { user_id: 0, controller: 'content_tape', action: 'tag', content_css_version: '1459538664', social_enabled: 0} );

Faiwer

Блог web-программиста

Поиск по метке: webpack

Babel to EsBuild migration for Webpack

9 февраля 2025

Recently I had some time to take a look at the performance of my pet project. One thing bothered me: the project is relatively small, but the bundle size was too big: 780 KiB. Whereas the project had only a few dependencies: react (~10 KiB), react-router (~70 KiB), react-dom (~90 KiB), dayjs (~3 KiB), lodash (~70 KiB). The bundle should not be that big for so few dependencies.

So I ran webpack-dev-analyzer and found a weird thing: the sum of the libs is 3 times smaller than the final bundle size. Then I decided to take a look at @statoscope/webpack-plugin. It was a good tool but it gave me the same result. The bundle has way too much trash. 

Ok. Probably it's just webpack wrappers over 300+ small modules. So I asked Perplexity - what can I do with it? Turned out all the proper options were already activated. So, I made a decision - it's time to try esbuild. It made a huge impact on my work project, so I knew it could work out. 

I knew that esbuild didn't support a lot of stuff that babel supports. So I expected that it would take a day to migrate the codebase to esbuild. But surprisingly once I replaced babel-loader with esbuild-loader the build command worked out. The build didn't work but the bundle was assembled without any errors.

Did it help? Nope. Instead of 780 KiB, I got ~1 MiB. Luckily I found out that there's also EsbuildPlugin, which replaces TerserPlugin and works better. Added this line, it worked like a charm, and... Whoa! 280 KiB. That's huuuge.

So I figured out - it worked. Now I have to find a way to fix the app (spoiler: it didn't work at all). 

Implicit React imports

React 17+ supports the possibility of avoiding adding import React from 'react' to every TSX-file. esbuild supports it too, but somehow it didn't work out of the box. Solution:

  const esbuildLoader: RuleSetRule = {
    test: /\.(js|ts|tsx)$/,
    loader: 'esbuild-loader',
    exclude: /node_modules/,
    options: {
       target: 'es2020',
       minify: cfg.PROD,
+      // Support implicit React imports:
+      jsx: 'automatic',
+      jsxFactory: 'React.createElement',
+      jsxImportSource: 'react',
    } satisfies LoaderOptions,
  };

TSX-Control-Statements

I just removed the packages and all their <If/>s. I liked them though. There's a plugin for Vite that adds their support. But I still use Webpack and the plugin works via string replacements, not via AST. 

SourceMaps

To enable sourcemaps I had to disable EsbuildPlugin for development. Theoretically, it supports sourcemaps, but no matter what I did it didn't work. I even debugged the package itself. Didn't help. So I surrendered and enabled it only for the prod build.

TypeScript

EsBuild supports TypeScript syntax out of the box but it doesn't check types. So it builds extremely fast but I need to know my errors. Solution:

  webpackConfig.plugins!.push(
    new ForkTsCheckerWebpackPlugin({
      typescript: {
        configFile: cfg.paths.TS_CONFIG,
      },
    })
  );

How does it work? The build and the type-checking processes are run in parallel. The 2nd doesn't linger the 1st. You still can see the errors in the browser (and CLI) console. And visually too. Nice.

ES-Lint

For some reason, it stopped working. The solution was pretty simple:

  webpackConfig.plugins!.push(
    new ESLintPlugin({
      extensions: ['ts', 'tsx'],

Final results

  • File sizes
    • Before: 641 KiB + 234 KiB + 29.5 KiB (css) = 904.5 KiB
    • After: 221 KiB + 55 KiB + 8.1 KiB (o_O, css) = 284.1 KiB
    • After minifyIdentifiers: true (scary thing) & minifyWhitespace: true:  138 KiB + 44.2 KiB + 8.1 KiB = 190.1 KiB
  • DOMContentLoaded:
    • Before: 737ms
    • After: 231ms
    • After-2: 131ms
  • Build time: esbuild was ~3x faster

Afterthought

Taking a deeper look I found:

  • The main issue wasn't in babel. The main issue was that I used inline-source-map for prod. I didn't want to do it. Just a mistake. And both bundle analyzers didn't notice that. Even when I manually looked at the file I barely found it (the horisontal scroll was too big). Fixing this issue dropped the file size 3x times even with babel.
  • esbuild has a better wrapper logic for a ton of tiny files than babel does. 
  • Whitespace minification is quite important. GZip doesn't eliminate the difference. 
  • Name mangling is a beast. But be careful, it can easily break your app in runtime. 

How to migrate Javascript webpack configuration to Typescript

25 марта 2023

It turned out that the newest versions (at least 5+) of webpack support Typescript out of the box. So the algorithm is next:

  • Create tsconfig.json at the root level. Content:
    {
      "compilerOptions": {
        "module": "CommonJS",
        "target": "ES5",
        "esModuleInterop": true,
        "checkJs": false,
        "strict": true,
      }
    }
    
  • Rename all *.js files to *.ts
  • Type all of them:
    • no more ugly require, use import.
    • webpack package has typings out of the box.
      • You may find these types useful: ConfigurationRuleSetRule
      • To enable devServer write this: 
        interface Configuration extends WebpackConfiguration {
          devServer?: WebpackDevServerConfiguration;
        }
        
    • Some of the popular plugins have types too.
    • Some of them don't have types at all:
      • Create a *.d.ts file
      • Put there something like this:
        declare module 'postcss-assets' {
          export default function postcssAssets(opts: {
            basePath: string;
            relative: boolean;
          }): unknown;
        }
        
  • Make sure your webpack.config.ts file is placed at the root level. I mean exactly at the same spot where node_modules is. Otherwise, you won't be able to build it. No compilerOptions helped me. 
  • Run webpack. It should work.

Webpack и SCSS импорты в купе с module-resolver

3 августа 2018

С приходоим ES7 в JS пришли import-ы и export-ы. Жить стало веселее, и... сложнее. Появилось много заморочек и вообще новых проблем. Одна из таких проблем выглядит так: `import some from ../../../../../some`. Знакомая ситуация?

Обычно её в случае webpack-а решают при помощи модуля module-resolver. Он позволяет в .babelrc (или где вы держите конфигурацию) указывать регулярные выражения для автозамены, а также добавляет поддержку root-записей. Скажем с ним можно указать `import actions from '@actions/header', где `@actions` будет алиасом к какому-нибудь пути в вашем проекте. 

Дальше встаёт вопрос инструментов. А как на это должен реагировать редактор? Как он узнает, что теперь пути в import-ах устроены хитрее. Что делать с линтингом? К счастью, для eslint есть такой плагин:  eslint-plugin-import. С редакторами ситуация может быть сложнее.

Хорошо, а что делать с SCSS\Sass? Нормальных рабочих решений с наскоку мне найти не удалось. Но как оказалось, всё можно решить относительно просто. В настройках webpack для sass-loader-а можно указать настройки. Они будут переданы как есть в пакет node-sass. А вот он, оказывается, достаточно гибкий. Если там указать метод importer, то можно навязать свою логику обработки @import-ов. Пример:

{
	loader: 'sass-loader',
	options:
	{
		sourceMap: true,
		url: false,
		importer: url => (
		{
			file: url
				.replace(...)
				.replace(...)
				.replace(...),
		}),
	},
}

Либо даже выцепить ваши настройки прямо из настроек в .babelrc.

Embedded webpack application

19 апреля 2018

Возникла необходимость внедрять готовый SPA в другой проект на страницу. Проект сразу писался под это дело, но всё равно возник ряд проблем, которые нужно было решать. Напишу про 2 из них сюда как шпаргалку.

webpack & dynamic import

Что если ваш проект использует динамический импорт? Например System.import. В этом случае webpack сделает для подгружаемых файлов отдельный chunk. И грузить он его будет исходя из publicPath в настройках webpack. Но ведь embedded решения могут быть внедрены по любому пути. Как быть? Всё просто, пишем в точке входа что-нибудь типа: __webpack_public_path__ = window.myPredefinedPath и дело в шляпе. Магия. До загрузки приложения нужно определить этот window.myPredefinedPath, webpack его увидит и будет использовать. Сразу отмечу, что надо писать как есть, не window.__webpack...=, а как-будто эта переменная уже определена в коде. Если на неё ругается eslint не беда, пишем: // eslint-disable-line и он более не ругается.

webpack css & url

Возникла проблема с тем, что в стилях указаны пути к шрифтам так, что webpack их найти не может (отдельная тема почему так). Да и не должен, него его это забота. Однако он пытается от-revolve-ить все пути, что находит в css. Лечится установкой url: false в cssLoader-е. После этой настройки пути отдаются как есть без каких-либо проверок.

 

Что не так с Webpack server-ом и React Hot Loader-ом

20 декабря 2016

Как минимум:

  • Актуальная версия RHL в какой-то глубокой бете. Некоторые инструменты, такие как onsen monaca, в итоге, прибиты ногами к конкретной версии (иначе оно попросту не работает). Даже готовые пресеты для webpack-а завязаны на конкретные версии. Обратной совместимостью там и не пахнет. Даже сообщений внятных о том, что изменилось в stack trace-ах не ждите. 
  • Webpack server довольно хитро устроен внутри, поэтому такую связку очень сложно дебажить. Попробуйте ради интереса убрать [HMR] и [WDS] сообщения. В настройках плагинов такой опции нет. Попытка вырезать их из кода руками заставит вас детально разобраться в работе этого сервера. Ну или забить. Я забил :)
  • Если ошибка произошла до первого успешного render-а, то stack trace показывает погоду на Марсе. Настоящая ошибка теряется в дебрях обёрток для ошибок.
  • Если в Chrome Developer Tools для webpack's sourceMap-ов есть какая-то кривоватая, но поддержка, то в Firefox инструментах там просто хаос. Про stack trace-ы вообще молчу. Там просто ссылка строку в bundle.js
  • React Hot Loader по понятным причинам не перегружает обновления методов, которые были за-bind-ны в contructor-е компонента сами на себя. В итоге приходится перегружаться почти по любому поводу.
  • Любые не тривиальные случаи приводят к необходимости обновить страницу.

Сложилось стойкое впечатление, что 3-ая версия React Hot Loader-а всё ещё слишком сырая, чтобы ей можно было пользоваться за пределами задач вёрстки. Но я бы не стал даже в их пределах. Этот инструмент оборачивает всё своими proxy-методами, чем сильно портит жизнь.

Поддержка sourceMap-ов тоже пока оставляет желать лучшего. Слишком часто при debug-инге проваливаешься невесть куда, не можешь поставить breakpoint, а то и вовсе наблюдаешь какие-то аномалии. Поддержка stacktrace-ов тоже пока далека от идеала. 

Всё больше склоняюсь к разработке с ссылками на настоящие ресурсы, благо поддержка es6 в браузерах очень радует. Но вот JSX, я полагаю, нам в нативном виде в них не видать никогда. Так что вопрос, "а может к чёрту его этот JSX?" для меня лично достаточно актуален :)