diff --git a/.env.development b/.env.development
index 08b95f66e1..66c5453cfc 100644
--- a/.env.development
+++ b/.env.development
@@ -1,5 +1,5 @@
-VUE_APP_ENV=development
-VUE_APP_DOMAIN=localhost:8080
-VUE_APP_IPFS_NODE=cloudflare-ipfs.com
-VUE_APP_BLOCKNATIVE_DAPP_ID=032e2fb8-6c66-46a5-bf1c-a049ac7eded2
-VUE_APP_FATHOM_SITE_ID=xxx
+VITE_ENV=development
+VITE_DOMAIN=localhost:8080
+VITE_IPFS_NODE=cloudflare-ipfs.com
+VITE_BLOCKNATIVE_DAPP_ID=032e2fb8-6c66-46a5-bf1c-a049ac7eded2
+VITE_FATHOM_SITE_ID=xxx
diff --git a/.env.docker b/.env.docker
index 63d5449c9a..2845c2fd7c 100644
--- a/.env.docker
+++ b/.env.docker
@@ -1,8 +1,8 @@
-VUE_APP_ENV=production
-VUE_APP_DOMAIN=localhost:8080
-VUE_APP_IPFS_NODE=cloudflare-ipfs.com
-VUE_APP_BLOCKNATIVE_DAPP_ID=VABNDAPPID
-VUE_APP_FATHOM_SITE_ID=xxx
-VUE_APP_INFURA_PROJECT_ID=VAINFURAPID
-VUE_APP_ALCHEMY_KEY=VAALCHEMYKEY
+VITE_ENV=production
+VITE_DOMAIN=localhost:8080
+VITE_IPFS_NODE=cloudflare-ipfs.com
+VITE_BLOCKNATIVE_DAPP_ID=VABNDAPPID
+VITE_FATHOM_SITE_ID=xxx
+VITE_INFURA_PROJECT_ID=VAINFURAPID
+VITE_ALCHEMY_KEY=VAALCHEMYKEY
SUBGRAPH_URL=https://api.thegraph.com/subgraphs/name/balancer-labs/balancer-v2
diff --git a/.env.test b/.env.test
index 6b182342ce..b8322ff940 100644
--- a/.env.test
+++ b/.env.test
@@ -1,7 +1,7 @@
-VUE_APP_ENV=test
-VUE_APP_DOMAIN=localhost:8080
-VUE_APP_IPFS_NODE=cloudflare-ipfs.com
-VUE_APP_NETWORK=5
-VUE_APP_BLOCKNATIVE_DAPP_ID=032e2fb8-6c66-46a5-bf1c-a049ac7eded2
-VUE_APP_FATHOM_SITE_ID=xxx
+VITE_ENV=test
+VITE_DOMAIN=localhost:8080
+VITE_IPFS_NODE=cloudflare-ipfs.com
+VITE_NETWORK=5
+VITE_BLOCKNATIVE_DAPP_ID=032e2fb8-6c66-46a5-bf1c-a049ac7eded2
+VITE_FATHOM_SITE_ID=xxx
SUBGRAPH_URL=https://api.thegraph.com/subgraphs/name/balancer-labs/balancer-goerli-v2
diff --git a/.eslintignore b/.eslintignore
index 680c0cf44b..6480de6b70 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -1,5 +1,8 @@
+// Run DEBUG=eslint:cli-engine npm run lint to discover eslint bottlenecks
node_modules/
dist/
tokenlists.json
public/data/tokenlists/*.json
package-lock.json
+components.d.ts
+/tailwind-static
diff --git a/.eslintrc.js b/.eslintrc.js
index 8b5c57687b..6a61f31a80 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -68,6 +68,7 @@ module.exports = {
],
},
],
+ // 'no-empty-function': ['error', { allow: ['arrowFunctions'] }],
},
overrides: [
diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml
index ee2419c2c4..f563f9b9f3 100644
--- a/.github/workflows/checks.yml
+++ b/.github/workflows/checks.yml
@@ -4,7 +4,7 @@ on:
- pull_request
env:
- VUE_APP_FATHOM_SITE_ID: xxx
+ VITE_FATHOM_SITE_ID: xxx
jobs:
Version:
@@ -15,7 +15,7 @@ jobs:
- name: Use Node.js
uses: actions/setup-node@v1
with:
- node-version: '14.x'
+ node-version: '16.x'
- name: Get package.json version
run: echo PACKAGE_VERSION=$(node -p -e "require('./package.json').version") >> $GITHUB_ENV
- name: Get package-lock.json version
@@ -44,10 +44,14 @@ jobs:
- name: Use Node.js
uses: actions/setup-node@v1
with:
- node-version: '14.x'
+ node-version: '16.x'
- name: Install deps
run: npm install -g npm@7 && npm ci
- name: Run build
+ env:
+ # Increase memory to avoid heap error
+ # https://github.com/vitejs/vite/issues/2433
+ NODE_OPTIONS: "--max-old-space-size=8192"
run: npm run build
Lint:
@@ -57,7 +61,7 @@ jobs:
- name: Use Node.js
uses: actions/setup-node@v1
with:
- node-version: '14.x'
+ node-version: '16.x'
- name: Install deps
run: npm install -g npm@7 && npm ci
- name: Run lint
@@ -74,7 +78,7 @@ jobs:
- name: Use Node.js
uses: actions/setup-node@v1
with:
- node-version: '14.x'
+ node-version: '16.x'
- name: Install deps
run: npm install -g npm@7 && npm ci
- name: Run typecheck
diff --git a/.gitignore b/.gitignore
index 1389c58e17..fe421663f7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -27,8 +27,11 @@ pnpm-debug.log*
*.sw?
# tokenlists
-/public/data/tokenlists.json
-/public/data/tokenlists/*.json
+/src/assets/data/tokenlists.json
+/src/assets/data/tokenlists/*.json
-# jest coverage
+#test coverage
/coverage
+
+#rollup analyzer
+stats.html
diff --git a/.vscode/settings.json b/.vscode/settings.json
index bfe78b1cf4..e099821f14 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -35,5 +35,7 @@
"editor.defaultFormatter": "dbaeumer.vscode-eslint",
"editor.formatOnSave": true
},
- "cSpell.words": ["Aprs", "Multicaller"]
+ "cSpell.words": ["Aprs", "Multicaller"],
+ // Avoid @apply warnings in style blocks
+ "css.validate": false
}
diff --git a/Dockerfile b/Dockerfile
index 48190d46e3..75ab8dd27d 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,5 +1,5 @@
-# This Dockerfile runs a production build of balancer.
-# It requires specifying an Infura API key to pull data.
+# This Dockerfile runs a production build of balancer.
+# It requires specifying an Infura API key to pull data.
# See README for futher information
FROM node:14 AS base
@@ -19,7 +19,7 @@ RUN npm ci
FROM dependencies AS build
COPY . .
-RUN npm run build -- --mode docker --skip-plugins webpack-bundle-analyzer
+RUN npm run build -- --mode docker
FROM nginx:1.23.3-alpine as release
diff --git a/README.md b/README.md
index 40ad29f506..d4e34f14cd 100644
--- a/README.md
+++ b/README.md
@@ -21,11 +21,53 @@ npm install
Start the app:
```bash
-npm run serve
+npm run dev
```
The app should be live at [http://localhost:8080](http://localhost:8080)
+### Testing
+
+Run unit tests:
+
+```bash
+npm run test:unit
+```
+
+Run unit tests in watch mode:
+
+```bash
+npm run test:unit:watch
+```
+
+Run unit tests with coverage:
+
+```bash
+npm run test:unit:coverage
+```
+
+### Build
+
+Run build:
+
+```bash
+npm run build
+```
+
+Preview build:
+
+```bash
+npm run preview
+```
+
+Run build in watch mode:
+
+```bash
+npm run build:watch
+```
+
+This mode is useful when you need to reproduce/fix bugs/issues in a **production-like** environment.
+
### Docker
If you'd rather spin up the app in a docker container, first install dependencies to you local folder:
@@ -82,3 +124,31 @@ The frontend can easily be deployed to any static host. Use the buttons below to
[](https://app.netlify.com/start/deploy?repository=https://github.com/balancer-labs/frontend-v2)
[](https://vercel.com/new/clone?repository-url=https://github.com/balancer-labs/frontend-v2)
+
+## Vite setup
+
+This app is powered by [vite](https://vitejs.dev/), which:
+
+- Runs a development dev server with [esbuild](https://esbuild.github.io/).
+- Builds production bundle with [Rollup](https://rollupjs.org/guide/en/).
+
+Both tools above rely on native ES modules but our app also depends on libraries like [ethers.js](https://docs.ethers.io/) which use Node.js built-in modules (like Buffer, stream or crypto) that require browser polyfills. Thats why our `vite.config.ts` uses [vite-plugin-node-stdlib-browser](https://github.com/sodatea/vite-plugin-node-stdlib-browser) and [rollup-plugin-polyfill-node](https://www.npmjs.com/package/rollup-plugin-polyfill-node).
+
+### unplugin-vue magic 🪄
+
+We use some Vite plugins to improve the Vue developer experience.
+
+[unplugin-vue-components](https://github.com/antfu/unplugin-vue-components):
+
+Auto imports components located in `src/components/_global` so that they are available from every other component in the application (and from _vitest_).
+(It also auto generates a _d.ts_ file for the auto imported components).
+
+### Analyze bundle
+
+Analyze and visualize the bundle dependencies by adding these env vars to your `.env` file before running the build:
+
+```bash
+# Local .env file
+VITE_BUILD_ANALIZE=true
+VITE_BUILD_VISUALIZE=true
+```
diff --git a/babel.config.js b/babel.config.js
deleted file mode 100644
index bd5f51af02..0000000000
--- a/babel.config.js
+++ /dev/null
@@ -1,9 +0,0 @@
-module.exports = {
- presets: ['@vue/cli-plugin-babel/preset'],
- plugins: ['@babel/plugin-proposal-numeric-separator'],
- env: {
- test: {
- plugins: ['require-context-hook'],
- },
- },
-};
diff --git a/components.d.ts b/components.d.ts
new file mode 100644
index 0000000000..33bcd4b6e1
--- /dev/null
+++ b/components.d.ts
@@ -0,0 +1,79 @@
+// generated by unplugin-vue-components
+// We suggest you to commit this file into source control
+// Read more: https://github.com/vuejs/core/pull/3399
+import '@vue/runtime-core'
+
+export {}
+
+declare module '@vue/runtime-core' {
+ export interface GlobalComponents {
+ ActivityIcon: typeof import('./src/components/_global/icons/ActivityIcon.vue')['default']
+ ArrowDownIcon: typeof import('./src/components/_global/icons/ArrowDownIcon.vue')['default']
+ BalAccordion: typeof import('./src/components/_global/BalAccordion/BalAccordion.vue')['default']
+ BalActionSteps: typeof import('./src/components/_global/BalActionSteps/BalActionSteps.vue')['default']
+ BalAlert: typeof import('./src/components/_global/BalAlert/BalAlert.vue')['default']
+ BalAsset: typeof import('./src/components/_global/BalAsset/BalAsset.vue')['default']
+ BalAssetSet: typeof import('./src/components/_global/BalAsset/BalAssetSet.vue')['default']
+ BalBlankSlate: typeof import('./src/components/_global/BalBlankSlate/BalBlankSlate.vue')['default']
+ BalBreakdown: typeof import('./src/components/_global/BalBreakdown/BalBreakdown.vue')['default']
+ BalBtn: typeof import('./src/components/_global/BalBtn/BalBtn.vue')['default']
+ BalBtnGroup: typeof import('./src/components/_global/BalBtnGroup/BalBtnGroup.vue')['default']
+ BalCard: typeof import('./src/components/_global/BalCard/BalCard.vue')['default']
+ BalChart: typeof import('./src/components/_global/BalChart/BalChart.vue')['default']
+ BalCheckbox: typeof import('./src/components/_global/BalCheckbox/BalCheckbox.vue')['default']
+ BalChip: typeof import('./src/components/_global/BalChip/BalChip.vue')['default']
+ BalCircle: typeof import('./src/components/_global/shapes/BalCircle/BalCircle.vue')['default']
+ BalCloseIcon: typeof import('./src/components/_global/icons/BalCloseIcon.vue')['default']
+ BalDataList: typeof import('./src/components/_global/BalDataList/BalDataList.vue')['default']
+ BalDataListRow: typeof import('./src/components/_global/BalDataList/BalDataListRow.vue')['default']
+ BalDetailsTable: typeof import('./src/components/_global/BalDetailsTable/BalDetailsTable.vue')['default']
+ BalDropdown: typeof import('./src/components/_global/BalDropdown/BalDropdown.vue')['default']
+ BalFlexGrid: typeof import('./src/components/_global/BalFlexGrid/BalFlexGrid.vue')['default']
+ BalForm: typeof import('./src/components/_global/BalForm/BalForm.vue')['default']
+ BalHorizSteps: typeof import('./src/components/_global/BalHorizSteps/BalHorizSteps.vue')['default']
+ BalIcon: typeof import('./src/components/_global/BalIcon/BalIcon.vue')['default']
+ BalImage: typeof import('./src/components/_global/BalImage/BalImage.vue')['default']
+ BalInlineInput: typeof import('./src/components/_global/BalInlineInput/BalInlineInput.vue')['default']
+ BalLazy: typeof import('./src/components/_global/BalLazy/BalLazy.vue')['default']
+ BalLink: typeof import('./src/components/_global/BalLink/BalLink.vue')['default']
+ BalLoadingBlock: typeof import('./src/components/_global/BalLoadingBlock/BalLoadingBlock.vue')['default']
+ BalLoadingIcon: typeof import('./src/components/_global/BalLoadingIcon/BalLoadingIcon.vue')['default']
+ BalLoadingNumber: typeof import('./src/components/_global/BalLoadingNumber/BalLoadingNumber.vue')['default']
+ BalModal: typeof import('./src/components/_global/BalModal/BalModal.vue')['default']
+ BalPopover: typeof import('./src/components/_global/BalPopover/BalPopover.vue')['default']
+ BalProgressBar: typeof import('./src/components/_global/BalProgressBar/BalProgressBar.vue')['default']
+ BalRadio: typeof import('./src/components/_global/BalRadio/BalRadio.vue')['default']
+ BalRangeInput: typeof import('./src/components/_global/BalRangeInput/BalRangeInput.vue')['default']
+ BalSelectInput: typeof import('./src/components/_global/BalSelectInput/BalSelectInput.vue')['default']
+ BalStack: typeof import('./src/components/_global/BalStack/BalStack.vue')['default']
+ BalTable: typeof import('./src/components/_global/BalTable/BalTable.vue')['default']
+ BalTableRow: typeof import('./src/components/_global/BalTable/BalTableRow.vue')['default']
+ BalTabs: typeof import('./src/components/_global/BalTabs/BalTabs.vue')['default']
+ BalTextInput: typeof import('./src/components/_global/BalTextInput/BalTextInput.vue')['default']
+ BalToggle: typeof import('./src/components/_global/BalToggle/BalToggle.vue')['default']
+ BalTooltip: typeof import('./src/components/_global/BalTooltip/BalTooltip.vue')['default']
+ BalVerticalSteps: typeof import('./src/components/_global/BalVerticalSteps/BalVerticalSteps.vue')['default']
+ CheckIcon: typeof import('./src/components/_global/icons/CheckIcon.vue')['default']
+ CompositionIcon: typeof import('./src/components/_global/icons/CompositionIcon.vue')['default']
+ DiscordIcon: typeof import('./src/components/_global/icons/brands/DiscordIcon.vue')['default']
+ EmailIcon: typeof import('./src/components/_global/icons/EmailIcon.vue')['default']
+ GithubIcon: typeof import('./src/components/_global/icons/brands/GithubIcon.vue')['default']
+ LightBulbIcon: typeof import('./src/components/_global/icons/LightBulbIcon.vue')['default']
+ MediumIcon: typeof import('./src/components/_global/icons/brands/MediumIcon.vue')['default']
+ MinusSquareIcon: typeof import('./src/components/_global/icons/MinusSquareIcon.vue')['default']
+ MoonIcon: typeof import('./src/components/_global/icons/MoonIcon.vue')['default']
+ NetworkIcon: typeof import('./src/components/_global/icons/NetworkIcon.vue')['default']
+ PinHeader: typeof import('./src/components/_global/BalTable/PinHeader.vue')['default']
+ PlusSquareIcon: typeof import('./src/components/_global/icons/PlusSquareIcon.vue')['default']
+ RouterLink: typeof import('vue-router')['RouterLink']
+ RouterView: typeof import('vue-router')['RouterView']
+ SpinnerIcon: typeof import('./src/components/_global/icons/SpinnerIcon.vue')['default']
+ StarsIcon: typeof import('./src/components/_global/icons/StarsIcon.vue')['default']
+ SunIcon: typeof import('./src/components/_global/icons/SunIcon.vue')['default']
+ TimelockIcon: typeof import('./src/components/_global/icons/TimelockIcon.vue')['default']
+ TotalsRow: typeof import('./src/components/_global/BalTable/TotalsRow.vue')['default']
+ TwitterIcon: typeof import('./src/components/_global/icons/brands/TwitterIcon.vue')['default']
+ WalletIcon: typeof import('./src/components/_global/icons/WalletIcon.vue')['default']
+ YoutubeIcon: typeof import('./src/components/_global/icons/brands/YoutubeIcon.vue')['default']
+ }
+}
diff --git a/env.d.ts b/env.d.ts
new file mode 100644
index 0000000000..5ba4685ae8
--- /dev/null
+++ b/env.d.ts
@@ -0,0 +1,11 @@
+///