V3: Migrate to Vite (#221)

This is version 3 of the boilerplate, which makes significant changes to the build script and local development tooling. It replaces Webpack with Vite, which required some significant changes to how the local development setup works with Google Apps Script.

A new dev-server-wrapper.html file is used as the wrapper app in development, which is a standalone html + js file instead of a React app. This reduces the need for a complex build step for the wrapper app. Instead the file is just copied over for each entrypoing and the file location is changed with a "replace" step.

Due to the way Vite works, the html templates are changed a bit to support local development.

Vite doesn't easily allow completely separate multiple builds, so some Vite plugins and custom plugins are written to support multiple entrypoints (sidebars/dialogs). The externalization for large packages is now handled in the Vite config instead of a plugin, and by manually adding in the script tags into the index.html templates.

Additional changes here:
- yarn is now used instead of npm as package manager
- eslint configs are updated to support vite
- GitHub workflows for tests are updated to use yarn and other minor changes such as node versions and OS.
- A VS Code launch.json configuration is provided
- Dev wrapper setup has been updated (see above description)
- ES Modules (import/export) updated throughout
- Unneeded packages supporting webpack configurations are removed. Many packages have been upgraded. NPM scripts have been updated to use yarn.
- Removed `import React` due to Vite config.
- Added script tags to each template since webpack externalization plugin is no longer used
- Added <script type="module" src="./index.jsx"></script> to index.html templates to support Vite local development
- Changed src/server/sheets.js to .ts typescript as exemplar
- Updated tests to support yarn commands, Vite changes, and listening for Vite stdout triggers.
- Add Vite-style tsconfig.json
- README is updated
This commit is contained in:
Elisha Nuchi
2024-05-20 00:25:31 -04:00
committed by GitHub
parent 5f2012ce56
commit 31befe1d68
54 changed files with 8328 additions and 35322 deletions
+1 -4
View File
@@ -1,6 +1,3 @@
{
"plugins": [
"@babel/plugin-proposal-object-rest-spread",
"@babel/plugin-proposal-class-properties"
]
"presets": ["@babel/preset-env"]
}
-40
View File
@@ -1,40 +0,0 @@
# reference code: https://stackoverflow.com/questions/70226493/webpack-dev-server-and-https-this-site-can-t-be-reached
# reference issue: https://github.com/FiloSottile/mkcert/issues/286
$dnsName = "localhost"
$expiry = [DateTime]::Now.AddYears(1);
$repoRoot = Split-Path $PSScriptRoot
$certsDir = "$repoRoot\certs";
$fileName = "cert.pfx";
$passwordText = "abc123";
$name = "ReactApp";
Write-Host "Creating cert directly into CurrentUser\My store"
$certificate = New-SelfSignedCertificate `
-KeyExportPolicy 'Exportable' `
-CertStoreLocation Cert:\CurrentUser\My `
-Subject $name `
-FriendlyName $name `
-DnsName $dnsName `
-NotAfter $expiry
$certFile = Join-Path $certsDir $fileName
Write-Host "Exporting certificate to $certFile"
$password = ConvertTo-SecureString `
-String $passwordText `
-Force -AsPlainText
Export-PfxCertificate `
-Cert $certificate `
-FilePath $certFile `
-Password $password | Out-Null
Write-Host "Importing $certFile to CurrentUser\Root store for immediate system wide trust"
Import-PfxCertificate `
-FilePath $certFile `
-CertStoreLocation Cert:\LocalMachine\Root `
-Password $password | Out-Null
+10 -9
View File
@@ -2,18 +2,19 @@
// jestjs.io/docs/puppeteer#custom-example-without-jest-puppeteer-preset
// This allows using stealth mode.
const { mkdir, writeFile } = require('fs').promises;
const os = require('os');
const path = require('path');
const puppeteer = require('puppeteer-extra');
import fs from 'fs';
const fsPromises = fs.promises;
import os from 'os';
import path from 'path';
import puppeteer from 'puppeteer-extra';
// add stealth plugin and use defaults (all evasion techniques)
const StealthPlugin = require('puppeteer-extra-plugin-stealth');
import StealthPlugin from 'puppeteer-extra-plugin-stealth';
const DIR = path.join(os.tmpdir(), 'jest_puppeteer_global_setup');
const jestPuppeteerConfig = require('./jest-puppeteer.config');
import jestPuppeteerConfig from './jest-puppeteer.config.js';
module.exports = async function globalSetup() {
export default async function globalSetup() {
puppeteer.use(StealthPlugin());
const browser = await puppeteer.launch(jestPuppeteerConfig.launch);
// store the browser instance so we can teardown it later
@@ -21,6 +22,6 @@ module.exports = async function globalSetup() {
global.__BROWSER_GLOBAL__ = browser;
// use the file system to expose the wsEndpoint for TestEnvironments
await mkdir(DIR, { recursive: true });
await writeFile(path.join(DIR, 'wsEndpoint'), browser.wsEndpoint());
await fsPromises.mkdir(DIR, { recursive: true });
await fsPromises.writeFile(path.join(DIR, 'wsEndpoint'), browser.wsEndpoint());
};
+6 -5
View File
@@ -2,15 +2,16 @@
// jestjs.io/docs/puppeteer#custom-example-without-jest-puppeteer-preset
// This allows using stealth mode.
const fs = require('fs').promises;
const os = require('os');
const path = require('path');
import fs from 'fs';
const fsPromises = fs.promises;
import os from 'os';
import path from 'path';
const DIR = path.join(os.tmpdir(), 'jest_puppeteer_global_setup');
module.exports = async function globalTeardown() {
export default async function globalTeardown() {
// close the browser instance
await global.__BROWSER_GLOBAL__.close();
// clean-up the wsEndpoint file
await fs.rmdir(DIR, { recursive: true, force: true });
await fsPromises.rmdir(DIR, { recursive: true, force: true });
};
+1 -1
View File
@@ -1,4 +1,4 @@
module.exports = {
export default {
launch: {
headless: false,
product: 'chrome',
+11 -10
View File
@@ -1,10 +1,11 @@
const fs = require('fs');
const path = require('path');
const { exec } = require('child_process');
const { configureToMatchImageSnapshot } = require('jest-image-snapshot');
const { openAddon } = require('./utils/open-addon');
import fs from 'fs';
import path from 'path';
import { exec } from 'child_process';
import { configureToMatchImageSnapshot } from 'jest-image-snapshot';
import { openAddon } from './utils/open-addon';
require('dotenv').config();
import dotenv from 'dotenv';
dotenv.config();
const isExtended = `${process.env.IS_EXTENDED}` === 'true';
@@ -26,10 +27,10 @@ const srcTestFile = path.join(
);
const webpackDevServerReady = async (process) => {
console.log('Waiting for Webpack Dev Server to finish loading...');
console.log('Waiting for vite to serve...');
return new Promise((resolve) => {
process.stdout.on('data', (data) => {
if (data.includes('CLIENT - Dialog Demo Bootstrap')) {
if (data.includes('ready in')) {
resolve();
}
});
@@ -42,7 +43,7 @@ describe(`Local setup ${isExtended ? '*extended*' : ''}`, () => {
const containerSelector = isExtended ? '.script-app-dialog' : 'body';
beforeAll(async () => {
process = exec('npm run serve');
process = exec('yarn dev');
page = await global.__BROWSER_GLOBAL__.newPage();
await page.setViewport({
@@ -56,7 +57,7 @@ describe(`Local setup ${isExtended ? '*extended*' : ''}`, () => {
if (isExtended) {
await openAddon(page);
} else {
await page.goto('https://localhost:3000/dialog-demo-bootstrap-impl.html');
await page.goto('https://localhost:3000/dialog-demo-bootstrap/index.html');
await page.waitForTimeout(3000);
}
});
+7 -9
View File
@@ -2,15 +2,15 @@
// jestjs.io/docs/puppeteer#custom-example-without-jest-puppeteer-preset
// This allows using stealth mode.
const { readFile } = require('fs').promises;
const os = require('os');
const path = require('path');
const puppeteer = require('puppeteer');
const NodeEnvironment = require('jest-environment-node').default;
import { readFile } from 'fs/promises';
import os from 'os';
import path from 'path';
import puppeteer from 'puppeteer';
import NodeEnvironment from 'jest-environment-node';
const DIR = path.join(os.tmpdir(), 'jest_puppeteer_global_setup');
class PuppeteerEnvironment extends NodeEnvironment {
export default class PuppeteerEnvironment extends NodeEnvironment.default {
constructor(config) {
super(config);
}
@@ -36,6 +36,4 @@ class PuppeteerEnvironment extends NodeEnvironment {
getVmContext() {
return super.getVmContext();
}
}
module.exports = PuppeteerEnvironment;
}
+7 -5
View File
@@ -10,13 +10,15 @@
*
* Note: If image reporter doesn't work in pipeline can try running as standalone script
* by creating a separate script file and running like this:
* "test:integration": "jest test/local-development.test || node test/utils/image-reporter-standalone.js"
* "test:integration:extended:report": "cross-env IS_EXTENDED=true jest --forceExit test/local-development.test || node test/utils/image-reporter-standalone.js",
*/
const fs = require('fs');
const AWS = require('aws-sdk/global');
const S3 = require('aws-sdk/clients/s3'); // this is needed
require('dotenv').config();
import fs from 'fs';
import AWS from 'aws-sdk/global.js';
import S3 from 'aws-sdk/clients/s3.js'; // this is needed
import dotenv from 'dotenv';
dotenv.config();
const UPLOAD_BUCKET = process.env.S3_BUCKET_NAME;
+8 -8
View File
@@ -13,10 +13,12 @@
* "test:integration": "jest test/local-development.test || node test/utils/image-reporter-standalone.js"
*/
const fs = require('fs');
const AWS = require('aws-sdk/global');
const S3 = require('aws-sdk/clients/s3'); // this is needed
require('dotenv').config();
import fs from 'fs';
import AWS from 'aws-sdk/global.js';
import S3 from 'aws-sdk/clients/s3.js'; // this is needed
import dotenv from "dotenv";
dotenv.config();
const UPLOAD_BUCKET = process.env.S3_BUCKET_NAME;
@@ -27,7 +29,7 @@ AWS.config.update({
const s3 = new AWS.S3({ apiVersion: '2006-03-01' });
class ImageReporter {
export default class ImageReporter {
constructor(globalConfig, options) {
this._globalConfig = globalConfig;
this._options = options;
@@ -78,6 +80,4 @@ class ImageReporter {
});
}
}
}
module.exports = ImageReporter;
}
+1 -3
View File
@@ -1,4 +1,4 @@
const openAddon = async (page) => {
export const openAddon = async (page) => {
await page.goto(process.env.SHEET_URL);
await page.click('a:nth-child(2)'); // click on signin button
@@ -104,5 +104,3 @@ const openAddon = async (page) => {
await page.waitForTimeout(3000);
};
module.exports = { openAddon };