🥳

Node.js/成果物に含まれる node_modules を減量する

2021/07/29に公開

動機

JavaScript ( や TypeScript などの AltJS ) で記述されたプロジェクトをデプロイするとき、対象となるサービスに設定されたデプロイパッケージの容量制限を意識せねばならない場合があります。 そういった目的のために、この記事では node_modules パッケージの容量を減らす方法についてまとめます。

node_modules のサイズについては、以下のコマンドなどで確認できます。

$ du -d 1 -kh ./node_modules | sort -hr | head -50

...

たとえば AWS Lambda にプロジェクトをデプロイするのならば、次のような制限があります。
https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/gettingstarted-limits.html

50 MB (zip 圧縮済み、直接アップロード)
250 MB (解凍、レイヤーを含む)
3 MB (コンソールエディタ)

なお『レイヤー』とは Lambda Layers のことです。
https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/configuration-layers.html

手段

modclean で軽量化する

https://www.npmjs.com/package/modclean

npx modclean --run などとすればお試しで実行することができます。

node-prune で軽量化する

https://www.npmjs.com/package/node-prune

npx node-prune などとすればお試しで実行することができます。

uglify-js で軽量化する

https://www.npmjs.com/package/uglify-js

$ yarn global add uglify-js
$ find node_modules -name '*.js' | while read line; do echo ${line}; uglifyjs ${line} -c -m -o ${line}; done

などとすると実行できます。

上掲した 2 つに比べると実行に時間が掛かるため、頻繁に実行するには検証が必要になります。

node_modules には含めず CDN から取得する

swagger-ui ( https://www.npmjs.com/package/swagger-ui ) を例に、
node_modules には含めず CDN から JavaScript を取得して、それを使用してみます。

まず CDN から JavaScript を取得して、その文字列を変数に格納します。

const [bundleResponse, standaloneResponse]: Response[] = await Promise.all([
  fetch('https://unpkg.com/swagger-ui-dist@3/swagger-ui-bundle.js'),
  fetch('https://unpkg.com/swagger-ui-dist@3/swagger-ui-standalone-preset.js'),
])

const [swaggerBundle, swaggerStandalone]: string[] = await Promise.all([
  bundleResponse.text(),
  standaloneResponse.text()
])

script.runInNewContext を用いて文字列を JavaScript として評価します。
文字列をソースコードとして評価する方法としては他に evalsafe-eval などがあります。

const swaggerBundle = {}
const scriptBundle = new vm.Script(swaggerBundle)
await scriptBundle.runInNewContext(swaggerBundle)

const swaggerStandalone = {}
const scriptStandalone = new vm.Script(swaggerStandalone)
await scriptStandalone.runInNewContext(swaggerStandalone)

これで swagger-ui-bundle.jsswaggerBundle として、
swagger-ui-standalone-preset.jsswaggerStandalone として、
たとえば以下のように使用できるようになります。

const options: SwaggerUIOptions = {
  spec, // yaml が格納された変数 spec があるとする
  domNode: document.getElementById('swagger'),
  deepLinking: true,
  presets: [
    swaggerBundle.SwaggerUIBundle.presets.apis,
    swaggerStandalone.SwaggerUIStandalonePreset,
  ],
  plugins: [swaggerBundle.SwaggerUIBundle.plugins.DownloadUrl],
  layout: 'StandaloneLayout',
}

swaggerBundle.SwaggerUIBundle(options)

⚠️ 注意点

eval() ないしそれに準ずる方法で、文字列を JavaScript として評価していることには注意が必要です。

この例のようにクライアントサイドで(ブラウザで)実行されるのならば、このページだけ CSP に script-src 'unsafe-eval' を追加するなどして、制限を緩めてやる必要があります。
https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Content-Security-Policy/script-src
( swagger-ui の JavaScript は window などを使用しており、クライアントサイドでしか実行できない )

...

ただし Content-Security-Policy レスポンスヘッダにある CSP の設定を、meta タグの CSP によって緩めることはできません。
https://stackoverflow.com/a/34563116

以上を考慮すると、この方法はサーバサイドで評価できる JavaScript 限定と言えるかもしれません。

--production フラグをつける

成果物に dependencies のみを含める ( devDependencies は含めない ) ようにします。

https://docs.npmjs.com/cli/v7/commands/npm-install#description
https://classic.yarnpkg.com/en/docs/cli/install/#toc-yarn-install-production-true-false

また npm prune --production すれば、devDependencies を削除することもできます。
https://docs.npmjs.com/cli/v7/commands/npm-prune

depcheck で使用していないパッケージを除く

https://www.npmjs.com/package/depcheck

npx depcheck などとすればお試しで実行することができます。
ただし精度には限界があり、実行結果に出力されているから除いて差し支えないとはならず、いざ除くのならば精査が必要です。

本来 devDependencies にあれば十分なパッケージが、誤って dependencies に記述されていると検出する

eslint にルールを設定することで間接的に検出することができます。
https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/no-extraneous-dependencies.md

リンク

https://dev.to/solegaonkar/cleanup-the-nodemodules-for-a-lighter-lambda-function-20jk
https://dev.to/poopcoder/remove-unused-npm-modules-from-package-json-2e7i

Discussion