parcel-bundler/parcel

How do i mark some modules to external?

garrydzeng opened this issue Β· 98 comments

Choose one: is this a πŸ› bug report or πŸ™‹ feature request?

πŸ™‹ feature request

πŸ€” Expected Behavior

Don't include external module in bundled file everywhere.
Like rollup globals option.
https://rollupjs.org/#big-list-of-options

🌍 Your Environment

Software Version(s)
Parcel 1.0.3
Node 9.2.0
npm/Yarn Yarn 1.2.1
Operating System Windows 10

Can you elaborate on the behavior you'd like to see here? I'm not sure how the option as documented by rollup is useful. Why not just use the global variable? Why is it an import at all?

@devongovett

Let me take React as an example.

I can use React as global variable in my project, but many third-party library imported React in their code and I can't control that, so, if I include React through a CDN, like <script src="//cdnjs.cloudflare.com/ajax/libs/react/16.2.0/umd/react.production.min.js"></script> and the bundle in my index.html, then browser has to download React twice.

  • one copy in the bundle
  • one from CDN

In order to avoid this problem, I have to import React in my project. If Parcel can replace React as global variable when it handling third-party library, then it can reduce bundle size & I can use CDN to speed up my website. React can be any library also.

I made an example: parcel-example.zip.
Hope this help!

I see. This seems somewhat related to aliasing actually. See #25.

Another vote for this issue... I really like what you've done here with Parcel. It was super-easy to get my project 95% set up. This issue, (external resources), along with dev-server proxying are the two things holding me back.

Motivation for both is that I'm developing components that will be used within a larger application. There are external dependencies (that I don't want packaged up in the component build), and there are external APIs I want to hit while developing. In both cases, these are things that will be there when the project is deployed, but which are not a part of this component.

garrydzeng mention Rollup's "globals" option. Webpack similarly has "externals" (https://webpack.js.org/configuration/externals/).

You mentioned babel aliasing. That, I think, is a separate issue, because it's after the packager has decided to include a file, you can tell babel an alternate path to get it. There doesn't appear to be a way to tell babel that it should be ignored entirely. (At least not in a way that will keep other things happy). The "external" concept would really need to be configured at the packager.

I'd be happy to help out and contribute code to solving these issues, but given the 'zero-configuration' mantra, it's not clear to me where or how either could/should be done in Parcel. If you have ideas along those lines, please let me know. --Thanks

+1 on this issue. The aliases implemented in #850 can not solve this issue. Just like globals in Rollup and externals in webpack, Parcel also need an option to map modules to a global value.

I'd like to help with this issue if needed : )

I have a patched version that allows external scripts. The idea is to mark scripts as β€œcopy” and provide a mapping of globale to modules that. But that still is kind of configuration...

@starkwang Cool! I’d be great if you can take this issue πŸ˜ƒ

@derolf @davidnagli Perhaps change the aliasing a slight bit and add something like this:

aliases {
  "react": false // This will ignore the package
}

This would cause it to skip without adding extra complex configurations. (Of course we still have to implement it and i'm not sure if it's sorta allowed by the standards)
This behaviour would be kinda similar to how browser.fs === false works for fs resolves

I have a proposal to extend the "alias" syntax to support externals:

"<module>": "<file>!<evaluated export term>"

Example (for cesiumjs):

"cesium": "./node_modules/cesium/Build/Cesium/Cesium.js!Cesium"

Semantics:

  • copy ./node_modules/cesium/Build/Cesium/** into the dest folder
  • lazy load or include in bundle (depending on how it is used)
  • create fake module "cesium" with cesium.exports = Cesium

Usage:
HTML inline load: <script src="cesium"></script>

@derolf isn't this different than the described issue? The issue is about using external packages from cdn's wouldn't this just be lazyloading everything from the local server or cdn?

@DeMoorJasper

For CDN:

"cesium": "http://cdn.xyz.com/foo/bar/Cesium.js!Cesium"

could work the same way. But instead of copying into dest folder it's is directly linked.

I think the main idea is to somehow refer to "prebuilt" folders -- disable parcel parsing -- and link them to a fake module.

So the exclamation mark disables parcel parsing of the content and the term afterwards creates the export directive for the fake module.

@derolf I think the ! syntax is fairly non-obvious, at least I haven't seen it used before. Is this 'standard' across similar implementations? Why not just use the key?

@woubuc So, if you look at webpack's external, you need a key plus which global symbol to bind to that key. So, we need to tell parcel which symbol to bind to the fake module.

Example: three-js exports a global symbol called THREE, so the syntax would be:

"three": "https://cdnjs.cloudflare.com/ajax/libs/three.js/90/three.min.js!THREE"

This follows the same approach in webpack.

@DeMoorJasper 's <PackageName>: false makes sense to me (but misses renaming... which may be important in certain cases).
I don't see the advantage of @derolf's <PackageName>: <CDN path>:!<import name>, unless it's solving a different problem than what I was talking about.

Could the webpack externals concept be followed directly, except inside package.json. Then it's a 1-to-1 with a well-known feature, and keeps the ability to rename the import.

package.json:

  "dependencies": { },
  "parcel": { /* Or should externals be at the top level? */
    "externals": {
      "MyProjectConfig": "config",
      "react": "react"
    }
  }
}

Unlike the aliases: {packageName: false}, this allows for renaming. You can import from the proper name of the package (as if you were really packaging it up), but still have the code get linked into whatever the actual named variable is in the CDN/inline script.

My deployed index.html/jsp that pulls in the parceled project...

   <script>
      config = {
        someValue: 123
      }
   </script>
   <script src="://some-cdn/react.min.js"></script>
   <script src="./build/parceled-up-project.js></script>

And finally... some random source file in my project:

import MyProjectConfig from 'MyProjectConfig'
import React, { Component } from 'react'

For both those imports, the packager can find the 'from' in the externals list, and know that it doesn't need to actually fetch/import anything. And the actual variable names can get assigned with the desired renaming.

Something like this makes the most sense to me... but it's obviously getting away from 'zero-config', so I assume it would need some consensus before building it in. What do you think?

Package.json is not available to the parser. Because resolution is done afterwards.

So, you need two things:

  • disable parcel’s parsing if you pull in a prebundled script
  • link a module to an exported variable

@MalcomDwyer okay, I can implement your β€œexternals” in package.json proposal and create module stubs the same way that Webpack does.

But, what is a good syntax to tell parcel to NOT bundle a script that is already in the public folder?

Like: <script parcel-ignore src=β€˜abc.js>

But, what is a good syntax to tell parcel to NOT bundle a script

In my example, that "deployed index.html/jsp" would not even be something that parcel ever sees...

I have my frontend project codebase that I use to develop, and test the javascript project. Then in a separate downstream project, I just pick up the bundle.js (src="./build/parceled-up-project.js in my example above) as a pre-built entity. It is in that downstream file that those external imports actually get resolved. (So they are external to parcel, but are part of the downstream project).

So I would probably have parcel building the real bundle.js (which ignores those externals), but also for development, I'd have parcel build a demo/index.html which would have the same imports and should include those files. I can't think of a time I'd be pointing parcel at an html with <script src="x"> and not want it to import "x".

I start bundling from a central HTML file and need to pull in stuff to parcel and stuff that is prebuilt (three js and cesium js).

Package.json is not available to the parser. Because resolution is done afterwards.

You shouldn't wait this long right?
You just append the externals to the resolver, this way u can detect it before it even gets to the parser

<script parcel-ignore src=β€˜abc.js>

No big fan of this, it would add a new sort of config to the ecosystem and limit the abilities it has, this seems to be more a use-case for the false key I described in my first comment on this issue, as this would extend beyond just html (adding the possibility to ignore in JS, for example optional dependencies parcel tries to resolve)

As far as I saw, parsing is done before resolving.

So, how can I tell the parser of the index.html not to bundle a script, but just leave it untouched.

In the current codebase u should look at asset.addUrlDep... and the Resolver as those are the only places we use to actually resolve assets.

I'll try to make it a bit more clear, the parser.getAsset() never gets called if the dependency never gets added to the asset in the first place. The package is known to the parent asset, so you have full access to package.json

@DeMoorJasper I looked a lot at the code this weekend and found the asset.addUrlDep. The problem is that the parsing of the asset, processing, and generating the output is done in a Worker. This Worker has NO access to the resolver, but just creates put dependency-links to the bundles that will contain these dependencies into the processed files.

Then later, the bundler picks up the queued dependencies and resolves (including aliases) them and starts another Worker process.

Check this Bundler: loadAsset(asset)

async loadAsset(asset) {
... FIRST PROCESS
    let processed = this.cache && (await this.cache.read(asset.name));
    if (!processed || asset.shouldInvalidate(processed.cacheData)) {
      processed = await this.farm.run(asset.name, asset.package, this.options);
...
    }
... THEN RESOLVE
    // Resolve and load asset dependencies
    let assetDeps = await Promise.all(
      dependencies.map(async dep => {
...
          let assetDep = await this.resolveDep(asset, dep);
...
      })
    );

...
}

To fix this, we would need to expose the resolver to the Worker and let resolution happen there???

Ah, you mean Assert.pkg? You are right, it's having full access to the package.json!

I'm pretty sure if the dependency never gets added, the bundler never picks them up anyway? The resolver changes would just be in case the bundler adds some dependencies.
Now that I looked at it resolver changes aren't even neccessary unless u wanna move the responsibility to the bundler, I haven't tested any of the things i say i'm suggesting as a fix.

And the code sample you show makes sense but doesn't have much to do with the ignore or this issue. It will never get to loadAsset if the asset is ignored, as that happens on the loadAsset of the previous/parent asset.

And if it's really necessary u could spin up a resolver per worker similar to how the parser is being used inside workers, although it would be nice to prevent this.

EDIT: Yes i do mean Asset.pkg (of the parent of the ignored/external package)

So, which syntax do we follow now? An external needs TWO things: where is the file located and what does it export.

Example with cesium js:

"externals": {
 "cesium": {
   "exports": "Cesium", // --> this generates a module stub with cesium.exports = Cesium;
   "file": "node_modules/cesium/Build/Cesium/Cesium.js", // ---> that is the file to be src for the script
   "copy": "node_modules/cesium/Build/Cesium", // ---> that tells parcel to copy (or symlink) all that content into the public dir
 }
}

"copy" here is important since Cesium.js includes other files from that folder. So, the WHOLE folder needs to be present!

Makes sense?

I still feel like these are two different concepts being mixed up...
https://webpack.js.org/configuration/externals/

The externals configuration option provides a way of excluding dependencies from the output bundles. Instead, the created bundle relies on that dependency to be present in the consumer's environment.

So I'm not following how your Cesium example... (telling parcel to copy/symlink folders to the public dir) is related here. (I'm not saying that's not useful... just not the same as webpack externals concept). That again seems closer to the "aliases" concept.

@MalcolmDwyer well, it's two related problems:

  • How do I specify an external (to create a module stub)
  • How do I OPTIONALLY bring that external into my public folder (to include it from HTML)

Pull request

Please read the documentation of the patch.

Symlinking or copying was too much black magic, so I implemented two things:

  • Externals in package.json. The semantics are the same way as in Webpack, but I only implemented stubbing a global that was exported by the external module (more can be done if needed)
  • External references in URLs, i.e. that already exist in the output directory. If the path starts with a /, Parcel assumes the file already exists in the output directory.

I get what you are saying... here's my train of thought, and halfway through I get what you meant. :)

I think you would set up two different parcel targets in that case...
Here's what I would do for the dev vs. build case I was describing. Not sure if it would apply to you:

package.json

"scripts": {
  "build": "parcel build src/index.html",
  "demo": "parcel watch demo/index.html"
}

src/index.html (or just src/index.js if that's what you are building) would not include any links to import the external stuff.

demo/index.html would include the <script> tags pointing either directly to remote CDNs (or maybe to the dist files inside your node_modules... <script src="node_modules/some_lib/dist/index.js"> though I'm not sure that would work the parcel demo server)

... Ok, I get what you're saying now. That demo html is going to be affected by the same 'external'="don't actually import this thing" rule as the JS.

The difference with the equivalent webpack set up is that you have to go out of your way to build an html template for your demo code, and the webpack packager is not going to try to pull in <script> tags no matter what. With parcel, if it sees <script> it's going to go get it.

So this thing would need to be able to differentiate between import React from 'react' in the JS, and treat that one as an 'external', but not ignore the <script src="react.min.js"> when it sees that in demo.html.

Malcom, I think I nicely resolved the problems in the pull request. Please read the doc of the pull request.

any update about this?

Dead because team doesnβ€˜t like my pull request...

look like I have to search the alternative bundler

Can't the 1.7.0 alias be used for that?

  • package.json
{
  "alias": {
    "react": "./externals/react",
    "react-dom": "./externals/react"
  }
}
  • externals/react.js
module.exports = React

Probably yes. Then the only thing missing is to convince Parcel to ignore the script tags to local prebundled files.

@fathyb Maybe we could add externals to package.json?
As this is basically rename or ignore instead of rename and package.
Could possibly even fix the use-case of ignoring as well

Maybe something like this:

externals: {
  "react": "ThisIsReact", // Renames and ignores
  "jQuery": false, // Ignores entirely
  "$": false, // Ignores entirely
  "/externals/**": false // Ignores an entire folder
}

This is bundler specific config, which is not such a good idea but I haven't seen any standardised non bundler specific config of this anywhere?

@derolf you can use inline JS for that, it's not perfect but it works while we find a correct solution :

<html>
<body>
  <!-- <script src="/lib/foo/index.js"></script> -->
  <script>
    var script = document.createElement('script')

    script.src = '/lib/foo/index.js'
    document.body.appendChild(script)
  </script>
</body>
</html>

OP seems to only talk about module externals (using require or import), we should probably restrict the discussion to this as it will probably require two different approaches. Feel free to follow up in a new issue if there is not one already open for that.


Dead because team doesnβ€˜t like my pull request

That's not how it works and nobody ever said that. For the context : #955

  1. The PR doesn't have any tests
  2. You were already told by Devon :

We cannot rely on absolute paths being external. Absolute paths are valid and can be resolved by the new Parcel resolver relative to the project directory

  1. If you discussed about it before, like @MalcolmDwyer did in this thread, maybe someone would've told you before that it would conflict with the new resolver. A PR isn't the right place to discuss this given the limited audience.

I totally get and understand that a stale PR can be frustrating but please avoid this kind of non-constructive comments.

@DeMoorJasper Yup, sounds like the best solution πŸ‘

@fathyb just take it easy, I am new to parcel. that's why I decide to find an alternative. but yeah, parcel is the best one.

@fathyb Yeah, sorry! And I still love Parcel!

@DeMoorJasper Your solution makes most sense to me. πŸ‘

After creating a PR and getting some feedback from @fathyb I think a valid workaround for now would be to alias packages to a file that simply exports the global variable you wanna point it to. This will probably be the way we'll implement it anyway

Turn

"externals": {
  "react": "React"
}

Into

"alias": {
  "react": "./react-external.js"
}
// This is react-external.js
module.exports = React;

If this works, I'm not sure if we'll even still need externals, as this works pretty decent.

@DeMoorJasper, your approach is working just fine, but the problem is, I have +7 external dependencies and is exhausting to be making every file for every dependency, a shortcut will be very good

@DeMoorJasper, that's not the correct approach to implement this. What if the reference to the external is not coming from the global but from a commonjs environement ? In that case require('react ') should be injected instead of window.React.

whatever , some syntax like this is really needed.

<script parcel-ignore src="./libs/abc.js">

use parcel-ignore to keep ./libs/abc.js untouched

I have a related issue, with TypeScript config.
My goal is simple: I want to use TypeScript types for Firebase in VS Code, and load Firebase from CDN.

But I couldn't get it to work with Parcel, so I'm not using Parcel even though I love the 0-config idea and using .ts directly.

Setup:
In index.ts:

import * as firebase from "firebase";

In index.pug:

html
    head
        script(src='https://cdn.jsdelivr.net/npm/firebase@4.13.1/firebase.min.js')

Expected:
During bundling, parcel.js does not double-include firebase

Actual:
During bundling, there's an extra 1MB+ file (thus my total website goes from ~40K to ~1MB size) containing second copy of firebase

External configuration is pretty conditional. For instance I want to use externals only if I'm building the client bundle for production.

Having them in package.json makes it harder to have such configurations.

I'm currently using this comments suggestion with this condition inside my code module code.

package.json

"alias": {
        "react": "./globals/react.js"
}

./global/react.js

if (!process.env.IS_SERVER && process.env.NODE_ENV === 'production') {
    module.exports = React;
} else {
    module.exports = require('../node_modules/react'); // has to be relative to avoid circular deps 
}

⚠️
But I'm running into this issue when I do that with Lodash:

If a dependency depends on submodules of lodash like this:

require('lodash/assign')

module resolution will fail.

I much rather pass this configuration to the CLI or API.

I switched to webpack, even though I love the no setup of parcel, when it works. My project went down from 5MB to 60K and I'm loving it :)
Also I feel much more confident with the configuration in place that what's needed to happen, happens.
Disclaimer: took 2 days to setup the initial config.

Honestly, I like @DeMoorJasper 's solution. When compared to the advantages that I get from using Parcel, this small inconvenience is totally fine with me. When I run create react app with WebPack, my CPU burns like anything. I love Parcel + React and this small compromise.
Thank you Parcel Team

Made a package for this, https://www.npmjs.com/package/parcel-globals, small command line with no test

πŸ‘ to parcel-globals. Another alternative for cases when a module is not globally available and must be imported, is to use a syntax similar to the one bellow:

let my_module = eval('require("my_module")');

// (updated) or, w/o eval
let _import = require;
let my_module = _import("my_module");

I'm using this in a serverless action in Apache OpenWhisk, where generic NodeJS actions are executed in a docker container with some pre-installed modules.

sgf commented

@DeMoorJasper @externals
i have the same problem.i talking about it in another issue with u.

I have a related issue, with TypeScript config.
My goal is simple: I want to use TypeScript types for Firebase in VS Code, and load Firebase from CDN.

But I couldn't get it to work with Parcel, so I'm not using Parcel even though I love the 0-config idea and using .ts directly.

Setup:
In index.ts:

import * as firebase from "firebase";
In index.pug:

html
head
script(src='https://cdn.jsdelivr.net/npm/firebase@4.13.1/firebase.min.js')
Expected:
During bundling, parcel.js does not double-include firebase

Actual:
During bundling, there's an extra 1MB+ file (thus my total website goes from ~40K to ~1MB size) containing second copy of firebase

just i need exclude the Vue.js when deploy.
but when in develop i need do import.

 "exclude": {
      "react": "react.js",
      "vue": "vue.js"
}

if Parcel support,that means Parcel will also better Freindly for Develop MPA(Multi Page App).

Aliasing (as mentioned by @fathyb above) partially solves this, which is great. Having to create the stub externals modules is a bit annoying, but doable.

The ideal scenario would be to also have some sort of HTML helper that supports external URLs, with a fallback to local URLs if the external URL fails to load. Of course, you can write the code for that manually, but it's useful for the framework to handle it.

For example, ASP.NET Core MVC has a script tag helper that supports specifying a fallback:

	<script 
		src="https://fb.me/react-0.14.3.min.js"
		asp-fallback-src="~/Content/js/lib/react-0.14.3.min.js"
		asp-fallback-test="window.React">
	</script>

This renders the following HTML:

<script src="https://fb.me/react-0.14.3.min.js"></script>
<script>(window.React||document.write("\u003Cscript src=\u0022\/Content\/js\/lib\/react-0.14.3.min.js\u0022\u003E\u003C\/script\u003E"));</script>

That is, try to load it from the CDN, but fall back to the local copy if the CDN load fails (based on the presence of the window.React global).

Something similar in Parcel could likely be done as a config option, say something like:

"externals": {
  "react": {
    "url": "https://examplecdn.com/react-1.2.3.min.js",
    "global": "window.React"
  }
}

As part of the build, this could compile the version of react in node_modules. At runtime, it could update the <script> tag in the HTML to load the CDN URL, and if it fails to load, use the fallback local version.

@Daniel15 this isn't a widely supported syntax, it's probably not a good idea to introduce custom syntax.

You could however just create a js file that does that for you and include that, that way you would not even need externals.

Inlined JS will be supported in Parcel 2 as far as I know. So you could inline it in the future.

@DeMoorJasper Sure, I didn't mean that specific syntax, but just the idea in general (having some way to fall back to a local build if the CDN URL fails to load).

Also useful when your html has scripts like FB SDK or Twitter pixel tags, etc.

Is this following use case doable with Parcel using alias?
lib.js

// needs to be processed by Parcel to resolve libraries e.g. lodash then assigned to global variable "globalLib", which is already supported by Parcel
module.exports = {
    "require": (function(x0) {
        return {
            "lodash": require("lodash"),
        }[x0]
    })
};

loader.js

const require = globalLib.require;

index.js

// output of another builder so it needs to be ignored by Parcel
const _ = require('lodash');
console.log(_.map([0, 1, 2], x => x * 2));

index.html

    <script src="lib.js"></script>
    <script src="loader.js"></script>
    <script src="index.js"></script>

It would be quite simple if file 2 and 3 is concatenated but they are kept separately, and then Parcel bundled the libraries twice in lib.js and index.js

@quanganhtran I'm pretty sure parcel will always run over require statements, therefore this isn't really possible unless require is named something else, like webpackRequire for example

Hello, thanks for this wonderful project. It saved me about 95% of my dev/configuration time, and we're talking about hours here (or could be days). However we hit a scenario where we needed the external library (pre-compiled, we only get the compiled version - we do/work with integration stuff from 3rd party providers/systems) to be marked as external - to avoid dual transpilation, but can't find a better way - hacks? possibly but we can't afford at this time.

I guess in this case, I don't mind parcel to have an option to do minor tweaks of the build process eg. parcelignore or something like that.

I'm in the process of moving back to babel "for the meantime" but happy/looking forward to use parcel in the coming weeks (?). Otherwise happy for any good suggestions. thanks

bard commented

A dirty hack I'm using is "hiding" the scripts from parcel by loading them through an inline script:

    <script src="lib.js"></script>
    <script src="loader.js"></script>
    <script>
      function loadNonLocalScriptSync(href) {
        var f=document.getElementsByTagName('script')[0],
            j=document.createElement('script');
        // Simulate <script> faithfully
        j.async=false;
        j.src=href;
        f.parentNode.insertBefore(j,f);
      };

      loadNonLocalScriptSync('index.js');
    </script>

But yeah, for me this has been the major pain point with parcel so far.

I tried @DeMoorJasper aliases workaround and it works OK for developing locally but as soon as you do parcel build to get a production build seems like it ignores the aliases and includes the whole external library again. Not sure if this is how is supposed to work or if I could be doing something wrong.

as soon as you do parcel build to get a production build seems like it ignores the aliases and includes the whole external library again.

Hmm, that's strange. I'm using that externals hack on one of my sites (https://srcbrowse.com/) and it's working fine for the production build.

Here's the code:
https://github.com/Daniel15/SrcBrowse/blob/d91a6b70060ef6d6cbdf6a3607ae5169d637d504/js/externals/prism.js
https://github.com/Daniel15/SrcBrowse/blob/d91a6b70060ef6d6cbdf6a3607ae5169d637d504/package.json#L13

Not sure if I'm having the exact same problem, but I struggle to import modules which only have vars (global variables, no exports).
With help from @nicolo-ribaudo I've looked into writing a babel plugin to convert such modules into having an export. He wrote this astexplorer for me.
It converts

var X = {};

into

var X = {};export default X;

This plugin works, but it doesn't seem to be run in parcel, but that might be expected.
Is there any way I can force the plugin to run?

Update:

If I run my plugin directly inside parcel (by modifying JSAsset, some hacking, and changing plugin to add module.exports) it actually works, and global vars are exported. Couldn't this be a visitor built into parcel? It shouldn't cause harm, and it could run only when nothing else is exported.

Possible to test by replacing async transform()-method in node_modules/parcel-bundler/src/assets/JSAsset.js with this gist.

Having the same issue has many in this thread - I need to include this file as an asset (https://cdnmc1.vod309.com/clm/release/veeva-library.js) that is exposing some functionality on the window global, but Parcel seems to tweak the output code once compiled, making it impossible to use.

Current options I found to workaround this at the moment are:

  1. Include the script using the remote address <script src="https://cdnmc1.vod309.com/clm/release/veeva-library.js"></script>
    1. This won't work if the compiled version is executed offline (which is our case)
  2. Save the asset in the codebase + update the compiled JavaScript with the original one <script src="./assets/veeva-library.js"></script>
    • Obviously a dirty workaround I don't want to go through

Seems to be the only thing preventing me from using Parcel today for production purposes versus Webpack. There are a couple of interesting suggestions in this thread to work around this, looking forward to see this added part of Parcel.

A dirty hack I'm using is "hiding" the scripts from parcel by loading them through an inline script:
...

While this is a good idea, unfortunately it seems that this is still an issue when attempting to load dependencies that need to be loaded in a particular order. In my case, it is a policy to self-host all scripts, so bringing in some minified dependency that uses window (like jQuery) causes issues when bundling.

However, I still want my own scripts to be bundled. An attempt at mixing the two approaches gets me

<script>
   loadScriptSync('/scripts/jquery.min.js'); // Hides from parcel
</script>
<script type="text/javascript" src="/scripts/depends-on/jquery-lib.js"></script>

The script that depends on the lib does not encounter it due to the execution order for the dynamically loaded script happening afterwards (even though async = false)

In summary:
Some passthrough/ignore would be really useful, despite some of the mentions discussing otherwise

bard commented

I re-tested this recently and confirm what @felipemullen wrote. Either something changed since I first used the hack or my code was not really depending on strict execution order.

Is there any solution to this?
Trying to include socket.io js file that obviously comes from server, how would i write it in the file and let parcel just include the script tag and not try to bundle the file ?

script(src="/socket.io/socket.io.js")

**Using PUG

@derolf Did you find a solution to pulling in parts of Three.js that are not part of the core? That was my exact question over in a new issue (#2412), seems people on webpack are using imports-loader, e.g.

require('imports-loader?THREE=three!three/examples/js/loaders/OBJLoader.js');

where the ?THREE=three part means to pass in the local variable three in to the imported script as the name THREE

I'm a bit late to this thread so I'm having a hard time digesting whether there's any workaround that people are currently using to pass globals into imported libraries (e.g. the THREE global that's needed by OBJLoader.js). I'm figuring that since the issue is still open that Parcel doesn't directly support this.

moos commented

Possible solution to use peerDependencies in package.json to signal "external" dependencies in #2578.

Having a peer dependency means that your package needs a dependency that is the same exact dependency as the person installing your package. This is useful for packages like react that need to have a single copy of react-dom that is also used by the person installing it. (yarn)

I'm also in favor of using peerDependencies of package.json to indicate the external requires

A part of this solution should be to build the external dependency to be able to host it on your own server. You still get benefits of caching and other niceties like being able to better keep track of your core app size, and easier debugging, etc.

This is something I'd be keen to see introduced as well. For my part, I'm keen to use Parcel as part of a boilerplate for creating plugins/blocks for the new "Gutenberg" editor in WordPress, which is heavily JS/React.

We need to pass in third party libaries via externals in the Webpack config, and from what I've read this isn't possible right now?

const wplib = [
	'blocks',
	'components',
	'date',
	'editor',
	'element',
	'i18n',
	'utils',
	'data',
];

...

externals: wplib.reduce(
		(externals, lib) => {
			externals[`wp.${lib}`] = {
				window: ['wp', lib],
			};
			return externals;
		},
		{
			react: 'React',
			'react-dom': 'ReactDOM',
		},
),

Any workaround for the same?

azz commented

We're running into this a lot with modules that aren't even used (required conditionally), and some that are builtins like v8. Currently that list is:

  • async_hooks
  • v8
  • graphql (dynamic require from dd-trace)
  • pg-native (dynamic require from pg)

We currently have a hideous sed -i statement that replaces the require('x') with eval('require')('x') to make parcel ignore these requires, but passing a comma-separated list via a command line option would be much better. All of these examples are not peerDependencies (nor should they be).

Until there's an actual solution, here's a tiny parcel-plugin-html-externals, which allows to ignore certain script and stylesheet link tags in html files by specifying

"externals": {
  "glob/**/*": false
}

in the package.json

@devongovett, are there any updates on that issue? Would be super useful to see this, or even @stoically's hack-around in the next release. Thanks!

+1 here

This keeps me from switching to Parcel on about 5 projects.

Love parcel but let's face it, it's not just as mature as Webpack yet!

I'm not sure about that. Webpack's code is (or was when I last looked at it) much more horrible. Additionally, the ecosystem of Webpack is quite fragmented. I lost interest in upgrading Webpack at some point - because either half of the plugins did not work any more at all, had some serious side-effects, or produced garbage.

With Parcel I never had a problem (but on the other side the API was not broken as often as with Webpack - I'm a little bit afraid of v2 for this reason).

Webpack may be more mature, but maturity !== simplicity. Parcel is a far, far simpler solution to the toooling needs of many, and this issue, once resolved, will unblock a lot of people who are waiting to jump ship for good reason.

I wonder if it's the best place to discuss parcel vs webpack for folks following this issue :P

Actually I don't think it's that difficult to do. We do that in Piral where we use Parcel as our build system integrated in our CLI. For us we need to strip out shared dependencies from frontend modules (called Pilets).

What we do could be (i.e., is definitely) considered a hack (and obviously I wish that there may be a simpler alternative, which may exist with Parcel 2), but it could easily be applied / transported in a plugin.

If you guys are interested I guess I could try to craft a simple plugin on the weekend. Only thing to discuss upfront is what packages to mark as external.

Options I see: peerDependencies (this is what we do in Piral essentially - though its a bit more complicated than that), a new key in package JSON (e.g., externalsPackages) or in a dedicated config / rc file.

Any thoughts?

Seems like maybe there's two separate but related features here:

  1. Excluding a module from a bundle, but leaving the import or require there. Use case would be for e.g. libraries with peer dependencies, builtin modules in node/electron, etc. Maybe this could be solved by excluding peerDependencies.
  2. Mapping an import to a global variable that is available somehow. Use case would be for e.g. libraries from a cdn. This might need separate configuration, e.g. with aliases.

See also #3305

So I hacked this together: https://www.npmjs.com/package/parcel-plugin-externals

Don't know if its useful. If there is some use, please give feedback. Thanks! 🍻

I created a pull request to support this without a plugin

You are officially a fullhero now πŸ‘

Guys! You are making parcel the easiest tool in the toolbelt to use! That is amazing! Thanks alot!

What's the status of this issue? As per @devongovett 's first use-case:

Excluding a module from a bundle, but leaving the import or require there. Use case would be for e.g. libraries with peer dependencies, builtin modules in node/electron, etc. Maybe this could be solved by excluding peerDependencies.

I'm deploying to Lambda, and I don't need to include the tremendous aws-sdk in my package. I just need a way to let require('aws-sdk') access the global available on Amazon's servers.

@FlorianRappl #144 (comment)
Saved my bacon to be able to use Parcel for a WordPress plugin that uses jQuery (which comes from WP itself)
THANK YOU!

@FlorianRappl

Any idea how to glob-exclude? I don't want these:
image

Hi @cliffordp - please create issues at https://github.com/FlorianRappl/parcel-plugin-externals such that they have increased visibility for other users of the plugin.

Have you tried https://github.com/FlorianRappl/parcel-plugin-externals#dynamic-dependency-resolution with a rule factory?

I can imagine that (taking only the @wordpress/* packages into consideration)

const rx = /node_modules\/@wordpress\/(.*?)\//;

module.exports = function(path) {
  const result = rx.exec(path);

  if (result) {
    const package = result[1];
    return `@wordpress/${package} => require('@wordpress/${package}')`;
  }

  return undefined;
};

could help here.

#4072 implemented this, for additional ways to exclude modules feel free to open an RFC to open discussion on this

@DeMoorJasper that didn't work for me (unless the "library" bit needs to be customized for my use case, in which I'm unsure how-to):

image

@cliffordp I think this is a Parcel v2 addition - for Parcel v1 I guess you'll need to stay with the parcel-plugin-externals.

Sorry to bump an issue three years later, but this is the first result in Google for

"parcel mark dependency as external global variable"

From looking at this issue, it's hard to tell if this was ever resolved? The cryptic comment above from @FlorianRappl

I think this is a Parcel v2 addition

Was it?

I'm trying not to bundle external dependencies (specifically BabylonJS, but whatever) into my build, and treat resolution of that import as a global variable BABYLON, to support this ecosystem which mostly uses global variables to communicate between scripts.

I've defined targets:

  "targets": {
    "babylon": {
      "source": "src/plugins/babylon/index.ts",
      "includeNodeModules": {
        "babylonjs": false
      }
    }
  }

This errors with:

@parcel/packager-js: External modules are not supported when building for browser

13 | Color4,
14 | } from 'babylonjs';
| ^^^^^^^^^^^

This error doesn't make sense.

If I remove the includeNodeModules line, the bundle works, except Parcel is very obviously doing the wrong thing:

✨ Built in 37.08s

dist/babylon/index.js       ⚠️  4.48 MB    20.88s

Was this ever resolved, or do I need to downgrade to v1 and install https://www.npmjs.com/package/parcel-plugin-externals ? That plugin readme has nothing about if it should still be used or not.

Searching the documentation for "externals" returns nothing.

So either Parcel is not read for prime time, or much, much more likely, I'm missing something and it's just not well documented? Can someone point me in the right direction to help future Googlers?

@AndrewRayCode I think this is achieved with aliases in v2.

@AndrewRayCode aliases achieves this in v2 - for v1 you can use the plugin.