Skip to content

Instantly share code, notes, and snippets.

@jrunestone
Last active January 26, 2024 20:25
Show Gist options
  • Star 28 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save jrunestone/2fbe5d6d5e425b7c046168b6d6e74e95 to your computer and use it in GitHub Desktop.
Save jrunestone/2fbe5d6d5e425b7c046168b6d6e74e95 to your computer and use it in GitHub Desktop.
Here's how to make jQuery DataTables work with npm and webpack. DT checks for AMD compatibility first
which breaks when you're using CommonJS with webpack.
Install DT core: npm install datatables.net
Install a DT style: npm install datatables.net-bs (bootstrap)
Install the imports-loader webpack plugin: https://github.com/webpack/imports-loader#disable-amd
Create a loader "exception" just for DT in webpack.config.js:
module: {
loaders: [
{
test: /datatables\.net.*/,
loader: 'imports?define=>false'
}
]
}
Then to initialize DT in your app, do this in your main entry point:
// you can use import or require
import 'datatables.net';
import dt from 'datatables.net-bs';
dt(window, $);
Now you can use .DataTable():
$('table[data-table]').DataTable(); // or whatever you want
@prantlf
Copy link

prantlf commented May 10, 2020

Did you execute the functions imported from DataTables and their plugin modules?

I noticed a peculiar behaviour of DataTables JavaScript bundles, when used as ES6 modules. When importing them, they did nothing. No registration in $.fn or $.fn.dataTables.ext. I found, that each module exports an object with the default property, which points to a function expecting window and $ as parameters. When this function gets called, the module performs its registration, like it does in an AMD or a plain JavaScript project. That is why I called that function after importing each module and my single bundle.

This is how esbuild bundles the source from my comment above, which is taken from a project of mine:

const jquery = __import(16 /* jquery ***/);**
const jszip = __import(26 /* jszip */);
const pdfmake = __import(69 /* pdfmake */);
const datatables = __import(3 /* datatables.net-bs4 */);
const datatables2 = __import(4 /* datatables.net-buttons-bs4 */);
const buttons_colVis = __import(5 /* datatables.net-buttons/js/buttons.colVis.js */);
const buttons_html5 = __import(6 /* datatables.net-buttons/js/buttons.html5.js */);
const buttons_print = __import(7 /* datatables.net-buttons/js/buttons.print.js */);
const datatables3 = __import(9 /* datatables.net-colreorder-bs4 */);
const datatables4 = __import(11 /* datatables.net-fixedcolumns-bs4 */);
const datatables5 = __import(12 /* datatables.net-scroller-bs4 */);
window.jQuery = window.$ = jquery.default;
window.JSZip = jszip.default;
datatables.default(window, jquery.default);
datatables2.default(window, jquery.default);
buttons_colVis.default(window, jquery.default);
buttons_html5.default(window, jquery.default);
buttons_print.default(window, jquery.default);
datatables3.default(window, jquery.default);
datatables4.default(window, jquery.default);
datatables5.default(window, jquery.default);

The prolog (simplified here) added by esbuild to the output bundle shows that DataTables UMD wrappers are called with (exports, module) to let them adapt to a CommonJS environment. Their exports are re-eported depending on their support of ES6:

__sources = [
  (exports, module) => {
    // ... source of one imported module as-is
  },
  // the rest or imported modules
]

__modules = {}

function __import (id) {
  let module = __modules[id];
  if (!module) {
    module = modules[id] = { exports: {} };
    __sources[id](module.exports, module);
  }
  return module.exports.__esModule ?
    module.exports : { default : module.exports };
}

A simplified DataTables module shows that DataTables do not export __esModule from their UMD wrapper. That is why their exports are put to the property default in the single exported object. Also, it shows that unlike in AMD and plain JavaScript environments, it does not execute the factory function in the CommonJS environment. It just exports it. That is why their exported functions have to be called manually before you try to use their functionality.

(function(factory) {
  if (typeof define === "function" && define.amd) {
    define(["jquery", "datatables.net", "datatables.net-buttons"],
      $ => factory($, window, document));
  } else if (typeof exports === "object") {
    module.exports = (root, $) => factory($, root, root.document);
  } else {
    factory(jQuery, window, document);
  }
})(function($, window2, document2, undefined) {
  $.extend($.fn.dataTable.ext.buttons, {
    // additional button declarations
  });
  return DataTable.Buttons;
});

You may want to inspect your webpack output to understand what is happening there too.

@prantlf
Copy link

prantlf commented May 10, 2020

Oh, I've just noticed that you did call dt(window, $); in your source and you use no Datatables plugins.

You may need to inspect the (unminified) build output or debug it in the browser to see what is happening with window.$ when your initialization gets executed.

@SamuelClab
Copy link

Thanks for you help!
I just found where was my error...
It was completely my fault...
but difficult to find because somewhere unexpected (for me at least).

So in fact the "only" error which was causing this was because as I am learning webpack I read the tree-shaking section of the manual and for some reason I copy and past a part of it in my package.json...

"sideEffects": false

Which was obviously a bad idea/error since everything is not in es6 in my modules (such as datatables but not only).

@DaveData
Copy link

The config syntax of the loader changed.

Here's what works for me:
https://stackoverflow.com/a/74204588/4653886

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment