mirror of
https://github.com/actualbudget/actual.git
synced 2026-04-30 18:20:24 -05:00
Proposal for switching desktop-client to vite (#2084)
* Proof of concept for switching desktop-client to vite * Fix other packages ts tests issues * Update jsx tests to use vitest instead of jest * Inject our global shims properly * Add comment regarding new plugin * Cleanup unnessary change after rebase * Fix inter fonts pathing * Remove manual chunks sizes for now Just set the limit higher * Bring back size compare * Suppress victory warnings * Remove craco config now that it's not used * Add vite basic ssl plugin - This autogenerates self-signed certs in dev mode when HTTPS env is set - Made to match the CRA behaviour * Add release note * Remove warning suppression for victory - Updated to a rollup version that includes the fix
This commit is contained in:
@@ -44,6 +44,9 @@ module.exports = {
|
||||
parser: '@typescript-eslint/parser',
|
||||
parserOptions: { project: [path.join(__dirname, './tsconfig.json')] },
|
||||
reportUnusedDisableDirectives: true,
|
||||
globals: {
|
||||
globalThis: false,
|
||||
},
|
||||
rules: {
|
||||
'prettier/prettier': 'warn',
|
||||
|
||||
|
||||
6
.github/workflows/size-compare.yml
vendored
6
.github/workflows/size-compare.yml
vendored
@@ -66,11 +66,11 @@ jobs:
|
||||
run: |
|
||||
sed -i -E 's/\.[0-9a-f]{8,}\././g' ./head/*.json
|
||||
sed -i -E 's/\.[0-9a-f]{8,}\././g' ./base/*.json
|
||||
- uses: github/webpack-bundlesize-compare-action@v1.8.2
|
||||
- uses: twk3/rollup-size-compare-action@v1.0.0
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
current-stats-json-path: ./head/desktop-client-stats.json
|
||||
base-stats-json-path: ./base/desktop-client-stats.json
|
||||
current-stats-json-path: ./head/web-stats.json
|
||||
base-stats-json-path: ./base/web-stats.json
|
||||
title: desktop-client
|
||||
|
||||
- uses: github/webpack-bundlesize-compare-action@v1.8.2
|
||||
|
||||
2
packages/desktop-client/.gitignore
vendored
2
packages/desktop-client/.gitignore
vendored
@@ -10,11 +10,13 @@ test-results
|
||||
# production
|
||||
build
|
||||
build-stats
|
||||
stats.json
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env
|
||||
npm-debug.log
|
||||
.swc
|
||||
|
||||
*kcab.*
|
||||
public/kcab
|
||||
|
||||
@@ -8,7 +8,6 @@ echo "Building the browser..."
|
||||
rm -fr build
|
||||
|
||||
export IS_GENERIC_BROWSER=1
|
||||
export INLINE_RUNTIME_CHUNK=false
|
||||
export REACT_APP_BACKEND_WORKER_HASH=`ls "$ROOT"/../public/kcab/kcab.worker.*.js | sed 's/.*kcab\.worker\.\(.*\)\.js/\1/'`
|
||||
|
||||
yarn build
|
||||
@@ -16,4 +15,4 @@ yarn build
|
||||
rm -fr build-stats
|
||||
mkdir build-stats
|
||||
mv build/kcab/stats.json build-stats/loot-core-stats.json
|
||||
mv build/stats.json build-stats/desktop-client-stats.json
|
||||
mv ./stats.json build-stats/web-stats.json
|
||||
|
||||
@@ -1,116 +0,0 @@
|
||||
const path = require('path');
|
||||
|
||||
const {
|
||||
loaderByName,
|
||||
removeLoaders,
|
||||
addAfterLoader,
|
||||
addPlugins,
|
||||
} = require('@craco/craco');
|
||||
const chokidar = require('chokidar');
|
||||
const TerserPlugin = require('terser-webpack-plugin');
|
||||
const { IgnorePlugin } = require('webpack');
|
||||
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
|
||||
|
||||
if (process.env.CI) {
|
||||
process.env.DISABLE_ESLINT_PLUGIN = 'true';
|
||||
}
|
||||
|
||||
// Forward Netlify env variables
|
||||
if (process.env.REVIEW_ID) {
|
||||
process.env.REACT_APP_REVIEW_ID = process.env.REVIEW_ID;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
webpack: {
|
||||
configure: (webpackConfig, { env, paths }) => {
|
||||
webpackConfig.mode =
|
||||
process.env.NODE_ENV === 'development' ? 'development' : 'production';
|
||||
|
||||
// swc-loader
|
||||
addAfterLoader(webpackConfig, loaderByName('babel-loader'), {
|
||||
test: /\.m?[tj]sx?$/,
|
||||
exclude: /node_modules/,
|
||||
loader: require.resolve('swc-loader'),
|
||||
});
|
||||
|
||||
// remove the babel loaders
|
||||
removeLoaders(webpackConfig, loaderByName('babel-loader'));
|
||||
|
||||
addPlugins(webpackConfig, [
|
||||
new BundleAnalyzerPlugin({
|
||||
analyzerMode: 'disabled',
|
||||
generateStatsFile: true,
|
||||
}),
|
||||
// Pikaday throws a warning if Moment.js is not installed however it doesn't
|
||||
// actually require it to be installed. As we don't use Moment.js ourselves
|
||||
// then we can just silence this warning.
|
||||
new IgnorePlugin({
|
||||
contextRegExp: /pikaday$/,
|
||||
resourceRegExp: /moment$/,
|
||||
}),
|
||||
]);
|
||||
|
||||
webpackConfig.resolve.extensions = [
|
||||
'.web.js',
|
||||
'.web.jsx',
|
||||
'.web.ts',
|
||||
'.web.tsx',
|
||||
'.js',
|
||||
'.jsx',
|
||||
'.ts',
|
||||
'.tsx',
|
||||
...webpackConfig.resolve.extensions,
|
||||
];
|
||||
|
||||
if (process.env.IS_GENERIC_BROWSER) {
|
||||
webpackConfig.resolve.extensions = [
|
||||
'.browser.js',
|
||||
'.browser.jsx',
|
||||
'.browser.ts',
|
||||
'.browser.tsx',
|
||||
...webpackConfig.resolve.extensions,
|
||||
];
|
||||
}
|
||||
|
||||
webpackConfig.optimization = {
|
||||
...webpackConfig.optimization,
|
||||
minimize:
|
||||
process.env.CI === 'true' || process.env.NODE_ENV !== 'development',
|
||||
minimizer: [
|
||||
new TerserPlugin({
|
||||
minify: TerserPlugin.swcMinify,
|
||||
// `terserOptions` options will be passed to `swc` (`@swc/core`)
|
||||
// Link to options - https://swc.rs/docs/config-js-minify
|
||||
terserOptions: {
|
||||
compress: false,
|
||||
mangle: true,
|
||||
},
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
||||
return webpackConfig;
|
||||
},
|
||||
},
|
||||
devServer: (devServerConfig, { env, paths, proxy, allowedHost }) => {
|
||||
devServerConfig.onBeforeSetupMiddleware = server => {
|
||||
chokidar
|
||||
.watch([
|
||||
path.resolve('../loot-core/lib-dist/*.js'),
|
||||
path.resolve('../loot-core/lib-dist/browser/*.js'),
|
||||
])
|
||||
.on('all', function () {
|
||||
for (const ws of server.webSocketServer.clients) {
|
||||
ws.send(JSON.stringify({ type: 'static-changed' }));
|
||||
}
|
||||
});
|
||||
};
|
||||
devServerConfig.headers = {
|
||||
...devServerConfig.headers,
|
||||
'Cross-Origin-Opener-Policy': 'same-origin',
|
||||
'Cross-Origin-Embedder-Policy': 'require-corp',
|
||||
};
|
||||
|
||||
return devServerConfig;
|
||||
},
|
||||
};
|
||||
@@ -8,25 +8,25 @@
|
||||
/>
|
||||
<title>Actual</title>
|
||||
<link rel="canonical" href="/" />
|
||||
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<link rel="shortcut icon" href="/favicon.ico" />
|
||||
<link
|
||||
rel="apple-touch-icon"
|
||||
sizes="180x180"
|
||||
href="%PUBLIC_URL%/apple-touch-icon.png"
|
||||
href="/apple-touch-icon.png"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="32x32"
|
||||
href="%PUBLIC_URL%/favicon-32x32.png"
|
||||
href="/favicon-32x32.png"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="16x16"
|
||||
href="%PUBLIC_URL%/favicon-16x16.png"
|
||||
href="/favicon-16x16.png"
|
||||
/>
|
||||
<link rel="manifest" href="%PUBLIC_URL%/site.webmanifest" crossorigin="use-credentials"/>
|
||||
<link rel="manifest" href="/site.webmanifest" crossorigin="use-credentials"/>
|
||||
<meta name="msapplication-TileColor" content="#da532c" />
|
||||
<meta name="theme-color" content="#ffffff" />
|
||||
|
||||
@@ -102,5 +102,6 @@
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/index.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -6,8 +6,6 @@
|
||||
"build"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@craco/craco": "^7.1.0",
|
||||
"@craco/types": "^7.1.0",
|
||||
"@juggle/resize-observer": "^3.1.2",
|
||||
"@playwright/test": "^1.37.1",
|
||||
"@reach/listbox": "^0.18.0",
|
||||
@@ -16,9 +14,11 @@
|
||||
"@react-aria/utils": "^3.19.0",
|
||||
"@react-stately/collections": "^3.10.0",
|
||||
"@react-stately/list": "^3.9.1",
|
||||
"@rollup/plugin-inject": "^5.0.5",
|
||||
"@svgr/cli": "^8.0.1",
|
||||
"@swc/core": "^1.3.82",
|
||||
"@swc/helpers": "^0.5.1",
|
||||
"@swc/plugin-remove-console": "^1.5.105",
|
||||
"@testing-library/react": "14.0.0",
|
||||
"@testing-library/user-event": "14.4.3",
|
||||
"@types/react": "^18.2.0",
|
||||
@@ -28,6 +28,8 @@
|
||||
"@types/uuid": "^9.0.2",
|
||||
"@types/webpack-bundle-analyzer": "^4.6.0",
|
||||
"@use-gesture/react": "^10.3.0",
|
||||
"@vitejs/plugin-basic-ssl": "^1.0.2",
|
||||
"@vitejs/plugin-react-swc": "^3.5.0",
|
||||
"chokidar": "^3.5.3",
|
||||
"cross-env": "^7.0.3",
|
||||
"date-fns": "^2.29.3",
|
||||
@@ -41,7 +43,7 @@
|
||||
"jest-watch-typeahead": "^2.2.2",
|
||||
"mdast-util-newline-to-break": "^2.0.0",
|
||||
"memoize-one": "^6.0.0",
|
||||
"pikaday": "1.8.0",
|
||||
"pikaday": "1.8.2",
|
||||
"react": "18.2.0",
|
||||
"react-dnd": "^16.0.1",
|
||||
"react-dnd-html5-backend": "^16.0.1",
|
||||
@@ -52,7 +54,6 @@
|
||||
"react-modal": "3.16.1",
|
||||
"react-redux": "7.2.1",
|
||||
"react-router-dom": "6.11.2",
|
||||
"react-scripts": "^5.0.1",
|
||||
"react-simple-pull-to-refresh": "^1.3.3",
|
||||
"react-spring": "^9.7.1",
|
||||
"react-virtualized-auto-sizer": "^1.0.2",
|
||||
@@ -60,22 +61,26 @@
|
||||
"redux": "^4.0.5",
|
||||
"redux-thunk": "^2.3.0",
|
||||
"remark-gfm": "^3.0.1",
|
||||
"rollup-plugin-visualizer": "^5.11.0",
|
||||
"sass": "^1.63.6",
|
||||
"swc-loader": "^0.2.3",
|
||||
"terser-webpack-plugin": "^5.3.9",
|
||||
"uuid": "^9.0.0",
|
||||
"victory": "^36.6.8",
|
||||
"vite": "^5.0.10",
|
||||
"vite-tsconfig-paths": "^4.2.2",
|
||||
"vitest": "^1.0.4",
|
||||
"webpack-bundle-analyzer": "^4.9.1",
|
||||
"xml2js": "^0.6.2"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "cross-env PORT=3001 craco start",
|
||||
"start": "cross-env PORT=3001 vite",
|
||||
"start:browser": "cross-env ./bin/watch-browser",
|
||||
"watch": "cross-env BROWSER=none yarn start",
|
||||
"build": "cross-env INLINE_RUNTIME_CHUNK=false craco build",
|
||||
"build": "vite build",
|
||||
"build:browser": "cross-env ./bin/build-browser",
|
||||
"generate:icons": "rm src/icons/*/*.tsx; cd src/icons && svgr --template template.ts --index-template index-template.ts --typescript --expand-props start -d . .",
|
||||
"test": "craco test",
|
||||
"test": "vitest",
|
||||
"e2e": "npx playwright test --browser=chromium",
|
||||
"vrt": "cross-env VRT=true npx playwright test --browser=chromium"
|
||||
}
|
||||
|
||||
14
packages/desktop-client/src/build-shims.js
Normal file
14
packages/desktop-client/src/build-shims.js
Normal file
@@ -0,0 +1,14 @@
|
||||
const global = globalThis || this || self;
|
||||
|
||||
const process = {
|
||||
env: {
|
||||
...import.meta.env,
|
||||
NODE_ENV: import.meta.env.MODE,
|
||||
PUBLIC_URL: import.meta.env.BASE_URL.slice(0, -1),
|
||||
},
|
||||
};
|
||||
|
||||
// eslint-disable-next-line import/no-unused-modules
|
||||
export { process };
|
||||
// eslint-disable-next-line import/no-unused-modules
|
||||
export { global };
|
||||
@@ -366,7 +366,7 @@ const TransactionHeader = memo(
|
||||
/>
|
||||
{showBalance && <Cell value="Balance" width={88} textAlign="right" />}
|
||||
{showCleared && <Field width={23} truncate={false} />}
|
||||
<Cell value="" width={5 + scrollWidth ?? 0} />
|
||||
<Cell value="" width={5 + (scrollWidth ?? 0)} />
|
||||
</Row>
|
||||
);
|
||||
},
|
||||
|
||||
@@ -25,8 +25,8 @@ import { ResponsiveProvider } from '../../ResponsiveProvider';
|
||||
|
||||
import { SplitsExpandedProvider, TransactionTable } from './TransactionsTable';
|
||||
|
||||
jest.mock('loot-core/src/platform/client/fetch');
|
||||
jest.mock('../../hooks/useFeatureFlag', () => jest.fn().mockReturnValue(false));
|
||||
vi.mock('loot-core/src/platform/client/fetch');
|
||||
vi.mock('../../hooks/useFeatureFlag', () => vi.fn().mockReturnValue(false));
|
||||
|
||||
const accounts = [generateAccount('Bank of America')];
|
||||
const payees = [
|
||||
|
||||
@@ -1,2 +1,4 @@
|
||||
@use '~inter-ui/variable';
|
||||
@use 'inter-ui/variable' with (
|
||||
$inter-font-path: '../../../node_modules/inter-ui/Inter (web)'
|
||||
);
|
||||
@include variable.default;
|
||||
|
||||
@@ -7,11 +7,15 @@ installPolyfills();
|
||||
global.IS_TESTING = true;
|
||||
global.Actual = {};
|
||||
|
||||
jest.mock(
|
||||
vi.mock(
|
||||
'react-virtualized-auto-sizer',
|
||||
() =>
|
||||
({ children }) =>
|
||||
children({ height: 1000, width: 600 }),
|
||||
() => ({
|
||||
default: (props) => {
|
||||
return (
|
||||
props.children({height: 1000, width: 600})
|
||||
);
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
global.Date.now = () => 123456789;
|
||||
165
packages/desktop-client/vite.config.mts
Normal file
165
packages/desktop-client/vite.config.mts
Normal file
@@ -0,0 +1,165 @@
|
||||
import * as path from 'path';
|
||||
|
||||
import inject from '@rollup/plugin-inject';
|
||||
import basicSsl from '@vitejs/plugin-basic-ssl';
|
||||
import react from '@vitejs/plugin-react-swc';
|
||||
import { visualizer } from 'rollup-plugin-visualizer';
|
||||
/// <reference types="vitest" />
|
||||
import { defineConfig, loadEnv, Plugin } from 'vite';
|
||||
import viteTsconfigPaths from 'vite-tsconfig-paths';
|
||||
|
||||
const addWatchers = (): Plugin => ({
|
||||
name: 'add-watchers',
|
||||
configureServer(server) {
|
||||
server.watcher
|
||||
.add([
|
||||
path.resolve('../loot-core/lib-dist/*.js'),
|
||||
path.resolve('../loot-core/lib-dist/browser/*.js'),
|
||||
])
|
||||
.on('all', function () {
|
||||
for (const wsc of server.ws.clients) {
|
||||
wsc.send(JSON.stringify({ type: 'static-changed' }));
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
// Inject build shims using the inject plugin
|
||||
const injectShims = (): Plugin[] => {
|
||||
const buildShims = path.resolve('./src/build-shims.js');
|
||||
const commonInject = {
|
||||
exclude: ['src/setupTests.jsx'],
|
||||
global: [buildShims, 'global'],
|
||||
};
|
||||
|
||||
return [
|
||||
{
|
||||
name: 'inject-build-process',
|
||||
config: () => ({
|
||||
// rename process.env in build mode so it doesn't get set to an empty object up by the vite:define plugin
|
||||
// this isn't needed in serve mode, because vite:define doesn't empty it in serve mode. And defines also happen last anyways in serve mode.
|
||||
define: {
|
||||
'process.env': `_process.env`,
|
||||
},
|
||||
}),
|
||||
apply: 'build',
|
||||
},
|
||||
{
|
||||
...inject({
|
||||
...commonInject,
|
||||
process: [buildShims, 'process'],
|
||||
}),
|
||||
enforce: 'post',
|
||||
apply: 'serve',
|
||||
},
|
||||
{
|
||||
...inject({
|
||||
...commonInject,
|
||||
_process: [buildShims, 'process'],
|
||||
}),
|
||||
enforce: 'post',
|
||||
apply: 'build',
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default defineConfig(async ({ mode }) => {
|
||||
const env = loadEnv(mode, process.cwd(), '');
|
||||
const devHeaders = {
|
||||
'Cross-Origin-Opener-Policy': 'same-origin',
|
||||
'Cross-Origin-Embedder-Policy': 'require-corp',
|
||||
};
|
||||
|
||||
// Forward Netlify env variables
|
||||
if (process.env.REVIEW_ID) {
|
||||
process.env.REACT_APP_REVIEW_ID = process.env.REVIEW_ID;
|
||||
}
|
||||
|
||||
let resolveExtensions = [
|
||||
'.web.js',
|
||||
'.web.jsx',
|
||||
'.web.ts',
|
||||
'.web.tsx',
|
||||
'.mjs',
|
||||
'.js',
|
||||
'.mts',
|
||||
'.ts',
|
||||
'.jsx',
|
||||
'.tsx',
|
||||
'.json',
|
||||
];
|
||||
|
||||
if (env.IS_GENERIC_BROWSER) {
|
||||
resolveExtensions = [
|
||||
'.browser.js',
|
||||
'.browser.jsx',
|
||||
'.browser.ts',
|
||||
'.browser.tsx',
|
||||
...resolveExtensions,
|
||||
];
|
||||
}
|
||||
|
||||
return {
|
||||
base: '/',
|
||||
envPrefix: 'REACT_APP_',
|
||||
build: {
|
||||
target: 'es2022',
|
||||
sourcemap: true,
|
||||
outDir: 'build',
|
||||
assetsDir: 'static',
|
||||
manifest: true,
|
||||
assetsInlineLimit: 0,
|
||||
chunkSizeWarningLimit: 1500,
|
||||
rollupOptions: {
|
||||
output: {
|
||||
assetFileNames: assetInfo => {
|
||||
const info = assetInfo.name.split('.');
|
||||
let extType = info[info.length - 1];
|
||||
if (/png|jpe?g|svg|gif|tiff|bmp|ico/i.test(extType)) {
|
||||
extType = 'img';
|
||||
} else if (/woff|woff2/.test(extType)) {
|
||||
extType = 'media';
|
||||
}
|
||||
return `static/${extType}/[name]-[hash][extname]`;
|
||||
},
|
||||
chunkFileNames: 'static/js/[name]-[hash].js',
|
||||
entryFileNames: 'static/js/[name]-[hash].js',
|
||||
},
|
||||
},
|
||||
},
|
||||
server: {
|
||||
headers: mode === 'development' ? devHeaders : undefined,
|
||||
port: +env.PORT || 5173,
|
||||
open: env.BROWSER
|
||||
? ['chrome', 'firefox', 'edge', 'browser', 'browserPrivate'].includes(
|
||||
env.BROWSER,
|
||||
)
|
||||
: true,
|
||||
watch: {
|
||||
disableGlobbing: false,
|
||||
},
|
||||
},
|
||||
resolve: {
|
||||
extensions: resolveExtensions,
|
||||
},
|
||||
plugins: [
|
||||
injectShims(),
|
||||
addWatchers(),
|
||||
react({
|
||||
plugins: [['@swc/plugin-remove-console', {}]],
|
||||
devTarget: 'es2022',
|
||||
}),
|
||||
viteTsconfigPaths({ root: '../..' }),
|
||||
visualizer({ template: 'raw-data' }),
|
||||
!!env.HTTPS && basicSsl(),
|
||||
],
|
||||
test: {
|
||||
include: ['src/**/*.{test,spec}.?(c|m)[jt]s?(x)'],
|
||||
environment: 'jsdom',
|
||||
globals: true,
|
||||
setupFiles: './src/setupTests.jsx',
|
||||
},
|
||||
};
|
||||
});
|
||||
@@ -19,6 +19,7 @@
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"skipLibCheck": true,
|
||||
"jsx": "preserve",
|
||||
"types": ["vite/client", "jest"],
|
||||
// Check JS files too
|
||||
"allowJs": true,
|
||||
"checkJs": false,
|
||||
|
||||
6
upcoming-release-notes/2084.md
Normal file
6
upcoming-release-notes/2084.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
category: Maintenance
|
||||
authors: [twk3]
|
||||
---
|
||||
|
||||
Switch desktop-client to the Vite JS framework.
|
||||
Reference in New Issue
Block a user