Proper way to import and use node modules with Laravel Mix

Viewed 5489

I'm just starting to use Laravel (v 8.x) Mix in a project, and am finding it frustrating to implement Javascript from node modules.

To begin, I have this in my webpack.mix.js:

mix.js('node_modules/mxgraph/javascript/mxClient.min.js', 'public/js');
mix.js('resources/js/*.js', 'public/js').postCss('resources/css/app.css', 'public/css', [
    require('postcss-import'),
    require('tailwindcss'),
    require('autoprefixer'),
]).version();

Next, my app.js contains the following:

import Canvas from './canvas';

require('mxgraph');

const canvas = new Canvas();

...which imports canvas.js:

export default class Canvas {
    constructor() {
        this.container = document.getElementById('graphContainer');
        if (!mxClient.isBrowserSupported())
        {
            // Displays an error message if the browser is not supported.
            alert('Browser is not supported!');
        }
.
.
.
    }
}

...and in the Scripts section of my Blade layout:

<script src="{{ mix('js/mxClient.min.js') }}" defer></script>
<script src="{{ mix('js/app.js') }}" defer></script>

When I load the page, I get the following error in the console:

Uncaught ReferenceError: mxClient is not defined
    at new Canvas (app.js:3866)
    at Module../resources/js/app.js (app.js:3813)
    at __webpack_require__ (app.js:114081)
    at app.js:114143
    at app.js:114149

var mxClient is definitely present in mxClient.min.js, and the reference to it in Canvas occurs after it's loaded.

I've tried a bunch of variations, and nothing seems to work. Any guidance would be greatly appreciated.

2 Answers

So, after playing around I found what you can do.

Assemble one file

Put mxClient.min.js and app.js into one file by doing

mix.js(
    [
        "resources/js/app.js",
        "node_modules/mxgraph/javascript/mxClient.min.js",
    ],
    "public/js"
).postCss('resources/css/app.css', 'public/css', [
    require('postcss-import'),
    require('tailwindcss'),
    require('autoprefixer'),
]).version();

Import mxGraph and Canvas

In your app.js you can put that

import "./canvas";

require("mxgraph");

const canvas = new Canvas();

and npm run dev runs without issues.

Update

I found a (maybe easier) option. Don't include mxClient in your webpack.mix.js, only your app.js is needed.

// app.js

window.mxClient = new require("mxgraph")().mxClient;

let isBrowserSupported = mxClient.isBrowserSupported();

console.log(isBrowserSupported);

After a lot more poking and searching around, I found a method that works. It was based on what I found here:

https://www.programmersought.com/article/85491421858/

My implementation seems a bit clunky, but it does work, and I can now continue the project I'm trying to develop with mxGraph.

Thus, I no longer pull in mxgraph explicitly in webpack.mix.js, so it now reverts to the Laravel default:

mix.js('resources/js/*.js', 'public/js').postCss('resources/css/app.css', 'public/css', [
    require('postcss-import'),
    require('tailwindcss'),
    require('autoprefixer'),
]).version();

I've also removed require('mxgraph'); from app.js, so it just looks like this:

import Canvas from './canvas';

const canvas = new Canvas();

My canvas.js now looks like this:

import mx from 'mxgraph';

const mxgraph = mx({
    mxImageBasePath: './src/images',
    mxBasePath: './src'
});

window.mxGraph = mxgraph.mxGraph;
window.mxGraphModel = mxgraph.mxGraphModel;
window.mxEvent = mxgraph.mxEvent;
window.mxEditor = mxgraph.mxEditor;
window.mxGeometry = mxgraph.mxGeometry;
window.mxRubberband = mxgraph.mxRubberband;
window.mxDefaultKeyHandler = mxgraph.mxDefaultKeyHandler;
window.mxDefaultPopupMenu = mxgraph.mxDefaultPopupMenu;
window.mxStylesheet = mxgraph.mxStylesheet;
window.mxDefaultToolbar = mxgraph.mxDefaultToolbar;

const {mxGraph, mxClient, mxEvent, mxCodec, mxUtils, mxConstants, mxPerimeter, mxRubberband} = mxgraph;

export default class Canvas {
    constructor() {
        let container = document.getElementById('graphContainer');
        if (typeof(mxClient) !== 'undefined') {
            this.draw(container);
        }
    }

    draw (container) {
        if (! mxClient.isBrowserSupported())
        {
            // Displays an error message if the browser is not supported.
            mxUtils.error('Browser is not supported!', 200, false);
        }
        else
        {
            // Disables the built-in context menu
            mxEvent.disableContextMenu(container);

            // Creates the graph inside the given container
            var graph = new mxGraph(container);

            // Enables rubberband selection
            new mxRubberband(graph);

            // Gets the default parent for inserting new cells. This
            // is normally the first child of the root (ie. layer 0).
            var parent = graph.getDefaultParent();

            // Adds cells to the model in a single step
            graph.getModel().beginUpdate();
            try
            {
                var v1 = graph.insertVertex(parent, null, 'Hello,', 20, 20, 80, 30);
                var v2 = graph.insertVertex(parent, null, 'World!', 200, 150, 80, 30);
                var e1 = graph.insertEdge(parent, null, '', v1, v2);
              
            }
            finally
            {
                // Updates the display
                graph.getModel().endUpdate();
            }
        }
    }
}

Most of the code in the draw() method is taken from an mxGraph "Hello World" demo (https://jgraph.github.io/mxgraph/docs/manual.html).

Many thanks to @codedge for the time you spent helping me!

Related