{"id":23770961,"url":"https://github.com/bubblydoo/serverless-externals-plugin","last_synced_at":"2025-09-05T14:33:47.197Z","repository":{"id":38644220,"uuid":"186235343","full_name":"bubblydoo/serverless-externals-plugin","owner":"bubblydoo","description":"Package only external modules, and let Rollup bundle all the other modules.","archived":false,"fork":false,"pushed_at":"2024-04-11T12:49:49.000Z","size":2975,"stargazers_count":8,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-08-19T09:51:52.781Z","etag":null,"topics":["rollup","rollup-plugin","serverless","serverless-framework"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/bubblydoo.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2019-05-12T09:30:08.000Z","updated_at":"2024-02-22T09:43:58.000Z","dependencies_parsed_at":"2025-08-19T09:53:23.948Z","dependency_job_id":"329d8b14-7344-4b34-9629-080ae86bc2bf","html_url":"https://github.com/bubblydoo/serverless-externals-plugin","commit_stats":null,"previous_names":["hansottowirtz/serverless-externals-plugin"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/bubblydoo/serverless-externals-plugin","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bubblydoo%2Fserverless-externals-plugin","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bubblydoo%2Fserverless-externals-plugin/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bubblydoo%2Fserverless-externals-plugin/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bubblydoo%2Fserverless-externals-plugin/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bubblydoo","download_url":"https://codeload.github.com/bubblydoo/serverless-externals-plugin/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bubblydoo%2Fserverless-externals-plugin/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":273769943,"owners_count":25164895,"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","status":"online","status_checked_at":"2025-09-05T02:00:09.113Z","response_time":402,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":["rollup","rollup-plugin","serverless","serverless-framework"],"created_at":"2025-01-01T03:18:40.790Z","updated_at":"2025-09-05T14:33:42.148Z","avatar_url":"https://github.com/bubblydoo.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"![npm](https://img.shields.io/npm/v/serverless-externals-plugin)\n\n# Serverless Externals Plugin\n\nOnly include listed `node_modules` and their dependencies in Serverless.\n\nThis plugin helps Serverless package only external modules, and Rollup bundle all the other modules.\n\n![Image](./res/graphic-main.png)\n\n## Installation\n\n```bash\nnpm install serverless-externals-plugin\n```\n\nor\n\n```bash\nyarn add serverless-externals-plugin\n```\n\n`serverless.yml`:\n\n```yml\nplugins:\n  - serverless-externals-plugin\n\npackage:\n  individually: true\n\nfunctions:\n  handler:\n    handler: dist/bundle.handler\n    externals:\n      report: dist/node-externals-report.json\n    package:\n      patterns:\n        - \"!./**\"\n        - ./dist/bundle.js\n```\n\n`rollup.config.js`:\n\n```js\nimport { rollupPlugin as externals } from \"serverless-externals-plugin\";\n\nexport default {\n  ...\n  output: { file: \"dist/bundle.js\", format: \"cjs\" },\n  treeshake: {\n    moduleSideEffects: \"no-external\",\n  },\n  plugins: [\n    externals(__dirname, { modules: [\"pkg3\"] }),\n    commonjs(),\n    nodeResolve({ preferBuiltins: true, exportConditions: [\"node\"] }),\n    ...\n  ],\n}\n```\n\n## Example\n\nExternals Plugin interacts with both **Serverless** and with your **bundler** (Rollup).\n\nLet's say you have two modules in your `package.json`, `pkg2` and `pkg3`. `pkg3` is a module with native binaries, so it can't be bundled.\n\n```\nroot\n+-- pkg3@2.0.0\n+-- pkg2@0.0.1\n    +-- pkg3@1.0.0\n```\n\nBecause `pkg3` can't be bundled, both `./node_modules/pkg3` and `./node_modules/pkg2/node_modules/pkg3` should be included in the bundle.\n`pkg2` can just be bundled, but should import `pkg3` as follows: `require('pkg2/node_modules/pkg3')`. It cannot just do `require('pkg3')`\nbecause `pkg3` has a different version than `pkg2/node_modules/pkg3`.\n\nIn the Serverless package, only `./node_modules/pkg3/**` and `./node_modules/pkg2/node_modules/pkg3/**` should be included, all the other contents of `node_modules` are already bundled.\n\nExternals Plugin provides a Serverless plugin and a Rollup plugin to support this.\n\nThere are other reasons modules can't be bundled. For example, [`readable-stream`](https://github.com/nodejs/readable-stream/issues/348) and [`sshpk`](https://github.com/joyent/node-sshpk/issues/42) cannot be bundled due to circular dependency errors.\n\n## Configuration\n\nIn `rollup.config.js`:\n\n```js\noutput: { file: \"dist/bundle.js\", format: \"cjs\" },\nplugins: [\n  externals(__dirname, { modules: [\"aws-sdk\"], packaging: { exclude: [\"aws-sdk\"] } }),\n  ...\n]\n```\n\nThis will generate a file called `node-externals-report.json` next to `bundle.js`, with the module paths that should be packaged.\n\nIt can then be included in `serverless.yml`:\n\n```yml\ncustom:\n  externals:\n    report: dist/node-externals-report.json\n```\n\n### Configuration object\n\nThe configuration object has these options:\n- `modules: string[]`: a list of module names that should be kept external (default `[]`)\n- `report?: boolean | string`: whether to generate a report or report path (default `${distPath}/node-externals-report.json`)\n- `packaging.exclude?: string[]`: modules which shouldn't be packaged by Serverless (e.g. `['aws-sdk']`, default `[]`)\n- `packaging.forceIncludeModuleRoots?: string[]`: module roots that should always be packaged (e.g. `['node_modules/pg']`, default `[]`)\n- `file?: string`: path to a different configuration object\n\nIt's also possible to filter on module versions. (e.g. `uuid@\u003c8`). This uses a semver range.\n\n## How it works\n\nExternals Plugin uses [Arborist](https://github.com/npm/arborist) by NPM to analyze the `node_modules` tree (using `loadActual()`).\n\nUsing the Externals configuration (a list of modules you want to keep external), the Plugin will then build a list of all dependencies that should be kept external.\nThis list will contain the modules in the configuration and all the (non-dev) dependencies, recursively.\n\nIn the example, the list will contain both `pkg2/node_modules/pkg3` and `pkg3`.\n\nThe Rollup Plugin will then generate a report (e.g. `node-externals-report.json`) which contains the modules that are actually imported. This file is then used by the Serverless Plugin to generate a list of include patterns.\n\n## Report\n\nIf you mark both `aws-sdk` and `sshpk` as external, but you don't use `sshpk`,\nthe generated report will look as follows:\n\n`node-externals-report.json`:\n\n```json\n{\n  \"isReport\": true,\n  \"importedModuleRoots\": [\n    \"node_modules/aws-sdk\"\n  ],\n  \"config\": {\n    \"modules\": [\n      \"aws-sdk\",\n      \"sshpk\"\n    ]\n  }\n}\n```\n\nServerless can therefore ignore `sshpk` and all its dependencies, making the bundle even smaller.\n\nThe report is generated by analyzing the files Rollup emits (including dynamic imports).\n\n## Rollup Plugin\n\nA fully configured `rollup.config.js` could look as follows:\n\n```js\nimport { rollupPlugin as externals } from \"serverless-externals-plugin\";\nimport commonjs from \"@rollup/plugin-commonjs\";\nimport nodeResolve from \"@rollup/plugin-node-resolve\";\n\n/** @type {import('rollup').RollupOptions} */\nconst config = {\n  input: \"index.js\",\n  output: {\n    file: \"bundle.js\",\n    format: \"cjs\",\n    exports: \"named\",\n    dynamicImportInCjs: false,\n  },\n  treeshake: {\n    moduleSideEffects: \"no-external\",\n  },\n  plugins: [\n    externals(__dirname, { modules: [\"aws-sdk\"], packaging: { exclude: [\"aws-sdk\"] } }),\n    commonjs({ strictMode: true, ignoreDynamicRequires: true }),\n    nodeResolve({ preferBuiltins: true, exportConditions: [\"node\"] }),\n  ],\n};\n\nexport default config;\n```\n\nMake sure `externals` comes **before** `@rollup/plugin-commonjs` and `@rollup/plugin-node-resolve`.\n\nMake sure [`moduleSideEffects: \"no-external\"`](https://rollupjs.org/guide/en/#treeshake) is set. By default, Rollup includes all external modules that appear in the code because they might contain side effects, even if they can be treeshaken.\nBy setting this option Rollup will assume external modules have no side effects.\n\n(`\"no-external\"` is equivalent to `(id, external) =\u003e !external`)\n\nPreferably, also set `exportConditions: [\"node\"]` as an option in the node-resolve plugin. This ensures that Rollup uses Node's resolution algorithm, so that packages like [`uuid` can be bundled](https://github.com/uuidjs/uuid/issues/544).\n\nNext to that, in rare cases setting `strictMode: true` in the commonjs plugin can help bundling some modules that depend on the order of requires, like `aws-sdk` when only importing `aws-sdk/clients/dynamodb`.\n\n### Implementation\n\nThe Rollup plugin provides a `resolveId` function to Rollup. For every import (e.g. `require('pkg3')`) in your source code,\nRollup will ask the Externals Plugin whether the import is external, and where to find it.\n\nThe Plugin will look for the import in the Arborist graph, and if it's declared as being external\nit will return the full path to the module that's being imported (e.g. `pkg2/node_modules/pkg3`).\n\n## `node-externals.json`\n\nIt's also possible to add the externals config to a file, called `node-externals.json`.\n\nIn `node-externals.json`:\n\n```json\n{\n  \"modules\": [\n    \"pkg3\"\n  ]\n}\n```\n\nIn `rollup.config.js`:\n\n```js\nplugins: [\n  externals(__dirname, { file: 'node-externals.json' }),\n  ...\n]\n```\n\n## Dynamic requires\n\nIf your code or one of your Node modules does the following:\n\n```js\nrequire(\"p\" + \"g\");\n// or\nimport(\"p\" + \"g\");\n```\n\nThen this plugin will not be able to detect which modules should be packaged. You can force include them by using `packaging.forceIncludeModuleRoots`:\n\n```js\n...\npackaging: {\n  forceIncludeModuleRoots: [\"node_modules/pg\"]\n}\n```\n\nThe plugin will then treat `node_modules/pg` as if it was imported directly in the bundle. It will also include all the dependencies.\n\nFor example: in `knex`, an SQL builder, `pg` is a peer dependency. Inside `knex` it is imported as follows:\n\n```js\n// knex/lib/index.js\nconst resolvedClientName = resolveClientNameWithAliases(clientName);\nDialect = require(`./dialects/${resolvedClientName}/index.js`);\n```\n```js\n// knex/lib/dialects/postgres/index.js\nrequire('pg');\n```\n\nIf you're using `knex`, you'd have to force include `node_modules/pg`.\nIf you want to bundle `knex`, you would also have to enable `ignoreDynamicRequires` in your `rollup.config.js`:\n\n```js\ncommonjs({ ignoreDynamicRequires: true }),\n```\n\nThis will make sure the `require` call is not changed.\n\nAnother solution is to add `knex` to the list of externals. In that case the whole `node_modules/knex` folder will be uploaded, and none of its code will be transformed.\n\nRollup sometimes keeps `await import` expressions in the bundle, which might cause import issues if the module is not commonjs. To fix this, you can add the following to your `rollup.config.js`:\n\n```js\noutput: {\n  dynamicImportInCjs: false\n}\n```\n\n## Usage in monorepos/workspaces\n\nIf your serverless project is a workspace within a larger monorepo, this is also supported, although not yet fully tested.\n\nFor example, in `apps/lambdas/rollup.config.js`:\n\n```js\nconst root = path.resolve(__dirname, \"../..\")\nconst workspaceName = \"main-app\"\n\n...\n\nplugins: [\n  externals([root, workspaceName], { modules: [\"undici\"] }),\n  ...\n]\n```\n\nIf `undici` is installed the monorepo root, the serverless plugin will generate include patterns as follows:\n\n```\n!./node_modules/**\n!../../node_modules/**\n../../node_modules/undici/**\n!../../node_modules/undici/node_modules\n../../node_modules/busboy/**\n!../../node_modules/busboy/node_modules\n../../node_modules/streamsearch/**\n!../../node_modules/streamsearch/node_modules\n```\n\nDue to the way packaging in serverless works, in the final package,\nthe included modules from the root node_modules will be merged with the included workspace node_modules,\nwhich is exactly what we want.\n\n## Caveats\n\n### Externals with side effects\n\nIt's unlikely, but if you have external modules with side effects (like polyfills), make sure to configure Rollup properly.\n\n**NOTE**: This only applies to external modules. You should probably bundle your polyfills.\n\n```js\nimport \"some-external-module\"; // this doesn't work, Rollup will treeshake it away\n```\n\nAs Rollup will remove external modules with side effects, make sure to add something like this\nto the Rollup config:\n\n```js\ntreeshake: {\n  moduleSideEffects: (id, external) =\u003e !id.test(/some-external-module/) || !external\n}\n```\n\n### Only one `node_modules` supported\n\nThis plugin doesn't have support for analyzing multiple `node_modules` folders. If you have\nmore `node_modules` folders on your `NODE_PATH` (e.g. from a Lambda layer), you can still use\nthe [`external` field of Rollup](https://rollupjs.org/guide/en/#external).\n\n### Keeping `aws-sdk` excluded\n\nAs the `aws-sdk` node module is included by default in Lambdas, you can add `packaging.exclude: [\"aws-sdk\"]` to exclude it from the Serverless package. Note that this is not recommended because of possible version differences. (Check the `aws-sdk` version included in runtimes [here](https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html))\n\n### All subdependencies marked as external\n\nWhen listing modules as external, all their subdependencies will also be marked as external.\n\nFor example:\n\n```js\n// rollup.config.js\nplugins: [\n  externals(__dirname, { modules: [\"botkit\"] }),\n  ...\n]\n```\n\n```js\n// lambda.js\nconst express = require('express');\nconst serverlessHttp = require('serverless-http');\nconst app = express();\n\nmodule.exports.handler = serverlessHttp(app);\n```\n\nIn the resulting bundle, `express` will not be bundled. This is because `botkit` also depends on `express`, and is therefore marked as external. `botkit` itself and the rest of its subdependencies will be filtered out of the modules to be uploaded.\n\nIt is therefore recommended to limit the length of the modules array to only the necessary. In that way you can achieve the smallest bundles.\n\n## Todo\n\n- Ensure compatibility with Serverless Jetpack or speedup packaging somehow\n- Webpack plugin\n- Esbuild plugin\n- Layer support\n- Look into externalizing single files in a module (e.g. `.node` files), and bundling the rest\n- Yarn PnP support\n- Pre-calculate actually used top-level externals to solve last caveat\n\n## Motivation\n\nI wanted to include Cheerio/JSDom and AWS SDK in a Typescript project, but neither could be bundled because of obscure errors, so they needed to be external. To reduce package size, I didn't want to make every module external. Manually looking up a module and adding its dependencies to `rollup.config.js` `and serverless.yml` is simply too much work. This plugin makes this much easier.\n\n## Credits\n\nSome Serverless-handling code was taken from [Serverless Jetpack](https://github.com/FormidableLabs/serverless-jetpack). Also inspired by [Serverless Plugin Include Dependencies](https://github.com/dougmoscrop/serverless-plugin-include-dependencies) and [Webpack Node Externals](https://github.com/liady/webpack-node-externals)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbubblydoo%2Fserverless-externals-plugin","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbubblydoo%2Fserverless-externals-plugin","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbubblydoo%2Fserverless-externals-plugin/lists"}