Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Bundle custom library using webpack and lodash as dependency

I need help here. I am bundling a custom library (named for the example LibraryJS and it uses lodash as dependency.

In the webpack configuration I setup lodash as an external dependency like so:

{
   externals: {
       "lodash"
   }
}

It works great. But then when I want to use this library inside another project, I got the following error: "libraryjs.js:77 Uncaught ReferenceError: lodash is not defined"

which is the following line:

/***/ }),
/* 1 */
/***/ (function(module, exports) {

=> module.exports = lodash;

/***/ }),

The fact is, I am using lodash as well in my project, so it should be working. What am I doing wrong?

By the way, I am using gulp + browserify to bundle my project and gulp + gulp-webpack to bundle the library.

Edit: I found a way to fix the error, but I really don't want to stick with this fix because it's really... ugly... See bellow:

const lodash = require('lodash')
window.lodash = lodash; // <= The fix, ugh

Thank you for the help!

like image 205
ChainList Avatar asked Jan 29 '23 01:01

ChainList


1 Answers

I had a similar situation trying to build a library - this is the solution I found.

Goal: Build a library that depends on Lodash without including Lodash in that library bundle. The consumer of the library is responsible for making Lodash available to our library.

I used the Authoring Libraries section of the documentation to put together this solution.

Specifically these two sections:

  • Externalize Lodash
  • Expose the Library

There is an example project showing this configuration as well: https://github.com/webpack/webpack/tree/master/examples/externals


Solution:

1. Bundle our library, excluding lodash

// "my-lib" package.json
{
    "name": "my-lib",
    "version": "0.5.0",
    "main": "my-lib.js",
    "peerDependencies": {
        "lodash": "^4.17.5"
    },
    "devDependencies": {
        "webpack": "^4.5.0",
        "webpack-cli": "^2.0.14"
    }
}

Here's our example library that imports lodash and uses it. Note that whilst we depend on lodash, it won't be included in our bundle.

// lib-entry.js
import _ from "lodash";

export default function doubleUp(listOfNum){
    // Do something using lodash
    let result = _.flatMap(listOfNum, (i) => [i, i]);
    return result;
}

Here's our webpack.config.js that bundles our library, but excludes lodash from the bundle:

// "my-lib" webpack.config.js
module.exports = {
    entry: {
        'my-lib': './lib-entry.ts', 
    },
    output: {
        filename: '[name].js',

        // [1]
        libraryTarget: 'umd',

        /*
        [2]
        NOTE: until this issue is fixed: https://github.com/webpack/webpack/issues/6525
        we need to define `globalObject` in Webpack 4 to correctly build a universal library
        (i.e. node and browser compatible).
        */
        globalObject: 'this',
    },
    externals: {
        // [3]
        'lodash': {
            commonjs: 'lodash',
            commonjs2: 'lodash',
            amd: 'lodash',
            root: '_',
        },
    },
    resolve: {
        extensions: ['.js'],
    },
};

[1] This tells Webpack to bundle our library for different environments (so it will work when loaded via: browser scripts, Node CommonJS, AMD, and downstream Webpack projects). NOTE: This is needed otherwise the externals config in [3] will output an incorrect undefined module in your bundle.

[2] Workaround for a Webpack 4 bug (if you're in the future then check if this is still relevant and needed). Also described here: https://stackoverflow.com/a/49119917/81723

[3] From the documentation: "set the externals option to define dependencies that should be resolved in the target environment". We're telling Webpack to exclude lodash from the library bundle and that consumers of our library are responsible for providing it.

2. Use the library in a downstream application

Define an application that depends on our my-lib library:

// "my-app" package.json
{
    "name": "my-app",
    "version": "1.0.0",
    "dependencies": {
        "lodash": "^4.17.5",
        "my-lib": "^0.5.0"
    },
    "devDependencies": {
        "webpack": "^4.5.0",
        "webpack-cli": "^2.0.14"
    }
}

Our application imports lodash into its environment (because our library needs it) and then use our library:

// app-entry.js
import _ from "lodash";
import doubleUp from "my-lib";

let result = doubleUp([1, 2, 3]);
console.log(result);    // [1, 1, 2, 2, 3, 3]

Here's the webpack.config.js for our application:

// "my-app" webpack.config.js
const path = require('path');

module.exports = {
    entry: {
        'my-app': './app-entry.ts', 
    },
    output: {
        filename: '[name].js',
    },
    resolve: {
        extensions: ['.js'],
        alias: {
            /*
            [1]
            Resolve all `lodash` requests to the same location otherwise
            we end up with two versions loaded into the bundle, one
            for `my-lib` and one for `my-app`.
            */
            lodash: path.resolve('./node_modules/lodash/index.js'),
        }
    },
};

[1] This alias config means anytime Webpack encounters require('lodash') or import ... from "lodash"; it will resolve it to this one module on disk. I found I had to do this to avoid getting multiple versions of lodash loaded into my bundle. Webpack was including one version for my-app and one for my-lib, but this fixes it so that both get the same version.

like image 115
Sly_cardinal Avatar answered Feb 01 '23 06:02

Sly_cardinal