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
@marcstober
Copy link

  1. Note that you have to npm install imports-loader.
  2. This doesn't currently work with the default "dt" styling package, because it's a CSS-only package and (I suspect) webpack doesn't know what to "do with" a CSS-only package. I made a gist that works with that styling package here: https://gist.github.com/marcstober/c34bb4bdf7ef622cb24d6675723749bd

@nitxs
Copy link

nitxs commented Mar 7, 2019

thanks. it is helpful.

@nullivex
Copy link

nullivex commented Apr 6, 2019

A couple updates for webpack 4 in 2019

module: {
  rules: [
    {test: /datatables\.net.*/, loader: 'imports-loader?define=>false'},
  ]
}

I got a working build! Thank you

@zsimo
Copy link

zsimo commented May 10, 2019

thanks!

@ITernovtsii
Copy link

Thanks!
It works like this for me with Symfony Webpack Encore bundle.

webpack.config.js:

Encore.addLoader({ test: /datatables\.net.*/, loader: 'imports-loader?define=>false' })

global.js:

require('datatables.net');
require('datatables.net-responsive')(window);
require('datatables.net-buttons')(window);

@kirykr
Copy link

kirykr commented Oct 16, 2019

Thanks alot.

@SamuelClab
Copy link

Thanks for that It almost work form me :)
It is working fine when I compile in development mode but not in production mode...

webpack.config.js :

const webpack = require('webpack');
const path = require('path');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const TerserJSPlugin = require('terser-webpack-plugin');
const ManifestPlugin = require('webpack-manifest-plugin');

let config = {
  entry: {
    check_process_nomenclature: path.join(
      __dirname,
      'src',
      'js',
      'toto.js'
    ),
    user_login: path.join(__dirname, 'src', 'js', 'tata.js')
  },
  output: {
    path: path.join(__dirname, 'web', 'dist'),
    filename: '[name].[chunkhash].bundle.js'
  },
  resolve: {
    extensions: ['.js'],
    alias: {
      style: path.resolve(__dirname, 'src', 'style')
    }
  },
  module: {
    rules: [
      { test: /datatables\.net.*/, loader: 'imports-loader?define=>false' },
      {
        test: /\.js$/i,
        exclude: [/(node_modules)/, /(ckfinder)/],
        use: ['babel-loader']
      },
      {
        test: /\.(png|webp|jpe?g|gif|svg|eot|ttf|woff|woff2)$/i,
        use: [
          {
            loader: 'file-loader',
            options: {
              publicPath: '/web/dist',
              name: '[name].[hash].[ext]'
            }
          }
        ]
      },
      {
        test: /\.css$/i,
        use: [
          'style-loader',
          {
            loader: 'css-loader',
            options: {}
          }
        ]
      },
      {
        test: /\.s[ac]ss$/i,
        use: [
          'style-loader',
          {
            loader: 'css-loader',
            options: {}
          },
          'sass-loader'
        ]
      }
    ]
  },
  plugins: [
    new MiniCssExtractPlugin(),
    new ManifestPlugin(),
    new webpack.ProvidePlugin({
      $: 'jquery',
      jQuery: 'jquery'
    }),
    new CleanWebpackPlugin()
  ],
  optimization: {
    minimizer: [
      new TerserJSPlugin({
        test: /\.js$/i,
        exclude: /\/node_modules/,
        parallel: true,
        sourceMap: true
      })
    ]
  },
  stats: true,
  devtool: 'source-map'
};

module.exports = (env, argv) => {
  if (argv.mode === 'production') {
    config.mode = 'production';
    config.optimization.minimize = true;
  } else {
    config.mode = 'development';
    config.optimization.minimize = false;
  }

  return config;
};

datatables.js :

import dt from 'datatables.net-bs';
import 'datatables.net-bs/css/dataTables.bootstrap.css';
import 'style/_datatables.css';
dt(window, $);

toto.js :

import { productInformation, alertMsg, autoLogout } from './_common';
import './datatables';
import { modalOnHide } from './_modal_onhide';
import 'style/toto.css';

const $table= $('#table');
$table.DataTable({
  autoWidth: true,
  paging: true,
  lengthMenu: [
    [25, 50, 100, 250],
    [25, 50, 100, 250]
  ],
  ordering: true,
  info: true,
  stateSave: true,
});

run :
webpack --mode=production

error :
image
image

If someone have any idea of how to solve this issue it would be great!!

@prantlf
Copy link

prantlf commented May 7, 2020

Did you set the global jQuery? I saw $.fn.dataTable defined, but $.fn.DataTable undefined before I did it. I see $ defined in Chrome. Either it is built in or some extension set it, but it does not come from jQuery. As soon as I set the global jQuery properly, DataTables started to work, including the plugins.

import $ from 'jquery'
window.jQuery = window.$ = $
import JSZip from 'jszip'
window.JSZip = JSZip
import 'pdfmake'
import dataTable from 'datatables.net-bs4'
dataTable(window, $)
import buttons from 'datatables.net-buttons-bs4'
buttons(window, $)
import columnVisibility from 'datatables.net-buttons/js/buttons.colVis.js'
columnVisibility(window, $)
import buttonsHtml5 from 'datatables.net-buttons/js/buttons.html5.js'
buttonsHtml5(window, $)
import buttonsPrint from 'datatables.net-buttons/js/buttons.print.js'
buttonsPrint(window, $)
import colReorder from 'datatables.net-colreorder-bs4'
colReorder(window, $)
import fixedColumns from 'datatables.net-fixedcolumns-bs4'
fixedColumns(window, $)
import scroller from 'datatables.net-scroller-bs4'
scroller(window, $)

I do not use webpack, but esbuild to bundle the JavaScript output.

@SamuelClab
Copy link

Thanks for your answer!

jQuery is automatically provided through the following piece of code:

new webpack.ProvidePlugin({
  $: 'jquery',
  jQuery: 'jquery'
}),

I have tried different variations by playing around with the plugins and optimization options (by following what is written here) without success...
I have also tried to to set globally jQuery as you did but that didn't change a thing. :(

For now I do create the bundle in development mode but that's not clean and it's annoyed me a little to not understand what's wrong...

@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