Search code examples
javascriptlaravel-5npmopenlayersopenlayers-5

Importing node_modules into Laravel via Mix


I'm trying to use OpenLayers (v5.3.0) in a Laravel 5.7 project, but am having a lot of trouble importing ol from node_modules.

I installed ol as follows (based on https://www.npmjs.com/package/ol):

npm install ol

I then updated my resources\js\app.js, which now contains only the following:

require('./bootstrap');
require('ol');

EDIT: I've also tried the following in resources\js\app.js, without success:

require('./bootstrap');
const ol = require('ol');

My webpack.mix.js contains the following:

const mix = require('laravel-mix');

mix.js('resources/js/app.js', 'public/js/app.js', )
   .sass('resources/sass/app.scss', 'public/css');

I also have the following relevant lines in a file called map.blade.php, which is where I want to display the OpenLayers map:

<script src="{!! mix('js/app.js') !!}"></script>
...
<div id='map' style='z-index: 1; width: 100%; height:calc(100% - 56px);'>
    <script>
        import Map from 'ol/Map';
        import View from 'ol/View';
        import TileLayer from 'ol/layer/Tile';
        import XYZ from 'ol/source/XYZ';

        new Map({
            target: 'map',
            layers: [
                new TileLayer({
                    source: new XYZ({
                        url: 'https://{a-c}.tile.openstreetmap.org/{z}/{x}/{y}.png'
                    })
                })
            ],
            view: new View({
                center: [0, 0],
                zoom: 2
            })
        });
    </script>
</div>

I have also run npm run dev.

When testing in Chrome, I get "Uncaught SyntaxError: Unexpected identifier" referring to the following line in map.blade.php:

import Map from 'ol/Map';

EDIT: I've also run the following to make sure all dependencies are installed:

npm install --save-dev parcel-bundler

I didn't get any errors when running the above, but the same error in Chrome is still there.

EDIT: I've also tried shifting the javascript out of my map.blade.php into a new file (mapscript.js), and then importing that in map.blade.php (after the 'map' div) as follows:

<script src="{!! asset('js/mapscript.js') !!}" type="module"></script>

But then I get the following error instead:

Uncaught TypeError: Failed to resolve module specifier "ol/Map". Relative references must start with either "/", "./", or "../".

Following that, I tried moving the following line out of app.js and into mapscript.js:

require('ol');

And also tried the same with:

const ol = require('ol');

But the same Uncaught TypeError persists in both cases.

I've tried the solutions given to a lot of similar questions in Stack Overflow and elsewhere, and I've also tried using ol outside of npm, but I haven't found anything that resolves the issue for me. I believe using npm and Mix is the best way to build OpenLayers into my project, but I can't work out why it's not working. Would really appreciate some help.


Solution

  • After some trial and error I have OpenLayers 6.1 working with Laravel 6.2 using Mix, Webpack, and ES6 module imports. The trick is to write all your javascript in a separate file and bundle it up into app.js.

    Install openlayers into your Laravel project using npm:

    npm install ol
    

    Create a new file in your Laravel project at resources/js/myMap.js (alongside bootstrap.js and app.js) and put your OpenLayers javascript code into it.

    Lets use a short code example copied from the official docs at https://openlayers.org/en/latest/doc/tutorials/bundle.html

    import 'ol/ol.css';
    import {Map, View} from 'ol';
    import TileLayer from 'ol/layer/Tile';
    import OSM from 'ol/source/OSM';
    
    const map = new Map({
        target: 'osm_map',
        layers: [
            new TileLayer({
                source: new OSM()
            })
        ],
        view: new View({
            center: [0, 0],
            zoom: 0
        })
    });
    

    We need to export this as a literal object to make it available to other code, so insert the five extra lines as shown below.

    import 'ol/ol.css';
    import {Map, View} from 'ol';
    import TileLayer from 'ol/layer/Tile';
    import OSM from 'ol/source/OSM';
    
    var my_map = {                       // <-- add this line to declare the object
        display: function () {           // <-- add this line to declare a method 
    
            const map = new Map({
                target: 'osm_map',
                layers: [
                    new TileLayer({
                        source: new OSM()
                    })
                ],
                view: new View({
                    center: [0, 0],
                    zoom: 0
                })
            });
    
        }                                // <-- close the method
    };                                   // <-- close the object
    export default my_map;               // <-- and export the object
    

    Add these two lines to the end of bootstrap.js so that it incorporates our code, and attach our object my_map to the global window object so we can reference it from the page.

    import my_map from './myMap.js';
    window.my_map = my_map;
    

    Now bundle it all up by executing npm run dev. Note that we are using Laravel's default webpack and mix configurations - we haven't needed to edit webpack.mix.js at all. npm run dev copies the code in our myMap.js file into app.js. We need to run this each time we edit myMap.js. ( npm run watch can be used to automate this step ).

    To get the map displayed in a blade template we need to have a div id matching the OpenLayers Map target, osm_map in our example code above. Here's a minimal blade.

    <!DOCTYPE html>
    <html lang="{{ app()->getLocale() }}">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <link href="{{ asset('css/app.css') }}" rel="stylesheet">
        <style>
            #osm_map {
                position: absolute;
                width: 100%;
                height: 100%;
            }
        </style>
    </head>
    <body>
    
    <div id="app">
        <div id="osm_map"></div>
    </div>
    
    <script src="{{ asset('js/app.js') }}" ></script>
    
    <script type="text/javascript">
        window.my_map.display();
    </script>
    
    </body>
    </html>
    

    Note:

    1. The div id="osm_map" matches the openlayers target.
    2. The OpenLayers CSS was also bundled and is referenced here. This styles the OpenLayers controls.
    3. The map is displayed when we call our my_map.display() method.

    This successfully displays an interactive OpenLayers map inside a Laravel blade template.