FlorianRappl / parcel-plugin-externals

Parcel plugin for declaring externals. These externals will not be bundled. :package:
https://piral.io
MIT License
47 stars 3 forks source link

Broken output with "--bundle-node-modules" option #15

Closed jls-tschanzc closed 4 years ago

jls-tschanzc commented 4 years ago

Env:

"dependencies": {
  "aws-sdk": "2.585.0"
},
"externals": {
  "aws-sdk": "aws-sdk"
},
"devDependencies": {
  "@types/aws-lambda": "^8.10.47",
  "@types/node": "^13.9.8",
  "parcel-bundler": "^1.12.4",
  "parcel-plugin-externals": "^0.3.3-pre.20200323.2",
  "typescript": "^3.8.3"
},
"engines": {
  "node": ">=12.0.0 <13.0.0"
}

Code written in Typescript.

What I wanted to achieve:

Generally bundle all node_modules except a few external ones ("aws-sdk" for a lambda in my case).

Problem I encountered:

My index.ts looks something like this:

import { Handler } from 'aws-lambda'; // Types only "@types/aws-lambda" in devDependencies
import { Athena } from 'aws-sdk';
import { format } from 'util';
// Other code imports local to src folder [...]

export const handler: Handler = async (event, context) => {
  const athenaService = new Athena();

  //...

  return format('%s Done', 'Really');
};

When I compile this normally with e.g. parcel build ./src/index.ts --out-dir dist --out-file index.js --global handler --target=node --experimental-scope-hoisting --no-source-maps --no-content-hash it works correctly and the require('aws-sdk') is correctly left in the output.

But if I compile it with the --bundle-node-modules it breaks and the output contains something like this:

var k={};k=aws-sdk;

also the parcel output shows:

dist/index.js                                1.33 KB    1.38s
dist/aws-sdk => aws-sdk.5848e953.external        0 B     26ms

In both cases the require('util') is present in the output as it should.

FlorianRappl commented 4 years ago

Sounds reasonable. I'll investigate!

jls-tschanzc commented 4 years ago

Forgot to post my tsconfig, thanks for checking :)

{
  "compilerOptions": {
    "target": "ES2018",
    "module": "commonjs",
    "lib": ["ES2018"],
    "sourceMap": false,
    "outDir": "./dist",
    "strict": true,
    "noImplicitAny": true,
    "baseUrl": "./src",
    "typeRoots": ["node_modules/@types"],
    "types": ["node"],
    "esModuleInterop": true,
    "inlineSourceMap": false,
    "resolveJsonModule": true,
    "forceConsistentCasingInFileNames": true
  }
}
FlorianRappl commented 4 years ago

Alright so I looked into this and the behavior is not a bug.

The following things appear:

  1. You use target=node, which - by default - does not bundle in any modules. So the plugin is not even active.
  2. You use bundle-node-modules, which activates the plugin - however, you use as rule "aws-sdk": "aws-sdk", which - in the string array mode - would translate to "aws-sdk => aws-sdk". Obviously, this is not what is desired. Potentially this is a confusion with "react": "React". In this case a global variable React would be used instead of require('react').

For an existing module you'd still want require("aws-sdk"). So you now have 2 options.

  1. Just use "aws-sdk => require('aws-sdk')
  2. Go to the string array version
"dependencies": {
  "aws-sdk": "2.585.0"
},
"externals": [
  "aws-sdk"
],
"devDependencies": {
  "@types/aws-lambda": "^8.10.47",
  "@types/node": "^13.9.8",
  "parcel-bundler": "^1.12.4",
  "parcel-plugin-externals": "^0.3.3-pre.20200323.2",
  "typescript": "^3.8.3"
},
"engines": {
  "node": ">=12.0.0 <13.0.0"
}

Hope that helps!

jls-tschanzc commented 4 years ago

Thanks @FlorianRappl for taking the time to check this, but sadly your proposed way does not work. When I set:

  "externals": [
    "aws-sdk"
  ],

or

  "externals": [
    "aws-sdk => require('aws-sdk')"
  ],

parcel will fail:

> parcel build ./src/index.ts --out-dir dist --out-file index.js --global handler --target=node --experimental-scope-hoisting --no-source-maps --no-content-hash --bundle-node-modules

🚨  /aws-sdk.external:1:23: Cannot resolve dependency 'aws-sdk'
    at Resolver.resolve (node_modules/parcel-bundler/src/Resolver.js:71:17)
    at async Bundler.resolveAsset (node_modules/parcel-bundler/src/Bundler.js:433:18)
    at async Bundler.resolveDep (node_modules/parcel-bundler/src/Bundler.js:484:14)
    at async node_modules/parcel-bundler/src/Bundler.js:608:26
    at async Promise.all (index 1)
    at async Bundler.loadAsset (node_modules/parcel-bundler/src/Bundler.js:599:21)
    at async node_modules/parcel-bundler/src/Bundler.js:610:13
    at async Promise.all (index 2)
    at async Bundler.loadAsset (node_modules/parcel-bundler/src/Bundler.js:599:21)
    at async Bundler.processAsset (node_modules/parcel-bundler/src/Bundler.js:557:5)
FlorianRappl commented 4 years ago

It works for me - sure you, e.g., installed aws-sdk?

Keep in mind that the package must exist - externals just means that it will not be bundled in.

jls-tschanzc commented 4 years ago

After a bit more testing I found the issue to be the --experimental-scope-hoisting parcel option. When using the build command (e.g.):

parcel build ./src/index.ts --out-dir dist --out-file index.js --global handler --target=node --bundle-node-modules --no-minify --experimental-scope-hoisting 

it will fail as I described in my last comment but without using the scope-hoisting:

parcel build ./src/index.ts --out-dir dist --out-file index.js --global handler --target=node --bundle-node-modules --no-minify

it works as you said. Sadly this leads to an increased final bundle size.

FlorianRappl commented 4 years ago

Yeah good point. Not sure yet if its something about this configuration or in general.

Note that parcel's scope hoisting option is experimental and that parcel does not make a guarantee on its correctness (as a matter of fact it sometimes misses important stuff).