├── .babelrc ├── .eslintrc.js ├── .github └── workflows │ └── test.yml ├── .gitignore ├── .npmignore ├── .nvmrc ├── CHANGELOG.md ├── LICENSE ├── README.md ├── dev ├── .gitignore ├── gatsby-config.js ├── nodemon.json ├── package.json ├── src │ └── pages │ │ └── index.js ├── static │ └── favicon.ico └── test │ ├── mocha-config.js │ ├── mocha-environment.js │ └── mocha-setup.js ├── example ├── .gitignore ├── LICENSE ├── README.md ├── assets │ ├── segment_ui.png │ ├── track_event.png │ └── track_page.png ├── gatsby-config.js ├── package.json └── src │ ├── components │ ├── header.js │ ├── image.js │ ├── layout.css │ ├── layout.js │ └── seo.js │ ├── images │ └── gatsby-icon.png │ └── pages │ ├── 404.js │ ├── index.js │ └── page-2.js ├── index.js ├── package.json ├── src ├── gatsby-browser.js └── gatsby-ssr.js └── tests ├── .eslintrc.js ├── gatsby-2 ├── gatsby-config.js ├── package.json ├── src │ └── pages │ │ └── index.js ├── static │ └── favicon.ico └── test │ ├── mocha-config.js │ ├── mocha-environment.js │ └── mocha-setup.js ├── gatsby-3 ├── gatsby-config.js ├── package.json ├── src │ └── pages │ │ └── index.js ├── static │ └── favicon.ico └── test │ ├── mocha-config.js │ ├── mocha-environment.js │ └── mocha-setup.js ├── gatsby-4 ├── gatsby-config.js ├── package.json ├── src │ └── pages │ │ └── index.js ├── static │ └── favicon.ico └── test │ ├── mocha-config.js │ ├── mocha-environment.js │ └── mocha-setup.js ├── gatsby-5 ├── gatsby-config.js ├── package.json ├── src │ └── pages │ │ └── index.js ├── static │ └── favicon.ico └── test │ ├── mocha-config.js │ ├── mocha-environment.js │ └── mocha-setup.js └── shared └── tests.test.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["env", "react"], 3 | "comments": false 4 | } 5 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "env": { 3 | "browser": true, 4 | "es2021": true, 5 | "node": true, 6 | }, 7 | "extends": [ 8 | "eslint:recommended", 9 | "plugin:react/recommended" 10 | ], 11 | "parserOptions": { 12 | "ecmaFeatures": { 13 | "jsx": true 14 | }, 15 | "ecmaVersion": 12, 16 | "sourceType": "module" 17 | }, 18 | "plugins": [ 19 | "react" 20 | ], 21 | "rules": { 22 | } 23 | }; 24 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: CI Tests 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | strategy: 13 | #fail-fast: false 14 | matrix: 15 | # gatsby 5 can only run on node 18.x and will be added below via "include" 16 | gatsby-version: [2, 3, 4] 17 | node-version: [12.x, 14.x, 16.x, 18.x] 18 | exclude: 19 | # excludes gatsby 4 on node 12.x because it's not compatible 20 | - node-version: 12.x 21 | gatsby-version: 4 22 | # excludes gatsby 2 on node 18.x because it's not compatible 23 | - node-version: 18.x 24 | gatsby-version: 2 25 | include: 26 | # gatsby 5 can only run on node 18.x 27 | - node-version: 18.x 28 | gatsby-version: 5 29 | 30 | 31 | steps: 32 | - uses: actions/checkout@v2 33 | 34 | # TODO: Figure out a way to cache the node_modules using `actions/setup-node` while also 35 | # installing the *latest* version of each major version of Gatsby 36 | - name: Use Node.js ${{ matrix.node-version }} 37 | uses: actions/setup-node@v3 38 | with: 39 | node-version: ${{ matrix.node-version }} 40 | 41 | # Run the yarn install on the parent directory so that build deps are present 42 | - run: yarn install 43 | 44 | # Run yarn test in the specific gatsby version directory 45 | - run: yarn test 46 | working-directory: ./tests/gatsby-${{ matrix.gatsby-version }} 47 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # possible output files from local builds 2 | gatsby-browser.js 3 | gatsby-node.js 4 | gatsby-ssr.js 5 | 6 | # gatsby files 7 | .cache/ 8 | public 9 | 10 | node_modules 11 | 12 | # Them lockfiles 13 | yarn.lock 14 | package-lock.json 15 | 16 | # Output of 'npm pack' 17 | *.tgz 18 | 19 | *.DS_Store 20 | 21 | yarn-error.log 22 | 23 | # Finally, whitelist all .js files in the src/ directory 24 | !src/*.js 25 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Ignore everything by default 2 | * 3 | 4 | # Whitelist these 5 | !/index.js 6 | !/gatsby-browser.js 7 | !/gatsby-ssr.js 8 | !/README.md 9 | !/CHANGELOG.md 10 | !/LICENSE 11 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v18 -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 5.0.0 2 | - BREAKING CHANGE: The `options` now support more complex things, and some thing are renamed. Please check out all of the options in the `README`. 3 | 4 | ## 4.0.3 5 | - Ensure that cb is a function before calling it. https://github.com/benjaminhoffman/gatsby-plugin-segment-js/pull/57 6 | 7 | ## 4.0.2 8 | - Fix syntax error in delayed loader code. https://github.com/benjaminhoffman/gatsby-plugin-segment-js/pull/56 9 | 10 | ## 4.0.1 11 | - Better idempotency. https://github.com/benjaminhoffman/gatsby-plugin-segment-js/pull/54 12 | 13 | ## 4.0.0 14 | 15 | - Add `includeTitleInPageCall` option, and set it to `true` by default. Strictly speaking, this was a "breaking change", hence the new major version. 16 | - Fix bug where `analytics.page()` would be called twice. 17 | 18 | ## 3.7.2 19 | 20 | - Updated `gatsby` peer dependency to support `^2 || ^3 || ^4 || ^5` https://github.com/benjaminhoffman/gatsby-plugin-segment-js/pull/52 21 | 22 | ## 3.7.1 23 | 24 | - Updated `gatsby` peer dependency to support `^2 || ^3 || ^4` https://github.com/benjaminhoffman/gatsby-plugin-segment-js/pull/43 25 | 26 | ## 3.7.0 27 | 28 | - Updated default snippet to `4.13.2` 29 | - Added `customSnippet` option to allow for providing arbitrary snippet code 30 | - Decreased NPM package size 31 | - Some small optimizations 32 | - Added linting for developers 33 | 34 | - Added correct peer dependencies(thanks @LekoArts) and updated readme to show correct usage of analytics.load(thanks @seankovacs) 35 | 36 | ## 3.6.1 37 | 38 | - Added correct peer dependencies(thanks @LekoArts) and updated readme to show correct usage of analytics.load(thanks @seankovacs) 39 | 40 | ## 3.6.0 41 | 42 | - Added `trackPageDelay` option that allows specifying the amount of time to wait before calling `trackPage`. Thanks to @sreucherand! 43 | 44 | ## 3.5.0 45 | 46 | - Added `addSourceMiddleware`, `addIntegrationMiddleware`, `setAnonymousId`, and `addDestinationMiddleware` methods to Segment snippet 47 | 48 | ## 3.4.0 49 | 50 | - Adds `manualLoad` plugin option to allow users to manually load Segment snippet, e.g. waiting for a user to opt into being tracked for GDPR 51 | 52 | ## 3.3.1 53 | 54 | - Send `document.title` with page tracking events for enhanced event data 55 | 56 | ## 3.3.0 57 | 58 | - Adds a 50ms delay to page tracking calls to prevent erroneously sending duplicate page events 59 | 60 | ## 3.2.1 61 | 62 | - Delete package-lock.json 63 | 64 | ## 3.2.0 65 | 66 | - Bug: Built files not uploaded to npm 67 | 68 | ## 3.1.1 69 | 70 | - Updated README.md 71 | 72 | ## 3.1.0 73 | 74 | - NEW FEATURE: we've added one new feature with two options: `delayLoad` and its friend `delayLoadTime`. When the former is set to true, we will delay loading Segment `delayLoadTime` seconds _after_ either a route change or user page scroll, whichever comes first. This functionality was built to help with SEO, UX, etc by preventing Segment from increasing your TTI. 75 | 76 | ## 3.0.0 77 | 78 | - BREAKING CHANGE: you must now explicitly pass `trackPage: true` in your `gatsby-config.js` file if you want us to automatically track pageviews 79 | 80 | - BREAKING CHANGE: previously we would only fire `analytics.page()` on the initial page load. But now we will invoke it on each route transition. See README for more details. 81 | 82 | - Added example dir 83 | 84 | - Expanded README.md and adds CHANGELOG.md 85 | 86 | ## 2.1.2 87 | 88 | - Bumps Segment snippet from 4.0.0 to 4.1.0 89 | 90 | ## 2.0.0 91 | 92 | - Basic version working 93 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Benjamin Hoffman 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 | # gatsby-plugin-segment-js 2 | 3 | A lightweight & feature-rich Gatsby plugin to easily add [Segment JS snippet](https://segment.com/docs/connections/sources/catalog/libraries/website/javascript/quickstart/) to your site. 4 | 5 | ## We need your help! 🙏🏽 6 | We're looking for active contributors to this repo! If you're interested, simply open an issue or PR of your own and indicate that you'd like to help. Check out our open issues and PRs. We also need to beef up testing. Contributors get to: 7 | - manage versioning and deploys by publishing new versions to NPM 8 | - determine which features get launched by merging pull requests 9 | - oversee the community through commenting on and managing pull requests and issues 10 | 11 | ⚜️ Being an active contributor is great for the community and your engineering resume.⚜️ 12 | 13 | ## Features 14 | 15 | Packed with features: 16 | 17 | - use multiple write keys (one for prod env, another optional one for dev) 18 | - disable page view tracking (just in case you want to add it later manually) 19 | - will use a default Segment code snippet (currently `v4.15.3`), or you can provide your own custom snippet 20 | 21 | ## Install 22 | 23 | - NPM: `$ npm install --save gatsby-plugin-segment-js` 24 | - YARN: `$ yarn add gatsby-plugin-segment-js` 25 | 26 | ## How to use 27 | 28 | ### Setup 29 | 30 | In your gatsby-config.js file: 31 | 32 | ```javascript 33 | plugins: [ 34 | { 35 | resolve: `gatsby-plugin-segment-js`, 36 | options: { 37 | // your segment write key for your production environment 38 | // when process.env.NODE_ENV === 'production' 39 | // required; non-empty string 40 | prodKey: 'SEGMENT_PRODUCTION_WRITE_KEY', 41 | 42 | // if you have a development env for your segment account, paste that key here 43 | // when process.env.NODE_ENV === 'development' 44 | // optional; non-empty string 45 | devKey: 'SEGMENT_DEV_WRITE_KEY', 46 | 47 | // Boolean indicating if you want this plugin to perform any automated analytics.page() calls 48 | // at all, or not. 49 | // If set to false, see below on how to track pageviews manually. 50 | // 51 | // This plugin will attempt to intelligently prevent duplicate page() calls. 52 | // 53 | // Default: true 54 | trackPage: true, 55 | 56 | // Boolean indicating if you want this plugin to perform a page() call immediately once the snippet 57 | // is loaded. 58 | // 59 | // You might want to disable this if you *only* want page() calls to occur upon Client-side routing 60 | // updates. See `trackPageOnRouteUpdate` option. 61 | // 62 | // This plugin will still attempt to intelligently prevent duplicate page() calls. 63 | // 64 | // Default: true 65 | trackPageImmediately: true, 66 | 67 | // Boolean indicating whether to ignore `page` calls by this plugin before we call `analytics.load` 68 | // and/or the `ready` event has been emitted by `analytics`. 69 | // 70 | // Useful in some cases to prevent issues "queuing" `page` events before Segment is fully loaded. 71 | trackPageOnlyIfReady: false, 72 | 73 | // Boolean indicating whether to perform any page() calls during Client-side routing updates. 74 | // 75 | // You might want to disable `trackPageImmediately` if you *only* want page() calls to occur upon 76 | // Client-side routing updates. 77 | // 78 | // This plugin will still attempt to intelligently prevent duplicate page() calls. 79 | // 80 | // Default: true 81 | trackPageOnRouteUpdate: true, 82 | 83 | // Number indicating what delay (in ms) should be used when calling analytics.page() in response 84 | // to a Gatsby `onRouteUpdate` event. This can help to ensure that the segment route tracking is 85 | // in sync with the actual Gatsby route. Otherwise you can end up in a state where the Segment 86 | // page tracking reports the previous page on route change. 87 | // 88 | // See https://www.gatsbyjs.com/docs/reference/config-files/gatsby-browser/ for more information. 89 | // 90 | // Default: 50 91 | trackPageOnRouteUpdateDelay: 50, 92 | 93 | // Boolean indicating whether or not to add the document.title as the first argument to 94 | // the analytics.page() calls. Segment uses some sane defaults, but some users of this plugin 95 | // have wanted to do this in the past. 96 | // 97 | // E.g `analytics.page(document.title)` 98 | // 99 | // Default: false 100 | trackPageWithTitle: false, 101 | 102 | // Boolean indicating whether to call analytics.load() immediately, or to delay it by a specified 103 | // number of ms. Can be useful if you want to wait a specifc amount of time before calling 104 | // analytics.load() and kicking off the activity that occurs with that call. 105 | // 106 | // Default: false 107 | delayLoad: false, 108 | 109 | // Number indicating (in ms) how long to wait for before analytics.load() will be called if 110 | // the `delayLoad` option is set to `true`. 111 | // 112 | // Default: 1000 113 | delayLoadDelay: 1000, 114 | 115 | // Boolean indicating whether to delay calling analytics.load() until either: 116 | // 1) The User interacts with the page by scrolling 117 | // OR 118 | // 2) The User triggers a Gatsby route change. 119 | // 120 | // The `delayLoad` option can be used in addition to this option. 121 | // 122 | // NOTE: 123 | // The route change will only be triggered if you leverage client-side routing (ie, Gatsby ) 124 | // So if you leverage server-side routing with this feature, only a User scroll will trigger 125 | // the `load` call. This is because client-side routing does not do 126 | // a full page refresh, but server-side routing does. Therfore server-side routing will never 127 | // appear to have been triggered by a User interaction. 128 | // 129 | // This is an advanced feature most often used to try to help improve your website's TTI (for SEO, UX, etc). 130 | // 131 | // See here for more context: 132 | // Client-side routing and browser code: https://www.gatsbyjs.com/docs/reference/config-files/gatsby-browser/ 133 | // GIF: https://github.com/benjaminhoffman/gatsby-plugin-segment-js/pull/19#issuecomment-559569483 134 | // TTI: https://github.com/GoogleChrome/lighthouse/blob/master/docs/scoring.md#performance 135 | // Problem/solution: https://marketingexamples.com/seo/performance 136 | // 137 | // Default: false 138 | delayLoadUntilActivity: false, 139 | 140 | // Number indicating (in ms) any additional delay to wait before calling analytics.load() 141 | // after User "activity" has occurred in conjunction with the `delayLoadUntilActivity` option. 142 | // 143 | // Default: 0 144 | delayLoadUntilActivityAdditionalDelay: 0, 145 | 146 | // Whether to completely skip calling `analytics.load({writeKey})`. 147 | // ADVANCED FEATURE: only use if you are calling `analytics.load({writeKey})` manually 148 | // elsewhere in your code or are using a library 149 | // like: https://github.com/segmentio/consent-manager that will call it for you. 150 | // Useful for only loading the tracking script once a user has opted in to being tracked, for example. 151 | // 152 | // Default: false 153 | manualLoad: false, 154 | 155 | // If you need to proxy events through a custom endpoint, 156 | // add a `host` property (defaults to https://cdn.segment.io) 157 | // Segment docs: 158 | // - https://segment.com/docs/connections/sources/custom-domains 159 | // - https://segment.com/docs/connections/sources/catalog/libraries/website/javascript/#proxy 160 | // 161 | // Default: 'https://cdn.segment.io' 162 | host: 'https://override-segment-endpoint', 163 | 164 | // This package will use a default version of Segment's code snippet, but 165 | // if you'd like to include your own you can do so here. This is useful if 166 | // the version this package uses is different than the one you'd like to 167 | // use...or you need to do something custom. 168 | // While you should NOT use a back-ticked template string here, the string 169 | // will be evaluated as template literal with the following variables 170 | // available to it: 171 | // - `writeKey`: The appropriate value from the `prodKey` and `devKey` 172 | // options, based on the `NODE_ENV` 173 | // - any of the other options passed here 174 | // 175 | // NOTES: 176 | // - If you provide a custom snippet, an immediate call to 177 | // `analytics.load()` and/or `analytics.page()` will not be added by 178 | // this plugin. You can - of course - add them yourself to your snippet. 179 | // - If your custom snippet does not include a call to `analytics.load()` 180 | // then you must either: 181 | // 1. Manually load it and set the `manualLoad` option here to `true` 182 | // 2. Use the `delayLoad` option here 183 | customSnippet: '!function(){var analytics=window.analytics||[];...;analytics.load("${writeKey}");analytics.page();}}();' 184 | } 185 | } 186 | ]; 187 | ``` 188 | 189 | ### Loading 190 | 191 | A typical Segment setup using this plugin will add an initial "snippet" for Segment to the page. Once that snippet is there, it needs to be "loaded" completely by making a call to `analytics.load()` before it will actually send over any events to Segment. This `load` call causes a flurry of XHR calls, and some people seem to be quite concerned with having those calls not occur until after some delay - usually for SEO scores, though it's not clear to me if the way Segment handles `load` calls will impact TTI, etc. Regardless, if you want this plugin to delay calling `load`, you have 2 options: 192 | 1. `delayLoad`: This will cause a straightforward delay before `load` is called. Use in conjunction with `delayLoadDelay` to tweak the amount of the delay. 193 | 2. `delayLoadUntilActivity`: This will wait to call `load` until __either__ (a) the user triggers a "scroll" event or (b) the user triggers route change. This option will take precendence over the `delayLoad` option. 194 | 195 | ### Track Events 196 | 197 | If you want to track events, you simply invoke Segment as normal in your React components — (`window.analytics.track('Event Name', {...})` — and you should see the events within your Segment debugger! For example, if you wanted to track events on a click, it may look something like this: 198 | 199 | ```javascript 200 | class IndexPage extends React.Component { 201 | ... 202 | _handleClick() { 203 | window.analytics.track("Track Event Fired", { 204 | userId: user.id, 205 | gender: 'male', 206 | age: 33, 207 | }); 208 | } 209 | render() { 210 | return ( 211 |

212 | 213 | Click here 214 | {" "} 215 | to see a track event 216 |

217 | ); 218 | } 219 | } 220 | ``` 221 | 222 | ### Track Pageviews 223 | 224 | If you want to track pageviews automatically, set (or leave) `trackPage` to `true` in your `gatsby-config.js` file. What we mean by _"automatically"_ is that we will intelligently decide how or when to invoke `window.analytics.page()` for you. 225 | 226 | This involves identifying if the last `page` call was made while the User was on the same `location.pathname` as the previous `page` call. If they appear to be the same, we will skip the duplicate call. 227 | 228 | This plugin can also leverage Gatsby's `onRouteUpdate` API in the `gatsby-browser.js` file ([link][on-route-update]) if you so desire. This is accomplished by setting (or leaving) the `trackPageOnRouteUpdate` to `true`. If this option is enabled, we will make a `page` call every time that the `onRouteUpdate` handler is called. That includes the initial route, and any route changes. 229 | 230 | If you only want to use this plugin to inject and load Segment, and not make any `page` calls, this can be done. Just set `trackPage: false`. But then you will have to make `page` calls yourself in your `gatsby-browser.js` file, like this: 231 | 232 | ```javascript 233 | // gatsby-browser.js 234 | exports.onRouteUpdate = () => { 235 | window.analytics && window.analytics.page(); 236 | }; 237 | ``` 238 | 239 | [on-route-update]: https://www.gatsbyjs.org/docs/browser-apis/#onRouteUpdate -------------------------------------------------------------------------------- /dev/.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | -------------------------------------------------------------------------------- /dev/gatsby-config.js: -------------------------------------------------------------------------------- 1 | // For future developers: create a .env file in this directory an populate it 2 | // with the ENV keys below to use them and not risk checking in your keys to 3 | // version control 4 | require('dotenv').config() 5 | 6 | /** 7 | * Configure your Gatsby site with this file. 8 | * 9 | * See: https://www.gatsbyjs.com/docs/gatsby-config/ 10 | */ 11 | 12 | module.exports = { 13 | /* Your site config here */ 14 | plugins: [ 15 | { 16 | resolve: require.resolve('..'), 17 | options: { 18 | prodKey: process.env.SEGMENT_KEY_PROD || 'SEGMENT_PROD_KEY', 19 | devKey: process.env.SEGMENT_KEY_DEV || 'SEGMENT_DEV_KEY', 20 | 21 | trackPage: true, 22 | trackPageImmediately: true, 23 | trackPageOnlyIfReady: true, 24 | trackPageOnRouteUpdate: true, 25 | trackPageOnRouteUpdateDelay: 5000, 26 | 27 | delayLoad: true, 28 | delayLoadDelay: 5000, 29 | 30 | delayLoadUntilActivity: true, 31 | delayLoadUntilActivityAdditionalDelay: 1000, 32 | } 33 | }, 34 | ], 35 | } 36 | -------------------------------------------------------------------------------- /dev/nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "verbose": true, 3 | "ext": "js,json", 4 | "watch": [ 5 | "./gatsby-config.js", 6 | "../gatsby-browser.js", 7 | "../gatsby-ssr.js" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /dev/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gatsby-plugin-segment-js-test-gatsby-5", 3 | "private": true, 4 | "description": "A simple Gatsby app to test the plugin with Gatsby 5", 5 | "version": "0.1.0", 6 | "license": "0BSD", 7 | "scripts": { 8 | "build": "gatsby build", 9 | "clean": "gatsby clean", 10 | "develop": "nodemon --config ./nodemon.json -x 'gatsby develop'", 11 | "developp": "gatsby develop", 12 | "plugin:build": "yarn --cwd .. build", 13 | "plugin:build:watch": "yarn --cwd .. build:watch", 14 | "test": "mocha --config ./test/mocha-config.js", 15 | "test:prepare": "yarn plugin:build && yarn clean && yarn build", 16 | "test:all": "yarn test:prepare && yarn test" 17 | }, 18 | "devDependencies": { 19 | "chai": "^4.3.4", 20 | "dotenv": "^16.0.3", 21 | "gatsby": "^5", 22 | "mocha": "^9.1.3", 23 | "nodemon": "^2.0.20", 24 | "react": "^18.0.0", 25 | "react-dom": "^18.0.0" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /dev/src/pages/index.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | 3 | export default function Home() { 4 | return
Hello world!
5 | } 6 | -------------------------------------------------------------------------------- /dev/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benjaminhoffman/gatsby-plugin-segment-js/efa49a609b0f08e0997180ed87e3fd727e6521e8/dev/static/favicon.ico -------------------------------------------------------------------------------- /dev/test/mocha-config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | diff: true, 3 | delay: false, 4 | extension: ['js'], 5 | package: './package.json', 6 | reporter: 'spec', 7 | slow: 75, 8 | timeout: 2000, 9 | spec: [ 10 | './test/**/*.test.js', 11 | '../tests/shared/**/*.test.js', 12 | ], 13 | require: [ 14 | // https://mochajs.org/#-require-module-r-module 15 | // '@babel/register', 16 | './test/mocha-environment.js', 17 | ], 18 | file: './test/mocha-setup.js', 19 | exit: true, 20 | } 21 | -------------------------------------------------------------------------------- /dev/test/mocha-environment.js: -------------------------------------------------------------------------------- 1 | const chai = require('chai') 2 | 3 | global.chai = chai 4 | global.expect = chai.expect 5 | -------------------------------------------------------------------------------- /dev/test/mocha-setup.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | // All the tests will be the same, so we'll share them. But we need to tell it where 4 | // to look for things 5 | global.pathToProject = path.resolve(__dirname, '../') 6 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # Typescript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # dotenv environment variables file 55 | .env 56 | 57 | # gatsby files 58 | .cache/ 59 | public 60 | 61 | # Mac files 62 | .DS_Store 63 | 64 | # Yarn 65 | yarn-error.log 66 | .pnp/ 67 | .pnp.js 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | -------------------------------------------------------------------------------- /example/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 gatsbyjs 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 | 23 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # Example Usage of `gatsby-plugin-segment` 2 | 3 | To create this example, I used Gatsby's default starter. Then I only modified two files: `src/pages/index.js` and `/gatsby-config.js`. It's a very simple example that doesn't really need to get started but if you still want to get it running, do the following: 4 | 5 | ```bash 6 | $ cd example/ 7 | $ yarn 8 | $ yarn develop 9 | ``` 10 | 11 | Then open your browser's dev tools. In the _Console_ tab, you should see the message I left for you. And if you click on the _Network_ tab, you'll see the call to Segment. 12 | 13 | Note that if you'd like to send events to your own Segment account, you need to update the API keys in `gatsby-config.js`. 14 | 15 | ## Track Event 16 | 17 | 18 | 19 | ## Track Page 20 | 21 | 22 | 23 | ## Segment UI 24 | 25 | 26 | -------------------------------------------------------------------------------- /example/assets/segment_ui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benjaminhoffman/gatsby-plugin-segment-js/efa49a609b0f08e0997180ed87e3fd727e6521e8/example/assets/segment_ui.png -------------------------------------------------------------------------------- /example/assets/track_event.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benjaminhoffman/gatsby-plugin-segment-js/efa49a609b0f08e0997180ed87e3fd727e6521e8/example/assets/track_event.png -------------------------------------------------------------------------------- /example/assets/track_page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benjaminhoffman/gatsby-plugin-segment-js/efa49a609b0f08e0997180ed87e3fd727e6521e8/example/assets/track_page.png -------------------------------------------------------------------------------- /example/gatsby-config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | siteMetadata: { 3 | title: `Gatsby Default Starter`, 4 | description: `Kick off your next, great Gatsby project with this default starter. This barebones starter ships with the main Gatsby configuration files you might need.`, 5 | author: `@gatsbyjs` 6 | }, 7 | plugins: [ 8 | { 9 | resolve: `gatsby-plugin-segment-js`, 10 | options: { 11 | prodKey: `ADD_API_KEY_PROD`, 12 | devKey: `ADD_API_KEY_DEV`, 13 | trackPage: true 14 | } 15 | }, 16 | `gatsby-plugin-react-helmet`, 17 | { 18 | resolve: `gatsby-source-filesystem`, 19 | options: { 20 | name: `images`, 21 | path: `${__dirname}/src/images` 22 | } 23 | }, 24 | `gatsby-transformer-sharp`, 25 | `gatsby-plugin-sharp`, 26 | { 27 | resolve: `gatsby-plugin-manifest`, 28 | options: { 29 | name: `gatsby-starter-default`, 30 | short_name: `starter`, 31 | start_url: `/`, 32 | background_color: `#663399`, 33 | theme_color: `#663399`, 34 | display: `minimal-ui`, 35 | icon: `src/images/gatsby-icon.png` // This path is relative to the root of the site. 36 | } 37 | } 38 | // this (optional) plugin enables Progressive Web App + Offline functionality 39 | // To learn more, visit: https://gatsby.app/offline 40 | // 'gatsby-plugin-offline', 41 | ] 42 | }; 43 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gatsby-starter-default", 3 | "private": true, 4 | "description": "A simple starter to get up and developing quickly with Gatsby", 5 | "version": "0.1.0", 6 | "author": "Kyle Mathews ", 7 | "dependencies": { 8 | "gatsby": "^2.1.4", 9 | "gatsby-plugin-manifest": "^2.0.17", 10 | "gatsby-plugin-offline": "^2.0.23", 11 | "gatsby-plugin-react-helmet": "^3.0.6", 12 | "gatsby-plugin-segment-js": "latest", 13 | "gatsby-plugin-sharp": "^2.0.20", 14 | "gatsby-source-filesystem": "^2.0.20", 15 | "gatsby-transformer-sharp": "^2.1.13", 16 | "prop-types": "^15.7.2", 17 | "react": "^16.8.2", 18 | "react-dom": "^16.8.2", 19 | "react-helmet": "^5.2.0" 20 | }, 21 | "keywords": [ 22 | "gatsby" 23 | ], 24 | "license": "MIT", 25 | "scripts": { 26 | "build": "gatsby build", 27 | "develop": "gatsby develop", 28 | "start": "npm run develop", 29 | "serve": "gatsby serve", 30 | "test": "echo \"Write tests! -> https://gatsby.app/unit-testing\"" 31 | }, 32 | "repository": { 33 | "type": "git", 34 | "url": "https://github.com/gatsbyjs/gatsby-starter-default" 35 | }, 36 | "bugs": { 37 | "url": "https://github.com/gatsbyjs/gatsby/issues" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /example/src/components/header.js: -------------------------------------------------------------------------------- 1 | import { Link } from "gatsby" 2 | import PropTypes from "prop-types" 3 | import React from "react" 4 | 5 | const Header = ({ siteTitle }) => ( 6 |
12 |
19 |

20 | 27 | {siteTitle} 28 | 29 |

30 |
31 |
32 | ) 33 | 34 | Header.propTypes = { 35 | siteTitle: PropTypes.string, 36 | } 37 | 38 | Header.defaultProps = { 39 | siteTitle: ``, 40 | } 41 | 42 | export default Header 43 | -------------------------------------------------------------------------------- /example/src/components/image.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import { StaticQuery, graphql } from "gatsby" 3 | import Img from "gatsby-image" 4 | 5 | /* 6 | * This component is built using `gatsby-image` to automatically serve optimized 7 | * images with lazy loading and reduced file sizes. The image is loaded using a 8 | * `StaticQuery`, which allows us to load the image from directly within this 9 | * component, rather than having to pass the image data down from pages. 10 | * 11 | * For more information, see the docs: 12 | * - `gatsby-image`: https://gatsby.app/gatsby-image 13 | * - `StaticQuery`: https://gatsby.app/staticquery 14 | */ 15 | 16 | const Image = () => ( 17 | } 30 | /> 31 | ) 32 | export default Image 33 | -------------------------------------------------------------------------------- /example/src/components/layout.css: -------------------------------------------------------------------------------- 1 | html { 2 | font-family: sans-serif; 3 | -ms-text-size-adjust: 100%; 4 | -webkit-text-size-adjust: 100%; 5 | } 6 | body { 7 | margin: 0; 8 | } 9 | article, 10 | aside, 11 | details, 12 | figcaption, 13 | figure, 14 | footer, 15 | header, 16 | main, 17 | menu, 18 | nav, 19 | section, 20 | summary { 21 | display: block; 22 | } 23 | audio, 24 | canvas, 25 | progress, 26 | video { 27 | display: inline-block; 28 | } 29 | audio:not([controls]) { 30 | display: none; 31 | height: 0; 32 | } 33 | progress { 34 | vertical-align: baseline; 35 | } 36 | [hidden], 37 | template { 38 | display: none; 39 | } 40 | a { 41 | background-color: transparent; 42 | -webkit-text-decoration-skip: objects; 43 | } 44 | a:active, 45 | a:hover { 46 | outline-width: 0; 47 | } 48 | abbr[title] { 49 | border-bottom: none; 50 | text-decoration: underline; 51 | text-decoration: underline dotted; 52 | } 53 | b, 54 | strong { 55 | font-weight: inherit; 56 | font-weight: bolder; 57 | } 58 | dfn { 59 | font-style: italic; 60 | } 61 | h1 { 62 | font-size: 2em; 63 | margin: 0.67em 0; 64 | } 65 | mark { 66 | background-color: #ff0; 67 | color: #000; 68 | } 69 | small { 70 | font-size: 80%; 71 | } 72 | sub, 73 | sup { 74 | font-size: 75%; 75 | line-height: 0; 76 | position: relative; 77 | vertical-align: baseline; 78 | } 79 | sub { 80 | bottom: -0.25em; 81 | } 82 | sup { 83 | top: -0.5em; 84 | } 85 | img { 86 | border-style: none; 87 | } 88 | svg:not(:root) { 89 | overflow: hidden; 90 | } 91 | code, 92 | kbd, 93 | pre, 94 | samp { 95 | font-family: monospace, monospace; 96 | font-size: 1em; 97 | } 98 | figure { 99 | margin: 1em 40px; 100 | } 101 | hr { 102 | box-sizing: content-box; 103 | height: 0; 104 | overflow: visible; 105 | } 106 | button, 107 | input, 108 | optgroup, 109 | select, 110 | textarea { 111 | font: inherit; 112 | margin: 0; 113 | } 114 | optgroup { 115 | font-weight: 700; 116 | } 117 | button, 118 | input { 119 | overflow: visible; 120 | } 121 | button, 122 | select { 123 | text-transform: none; 124 | } 125 | [type="reset"], 126 | [type="submit"], 127 | button, 128 | html [type="button"] { 129 | -webkit-appearance: button; 130 | } 131 | [type="button"]::-moz-focus-inner, 132 | [type="reset"]::-moz-focus-inner, 133 | [type="submit"]::-moz-focus-inner, 134 | button::-moz-focus-inner { 135 | border-style: none; 136 | padding: 0; 137 | } 138 | [type="button"]:-moz-focusring, 139 | [type="reset"]:-moz-focusring, 140 | [type="submit"]:-moz-focusring, 141 | button:-moz-focusring { 142 | outline: 1px dotted ButtonText; 143 | } 144 | fieldset { 145 | border: 1px solid silver; 146 | margin: 0 2px; 147 | padding: 0.35em 0.625em 0.75em; 148 | } 149 | legend { 150 | box-sizing: border-box; 151 | color: inherit; 152 | display: table; 153 | max-width: 100%; 154 | padding: 0; 155 | white-space: normal; 156 | } 157 | textarea { 158 | overflow: auto; 159 | } 160 | [type="checkbox"], 161 | [type="radio"] { 162 | box-sizing: border-box; 163 | padding: 0; 164 | } 165 | [type="number"]::-webkit-inner-spin-button, 166 | [type="number"]::-webkit-outer-spin-button { 167 | height: auto; 168 | } 169 | [type="search"] { 170 | -webkit-appearance: textfield; 171 | outline-offset: -2px; 172 | } 173 | [type="search"]::-webkit-search-cancel-button, 174 | [type="search"]::-webkit-search-decoration { 175 | -webkit-appearance: none; 176 | } 177 | ::-webkit-input-placeholder { 178 | color: inherit; 179 | opacity: 0.54; 180 | } 181 | ::-webkit-file-upload-button { 182 | -webkit-appearance: button; 183 | font: inherit; 184 | } 185 | html { 186 | font: 112.5%/1.45em georgia, serif; 187 | box-sizing: border-box; 188 | overflow-y: scroll; 189 | } 190 | * { 191 | box-sizing: inherit; 192 | } 193 | *:before { 194 | box-sizing: inherit; 195 | } 196 | *:after { 197 | box-sizing: inherit; 198 | } 199 | body { 200 | color: hsla(0, 0%, 0%, 0.8); 201 | font-family: georgia, serif; 202 | font-weight: normal; 203 | word-wrap: break-word; 204 | font-kerning: normal; 205 | -moz-font-feature-settings: "kern", "liga", "clig", "calt"; 206 | -ms-font-feature-settings: "kern", "liga", "clig", "calt"; 207 | -webkit-font-feature-settings: "kern", "liga", "clig", "calt"; 208 | font-feature-settings: "kern", "liga", "clig", "calt"; 209 | } 210 | img { 211 | max-width: 100%; 212 | margin-left: 0; 213 | margin-right: 0; 214 | margin-top: 0; 215 | padding-bottom: 0; 216 | padding-left: 0; 217 | padding-right: 0; 218 | padding-top: 0; 219 | margin-bottom: 1.45rem; 220 | } 221 | h1 { 222 | margin-left: 0; 223 | margin-right: 0; 224 | margin-top: 0; 225 | padding-bottom: 0; 226 | padding-left: 0; 227 | padding-right: 0; 228 | padding-top: 0; 229 | margin-bottom: 1.45rem; 230 | color: inherit; 231 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, 232 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; 233 | font-weight: bold; 234 | text-rendering: optimizeLegibility; 235 | font-size: 2.25rem; 236 | line-height: 1.1; 237 | } 238 | h2 { 239 | margin-left: 0; 240 | margin-right: 0; 241 | margin-top: 0; 242 | padding-bottom: 0; 243 | padding-left: 0; 244 | padding-right: 0; 245 | padding-top: 0; 246 | margin-bottom: 1.45rem; 247 | color: inherit; 248 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, 249 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; 250 | font-weight: bold; 251 | text-rendering: optimizeLegibility; 252 | font-size: 1.62671rem; 253 | line-height: 1.1; 254 | } 255 | h3 { 256 | margin-left: 0; 257 | margin-right: 0; 258 | margin-top: 0; 259 | padding-bottom: 0; 260 | padding-left: 0; 261 | padding-right: 0; 262 | padding-top: 0; 263 | margin-bottom: 1.45rem; 264 | color: inherit; 265 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, 266 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; 267 | font-weight: bold; 268 | text-rendering: optimizeLegibility; 269 | font-size: 1.38316rem; 270 | line-height: 1.1; 271 | } 272 | h4 { 273 | margin-left: 0; 274 | margin-right: 0; 275 | margin-top: 0; 276 | padding-bottom: 0; 277 | padding-left: 0; 278 | padding-right: 0; 279 | padding-top: 0; 280 | margin-bottom: 1.45rem; 281 | color: inherit; 282 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, 283 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; 284 | font-weight: bold; 285 | text-rendering: optimizeLegibility; 286 | font-size: 1rem; 287 | line-height: 1.1; 288 | } 289 | h5 { 290 | margin-left: 0; 291 | margin-right: 0; 292 | margin-top: 0; 293 | padding-bottom: 0; 294 | padding-left: 0; 295 | padding-right: 0; 296 | padding-top: 0; 297 | margin-bottom: 1.45rem; 298 | color: inherit; 299 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, 300 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; 301 | font-weight: bold; 302 | text-rendering: optimizeLegibility; 303 | font-size: 0.85028rem; 304 | line-height: 1.1; 305 | } 306 | h6 { 307 | margin-left: 0; 308 | margin-right: 0; 309 | margin-top: 0; 310 | padding-bottom: 0; 311 | padding-left: 0; 312 | padding-right: 0; 313 | padding-top: 0; 314 | margin-bottom: 1.45rem; 315 | color: inherit; 316 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, 317 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; 318 | font-weight: bold; 319 | text-rendering: optimizeLegibility; 320 | font-size: 0.78405rem; 321 | line-height: 1.1; 322 | } 323 | hgroup { 324 | margin-left: 0; 325 | margin-right: 0; 326 | margin-top: 0; 327 | padding-bottom: 0; 328 | padding-left: 0; 329 | padding-right: 0; 330 | padding-top: 0; 331 | margin-bottom: 1.45rem; 332 | } 333 | ul { 334 | margin-left: 1.45rem; 335 | margin-right: 0; 336 | margin-top: 0; 337 | padding-bottom: 0; 338 | padding-left: 0; 339 | padding-right: 0; 340 | padding-top: 0; 341 | margin-bottom: 1.45rem; 342 | list-style-position: outside; 343 | list-style-image: none; 344 | } 345 | ol { 346 | margin-left: 1.45rem; 347 | margin-right: 0; 348 | margin-top: 0; 349 | padding-bottom: 0; 350 | padding-left: 0; 351 | padding-right: 0; 352 | padding-top: 0; 353 | margin-bottom: 1.45rem; 354 | list-style-position: outside; 355 | list-style-image: none; 356 | } 357 | dl { 358 | margin-left: 0; 359 | margin-right: 0; 360 | margin-top: 0; 361 | padding-bottom: 0; 362 | padding-left: 0; 363 | padding-right: 0; 364 | padding-top: 0; 365 | margin-bottom: 1.45rem; 366 | } 367 | dd { 368 | margin-left: 0; 369 | margin-right: 0; 370 | margin-top: 0; 371 | padding-bottom: 0; 372 | padding-left: 0; 373 | padding-right: 0; 374 | padding-top: 0; 375 | margin-bottom: 1.45rem; 376 | } 377 | p { 378 | margin-left: 0; 379 | margin-right: 0; 380 | margin-top: 0; 381 | padding-bottom: 0; 382 | padding-left: 0; 383 | padding-right: 0; 384 | padding-top: 0; 385 | margin-bottom: 1.45rem; 386 | } 387 | figure { 388 | margin-left: 0; 389 | margin-right: 0; 390 | margin-top: 0; 391 | padding-bottom: 0; 392 | padding-left: 0; 393 | padding-right: 0; 394 | padding-top: 0; 395 | margin-bottom: 1.45rem; 396 | } 397 | pre { 398 | margin-left: 0; 399 | margin-right: 0; 400 | margin-top: 0; 401 | margin-bottom: 1.45rem; 402 | font-size: 0.85rem; 403 | line-height: 1.42; 404 | background: hsla(0, 0%, 0%, 0.04); 405 | border-radius: 3px; 406 | overflow: auto; 407 | word-wrap: normal; 408 | padding: 1.45rem; 409 | } 410 | table { 411 | margin-left: 0; 412 | margin-right: 0; 413 | margin-top: 0; 414 | padding-bottom: 0; 415 | padding-left: 0; 416 | padding-right: 0; 417 | padding-top: 0; 418 | margin-bottom: 1.45rem; 419 | font-size: 1rem; 420 | line-height: 1.45rem; 421 | border-collapse: collapse; 422 | width: 100%; 423 | } 424 | fieldset { 425 | margin-left: 0; 426 | margin-right: 0; 427 | margin-top: 0; 428 | padding-bottom: 0; 429 | padding-left: 0; 430 | padding-right: 0; 431 | padding-top: 0; 432 | margin-bottom: 1.45rem; 433 | } 434 | blockquote { 435 | margin-left: 1.45rem; 436 | margin-right: 1.45rem; 437 | margin-top: 0; 438 | padding-bottom: 0; 439 | padding-left: 0; 440 | padding-right: 0; 441 | padding-top: 0; 442 | margin-bottom: 1.45rem; 443 | } 444 | form { 445 | margin-left: 0; 446 | margin-right: 0; 447 | margin-top: 0; 448 | padding-bottom: 0; 449 | padding-left: 0; 450 | padding-right: 0; 451 | padding-top: 0; 452 | margin-bottom: 1.45rem; 453 | } 454 | noscript { 455 | margin-left: 0; 456 | margin-right: 0; 457 | margin-top: 0; 458 | padding-bottom: 0; 459 | padding-left: 0; 460 | padding-right: 0; 461 | padding-top: 0; 462 | margin-bottom: 1.45rem; 463 | } 464 | iframe { 465 | margin-left: 0; 466 | margin-right: 0; 467 | margin-top: 0; 468 | padding-bottom: 0; 469 | padding-left: 0; 470 | padding-right: 0; 471 | padding-top: 0; 472 | margin-bottom: 1.45rem; 473 | } 474 | hr { 475 | margin-left: 0; 476 | margin-right: 0; 477 | margin-top: 0; 478 | padding-bottom: 0; 479 | padding-left: 0; 480 | padding-right: 0; 481 | padding-top: 0; 482 | margin-bottom: calc(1.45rem - 1px); 483 | background: hsla(0, 0%, 0%, 0.2); 484 | border: none; 485 | height: 1px; 486 | } 487 | address { 488 | margin-left: 0; 489 | margin-right: 0; 490 | margin-top: 0; 491 | padding-bottom: 0; 492 | padding-left: 0; 493 | padding-right: 0; 494 | padding-top: 0; 495 | margin-bottom: 1.45rem; 496 | } 497 | b { 498 | font-weight: bold; 499 | } 500 | strong { 501 | font-weight: bold; 502 | } 503 | dt { 504 | font-weight: bold; 505 | } 506 | th { 507 | font-weight: bold; 508 | } 509 | li { 510 | margin-bottom: calc(1.45rem / 2); 511 | } 512 | ol li { 513 | padding-left: 0; 514 | } 515 | ul li { 516 | padding-left: 0; 517 | } 518 | li > ol { 519 | margin-left: 1.45rem; 520 | margin-bottom: calc(1.45rem / 2); 521 | margin-top: calc(1.45rem / 2); 522 | } 523 | li > ul { 524 | margin-left: 1.45rem; 525 | margin-bottom: calc(1.45rem / 2); 526 | margin-top: calc(1.45rem / 2); 527 | } 528 | blockquote *:last-child { 529 | margin-bottom: 0; 530 | } 531 | li *:last-child { 532 | margin-bottom: 0; 533 | } 534 | p *:last-child { 535 | margin-bottom: 0; 536 | } 537 | li > p { 538 | margin-bottom: calc(1.45rem / 2); 539 | } 540 | code { 541 | font-size: 0.85rem; 542 | line-height: 1.45rem; 543 | } 544 | kbd { 545 | font-size: 0.85rem; 546 | line-height: 1.45rem; 547 | } 548 | samp { 549 | font-size: 0.85rem; 550 | line-height: 1.45rem; 551 | } 552 | abbr { 553 | border-bottom: 1px dotted hsla(0, 0%, 0%, 0.5); 554 | cursor: help; 555 | } 556 | acronym { 557 | border-bottom: 1px dotted hsla(0, 0%, 0%, 0.5); 558 | cursor: help; 559 | } 560 | abbr[title] { 561 | border-bottom: 1px dotted hsla(0, 0%, 0%, 0.5); 562 | cursor: help; 563 | text-decoration: none; 564 | } 565 | thead { 566 | text-align: left; 567 | } 568 | td, 569 | th { 570 | text-align: left; 571 | border-bottom: 1px solid hsla(0, 0%, 0%, 0.12); 572 | font-feature-settings: "tnum"; 573 | -moz-font-feature-settings: "tnum"; 574 | -ms-font-feature-settings: "tnum"; 575 | -webkit-font-feature-settings: "tnum"; 576 | padding-left: 0.96667rem; 577 | padding-right: 0.96667rem; 578 | padding-top: 0.725rem; 579 | padding-bottom: calc(0.725rem - 1px); 580 | } 581 | th:first-child, 582 | td:first-child { 583 | padding-left: 0; 584 | } 585 | th:last-child, 586 | td:last-child { 587 | padding-right: 0; 588 | } 589 | tt, 590 | code { 591 | background-color: hsla(0, 0%, 0%, 0.04); 592 | border-radius: 3px; 593 | font-family: "SFMono-Regular", Consolas, "Roboto Mono", "Droid Sans Mono", 594 | "Liberation Mono", Menlo, Courier, monospace; 595 | padding: 0; 596 | padding-top: 0.2em; 597 | padding-bottom: 0.2em; 598 | } 599 | pre code { 600 | background: none; 601 | line-height: 1.42; 602 | } 603 | code:before, 604 | code:after, 605 | tt:before, 606 | tt:after { 607 | letter-spacing: -0.2em; 608 | content: " "; 609 | } 610 | pre code:before, 611 | pre code:after, 612 | pre tt:before, 613 | pre tt:after { 614 | content: ""; 615 | } 616 | @media only screen and (max-width: 480px) { 617 | html { 618 | font-size: 100%; 619 | } 620 | } 621 | -------------------------------------------------------------------------------- /example/src/components/layout.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import PropTypes from "prop-types" 3 | import { StaticQuery, graphql } from "gatsby" 4 | 5 | import Header from "./header" 6 | import "./layout.css" 7 | 8 | const Layout = ({ children }) => ( 9 | ( 20 | <> 21 |
22 |
30 |
{children}
31 |
32 | © {new Date().getFullYear()}, Built with 33 | {` `} 34 | Gatsby 35 |
36 |
37 | 38 | )} 39 | /> 40 | ) 41 | 42 | Layout.propTypes = { 43 | children: PropTypes.node.isRequired, 44 | } 45 | 46 | export default Layout 47 | -------------------------------------------------------------------------------- /example/src/components/seo.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import PropTypes from "prop-types" 3 | import Helmet from "react-helmet" 4 | import { StaticQuery, graphql } from "gatsby" 5 | 6 | function SEO({ description, lang, meta, keywords, title }) { 7 | return ( 8 | { 11 | const metaDescription = 12 | description || data.site.siteMetadata.description 13 | return ( 14 | 0 56 | ? { 57 | name: `keywords`, 58 | content: keywords.join(`, `), 59 | } 60 | : [] 61 | ) 62 | .concat(meta)} 63 | /> 64 | ) 65 | }} 66 | /> 67 | ) 68 | } 69 | 70 | SEO.defaultProps = { 71 | lang: `en`, 72 | meta: [], 73 | keywords: [], 74 | } 75 | 76 | SEO.propTypes = { 77 | description: PropTypes.string, 78 | lang: PropTypes.string, 79 | meta: PropTypes.array, 80 | keywords: PropTypes.arrayOf(PropTypes.string), 81 | title: PropTypes.string.isRequired, 82 | } 83 | 84 | export default SEO 85 | 86 | const detailsQuery = graphql` 87 | query DefaultSEOQuery { 88 | site { 89 | siteMetadata { 90 | title 91 | description 92 | author 93 | } 94 | } 95 | } 96 | ` 97 | -------------------------------------------------------------------------------- /example/src/images/gatsby-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benjaminhoffman/gatsby-plugin-segment-js/efa49a609b0f08e0997180ed87e3fd727e6521e8/example/src/images/gatsby-icon.png -------------------------------------------------------------------------------- /example/src/pages/404.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | 3 | import Layout from "../components/layout" 4 | import SEO from "../components/seo" 5 | 6 | const NotFoundPage = () => ( 7 | 8 | 9 |

NOT FOUND

10 |

You just hit a route that doesn't exist... the sadness.

11 |
12 | ) 13 | 14 | export default NotFoundPage 15 | -------------------------------------------------------------------------------- /example/src/pages/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Link } from "gatsby"; 3 | 4 | import Layout from "../components/layout"; 5 | import SEO from "../components/seo"; 6 | 7 | class IndexPage extends React.Component { 8 | _handleClick() { 9 | // Invoke `window.analytics.track` and send any 10 | // add'l properties you'd like to send 11 | window.analytics.track("Track Event Fired", { 12 | plugin: "gatsby-plugin-segment", 13 | author: "Ben Hoffman", 14 | href: "https://github.com/benjaminhoffman/gatsby-plugin-segment" 15 | }); 16 | 17 | console.log(` 18 | Check your network tab of your dev tools... 19 | you should see the track call there. And if 20 | you put in the correct API keys, you should also 21 | see the track event show up in your Segment account. 22 | `); 23 | } 24 | 25 | render() { 26 | return ( 27 | 28 | 29 | {/* Here is where we invoke our Segment track event 30 | via a click handler on Gatsby's component */} 31 |

32 | 33 | Click here 34 | {" "} 35 | to see a track event 36 |

37 | Go to page 2 38 |
39 |
40 | Check full page refresh 41 |
42 |
43 | ); 44 | } 45 | } 46 | 47 | export default IndexPage; 48 | -------------------------------------------------------------------------------- /example/src/pages/page-2.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import { Link } from "gatsby" 3 | 4 | import Layout from "../components/layout" 5 | import SEO from "../components/seo" 6 | 7 | const SecondPage = () => ( 8 | 9 | 10 |

Hi from the second page

11 |

Welcome to page 2

12 | Go back to the homepage 13 |
14 | ) 15 | 16 | export default SecondPage 17 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | // noop -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gatsby-plugin-segment-js", 3 | "description": "Easily add Segment JS snippet to your website", 4 | "author": "benjamin hoffman <6520022+benjaminhoffman@users.noreply.github.com>", 5 | "license": "MIT", 6 | "version": "5.0.0", 7 | "main": "gatsby-ssr.js", 8 | "homepage": "https://github.com/benjaminhoffman/gatsby-plugin-segment-js#readme", 9 | "repository": "git@github.com:benjaminhoffman/gatsby-plugin-segment-js.git", 10 | "bugs": { 11 | "url": "https://github.com/benjaminhoffman/gatsby-plugin-segment-js/issues" 12 | }, 13 | "keywords": [ 14 | "segment", 15 | "segment analytics", 16 | "segment gatsby", 17 | "segment javascript", 18 | "analytics.js", 19 | "gatsby", 20 | "gatsby segment", 21 | "gatsby-plugin", 22 | "gatsby-plugin segment" 23 | ], 24 | "scripts": { 25 | "build": "babel src --out-dir . --ignore tests", 26 | "build:watch": "yarn build -w", 27 | "prepare": "yarn build", 28 | "build-n-pack": "yarn build && yarn pack --filename ./gatsby-plugin-segment-js.tgz" 29 | }, 30 | "peerDependencies": { 31 | "gatsby": "^2 || ^3 || ^4 || ^5" 32 | }, 33 | "devDependencies": { 34 | "babel-cli": "^6.24.1", 35 | "babel-preset-env": "^1.6.1", 36 | "babel-preset-react": "^6.24.1", 37 | "eslint": "^7.32.0", 38 | "eslint-plugin-mocha": "^9", 39 | "eslint-plugin-react": "^7.24.0" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/gatsby-browser.js: -------------------------------------------------------------------------------- 1 | // https://www.gatsbyjs.com/docs/reference/config-files/gatsby-browser/ 2 | exports.onRouteUpdate = ({ prevLocation }, { 3 | // For now this option is SSR only 4 | // delayLoad = false, 5 | // For now this option is SSR only 6 | // delayLoadDelay = 1000, 7 | trackPage = true, 8 | // For now this option is SSR only 9 | // trackOnlyIfLoaded = false, 10 | // For now this option is SSR only 11 | // trackPageOnlyIfReady = false, 12 | // For now, trackPageImmediately should ONLY be a thing that's used in the 13 | // SSR code, while trackPageOnRouteUpdate is more for the Broser code 14 | // trackPageImmediately = true, 15 | trackPageOnRouteUpdate = true, 16 | trackPageOnRouteUpdateDelay = 50, 17 | delayLoadUntilActivity = false, 18 | delayLoadUntilActivityAdditionalDelay = 0, 19 | includeTitleInTrackPage, 20 | trackPageWithTitle = false, 21 | }) => { 22 | 23 | if (typeof includeTitleInTrackPage === 'boolean') { 24 | console.warn('WARNING: option for gatsby-plugin-segment "includeTitleInTrackPage" is deprecated. Please use "trackPageWithTitle" instead.') 25 | trackPageWithTitle = includeTitleInTrackPage 26 | } 27 | 28 | // If this is meant to be responsible for calling "load", then let's do it 29 | // and maybe also track the page once loading is done. 30 | if (prevLocation && delayLoadUntilActivity) { 31 | const additionalLoadDelay = Math.max(0, delayLoadUntilActivityAdditionalDelay || 0) 32 | if (additionalLoadDelay) { 33 | const trackPageCb = () => { 34 | // Just maybe track the page. Since we've already delayed, don't delay more. 35 | return trackPageFn(additionalLoadDelay) 36 | } 37 | 38 | setTimeout( 39 | function () { 40 | const callbackWasQueued = loaderCallback(trackPageCb) 41 | if (!callbackWasQueued) { 42 | trackPageCb() 43 | } 44 | }, 45 | additionalLoadDelay, 46 | ) 47 | } else { 48 | const callbackWasQueued = loaderCallback(trackPageFn) 49 | if (!callbackWasQueued) { 50 | // Just maybe track the page 51 | trackPageFn() 52 | } 53 | } 54 | 55 | return 56 | } 57 | 58 | // Just maybe track the page 59 | trackPageFn() 60 | return 61 | 62 | function pageviewCallback () { 63 | if (window.gatsbyPluginSegmentPageviewCaller) { 64 | window.gatsbyPluginSegmentPageviewCaller(); 65 | } else if (window.analytics) { 66 | window.analytics.page(trackPageWithTitle ? document.title : undefined); 67 | } 68 | } 69 | 70 | // "page" if necessary. delaoyed if necessary 71 | function trackPageFn (alreadyDelayedBy = 0) { 72 | // Do we actually want to track the page? 73 | if (!(trackPage && trackPageOnRouteUpdate)) { 74 | return 75 | } 76 | 77 | // Adding a delay (defaults to 50ms when not provided by plugin option `trackPageDelay`) 78 | // helps to ensure that the segment route tracking is in sync with the actual Gatsby route. 79 | // Otherwise you can end up in a state where the Segment page tracking reports 80 | // the previous page on route change. 81 | const delay = Math.max(0, trackPageOnRouteUpdateDelay || 0) - alreadyDelayedBy 82 | if (delay > 0) { 83 | setTimeout(pageviewCallback, delay) 84 | } else { 85 | pageviewCallback() 86 | } 87 | } 88 | 89 | // "load" then "page" if necessary 90 | function loaderCallback (cb) { 91 | if (window.gatsbyPluginSegmentLoader) { 92 | window.gatsbyPluginSegmentLoader(cb); 93 | return true; 94 | } 95 | } 96 | }; 97 | -------------------------------------------------------------------------------- /src/gatsby-ssr.js: -------------------------------------------------------------------------------- 1 | // https://www.gatsbyjs.com/docs/reference/config-files/gatsby-ssr/ 2 | import React from "react"; 3 | 4 | export function onRenderBody({ setHeadComponents }, pluginOptions) { 5 | const { 6 | prodKey, 7 | devKey, 8 | host = "https://cdn.segment.io", 9 | trackPage = true, 10 | trackPageImmediately = true, 11 | trackPageOnlyIfReady = false, 12 | // This is ONLY for the Browser side code 13 | // trackPageOnRouteUpdate = true, 14 | // trackPageOnRouteUpdateDelay = 50, 15 | includeTitleInTrackPage, 16 | manualLoad = false, 17 | delayLoad = false, 18 | delayLoadDelay = 1000, 19 | delayLoadUntilActivity = false, 20 | delayLoadUntilActivityAdditionalDelay = 0, 21 | customSnippet, 22 | } = pluginOptions; 23 | 24 | let { 25 | trackPageWithTitle = false, 26 | } = pluginOptions; 27 | 28 | if (typeof includeTitleInTrackPage === 'boolean') { 29 | console.warn('WARNING: option for gatsby-plugin-segment "includeTitleInTrackPage" is deprecated. Please use "trackPageWithTitle" instead.') 30 | trackPageWithTitle = includeTitleInTrackPage 31 | } 32 | 33 | // ensures Segment write key is present 34 | if (!prodKey || prodKey.length < 10) 35 | console.error("segment prodKey must be at least 10 char in length"); 36 | 37 | // if dev key is present, ensures it is at least 10 car in length 38 | if (devKey && devKey.length < 10) 39 | console.error("if present, devKey must be at least 10 char in length"); 40 | 41 | // use prod write key when in prod env, else use dev write key 42 | // note below, snippet wont render unless writeKey is truthy 43 | const writeKey = process.env.NODE_ENV === "production" ? prodKey : devKey; 44 | 45 | const loadImmediately = !(delayLoad || delayLoadUntilActivity || manualLoad); 46 | const reallyTrackPageImmediately = trackPageImmediately && trackPage 47 | 48 | const idempotentPageviewCode = `(function () { 49 | let lastPageviewPath; 50 | window.gatsbyPluginSegmentPageviewCaller = function () { 51 | if (!window.analytics) { 52 | return 53 | }${ trackPageOnlyIfReady ? ` 54 | if (!gatsbyPluginSegmentReady) { 55 | return 56 | }` : ''} 57 | 58 | let thisPageviewPath = window.location.pathname; 59 | if (thisPageviewPath === lastPageviewPath) { 60 | return 61 | } 62 | lastPageviewPath = thisPageviewPath; 63 | window.analytics.page(${ trackPageWithTitle ? 'document.title' : ''}); 64 | }; 65 | })();`; 66 | 67 | let delayedLoadingCode = `(function () { 68 | let segmentLoaded = false; 69 | let segmentLoading = false; 70 | const callbacks = []; 71 | function safeExecCallback (cb) { 72 | if (typeof cb === "function") { 73 | cb(); 74 | } 75 | } 76 | 77 | window.gatsbyPluginSegmentLoader = function (cb) { 78 | if (segmentLoaded) { 79 | safeExecCallback(cb); 80 | return; 81 | } 82 | 83 | callbacks.push(cb); 84 | if (segmentLoading) { 85 | return; 86 | } 87 | 88 | segmentLoading = true; 89 | 90 | function loader() { 91 | if (typeof window.analytics.load !== "function") { 92 | console.error("Gatsby Plugin Segment: analytics.load is not a function. Has it already been called?"); 93 | return 94 | } 95 | gatsbySegmentLoad('${writeKey}'); 96 | segmentLoading = false; 97 | segmentLoaded = true; 98 | let cb; 99 | while ((cb = callbacks.pop()) != null) { 100 | safeExecCallback(cb); 101 | }${reallyTrackPageImmediately ? ` 102 | window.gatsbyPluginSegmentPageviewCaller();` : ''} 103 | }; 104 | 105 | if ("requestIdleCallback" in window) { 106 | requestIdleCallback(loader); 107 | } else { 108 | loader(); 109 | } 110 | };` 111 | if (delayLoadUntilActivity) { 112 | delayedLoadingCode += ` 113 | window.addEventListener( 114 | "scroll", 115 | function () { 116 | setTimeout( 117 | function () { 118 | window.gatsbyPluginSegmentLoader && window.gatsbyPluginSegmentLoader(); 119 | }, 120 | ${Math.max(0, delayLoadUntilActivityAdditionalDelay || 0)}, 121 | ) 122 | }, 123 | { once: true }, 124 | );` 125 | } 126 | if (delayLoad) { 127 | delayedLoadingCode += ` 128 | setTimeout( 129 | function () { 130 | window.gatsbyPluginSegmentLoader && window.gatsbyPluginSegmentLoader(); 131 | }, 132 | ${delayLoadDelay || 1000}, 133 | );` 134 | } 135 | delayedLoadingCode += ` 136 | })();`; 137 | 138 | let snippet 139 | if (customSnippet) { 140 | snippet = eval('`' + customSnippet + '`') 141 | snippet += ` 142 | window.analytics.ready(function () { 143 | gatsbyPluginSegmentReady = true; 144 | }) 145 | ` 146 | } else { 147 | // Segment's snippet (version 4.15.3) 148 | // If this is updated, probably good to update the README to include the version 149 | snippet = `(function(){var analytics=window.analytics=window.analytics||[];if(!analytics.initialize)if(analytics.invoked)window.console&&console.error&&console.error("Segment snippet included twice.");else{analytics.invoked=!0;analytics.methods=["trackSubmit","trackClick","trackLink","trackForm","pageview","identify","reset","group","track","ready","alias","debug","page","once","off","on","addSourceMiddleware","addIntegrationMiddleware","setAnonymousId","addDestinationMiddleware"];analytics.factory=function(e){return function(){var t=Array.prototype.slice.call(arguments);t.unshift(e);analytics.push(t);return analytics}};for(var e=0;e, 188 | ]); 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /tests/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: '../.eslintrc.js', 3 | env: { 4 | mocha: true 5 | }, 6 | plugins: [ 7 | 'mocha', 8 | ], 9 | rules: { 10 | 'mocha/no-skipped-tests': 'error', 11 | 'mocha/no-exclusive-tests': 'error', 12 | }, 13 | globals: { 14 | expect: 'readonly', 15 | pathToProject: 'readonly' 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tests/gatsby-2/gatsby-config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Configure your Gatsby site with this file. 3 | * 4 | * See: https://www.gatsbyjs.com/docs/gatsby-config/ 5 | */ 6 | 7 | module.exports = { 8 | /* Your site config here */ 9 | plugins: [ 10 | { 11 | resolve: `gatsby-plugin-segment-js`, 12 | options: { 13 | prodKey: `ADD_API_KEY_PROD`, 14 | devKey: `ADD_API_KEY_DEV`, 15 | trackPage: true 16 | } 17 | }, 18 | ], 19 | } 20 | -------------------------------------------------------------------------------- /tests/gatsby-2/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gatsby-plugin-segment-js-test-gatsby-2", 3 | "private": true, 4 | "description": "A simple Gatsby app to test the plugin with Gatsby 2", 5 | "version": "0.1.0", 6 | "license": "0BSD", 7 | "scripts": { 8 | "build": "gatsby build", 9 | "develop": "gatsby develop", 10 | "clean": "gatsby clean", 11 | "plugin": "yarn plugin:build && yarn plugin:install", 12 | "plugin:build": "yarn --cwd ../.. build-n-pack", 13 | "plugin:remove": "yarn remove gatsby-plugin-segment-js", 14 | "plugin:install": "yarn add ../../gatsby-plugin-segment-js.tgz", 15 | "test": "yarn test:prepare && yarn test:run", 16 | "test:prepare": "yarn plugin && yarn clean && yarn build", 17 | "test:run": "mocha --config ./test/mocha-config.js" 18 | }, 19 | "dependencies": { 20 | "gatsby": "^2", 21 | "gatsby-plugin-segment-js": "../../gatsby-plugin-segment-js.tgz", 22 | "react": "^16.12.0", 23 | "react-dom": "^16.12.0" 24 | }, 25 | "devDependencies": { 26 | "chai": "^4.3.4", 27 | "mocha": "^9.1.3" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/gatsby-2/src/pages/index.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | 3 | export default function Home() { 4 | return
Hello world!
5 | } 6 | -------------------------------------------------------------------------------- /tests/gatsby-2/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benjaminhoffman/gatsby-plugin-segment-js/efa49a609b0f08e0997180ed87e3fd727e6521e8/tests/gatsby-2/static/favicon.ico -------------------------------------------------------------------------------- /tests/gatsby-2/test/mocha-config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | diff: true, 3 | delay: false, 4 | extension: ['js'], 5 | package: './package.json', 6 | reporter: 'spec', 7 | slow: 75, 8 | timeout: 2000, 9 | spec: [ 10 | './test/**/*.test.js', 11 | '../shared/**/*.test.js', 12 | ], 13 | require: [ 14 | // https://mochajs.org/#-require-module-r-module 15 | // '@babel/register', 16 | './test/mocha-environment.js', 17 | ], 18 | file: './test/mocha-setup.js', 19 | exit: true, 20 | } 21 | -------------------------------------------------------------------------------- /tests/gatsby-2/test/mocha-environment.js: -------------------------------------------------------------------------------- 1 | const chai = require('chai') 2 | 3 | global.chai = chai 4 | global.expect = chai.expect 5 | -------------------------------------------------------------------------------- /tests/gatsby-2/test/mocha-setup.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | // All the tests will be the same, so we'll share them. But we need to tell it where 4 | // to look for things 5 | global.pathToProject = path.resolve(__dirname, '../') 6 | -------------------------------------------------------------------------------- /tests/gatsby-3/gatsby-config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Configure your Gatsby site with this file. 3 | * 4 | * See: https://www.gatsbyjs.com/docs/gatsby-config/ 5 | */ 6 | 7 | module.exports = { 8 | /* Your site config here */ 9 | plugins: [ 10 | { 11 | resolve: `gatsby-plugin-segment-js`, 12 | options: { 13 | prodKey: `ADD_API_KEY_PROD`, 14 | devKey: `ADD_API_KEY_DEV`, 15 | trackPage: true 16 | } 17 | }, 18 | ], 19 | } 20 | -------------------------------------------------------------------------------- /tests/gatsby-3/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gatsby-plugin-segment-js-test-gatsby-3", 3 | "private": true, 4 | "description": "A simple Gatsby app to test the plugin with Gatsby 3", 5 | "version": "0.1.0", 6 | "license": "0BSD", 7 | "scripts": { 8 | "build": "gatsby build", 9 | "develop": "gatsby develop", 10 | "clean": "gatsby clean", 11 | "plugin": "yarn plugin:build && yarn plugin:install", 12 | "plugin:build": "yarn --cwd ../.. build-n-pack", 13 | "plugin:remove": "yarn remove gatsby-plugin-segment-js", 14 | "plugin:install": "yarn add ../../gatsby-plugin-segment-js.tgz", 15 | "test": "yarn test:prepare && yarn test:run", 16 | "test:prepare": "yarn plugin && yarn clean && yarn build", 17 | "test:run": "mocha --config ./test/mocha-config.js" 18 | }, 19 | "dependencies": { 20 | "gatsby": "^3", 21 | "gatsby-plugin-segment-js": "../../gatsby-plugin-segment-js.tgz", 22 | "react": "^17.0.1", 23 | "react-dom": "^17.0.1" 24 | }, 25 | "devDependencies": { 26 | "chai": "^4.3.4", 27 | "mocha": "^9.1.3" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/gatsby-3/src/pages/index.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | 3 | export default function Home() { 4 | return
Hello world!
5 | } 6 | -------------------------------------------------------------------------------- /tests/gatsby-3/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benjaminhoffman/gatsby-plugin-segment-js/efa49a609b0f08e0997180ed87e3fd727e6521e8/tests/gatsby-3/static/favicon.ico -------------------------------------------------------------------------------- /tests/gatsby-3/test/mocha-config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | diff: true, 3 | delay: false, 4 | extension: ['js'], 5 | package: './package.json', 6 | reporter: 'spec', 7 | slow: 75, 8 | timeout: 2000, 9 | spec: [ 10 | './test/**/*.test.js', 11 | '../shared/**/*.test.js', 12 | ], 13 | require: [ 14 | // https://mochajs.org/#-require-module-r-module 15 | // '@babel/register', 16 | './test/mocha-environment.js', 17 | ], 18 | file: './test/mocha-setup.js', 19 | exit: true, 20 | } 21 | -------------------------------------------------------------------------------- /tests/gatsby-3/test/mocha-environment.js: -------------------------------------------------------------------------------- 1 | const chai = require('chai') 2 | 3 | global.chai = chai 4 | global.expect = chai.expect 5 | -------------------------------------------------------------------------------- /tests/gatsby-3/test/mocha-setup.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | // All the tests will be the same, so we'll share them. But we need to tell it where 4 | // to look for things 5 | global.pathToProject = path.resolve(__dirname, '../') 6 | -------------------------------------------------------------------------------- /tests/gatsby-4/gatsby-config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Configure your Gatsby site with this file. 3 | * 4 | * See: https://www.gatsbyjs.com/docs/gatsby-config/ 5 | */ 6 | 7 | module.exports = { 8 | /* Your site config here */ 9 | plugins: [ 10 | { 11 | resolve: `gatsby-plugin-segment-js`, 12 | options: { 13 | prodKey: `ADD_API_KEY_PROD`, 14 | devKey: `ADD_API_KEY_DEV`, 15 | trackPage: true 16 | } 17 | }, 18 | ], 19 | } 20 | -------------------------------------------------------------------------------- /tests/gatsby-4/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gatsby-plugin-segment-js-test-gatsby-4", 3 | "private": true, 4 | "description": "A simple Gatsby app to test the plugin with Gatsby 4", 5 | "version": "0.1.0", 6 | "license": "0BSD", 7 | "scripts": { 8 | "build": "gatsby build", 9 | "develop": "gatsby develop", 10 | "clean": "gatsby clean", 11 | "plugin": "yarn plugin:build && yarn plugin:install", 12 | "plugin:build": "yarn --cwd ../.. build-n-pack", 13 | "plugin:remove": "yarn remove gatsby-plugin-segment-js", 14 | "plugin:install": "yarn add ../../gatsby-plugin-segment-js.tgz", 15 | "test": "yarn test:prepare && yarn test:run", 16 | "test:prepare": "yarn plugin && yarn clean && yarn build", 17 | "test:run": "mocha --config ./test/mocha-config.js" 18 | }, 19 | "dependencies": { 20 | "gatsby": "^4", 21 | "gatsby-plugin-segment-js": "../../gatsby-plugin-segment-js.tgz", 22 | "react": "^17.0.1", 23 | "react-dom": "^17.0.1" 24 | }, 25 | "devDependencies": { 26 | "chai": "^4.3.4", 27 | "mocha": "^9.1.3" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/gatsby-4/src/pages/index.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | 3 | export default function Home() { 4 | return
Hello world!
5 | } 6 | -------------------------------------------------------------------------------- /tests/gatsby-4/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benjaminhoffman/gatsby-plugin-segment-js/efa49a609b0f08e0997180ed87e3fd727e6521e8/tests/gatsby-4/static/favicon.ico -------------------------------------------------------------------------------- /tests/gatsby-4/test/mocha-config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | diff: true, 3 | delay: false, 4 | extension: ['js'], 5 | package: './package.json', 6 | reporter: 'spec', 7 | slow: 75, 8 | timeout: 2000, 9 | spec: [ 10 | './test/**/*.test.js', 11 | '../shared/**/*.test.js', 12 | ], 13 | require: [ 14 | // https://mochajs.org/#-require-module-r-module 15 | // '@babel/register', 16 | './test/mocha-environment.js', 17 | ], 18 | file: './test/mocha-setup.js', 19 | exit: true, 20 | } 21 | -------------------------------------------------------------------------------- /tests/gatsby-4/test/mocha-environment.js: -------------------------------------------------------------------------------- 1 | const chai = require('chai') 2 | 3 | global.chai = chai 4 | global.expect = chai.expect 5 | -------------------------------------------------------------------------------- /tests/gatsby-4/test/mocha-setup.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | // All the tests will be the same, so we'll share them. But we need to tell it where 4 | // to look for things 5 | global.pathToProject = path.resolve(__dirname, '../') 6 | -------------------------------------------------------------------------------- /tests/gatsby-5/gatsby-config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Configure your Gatsby site with this file. 3 | * 4 | * See: https://www.gatsbyjs.com/docs/gatsby-config/ 5 | */ 6 | 7 | module.exports = { 8 | /* Your site config here */ 9 | plugins: [ 10 | { 11 | resolve: `gatsby-plugin-segment-js`, 12 | options: { 13 | prodKey: `ADD_API_KEY_PROD`, 14 | devKey: `ADD_API_KEY_DEV`, 15 | trackPage: true 16 | } 17 | }, 18 | ], 19 | } 20 | -------------------------------------------------------------------------------- /tests/gatsby-5/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gatsby-plugin-segment-js-test-gatsby-5", 3 | "private": true, 4 | "description": "A simple Gatsby app to test the plugin with Gatsby 5", 5 | "version": "0.1.0", 6 | "license": "0BSD", 7 | "scripts": { 8 | "build": "gatsby build", 9 | "develop": "gatsby develop", 10 | "clean": "gatsby clean", 11 | "plugin": "yarn plugin:build && yarn plugin:install", 12 | "plugin:build": "yarn --cwd ../.. build-n-pack", 13 | "plugin:remove": "yarn remove gatsby-plugin-segment-js", 14 | "plugin:install": "yarn add ../../gatsby-plugin-segment-js.tgz", 15 | "test": "yarn test:prepare && yarn test:run", 16 | "test:prepare": "yarn plugin && yarn clean && yarn build", 17 | "test:run": "mocha --config ./test/mocha-config.js" 18 | }, 19 | "dependencies": { 20 | "gatsby": "^5", 21 | "gatsby-plugin-segment-js": "../../gatsby-plugin-segment-js.tgz", 22 | "react": "^18.0.0", 23 | "react-dom": "^18.0.0" 24 | }, 25 | "devDependencies": { 26 | "chai": "^4.3.4", 27 | "mocha": "^9.1.3" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/gatsby-5/src/pages/index.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | 3 | export default function Home() { 4 | return
Hello world!
5 | } 6 | -------------------------------------------------------------------------------- /tests/gatsby-5/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benjaminhoffman/gatsby-plugin-segment-js/efa49a609b0f08e0997180ed87e3fd727e6521e8/tests/gatsby-5/static/favicon.ico -------------------------------------------------------------------------------- /tests/gatsby-5/test/mocha-config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | diff: true, 3 | delay: false, 4 | extension: ['js'], 5 | package: './package.json', 6 | reporter: 'spec', 7 | slow: 75, 8 | timeout: 2000, 9 | spec: [ 10 | './test/**/*.test.js', 11 | '../shared/**/*.test.js', 12 | ], 13 | require: [ 14 | // https://mochajs.org/#-require-module-r-module 15 | // '@babel/register', 16 | './test/mocha-environment.js', 17 | ], 18 | file: './test/mocha-setup.js', 19 | exit: true, 20 | } 21 | -------------------------------------------------------------------------------- /tests/gatsby-5/test/mocha-environment.js: -------------------------------------------------------------------------------- 1 | const chai = require('chai') 2 | 3 | global.chai = chai 4 | global.expect = chai.expect 5 | -------------------------------------------------------------------------------- /tests/gatsby-5/test/mocha-setup.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | // All the tests will be the same, so we'll share them. But we need to tell it where 4 | // to look for things 5 | global.pathToProject = path.resolve(__dirname, '../') 6 | -------------------------------------------------------------------------------- /tests/shared/tests.test.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const fs = require('fs') 3 | 4 | const pathToPublic = path.resolve(pathToProject, 'public') 5 | const fileNames = fs.readdirSync(pathToPublic) 6 | 7 | const indexFilePath = path.resolve(pathToPublic, 'index.html') 8 | 9 | const appJsFileName = fileNames.find((fileName) => fileName.startsWith('app-') && fileName.endsWith('.js')) 10 | if (!appJsFileName) { 11 | throw Error('Could not find app-*.js filename') 12 | } 13 | const appJsFilePath = path.resolve(pathToPublic, appJsFileName) 14 | 15 | const appJsMapFileName = fileNames.find((fileName) => fileName.startsWith('app-') && fileName.endsWith('.js.map')) 16 | if (!appJsMapFileName) { 17 | throw Error('Could not find app-*.js.map filename') 18 | } 19 | const appJsMapFilePath = path.resolve(pathToPublic, appJsMapFileName) 20 | 21 | describe('Code is there', function () { 22 | it('has snippet in the ', function () { 23 | const indexText = fs.readFileSync(indexFilePath).toString() 24 | const indexOfHeadOpen = indexText.indexOf('') 25 | const indexOfHeadClose = indexText.indexOf('') 26 | const indexOfFirstThing = indexText.indexOf('analytics._writeKey="ADD_API_KEY_PROD";') 27 | expect(indexOfFirstThing).to.be.gt(indexOfHeadOpen) 28 | expect(indexOfFirstThing).to.be.lt(indexOfHeadClose) 29 | 30 | expect(indexText).to.include('analytics._writeKey="ADD_API_KEY_PROD";') 31 | expect(indexText).to.include("gatsbySegmentLoad('ADD_API_KEY_PROD');") 32 | expect(indexText).to.include('analytics.SNIPPET_VERSION="4.15.3"') 33 | expect(indexText).to.include('analytics.page();') 34 | 35 | const indexOfLastThing = indexText.indexOf('analytics.page();') 36 | expect(indexOfLastThing).to.be.gt(indexOfHeadOpen) 37 | expect(indexOfLastThing).to.be.lt(indexOfHeadClose) 38 | }) 39 | 40 | it('has segment code in app-*.js', function () { 41 | const jsText = fs.readFileSync(appJsFilePath).toString() 42 | expect(jsText).to.include('window.gatsbyPluginSegmentLoader(') 43 | }) 44 | 45 | it('has segment code in app-*.js.map', function () { 46 | const jsMapText = fs.readFileSync(appJsMapFilePath).toString() 47 | expect( 48 | compactString(jsMapText) 49 | ) 50 | .to.include( 51 | compactString( 52 | String.raw` function trackPageFn() {\n var alreadyDelayedBy = ` 53 | ) 54 | ) 55 | }) 56 | }) 57 | 58 | 59 | function compactString(str) { 60 | return str.replace(/\\n/g,'').replace(/\s+/g,'') 61 | } --------------------------------------------------------------------------------