Support react-refresh and shared client/server types (#98)
- Adds support for react-refresh through `@pmmwh/react-refresh-webpack-plugin`. Updated webpack config to support this. Means entire app is no longer refreshed on changes, only individual components, and state is maintained. - React refresh removes need for `google-apps-script-webpack-dev-server` package - In client code, support for server function autocomplete through use of gas-client 1.0.0. Requires certain files to be typescript .ts files - Remove need for "global" exports in main server file through updated gas-webpack-plugin
This commit is contained in:
@@ -11,7 +11,7 @@ jobs:
|
|||||||
os: [macos-10.15, macos-latest, windows-latest]
|
os: [macos-10.15, macos-latest, windows-latest]
|
||||||
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
|
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
|
||||||
node-version: [12, 14, 16]
|
node-version: [12, 14, 16]
|
||||||
timeout-minutes: 10
|
timeout-minutes: 11
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
|
|||||||
@@ -2,6 +2,9 @@
|
|||||||
<a href="" rel="noopener">
|
<a href="" rel="noopener">
|
||||||
<img width="400" src="https://i.imgur.com/83Y7bWN.png" alt="React & Google Apps Script logos"></a>
|
<img width="400" src="https://i.imgur.com/83Y7bWN.png" alt="React & Google Apps Script logos"></a>
|
||||||
</p>
|
</p>
|
||||||
|
<p align="center"><i>
|
||||||
|
Update 2022: Now with support for React v17 and React Fast Refresh
|
||||||
|
</i></p>
|
||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
|
|
||||||
@@ -12,7 +15,7 @@
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p align="center"> This is your boilerplate project for developing React apps inside Google Sheets, Docs, Forms and Slides projects. It's perfect for personal projects and for publishing complex add-ons in the G Suite Marketplace.
|
<p align="center"> 🚀 This is your boilerplate project for developing React apps inside Google Sheets, Docs, Forms and Slides projects. It's perfect for personal projects and for publishing complex add-ons in the G Suite Marketplace.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -189,6 +192,8 @@ After running the start command, navigate to your spreadsheet and open one of th
|
|||||||
|
|
||||||
<img width="100%" src="https://i.imgur.com/EsnOEHP.gif">
|
<img width="100%" src="https://i.imgur.com/EsnOEHP.gif">
|
||||||
|
|
||||||
|
Support for [Fast Refresh](https://github.com/pmmmwh/react-refresh-webpack-plugin) now means that only modified components are refreshed when files are changed, and state is not lost.
|
||||||
|
|
||||||
<br/>
|
<br/>
|
||||||
|
|
||||||
### 🔍 Using React DevTools <a name="dev-tools"></a>
|
### 🔍 Using React DevTools <a name="dev-tools"></a>
|
||||||
|
|||||||
+2
-7
@@ -1,8 +1,6 @@
|
|||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import server from '../src/client/utils/server';
|
import { serverFunctions } from '../src/client/utils/serverFunctions.ts';
|
||||||
|
|
||||||
const { serverFunctions } = server;
|
|
||||||
|
|
||||||
const { FILENAME, PORT } = process.env;
|
const { FILENAME, PORT } = process.env;
|
||||||
|
|
||||||
@@ -54,10 +52,7 @@ const DevServer = () => {
|
|||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
}}
|
}}
|
||||||
ref={iframe}
|
ref={iframe}
|
||||||
// The "/gas/" path here must match the path where the custom dev server is being loaded.
|
src={`https://localhost:${PORT}/${FILENAME}-impl.html`}
|
||||||
// See webpack.config.js "devServer" "before" settings.
|
|
||||||
// Filename extension "-impl" must match webpack.config.js clientConfigs filename settings for development.
|
|
||||||
src={`https://localhost:${PORT}/gas/${FILENAME}-impl.html`}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
Generated
+1510
-1329
File diff suppressed because it is too large
Load Diff
+7
-7
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "react-google-apps-script",
|
"name": "react-google-apps-script",
|
||||||
"version": "2.0.0",
|
"version": "2.1.0",
|
||||||
"description": "Starter project for using React with Google Apps Script",
|
"description": "Starter project for using React with Google Apps Script",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@@ -34,7 +34,6 @@
|
|||||||
"npm": ">=6.0.0"
|
"npm": ">=6.0.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"gas-client": "^0.2.1",
|
|
||||||
"prop-types": "^15.7.2",
|
"prop-types": "^15.7.2",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-bootstrap": "^1.3.0",
|
"react-bootstrap": "^1.3.0",
|
||||||
@@ -51,7 +50,8 @@
|
|||||||
"@babel/polyfill": "^7.10.4",
|
"@babel/polyfill": "^7.10.4",
|
||||||
"@babel/preset-env": "^7.11.0",
|
"@babel/preset-env": "^7.11.0",
|
||||||
"@babel/preset-react": "^7.10.4",
|
"@babel/preset-react": "^7.10.4",
|
||||||
"@google/clasp": "^2.3.1",
|
"@google/clasp": "2.3.1",
|
||||||
|
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.4",
|
||||||
"@types/expect-puppeteer": "^4.4.6",
|
"@types/expect-puppeteer": "^4.4.6",
|
||||||
"@types/jest-environment-puppeteer": "^4.4.1",
|
"@types/jest-environment-puppeteer": "^4.4.1",
|
||||||
"@types/puppeteer": "^5.4.4",
|
"@types/puppeteer": "^5.4.4",
|
||||||
@@ -66,11 +66,10 @@
|
|||||||
"css-loader": "^3.6.0",
|
"css-loader": "^3.6.0",
|
||||||
"dotenv": "^8.2.0",
|
"dotenv": "^8.2.0",
|
||||||
"dynamic-cdn-webpack-plugin": "^5.0.0",
|
"dynamic-cdn-webpack-plugin": "^5.0.0",
|
||||||
"eslint": "^6.8.0",
|
"eslint": "^8.11.0",
|
||||||
"eslint-config-airbnb-base": "^14.2.0",
|
"eslint-config-airbnb-base": "^14.2.0",
|
||||||
"eslint-config-prettier": "^6.11.0",
|
"eslint-config-prettier": "^6.11.0",
|
||||||
"eslint-config-standard": "^14.1.1",
|
"eslint-config-standard": "^14.1.1",
|
||||||
"eslint-loader": "^3.0.4",
|
|
||||||
"eslint-plugin-babel": "^5.3.1",
|
"eslint-plugin-babel": "^5.3.1",
|
||||||
"eslint-plugin-googleappsscript": "^1.0.3",
|
"eslint-plugin-googleappsscript": "^1.0.3",
|
||||||
"eslint-plugin-import": "^2.22.0",
|
"eslint-plugin-import": "^2.22.0",
|
||||||
@@ -81,10 +80,10 @@
|
|||||||
"eslint-plugin-promise": "^4.2.1",
|
"eslint-plugin-promise": "^4.2.1",
|
||||||
"eslint-plugin-react": "^7.20.5",
|
"eslint-plugin-react": "^7.20.5",
|
||||||
"eslint-plugin-standard": "^4.0.1",
|
"eslint-plugin-standard": "^4.0.1",
|
||||||
|
"gas-client": "^1.0.0",
|
||||||
"gas-lib": "^2.0.3",
|
"gas-lib": "^2.0.3",
|
||||||
"gas-types-detailed": "^1.0.0",
|
"gas-types-detailed": "^1.0.0",
|
||||||
"gas-webpack-plugin": "^1.0.4",
|
"gas-webpack-plugin": "^1.2.3",
|
||||||
"google-apps-script-webpack-dev-server": "^1.0.0",
|
|
||||||
"html-webpack-inline-source-plugin": "0.0.10",
|
"html-webpack-inline-source-plugin": "0.0.10",
|
||||||
"html-webpack-plugin": "^3.2.0",
|
"html-webpack-plugin": "^3.2.0",
|
||||||
"jest": "^27.5.1",
|
"jest": "^27.5.1",
|
||||||
@@ -95,6 +94,7 @@
|
|||||||
"puppeteer": "^13.4.1",
|
"puppeteer": "^13.4.1",
|
||||||
"puppeteer-extra": "^3.2.3",
|
"puppeteer-extra": "^3.2.3",
|
||||||
"puppeteer-extra-plugin-stealth": "^2.9.0",
|
"puppeteer-extra-plugin-stealth": "^2.9.0",
|
||||||
|
"react-refresh": "^0.10.0",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
"style-loader": "^1.2.1",
|
"style-loader": "^1.2.1",
|
||||||
"tern": "^0.24.3",
|
"tern": "^0.24.3",
|
||||||
|
|||||||
@@ -29,7 +29,19 @@
|
|||||||
"camelcase": "warn",
|
"camelcase": "warn",
|
||||||
"import/prefer-default-export": "warn",
|
"import/prefer-default-export": "warn",
|
||||||
"import/no-extraneous-dependencies": "warn",
|
"import/no-extraneous-dependencies": "warn",
|
||||||
"prefer-object-spread": "warn"
|
"prefer-object-spread": "warn",
|
||||||
|
"rules": {
|
||||||
|
"import/extensions": [
|
||||||
|
"error",
|
||||||
|
"ignorePackages",
|
||||||
|
{
|
||||||
|
"js": "never",
|
||||||
|
"jsx": "never",
|
||||||
|
"ts": "never",
|
||||||
|
"tsx": "never"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"react": {
|
"react": {
|
||||||
|
|||||||
@@ -4,9 +4,7 @@ import { Button, ListGroup } from 'react-bootstrap';
|
|||||||
import FormInput from './FormInput.tsx';
|
import FormInput from './FormInput.tsx';
|
||||||
|
|
||||||
// This is a wrapper for google.script.run that lets us use promises.
|
// This is a wrapper for google.script.run that lets us use promises.
|
||||||
import server from '../../utils/server';
|
import { serverFunctions } from '../../utils/serverFunctions';
|
||||||
|
|
||||||
const { serverFunctions } = server;
|
|
||||||
|
|
||||||
const SheetEditor = () => {
|
const SheetEditor = () => {
|
||||||
const [names, setNames] = useState([]);
|
const [names, setNames] = useState([]);
|
||||||
|
|||||||
@@ -4,9 +4,7 @@ import FormInput from './FormInput';
|
|||||||
import SheetButton from './SheetButton';
|
import SheetButton from './SheetButton';
|
||||||
|
|
||||||
// This is a wrapper for google.script.run that lets us use promises.
|
// This is a wrapper for google.script.run that lets us use promises.
|
||||||
import server from '../../utils/server';
|
import { serverFunctions } from '../../utils/serverFunctions';
|
||||||
|
|
||||||
const { serverFunctions } = server;
|
|
||||||
|
|
||||||
const SheetEditor = () => {
|
const SheetEditor = () => {
|
||||||
const [names, setNames] = useState([]);
|
const [names, setNames] = useState([]);
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
import Server from 'gas-client';
|
|
||||||
|
|
||||||
const { PORT } = process.env;
|
|
||||||
|
|
||||||
const server = new Server({
|
|
||||||
// this is necessary for local development but will be ignored in production
|
|
||||||
allowedDevelopmentDomains: `https://localhost:${PORT}`,
|
|
||||||
});
|
|
||||||
|
|
||||||
export default server;
|
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import { GASClient } from 'gas-client';
|
||||||
|
import * as publicServerFunctions from '../../server';
|
||||||
|
|
||||||
|
const { serverFunctions } = new GASClient<typeof publicServerFunctions>({
|
||||||
|
// this is necessary for local development but will be ignored in production
|
||||||
|
allowedDevelopmentDomains: origin =>
|
||||||
|
/https:\/\/.*\.googleusercontent\.com$/.test(origin),
|
||||||
|
});
|
||||||
|
|
||||||
|
export { serverFunctions };
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
import * as publicUiFunctions from './ui';
|
|
||||||
import * as publicSheetFunctions from './sheets';
|
|
||||||
|
|
||||||
// Expose public functions by attaching to `global`
|
|
||||||
global.onOpen = publicUiFunctions.onOpen;
|
|
||||||
global.openDialog = publicUiFunctions.openDialog;
|
|
||||||
global.openDialogBootstrap = publicUiFunctions.openDialogBootstrap;
|
|
||||||
global.openAboutSidebar = publicUiFunctions.openAboutSidebar;
|
|
||||||
global.getSheetsData = publicSheetFunctions.getSheetsData;
|
|
||||||
global.addSheet = publicSheetFunctions.addSheet;
|
|
||||||
global.deleteSheet = publicSheetFunctions.deleteSheet;
|
|
||||||
global.setActiveSheet = publicSheetFunctions.setActiveSheet;
|
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import {
|
||||||
|
onOpen,
|
||||||
|
openDialog,
|
||||||
|
openDialogBootstrap,
|
||||||
|
openAboutSidebar,
|
||||||
|
} from './ui';
|
||||||
|
|
||||||
|
import { getSheetsData, addSheet, deleteSheet, setActiveSheet } from './sheets';
|
||||||
|
|
||||||
|
// Public functions must be exported as named exports
|
||||||
|
export {
|
||||||
|
onOpen,
|
||||||
|
openDialog,
|
||||||
|
openDialogBootstrap,
|
||||||
|
openAboutSidebar,
|
||||||
|
getSheetsData,
|
||||||
|
addSheet,
|
||||||
|
deleteSheet,
|
||||||
|
setActiveSheet,
|
||||||
|
};
|
||||||
@@ -18,7 +18,7 @@ const toMatchImageSnapshot = configureToMatchImageSnapshot({
|
|||||||
allowSizeMismatch: true,
|
allowSizeMismatch: true,
|
||||||
});
|
});
|
||||||
expect.extend({ toMatchImageSnapshot });
|
expect.extend({ toMatchImageSnapshot });
|
||||||
jest.setTimeout(120000);
|
jest.setTimeout(180000);
|
||||||
|
|
||||||
const srcTestFile = path.join(
|
const srcTestFile = path.join(
|
||||||
__dirname,
|
__dirname,
|
||||||
@@ -48,10 +48,11 @@ describe(`Local setup ${isExtended ? '*extended*' : ''}`, () => {
|
|||||||
deviceScaleFactor: 1,
|
deviceScaleFactor: 1,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await webpackDevServerReady(process);
|
||||||
|
|
||||||
if (isExtended) {
|
if (isExtended) {
|
||||||
await openAddon(page);
|
await openAddon(page);
|
||||||
} else {
|
} else {
|
||||||
await webpackDevServerReady(process);
|
|
||||||
await page.goto('https://localhost:3000/dialog-demo-bootstrap.html');
|
await page.goto('https://localhost:3000/dialog-demo-bootstrap.html');
|
||||||
await page.waitForTimeout(3000);
|
await page.waitForTimeout(3000);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
const openAddon = async page => {
|
const openAddon = async page => {
|
||||||
await page.goto(process.env.SHEET_URL);
|
await page.goto(process.env.SHEET_URL);
|
||||||
await page.waitForTimeout(5000);
|
|
||||||
|
|
||||||
await page.click('a:nth-child(2)'); // click on signin button
|
await page.click('a:nth-child(2)'); // click on signin button
|
||||||
await page.waitForTimeout(3000);
|
|
||||||
|
await page.waitForSelector('input[name="identifier"]', { visible: true });
|
||||||
await page.type('input[name="identifier"]', process.env.EMAIL); // type email
|
await page.type('input[name="identifier"]', process.env.EMAIL); // type email
|
||||||
await page.click('#identifierNext'); // click "next" button
|
await page.click('#identifierNext'); // click "next" button
|
||||||
await page.waitForTimeout(3000);
|
|
||||||
|
await page.waitForSelector('input[name="password"]', { visible: true });
|
||||||
await page.type('input[name="password"]', process.env.PASSWORD); // type pass
|
await page.type('input[name="password"]', process.env.PASSWORD); // type pass
|
||||||
await page.waitForTimeout(3000);
|
await page.waitForTimeout(500);
|
||||||
|
|
||||||
await page.click('#passwordNext'); // click "next" button
|
await page.click('#passwordNext'); // click "next" button
|
||||||
await page.waitForTimeout(3000);
|
await page.waitForTimeout(3000);
|
||||||
|
|
||||||
@@ -35,7 +37,10 @@ const openAddon = async page => {
|
|||||||
await page.waitForTimeout(5000);
|
await page.waitForTimeout(5000);
|
||||||
}
|
}
|
||||||
|
|
||||||
await page.waitForTimeout(25000); // wait long enough for onopen to be called
|
await page.waitForSelector(
|
||||||
|
'div.menu-button.goog-control.goog-inline-block:nth-child(11)',
|
||||||
|
{ visible: true }
|
||||||
|
);
|
||||||
|
|
||||||
// open new addon menubar item
|
// open new addon menubar item
|
||||||
await page.evaluate(() => {
|
await page.evaluate(() => {
|
||||||
@@ -48,7 +53,10 @@ const openAddon = async page => {
|
|||||||
addOnMenuButton.dispatchEvent(new MouseEvent('mouseup', { bubbles: true }));
|
addOnMenuButton.dispatchEvent(new MouseEvent('mouseup', { bubbles: true }));
|
||||||
});
|
});
|
||||||
|
|
||||||
await page.waitForTimeout(6000);
|
await page.waitForSelector(
|
||||||
|
'div.goog-menu.goog-menu-vertical.apps-menu-hide-mnemonics:last-child > div:nth-child(2) > div',
|
||||||
|
{ visible: true }
|
||||||
|
);
|
||||||
|
|
||||||
// open "bootstrap" menu item
|
// open "bootstrap" menu item
|
||||||
await page.evaluate(() => {
|
await page.evaluate(() => {
|
||||||
@@ -68,7 +76,12 @@ const openAddon = async page => {
|
|||||||
new MouseEvent('mouseup', { bubbles: true })
|
new MouseEvent('mouseup', { bubbles: true })
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
await page.waitForTimeout(10000);
|
await page.waitForSelector('.script-app-dialog', {
|
||||||
|
visible: true,
|
||||||
|
timeout: 10000,
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.waitForTimeout(3000);
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = { openAddon };
|
module.exports = { openAddon };
|
||||||
|
|||||||
+42
-19
@@ -11,6 +11,7 @@ const HtmlWebpackPlugin = require('html-webpack-plugin');
|
|||||||
const HtmlWebpackInlineSourcePlugin = require('html-webpack-inline-source-plugin');
|
const HtmlWebpackInlineSourcePlugin = require('html-webpack-inline-source-plugin');
|
||||||
const DynamicCdnWebpackPlugin = require('dynamic-cdn-webpack-plugin');
|
const DynamicCdnWebpackPlugin = require('dynamic-cdn-webpack-plugin');
|
||||||
const moduleToCdn = require('module-to-cdn');
|
const moduleToCdn = require('module-to-cdn');
|
||||||
|
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
|
||||||
|
|
||||||
/*********************************
|
/*********************************
|
||||||
* set up environment variables
|
* set up environment variables
|
||||||
@@ -32,7 +33,7 @@ const isProd = process.env.NODE_ENV === 'production';
|
|||||||
const destination = path.resolve(__dirname, 'dist');
|
const destination = path.resolve(__dirname, 'dist');
|
||||||
|
|
||||||
// define server paths
|
// define server paths
|
||||||
const serverEntry = './src/server/index.js';
|
const serverEntry = './src/server/index.ts';
|
||||||
|
|
||||||
// define appsscript.json file path
|
// define appsscript.json file path
|
||||||
const copyAppscriptEntry = './appsscript.json';
|
const copyAppscriptEntry = './appsscript.json';
|
||||||
@@ -98,7 +99,7 @@ const sharedClientAndServerConfig = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// webpack settings used by all client entrypoints
|
// webpack settings used by all client entrypoints
|
||||||
const clientConfig = {
|
const clientConfig = ({ isDevClientWrapper }) => ({
|
||||||
...sharedClientAndServerConfig,
|
...sharedClientAndServerConfig,
|
||||||
mode: isProd ? 'production' : 'development',
|
mode: isProd ? 'production' : 'development',
|
||||||
output: {
|
output: {
|
||||||
@@ -119,6 +120,14 @@ const clientConfig = {
|
|||||||
use: [
|
use: [
|
||||||
{
|
{
|
||||||
loader: 'babel-loader',
|
loader: 'babel-loader',
|
||||||
|
// only enable react-refresh for dev builds, and not when building the dev client "wrapper"
|
||||||
|
options: {
|
||||||
|
plugins: [
|
||||||
|
!isProd &&
|
||||||
|
!isDevClientWrapper &&
|
||||||
|
require.resolve('react-refresh/babel'),
|
||||||
|
].filter(Boolean),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
loader: 'ts-loader',
|
loader: 'ts-loader',
|
||||||
@@ -130,6 +139,14 @@ const clientConfig = {
|
|||||||
exclude: /node_modules/,
|
exclude: /node_modules/,
|
||||||
use: {
|
use: {
|
||||||
loader: 'babel-loader',
|
loader: 'babel-loader',
|
||||||
|
// only enable react-refresh for dev builds, and not when building the dev client "wrapper"
|
||||||
|
options: {
|
||||||
|
plugins: [
|
||||||
|
!isProd &&
|
||||||
|
!isDevClientWrapper &&
|
||||||
|
require.resolve('react-refresh/babel'),
|
||||||
|
].filter(Boolean),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// we could add support for scss here
|
// we could add support for scss here
|
||||||
@@ -139,7 +156,7 @@ const clientConfig = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
|
|
||||||
// DynamicCdnWebpackPlugin settings
|
// DynamicCdnWebpackPlugin settings
|
||||||
// these settings help us load 'react', 'react-dom' and the packages defined below from a CDN
|
// these settings help us load 'react', 'react-dom' and the packages defined below from a CDN
|
||||||
@@ -150,9 +167,19 @@ const DynamicCdnWebpackPluginConfig = {
|
|||||||
resolver: (packageName, packageVersion, options) => {
|
resolver: (packageName, packageVersion, options) => {
|
||||||
const packageSuffix = isProd ? '.min.js' : '.js';
|
const packageSuffix = isProd ? '.min.js' : '.js';
|
||||||
const moduleDetails = moduleToCdn(packageName, packageVersion, options);
|
const moduleDetails = moduleToCdn(packageName, packageVersion, options);
|
||||||
|
|
||||||
|
// don't externalize react during development due to issue with react-refresh
|
||||||
|
// https://github.com/pmmmwh/react-refresh-webpack-plugin/issues/334
|
||||||
|
if (!isProd && packageName === 'react') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// return defaults if Dynamic CDN plugin finds package
|
||||||
if (moduleDetails) {
|
if (moduleDetails) {
|
||||||
return moduleDetails;
|
return moduleDetails;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// define custom CDN configuration for new packages
|
||||||
// "name" should match the package being imported
|
// "name" should match the package being imported
|
||||||
// "var" is important to get right -- this should be the exposed global. Look up "webpack externals" for info.
|
// "var" is important to get right -- this should be the exposed global. Look up "webpack externals" for info.
|
||||||
switch (packageName) {
|
switch (packageName) {
|
||||||
@@ -178,11 +205,14 @@ const DynamicCdnWebpackPluginConfig = {
|
|||||||
|
|
||||||
// webpack settings used by each client entrypoint defined at top
|
// webpack settings used by each client entrypoint defined at top
|
||||||
const clientConfigs = clientEntrypoints.map(clientEntrypoint => {
|
const clientConfigs = clientEntrypoints.map(clientEntrypoint => {
|
||||||
|
const isDevClientWrapper = false;
|
||||||
return {
|
return {
|
||||||
...clientConfig,
|
...clientConfig({ isDevClientWrapper }),
|
||||||
name: clientEntrypoint.name,
|
name: clientEntrypoint.name,
|
||||||
entry: clientEntrypoint.entry,
|
entry: clientEntrypoint.entry,
|
||||||
plugins: [
|
plugins: [
|
||||||
|
!isProd && new webpack.HotModuleReplacementPlugin(),
|
||||||
|
!isProd && new ReactRefreshWebpackPlugin(),
|
||||||
new webpack.DefinePlugin({
|
new webpack.DefinePlugin({
|
||||||
'process.env': JSON.stringify(envVars),
|
'process.env': JSON.stringify(envVars),
|
||||||
}),
|
}),
|
||||||
@@ -195,26 +225,15 @@ const clientConfigs = clientEntrypoints.map(clientEntrypoint => {
|
|||||||
new HtmlWebpackInlineSourcePlugin(),
|
new HtmlWebpackInlineSourcePlugin(),
|
||||||
// this plugin allows us to add dynamically load packages from a CDN
|
// this plugin allows us to add dynamically load packages from a CDN
|
||||||
new DynamicCdnWebpackPlugin(DynamicCdnWebpackPluginConfig),
|
new DynamicCdnWebpackPlugin(DynamicCdnWebpackPluginConfig),
|
||||||
],
|
].filter(Boolean),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
const gasWebpackDevServerPath = require.resolve(
|
|
||||||
'google-apps-script-webpack-dev-server'
|
|
||||||
);
|
|
||||||
|
|
||||||
// webpack settings for devServer https://webpack.js.org/configuration/dev-server/
|
// webpack settings for devServer https://webpack.js.org/configuration/dev-server/
|
||||||
const devServer = {
|
const devServer = {
|
||||||
|
hot: true,
|
||||||
port: PORT,
|
port: PORT,
|
||||||
https: true,
|
https: true,
|
||||||
// run our own route to serve the package google-apps-script-webpack-dev-server
|
|
||||||
before: app => {
|
|
||||||
// this '/gas/' path needs to match the path loaded in the iframe in dev/index.js
|
|
||||||
app.get('/gas/*', (req, res) => {
|
|
||||||
res.setHeader('Content-Type', 'text/html');
|
|
||||||
fs.createReadStream(gasWebpackDevServerPath).pipe(res);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (fs.existsSync(keyPath) && fs.existsSync(certPath)) {
|
if (fs.existsSync(keyPath) && fs.existsSync(certPath)) {
|
||||||
@@ -238,8 +257,9 @@ if (fs.existsSync(pfxPath)) {
|
|||||||
// webpack settings for the development client wrapper
|
// webpack settings for the development client wrapper
|
||||||
const devClientConfigs = clientEntrypoints.map(clientEntrypoint => {
|
const devClientConfigs = clientEntrypoints.map(clientEntrypoint => {
|
||||||
envVars.FILENAME = clientEntrypoint.filename;
|
envVars.FILENAME = clientEntrypoint.filename;
|
||||||
|
const isDevClientWrapper = true;
|
||||||
return {
|
return {
|
||||||
...clientConfig,
|
...clientConfig({ isDevClientWrapper }),
|
||||||
name: `DEVELOPMENT: ${clientEntrypoint.name}`,
|
name: `DEVELOPMENT: ${clientEntrypoint.name}`,
|
||||||
entry: devDialogEntry,
|
entry: devDialogEntry,
|
||||||
plugins: [
|
plugins: [
|
||||||
@@ -332,7 +352,10 @@ const serverConfig = {
|
|||||||
isProd ? 'production' : 'development'
|
isProd ? 'production' : 'development'
|
||||||
),
|
),
|
||||||
}),
|
}),
|
||||||
new GasPlugin(),
|
new GasPlugin({
|
||||||
|
// removes need for assigning public server functions to "global"
|
||||||
|
autoGlobalExportsFiles: [serverEntry],
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user