diff --git a/examples/index.html b/examples/index.html
index dfa8abb5..7fa9d7dd 100644
--- a/examples/index.html
+++ b/examples/index.html
@@ -36,7 +36,7 @@
diff --git a/package-lock.json b/package-lock.json
index aff95591..c64ceda2 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -3804,6 +3804,32 @@
"fastq": "^1.6.0"
}
},
+ "@rollup/plugin-node-resolve": {
+ "version": "10.0.0",
+ "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-10.0.0.tgz",
+ "integrity": "sha512-sNijGta8fqzwA1VwUEtTvWCx2E7qC70NMsDh4ZG13byAXYigBNZMxALhKUSycBks5gupJdq0lFrKumFrRZ8H3A==",
+ "dev": true,
+ "requires": {
+ "@rollup/pluginutils": "^3.1.0",
+ "@types/resolve": "1.17.1",
+ "builtin-modules": "^3.1.0",
+ "deepmerge": "^4.2.2",
+ "is-module": "^1.0.0",
+ "resolve": "^1.17.0"
+ },
+ "dependencies": {
+ "resolve": {
+ "version": "1.19.0",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.19.0.tgz",
+ "integrity": "sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg==",
+ "dev": true,
+ "requires": {
+ "is-core-module": "^2.1.0",
+ "path-parse": "^1.0.6"
+ }
+ }
+ }
+ },
"@rollup/pluginutils": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz",
@@ -3963,6 +3989,12 @@
"integrity": "sha512-8+KAKzEvSUdeo+kmqnKrqgeE+LcA0tjYWFY7RPProVYwnqDjukzO+3b6dLD56rYX5TdWejnEOLJYOIeh4CXKuA==",
"dev": true
},
+ "@types/lodash": {
+ "version": "4.14.165",
+ "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.165.tgz",
+ "integrity": "sha512-tjSSOTHhI5mCHTy/OOXYIhi2Wt1qcbHmuXD1Ha7q70CgI/I71afO4XtLb/cVexki1oVYchpul/TOuu3Arcdxrg==",
+ "dev": true
+ },
"@types/minimatch": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz",
@@ -3987,6 +4019,15 @@
"integrity": "sha512-5qOlnZscTn4xxM5MeGXAMOsIOIKIbh9e85zJWfBRVPlRMEVawzoPhINYbRGkBZCI8LxvBe7tJCdWiarA99OZfQ==",
"dev": true
},
+ "@types/resolve": {
+ "version": "1.17.1",
+ "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz",
+ "integrity": "sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==",
+ "dev": true,
+ "requires": {
+ "@types/node": "*"
+ }
+ },
"@types/selenium-webdriver": {
"version": "4.0.10",
"resolved": "https://registry.npmjs.org/@types/selenium-webdriver/-/selenium-webdriver-4.0.10.tgz",
@@ -4736,6 +4777,12 @@
"integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==",
"dev": true
},
+ "builtin-modules": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.1.0.tgz",
+ "integrity": "sha512-k0KL0aWZuBt2lrxrcASWDfwOLMnodeQjodT/1SxEQAXsHANgo6ZC/VEaSEHCXt7aSTZ4/4H5LKa+tBXmW7Vtvw==",
+ "dev": true
+ },
"cache-base": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz",
@@ -6500,6 +6547,15 @@
"ci-info": "^2.0.0"
}
},
+ "is-core-module": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.1.0.tgz",
+ "integrity": "sha512-YcV7BgVMRFRua2FqQzKtTDMz8iCuLEyGKjr70q8Zm1yy2qKcurbFEd79PAdHV77oL3NrAaOVQIbMmiHQCHB7ZA==",
+ "dev": true,
+ "requires": {
+ "has": "^1.0.3"
+ }
+ },
"is-data-descriptor": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz",
@@ -6585,6 +6641,12 @@
"is-extglob": "^2.1.1"
}
},
+ "is-module": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz",
+ "integrity": "sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE=",
+ "dev": true
+ },
"is-negative-zero": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.0.tgz",
@@ -7012,17 +7074,45 @@
}
},
"jest-diff": {
- "version": "26.6.1",
- "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-26.6.1.tgz",
- "integrity": "sha512-BBNy/zin2m4kG5In126O8chOBxLLS/XMTuuM2+YhgyHk87ewPzKTuTJcqj3lOWOi03NNgrl+DkMeV/exdvG9gg==",
+ "version": "26.6.2",
+ "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-26.6.2.tgz",
+ "integrity": "sha512-6m+9Z3Gv9wN0WFVasqjCL/06+EFCMTqDEUl/b87HYK2rAPTyfz4ZIuSlPhY51PIQRWx5TaxeF1qmXKe9gfN3sA==",
"dev": true,
"requires": {
"chalk": "^4.0.0",
- "diff-sequences": "^26.5.0",
+ "diff-sequences": "^26.6.2",
"jest-get-type": "^26.3.0",
- "pretty-format": "^26.6.1"
+ "pretty-format": "^26.6.2"
},
"dependencies": {
+ "@jest/types": {
+ "version": "26.6.2",
+ "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz",
+ "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==",
+ "dev": true,
+ "requires": {
+ "@types/istanbul-lib-coverage": "^2.0.0",
+ "@types/istanbul-reports": "^3.0.0",
+ "@types/node": "*",
+ "@types/yargs": "^15.0.0",
+ "chalk": "^4.0.0"
+ }
+ },
+ "@types/istanbul-reports": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz",
+ "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==",
+ "dev": true,
+ "requires": {
+ "@types/istanbul-lib-report": "*"
+ }
+ },
+ "ansi-regex": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
+ "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==",
+ "dev": true
+ },
"chalk": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
@@ -7034,9 +7124,9 @@
}
},
"diff-sequences": {
- "version": "26.5.0",
- "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.5.0.tgz",
- "integrity": "sha512-ZXx86srb/iYy6jG71k++wBN9P9J05UNQ5hQHQd9MtMPvcqXPx/vKU69jfHV637D00Q2gSgPk2D+jSx3l1lDW/Q==",
+ "version": "26.6.2",
+ "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.6.2.tgz",
+ "integrity": "sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q==",
"dev": true
},
"jest-get-type": {
@@ -7044,6 +7134,24 @@
"resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.3.0.tgz",
"integrity": "sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==",
"dev": true
+ },
+ "pretty-format": {
+ "version": "26.6.2",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz",
+ "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==",
+ "dev": true,
+ "requires": {
+ "@jest/types": "^26.6.2",
+ "ansi-regex": "^5.0.0",
+ "ansi-styles": "^4.0.0",
+ "react-is": "^17.0.1"
+ }
+ },
+ "react-is": {
+ "version": "17.0.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.1.tgz",
+ "integrity": "sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA==",
+ "dev": true
}
}
},
@@ -7996,10 +8104,9 @@
}
},
"lodash": {
- "version": "4.17.19",
- "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz",
- "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==",
- "dev": true
+ "version": "4.17.20",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
+ "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA=="
},
"lodash.memoize": {
"version": "4.1.2",
diff --git a/package.json b/package.json
index 2789571d..d0da1e56 100644
--- a/package.json
+++ b/package.json
@@ -31,8 +31,10 @@
"devDependencies": {
"@babel/preset-env": "^7.9.5",
"@babel/runtime-corejs3": "^7.9.2",
+ "@rollup/plugin-node-resolve": "^10.0.0",
"@types/googlemaps": "^3.39.3",
"@types/jest": "^26.0.10",
+ "@types/lodash": "^4.14.165",
"@types/selenium-webdriver": "^4.0.9",
"@typescript-eslint/eslint-plugin": ">=2.25.0",
"@typescript-eslint/parser": ">=2.25.0",
@@ -56,5 +58,8 @@
"publishConfig": {
"access": "public",
"registry": "https://wombat-dressing-room.appspot.com"
+ },
+ "dependencies": {
+ "lodash": "^4.17.20"
}
}
diff --git a/rollup.config.js b/rollup.config.js
index 5756fbd7..a4fb9cb1 100644
--- a/rollup.config.js
+++ b/rollup.config.js
@@ -16,6 +16,7 @@
import babel from "rollup-plugin-babel";
import commonjs from "rollup-plugin-commonjs";
+import { nodeResolve } from "@rollup/plugin-node-resolve";
import { terser } from "rollup-plugin-terser";
import typescript from "rollup-plugin-typescript2";
@@ -25,11 +26,16 @@ const babelOptions = {
const terserOptions = { output: { comments: "" } };
+const resolveOptions = {
+ mainFields: ["browser", "jsnext:main", "module", "main"],
+};
+
export default [
{
input: "src/index.ts",
plugins: [
typescript(),
+ nodeResolve(resolveOptions),
commonjs(),
babel(babelOptions),
terser(terserOptions),
@@ -45,6 +51,7 @@ export default [
input: "src/index.ts",
plugins: [
typescript(),
+ nodeResolve(resolveOptions),
commonjs(),
babel(babelOptions),
terser(terserOptions),
@@ -57,7 +64,12 @@ export default [
},
{
input: "src/index.ts",
- plugins: [typescript(), commonjs(), babel(babelOptions)],
+ plugins: [
+ typescript(),
+ nodeResolve(resolveOptions),
+ commonjs(),
+ babel(babelOptions),
+ ],
output: {
file: "dist/index.dev.js",
format: "iife",
@@ -66,7 +78,7 @@ export default [
},
{
input: "src/index.ts",
- plugins: [typescript()],
+ plugins: [typescript(), nodeResolve(resolveOptions), commonjs()],
output: {
file: "dist/index.esm.js",
format: "esm",
diff --git a/src/index.test.ts b/src/index.test.ts
index a63faff3..bb9c7b98 100644
--- a/src/index.test.ts
+++ b/src/index.test.ts
@@ -18,6 +18,7 @@ import { Loader, LoaderOptions } from ".";
afterEach(() => {
document.getElementsByTagName("html")[0].innerHTML = "";
+ delete Loader["instance"];
});
test.each([
@@ -108,20 +109,50 @@ test("loadCallback callback should fire", () => {
window.__googleMapsCallback(null);
});
-test("script onerror should reject promise", () => {
+test("script onerror should reject promise", async () => {
const loader = new Loader({ apiKey: "foo" });
- expect.assertions(3);
+ const rejection = expect(loader.load()).rejects.toBeInstanceOf(ErrorEvent);
- const promise = loader.load().catch((e) => {
- expect(e).toBeTruthy();
- expect(loader["done"]).toBeTruthy();
- expect(loader["loading"]).toBeFalsy();
- });
+ loader["loadErrorCallback"](document.createEvent("ErrorEvent"));
+
+ await rejection;
+ expect(loader["done"]).toBeTruthy();
+ expect(loader["loading"]).toBeFalsy();
+});
+
+test("script onerror should reject promise with multiple loaders", async () => {
+ const loader = new Loader({ apiKey: "foo" });
+ const extraLoader = new Loader({ apiKey: "foo" });
+ let rejection = expect(loader.load()).rejects.toBeInstanceOf(ErrorEvent);
loader["loadErrorCallback"](document.createEvent("ErrorEvent"));
- return promise;
+ await rejection;
+ expect(loader["done"]).toBeTruthy();
+ expect(loader["loading"]).toBeFalsy();
+ expect(loader["onerrorEvent"]).toBeInstanceOf(ErrorEvent);
+ rejection = expect(extraLoader.load()).rejects.toBeInstanceOf(ErrorEvent);
+
+ await rejection;
+ expect(extraLoader["done"]).toBeTruthy();
+ expect(extraLoader["loading"]).toBeFalsy();
+});
+
+test("singleton should be used", () => {
+ const loader = new Loader({ apiKey: "foo" });
+ const extraLoader = new Loader({ apiKey: "foo" });
+ expect(extraLoader).toBe(loader);
+
+ loader["done"] = true;
+ expect(extraLoader["done"]).toBe(loader["done"]);
+});
+
+test("singleton should throw with different options", () => {
+ new Loader({ apiKey: "foo" });
+ expect(() => {
+ new Loader({ apiKey: "bar" });
+ }).toThrowError();
});
test("loader should resolve immediately when successfully loaded", async () => {
diff --git a/src/index.ts b/src/index.ts
index 7407b5bf..c01fe598 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+import isEqual from "lodash/fp/isEqual";
+
/**
* @ignore
*/
@@ -42,7 +44,7 @@ export interface LoaderOptions {
*/
apiKey: string;
/**
- * @deprecated See https://developers.google.com/maps/premium/overview.
+ * @deprecated See https://developers.google.com/maps/premium/overview.
*/
channel?: string;
/**
@@ -231,6 +233,7 @@ export class Loader {
private done = false;
private loading = false;
private onerrorEvent: Event;
+ private static instance: Loader;
/**
* Creates an instance of Loader using [[LoaderOptions]]. No defaults are set
@@ -265,6 +268,36 @@ export class Loader {
this.mapIds = mapIds;
this.nonce = nonce;
this.url = url;
+
+ if (Loader.instance) {
+ if (!isEqual(this.options, Loader.instance.options)) {
+ throw new Error(
+ `Loader must not be called again with different options. ${JSON.stringify(
+ this.options
+ )} !== ${JSON.stringify(Loader.instance.options)}`
+ );
+ }
+
+ return Loader.instance;
+ }
+
+ Loader.instance = this;
+ }
+
+ get options(): LoaderOptions {
+ return {
+ version: this.version,
+ apiKey: this.apiKey,
+ channel: this.channel,
+ client: this.client,
+ id: this.id,
+ libraries: this.libraries,
+ language: this.language,
+ region: this.region,
+ mapIds: this.mapIds,
+ nonce: this.nonce,
+ url: this.url,
+ };
}
/**
* CreateUrl returns the Google Maps JavaScript API script url given the [[LoaderOptions]].
@@ -348,6 +381,7 @@ export class Loader {
*/
private setScript(): void {
if (this.id && document.getElementById(this.id)) {
+ // TODO wrap onerror callback for cases where the script was loaded elsewhere
this.callback();
return;
}
diff --git a/tsconfig.json b/tsconfig.json
index 5b210ceb..627d3dcb 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -6,7 +6,9 @@
"outDir": "./dist",
"sourceMap": true,
"esModuleInterop": true,
- "lib": ["DOM", "ESNext"]
+ "lib": ["DOM", "ESNext"],
+ "target": "ES6",
+ "moduleResolution": "node"
},
"include": ["src/**/*"]
}