Skip to content

Commit 0ea874e

Browse files
committed
update webpack, update packages, dev experience
update webpack, clasp, client code, eslint, prettier, readme, packages to latest
1 parent f898046 commit 0ea874e

30 files changed

+1986
-2859
lines changed

.clasp.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
{
22
"rootDir": "dist",
3-
"scriptId": "...paste scriptId here..."
3+
"scriptId": "...add scriptId here..."
44
}

.eslintrc.json

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"parser": "babel-eslint"
3+
}

.npmrc

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
engine-strict=true

.nvmrc

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
lts/*

.prettierrc.json .prettierrc

File renamed without changes.

README.md

+126-165
Original file line numberDiff line numberDiff line change
@@ -1,224 +1,185 @@
11

22

33
## React + Google Apps Script
4-
*Use this demo project as your boilerplate React app for HTML dialogs in Google Sheets, Docs and Forms.*
4+
*Use this project as your boilerplate for React apps inside Google Sheets, Docs and Forms pages.*
55

6-
This project uses labnol's excellent [apps-script-starter](https://github.com/labnol/apps-script-starter) as a starting point, adding support for React. It demonstrates how easy it is to build React apps that interact with Google Apps server-side scripts. Simply clone this project and modify the source code to get started developing with React for Google Apps Script client-side dialogs.
6+
This project uses labnol's excellent [apps-script-starter](https://github.com/labnol/apps-script-starter) as a starting point, and adds support for building React apps inside Google Sheets, Forms, and Docs pages. Simply clone this project and modify the source code to get started developing React apps with Google Apps Script.
77

88
![Google Apps Script / React development](https://i.imgur.com/0yYQoYj.jpg "Start a React project for Google Apps Script")
99
*The demo app for Google Sheets shows insertion/deletion/activation of sheets through React-built HTML dialog.*
1010

1111
## Installation
1212

13-
14-
1513
1. Clone the sample project and install dependencies:
16-
```
17-
> git clone https://github.com/enuchi/React-Google-Apps-Script.git
18-
> cd React-Google-Apps-Script
19-
> npm install
20-
```
14+
```bash
15+
> git clone https://github.com/enuchi/React-Google-Apps-Script.git
16+
> cd React-Google-Apps-Script
17+
> npm install
18+
```
2119
2. Enable the Google Apps Script API [(script.google.com/home/usersettings)](https://script.google.com/home/usersettings):
22-
![Enable Google Apps Script](https://i.imgur.com/PxuNbP3.png "enable the Google Apps Script API")
20+
21+
![Enable Google Apps Script](https://i.imgur.com/PxuNbP3.png "enable the Google Apps Script API")
2322

24-
3. Log in to `clasp` to manage your Google Apps Scripts from the command line:
25-
```
26-
> npm run login
27-
```
23+
3. Log in to `clasp`, google's tool to help you manage your Google Apps Script projects from the command line.
24+
```bash
25+
> npm run login
26+
```
2827
4. Create a new Google Sheets file and a bound Google Scripts project for your React project:
29-
```
30-
> npm run setup
31-
Created new Google Sheet: https://drive.google.com/open?id=1lVQUPZ*************************************
32-
Created new Google Sheets Add-on script: https://script.google.com/d/1K7MPtCH*************************************-**/edit
33-
```
34-
You've created a new Sheet and attached Script file! (But they're still empty for now.) See the notes below if you want to use an existing Google Sheet and Script instead of creating a new one.
35-
5. Build the app and deploy!
36-
```
37-
> npm run deploy
38-
```
39-
You can now visit your Google Sheet and see your new React app.
28+
```bash
29+
> npm run setup
30+
31+
Created new Google Sheet: https://drive.google.com/open?id=1lVQUPZ*************************************
32+
Created new Google Sheets Add-on script: https://script.google.com/d/1K7MPtCH*************************************-**/edit
33+
```
34+
This will use `clasp` to create a new Google Sheets spreadsheet and a 'bound' Google Apps Script project, and save the credentials to the `.clasp.json` file in your root directory. If you don't want to create a new spreadsheet and script, and instead want to use this project with an existing project, [see the section below](#Using-an-existing-Sheet).
35+
36+
Okay, now you've created a new sheet and bound script file! (But they're still empty for now.)
37+
38+
5. Deploy the app!
39+
```bash
40+
> npm run deploy
41+
```
42+
Open your new spreadsheet in Google Sheets. Your new React app will be available from a new menu item!
4043

4144
### Using an existing Sheet
42-
The above installation instructions create a new Google Sheets spreadsheet and 'bound' Apps Script, and saves the credentials to a `.clasp.json` file in the root directory. If you want to use an existing sheet's script file, then simply copy the scriptId into the `.clasp.json` file as below. You can find the script's scriptId by opening your sheet, selecting **Tools > Script Editor**, then **File > Project properties**.
45+
If you want to use an existing google script for your project:
46+
1. Copy the existing script project's `scriptId`. You can find it by opening your sheet, selecting **Tools > Script Editor**, then **File > Project properties**.
47+
48+
2. Run the command below to add the existing project's `scriptId` to your`.clasp.json` file. (You can also just edit the `.clasp.json` file directly. Make sure not to remove `"rootDir": "dist"` from the `.clasp.json` file.)
49+
50+
```bash
51+
# If your scriptId is 1K7MPtCHkjasdf93238234asdKFDF3sa9 then run
52+
> npm run setup:use-id 1K7MPtCHkjasdf93238234asdKFDF3sa9
53+
```
4354

44-
Paste the `scriptId` into the `.clasp.json` file. *Make sure to include `"rootDir": "dist"` in this file:
45-
```
46-
// .clasp.json
47-
{"rootDir": "dist", "scriptId":"...paste scriptId here..."}
48-
```
4955
### Making changes to the code
50-
Modify the server-side and client-side source code in the `src` folder using ES6/7 and React. Change the scopes in `appsscript.json` if needed. When you're ready, build the app and deploy! You can run `npm run deploy` to build and deploy, or `npm run build` just to build the bundled files in the `./dist` directory.
56+
Modify the server-side and client-side source code in the `src` folder. [Add any additional scopes](https://developers.google.com/apps-script/concepts/scopes) to `appsscript.json` as needed. When you're ready, build the app and deploy! You can run `npm run deploy` to build and deploy, or `npm run build` just to build the bundled files in the `./dist` directory.
5157
5258
5359
## The sample app
5460
Insert/activate/delete sheets through a simple HTML dialog, built with React. Access the dialog through the new menu item that appears. You may need to refresh the spreadsheet and approve the app's permissions the first time you use it.
5561

56-
## How it works
57-
"[Google Apps Script](https://en.wikipedia.org/wiki/Google_Apps_Script) is based on JavaScript 1.6 with some portions of 1.7 and 1.8 and provides subset of ECMAScript 5 API."
58-
59-
That means many JavaScript tools used today in modern web development will not work in the Google Apps Script environment, including `let`/`const` declarations, arrow functions, spread operator, etc.
60-
61-
This project circumvents those restrictions by transpiling newer code to older code that Google Apps Script understands using Babel, and also bundles separate files and modules using Webpack.
62-
63-
On the client-side, Google Apps Scripts has restrictions on the way HTML dialogs are used. While in normal web development you can simply reference a separate css file, e.g.
64-
```
65-
<link rel="stylesheet" href="styles.css">
66-
```
67-
in the Google Apps Script environment you need to use [HTML templates](https://developers.google.com/apps-script/guides/html/templates), which can be cumbersome to work with. With this project, all files are bundled together by inlining .css and .js files. Using a transpiler and bundling tool also allows us to use JSX syntax, and external libraries such as React.
6862

6963
## Features
70-
- Support for JSX syntax:
71-
```
72-
return <div>Name: {person.firstName}</div>
73-
```
7464
- Support for external packages. Simply install with npm or from a file and `import`:
75-
```
76-
$ npm install react-addons-css-transition-group
77-
```
78-
```
79-
// index.jsx
80-
import ReactCSSTransitionGroup from 'react-addons-css-transition-group';
81-
```
65+
```bash
66+
> npm install react-transition-group
67+
```
68+
```js
69+
// index.jsx
70+
import { TransitionGroup, CSSTransition } from 'react-transition-group';
71+
```
8272
- `import` CSS from another file:
83-
```
84-
import "./styles.css";
85-
```
86-
- Use promises with e.g. `.then`/`.catch` instead of `google.script.run`:
87-
```
88-
// instead of this:
89-
google.script.run
90-
.withSuccessHandler(successHandler)
91-
.withFailureHandler(failureHandler)
92-
.getSheetsData()
93-
94-
// you can do this:
95-
import server from '../server';
96-
// access all your server functions here:
97-
const { getSheetsData } = server;
98-
addSheet(newSheetTitle)
99-
.then(successHandler)
100-
.catch(failureHandler);
73+
```js
74+
import "./styles.css";
10175
```
102-
How does this work? We rewrite `google.script.run` to support Promises:
103-
```
104-
// ./src/client/server.js
105-
106-
const serverMethods = {};
107-
108-
// skip the reserved methods
109-
const ignoredMethods = [
110-
'withFailureHandler',
111-
'withLogger',
112-
'withSuccessHandler',
113-
'withUserObject',
114-
];
115-
116-
for (const method in google.script.run) {
117-
if (!ignoredMethods.includes(method)) {
118-
serverMethods[method] = (...args) => {
119-
return new Promise((resolve, reject) => {
120-
google.script.run
121-
.withSuccessHandler(resolve)
122-
.withFailureHandler(reject)[method](...args);
123-
});
124-
};
125-
}
126-
}
127-
export default serverMethods;
128-
```
129-
Now we can use familiar Promises in our client-side code and have easy access to all server functions!
130-
131-
- Use newer ES6/7 code, including arrow functions, spread operators, `const`/`let`, and more:
132-
```
133-
const getSheetsData = () => {
134-
let activeSheetName = getActiveSheetName();
135-
return getSheets().map((sheet, index) => {
136-
let sheetName = sheet.getName();
137-
return {
138-
text: sheetName,
139-
sheetIndex: index,
140-
isActive: sheetName === activeSheetName,
141-
};
142-
});
143-
};
144-
```
145-
## Tern support
146-
This project includes support for GAS definitions and autocomplete through a [Tern](http://ternjs.net/) plugin. Tern is a code-analysis engine for JavaScript, providing many useful tools for developing. See Tern's site for setup instructions for many popular code editors, such as Sublime, Vim and others.
147-
148-
Tern provides many indispensable tools for working with Google Apps Script, such as autocompletion on variables and properties, function argument hints and querying the type of an expression.
149-
150-
- Autocomplete example. Lists all available methods from the appropriate Google Apps Script API:
151-
![tern support](https://i.imgur.com/s1OrQNr.png "autocomplete and intelligent type detection with Tern")
152-
76+
- Use promises to call and handle responses from the server instead of using `google.script.run`:
77+
```js
78+
// Google's documentation wants you to do this. Boo.
79+
google.script.run
80+
.withSuccessHandler(response => doSomething(response))
81+
.withFailureHandler(err => handleError(err))
82+
.addSheet(sheetTitle)
83+
84+
85+
// Poof! With a little magic we can now do this:
86+
import server from '../server';
87+
88+
// We now have access to all our server functions, which return promises!
89+
const { addSheet } = server;
90+
addSheet(sheetTitle)
91+
.then(response => doSomething(response))
92+
.catch(handleError(err));
93+
94+
// Or we can use async/await:
95+
async () => {
96+
try {
97+
const response = await addSheet(sheetTitle);
98+
doSomething(response);
99+
} catch (err) {
100+
handleError(err)
101+
}
102+
}
103+
```
104+
Now we can use familiar Promises in our client-side code and have easy access to all server functions. See [the code](./src/client/server.js) for the implementation details.
105+
106+
## Full Autocompletion
107+
This project includes support for the full autocompletion and type definitions.
108+
109+
![autocomplete support](https://i.imgur.com/W0Ks6Wj.gif "autocomplete")
110+
111+
- Lists all available methods from the appropriate Google Apps Script API:
153112
- Full definitions with links to official documentation, plus information on argument and return type:
154-
![tern support](https://i.imgur.com/yg5VwAC.png "definitions with links to official documentation make developing with Google Apps Script")
155-
156113
157114
158115
## Extending this app
159116
- You can split up server-side code into multiple files and folders using `import` and `export` statements.
160117
- Make sure to expose all public functions including `onOpen` and any functions you are calling from the client. Example below shows assignment to `global` object:
161-
```
162-
const onOpen = () => {
163-
SpreadsheetApp.getUi() // Or DocumentApp or FormApp.
164-
.createMenu('Dialog')
165-
.addItem('Add sheets', 'openDialog')
166-
.addToUi();
167-
}
168-
169-
global.onOpen = onOpen
170-
```
118+
```js
119+
const onOpen = () => {
120+
SpreadsheetApp.getUi() // Or DocumentApp or FormApp.
121+
.createMenu('Dialog')
122+
.addItem('Add sheets', 'openDialog')
123+
.addToUi();
124+
}
125+
126+
global.onOpen = onOpen
127+
```
171128
- You may wish to remove automatic linting when running Webpack. You can do so by editing the Webpack config file and commenting out the eslintConfig line in client or server settings:
172-
```
173-
// webpack.config.js
174-
175-
const clientConfig = Object.assign({}, sharedConfigSettings, {
176-
...
177-
module: {
178-
rules: [
179-
// eslintConfig,
180-
{
181-
```
129+
```js
130+
// webpack.config.js
131+
132+
const clientConfig = Object.assign({}, sharedConfigSettings, {
133+
...
134+
module: {
135+
rules: [
136+
// eslintConfig,
137+
{
138+
```
182139
183140
## Multiple dialogs
184-
This project now supports multiple dialogs and sidebars. Here is an example of the `server` code for a 'main.html' dialog and an 'about.html' sidebar:
185-
```
186-
// ./src/server/sheets-utilities.js
141+
This project now supports multiple dialogs and sidebars. See the `server` code at [src/server/ui.js](./src/server/ui.js) for a 'main.html' dialog and an 'about.html' sidebar:
142+
```js
143+
// ./src/server/ui.js
144+
145+
export const onOpen = () => {
146+
SpreadsheetApp.getUi()
147+
.createMenu('My Sample React Project') // edit me!
148+
.addItem('Sheet Name Editor', 'openDialog')
149+
.addItem('About me', 'openAboutSidebar')
150+
.addToUi();
151+
};
187152
188-
const openDialog = () => {
153+
export const openDialog = () => {
189154
const html = HtmlService.createHtmlOutputFromFile('main')
190155
.setWidth(400)
191156
.setHeight(600);
192-
SpreadsheetApp
193-
.getUi() // Or DocumentApp or FormApp.
194-
.showModalDialog(html, 'Sheet Editor');
157+
SpreadsheetApp.getUi().showModalDialog(html, 'Sheet Editor');
195158
};
196159
197-
const openAboutSidebar = () => {
160+
export const openAboutSidebar = () => {
198161
const html = HtmlService.createHtmlOutputFromFile('about');
199-
SpreadsheetApp
200-
.getUi()
201-
.showSidebar(html);
162+
SpreadsheetApp.getUi().showSidebar(html);
202163
};
203164
```
204165
And here is the configuration in webpack that creates multiple html files. You will need to edit this if you want to add more dialog html files:
205-
```
166+
```js
206167
// ./webpack.config.js
207168
208169
// Client entrypoints:
209170
const clientEntrypoints = [
210171
{
211-
name: "CLIENT - main dialog",
212-
entry: "./src/client/main.jsx",
213-
filename: "main.html"
172+
name: 'CLIENT - main dialog',
173+
entry: './src/client/MainDialog.jsx',
174+
filename: 'main.html',
214175
},
215176
{
216-
name: "CLIENT - about sidebar",
217-
entry: "./src/client/about.jsx",
218-
filename: "about.html"
177+
name: 'CLIENT - about sidebar',
178+
entry: './src/client/AboutDialog.jsx',
179+
filename: 'about.html',
219180
},
220181
];
221182
```
222183
223184
## Suggestions
224-
Open a pull request!
185+
Pull requests welcome!

dist/about.html

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,5 @@
77
<section id="index">
88
<!-- bundled js and css will get inlined here -->
99
</section>
10-
<script type="text/javascript" src="https://unpkg.com/[email protected]/umd/react.production.min.js"></script><script type="text/javascript" src="https://unpkg.com/[email protected]/umd/react-dom.production.min.js"></script><script type="text/javascript">!function(e){var t={};function r(n){if(t[n])return t[n].exports;var o=t[n]={i:n,l:!1,exports:{}};return e[n].call(o.exports,o,o.exports,r),o.l=!0,o.exports}r.m=e,r.c=t,r.d=function(e,t,n){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)r.d(n,o,function(t){return e[t]}.bind(null,o));return n},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="",r(r.s=2)}([function(e,t){e.exports=React},function(e,t){e.exports=ReactDOM},function(e,t,r){"use strict";r.r(t);var n=r(0),o=r.n(n),u=r(1);r.n(u).a.render(o.a.createElement(()=>o.a.createElement("div",{className:"sheetNameText"},o.a.createElement("div",null,"Github repo:"),o.a.createElement("a",{href:"https://www.github.com/enuchi/React-Google-Apps-Script",target:"_blank",rel:"noopener noreferrer"},"React + Google Apps Script"),o.a.createElement("div",null,"-Elisha Nuchi")),null),document.getElementById("index"))}]);</script></body>
10+
<script type="text/javascript" src="https://unpkg.com/[email protected]/umd/react.production.min.js"></script><script type="text/javascript" src="https://unpkg.com/[email protected]/umd/react-dom.production.min.js"></script><script type="text/javascript">!function(e){var t={};function r(n){if(t[n])return t[n].exports;var o=t[n]={i:n,l:!1,exports:{}};return e[n].call(o.exports,o,o.exports,r),o.l=!0,o.exports}r.m=e,r.c=t,r.d=function(e,t,n){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)r.d(n,o,function(t){return e[t]}.bind(null,o));return n},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="",r(r.s=2)}([function(e,t){e.exports=React},function(e,t){e.exports=ReactDOM},function(e,t,r){"use strict";r.r(t);var n=r(0),o=r.n(n),u=r(1);var a=()=>o.a.createElement("div",{className:"sheetNameText"},o.a.createElement("div",null,"Github repo:"),o.a.createElement("a",{href:"https://www.github.com/enuchi/React-Google-Apps-Script",target:"_blank",rel:"noopener noreferrer"},"React + Google Apps Script"),o.a.createElement("div",null,"-Elisha Nuchi"));r.n(u).a.render(o.a.createElement(a,null),document.getElementById("index"))}]);</script></body>
1111
</html>

0 commit comments

Comments
 (0)