Comparing version
# Next | ||
[diff](https://github.com/mib200/vue-gtm/compare/3.4.1...master) | ||
[diff](https://github.com/mib200/vue-gtm/compare/3.5.0...master) | ||
# 3.5.0 | ||
[diff](https://github.com/mib200/vue-gtm/compare/3.4.1...3.5.0) | ||
- Add `nonce` for CSP support ([#116]) | ||
**Vue 3 only** | ||
- Handle optional dependency of `vue-router` ([#122]) | ||
[#116]: https://github.com/mib200/vue-gtm/issues/116 | ||
[#122]: https://github.com/mib200/vue-gtm/issues/122 | ||
# 3.4.1 | ||
@@ -6,0 +19,0 @@ |
@@ -1,2 +0,2 @@ | ||
import type { Router } from "vue-router"; | ||
import type Router from "vue-router"; | ||
/** | ||
@@ -80,2 +80,8 @@ * Query parameter object that will be send to GTM. | ||
/** | ||
* Will add `nonce` to the script tag. | ||
* | ||
* @see [Using Google Tag Manager with a Content Security Policy](https://developers.google.com/tag-manager/web/csp) | ||
*/ | ||
nonce?: string; | ||
/** | ||
* Plugin can be disabled by setting this to `false`. | ||
@@ -82,0 +88,0 @@ * |
@@ -1,13 +0,6 @@ | ||
import { Plugin } from "vue"; | ||
import { PluginObject } from "vue"; | ||
import { VueGtmUseOptions } from "./config"; | ||
import GtmPlugin from "./plugin"; | ||
/** | ||
* Create the Vue GTM instance. | ||
* | ||
* @param options Options. | ||
* @returns The Vue GTM plugin instance. | ||
*/ | ||
export declare function createGtm(options: VueGtmUseOptions): VueGtmPlugin; | ||
declare module "@vue/runtime-core" { | ||
interface ComponentCustomProperties { | ||
declare module "vue/types/vue" { | ||
interface Vue { | ||
/** | ||
@@ -18,2 +11,8 @@ * The Vue GTM Plugin instance. | ||
} | ||
interface VueConstructor<V extends Vue = Vue> { | ||
/** | ||
* The Vue GTM Plugin instance. | ||
*/ | ||
gtm: GtmPlugin; | ||
} | ||
} | ||
@@ -23,3 +22,3 @@ /** | ||
*/ | ||
export declare type VueGtmPlugin = Plugin; | ||
export declare type VueGtmPlugin = PluginObject<VueGtmUseOptions>; | ||
export { VueGtmUseOptions } from "./config"; | ||
@@ -26,0 +25,0 @@ declare const _default: VueGtmPlugin; |
@@ -13,38 +13,2 @@ "use strict"; | ||
}; | ||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } | ||
return new (P || (P = Promise))(function (resolve, reject) { | ||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } | ||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } | ||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } | ||
step((generator = generator.apply(thisArg, _arguments || [])).next()); | ||
}); | ||
}; | ||
var __generator = (this && this.__generator) || function (thisArg, body) { | ||
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; | ||
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; | ||
function verb(n) { return function (v) { return step([n, v]); }; } | ||
function step(op) { | ||
if (f) throw new TypeError("Generator is already executing."); | ||
while (_) try { | ||
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; | ||
if (y = 0, t) op = [op[0] & 2, t.value]; | ||
switch (op[0]) { | ||
case 0: case 1: t = op; break; | ||
case 4: _.label++; return { value: op[1], done: false }; | ||
case 5: _.label++; y = op[1]; op = [0]; continue; | ||
case 7: op = _.ops.pop(); _.trys.pop(); continue; | ||
default: | ||
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } | ||
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } | ||
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } | ||
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } | ||
if (t[2]) _.ops.pop(); | ||
_.trys.pop(); continue; | ||
} | ||
op = body.call(thisArg, _); | ||
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } | ||
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; | ||
} | ||
}; | ||
var __values = (this && this.__values) || function(o) { | ||
@@ -62,4 +26,3 @@ var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.useGtm = exports.createGtm = void 0; | ||
var vue_1 = require("vue"); | ||
exports.useGtm = void 0; | ||
var config_1 = require("./config"); | ||
@@ -85,6 +48,6 @@ var plugin_1 = require("./plugin"); | ||
* | ||
* @param app The Vue app instance. | ||
* @param Vue The Vue instance. | ||
* @param options Configuration options. | ||
*/ | ||
function install(app, options) { | ||
function install(Vue, options) { | ||
var e_1, _a; | ||
@@ -119,6 +82,6 @@ if (options === void 0) { options = { id: "" }; } | ||
gtmPlugin = new plugin_1.default(options.id, options); | ||
app.config.globalProperties.$gtm = gtmPlugin; | ||
Vue.prototype.$gtm = Vue.gtm = gtmPlugin; | ||
// Handle vue-router if defined | ||
if (options.vueRouter) { | ||
void initVueRouterGuard(app, options.vueRouter, options.ignoredViews, options.trackOnNextTick); | ||
initVueRouterGuard(Vue, options.vueRouter, options.ignoredViews, options.trackOnNextTick); | ||
} | ||
@@ -145,3 +108,2 @@ // Load GTM script when enabled | ||
} | ||
app.provide("gtm", options); | ||
} | ||
@@ -151,3 +113,3 @@ /** | ||
* | ||
* @param app The Vue app instance. | ||
* @param Vue The Vue instance. | ||
* @param vueRouter The Vue router instance to attach the guard. | ||
@@ -157,70 +119,35 @@ * @param ignoredViews An array of route name that will be ignored. | ||
*/ | ||
function initVueRouterGuard(app, vueRouter, ignoredViews, trackOnNextTick) { | ||
function initVueRouterGuard(Vue, vueRouter, ignoredViews, trackOnNextTick) { | ||
if (ignoredViews === void 0) { ignoredViews = []; } | ||
return __awaiter(this, void 0, void 0, function () { | ||
var vueRouterModule, _a; | ||
return __generator(this, function (_b) { | ||
switch (_b.label) { | ||
case 0: | ||
_b.trys.push([0, 2, , 3]); | ||
return [4 /*yield*/, Promise.resolve().then(function () { return require("vue-router"); })]; | ||
case 1: | ||
vueRouterModule = _b.sent(); | ||
return [3 /*break*/, 3]; | ||
case 2: | ||
_a = _b.sent(); | ||
console.warn("[VueGtm]: You tried to register 'vueRouter' for vue-gtm, but 'vue-router' was not found."); | ||
return [2 /*return*/]; | ||
case 3: | ||
// Flatten routes name | ||
ignoredViews = ignoredViews.map(function (view) { return view.toLowerCase(); }); | ||
vueRouter.afterEach(function (to, from, failure) { | ||
var _a, _b, _c, _d, _e; | ||
// Ignore some routes | ||
if (typeof to.name !== "string" || ignoredViews.indexOf(to.name.toLowerCase()) !== -1) { | ||
return; | ||
} | ||
// Dispatch vue event using meta gtm value if defined otherwise fallback to route name | ||
var name = to.meta && typeof to.meta.gtm === "string" && !!to.meta.gtm ? to.meta.gtm : to.name; | ||
if (vueRouterModule.isNavigationFailure(failure, vueRouterModule.NavigationFailureType.aborted)) { | ||
if (gtmPlugin === null || gtmPlugin === void 0 ? void 0 : gtmPlugin.debugEnabled()) { | ||
console.log("[VueGtm]: '" + name + "' not tracked due to navigation aborted"); | ||
} | ||
} | ||
else if (vueRouterModule.isNavigationFailure(failure, vueRouterModule.NavigationFailureType.cancelled)) { | ||
if (gtmPlugin === null || gtmPlugin === void 0 ? void 0 : gtmPlugin.debugEnabled()) { | ||
console.log("[VueGtm]: '" + name + "' not tracked due to navigation cancelled"); | ||
} | ||
} | ||
var additionalEventData = (_b = (_a = to.meta) === null || _a === void 0 ? void 0 : _a.gtmAdditionalEventData) !== null && _b !== void 0 ? _b : {}; | ||
var baseUrl = (_e = (_d = (_c = vueRouter.options) === null || _c === void 0 ? void 0 : _c.history) === null || _d === void 0 ? void 0 : _d.base) !== null && _e !== void 0 ? _e : ""; | ||
var fullUrl = baseUrl; | ||
if (!fullUrl.endsWith("/")) { | ||
fullUrl += "/"; | ||
} | ||
fullUrl += to.fullPath.startsWith("/") ? to.fullPath.substr(1) : to.fullPath; | ||
if (trackOnNextTick) { | ||
void vue_1.nextTick(function () { | ||
gtmPlugin === null || gtmPlugin === void 0 ? void 0 : gtmPlugin.trackView(name, fullUrl, additionalEventData); | ||
}); | ||
} | ||
else { | ||
gtmPlugin === null || gtmPlugin === void 0 ? void 0 : gtmPlugin.trackView(name, fullUrl, additionalEventData); | ||
} | ||
}); | ||
return [2 /*return*/]; | ||
} | ||
}); | ||
if (!vueRouter) { | ||
console.warn("[VueGtm]: You tried to register 'vueRouter' for vue-gtm, but 'vue-router' was not found."); | ||
return; | ||
} | ||
// Flatten routes name | ||
ignoredViews = ignoredViews.map(function (view) { return view.toLowerCase(); }); | ||
vueRouter.afterEach(function (to) { | ||
var _a, _b, _c; | ||
// Ignore some routes | ||
if (typeof to.name !== "string" || ignoredViews.indexOf(to.name.toLowerCase()) !== -1) { | ||
return; | ||
} | ||
// Dispatch vue event using meta gtm value if defined otherwise fallback to route name | ||
var name = to.meta && typeof to.meta.gtm === "string" && !!to.meta.gtm ? to.meta.gtm : to.name; | ||
var additionalEventData = (_b = (_a = to.meta) === null || _a === void 0 ? void 0 : _a.gtmAdditionalEventData) !== null && _b !== void 0 ? _b : {}; | ||
var baseUrl = (_c = vueRouter.options.base) !== null && _c !== void 0 ? _c : ""; | ||
var fullUrl = baseUrl; | ||
if (!fullUrl.endsWith("/")) { | ||
fullUrl += "/"; | ||
} | ||
fullUrl += to.fullPath.startsWith("/") ? to.fullPath.substr(1) : to.fullPath; | ||
if (trackOnNextTick) { | ||
Vue.nextTick(function () { | ||
gtmPlugin === null || gtmPlugin === void 0 ? void 0 : gtmPlugin.trackView(name, fullUrl, additionalEventData); | ||
}); | ||
} | ||
else { | ||
gtmPlugin === null || gtmPlugin === void 0 ? void 0 : gtmPlugin.trackView(name, fullUrl, additionalEventData); | ||
} | ||
}); | ||
} | ||
/** | ||
* Create the Vue GTM instance. | ||
* | ||
* @param options Options. | ||
* @returns The Vue GTM plugin instance. | ||
*/ | ||
function createGtm(options) { | ||
return { install: function (app) { return install(app, options); } }; | ||
} | ||
exports.createGtm = createGtm; | ||
var _default = { install: install }; | ||
@@ -227,0 +154,0 @@ exports.default = _default; |
@@ -9,3 +9,3 @@ import "url-search-params-polyfill"; | ||
*/ | ||
export declare function loadScript(id: string, config?: Pick<VueGtmUseOptions, "defer" | "compatibility" | "queryParams">): void; | ||
export declare function loadScript(id: string, config?: Pick<VueGtmUseOptions, "defer" | "compatibility" | "nonce" | "queryParams">): void; | ||
/** | ||
@@ -12,0 +12,0 @@ * Check if GTM script is in the document. |
@@ -38,2 +38,5 @@ "use strict"; | ||
script.defer = Boolean(config.defer || config.compatibility); | ||
if (config.nonce) { | ||
script.nonce = config.nonce; | ||
} | ||
var queryString = new URLSearchParams(__assign({ id: id }, ((_c = config.queryParams) !== null && _c !== void 0 ? _c : {}))); | ||
@@ -40,0 +43,0 @@ script.src = "https://www.googletagmanager.com/gtm.js?" + queryString; |
{ | ||
"name": "vue-gtm", | ||
"version": "3.4.2-issue-122.1", | ||
"version": "3.5.0-vue2", | ||
"description": "Google Tag Manager implementation in Vue application", | ||
@@ -62,8 +62,8 @@ "main": "dist/index.js", | ||
"devDependencies": { | ||
"@types/jest": "~26.0.20", | ||
"@typescript-eslint/eslint-plugin": "~4.17.0", | ||
"@typescript-eslint/parser": "~4.17.0", | ||
"eslint": "~7.21.0", | ||
"@types/jest": "~26.0.21", | ||
"@typescript-eslint/eslint-plugin": "~4.19.0", | ||
"@typescript-eslint/parser": "~4.19.0", | ||
"eslint": "~7.22.0", | ||
"eslint-config-prettier": "~8.1.0", | ||
"eslint-plugin-jsdoc": "~32.2.0", | ||
"eslint-plugin-jsdoc": "~32.3.0", | ||
"eslint-plugin-prettier": "~3.3.1", | ||
@@ -75,13 +75,10 @@ "eslint-plugin-spellcheck": "~0.0.17", | ||
"prettier-plugin-organize-imports": "~1.1.1", | ||
"ts-jest": "~26.5.3", | ||
"ts-jest": "~26.5.4", | ||
"typescript": "~4.2.3", | ||
"vue": "^3.0.0", | ||
"vue-router": "^4.0.0" | ||
"vue": "^2.6.12", | ||
"vue-router": "^3.5.1" | ||
}, | ||
"peerDependencies": { | ||
"vue": "^3.0.0" | ||
}, | ||
"optionalDependencies": { | ||
"vue-router": "^4.0.0" | ||
"vue": "^2.6.0" | ||
} | ||
} |
@@ -78,2 +78,3 @@ <h1 align="center">Vue Google Tag Manager</h1> | ||
compatibility: false, // Will add `async` and `defer` to the script tag to not block requests for old browsers that do not support `async` | ||
nonce: "2726c7f26c", // Will add `nonce` to the script tag | ||
enabled: true, // defaults to true. Plugin can be disabled by setting this to false for Ex: enabled: !!GDPR_Cookie (optional) | ||
@@ -80,0 +81,0 @@ debug: true, // Whether or not display console logs debugs (optional) |
@@ -1,2 +0,2 @@ | ||
import type { Router } from "vue-router"; | ||
import type Router from "vue-router"; | ||
@@ -84,2 +84,8 @@ /** | ||
/** | ||
* Will add `nonce` to the script tag. | ||
* | ||
* @see [Using Google Tag Manager with a Content Security Policy](https://developers.google.com/tag-manager/web/csp) | ||
*/ | ||
nonce?: string; | ||
/** | ||
* Plugin can be disabled by setting this to `false`. | ||
@@ -86,0 +92,0 @@ * |
@@ -1,2 +0,2 @@ | ||
import { App, nextTick, Plugin } from "vue"; | ||
import _Vue, { PluginObject } from "vue"; | ||
import { DEFAULT_CONFIG, VueGtmContainer, VueGtmQueryParams, VueGtmUseOptions } from "./config"; | ||
@@ -25,6 +25,6 @@ import GtmPlugin from "./plugin"; | ||
* | ||
* @param app The Vue app instance. | ||
* @param Vue The Vue instance. | ||
* @param options Configuration options. | ||
*/ | ||
function install(app: App, options: VueGtmUseOptions = { id: "" }): void { | ||
function install(Vue: typeof _Vue, options: VueGtmUseOptions = { id: "" }): void { | ||
if (Array.isArray(options.id)) { | ||
@@ -47,7 +47,7 @@ for (const idOrObject of options.id) { | ||
gtmPlugin = new GtmPlugin(options.id, options); | ||
app.config.globalProperties.$gtm = gtmPlugin; | ||
Vue.prototype.$gtm = Vue.gtm = gtmPlugin; | ||
// Handle vue-router if defined | ||
if (options.vueRouter) { | ||
void initVueRouterGuard(app, options.vueRouter, options.ignoredViews, options.trackOnNextTick); | ||
initVueRouterGuard(Vue, options.vueRouter, options.ignoredViews, options.trackOnNextTick); | ||
} | ||
@@ -80,4 +80,2 @@ | ||
} | ||
app.provide("gtm", options); | ||
} | ||
@@ -88,3 +86,3 @@ | ||
* | ||
* @param app The Vue app instance. | ||
* @param Vue The Vue instance. | ||
* @param vueRouter The Vue router instance to attach the guard. | ||
@@ -94,12 +92,9 @@ * @param ignoredViews An array of route name that will be ignored. | ||
*/ | ||
async function initVueRouterGuard( | ||
app: App, | ||
function initVueRouterGuard( | ||
Vue: typeof _Vue, | ||
vueRouter: Exclude<VueGtmUseOptions["vueRouter"], undefined>, | ||
ignoredViews: VueGtmUseOptions["ignoredViews"] = [], | ||
trackOnNextTick: VueGtmUseOptions["trackOnNextTick"] | ||
): Promise<void> { | ||
let vueRouterModule: typeof import("vue-router"); | ||
try { | ||
vueRouterModule = await import("vue-router"); | ||
} catch { | ||
): void { | ||
if (!vueRouter) { | ||
console.warn("[VueGtm]: You tried to register 'vueRouter' for vue-gtm, but 'vue-router' was not found."); | ||
@@ -112,3 +107,3 @@ return; | ||
vueRouter.afterEach((to, from, failure) => { | ||
vueRouter.afterEach((to) => { | ||
// Ignore some routes | ||
@@ -121,15 +116,4 @@ if (typeof to.name !== "string" || ignoredViews.indexOf(to.name.toLowerCase()) !== -1) { | ||
const name: string = to.meta && typeof to.meta.gtm === "string" && !!to.meta.gtm ? to.meta.gtm : to.name; | ||
if (vueRouterModule.isNavigationFailure(failure, vueRouterModule.NavigationFailureType.aborted)) { | ||
if (gtmPlugin?.debugEnabled()) { | ||
console.log(`[VueGtm]: '${name}' not tracked due to navigation aborted`); | ||
} | ||
} else if (vueRouterModule.isNavigationFailure(failure, vueRouterModule.NavigationFailureType.cancelled)) { | ||
if (gtmPlugin?.debugEnabled()) { | ||
console.log(`[VueGtm]: '${name}' not tracked due to navigation cancelled`); | ||
} | ||
} | ||
const additionalEventData: Record<string, any> = (to.meta?.gtmAdditionalEventData as Record<string, any>) ?? {}; | ||
const baseUrl: string = vueRouter.options?.history?.base ?? ""; | ||
const additionalEventData: Record<string, any> = to.meta?.gtmAdditionalEventData ?? {}; | ||
const baseUrl: string = vueRouter.options.base ?? ""; | ||
let fullUrl: string = baseUrl; | ||
@@ -142,3 +126,3 @@ if (!fullUrl.endsWith("/")) { | ||
if (trackOnNextTick) { | ||
void nextTick(() => { | ||
Vue.nextTick(() => { | ||
gtmPlugin?.trackView(name, fullUrl, additionalEventData); | ||
@@ -152,15 +136,5 @@ }); | ||
/** | ||
* Create the Vue GTM instance. | ||
* | ||
* @param options Options. | ||
* @returns The Vue GTM plugin instance. | ||
*/ | ||
export function createGtm(options: VueGtmUseOptions): VueGtmPlugin { | ||
return { install: (app: App) => install(app, options) }; | ||
} | ||
declare module "@vue/runtime-core" { | ||
declare module "vue/types/vue" { | ||
// eslint-disable-next-line jsdoc/require-jsdoc | ||
export interface ComponentCustomProperties { | ||
export interface Vue { | ||
/** | ||
@@ -171,2 +145,9 @@ * The Vue GTM Plugin instance. | ||
} | ||
// eslint-disable-next-line jsdoc/require-jsdoc | ||
export interface VueConstructor<V extends Vue = Vue> { | ||
/** | ||
* The Vue GTM Plugin instance. | ||
*/ | ||
gtm: GtmPlugin; | ||
} | ||
} | ||
@@ -177,3 +158,3 @@ | ||
*/ | ||
export type VueGtmPlugin = Plugin; | ||
export type VueGtmPlugin = PluginObject<VueGtmUseOptions>; | ||
export { VueGtmUseOptions } from "./config"; | ||
@@ -180,0 +161,0 @@ |
@@ -12,3 +12,3 @@ import "url-search-params-polyfill"; | ||
id: string, | ||
config: Pick<VueGtmUseOptions, "defer" | "compatibility" | "queryParams"> = {} | ||
config: Pick<VueGtmUseOptions, "defer" | "compatibility" | "nonce" | "queryParams"> = {} | ||
): void { | ||
@@ -33,2 +33,6 @@ const doc: Document = document; | ||
if (config.nonce) { | ||
script.nonce = config.nonce; | ||
} | ||
const queryString: URLSearchParams = new URLSearchParams({ | ||
@@ -35,0 +39,0 @@ id, |
2
-33.33%288
0.35%70241
-6.38%1250
-5.37%