Files
gas-react-sheets/vite.config.ts
T
Elisha Nuchi 85223ca42a Chore: Update eslint configuration (#224)
- Update missing dev dependencies
- Fix root .eslintrc.json and client and server versions
- Add lint.yaml workflow
- Remove babel settings
- Clean up and fix eslint and ts errors
2024-06-02 18:01:12 -04:00

216 lines
6.7 KiB
TypeScript

/* eslint-disable import/no-extraneous-dependencies */
import { resolve } from 'path';
import { BuildOptions, ServerOptions, build, defineConfig } from 'vite';
import { existsSync, readFileSync } from 'fs';
import react from '@vitejs/plugin-react-swc';
import { viteStaticCopy } from 'vite-plugin-static-copy';
import { viteSingleFile } from 'vite-plugin-singlefile';
import { writeFile } from 'fs/promises';
const PORT = 3000;
const clientRoot = './src/client';
const outDir = './dist';
const serverEntry = 'src/server/index.ts';
const copyAppscriptEntry = './appsscript.json';
const devServerWrapper = './dev/dev-server-wrapper.html';
const clientEntrypoints = [
{
name: 'CLIENT - Dialog Demo',
filename: 'dialog-demo', // we'll add the .html suffix to these
template: 'dialog-demo/index.html',
},
{
name: 'CLIENT - Dialog Demo Bootstrap',
filename: 'dialog-demo-bootstrap',
template: 'dialog-demo-bootstrap/index.html',
},
{
name: 'CLIENT - Dialog Demo MUI',
filename: 'dialog-demo-mui',
template: 'dialog-demo-mui/index.html',
},
{
name: 'CLIENT - Dialog Demo Tailwind CSS',
filename: 'dialog-demo-tailwindcss',
template: 'dialog-demo-tailwindcss/index.html',
},
{
name: 'CLIENT - Sidebar About Page',
filename: 'sidebar-about-page',
template: 'sidebar-about-page/index.html',
},
];
const keyPath = resolve(__dirname, './certs/key.pem');
const certPath = resolve(__dirname, './certs/cert.pem');
const pfxPath = resolve(__dirname, './certs/cert.pfx'); // if needed for Windows
const devServerOptions: ServerOptions = {
port: PORT,
};
// use key and cert settings only if they are found
if (existsSync(keyPath) && existsSync(certPath)) {
devServerOptions.https = {
key: readFileSync(resolve(__dirname, './certs/key.pem')),
cert: readFileSync(resolve(__dirname, './certs/cert.pem')),
};
}
// If mkcert -install cannot be used on Windows machines (in pipeline, for example), the
// script at scripts/generate-cert.ps1 can be used to create a .pfx cert
if (existsSync(pfxPath)) {
// use pfx file if it's found
devServerOptions.https = {
pfx: readFileSync(pfxPath),
passphrase: 'abc123',
};
}
const clientServeConfig = () =>
defineConfig({
plugins: [react()],
server: devServerOptions,
root: clientRoot,
});
const clientBuildConfig = ({
clientEntrypointRoot,
template,
}: {
clientEntrypointRoot: string;
template: string;
}) =>
defineConfig({
plugins: [react(), viteSingleFile({ useRecommendedBuildConfig: true })],
root: resolve(__dirname, clientRoot, clientEntrypointRoot),
build: {
sourcemap: false,
write: false, // don't write to disk
outDir,
emptyOutDir: true,
minify: true,
rollupOptions: {
external: [
'react',
'react-dom',
'react-transition-group',
'react-bootstrap',
'@mui/material',
'@emotion/react',
'@emotion/styled',
'gas-client',
'@types/react',
],
output: {
format: 'iife', // needed to use globals from UMD builds
dir: outDir,
globals: {
react: 'React',
'react-dom': 'ReactDOM',
'react-transition-group': 'ReactTransitionGroup',
'react-bootstrap': 'ReactBootstrap',
'@mui/material': 'MaterialUI',
'@emotion/react': 'emotionReact',
'@emotion/styled': 'emotionStyled',
'gas-client': 'GASClient',
'@types/react': '@types/react',
},
},
input: resolve(__dirname, clientRoot, template),
},
},
});
const serverBuildConfig: BuildOptions = {
emptyOutDir: true,
minify: false, // needed to work with footer
lib: {
entry: resolve(__dirname, serverEntry),
fileName: 'code',
name: 'globalThis',
formats: ['iife'],
},
rollupOptions: {
output: {
entryFileNames: 'code.js',
extend: true,
footer: (chunk) =>
chunk.exports
.map((exportedFunction) => `function ${exportedFunction}() {};`)
.join('\n'),
},
},
};
const buildConfig = ({ mode }: { mode: string }) => {
const targets = [{ src: copyAppscriptEntry, dest: './' }];
if (mode === 'development') {
targets.push(
...clientEntrypoints.map((entrypoint) => ({
src: devServerWrapper,
dest: './',
rename: `${entrypoint.filename}.html`,
transform: (contents: string) =>
contents
.toString()
.replace(/__PORT__/g, String(PORT))
.replace(/__FILE_NAME__/g, entrypoint.template),
}))
);
}
return defineConfig({
plugins: [
viteStaticCopy({
targets,
}),
/**
* This builds the client react app bundles for production, and writes them to disk.
* Because multiple client entrypoints (dialogs) are built, we need to loop through
* each entrypoint and build the client bundle for each. Vite doesn't have great tooling for
* building multiple single-page apps in one project, so we have to do this manually with a
* post-build closeBundle hook (https://rollupjs.org/guide/en/#closebundle).
*/
mode === 'production' && {
name: 'build-client-production-bundles',
closeBundle: async () => {
console.log('Building client production bundles...');
// eslint-disable-next-line no-restricted-syntax
for (const clientEntrypoint of clientEntrypoints) {
console.log('Building client bundle for', clientEntrypoint.name);
// eslint-disable-next-line no-await-in-loop
const buildOutput = await build(
clientBuildConfig({
clientEntrypointRoot: clientEntrypoint.filename,
template: clientEntrypoint.template,
})
);
// eslint-disable-next-line no-await-in-loop
await writeFile(
resolve(__dirname, outDir, `${clientEntrypoint.filename}.html`),
// @ts-expect-error - output is an array of RollupOutput
buildOutput.output[0].source
);
}
console.log('Finished building client bundles!');
},
},
].filter(Boolean),
build: serverBuildConfig,
});
};
// https://vitejs.dev/config/
export default async ({ command, mode }: { command: string; mode: string }) => {
if (command === 'serve') {
// for 'serve' mode, we only want to serve the client bundle locally
return clientServeConfig();
}
if (command === 'build') {
// for 'build' mode, we have two paths: build assets for local development, and build for production
return buildConfig({ mode });
}
return {};
};