├── .babelrc ├── .gitignore ├── .npmignore ├── README.md ├── docs └── publish-to-npm.md ├── package.json └── src ├── index.js ├── snippet.js └── tools.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ "es2015" ] 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | yarn-error.log 3 | yarn.lock 4 | index.js 5 | snippet.js 6 | tools.js 7 | *.DS_Store 8 | !src/*.js 9 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Use analytics.js without Segment (analytics-js-without-segment) 2 | 3 | A tool to use (and render) Segments open-source analytics library (analytics.js) **WITHOUT using the paid Segment service** (segment.com). To be used with your favorite analytics-tools like **Google Analytics, Mixpanel, Hotjar, etc.** 4 | 5 | @Gatsby-users: Use the [related gatsby-package](https://github.com/thebarty/gatsby-plugin-analytics-without-segment). 6 | 7 | ## Features 8 | 9 | * **Best-practise loading of analytics.js**: This plugin loads [Segments analytics.js](https://github.com/segmentio/analytics.js) following [best-practises](http://www.ianww.com/blog/2017/08/06/analytics-js-standalone-library/) and enables you to easily set options (`cdnUrl` and `services`). 10 | * **Unlimited and free**: You'll use the free analytics.js and not depend on Segment (which has aggressive pay-or-leave policies, after you hit the 1000 free monthly users) 11 | * **Track events anywhere**: You can track custom-events anywhere in your code via `analytics.track()` 12 | * **Supported integrations**: A LOT of analytics-tools are supported. see https://github.com/segment-integrations. 13 | * **Development-mode**: In development you'll only see the events in the console. Your events will NOT be forwarded 14 | * **Production-mode**: All events will be routed to the services. Check your console for possible errors. 15 | 16 | ## INSTALL 17 | 18 | Install via npm 19 | 20 | ``` 21 | npm install analytics-js-without-segment --save 22 | ``` 23 | 24 | ## Usage 25 | 26 | In your client, add: 27 | 28 | ``` 29 | const { renderAnalytics, runAnalytics } = require('analytics-js-without-segment') 30 | 31 | const options = { 32 | cdnUrl: 'https://cdnjs.cloudflare.com/ajax/libs/analytics.js/2.9.1/analytics.min.js', // host yourself or use cdnjs (https://cdnjs.com/libraries/analytics.js) 33 | services: { 34 | // see integration https://github.com/segment-integrations/analytics.js-integration-google-analytics/blob/master/lib/index.js 35 | 'Google Analytics': { 36 | trackingId: 'UA-XXX-1', 37 | anonymizeIp: true, 38 | }, 39 | // see integration https://github.com/segment-integrations/analytics.js-integration-mixpanel/blob/master/lib/index.js 40 | 'Mixpanel': { 41 | token: 'XXX', 42 | people: true, 43 | trackAllPages: true, 44 | }, 45 | // see integration https://github.com/segment-integrations/analytics.js-integration-fullstory/blob/master/lib/index.js 46 | 'FullStory': { 47 | org: 'XXX', 48 | debug: true, 49 | }, 50 | // ... other service? See supported integrations (that can be loaded via analytics.js) at https://github.com/segment-integrations. 51 | }, 52 | } 53 | 54 | // OPTION 1) 55 | // AUTOMATICALLY attach to window.analytics and make global `analytics.*`-object available 56 | runAnalytics(options) // after running this, `analytics` is globally available 57 | 58 | // OPTION 2) 59 | // MANUALLY attach to window.analytics and make global `analytics.*`-object available 60 | const snippet = renderAnalytics(options) 61 | eval(snippet) 62 | ``` 63 | 64 | ## Why this package? 65 | The concept of analytics.js is awesome. **BUT Segments's free tier (max 1.000 monthly users) might not work for you**, if you have a lot of non-paying visitors on your website. 66 | 67 | So if you want an **easy-to-configure** `analytics.js`-wrapper for your analytics (**Google Analytics, Mixpanel, Hotjar, YouNameIt, ...**), then this is your go-to! Basically **ALL big analytics-integrations are supported**. 68 | 69 | **This solution is open source, unlimited and free forever!** 70 | 71 | *Background (state 2018-10-24): Segment.com has aggressive pay-or-leave policies, which they will apply after you hit the 1000 free monthly users limit. They will lock your account and stop processing your events after a deadline.* 72 | 73 | ## WHY using this? 74 | 75 | Advantages: 76 | * You will NOT run out of segments free tier after hitting 1000 MPU (monthly users), because you simply do NOT use it 77 | * You'll have a single API for tracking analytics (p.e. `analytics.page()` or `analytics.track()`) 78 | 79 | Disadvantages: 80 | * This does NOT work on server-side 81 | * It might take a bit more time to configure than using segment 82 | * You do NOT any of segments other cool features (which you will not need when starting a side-project) 83 | 84 | ## Question: What analytics-services are supported? 85 | You should be able to use ALL services that segment itself supports and has integrations available. You'll have to dig around a bit in segment's code to find the options for each individual tool. 86 | 87 | Check out the supported Integrations (that can be loaded via analytics.js) at https://github.com/segment-integrations. 88 | 89 | ## Credits 90 | 91 | This is project is based on https://gist.github.com/typpo/5e2e4403c60314e04e8b6b257555f6de 92 | and the related blogpost at http://www.ianww.com/blog/2017/08/06/analytics-js-standalone-library/. 93 | 94 | ## Contribute (Development notes) 95 | 96 | CLI cheatsheat: 97 | 98 | ``` 99 | npm run build-watch; # auto-compile on file-change from /src to / via babel 100 | ``` 101 | 102 | ## License 103 | 104 | MIT 105 | -------------------------------------------------------------------------------- /docs/publish-to-npm.md: -------------------------------------------------------------------------------- 1 | # Notes on publish process 2 | 3 | ``` 4 | # clone project to local 5 | cd ~/OpenSourceProjects 6 | git clone https://github.com/thebarty/analytics-js-without-segment.git 7 | cd analytics-js-without-segment 8 | 9 | # ... edit changes 10 | 11 | # push to github 12 | git add . 13 | git commit -m "message" 14 | git push -all 15 | 16 | # publish to npm 17 | npm run publish-to-npm; # build and publish to npm 18 | ``` 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "analytics-js-without-segment", 3 | "version": "1.0.0", 4 | "description": "A plugin use (and render) Segments open-source analytics library (analytics.js) WITHOUT using the paid Segment service (segment.com). To be used with your favorite analytics-tools like Google Analytics, Mixpanel, Hotjar, etc.", 5 | "keywords": [ 6 | "standalone", 7 | "free", 8 | "analytics", 9 | "mixpanel", 10 | "hotjar", 11 | "google analytics", 12 | "analyticsjs", 13 | "analytics.js", 14 | "without", 15 | "segment" 16 | ], 17 | "main": "index.js", 18 | "license": "MIT", 19 | "repository": { 20 | "type": "git", 21 | "url": "https://github.com/thebarty/analytics-js-without-segment" 22 | }, 23 | "author": "Henning ", 24 | "scripts": { 25 | "lint": "standard", 26 | "build": "babel src --out-dir .", 27 | "build-watch": "npx babel src --out-dir . --watch", 28 | "publish-to-npm": "npm run build; npm publish .;" 29 | }, 30 | "dependencies": { 31 | "lodash": "^4.17.11", 32 | "terser": "^3.10.2" 33 | }, 34 | "devDependencies": { 35 | "babel-cli": "^6.24.1", 36 | "babel-eslint": "^7.2.3", 37 | "babel-preset-es2015": "^6.24.1", 38 | "standard": "^10.0.2" 39 | }, 40 | "standard": { 41 | "parser": "babel-eslint", 42 | "ignore": [ 43 | ] 44 | }, 45 | "bugs": { 46 | "url": "https://github.com/thebarty/analytics-js-without-segment/issues" 47 | }, 48 | "homepage": "https://github.com/thebarty/analytics-js-without-segment#readme" 49 | } 50 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | const Terser = require('terser') 2 | const template = require('lodash/template') 3 | 4 | import { log } from './tools.js' 5 | import { getSnippet } from './snippet.js' 6 | 7 | /** 8 | * Render analytics as string. 9 | * Validate options and output logs. 10 | */ 11 | export const renderAnalytics = (options) => { 12 | const { cdnUrl, services } = options 13 | // validate parameters 14 | if (services===undefined) throw new Error('please pass "services"-option') 15 | // DEVELOPMENT 16 | // in development, stub out all analytics.js methods 17 | // this prevents "dirtying" your real analytics with local testing/traffic 18 | const { NODE_ENV = 'development' } = process.env 19 | if (NODE_ENV === 'development') { 20 | log('development mode detected! NOT sending data to analytics-tools') 21 | return ` 22 | (function () { 23 | // analytics.js stub 24 | const analytics = window.analytics = {} 25 | const methods = [ 26 | 'trackSubmit', 'trackClick', 'trackLink', 'trackForm', 'pageview', 27 | 'identify', 'reset', 'group', 'track', 'ready', 'alias', 'debug', 28 | 'page', 'once', 'off', 'on' 29 | ] 30 | methods.forEach(method => 31 | analytics[method] = (...args) => console.log(\`[analytics-js-without-segment development-mode active] analytics.\${method}\`, ...args) 32 | ) 33 | })() 34 | ` 35 | } 36 | // PRODUCTION 37 | log('production mode! SENDING data to analytics-tools') 38 | const snippet = getSnippet() 39 | const theTemplate = template(snippet) 40 | const sourceWithValues = theTemplate({ 41 | cdnUrl: cdnUrl || 'https://cdnjs.cloudflare.com/ajax/libs/analytics.js/2.9.1/analytics.min.js', // default 42 | services: JSON.stringify(services), 43 | }) 44 | const result = Terser.minify(sourceWithValues) // see https://www.npmjs.com/package/terser 45 | if (result.error) throw new Error(result.error) 46 | log(result.code) 47 | return result.code 48 | } 49 | 50 | /** 51 | * Run analytics and attach them to window.analytics 52 | */ 53 | export const runAnalytics = (options) => { 54 | eval(renderAnalytics(options)) 55 | } 56 | -------------------------------------------------------------------------------- /src/snippet.js: -------------------------------------------------------------------------------- 1 | /** 2 | * LOAD ANALYTICS.JS with configured `services` FROM `cdnUrl`. 3 | * 4 | * DETAILS 5 | * This file is the pure script that loads analytics.js from the cdnUrl. 6 | * It contains placeholders for the parameters `services` and `cdnUrl`, 7 | * which will be replaced when building/rendering this file (using `lodash.template()`). 8 | * 9 | * WHAT DOES THIS FILE DO? 10 | * It creates a queue for stubbed methods, 11 | * which are available BEFORE analytics.js has been full loaded. 12 | * After analytics.js and its services has been fully loaded, 13 | * it forwards events within the cue to the services, 14 | * so that no events get lost. 15 | * 16 | * SOURCE 17 | * This file originally comes from https://gist.github.com/typpo/5e2e4403c60314e04e8b6b257555f6de. 18 | * => see the related blogpost at http://www.ianww.com/blog/2017/08/06/analytics-js-standalone-library/ 19 | */ 20 | export function getSnippet () { 21 | return ` 22 | window.analytics || (window.analytics = {}); 23 | 24 | // Create a queue to push events and stub all methods 25 | window.analytics_queue || (window.analytics_queue = []); 26 | (function() { 27 | var methods = ['identify', 'track', 'trackLink', 'trackForm', 'trackClick', 'trackSubmit', 'page', 'pageview', 'ab', 'alias', 'ready', 'group', 'on', 'once', 'off']; 28 | var factory = function(method) { 29 | return function () { 30 | var args = Array.prototype.slice.call(arguments); 31 | args.unshift(method); 32 | analytics_queue.push(args); 33 | return window.analytics; 34 | }; 35 | }; 36 | for (var i = 0; i < methods.length; i++) { 37 | var method = methods[i]; 38 | window.analytics[method] = factory(method); 39 | } 40 | })(); 41 | 42 | // Load analytics.js after everything else 43 | analytics.load = function(callback) { 44 | var script = document.createElement('script'); 45 | script.async = true; 46 | script.type = 'text/javascript'; 47 | script.src = '<%= cdnUrl %>'; // NOTE: is replaced when building (via lodash.template()) 48 | if (script.addEventListener) { 49 | script.addEventListener('load', function(e) { 50 | if (typeof callback === 'function') { 51 | callback(e); 52 | } 53 | }, false); 54 | } else { // IE8 55 | script.onreadystatechange = function () { 56 | if (this.readyState == 'complete' || this.readyState == 'loaded') { 57 | callback(window.event); 58 | } 59 | }; 60 | } 61 | var firstScript = document.getElementsByTagName('script')[0]; 62 | firstScript.parentNode.insertBefore(script, firstScript); 63 | }; 64 | 65 | analytics.load(function() { 66 | analytics.initialize(<%= services %>); // NOTE: is replaced when building (via lodash.template()) 67 | // Loop through the interim analytics queue and reapply the calls to their 68 | // proper analytics.js method. 69 | while (window.analytics_queue.length > 0) { 70 | var item = window.analytics_queue.shift(); 71 | var method = item.shift(); 72 | if (analytics[method]) analytics[method].apply(analytics, item); 73 | } 74 | }); 75 | ` 76 | } 77 | -------------------------------------------------------------------------------- /src/tools.js: -------------------------------------------------------------------------------- 1 | const { NODE_ENV = 'development' } = process.env 2 | 3 | export const isDevelopment = NODE_ENV==='development' 4 | 5 | export function log(...args) { 6 | if (isDevelopment) { 7 | console.log('[analytics-js-without-segment]', ...args) 8 | } 9 | } 10 | --------------------------------------------------------------------------------