parcel doesn't bundle typescript imports

Created on 25 Oct 2018  ยท  13Comments  ยท  Source: parcel-bundler/parcel

โ” Question

How can I let parcel bundle the imports from typescript ?

๐Ÿ”ฆ Context

I'm using parcel to process typescript for a webextension.
JQuery and its type definitions are installed via npm.
At the top of my typescript file I have:

import $ from "jquery";
import "bootstrap";

But at runtime, Chrome complains that jquery is not defined.

๐Ÿ’ป Code Sample

A minimal example is on git: https://github.com/lhk/parcel_jquery_bug_demo

git clone https://github.com/lhk/parcel_jquery_bug_demo
cd parcel_jquery_bug_demo
npm install
parcel build src/manifest.json

-> open chrome and load the dist folder

The git repo contains:

src/manifest.json

{
  "manifest_version": 2,
  "name": "pc",
  "version": "0.0.1",
  "description": "none",
  "author": "",
  "content_security_policy":"script-src 'self'; object-src 'self'",
  "content_scripts": [
    {
        "matches": ["<all_urls>"],
        "js": [
         "./content/index.ts"]
    }
  ]
}

src/content/index.ts

import $ from "jquery";
import "bootstrap";

$(function () {
  $('[data-toggle="popover"]').popover();
});

./package.json

{
  "name": "pc",
  "version": "0.1.0",
  "description": "",
  "author": "",
  "license": "",
  "devDependencies": {
    "parcel-plugin-web-extension": "^1.4.0",
    "typescript": "^3.1.3"
  },
  "dependencies": {
    "@types/bootstrap": "^3.3.7",
    "@types/jquery": "^3.3.10",
    "bootstrap": "^3.3.7",
    "jquery": "^3.3.1"
  }
}

Then load the dist directory as an extension in chrome. And load any website.
The error message is:

Uncaught ReferenceError: jQuery is not defined

๐ŸŒ Your Environment

| Software | Version(s) |
| ---------------- | ---------- |
| Parcel | 1.10.1
| Node | v8.12.0
| npm/Yarn | npm 6.4.1
| Operating System | ubuntu 18.04.1 64bit
| Chrome | 70

Question

Most helpful comment

Not a parcel related issue, just jQuery beeing jQuery.It needs special treatment when imported via ESM it exports itself twice.

import {jQuery, $} from 'jquery';

Or doing it like this is more inline on how jQuery actually wants you to be used. You won't need to import it anymore in other scripts afterward.

import jQuery from "jquery";
window.$ = $;
window.jQuery = jQuery;

All 13 comments

Not a parcel related issue, just jQuery beeing jQuery.It needs special treatment when imported via ESM it exports itself twice.

import {jQuery, $} from 'jquery';

Or doing it like this is more inline on how jQuery actually wants you to be used. You won't need to import it anymore in other scripts afterward.

import jQuery from "jquery";
window.$ = $;
window.jQuery = jQuery;

Thank you for the quick answer. I've tried your two versions, exactly as they are.

The second version is not valid typescript, the compiler complains that $ and jQuery don't exist on type window. This is not a compile time error, it simply warns me about it. I bundled it like this anyways and it results in the error message:

Uncaught ReferenceError: $ is not defined

So I guess it wants me to import $, too.

The first version also confuses the compiler. The warning is: [ts] Module '"/home/lhk/programming/parcel_jquery_bug_demo/node_modules/@types/jquery/index"' has no exported member '$'.
I can compile it like this, but then the error message in Chrome doesn't change.

I think this is related to bootstrap. The following code works fine:

import $ from "jquery";

//import "bootstrap";

$(function () {
  //$('[data-toggle="popover"]').popover();
  $('[data-toggle="popover"]').click(function(){});
});

It seems as if parcel doesn't see that bootstrap also needs jQuery. And somehow the dependency for bootstrap isn't satisfied.

Sorry, I messed up (don't know how though. I tested more than once and tried to do everything carefully).

The import of {$, jQuery} doesn't work. Your second recommendation does, though:

import $ from "jquery";
(<any>window).jQuery = $

So it does import everything correctly now.
Thank you very much for the quick help :)

Is there a cleaner way to make this work, though ?
I know that this might be very bootstrap specific and is probably off-topic for a parcel issue.
Feel free to close this.

I have created a question on SO for this problem, too.
If you post your answer there, I'd accept it:
https://stackoverflow.com/questions/52991489/using-parcel-to-pull-in-typescript-dependencies?noredirect=1#comment92895551_52991489

Oh now that i see it i messed up the second code a bit :P

[EDITED]

import jquery from "jquery";
(<any>window).$ = (<any>window).jQuery = jquery;
import "bootstrap";

Also you're right about that this still is not 100% set up for typescript, you need to define the "dom" lib to make the browser API available same applies to document etc. i'll give you a proper fully explained answer on SO. ;)

Oh, I have another follow-up:

It is very much related to this setup. But please tell me when I should close this issue and open another one.
I would like to add bootstrap and jquery to my manifest, too. There will be some html injected into the site, and this needs bootstrap. I think a good way to serve this is by adding jquery and bootstrap as contentscripts.
In any case, my manifest now has these added contentscripts:

  "content_scripts": [
    {
        "matches": ["<all_urls>"],
        "js": [
        "./assets/jquery/dist/jquery.min.js",
        "./assets/bootstrap/dist/js/bootstrap.min.js",
        "./content/index.ts"]
    }
  ]

This leads to a similar error message (in chrome again):

Uncaught Error: Bootstrap's JavaScript requires jQuery

To reproduce the error, I have added another branch on my sample repository:

git clone https://github.com/lhk/parcel_jquery_bug_demo
cd parcel_jquery_bug_demo
git checkout  bootstrap_manifest
npm install
parcel build src/manifest.json

I understand how to use imports to make parcel pick up the dependencies. And that bootstrap needs jQuery added to window explicitly.
But why is bootstrap unable to pick up jquery if they are both added as scripts ?
The jquery script is imported before bootstrap, it should be ran first and add itself to window.

I think i will be able to help you more on your repository, lets talk on the PR i made ;)

The jquery script is imported before bootstrap, it should be ran first and add itself to window.

Note: The npm package for jquery does not register itself globally if it detects a commonjs like environment, i.e. that module.exports is an object. However, several plugins and libraries for resp. depending on jquery rely on it to be available at global scope, either as $ or jQuery. Strictly speaking, it's not a problem of jquery itself, but of the ecosystem relying on it.

@DorianGrey to be honest, I don't understand what you are saying.

So far I thought there would be two options:

  1. Include jQuery in typescript. Here you would be right, there is a commonjs environment. So I have to register jQuery myself.
  2. Include jQuery and bootstrap in the manifest. Now they are simply executed as scripts, when a page is loaded. There should be no commonjs environment here.

How is version 2 different from simply including jQuery and bootstraps as script tags in HTML ?
Actually (as far as I know), webextensions don't support the javascript module systems. That's basically why a bundler is a must-have if you want to combine typescript and webextensions.
So why is there a commonjs environment to confuse jQuery ?

Most bundlers - including parcel and webpack - provide module.exports for the modules they handle, since it is required to bundle those in commonjs format. This holds true for every package installed via npm. Most packages intended for usage in the browser are using a UMD check to determine the module system that they are imported with, or if there isn't any.
Some bundlers offer ways to replace or omit this behavior for particular modules so that the UMD check determines a global environment to attach to. It seems that the plugin you are using does not do something like that (not sure if parcel offers a way to trigger something like that), thus module.exports is provided to every module imported (directly or indirectly) from your input file, including jquery.

How is version 2 different from simply including jQuery and bootstraps as script tags in HTML ?

Version 2 is used for webextensions, the other one for regular websites.

@DorianGrey, ok that makes a lot of sense.

And it also explains a problem with the manifest setup.
In principle, including a script with a tag directly in the html, or injecting it as a contentscript, should have the exact same effect. So I didn't understand why bootstrap couldn't see jQuery - after all, it is added in just the normal way.

But here, the jQuery which is added as a contentscript is bundled with parcel. It is not the original jQuery code, the code is executed in the commonjs environment. So I guess that's why version 2 is not behaving as expected.

Is there some way I can exclude scripts from being processed by parcel ?

It is not the original jQuery code, the code is executed in the commonjs environment.

It is. The distributed jQuery code can handle both a module and a global environment, it just detects the active environment and - depending on the result - either registers itself globally or provides a proper export.

Is there some way I can exclude scripts from being processed by parcel ?

At least I haven't found one in the docs. Suppose it would require a different asset handler for this purpose - at least that's how other bundlers are achieving such functionality. E.g. for webpack, there is an imports-loader that allows to override the values like module.exports, so that the environment detection of jquery no longer assumes to be in a commonjs environment.

@Hammster @DorianGrey
I appreciate the detailed and helpful feedback :)

For anyone scrolling down here for the final solution,
the way it works for me is the following snippet from @Hammster:

import jquery from "jquery";
(<any>window).$ = (<any>window).jQuery = jquery;
import "bootstrap";

I think my original question has been answered and am closing this now.

Was this page helpful?
0 / 5 - 0 ratings