4

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.

Gareth Jones
  • 517
  • 1
  • 10
  • 25

2 Answers2

2

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.

Dave Robertson
  • 431
  • 3
  • 6
  • This is fantastic. Got the basic example working successfully! Can't thank you enough. FYI, I just had to remove the semicolon at the end of the 'close the method' line in myMap.js. – Gareth Jones Jan 18 '20 at 11:53
1

Install ol package :

npm install ol

Inside your resources directory in js folder(where bootstrap.js and app.js exists) create a file name map.js

resources/js/map.js

Write your Openlayer code in map.js(example code):

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: 'map',
layers: [
    new TileLayer({
        source: new OSM()
    })
],
view: new View({
    center: [0, 0],
    zoom: 0
             })
});

Now, goto webpack.mix.js, it is usually at the end.

Add .js('resources/js/map.js', 'public/js') at end of the file

webpack.mix.js will look like this:

const mix = require('laravel-mix');
/*
 |--------------------------------------------------------------------------
 | Mix Asset Management
 |--------------------------------------------------------------------------
 |
 | Mix provides a clean, fluent API for defining some Webpack build steps
 | for your Laravel application. By default, we are compiling the Sass
 | file for the application as well as bundling up all the JS files.
 |
 */
mix.js('resources/js/app.js', 'public/js')
.sass('resources/sass/app.scss', 'public/css')
.js('resources/js/map.js', 'public/js');

Now, npm run dev in terminal

Your *.blade.php code should be as following example code:

<!DOCTYPE HTML>
<html>
<head>
    <meta charset="utf-8">
    <title>Using Webpack with OpenLayers</title>
    <style>
        #map {
            height: 100%;
            width: 100%;
            left: 0;
            top: 0;
            overflow: hidden;
            position: fixed;
        }
    </style>
</head>

<body>
    <div id="map"></div>
    <script type="text/javascript" src="{{ asset('/js/map.js') }}"></script>
</body>
</html>

Importing node modules from public folder directly is not allowed. Here, we import module within the project and use it with the help of webpack in the public folder.

For production: npm run production in terminal