6

I've been trying to load a jquery plugin through webpack. This plugin is packaged as a npm module and in its dependencies includes only jquery. I think webpack loads that instance of jquery instead of using the one I provide globally with the ProvidePlugin. I tried all the solutions provided in another stackoverflow post (Managing jQuery plugin dependency in webpack) but they didn't make the trick; the result is always the same: "terminal() is not a function". If I manually modify the package in the node_modules folder deleting the jquery dependency in the package.json and the downloaded dependency in the node_modules plugin folder webpack successfully binds the plugin with the global instance of jquery. I know, I could simply make a fork of that package and use a private npm repository but I would like to use the official package.

That's my webpack configuration:

var webpack = require('webpack');
var HtmlWebpackPlugin = require('html-webpack-plugin');
var CleanWebpackPlugin = require('clean-webpack-plugin');
var CopyWebpackPlugin = require('copy-webpack-plugin');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var moment = require('moment');
var path = require('path');
var environment = process.env.APP_ENVIRONMENT || 'dev';

module.exports = {

  entry: {
    'app': './src/main.ts',
    'polyfills': './src/polyfills.ts',
    'vendor': './src/vendor.ts'
  },
  /*devtool: 'source-map',*/
  output: {
    path: './dist',
    filename: '[name].browser.' + moment().format('DDMMYYYYHHmm') + '.js'
  },
  module: {
    loaders: [
      { test: /\.component.ts$/, loader: 'ts!angular2-template' },
      { test: /\.ts$/, exclude: /\.component.ts$/, loader: 'ts' },
      { test: /\.html$/, loader: 'raw-loader' },
      { test: /\.css$/, include: path.resolve('src/app'), loader: 'raw-loader' },
      {
        test: /\.css$/, exclude: path.resolve('src/app'), loader: ExtractTextPlugin.extract('style', 'css', {
          fallbackLoader: "style-loader",
          loader: "css-loader"
        })
      },
      { test: /\.(png|jpe?g|gif|ico)$/, loader: 'file?name=fonts/[name].[ext]' },
      { test: /\.woff(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=application/font-woff&name=fonts/[name].[ext]" },
      { test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=application/font-woff&name=fonts/[name].[ext]" },
      { test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=application/octet-stream&name=fonts/[name].[ext]" },
      { test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, loader: "file?name=fonts/[name].[ext]" },
      { test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=image/svg+xml&name=fonts/[name].[ext]" },
    ]
  },
  resolve: {
    extensions: ['', '.js', '.ts', '.html', '.css']
  },
  plugins: [
    new webpack.optimize.CommonsChunkPlugin({
      name: ['vendor', 'polyfills']
    }),
    new HtmlWebpackPlugin({
      template: './src/index.html'
    }),
    new webpack.DefinePlugin({
      app: {
        environment: JSON.stringify(environment),
        config: JSON.stringify(require('./profile/' + environment + ".profile.js"))
      }
    }),
    new CleanWebpackPlugin(
      ['dist']
    ),
    new CopyWebpackPlugin([
      { from: './src/images', to: 'images' }
    ]),
    new ExtractTextPlugin('[name].browser.css'),
    new webpack.optimize.UglifyJsPlugin({ minimize: true }),
    new webpack.ProvidePlugin({
      $: "jquery",
      jQuery: "jquery",
      "window.jQuery": "jquery"
    })
  ]
};

Here the jquery.terminal package.json:

{
  "_args": [
    [
      {
        "raw": "jquery.terminal@0.11.11",
        "scope": null,
        "escapedName": "jquery.terminal",
        "name": "jquery.terminal",
        "rawSpec": "0.11.11",
        "spec": "0.11.11",
        "type": "version"
      },
      "/home/giovanni/Projects/Private/site"
    ]
  ],
  "_from": "jquery.terminal@0.11.11",
  "_id": "jquery.terminal@0.11.11",
  "_inCache": true,
  "_installable": true,
  "_location": "/jquery.terminal",
  "_nodeVersion": "4.2.6",
  "_npmOperationalInternal": {
    "host": "packages-16-east.internal.npmjs.com",
    "tmp": "tmp/jquery.terminal-0.11.11.tgz_1475868856610_0.3736777463927865"
  },
  "_npmUser": {
    "name": "jcubic",
    "email": "jcubic@onet.pl"
  },
  "_npmVersion": "3.5.2",
  "_phantomChildren": {},
  "_requested": {
    "raw": "jquery.terminal@0.11.11",
    "scope": null,
    "escapedName": "jquery.terminal",
    "name": "jquery.terminal",
    "rawSpec": "0.11.11",
    "spec": "0.11.11",
    "type": "version"
  },
  "_requiredBy": [
    "#USER",
    "/"
  ],
  "_resolved": "https://registry.npmjs.org/jquery.terminal/-/jquery.terminal-0.11.11.tgz",
  "_shasum": "eaeed2f8f305ac0477d71ef492e7d98d6064d812",
  "_shrinkwrap": null,
  "_spec": "jquery.terminal@0.11.11",
  "_where": "/home/giovanni/Projects/Private/site",
  "author": {
    "name": "Jakub Jankiewicz",
    "email": "jcubic@onet.pl",
    "url": "http://jakub.jankiewi.cz"
  },
  "bugs": {
    "url": "https://github.com/jcubic/jquery.terminal/issues"
  },
  "dependencies": {
    "jquery": "^2.1.4"
  },
  "description": "jQuery Terminal Emulator is a plugin for creating command line interpreters in your applications.",
  "devDependencies": {
    "istanbul": "^0.4.3",
    "jasmine": "^2.4.1",
    "jasmine-node": "^1.14.5",
    "jsdom": "^3.1.2"
  },
  "directories": {},
  "dist": {
    "shasum": "eaeed2f8f305ac0477d71ef492e7d98d6064d812",
    "tarball": "https://registry.npmjs.org/jquery.terminal/-/jquery.terminal-0.11.11.tgz"
  },
  "gitHead": "0f2e55a6501d96aa17d42e4fcc071fab906309d8",
  "homepage": "http://terminal.jcubic.pl",
  "keywords": [
    "terminal",
    "emulator",
    "prompt",
    "console",
    "keyboard",
    "type",
    "rpc",
    "input",
    "ui"
  ],
  "license": "MIT",
  "main": "js/jquery.terminal-0.11.11.js",
  "maintainers": [
    {
      "name": "jcubic",
      "email": "jcubic@onet.pl"
    }
  ],
  "name": "jquery.terminal",
  "optionalDependencies": {},
  "readme": "ERROR: No README data found!",
  "repository": {
    "type": "git",
    "url": "git+https://github.com/jcubic/jquery.terminal.git"
  },
  "scripts": {},
  "version": "0.11.11"
}
Community
  • 1
  • 1

3 Answers3

11

I solved my problem by slightly modifying my Webpack configuration. As suggested here (Managing jQuery plugin dependency in webpack), I added a jquery alias. In the example provided the alias is statically specified and it didn't work for me. I switched to a dynamic value and that solved the problem.

Here it is the snippet:

alias: {
      'jquery': path.resolve(path.join(__dirname, 'node_modules', 'jquery'))
     },
Community
  • 1
  • 1
  • 3
    This is the thing that helped me. I was using `jquery/src/jquery` as an alias, but few old jquery plugins have dependencies in their `package.json` and this alias matches the wrong directory. For example: `some-jquery-plugin/node_modules/jquery/src/jquery` when the plugin has jQuery as a dependency. With `jquery/src/jquery` as an alias, I had two different version of jQuery in my vendor.js because the old jQuery plugin expected anything after jQuery 1.0.0 and yarn/npm did download jQuery 3 while my project required older version 1.12.4 – Sams Nov 23 '17 at 16:10
  • I was facing this problem when using three jquery plugins in my project (jquery-asScrollable, jquery-asScrollbar and jquery-asHoverScroll) and this answer pointed me in the right direction. In my case, the static alias `jquery: "jquery/src/jquery"` solved my problem. Thanks!! – aalbagarcia Apr 26 '18 at 05:33
  • Thank you very much. I've been looking for this guidance for over a day. I'm bundling a third party bootstrap 4 theme with a long list of vendor dependencies and it's own gulp-webpack task. After noticing a lot of inconsistent success with varying plugins - mostly centered around jquery - I've found it difficult to determine if each module is referencing the same jquery dependency. This technique immediately worked for me. – elight Nov 21 '18 at 06:02
2

An alternative solution, for the yarn package manager, is to force using the library dependency of your main application. By specifying the resolutions in your package.json, it can be used to prevent downloading of differing versions of sub-dependencies. Other than your what your application uses.

You should not use this solution if your package is publicly redistributed.

Issue

If for example your application depends on"jquery": "~1.12.0" and jquery-plugin which includes its own dependencies: { "jquery": ">=1.9" }. Assuming the latest version of jquery is 3.4.1, running yarn install will download a directory structure like.

node_modules
    jquery@1.12.4
    jquery-plugin
        node_modules
             jquery@3.4.1
//app.js
const $ = require('jquery');
require('jquery-plugin');
console.log(typeof $.fn.plugin); //undefined

Solution

Add a resolutions field to your package.json file and define your version overrides.

package.json

Force ALL sub-dependencies to use jQuery ~1.12.0

{
    "name": "project-name",
     "dependencies": {
         "jquery": "~1.12.0",
         "jquery-plugin": "*"
     },
     "resolutions": {
         "jquery": "~1.12.0"
     }
}

or Force jquery-plugin to use jQuery ~1.12.0

{
    "name": "project-name",
     "dependencies": {
         "jquery": "~1.12.0",
         "jquery-plugin": "*"
     },
     "resolutions": {
         "jquery-plugin/jquery": "~1.12.0"
     }
}

Then run yarn install.

Result the duplicate jquery sub-dependencies are removed.

node_modules
    jquery@1.12.4
    jquery-plugin
//app.js
const $ = require('jquery');
require('jquery-plugin');
console.log(typeof $.fn.plugin); //function
Will B.
  • 17,883
  • 4
  • 67
  • 69
0

Not familiar with jquery.terminal, but telling plugins explicitly how to load jQuery may help. I've had to do this for several plugins for similar issues.

    loaders: [{
        test: /jquery.terminal.+\.(js)$/,
        loader: 'imports?jQuery=jquery,$=jquery,this=>window'
    }]
Quotidian
  • 2,870
  • 2
  • 24
  • 19