Skip to content

Commit

Permalink
Support multiple entry points with configurable HTML tags (#398)
Browse files Browse the repository at this point in the history
  • Loading branch information
pswies authored Mar 29, 2024
1 parent b932c9e commit 78815ce
Show file tree
Hide file tree
Showing 11 changed files with 317 additions and 208 deletions.
32 changes: 32 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,38 @@ Set environment variables via `.env`.
- `STATUS_PAGE_SCRIPT_URI` (optional): Used for enabling the status page; used with `pnpm run build:inject-statuspage`.
- `SMARTBANNER_APP_NAME`, `SMARTBANNER_ORG_NAME`, `SMARTBANNER_ICON_URL`, `SMARTBANNER_APPSTORE_URL` (optional): Used for enabling the smart app banner; used with `pnpm run build:inject-smartbanner`.

## Part 5: Configure entry points

### HTML files

Edit `scripts/generate-entry-points.js` and set up entry points according to your SEO needs. At least one entry point must be configured,
i.e. at least one element must be present in the `ENTRY_POINTS` array. This array consists of objects of the form:

```
{
title: 'Page title',
description: 'Page description.',
fileName: 'HTML entry point file name, e.g.: index.html',
},
```

The build script will traverse these entries and create files in `entry-points` directory, modifying the `template.html` file accordingly
for each entry. The `rollupOptions` config option in `vite.config.ts` informs the framework about the location of all the entry points
created above.

### Rewrite rules

Edit `vercel.json` and configure the `rewrites` configuration option. It is an array of objects of the form:

```
{
"source": "Regexp for matching the URL path, e.g.: /portfolio(/?.*)",
"destination": "Entry point file to use, e.g.: /entry-points/portfolio.html"
},
```

Note: The first matching rule takes precedence over anything defined afterwards in the array.

# Testing

## Unit testing
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,14 @@
},
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"build": "pnpm run build:generate-entry-points && tsc && vite build",
"build:inject-app-deeplinks": "sh scripts/inject-app-deeplinks.sh",
"build:inject-amplitude": "node scripts/inject-amplitude.js",
"build:inject-bugsnag": "node scripts/inject-bugsnag.js",
"build:inject-intercom": "node scripts/inject-intercom.js",
"build:inject-statuspage": "node scripts/inject-statuspage.js",
"build:inject-smartbanner": "node scripts/inject-smartbanner.js",
"build:generate-entry-points": "node scripts/generate-entry-points.js",
"deploy:ipfs": "node scripts/upload-ipfs.js --verbose",
"deploy:update-ipns": "node scripts/update-ipns.js",
"deploy:update-dnslink": "node scripts/update-dnslink.js",
Expand Down
32 changes: 32 additions & 0 deletions scripts/generate-entry-points.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import fs from 'fs/promises';
import path from 'path';
import { fileURLToPath } from 'url';

const currentPath = fileURLToPath(import.meta.url);
const projectRoot = path.dirname(currentPath);
const templateFilePath = path.resolve(projectRoot, '../template.html');
const entryPointsDir = path.resolve(projectRoot, '../entry-points');

const ENTRY_POINTS = [
{
title: 'dYdX',
description: 'dYdX',
fileName: 'index.html',
},
];

try {
fs.mkdir(entryPointsDir, { recursive: true });

for (const entryPoint of ENTRY_POINTS) {
const html = await fs.readFile(templateFilePath, 'utf-8');
const destinationFilePath = path.resolve(entryPointsDir, entryPoint.fileName);
const injectedHtml = html.replace(
'<title>dYdX</title>',
`<title>${entryPoint.title}</title>\n <meta name="description" content="${entryPoint.description}" />`
);
await fs.writeFile(destinationFilePath, injectedHtml, 'utf-8');
}
} catch (err) {
console.error('Error generating entry points:', err);
}
98 changes: 52 additions & 46 deletions scripts/inject-amplitude.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,56 +7,62 @@ const AMPLITUDE_SERVER_URL = process.env.AMPLITUDE_SERVER_URL;

const currentPath = fileURLToPath(import.meta.url);
const projectRoot = path.dirname(currentPath);
const htmlFilePath = path.resolve(projectRoot, '../dist/index.html');

if (AMPLITUDE_API_KEY) {
try {
const html = await fs.readFile(htmlFilePath, 'utf-8');

const amplitudeCdnScript = `<script type="text/javascript">
!function(){"use strict";!function(e,t){var n=e.amplitude||{_q:[],_iq:{}};if(n.invoked)e.console&&console.error&&console.error("Amplitude snippet has been loaded.");else{var r=function(e,t){e.prototype[t]=function(){return this._q.push({name:t,args:Array.prototype.slice.call(arguments,0)}),this}},s=function(e,t,n){return function(r){e._q.push({name:t,args:Array.prototype.slice.call(n,0),resolve:r})}},o=function(e,t,n){e[t]=function(){if(n)return{promise:new Promise(s(e,t,Array.prototype.slice.call(arguments)))}}},i=function(e){for(var t=0;t<m.length;t++)o(e,m[t],!1);for(var n=0;n<g.length;n++)o(e,g[n],!0)};n.invoked=!0;var u=t.createElement("script");u.type="text/javascript",u.integrity="sha384-BVo5ZjsjH373rWbcjz9Qjb2L6BgLwLADcZtZZPu3nMl8+7LPDhi1NcUEf0Ate41Y",u.crossOrigin="anonymous",u.async=!0,u.src="/libs/amplitude-analytics-browser-2.0.0-min.js",u.onload=function(){e.amplitude.runQueuedFunctions||console.log("[Amplitude] Error: could not load SDK")};var a=t.getElementsByTagName("script")[0];a.parentNode.insertBefore(u,a);for(var c=function(){return this._q=[],this},p=["add","append","clearAll","prepend","set","setOnce","unset","preInsert","postInsert","remove","getUserProperties"],l=0;l<p.length;l++)r(c,p[l]);n.Identify=c;for(var d=function(){return this._q=[],this},f=["getEventProperties","setProductId","setQuantity","setPrice","setRevenue","setRevenueType","setEventProperties"],v=0;v<f.length;v++)r(d,f[v]);n.Revenue=d;var m=["getDeviceId","setDeviceId","getSessionId","setSessionId","getUserId","setUserId","setOptOut","setTransport","reset","extendSession"],g=["init","add","remove","track","logEvent","identify","groupIdentify","setGroup","revenue","flush"];i(n),n.createInstance=function(e){return n._iq[e]={_q:[]},i(n._iq[e]),n._iq[e]},e.amplitude=n}}(window,document)}();
</script>
`;

const amplitudeListenerScript = `<script type="module">
!(function () {
var e = "${AMPLITUDE_API_KEY}";
e &&
(globalThis.amplitude.init(e${
AMPLITUDE_SERVER_URL
? `, undefined, {
serverUrl: "${AMPLITUDE_SERVER_URL}"
}`
: ''
}),
globalThis.amplitude.setOptOut(!1),
globalThis.addEventListener("dydx:track", function (e) {
var t = e.detail.eventType,
d = e.detail.eventData;
globalThis.amplitude.track(t, d);
}),
globalThis.addEventListener("dydx:identify", function (e) {
var t = e.detail.property,
d = e.detail.propertyValue;
if ("walletAddress" === t) globalThis.amplitude.setUserId(d);
else {
var i = new globalThis.amplitude.Identify();
i.set(t, d), globalThis.amplitude.identify(i);
}
}),
console.log("Amplitude enabled."));
})();
</script>`;

const injectedHtml = html.replace(
'<div id="root"></div>',
`<div id="root"></div>\n${amplitudeCdnScript}\n${amplitudeListenerScript}`
);

await fs.writeFile(htmlFilePath, injectedHtml, 'utf-8');

console.log('Amplitude scripts successfully injected.');
const files = await fs.readdir('entry-points');
for (const file of files) {
inject(file);
};
} catch (err) {
console.error('Error injecting Amplitude scripts:', err);
}
}

async function inject(fileName) {
const htmlFilePath = path.resolve(projectRoot, `../dist/entry-points/${fileName}`);
const html = await fs.readFile(htmlFilePath, 'utf-8');

const amplitudeCdnScript = `<script type="text/javascript">
!function(){"use strict";!function(e,t){var n=e.amplitude||{_q:[],_iq:{}};if(n.invoked)e.console&&console.error&&console.error("Amplitude snippet has been loaded.");else{var r=function(e,t){e.prototype[t]=function(){return this._q.push({name:t,args:Array.prototype.slice.call(arguments,0)}),this}},s=function(e,t,n){return function(r){e._q.push({name:t,args:Array.prototype.slice.call(n,0),resolve:r})}},o=function(e,t,n){e[t]=function(){if(n)return{promise:new Promise(s(e,t,Array.prototype.slice.call(arguments)))}}},i=function(e){for(var t=0;t<m.length;t++)o(e,m[t],!1);for(var n=0;n<g.length;n++)o(e,g[n],!0)};n.invoked=!0;var u=t.createElement("script");u.type="text/javascript",u.integrity="sha384-BVo5ZjsjH373rWbcjz9Qjb2L6BgLwLADcZtZZPu3nMl8+7LPDhi1NcUEf0Ate41Y",u.crossOrigin="anonymous",u.async=!0,u.src="/libs/amplitude-analytics-browser-2.0.0-min.js",u.onload=function(){e.amplitude.runQueuedFunctions||console.log("[Amplitude] Error: could not load SDK")};var a=t.getElementsByTagName("script")[0];a.parentNode.insertBefore(u,a);for(var c=function(){return this._q=[],this},p=["add","append","clearAll","prepend","set","setOnce","unset","preInsert","postInsert","remove","getUserProperties"],l=0;l<p.length;l++)r(c,p[l]);n.Identify=c;for(var d=function(){return this._q=[],this},f=["getEventProperties","setProductId","setQuantity","setPrice","setRevenue","setRevenueType","setEventProperties"],v=0;v<f.length;v++)r(d,f[v]);n.Revenue=d;var m=["getDeviceId","setDeviceId","getSessionId","setSessionId","getUserId","setUserId","setOptOut","setTransport","reset","extendSession"],g=["init","add","remove","track","logEvent","identify","groupIdentify","setGroup","revenue","flush"];i(n),n.createInstance=function(e){return n._iq[e]={_q:[]},i(n._iq[e]),n._iq[e]},e.amplitude=n}}(window,document)}();
</script>
`;

const amplitudeListenerScript = `<script type="module">
!(function () {
var e = "${AMPLITUDE_API_KEY}";
e &&
(globalThis.amplitude.init(e${AMPLITUDE_SERVER_URL
? `, undefined, {
serverUrl: "${AMPLITUDE_SERVER_URL}"
}`
: ''
}),
globalThis.amplitude.setOptOut(!1),
globalThis.addEventListener("dydx:track", function (e) {
var t = e.detail.eventType,
d = e.detail.eventData;
globalThis.amplitude.track(t, d);
}),
globalThis.addEventListener("dydx:identify", function (e) {
var t = e.detail.property,
d = e.detail.propertyValue;
if ("walletAddress" === t) globalThis.amplitude.setUserId(d);
else {
var i = new globalThis.amplitude.Identify();
i.set(t, d), globalThis.amplitude.identify(i);
}
}),
console.log("Amplitude enabled."));
})();
</script>`;

const injectedHtml = html.replace(
'<div id="root"></div>',
`<div id="root"></div>\n${amplitudeCdnScript}\n${amplitudeListenerScript}`
);

await fs.writeFile(htmlFilePath, injectedHtml, 'utf-8');

console.log(`Amplitude scripts successfully injected (${fileName}).`);
}
119 changes: 63 additions & 56 deletions scripts/inject-bugsnag.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,73 +6,80 @@ const BUGSNAG_API_KEY = process.env.BUGSNAG_API_KEY;

const currentPath = fileURLToPath(import.meta.url);
const projectRoot = path.dirname(currentPath);
const htmlFilePath = path.resolve(projectRoot, '../dist/index.html');

try {
const files = await fs.readdir('entry-points');
for (const file of files) {
inject(file);
};
} catch (err) {
console.error('Error injecting Bugsnag scripts:', err);
}

async function inject(fileName) {
const htmlFilePath = path.resolve(projectRoot, `../dist/entry-points/${fileName}`);
const html = await fs.readFile(htmlFilePath, 'utf-8');

const scripts = `
<script src="//d2wy8f7a9ursnm.cloudfront.net/v7/bugsnag.min.js"></script>
<script src="//d2wy8f7a9ursnm.cloudfront.net/v7/bugsnag.min.js"></script>
<script type="module">
(function() {
var BUGSNAG_API_KEY = '${BUGSNAG_API_KEY}';
var walletType;
<script type="module">
(function() {
var BUGSNAG_API_KEY = '${BUGSNAG_API_KEY}';
var walletType;
if (BUGSNAG_API_KEY) {
Bugsnag.start(BUGSNAG_API_KEY);
if (BUGSNAG_API_KEY) {
Bugsnag.start(BUGSNAG_API_KEY);
}
globalThis.addEventListener('dydx:identify', function (event) {
var property = event.detail.property;
var value = event.detail.propertyValue;
switch (property) {
case 'walletType':
walletType = value;
break;
default:
break;
}
});
globalThis.addEventListener('dydx:identify', function (event) {
var property = event.detail.property;
var value = event.detail.propertyValue;
switch (property) {
case 'walletType':
walletType = value;
break;
default:
break;
}
});
globalThis.addEventListener('dydx:log', function (event) {
var error = event.detail.error;
var metadata = event.detail.metadata;
var location = event.detail.location;
if (BUGSNAG_API_KEY && Bugsnag.isStarted()) {
Bugsnag.notify(error, function (event) {
event.context = location;
if (metadata) {
event.addMetadata('metadata', metadata);
}
if (walletType) {
event.addMetadata('walletType', walletType);
}
});
} else {
console.warn(location, error, metadata);
}
});
})();
</script>
<script type="module">
import BugsnagPerformance from '//d2wy8f7a9ursnm.cloudfront.net/v1.0.0/bugsnag-performance.min.js'
BugsnagPerformance.start({
apiKey: '${BUGSNAG_API_KEY}',
appVersion: '4.10.0',
enabledReleaseStages: ['production', 'development', 'testing']
})
</script>`;
globalThis.addEventListener('dydx:log', function (event) {
var error = event.detail.error;
var metadata = event.detail.metadata;
var location = event.detail.location;
if (BUGSNAG_API_KEY && Bugsnag.isStarted()) {
Bugsnag.notify(error, function (event) {
event.context = location;
if (metadata) {
event.addMetadata('metadata', metadata);
}
if (walletType) {
event.addMetadata('walletType', walletType);
}
});
} else {
console.warn(location, error, metadata);
}
});
})();
</script>
<script type="module">
import BugsnagPerformance from '//d2wy8f7a9ursnm.cloudfront.net/v1.0.0/bugsnag-performance.min.js'
BugsnagPerformance.start({
apiKey: '${BUGSNAG_API_KEY}',
appVersion: '4.10.0',
enabledReleaseStages: ['production', 'development', 'testing']
})
</script>`;

const injectedHtml = html.replace('<div id="root"></div>', `<div id="root"></div>\n${scripts}\n`);

await fs.writeFile(htmlFilePath, injectedHtml, 'utf-8');

console.log('Bugsnag scripts successfully injected.');
} catch (err) {
console.error('Error injecting Bugsnag scripts:', err);
}
console.log(`Bugsnag scripts successfully injected (${fileName}).`);
}
Loading

0 comments on commit 78815ce

Please sign in to comment.