По умолчанию npm публикует в registry весь модуль целиком. За исключением явно указанных в .gitignore файлов. Это отбрасывает зависимости, но все равно позволяет куче не очень нужных файлов просочиться в опубликованное. После чего благодарные пользователи ждут, пока все это скачается. Для grunt, кстати, ждать придется порядка 6 мегабайт. А он такой обычно не один.Я решил разобраться, как измерить размер своих модулей после публикации и, по возможности, этот размер уменьшить. В качестве примера буду использовать модуль check-more-types, который содержит всего несколько файлов. Плюс юнит тесты и документацию, которая собирается в README markdown файл.
В первую очередь мы должны посчитать текущий размер модуля. NPM хранит все файлы в виде tar архивов, так что достаточно будет создать такой архив и посмотреть его размер. Более того, у npm есть для этого специальная команда npm pack, создающая архив из содержимого указанной директории. Mathias Bynens предлагает следующий скрипт для определения размера модуля:
tarball="$(npm pack .)"; wc -c "${tarball}"; tar tvf "${tarball}"; rm "${tarball}";
Я измерил размер архива для коммита с хешом 3ada360:
немного shell магии
$ tarball="$(npm pack .)"; wc -c "${tarball}"; tar tvf "${tarball}"; rm "${tarball}"; 25184 check-more-types-2.1.2.tgz -rw-r--r-- 0 501 20 1977 Nov 19 13:55 package/package.json -rw-r--r-- 0 501 20 64 Nov 19 13:18 package/.npmignore -rw-r--r-- 0 501 20 19703 Nov 19 13:49 package/README.md -rw-r--r-- 0 501 20 1073 Nov 19 13:18 package/LICENSE -rw-r--r-- 0 501 20 2534 Nov 19 13:18 package/Gruntfile.js -rw-r--r-- 0 501 20 18204 Nov 19 13:18 package/check-more-types.js -rw-r--r-- 0 501 20 6723 Nov 19 13:49 package/check-more-types.min.js -rw-r--r-- 0 501 20 600 Nov 19 13:49 package/bower.json -rw-r--r-- 0 501 20 162 Nov 19 13:18 package/.travis.yml -rw-r--r-- 0 501 20 1756 Nov 19 13:18 package/.jshintrc -rw-r--r-- 0 501 20 655 Nov 19 13:18 package/docs/README.tmpl.md -rw-r--r-- 0 501 20 1936 Nov 19 13:18 package/docs/badges.md -rw-r--r-- 0 501 20 255 Nov 19 13:18 package/docs/footer.md -rw-r--r-- 0 501 20 240 Nov 19 13:18 package/docs/install.md -rw-r--r-- 0 501 20 13707 Nov 19 13:49 package/docs/use.md -rw-r--r-- 0 501 20 127 Nov 19 13:18 package/test/check-more-types-minified-spec.js -rw-r--r-- 0 501 20 78 Nov 19 13:18 package/test/check-more-types-spec.js -rw-r--r-- 0 501 20 467 Nov 19 13:18 package/test/load-under-node-test.js -rw-r--r-- 0 501 20 738 Nov 19 13:18 package/test/synthetic-browser-spec.js -rw-r--r-- 0 501 20 37754 Nov 19 13:18 package/test/unit-tests.js
Что мы видим? Куча файлов и размер сжатых данных в 251184 байт. Для начала немного автоматизируем этот процесс. Я нашел хорошую утилиту для получения размера без использования шелл команд: pkgfiles за авторством Tim Oxley.
Я инсталлировал утилиту как дев зависимость и добавил ее в “prepublish” скрипт для package.json:
{ "devDependencies": { "pkgfiles": "2.3.0" }, "scripts": { "prepublish": "pkgfiles" } }
“Prepublish” скрипт будет выполняться каждый раз при локальной установке и по команде npm publish. Посмотрим на результат npm run publish после наших изменений:
результат npm run prepublish
$ npm run prepublish > check-more-types@2.1.2 prepublish /Users/kensho/git/check-more-types > pkgfiles PATH SIZE % .npmignore 0 B 0% test/check-more-types-spec.js 78 B 0% test/check-more-types-minified-spec.js 127 B 0% .travis.yml 162 B 0% docs/install.md 240 B 0% docs/footer.md 255 B 0% test/load-under-node-test.js 467 B 0% bower.json 600 B 1% docs/README.tmpl.md 655 B 1% test/synthetic-browser-spec.js 738 B 1% LICENSE 1.07 kB 1% .jshintrc 1.76 kB 2% docs/badges.md 1.94 kB 2% package.json 2.05 kB 2% Gruntfile.js 2.53 kB 2% check-more-types.min.js 6.72 kB 6% docs/use.md 13.71 kB 13% check-more-types.js 18.2 kB 17% README.md 19.7 kB 18% test/unit-tests.js 37.75 kB 35% DIR SIZE % docs/ 16.79 kB 15% test/ 39.16 kB 36% . 108.77 kB 100% PKGFILES SUMMARY Size on Disk with Dependencies ~126.72 MB Size with Dependencies ~88.58 MB Publishable Size ~108.77 kB Number of Directories 3 Number of Files 20
Очень детальная информация. И самые “тяжелые” файлы указаны последними, что дает возможность удобно анализировать результаты в терминале. Больше всего места занимают директории с документацией и тестами — и это при том, что мы не собираемся их публиковать!
Есть три способа указать, какие файлы не будут публиковаться в npm. Мы используем способ по умолчанию: файлы, указанные в .gitignore, автоматически заносятся в черный список. И мы можем создать еще один файл, .npmignore, в котором указать независящий от git набор файлов, который мы не хотим публиковать. Альтернативные способ: добавить файлы в “белый список” с помощью package.json. Лично я предпочитаю именно такой способ. Обратите внимание, что ряд файлов, такие как package.json или README, автоматически находятся в белом списке.
{ "files": [ "bower.json", "check-more-types.js", "check-more-types.min.js" ] }
А чтобы исключить файлы из уже добавленных, можно воспользоваться восклицательным знаком. Например, если в вашей директории src, которую вы хотите публиковать, есть поддиректория test, которую вы публиковать совсем не хотите, то:
{ "files": [ "src", "!src/test" ] }
Ну а если в одной директории src у вас случились файлы как для production, так и для test/staging, то вы можете исключить файлы по одному или группами:
{ "files": [ "src/*.js", "!src/*-spec.js" ] }
Хеш коммита со всеми этими изменениями начинается с bc3e2a1. Посмотрим, что получилось с размером публикуемого модуля:
еще один результат npm run prepublish
$ npm run prepublish > check-more-types@2.1.2 prepublish /Users/kensho/git/check-more-types > pkgfiles PATH SIZE % bower.json 600 B 1% LICENSE 1.07 kB 2% package.json 2.15 kB 4% check-more-types.min.js 6.72 kB 14% check-more-types.js 18.2 kB 38% README.md 19.7 kB 41% DIR SIZE % . 48.45 kB 100% PKGFILES SUMMARY Size on Disk with Dependencies ~126.72 MB Size with Dependencies ~88.58 MB Publishable Size ~48.45 kB Number of Directories 1 Number of Files 6
Получилось все неплохо: размер публикуемого модуля уменьшался на 55% со 107 килобайт до 48. Это общее уменьшение размера, но мы еще можем посмотреть, что именно поменялось внутри tar архива. К сожалению, npm pack вызывает prepublish скрипт и не может корректно обработать его вывод. Поэтому я временно переименую prepublish и добавлю “tarball=...” под именем reuse:
результат npm run size
$ npm run size > check-more-types@2.1.2 size /Users/kensho/git/check-more-types > tarball="$(npm pack .)"; wc -c "${tarball}"; tar tvf "${tarball}"; rm "${tarball}"; 13179 check-more-types-2.1.2.tgz -rw-r--r-- 0 501 20 2256 Nov 19 14:09 package/package.json -rw-r--r-- 0 501 20 19703 Nov 19 13:58 package/README.md -rw-r--r-- 0 501 20 1073 Nov 19 13:18 package/LICENSE -rw-r--r-- 0 501 20 18204 Nov 19 13:58 package/check-more-types.js -rw-r--r-- 0 501 20 6723 Nov 19 13:58 package/check-more-types.min.js -rw-r--r-- 0 501 20 600 Nov 19 13:58 package/bower.json
Теперь клиенту нужно сказать только 13 килобайт вместо 28, а это 50% уменьшение размера!
Мне также пришла в голову идея показывать размер публикуемого модуля при каждом push из локального репозитория в “remote master”. Чтобы это сделать, достаточно добавить обе команды, size и pkgfiles, в pre-push шаг и воспользоваться модулем “pre-git”:
npm install -D pre-git
package.json
Чтобы проверить, что все сработало, я увеличил версию пакета с 2.1.2 до 2.2.0 и воспользовался для установки чистой директорий и npm версии “3.4.0”:
Почти 3 секунды. Сотрем директорию node_modules и попробуем новую версию пакета:
Мы успешно откусили 1 секунду от инсталляции — а это 30% от всего времени инсталяции для нашего маленького модуля!
Полная версия использованного мной для “очистки” package.json (можно посмотреть в моем репозитории):
Кстати, эту версию можно немного уменьшить, если использовать “t” вместо “tarball”:
{ "scripts": { "pkgfiles": "pkgfiles", "size": "tarball=\"$(npm pack .)\"; wc -c \"${tarball}\"; tar tvf \"${tarball}\"; rm \"${tarball}\";" }, "config": { "pre-git": { "pre-push": [ "npm run size", "npm run pkgfiles" ] } } } </spoiler>
Чтобы проверить, что все сработало, я увеличил версию пакета с 2.1.2 до 2.2.0 и воспользовался для установки чистой директорий и npm версии “3.4.0”:
$ time npm i check-more-types@2.1.2 /private/tmp/test-small └── check-more-types@2.1.2 real 0m2.706s user 0m1.419s sys 0m0.323s
Почти 3 секунды. Сотрем директорию node_modules и попробуем новую версию пакета:
$ rm -rf node_modules/ $ time npm i check-more-types@2.2.0 /private/tmp/test-small └── check-more-types@2.2.0 real 0m1.716s user 0m1.244s sys 0m0.198s
Мы успешно откусили 1 секунду от инсталляции — а это 30% от всего времени инсталяции для нашего маленького модуля!
Полная версия использованного мной для “очистки” package.json (можно посмотреть в моем репозитории):
npm install -D pkgfiles pre-git
package.json целиком
{ "devDependencies": { "pkgfiles": "2.3.0", "pre-git": "1.3.0" }, "scripts": { "pkgfiles": "pkgfiles", "size": "tarball=\"$(npm pack .)\"; wc -c \"${tarball}\"; tar tvf \"${tarball}\"; rm \"${tarball}\";" }, "config": { "pre-git": { "pre-push": [ "npm run size", "npm run pkgfiles" ] } } }
Кстати, эту версию можно немного уменьшить, если использовать “t” вместо “tarball”:
"scripts": { "pkgfiles": "pkgfiles", "size": "t=\"$(npm pack .)\"; wc -c \"${t}\"; tar tvf \"${t}\"; rm \"${t}\";" }