├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── index.js ├── package.json ├── test.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.log 3 | node_modules/ 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | notifications: 2 | email: false 3 | 4 | services: 5 | - xvfb 6 | 7 | language: node_js 8 | 9 | node_js: 10 | - 'node' 11 | 12 | script: 13 | - yarn lint 14 | - yarn test 15 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 2.0.0 4 | 5 | - Bump dependencies 6 | - Use modern syntax, namely let/const and default parameters 7 | 8 | ## 1.3.1 9 | 10 | - Support repeated/duplicate calls by caching and returning the initial Promise 11 | 12 | ## 1.3.0 13 | 14 | - Return `google.maps` if previously-loaded 15 | 16 | ## 1.2.0 17 | 18 | - Remove check on `window` 19 | - Add `standard` 20 | - Bump dependencies 21 | 22 | ## 1.1.0 23 | 24 | - Add a simple CodePen 25 | - Use `prettier-standard` 26 | - Add a `weight` script 27 | 28 | ## 1.0.1 29 | 30 | - Add `channel` option 31 | 32 | ## 1.0.0 33 | 34 | - Major rewrite 35 | - Upgrade dependencies 36 | 37 | ## 0.0.3 38 | 39 | - Add `region` option 40 | 41 | ## 0.0.2 42 | 43 | - Add `language` option 44 | 45 | ## 0.0.1 46 | 47 | - Initial release 48 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020 Lim Yuan Qing 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # load-google-maps-api [![npm Version](https://badgen.net/npm/v/load-google-maps-api)](https://www.npmjs.org/package/load-google-maps-api) [![Build Status](https://badgen.net/travis/yuanqing/load-google-maps-api?label=build)](https://travis-ci.org/yuanqing/load-google-maps-api) [![Bundle Size](https://badgen.net/bundlephobia/minzip/load-google-maps-api)](https://bundlephobia.com/result?p=load-google-maps-api) 2 | 3 | > A lightweight Promise-returning helper for loading the [Google Maps JavaScript API](https://developers.google.com/maps/documentation/javascript/) 4 | 5 | - The Promise’s fulfilled callback is passed the `google.maps` object 6 | - Optionally set a timeout, an API key, the language, [and more](#loadgooglemapsapioptions) 7 | 8 | ## Usage 9 | 10 | > [**Editable demo (CodePen)**](https://codepen.io/lyuanqing/pen/YeYBrN) 11 | 12 | ```js 13 | const loadGoogleMapsApi = require('load-google-maps-api') 14 | 15 | loadGoogleMapsApi().then(function (googleMaps) { 16 | new googleMaps.Map(document.querySelector('.map'), { 17 | center: { 18 | lat: 40.7484405, 19 | lng: -73.9944191 20 | }, 21 | zoom: 12 22 | }) 23 | }).catch(function (error) { 24 | console.error(error) 25 | }) 26 | ``` 27 | 28 | *N.B.* Just like the Google Maps API itself, this module is client-side only. 29 | 30 | ## Motivation 31 | 32 | [Without this module](https://developers.google.com/maps/documentation/javascript/tutorial#Loading_the_Maps_API), you would need to specify a named *global* callback, and pass said callback’s name as a parameter in the `script` tag’s `src`. For example: 33 | 34 | ```html 35 | 40 | 41 | ``` 42 | 43 | This module abstracts this ceremony away, and fits better with modern bundlers like [Browserify](http://browserify.org/) or [Webpack](https://webpack.github.io/). 44 | 45 | ## API 46 | 47 | ```js 48 | const loadGoogleMapsApi = require('load-google-maps-api') 49 | ``` 50 | 51 | ### loadGoogleMapsApi([options]) 52 | 53 | Returns a [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise). 54 | 55 | - **Fulfilled** if loading was successful. The fulfilled callback is passed the `google.maps` object. If `loadGoogleMapsApi` is called multiple times on a page, the fulfilled callback will be passed the previously-loaded `google.maps` object. 56 | - **Rejected** if we weren’t able to load the Google Maps API after `options.timeout`. 57 | 58 | See [Usage](#usage). 59 | 60 | `options` is an optional object literal: 61 | 62 | Key | Description | Default 63 | :--|:--|:-- 64 | `apiUrl` | The Google Maps API `script` tag URL | `'https://maps.googleapis.com/maps/api/js'` 65 | `channel` | [Client usage reporting channel](https://developers.google.com/maps/premium/reports/usage-reports#channels) | `undefined` 66 | `client` | [Client ID](https://developers.google.com/maps/documentation/javascript/get-api-key#specifying-a-client-id-when-loading-the-api) | `undefined` 67 | `key` | [Your API key](https://developers.google.com/maps/documentation/javascript/get-api-key#step-2-add-the-api-key-to-your-application) | `undefined` 68 | `language` | [Language](https://developers.google.com/maps/documentation/javascript/localization#Language) | `undefined` 69 | `libraries` | [Supplemental libraries to load](https://developers.google.com/maps/documentation/javascript/libraries) | `[]` 70 | `region` | [Region](https://developers.google.com/maps/documentation/javascript/localization#Region) | `undefined` 71 | `timeout` | Time in milliseconds before rejecting the Promise | `10000` 72 | `v` | [API version](https://developers.google.com/maps/documentation/javascript/versions) | `undefined` 73 | 74 | ## Installation 75 | 76 | ```sh 77 | $ yarn add load-google-maps-api 78 | ``` 79 | 80 | ## License 81 | 82 | [MIT](LICENSE.md) 83 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const API_URL = 'https://maps.googleapis.com/maps/api/js' 2 | const CALLBACK_NAME = '__googleMapsApiOnLoadCallback' 3 | 4 | const optionsKeys = ['channel', 'client', 'key', 'language', 'region', 'v'] 5 | 6 | let promise = null 7 | 8 | module.exports = function (options = {}) { 9 | promise = 10 | promise || 11 | new Promise(function (resolve, reject) { 12 | // Reject the promise after a timeout 13 | const timeoutId = setTimeout(function () { 14 | window[CALLBACK_NAME] = function () {} // Set the on load callback to a no-op 15 | reject(new Error('Could not load the Google Maps API')) 16 | }, options.timeout || 10000) 17 | 18 | // Hook up the on load callback 19 | window[CALLBACK_NAME] = function () { 20 | if (timeoutId !== null) { 21 | clearTimeout(timeoutId) 22 | } 23 | resolve(window.google.maps) 24 | delete window[CALLBACK_NAME] 25 | } 26 | 27 | // Prepare the `script` tag to be inserted into the page 28 | const scriptElement = document.createElement('script') 29 | const params = [`callback=${CALLBACK_NAME}`] 30 | optionsKeys.forEach(function (key) { 31 | if (options[key]) { 32 | params.push(`${key}=${options[key]}`) 33 | } 34 | }) 35 | if (options.libraries && options.libraries.length) { 36 | params.push(`libraries=${options.libraries.join(',')}`) 37 | } 38 | scriptElement.src = `${options.apiUrl || API_URL}?${params.join('&')}` 39 | 40 | // Insert the `script` tag 41 | document.body.appendChild(scriptElement) 42 | }) 43 | return promise 44 | } 45 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "load-google-maps-api", 3 | "version": "2.0.2", 4 | "description": "A lightweight Promise-returning helper for loading the Google Maps JavaScript API", 5 | "author": "Lim Yuan Qing", 6 | "license": "MIT", 7 | "repository": { 8 | "type": "git", 9 | "url": "git://github.com/yuanqing/load-google-maps-api.git" 10 | }, 11 | "devDependencies": { 12 | "browserify": "^16.5.0", 13 | "gzip-size-cli": "^3.0.0", 14 | "husky": "^4.2.3", 15 | "lint-staged": "^10.0.8", 16 | "prettier-standard": "^16.2.1", 17 | "rimraf": "^3.0.2", 18 | "standard": "^14.3.1", 19 | "tape": "^4.13.2", 20 | "tape-run": "^6.0.1", 21 | "terser": "^4.6.6" 22 | }, 23 | "scripts": { 24 | "clean": "rimraf '*.log'", 25 | "fix": "prettier-standard '*.js'", 26 | "lint": "standard '*.js'", 27 | "test": "browserify test.js | tape-run", 28 | "weight": "terser index.js --compress --mangle --toplevel | gzip-size" 29 | }, 30 | "husky": { 31 | "hooks": { 32 | "pre-commit": "lint-staged" 33 | } 34 | }, 35 | "lint-staged": { 36 | "*.js": [ 37 | "standard", 38 | "prettier-standard" 39 | ] 40 | }, 41 | "files": [ 42 | "index.js" 43 | ], 44 | "keywords": [ 45 | "api", 46 | "google", 47 | "google-maps", 48 | "google-maps-api", 49 | "loader", 50 | "maps", 51 | "promise" 52 | ] 53 | } 54 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | const test = require('tape') 2 | const loadGoogleMapsApi = require('./') 3 | 4 | test('resolves the promise to the `googleMaps` object', function (t) { 5 | t.plan(1) 6 | loadGoogleMapsApi().then(function (googleMaps) { 7 | t.true(typeof googleMaps.Map === 'function') 8 | }, t.fail) 9 | }) 10 | 11 | test('resolves the promise to the `googleMaps` object, with support for duplicate calls', function (t) { 12 | t.plan(2) 13 | const promises = [loadGoogleMapsApi(), loadGoogleMapsApi()] 14 | Promise.all(promises).then(function (values) { 15 | t.equal(values[0], values[1]) 16 | t.true(typeof values[0].Map === 'function') 17 | }, t.fail) 18 | }) 19 | --------------------------------------------------------------------------------