diff --git a/README.md b/README.md index c86c9369f..8422bbe48 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/package.json b/package.json index 45b88552e..391c451e7 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/scripts/generate-entry-points.js b/scripts/generate-entry-points.js new file mode 100755 index 000000000..609ed3c92 --- /dev/null +++ b/scripts/generate-entry-points.js @@ -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( + 'dYdX', + `${entryPoint.title}\n ` + ); + await fs.writeFile(destinationFilePath, injectedHtml, 'utf-8'); + } +} catch (err) { + console.error('Error generating entry points:', err); +} diff --git a/scripts/inject-amplitude.js b/scripts/inject-amplitude.js index b1bdc79c8..3caa733f6 100644 --- a/scripts/inject-amplitude.js +++ b/scripts/inject-amplitude.js @@ -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 = ` - `; - - const amplitudeListenerScript = ``; - - const injectedHtml = html.replace( - '
', - `
\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 = ` + `; + + const amplitudeListenerScript = ``; + + const injectedHtml = html.replace( + '
', + `
\n${amplitudeCdnScript}\n${amplitudeListenerScript}` + ); + + await fs.writeFile(htmlFilePath, injectedHtml, 'utf-8'); + + console.log(`Amplitude scripts successfully injected (${fileName}).`); +} \ No newline at end of file diff --git a/scripts/inject-bugsnag.js b/scripts/inject-bugsnag.js index 305bab523..7dff7cef5 100644 --- a/scripts/inject-bugsnag.js +++ b/scripts/inject-bugsnag.js @@ -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 = ` - + - - - `; + 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); + } + }); + })(); + + + `; const injectedHtml = html.replace('
', `
\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}).`); +} \ No newline at end of file diff --git a/scripts/inject-intercom.js b/scripts/inject-intercom.js index f344e92f5..f6442d9de 100644 --- a/scripts/inject-intercom.js +++ b/scripts/inject-intercom.js @@ -7,70 +7,77 @@ const INTERCOM_APP_ID = process.env.INTERCOM_APP_ID; const currentPath = fileURLToPath(import.meta.url); const projectRoot = path.dirname(currentPath); -const htmlFilePath = path.resolve(projectRoot, '../dist/index.html'); if (INTERCOM_APP_ID) { try { - const html = await fs.readFile(htmlFilePath, 'utf-8'); + const files = await fs.readdir('entry-points'); + for (const file of files) { + inject(file); + }; + } catch (err) { + console.error('Error injecting Intercom scripts:', err); + } +} - const intercomScripts = ` - - +async function inject(fileName) { + const htmlFilePath = path.resolve(projectRoot, `../dist/entry-points/${fileName}`); + const html = await fs.readFile(htmlFilePath, 'utf-8'); + + const intercomScripts = ` + + - - `; + } + })(); + + `; - const injectedHtml = html.replace( - '
', - `
\n${intercomScripts}\n` - ); + const injectedHtml = html.replace( + '
', + `
\n${intercomScripts}\n` + ); - await fs.writeFile(htmlFilePath, injectedHtml, 'utf-8'); + await fs.writeFile(htmlFilePath, injectedHtml, 'utf-8'); - console.log('Intercom scripts successfully injected.'); - } catch (err) { - console.error('Error injecting Intercom scripts:', err); - } -} + console.log(`Intercom scripts successfully injected (${fileName}).`); +} \ No newline at end of file diff --git a/scripts/inject-smartbanner.js b/scripts/inject-smartbanner.js index b5f4767e7..639fbe7fe 100644 --- a/scripts/inject-smartbanner.js +++ b/scripts/inject-smartbanner.js @@ -11,7 +11,6 @@ const SMARTBANNER_GOOGLEPLAY_URL = process.env.SMARTBANNER_GOOGLEPLAY_URL; const currentPath = fileURLToPath(import.meta.url); const projectRoot = path.dirname(currentPath); -const htmlFilePath = path.resolve(projectRoot, '../dist/index.html'); const smartbannerFilePath = path.resolve(projectRoot, '../dist/smartbanner.html'); if ( @@ -21,40 +20,48 @@ if ( (SMARTBANNER_APPSTORE_URL || SMARTBANNER_GOOGLEPLAY_URL) ) { try { - const html = await fs.readFile(htmlFilePath, 'utf-8'); - let smartbanner = await fs.readFile(smartbannerFilePath, 'utf-8'); - smartbanner = smartbanner - .replace('SMARTBANNER_APP_NAME', SMARTBANNER_APP_NAME) - .replace('SMARTBANNER_ORG_NAME', SMARTBANNER_ORG_NAME) - .replace('SMARTBANNER_ICON_URL', SMARTBANNER_ICON_URL) - .replace('SMARTBANNER_ICON_URL', SMARTBANNER_ICON_URL); - - /* hardcoded injection depending on whether the app is available on App Store and/or Google Play */ - - if (SMARTBANNER_APPSTORE_URL) { - smartbanner = `\t\n` + smartbanner; - } + const files = await fs.readdir('entry-points'); + for (const file of files) { + inject(file); + }; + } catch (err) { + console.error('Error injecting Smartbanner scripts:', err); + } +} + +async function inject(fileName) { + const htmlFilePath = path.resolve(projectRoot, `../dist/entry-points/${fileName}`); + const html = await fs.readFile(htmlFilePath, 'utf-8'); + let smartbanner = await fs.readFile(smartbannerFilePath, 'utf-8'); + smartbanner = smartbanner + .replace('SMARTBANNER_APP_NAME', SMARTBANNER_APP_NAME) + .replace('SMARTBANNER_ORG_NAME', SMARTBANNER_ORG_NAME) + .replace('SMARTBANNER_ICON_URL', SMARTBANNER_ICON_URL) + .replace('SMARTBANNER_ICON_URL', SMARTBANNER_ICON_URL); + + /* hardcoded injection depending on whether the app is available on App Store and/or Google Play */ + + if (SMARTBANNER_APPSTORE_URL) { + smartbanner = `\t\n` + smartbanner; + } + if (SMARTBANNER_GOOGLEPLAY_URL) { + smartbanner = `\t\n` + smartbanner; + } + if (SMARTBANNER_APPSTORE_URL) { if (SMARTBANNER_GOOGLEPLAY_URL) { - smartbanner = `\t\n` + smartbanner; - } - if (SMARTBANNER_APPSTORE_URL) { - if (SMARTBANNER_GOOGLEPLAY_URL) { - smartbanner = `\t\n` + smartbanner; - } else { - smartbanner = `\t\n` + smartbanner; - } + smartbanner = `\t\n` + smartbanner; } else { - if (SMARTBANNER_GOOGLEPLAY_URL) { - smartbanner = `\t\n` + smartbanner; - } + smartbanner = `\t\n` + smartbanner; } + } else { + if (SMARTBANNER_GOOGLEPLAY_URL) { + smartbanner = `\t\n` + smartbanner; + } + } - const injectedHtml = html.replace('', `${smartbanner}\n`); + const injectedHtml = html.replace('', `${smartbanner}\n`); - await fs.writeFile(htmlFilePath, injectedHtml, 'utf-8'); + await fs.writeFile(htmlFilePath, injectedHtml, 'utf-8'); - console.log('Smartbanner scripts successfully injected.'); - } catch (err) { - console.error('Error injecting Smartbanner scripts:', err); - } -} + console.log(`Smartbanner scripts successfully injected (${fileName}).`); +} \ No newline at end of file diff --git a/scripts/inject-statuspage.js b/scripts/inject-statuspage.js index 0315cbfba..34d9d82b8 100644 --- a/scripts/inject-statuspage.js +++ b/scripts/inject-statuspage.js @@ -6,23 +6,29 @@ const STATUS_PAGE_SCRIPT_URI = process.env.STATUS_PAGE_SCRIPT_URI; const currentPath = fileURLToPath(import.meta.url); const projectRoot = path.dirname(currentPath); -const htmlFilePath = path.resolve(projectRoot, '../dist/index.html'); if (STATUS_PAGE_SCRIPT_URI) { try { - const html = await fs.readFile(htmlFilePath, 'utf-8'); + const files = await fs.readdir('entry-points'); + for (const file of files) { + inject(file); + }; + } catch (err) { + console.error('Error injecting StatusPage scripts:', err); + } +} - const statusPageScript = ``; +async function inject(fileName) { + const htmlFilePath = path.resolve(projectRoot, `../dist/entry-points/${fileName}`); + const html = await fs.readFile(htmlFilePath, 'utf-8'); + const statusPageScript = ``; - const injectedHtml = html.replace( - '
', - `
\n${statusPageScript}\n` - ); + const injectedHtml = html.replace( + '
', + `
\n${statusPageScript}\n` + ); - await fs.writeFile(htmlFilePath, injectedHtml, 'utf-8'); + await fs.writeFile(htmlFilePath, injectedHtml, 'utf-8'); - console.log('StatusPage script successfully injected.'); - } catch (err) { - console.error('Error injecting StatusPage scripts:', err); - } + console.log(`StatusPage script successfully injected (${fileName}).`); } diff --git a/index.html b/template.html similarity index 99% rename from index.html rename to template.html index 3b1873fe1..88fbc243f 100644 --- a/index.html +++ b/template.html @@ -36,4 +36,4 @@
- + \ No newline at end of file diff --git a/vercel.json b/vercel.json index 0f32683a9..daa7ecd39 100644 --- a/vercel.json +++ b/vercel.json @@ -1,3 +1,8 @@ { - "rewrites": [{ "source": "/(.*)", "destination": "/index.html" }] -} + "rewrites": [ + { + "source": "/(.*)", + "destination": "/entry-points/index.html" + } + ] +} \ No newline at end of file diff --git a/vite.config.ts b/vite.config.ts index ad8abba84..c3ad3bdf7 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,3 +1,4 @@ +import fs from 'fs'; import react from '@vitejs/plugin-react'; import path from 'path'; import { defineConfig } from 'vite'; @@ -77,4 +78,9 @@ export default defineConfig(({ mode }) => ({ '**/e2e/**', ], }, -})); \ No newline at end of file + build: { + rollupOptions: { + input: fs.readdirSync('entry-points').map(file => `/entry-points/${file}`) + }, + }, +}));