Synchro's avatar

Loading a module from mix

Further to my question about this on the plugin page, I'm thinking that this is a mix problem rather than anything amiss with the package.

I'm loading the chart.js package along with a "datalabels" plugin for it from mix:

mix.js(
    [
        'node_modules/chart.js/dist/chart.js',
        'node_modules/chartjs-plugin-datalabels/dist/chartjs-plugin-datalabels.js'
    ],
    'public/js/chart.js'
);

The file that this generates in public/js/chart.js contains the two scripts, but I can see that the code for the plugin has had the definition of ChartDataLabels stripped, so I'm cut off from it and don't know of any way to get hold of an instance to pass to my Chart instance, and all attempts to register it as per the docs fail. From what I gather, chart.js is not a "module", but the plugin is. I don't know what this implies or how to do anything about it from mix.

I'm generating this separately from the rest of my app.js because it's quite big and is only used on some specific pages, and I don't want to load it from a CDN.

How should I configure mix to load this?

0 likes
5 replies
Synchro's avatar

@bobbybouwmann Having generated the combined script in public/js/chart.js, I then load that from a script tag in blade:

<script src="{{ asset('js/chart.js') }}"></script>

Then I have some inline code that uses it. For example, this works fine:

    const cupReturnChart = new Chart(document.getElementById('cupReturnsChart').getContext('2d'), {
        type: 'bar',
        data: {
            datasets: [{
                data: {!! json_encode($cupReturns) !!},
                borderWidth: 1,
                backgroundColor: '#FFAC2B',
                borderColor: '#FFAC2B'
            }]
        },
        options: options
    });

That's just using chart.js, not the datalabels extension – and that's where this issues arise, for example if I try to do this:

    import * as datalabels from 'chartjs-plugin-datalabels';
    const cupReturnChart = new Chart(document.getElementById('cupReturnsChart').getContext('2d'), {
        type: 'bar',
        plugins: [datalabels.ChartDataLabels],
        data: {
            datasets: [{
                data: {!! json_encode($cupReturns) !!},
                borderWidth: 1,
                backgroundColor: '#FFAC2B',
                borderColor: '#FFAC2B'
            }]
        },
        options: options
    });

That import line fails saying

SyntaxError: Unexpected token '*'. import call expects exactly one argument.

Which is apparently due to the script context not being a module, though I don't know how to make it one.

Because mix appears to strip out the ChartDataLabels variable (it's present in the source file that mix is pointed at), I can't call Chart.register(ChartDataLabels); as the docs suggest either. So I'm a bit lost...

bobbybouwmann's avatar

@Synchro You can't combine the import syntax with the script tags as far as I know. If you want to do it like this, you also need to use import to load chartjs as a whole

import {Chart} from 'chart.js/auto';
import ChartDataLabels from 'chartjs-plugin-datalabels';

var chart = new Chart(ctx, {
		plugins: [ChartDataLabels],
		options: {
// ...
  }
})
Synchro's avatar

@bobbybouwmann But it seems I don't need to do any kind of import for Chart, it just works. I can't seem to use import at all – and as far as I'm aware, import in JS is more like use in PHP rather than it actually loading anything – the datalabels script is already loaded; I can inspect it in my browser right there in the page, along with the chart.js code.

I do know that import ChartDataLabels from 'chartjs-plugin-datalabels'; can't work, because mix strips the ChartDataLabels variable from the source file, aside from the fact that it doesn't seem possible to use the import syntax in a script tag anyway.

I don't believe I'm trying to do anything complicated here; I just want to load a library and use it. I don't understand JS's loading/namespacing mechanisms enough to know why it's failing to do this simple thing. I don't want to do it any particular way (though I don't want to use a CDN), I just want to use it whatever way works, as simply as possible, ideally following "the Laravel way", hence my attempt to use mix.

Synchro's avatar
Synchro
OP
Best Answer
Level 2

I've found a solution, though it's really a poor workaround. I reconfigured mix to do a dumb copy instead of using its js method, like this:

mix.copy(
    [
        'node_modules/chart.js/dist/chart.js'
    ],
    'public/js/chart.js'
);
mix.copy(
    [
        'node_modules/chartjs-plugin-datalabels/dist/chartjs-plugin-datalabels.js'
    ],
    'public/js/chartdatalabels.js'
);

Then I load them separately:

<script src="{{ asset('js/chart.js') }}"></script>
<script src="{{ asset('js/chartdatalabels.js') }}"></script>

and then both Chart and ChartDataLabels are available in the global scope.

While this works, it strikes me as wrong, seeming to bypass whatever mix is meant to do, so I guess the answer is not to trust mix, except as a means of copying files into place.

Note that simply combining the files fails; this results in the same script errors as before, even though the resulting code is identical:

mix.copy(
    [
        'node_modules/chart.js/dist/chart.js',
        'node_modules/chartjs-plugin-datalabels/dist/chartjs-plugin-datalabels.js'
    ],
    'public/js/chart.js'
);

Please or to participate in this conversation.