├── .clasp.json ├── .eslintrc.cjs ├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── .prettierignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── HISTORY.md ├── README.md ├── __tests__ └── generateRandomHexColor.test.js ├── appsscript.json ├── dist ├── copy-paste │ └── app.iife.js ├── gas │ ├── appsscript.json │ ├── exports.js │ ├── server │ │ └── server.iife.js │ └── ui │ │ └── index.html └── node │ ├── app.js │ └── app.umd.cjs ├── env-mgt ├── ENV.js ├── dev │ └── .clasp.json ├── prod │ └── .clasp.json ├── set-env.js └── uat │ └── .clasp.json ├── index.html ├── jsconfig.json ├── package-lock.json ├── package.json ├── postcss.config.cjs ├── prettier.config.cjs ├── src ├── app.js ├── client │ ├── app.js │ ├── assets │ │ └── favicon.ico │ ├── credits.js │ ├── getMocks.js │ ├── isJest.js │ ├── polyfillScriptRun.js │ ├── runGas.js │ └── styles.css └── server │ ├── helpers.js │ └── server.js ├── tailwind.config.cjs ├── types └── colors.js ├── vite-plugin ├── traverse │ ├── collect-function-declarations.js │ ├── extract-export-details.js │ ├── extract-exports.js │ ├── extract-params.js │ └── find-exports-identifier.js └── vite-plugin-appsscript.js ├── vite.config.copy-paste.js ├── vite.config.gas.js ├── vite.config.js └── vite.config.node.js /.clasp.json: -------------------------------------------------------------------------------- 1 | {"scriptId": "", "rootDir": "dist/gas"} -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: ['googleappsscript'], 3 | env: { 4 | browser: true, 5 | es2021: true, 6 | node: true, 7 | 'googleappsscript/googleappsscript': true, 8 | }, 9 | extends: [ 10 | 'eslint:recommended', 11 | 'prettier', 12 | ], 13 | overrides: [], 14 | parserOptions: { 15 | ecmaVersion: 'latest', 16 | sourceType: 'module', 17 | project: './jsconfig.json', 18 | }, 19 | rules: { 20 | 'no-extra-boolean-cast': 'off', 21 | 'no-unused-vars': 'off', 22 | }, 23 | }; 24 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist-ssr 12 | *.local 13 | .husky 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | dist/ui/ 2 | *.min.js 3 | **/output.css -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to make participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | - Using welcoming and inclusive language 18 | - Being respectful of differing viewpoints and experiences 19 | - Gracefully accepting constructive criticism 20 | - Focusing on what is best for the community 21 | - Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | - The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | - Trolling, insulting/derogatory comments, and personal or political attacks 28 | - Public or private harassment 29 | - Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | - Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies within all project spaces, and it also applies when 49 | an individual is representing the project or its community in public spaces. 50 | Examples of representing a project or community include using an official 51 | project e-mail address, posting via an official social media account, or acting 52 | as an appointed representative at an online or offline event. Representation of 53 | a project may be further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | First off, thanks for taking the time to contribute! ❤️ 4 | 5 | When contributing to this repository, please first discuss the change you wish to make via issue. You may pick an existing issue if it hasn't been assigned yet, or suggest your own. 6 | 7 | See also the [Code of Conduct](CODE_OF_CONDUCT.md) 8 | 9 | ## Creating Issues 10 | 11 | Issues should be used to report problems with the library, request a new feature, or to discuss potential changes before a PR is created. When you create a new Issue, a template will be loaded that will guide you through collecting and providing the information we need to investigate. 12 | 13 | Search for existing Issues and PRs before creating your own. 14 | 15 | If you find an Issue that addresses the problem you're having, please add your own reproduction information to the existing issue rather than creating a new one. Adding a reaction can also help be indicating to our maintainers that a particular problem is affecting more than just the reporter. 16 | 17 | ## GitHub Flow 18 | 19 | - Fork the repository to your own Github account 20 | - Clone the project to your machine 21 | - Create a branch locally with a succinct but descriptive name 22 | - Commit changes to the branch 23 | - Following any formatting and testing guidelines specific to this repo 24 | - Push changes to your fork 25 | - Open a PR in our repository and follow the PR template so that we can efficiently review the changes. 26 | 27 | ## Check List 28 | 29 | 1. Make sure that code you merge is fully covered by unit tests, otherwise your pull request will not be merged. I strongly suggest using TDD. 30 | 2. Update the README.md with details of changes to the interface, this includes new environment variables, exposed ports, useful file locations and container parameters. 31 | 3. Increase the version numbers in any examples files and the `README.md` to the new version that this Pull Request would represent. The versioning scheme we use is [SemVer](http://semver.org/). 32 | 4. Increase the version number in `package.json`. 33 | 5. Update the `HISTORY.md` file. 34 | 6. Make sure your code is clean, readable, linted and prettified. 35 | 7. Remember to add JSDoc comments. 36 | 8. Every function must reside in its own file. 37 | 9. As a general guideline, no function should be longer than 40 lines; shorter is better. 38 | -------------------------------------------------------------------------------- /HISTORY.md: -------------------------------------------------------------------------------- 1 | # History 2 | 3 | ## Version 1.3.2 | 2025-06-06 4 | 5 | - Update dependencies 6 | - Update formatting 7 | - Migrate from Jest to Vitest 8 | 9 | ## Version 1.3.1 | 2024-09-05 10 | 11 | - Add support for rest parameters in exported functions 12 | - Add code of conduct 13 | - Add contributing guide 14 | 15 | ## Version 1.3.0 | 2024-09-04 16 | 17 | - Automatic compilation of TailwindCSS 18 | - Added PostCSS and Autoprefixer 19 | - Upgrade Vite to v5 20 | - Upgrade DaisyUI to v4 21 | - Added dist folder to .gitignore 22 | 23 | ## Version 1.2.0 | 2024-08-22 24 | 25 | - Write full documentation in README 26 | - Variable name change in environment management dependency 27 | 28 | ## Version 1.1.0 | 2024-06-07 29 | 30 | - Upgrade Vite plugin to use ASM to generate the `dist/exports.js` file 31 | - Bundle all server-side and client-side code 32 | - Include library buliding mode, supporting NPM and copy & paste 33 | 34 | ## Version 1.0.0 | 2024-06-07 35 | 36 | - Migrate from TypeScript to JSDoc 37 | - Add a generic favicon for local development 38 | - Add credits to the console 39 | - Add Vite config file for Apps Script 40 | - Add Vite plugin for Apps Script 41 | - Migrate client-side code to `src/client` 42 | - Migrate Apps Script code to `src/server` 43 | - Add environment management tool 44 | - Add HISTORY.md 45 | 46 | ## Version 0.0.1 | 2023-06-21 47 | 48 | - Initial deployment 49 | - TypeScript, Alpine.js, TailwindCSS, DaisyUI 50 | - Husky, Jest, ESLint, Prettier, Vite 51 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Welcome to the Apps Script Engine Template! 👋 2 | 3 | ![Version](https://img.shields.io/badge/version-1.3.2-blue.svg?cacheSeconds=2592000) 4 | 5 | > Kickstart your Google Apps Script projects with this robust, highly configurable template that includes essential development tools right out of the box. 6 | 7 | ## Table of Contents 8 | 9 | 1. [Welcome to the Apps Script Engine Template! 👋](#welcome-to-the-apps-script-engine-template-) 10 | 2. [About](#about) 11 | - [The Problems it Solves](#the-problems-it-solves) 12 | 3. [Prerequisites](#prerequisites) 13 | 4. [Install](#install) 14 | 5. [Global Settings](#global-settings) 15 | - [Template Structure](#template-structure) 16 | - [NPM Modules](#npm-modules) 17 | - [Dependencies](#dependencies) 18 | - [External](#external) 19 | - [Custom](#custom) 20 | - [Formatting](#formatting) 21 | - [Run Tests](#run-tests) 22 | - [Git Hooks](#git-hooks) 23 | 6. [Managing Environments](#managing-environments) 24 | - [Environment Configuration](#environment-configuration) 25 | 7. [Client-Side `HtmlService` Code](#client-side-htmlservice-code) 26 | - [The Philosophy](#the-philosophy) 27 | - [The Solution](#the-solution) 28 | - [The Stack](#the-stack) 29 | - [Running the Local Development Server with Vite](#running-the-local-development-server-with-vite) 30 | - [`google.script.run` Promisified](#googlescriptrun-promisified) 31 | - [Mocking Server-Side Apps Script Functions](#mocking-server-side-apps-script-functions) 32 | - [Building and Deploying](#building-and-deploying) 33 | 8. [Server-Side Google Apps Script Code](#server-side-google-apps-script-code) 34 | - [The Philosophy](#the-philosophy-1) 35 | - [Building and Deploying](#building-and-deploying-1) 36 | - [NPM Modules](#npm-modules-1) 37 | - [Server-Side Code Configuration](#server-side-code-configuration) 38 | 9. [NPM Library Code](#npm-library-code) 39 | 10. [Copy-and-Paste Code](#copy-and-paste-code) 40 | 11. [Other Resources](#other-resources) 41 | 12. [Author](#author) 42 | 13. [Show Your Support](#show-your-support) 43 | 44 | ## About 45 | 46 | This is a Node-powered, highly opinionated yet easily configurable template designed for creating outstanding Google Apps Script projects. It includes top-tier software engineering tools, pre-setup dev tools like Husky, Prettier, ESLint, and Jest, and offers seamless support for CI/CD workflows, such as GitHub Actions. With this template, you can easily include NPM modules in your projects and support multiple environments (development, staging, production, and more). It also enables the use of ES6 modules in your source code. 47 | 48 | The template is set up to bundle front-ends built with `HtmlService`, back-end Apps Script code, and Apps Script libraries (both native and deployable on NPM). 49 | 50 | ### The Problems it Solves 51 | 52 | - **ES6 Modules in Apps Script:** Work seamlessly with modern JavaScript. 53 | - **Fast Local Development:** Develop both client-side and server-side code locally with mock functions and promisified calls to `google.script.run`. 54 | - **Support for Front-End Frameworks:** Comes with Alpine.js and Tailwind CSS (with the Daisy UI plugin) by default. TypeScript support is also easy to add. 55 | - **NPM Modules Support:** Integrate NPM modules for both front-end and back-end code. 56 | - **Unit Testing:** Set up with Jest to ensure your code works as expected. 57 | - **CI/CD Workflows:** Integrate with GitHub Actions or Cloud Build for robust, automated deployments. 58 | - **Optimized Deployments:** Streamline the deployment process for server-side code, library code, and copy-and-paste code. 59 | - **Environment Management:** Built-in support for different environments (DEV, UAT, PROD) with specific configurations and environment files for each. 60 | 61 | ## Prerequisites 62 | 63 | Ensure that you have `npm`, `git`, and `clasp` installed by running: 64 | 65 | ```shell 66 | npm --version 67 | git --version 68 | clasp --version 69 | ``` 70 | 71 | Yes, we know clasp is no longer maintained; when it breaks, we'll have our own solution ready. 72 | 73 | ## Installation 74 | 75 | To install the template, run the following command with an optional directory name: 76 | 77 | ```shell 78 | npx apps-script-engine [directory-name] 79 | ``` 80 | 81 | If no directory name is provided, it will default to `./apps-script-project`. 82 | 83 | To create a new Apps Script project in the current directory: 84 | 85 | ```shell 86 | npx apps-script-engine . 87 | ``` 88 | 89 | Or in a specific directory: 90 | 91 | ```shell 92 | npx apps-script-engine my-project-directory 93 | ``` 94 | 95 | The script will generate the necessary boilerplate files in the specified directory. 96 | 97 | ## Global Settings 98 | 99 | The Apps Script Engine has global settings as well as front-end and back-end-specific ones. 100 | 101 | ### Template Structure 102 | 103 | Below is the full template structure. The main components include the `src` folder, which contains the client-side and server-side code boilerplate, an environment management tool, a unit tests folder, a custom Vite plugin, and several configuration files. Everything is discussed in detail below: 104 | 105 | ```text 106 | . 107 | ├── dist 108 | │   ├── copy-paste 109 | │   │   └── app.iife.js 110 | │   ├── gas 111 | │   │   ├── server 112 | │   │   │   └── server.iife.js 113 | │   │   ├── ui 114 | │   │   │   └── index.html 115 | │   │   ├── appsscript.json 116 | │   │   └── exports.js 117 | │   └── node 118 | │   ├── app.js 119 | │   └── app.umd.cjs 120 | ├── env-mgt 121 | │   ├── dev 122 | │   │   └── .clasp.json 123 | │   ├── prod 124 | │   │   └── .clasp.json 125 | │   ├── uat 126 | │   │   └── .clasp.json 127 | │   ├── ENV.js 128 | │   └── set-env.js 129 | ├── .husky 130 | │   ├── _ 131 | │   │   ├── .gitignore 132 | │   │   └── husky.sh 133 | │   └── pre-commit 134 | ├── src 135 | │   ├── client 136 | │   │   ├── assets 137 | │   │   │   └── favicon.ico 138 | │   │   ├── app.js 139 | │   │   ├── styles.css 140 | │   │   ├── credits.js 141 | │   │   ├── getMocks.js 142 | │   │   ├── isJest.js 143 | │   │   └── runGas.js 144 | │   ├── server 145 | │   │   ├── helpers.js 146 | │   │   └── server.js 147 | │   └── app.js 148 | ├── __tests__ 149 | │   └── generateRandomHexColor.test.js 150 | ├── types 151 | │   └── colors.js 152 | ├── vite-plugin 153 | │   ├── traverse 154 | │   │   ├── collect-function-declarations.js 155 | │   │   ├── extract-export-details.js 156 | │   │   ├── extract-exports.js 157 | │   │   ├── extract-params.js 158 | │   │   └── find-exports-identifier.js 159 | │   └── vite-plugin-appsscript.js 160 | ├── appsscript.json 161 | ├── .clasp.json 162 | ├── .eslintrc.cjs 163 | ├── .gitignore 164 | ├── HISTORY.md 165 | ├── index.html 166 | ├── jsconfig.json 167 | ├── package.json 168 | ├── package-lock.json 169 | ├── prettier.config.cjs 170 | ├── postcss.config.cjs 171 | ├── .prettierignore 172 | ├── INSTRUCTIONS.md 173 | ├── tailwind.config.cjs 174 | ├── vite.config.copy-paste.js 175 | ├── vite.config.gas.js 176 | ├── vite.config.js 177 | └── vite.config.node.js 178 | ``` 179 | 180 | ### NPM Modules 181 | 182 | The template supports installing and bundling NPM modules in both client-side and server-side code. Simply install, import, and build your code as you normally would. The template handles the rest. 183 | 184 | ### Dependencies 185 | 186 | The Apps Script Engine Template uses the following external and custom dependencies: 187 | 188 | #### External 189 | 190 | - `@types/google-apps-script`: Type definitions for Apps Script. 191 | - `clasp`: For pushing the code to your Apps Script project. 192 | - `vite-plugin-singlefile`: Compiles HTML/CSS/JavaScript into a single file. 193 | 194 | #### Custom 195 | 196 | - **Environment Manager:** Located in `./env-mgt`, this tool updates the `.clasp.json` file with the correct script ID and any other environment-specific files. 197 | - **Custom Vite Plugin:** Facilitates the unique needs of Apps Script projects. 198 | 199 | ### Formatting 200 | 201 | To run linting (ESLint) and pretty-printing (Prettier) with the `--fix` and `--write` options respectively, use: 202 | 203 | ```sh 204 | npm run format 205 | ``` 206 | 207 | ### Run Tests 208 | 209 | To run the tests: 210 | 211 | ```sh 212 | npm t 213 | ``` 214 | 215 | ### Git Hooks 216 | 217 | The template comes with a pre-configured `pre-commit` Git hook using Husky. Every time you commit your code, it will automatically run formatting and testing by executing `npm run format && npm t`. 218 | 219 | ## Managing Environments 220 | 221 | The template is designed to be deployable to different Google Apps Script projects, each acting as a different environment. It works by copying relevant environmental files (like `.clasp.json`) from `env-mgt/` to the specified path (the root directory by default). You can add any number of files to each environment. Configurations are handled in the `env-mgt/ENV.js` file. 222 | 223 | ### Configuring Environments 224 | 225 | The `env-mgt/ENV.js` file defines the path for each environment-specific file to be copied. The default configuration looks like this: 226 | 227 | ```js 228 | // /env-mgt/ENV.js 229 | export default { 230 | dev: [ 231 | { 232 | fileName: '.clasp.json', 233 | filePath: 'env-mgt/dev', 234 | copyTo: './' 235 | } 236 | ], 237 | uat: [ 238 | { 239 | fileName: '.clasp.json', 240 | filePath: 'env-mgt/uat', 241 | copyTo: './' 242 | } 243 | ], 244 | prod: [ 245 | { 246 | fileName: '.clasp.json', 247 | filePath: 'env-mgt/prod', 248 | copyTo: './' 249 | } 250 | ], 251 | } 252 | ``` 253 | 254 | Each environment is represented by an object key and contains an array of objects specifying the file paths and their destinations. The `.clasp.json` files are typically copied to the root directory, where all other configuration files reside. 255 | 256 | The `package.json` file contains scripts that allow you to switch environments: 257 | 258 | ```json 259 | { 260 | "scripts": { 261 | "env:dev": "node ./env-mgt/set-env.js dev", 262 | "env:uat": "node ./env-mgt/set-env.js uat", 263 | "env:prod": "node ./env-mgt/set-env.js prod" 264 | } 265 | } 266 | ``` 267 | 268 | You can follow this format to add or remove environments as needed. 269 | 270 | ## Client-Side `HtmlService` Code 271 | 272 | For web apps, sidebars, or modal dialogs built with `HtmlService`, the source code is located in `src/client`. 273 | 274 | ### The Philosophy 275 | 276 | There are two primary ways to build front-ends with Apps Script: 277 | 278 | 1. **Server-Side Rendering (SSR):** You can directly create an `HTMLOutput` or `HTMLTemplate` that allows you to pass data to the front-end using template properties and the `<% %>` tags. 279 | 2. **Ajax Calls with `google.script.run`:** Alternatively, you can call an Apps Script function directly from the front-end using `google.script.run`, and handle return values and errors with callbacks. 280 | 281 | However, these approaches have limitations: 282 | 283 | 1. **Local Development Constraints:** You can't run Apps Script locally on your machine. Each time you update, you must wait for clasp to push your code, switch environments, run the code, determine if it works, and repeat. This process is slow and inefficient. 284 | 2. **`google.script.run` Limitations:** You can't execute `google.script.run` locally by default, as the library isn't included. Even if it were, you can't call Apps Script functions from your local environment. 285 | 3. **File Organization Limitations:** Apps Script doesn't support using multiple JavaScript files with imports and exports, or references like ` 191 | 192 | 193 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "NodeNext", 4 | "target": "ES6", 5 | "checkJs": true, 6 | "types": ["jest", "google-apps-script"], 7 | "moduleResolution": "NodeNext" 8 | }, 9 | "include": [ 10 | "src/**/*", 11 | "tests/**/*", 12 | ".eslintrc.cjs", 13 | "vite.config.js", 14 | "tailwind.config.cjs", 15 | "prettier.config.js", 16 | "types" 17 | ], 18 | "exclude": ["dist", "node_modules"] 19 | } 20 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wurkspaces-dev-boilerplate", 3 | "private": true, 4 | "version": "1.3.2", 5 | "type": "module", 6 | "main": "./dist/node/app.umd.cjs", 7 | "module": "./dist/node/app.js", 8 | "exports": { 9 | ".": { 10 | "import": "./dist/node/app.js", 11 | "require": "./dist/node/app.umd.cjs" 12 | } 13 | }, 14 | "homepage": "https://github.com/WildH0g/wurkspaces-dev-gas-boilerplate.git", 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/WildH0g/wurkspaces-dev-gas-boilerplate.git" 18 | }, 19 | "keywords": [], 20 | "scripts": { 21 | "install:husky": "husky install && npx husky add .husky/pre-commit \"npm run format && npm t\"", 22 | "mkreadme": "npx readme-md-generator", 23 | "env:dev": "node ./env-mgt/set-env.js dev", 24 | "env:uat": "node ./env-mgt/set-env.js uat", 25 | "env:prod": "node ./env-mgt/set-env.js prod", 26 | "dev": "vite", 27 | "build:ui": "vite build", 28 | "build:gas": "vite build --config vite.config.gas.js", 29 | "build:addon:push": "npm run build:ui && npm run build:gas && npm run push", 30 | "build:cp": "vite build --config vite.config.copy-paste.js", 31 | "build:node": "vite build --config vite.config.node.js", 32 | "build": "npm run build:ui && npm run build:gas", 33 | "build:lib": "npm run build:cp && npm run build:node && npm run build:gas", 34 | "push": "clasp push -f", 35 | "lint": "npx eslint src/**/*.js", 36 | "lint:fix": "npm run lint -- --fix", 37 | "prettier": "npx prettier src/**/*.js --check", 38 | "prettier:fix": "npm run prettier -- --write", 39 | "format": "npm run prettier:fix && npm run lint:fix", 40 | "test": "vitest", 41 | "tree": "tree -I 'node_modules' -I '.git' --dirsfirst -a" 42 | }, 43 | "devDependencies": { 44 | "@babel/parser": "^7.27.0", 45 | "@babel/traverse": "^7.27.0", 46 | "@tailwindcss/typography": "^0.5.15", 47 | "@types/alpinejs": "^3.13.10", 48 | "@types/google-apps-script": "^1.0.83", 49 | "autoprefixer": "^10.4.20", 50 | "daisyui": "^4.12.10", 51 | "eslint": "^8.57.0", 52 | "eslint-config-prettier": "^9.1.0", 53 | "eslint-plugin-googleappsscript": "^1.0.5", 54 | "husky": "^8.0.3", 55 | "postcss": "^8.4.45", 56 | "prettier": "^3.3.3", 57 | "tailwindcss": "^3.4.10", 58 | "vite": "^6.3.5", 59 | "vitest": "^3.1.2", 60 | "vite-plugin-singlefile": "^2.0.2" 61 | }, 62 | "dependencies": { 63 | "@google/clasp": "^3.0.3-alpha", 64 | "alpinejs": "^3.14.1" 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /postcss.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /prettier.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | trailingComma: 'es5', 3 | tabWidth: 2, 4 | semi: true, 5 | singleQuote: true, 6 | arrowParens: 'avoid', 7 | bracketSameLine: false, 8 | bracketSpacing: true, 9 | endOfLine: 'lf', 10 | htmlWhitespaceSensitivity: 'css', 11 | singleAttributePerLine: false, 12 | }; 13 | -------------------------------------------------------------------------------- /src/app.js: -------------------------------------------------------------------------------- 1 | import { randomizeCellColors, onOpen, showSidebar } from './server/server.js'; 2 | export { randomizeCellColors, onOpen, showSidebar }; 3 | -------------------------------------------------------------------------------- /src/client/app.js: -------------------------------------------------------------------------------- 1 | import Alpine from 'alpinejs'; 2 | import './credits.js'; 3 | import runGas from './runGas.js'; 4 | import './styles.css'; 5 | // @ts-ignore 6 | window.Alpine = Alpine; 7 | 8 | Alpine.data('colorRandomizer', () => ({ 9 | ready: 'Randomize Colors', 10 | loading: 'Loading...', 11 | isReady: true, 12 | async runRandomize() { 13 | try { 14 | this.isReady = false; 15 | await runGas('randomizeCellColors'); 16 | } catch (error) { 17 | console.error(error); 18 | alert(error); 19 | } finally { 20 | this.isReady = true; 21 | } 22 | }, 23 | })); 24 | 25 | Alpine.start(); 26 | -------------------------------------------------------------------------------- /src/client/assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WildH0g/apps-script-engine-template/9037da99c5a41c33cf70d76f159fd9db90cfcb9b/src/client/assets/favicon.ico -------------------------------------------------------------------------------- /src/client/credits.js: -------------------------------------------------------------------------------- 1 | export default (function () { 2 | const msg = ` 3 | 4 |  █▀▄ █▀▀ █ █ █▀▀ █  █▀█ █▀█ █▀▀ █▀▄   5 |  █▄▀ ██▄ ▀▄▀ ██▄ █▄▄ █▄█ █▀▀ ██▄ █▄▀   6 | 7 |  █▄▄ █▄█   8 |  █▄█  █    9 | 10 |  █▀▄ █▀▄▀█ █ ▀█▀ █▀█ █▄█   11 |  █▄▀ █ ▀ █ █  █  █▀▄  █    12 | 13 |  █▄▀ █▀█ █▀▀ ▀█▀ █▄█ █ █ █▄▀ 14 |  █ █ █▄█ ▄▄█  █   █  █▄█ █ █ 15 | 16 | https://wurkspaces.dev 17 | -- 18 | Favicon by https://www.flaticon.com/authors/popo2021 19 | `; 20 | console.log(msg); 21 | })(); 22 | -------------------------------------------------------------------------------- /src/client/getMocks.js: -------------------------------------------------------------------------------- 1 | import isJest from './isJest.js'; 2 | 3 | /** 4 | * Pauses a function's execution for a specified amount of time. 5 | * @param {number} ms - The amount of time to pause the function's execution in milliseconds. 6 | * @returns {Promise} - A promise that resolves after the specified amount of time. 7 | */ 8 | const sleep = ms => 9 | new Promise(resolve => setTimeout(resolve, isJest() ? 0 : ms)); 10 | 11 | /** 12 | * Returns an object containing mock functions for the Google Apps Script API. 13 | * @param {Function} resolve - The Promise.resolve callback 14 | * @returns 15 | */ 16 | export default function getMocks(resolve) { 17 | return { 18 | async randomizeCellColors() { 19 | await sleep(1000); 20 | resolve(true); 21 | }, 22 | }; 23 | } 24 | -------------------------------------------------------------------------------- /src/client/isJest.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Determines whether Jest is running 3 | * @returns {boolean} True if Jest is running, false otherwise 4 | */ 5 | export default () => typeof globalThis.it === 'function'; 6 | -------------------------------------------------------------------------------- /src/client/polyfillScriptRun.js: -------------------------------------------------------------------------------- 1 | let polyfilled = false; 2 | import getMocks from './getMocks.js'; 3 | 4 | /** 5 | * Polyfills google.script.run with mock functions. 6 | * @returns {Promise} 7 | */ 8 | export default async function polyfillScriptRun() { 9 | if (polyfilled) return; 10 | polyfilled = true; 11 | const _window = 12 | 'undefined' !== typeof window 13 | ? window 14 | : 'undefined' !== typeof globalThis 15 | ? globalThis 16 | : {}; 17 | const google = _window?.google || {}; 18 | _window.google = google; 19 | 20 | if (!google.script || !google.script.run) { 21 | google.script = google.script || {}; 22 | google.script.run = { 23 | withSuccessHandler: resolve => { 24 | const mocks = getMocks(resolve); 25 | return { 26 | withFailureHandler: () => ({ 27 | ...mocks, 28 | }), 29 | }; 30 | }, 31 | }; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/client/runGas.js: -------------------------------------------------------------------------------- 1 | /*eslint no-undef: "off"*/ 2 | import polyfillScriptRun from './polyfillScriptRun.js'; 3 | polyfillScriptRun(); 4 | 5 | /** 6 | * Promisifies google.script.run 7 | * @param {string} functionName - The name of the Apps Script function to call 8 | * @param {Array<*>} args - The array of arguments to pass to the Apps Script function 9 | */ 10 | export default async function runGas(functionName, args = []) { 11 | return new Promise((resolve, reject) => { 12 | // @ts-ignore 13 | google.script.run 14 | .withSuccessHandler(resolve) 15 | .withFailureHandler(reject) 16 | [functionName](...args); 17 | }); 18 | } 19 | -------------------------------------------------------------------------------- /src/client/styles.css: -------------------------------------------------------------------------------- 1 | /* src/input.css */ 2 | @tailwind base; 3 | @tailwind components; 4 | @tailwind utilities; 5 | 6 | ::-webkit-scrollbar { 7 | width: 4px; 8 | cursor: pointer; 9 | } 10 | ::-webkit-scrollbar-track { 11 | background-color: rgba(229, 231, 235, var(--bg-opacity)); 12 | cursor: pointer; 13 | } 14 | ::-webkit-scrollbar-thumb { 15 | cursor: pointer; 16 | background-color: #a0aec0; 17 | } 18 | 19 | [x-cloak] { 20 | display: none; 21 | } 22 | -------------------------------------------------------------------------------- /src/server/helpers.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Genearates a two-dimensional array of random hexadecimal colors 3 | * @param {number} numRows Number of rows in the array 4 | * @param {number} numCols Number of columns in each row 5 | * @returns {import("../../types/colors.js").HexColor[][]} Two-dimensional array of hexadecimal colors 6 | */ 7 | export function generateRandomHexColor(numRows, numCols) { 8 | return Array.from({ length: numRows }, () => 9 | Array.from({ length: numCols }, getRandomColor) 10 | ); 11 | } 12 | 13 | /** 14 | * Generates a random hexadecimal color 15 | * @returns {import("../../types/colors.js").HexColor} Random hexadecimal color 16 | */ 17 | export function getRandomColor() { 18 | const letters = '0123456789ABCDEF'; 19 | let color = '#'; 20 | for (let i = 0; i < 6; i++) { 21 | color += letters[Math.floor(Math.random() * 16)]; 22 | } 23 | return color; 24 | } 25 | 26 | /** 27 | * @typedef {Object} GASClass 28 | * @property {typeof DocumentApp | typeof FormApp | typeof SlidesApp | typeof SpreadsheetApp} gasClass - The class of the Google Apps Script app. 29 | */ 30 | 31 | /** 32 | * @typedef {Object} AppName 33 | * @property {'DocumentApp'|'FormApp'|'SlidesApp'|'SpreadsheetApp'} name - The name of the app. 34 | */ 35 | 36 | /** 37 | * Get the context of the Google Apps Script app. Works in editors: Docs, Forms, Slides, and Sheets. 38 | * @returns {{gasClass: GASClass, name: AppName, ui: GoogleAppsScript.Base.Ui}} 39 | */ 40 | export function getContext() { 41 | const contexts = [ 42 | { gasClass: DocumentApp, name: 'DocumentApp' }, 43 | { gasClass: FormApp, name: 'FormApp' }, 44 | { gasClass: SlidesApp, name: 'SlidesApp' }, 45 | { gasClass: SpreadsheetApp, name: 'SpreadsheetApp' }, 46 | ]; 47 | let ui = null; 48 | let context = null; 49 | contexts.forEach(_context => { 50 | if (null !== ui) return; 51 | try { 52 | ui = _context.gasClass.getUi(); 53 | context = _context; 54 | context.ui = ui; 55 | console.log(`Selected context: ${_context.name}`); 56 | } catch (err) { 57 | ui = null; 58 | context = null; 59 | } 60 | }); 61 | return context; 62 | } 63 | -------------------------------------------------------------------------------- /src/server/server.js: -------------------------------------------------------------------------------- 1 | import { generateRandomHexColor, getContext } from './helpers.js'; 2 | 3 | /** 4 | * Randomizes the background color of cells in the range A1:E20 5 | * @returns {void} 6 | */ 7 | export function randomizeCellColors() { 8 | const sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet(); 9 | const range = sheet.getRange('A1:E20'); 10 | 11 | const colors = generateRandomHexColor( 12 | range.getNumRows(), 13 | range.getNumColumns() 14 | ); 15 | 16 | range.setBackgrounds(colors); 17 | } 18 | 19 | /** 20 | * Runs when the Google Apps Script app is opened. 21 | */ 22 | export function onOpen() { 23 | const ui = getContext().ui; 24 | ui.createMenu('👨‍🏭 Wurkspaces.dev') 25 | .addItem('☕🍽️ Boilerplate', 'showSidebar') 26 | .addToUi(); 27 | } 28 | 29 | /** 30 | * Shows the sidebar in the Google Apps Script app. 31 | */ 32 | export function showSidebar() { 33 | SpreadsheetApp.getUi().showSidebar( 34 | HtmlService.createHtmlOutputFromFile('ui/index') 35 | ); 36 | } 37 | -------------------------------------------------------------------------------- /tailwind.config.cjs: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: ['./src/client/**/*.{html,js,ts}', './index.html'], 4 | theme: { 5 | extend: {}, 6 | }, 7 | plugins: [require('daisyui')], 8 | }; 9 | -------------------------------------------------------------------------------- /types/colors.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @typedef {string} HexColor 3 | * @description A 6-character hexadecimal color code in the format `#RRGGBB`, where each character is one of `0123456789ABCDEF`. 4 | */ 5 | -------------------------------------------------------------------------------- /vite-plugin/traverse/collect-function-declarations.js: -------------------------------------------------------------------------------- 1 | import t from '@babel/traverse'; 2 | const traverse = t.default; 3 | 4 | /** 5 | * Collects all function declarations in the AST. 6 | * @param {Object} ast - The AST of the minified IIFE. 7 | * @returns {Map} - A map of function names to their AST nodes. 8 | */ 9 | export default function collectFunctionDeclarations(ast) { 10 | const functionMap = new Map(); 11 | 12 | traverse(ast, { 13 | FunctionDeclaration(path) { 14 | const node = path.node; 15 | functionMap.set(node.id.name, node); 16 | }, 17 | }); 18 | 19 | return functionMap; 20 | } -------------------------------------------------------------------------------- /vite-plugin/traverse/extract-export-details.js: -------------------------------------------------------------------------------- 1 | import extractParameters from './extract-params'; 2 | import t from '@babel/traverse'; 3 | const traverse = t.default; 4 | 5 | /** 6 | * Extracts export details from AST based on the exports identifier and function map. 7 | * @param {Object} ast - The AST of the minified IIFE. 8 | * @param {string} exportsIdentifier - The name of the exports identifier. 9 | * @param {Map} functionMap - A map of function names to their AST nodes. 10 | * @returns {Array} - An array of objects with export details. 11 | */ 12 | export default function extractExportDetails(ast, exportsIdentifier, functionMap) { 13 | const exports = []; 14 | 15 | traverse(ast, { 16 | AssignmentExpression(path) { 17 | const node = path.node; 18 | 19 | if (!node.left.object || node.left.object.name !== exportsIdentifier) 20 | return; 21 | 22 | const exportName = node.left.property.name; 23 | const exportDetails = { name: exportName, parameters: [] }; 24 | 25 | if ( 26 | node.right.type === 'Identifier' && 27 | functionMap.has(node.right.name) 28 | ) { 29 | const functionNode = functionMap.get(node.right.name); 30 | exportDetails.parameters = extractParameters(functionNode.params); 31 | } 32 | 33 | if ( 34 | ['FunctionExpression', 'ArrowFunctionExpression'].includes( 35 | node.right.type 36 | ) 37 | ) { 38 | exportDetails.parameters = extractParameters(node.right.params); 39 | } 40 | 41 | exports.push(exportDetails); 42 | }, 43 | }); 44 | 45 | return exports; 46 | } -------------------------------------------------------------------------------- /vite-plugin/traverse/extract-exports.js: -------------------------------------------------------------------------------- 1 | import findExportsIdentifier from './find-exports-identifier'; 2 | import collectFunctionDeclarations from './collect-function-declarations'; 3 | import extractExportDetails from './extract-export-details'; 4 | 5 | /** 6 | * Extracts export details from AST. 7 | * @param {Object} ast - The AST of the minified IIFE. 8 | * @returns {Array} - An array of objects with export details. 9 | */ 10 | export default function extractExports(ast) { 11 | const exportsIdentifier = findExportsIdentifier(ast); 12 | if (!exportsIdentifier) { 13 | throw new Error('Could not find the exports identifier'); 14 | } 15 | 16 | const functionMap = collectFunctionDeclarations(ast); 17 | return extractExportDetails(ast, exportsIdentifier, functionMap); 18 | } 19 | -------------------------------------------------------------------------------- /vite-plugin/traverse/extract-params.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Extracts parameter names and default values from a function node. 3 | * @param {Array} params - The parameters node array of the function. 4 | * @returns {Array} - An array of parameter objects with name and defaultValue. 5 | */ 6 | export default function extractParameters(params) { 7 | return params.map(param => { 8 | if (param.type === 'Identifier') { 9 | return { name: param.name }; 10 | } else if ( 11 | param.type === 'AssignmentPattern' && 12 | param.left.type === 'Identifier' 13 | ) { 14 | return { name: param.left.name, defaultValue: param.right.value }; 15 | } else if (param.type === 'RestElement') { 16 | return { name: `...${param.argument.name}` }; 17 | } 18 | return { name: undefined }; 19 | }); 20 | } -------------------------------------------------------------------------------- /vite-plugin/traverse/find-exports-identifier.js: -------------------------------------------------------------------------------- 1 | import t from '@babel/traverse'; 2 | const traverse = t.default; 3 | 4 | /** 5 | * Identifies the exports identifier used in the IIFE. 6 | * @param {Object} ast - The AST of the minified IIFE. 7 | * @returns {string|null} - The name of the exports identifier. 8 | */ 9 | export default function findExportsIdentifier(ast) { 10 | let exportsIdentifier = null; 11 | 12 | traverse(ast, { 13 | FunctionExpression(path) { 14 | const node = path.node; 15 | if (node.params.length > 0) { 16 | exportsIdentifier = node.params[0].name; 17 | path.stop(); 18 | } 19 | }, 20 | }); 21 | 22 | return exportsIdentifier; 23 | } -------------------------------------------------------------------------------- /vite-plugin/vite-plugin-appsscript.js: -------------------------------------------------------------------------------- 1 | /** @module GoogleAppsScriptExportsPlugin */ 2 | 3 | import fs from 'fs'; 4 | import { resolve } from 'path'; 5 | import { parse } from '@babel/parser'; 6 | import t from '@babel/traverse'; 7 | import extractExports from './traverse/extract-exports.js'; 8 | 9 | const traverse = t.default; 10 | 11 | /** 12 | * Extracts the exports as stand-alone functions 13 | * @param {string} origPath The file to extract the exports from 14 | * @param {string} exportsPath The file to extract the exports to 15 | * @param {string} varName The name of the variable that stores the return value of the IIFE 16 | * @param {GasExportOptions} [options] The options for the plugin 17 | */ 18 | export async function GoogleAppsScriptExportsPlugin( 19 | origPath = 'dist/gas/server/server.iife.js', 20 | exportsPath = 'dist/gas/exports.js', 21 | varName = 'lib_', 22 | options = { 23 | exportsFile: true, 24 | copyFiles: [{ from: './appsscript.json', to: 'dist/gas/appsscript.json' }], 25 | } 26 | ) { 27 | return { 28 | name: 'vite-plugin-appscript-exports', 29 | async closeBundle() { 30 | try { 31 | console.log('closeBundle'); 32 | const code = fs.readFileSync(origPath, 'utf-8'); 33 | const ast = parse(code); 34 | const exportDetails = extractExports(ast); 35 | // fs.writeFileSync( 36 | // 'exportDetails.json', 37 | // JSON.stringify(exportDetails, null, 2) 38 | // ); 39 | 40 | const getParam = paramObj => { 41 | if (!paramObj.defaultValue) return paramObj.name; 42 | if ('string' === typeof paramObj.defaultValue) 43 | return `${paramObj.name} = '${paramObj.defaultValue.replace( 44 | /'/g, 45 | "\\'" 46 | )}'`; 47 | return `${paramObj.name} = ${paramObj.defaultValue}`; 48 | }; 49 | const exportsText = 50 | '/* eslint-disable no-undef */\n' + 51 | exportDetails 52 | .map(fn => { 53 | return null === fn.parameters 54 | ? `const ${fn.name} = ${varName}.${fn.name};` 55 | : [ 56 | `function ${fn.name}(${fn.parameters 57 | .map(getParam) 58 | .join(', ')}) {`, 59 | ` return ${varName}.${fn.name}(${fn.parameters 60 | .map(p => p.name) 61 | .join(', ')});`, 62 | '}', 63 | ].join('\n'); 64 | }) 65 | .join('\n'); 66 | 67 | fs.writeFileSync(exportsPath, exportsText, 'utf8'); 68 | 69 | if (false === options.exportsFile) { 70 | const exp = fs.readFileSync( 71 | resolve(process.cwd(), exportsPath), 72 | 'utf8' 73 | ); 74 | fs.appendFileSync( 75 | resolve(process.cwd(), origPath), 76 | `\n${exp}`, 77 | 'utf8' 78 | ); 79 | fs.unlinkSync(resolve(process.cwd(), exportsPath)); 80 | } 81 | } catch (err) { 82 | console.error(`Could not create exports: ${err}`); 83 | } 84 | 85 | if (!options.copyFiles || !options.copyFiles.length) return; 86 | options.copyFiles.forEach(file => { 87 | fs.copyFileSync( 88 | resolve(process.cwd(), file.from), 89 | resolve(process.cwd(), file.to) 90 | ); 91 | }); 92 | }, 93 | }; 94 | } 95 | -------------------------------------------------------------------------------- /vite.config.copy-paste.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import { resolve } from 'path'; 3 | import { GoogleAppsScriptExportsPlugin } from './vite-plugin/vite-plugin-appsscript.js'; 4 | 5 | export default defineConfig({ 6 | plugins: [ 7 | GoogleAppsScriptExportsPlugin( 8 | 'dist/copy-paste/app.iife.js', 9 | 'dist/copy-paste/exports.js', 10 | '__lib__', 11 | { 12 | exportsFile: false, 13 | } 14 | ), 15 | ], 16 | build: { 17 | minify: true, 18 | outDir: resolve(process.cwd(), 'dist/copy-paste'), 19 | lib: { 20 | entry: resolve(process.cwd(), 'src/app.js'), 21 | name: '__lib__', 22 | fileName: 'app', 23 | formats: ['iife'], 24 | }, 25 | rollupOptions: { 26 | output: { 27 | extend: false, 28 | }, 29 | }, 30 | }, 31 | }); 32 | -------------------------------------------------------------------------------- /vite.config.gas.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import { resolve } from 'path'; 3 | import { GoogleAppsScriptExportsPlugin } from './vite-plugin/vite-plugin-appsscript.js'; 4 | 5 | export default defineConfig({ 6 | plugins: [GoogleAppsScriptExportsPlugin()], 7 | build: { 8 | minify: false, 9 | outDir: resolve(process.cwd(), 'dist/gas/server'), 10 | lib: { 11 | entry: resolve(process.cwd(), 'src/server/server.js'), 12 | name: 'lib_', 13 | fileName: 'server', 14 | formats: ['iife'], 15 | }, 16 | }, 17 | }); 18 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import { viteSingleFile } from 'vite-plugin-singlefile'; 3 | import { resolve } from 'path'; 4 | 5 | export default defineConfig({ 6 | plugins: [viteSingleFile()], 7 | build: { 8 | minify: false, 9 | outDir: resolve(__dirname, 'dist/gas/ui'), 10 | }, 11 | }); 12 | -------------------------------------------------------------------------------- /vite.config.node.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import { resolve } from 'path'; 3 | // import { AppsScriptPlugin } from './vite-plugin/vite-plugin-appsscript.js'; 4 | 5 | export default defineConfig({ 6 | // plugins: [AppsScriptPlugin('dist/lib/consolas.js')], 7 | build: { 8 | minify: true, 9 | outDir: resolve(process.cwd(), 'dist/node'), 10 | lib: { 11 | entry: resolve(process.cwd(), 'src/app.js'), 12 | name: 'init', 13 | fileName: 'app', 14 | }, 15 | rollupOptions: { 16 | output: { 17 | extend: false, 18 | }, 19 | }, 20 | }, 21 | }); 22 | --------------------------------------------------------------------------------