enuchi / React-Google-Apps-Script

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 Google Workspace Marketplace.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

serverFunctions[functionName] is not a function after upgrading project to match most recent repo update

sheldoncoates opened this issue · comments

After matching up deps, files, webpack, etc. I've almost got my add-on up and running again but I'm getting a weird error relating to my server functions when I run with npm start.

Screenshot 2023-03-31 at 10 01 58 AM

I'm not really sure why it's happening... I can see my code.gs has all the ui.js and sheets.js functions in it as I've exported them from index.ts

Any ideas?

Just to add to this import { serverFunctions } from '../src/client/utils/serverFunctions.ts' resolves to an empty object {} inside ./dev/index.js. Still investigating why this is.

I have converted src/server/index.ts, src/client/utils/serverFunctions.ts both to js files and this fixed the issue, but I'm not 100% sure why.

Not sure when your original project was cloned but there have been some changes to how gas-client project exports serverFunctions (default vs named export). Also there have been some changes as to how to expose global functions in the src/server/index.ts file. Old way was with .js and using the global.myFunction = myFunction approach, but with latest version you can use export { myFunction } to expose global server functions.

it was cloned a long time ago, but i've gone through and update all files to match this repo and the screenshot is the error I'm getting... not sure why the serverFunctions aren't loading. You have any ideas?

To me it seems like

 plugins: [
        new GasPlugin({
            // removes need for assigning public server functions to "global"
            autoGlobalExportsFiles: [serverEntry],
        }),
    ],

isn't actually exporting

yes you may be right -- if I remove that config on a fresh clone I see that message. What version of gas-webpack-plugin do you have? Works for me with 2.2.2

I'm using the same version... tried with a newer version as well and still have the same issue

does your code.gs have the keyword global in it?

Yes it does. Here's what code.gs should look like with fresh clone. What does your src/server/index.ts look like? I don't think it works properly for me using .js.

var global = this;

function __esModule() {}

function setActiveSheet() {}

function onOpen() {}

function openDialog() {}

function openDialogBootstrap() {}

function openDialogMUI() {}

function openDialogTailwindCSS() {}

function openAboutSidebar() {}

function getSheetsData() {}

function addSheet() {}

function deleteSheet() {}

(() => {
    "use strict";
    var __webpack_modules__ = [ , (__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
        __webpack_require__.r(__webpack_exports__), __webpack_require__.d(__webpack_exports__, {
            onOpen: () => onOpen,
            openAboutSidebar: () => openAboutSidebar,
            openDialog: () => openDialog,
            openDialogBootstrap: () => openDialogBootstrap,
            openDialogMUI: () => openDialogMUI,
            openDialogTailwindCSS: () => openDialogTailwindCSS
        });
        var onOpen = function() {
            SpreadsheetApp.getUi().createMenu("My Sample React Project").addItem("Sheet Editor", "openDialog").addItem("Sheet Editor (Bootstrap)", "openDialogBootstrap").addItem("Sheet Editor (MUI)", "openDialogMUI").addItem("Sheet Editor (Tailwind CSS)", "openDialogTailwindCSS").addItem("About me", "openAboutSidebar").addToUi();
        }, openDialog = function() {
            var html = HtmlService.createHtmlOutputFromFile("dialog-demo").setWidth(600).setHeight(600);
            SpreadsheetApp.getUi().showModalDialog(html, "Sheet Editor");
        }, openDialogBootstrap = function() {
            var html = HtmlService.createHtmlOutputFromFile("dialog-demo-bootstrap").setWidth(600).setHeight(600);
            SpreadsheetApp.getUi().showModalDialog(html, "Sheet Editor (Bootstrap)");
        }, openDialogMUI = function() {
            var html = HtmlService.createHtmlOutputFromFile("dialog-demo-mui").setWidth(600).setHeight(600);
            SpreadsheetApp.getUi().showModalDialog(html, "Sheet Editor (MUI)");
        }, openDialogTailwindCSS = function() {
            var html = HtmlService.createHtmlOutputFromFile("dialog-demo-tailwindcss").setWidth(600).setHeight(600);
            SpreadsheetApp.getUi().showModalDialog(html, "Sheet Editor (Tailwind CSS)");
        }, openAboutSidebar = function() {
            var html = HtmlService.createHtmlOutputFromFile("sidebar-about-page");
            SpreadsheetApp.getUi().showSidebar(html);
        };
    }, (__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
        __webpack_require__.r(__webpack_exports__), __webpack_require__.d(__webpack_exports__, {
            addSheet: () => addSheet,
            deleteSheet: () => deleteSheet,
            getSheetsData: () => getSheetsData,
            setActiveSheet: () => setActiveSheet
        });
        var getSheets = function() {
            return SpreadsheetApp.getActive().getSheets();
        }, getSheetsData = function() {
            var activeSheetName = SpreadsheetApp.getActive().getSheetName();
            return getSheets().map((function(sheet, index) {
                var name = sheet.getName();
                return {
                    name: name,
                    index: index,
                    isActive: name === activeSheetName
                };
            }));
        }, addSheet = function(sheetTitle) {
            return SpreadsheetApp.getActive().insertSheet(sheetTitle), getSheetsData();
        }, deleteSheet = function(sheetIndex) {
            var sheets = getSheets();
            return SpreadsheetApp.getActive().deleteSheet(sheets[sheetIndex]), getSheetsData();
        }, setActiveSheet = function(sheetName) {
            return SpreadsheetApp.getActive().getSheetByName(sheetName).activate(), getSheetsData();
        };
    } ], __webpack_module_cache__ = {};
    function __webpack_require__(moduleId) {
        var cachedModule = __webpack_module_cache__[moduleId];
        if (void 0 !== cachedModule) return cachedModule.exports;
        var module = __webpack_module_cache__[moduleId] = {
            exports: {}
        };
        return __webpack_modules__[moduleId](module, module.exports, __webpack_require__), 
        module.exports;
    }
    __webpack_require__.d = (exports, definition) => {
        for (var key in definition) __webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key) && Object.defineProperty(exports, key, {
            enumerable: !0,
            get: definition[key]
        });
    }, __webpack_require__.o = (obj, prop) => Object.prototype.hasOwnProperty.call(obj, prop), 
    __webpack_require__.r = exports => {
        "undefined" != typeof Symbol && Symbol.toStringTag && Object.defineProperty(exports, Symbol.toStringTag, {
            value: "Module"
        }), Object.defineProperty(exports, "__esModule", {
            value: !0
        });
    };
    var __webpack_exports__ = {};
    (() => {
        var exports = __webpack_exports__;
        exports.__esModule = !0, exports.setActiveSheet = exports.deleteSheet = exports.addSheet = exports.getSheetsData = exports.openAboutSidebar = exports.openDialogTailwindCSS = exports.openDialogMUI = exports.openDialogBootstrap = exports.openDialog = exports.onOpen = void 0;
        var ui_1 = __webpack_require__(1);
        exports.onOpen = ui_1.onOpen, exports.openDialog = ui_1.openDialog, exports.openDialogBootstrap = ui_1.openDialogBootstrap, 
        exports.openDialogMUI = ui_1.openDialogMUI, exports.openDialogTailwindCSS = ui_1.openDialogTailwindCSS, 
        exports.openAboutSidebar = ui_1.openAboutSidebar;
        var sheets_1 = __webpack_require__(2);
        exports.getSheetsData = sheets_1.getSheetsData, exports.addSheet = sheets_1.addSheet, 
        exports.deleteSheet = sheets_1.deleteSheet, exports.setActiveSheet = sheets_1.setActiveSheet, 
        global.__esModule = exports.__esModule, global.setActiveSheet = exports.setActiveSheet, 
        global.onOpen = exports.onOpen, global.openDialog = exports.openDialog, global.openDialogBootstrap = exports.openDialogBootstrap, 
        global.openDialogMUI = exports.openDialogMUI, global.openDialogTailwindCSS = exports.openDialogTailwindCSS, 
        global.openAboutSidebar = exports.openAboutSidebar, global.getSheetsData = exports.getSheetsData, 
        global.addSheet = exports.addSheet, global.deleteSheet = exports.deleteSheet;
    })();
    for (var i in __webpack_exports__) this[i] = __webpack_exports__[i];
    __webpack_exports__.__esModule && Object.defineProperty(this, "__esModule", {
        value: !0
    });
})();

Screenshot 2023-03-31 at 12 43 30 PM
I do see the message Could not find a declaration file for module './ui'. on the imports and it doesn't seem like the functions are being recognized.

so I've narrowed it down to the tsconfig causing the issues

{
    "compilerOptions": {
        "types": ["gas-types-detailed"],
        "jsx": "react",
        "esModuleInterop": true,
        "lib": ["es2019", "dom"]
    }
}

this adds global to code.gs, but only for the last exported function in server/index.ts and if i change this in anyway it removes global.

Have you experienced this before?

Hmm I haven't. Can you try using the latest tsconfig used here? There are a couple other issues here that bring up tsconfig but not sure if they're relevant.

{
  "compilerOptions": {
    "types": ["gas-types-detailed"],
    "jsx": "react",
    "esModuleInterop": true
  }
}

I get the same error Could not find a declaration file for module './ui'. in my code but it compiles fine so don't think that's the issue.

I need to have my tsconfig as

{
    "compilerOptions": {
        "types": ["gas-types-detailed"],
        "jsx": "react",
        "lib": ["es2019", "dom"],
        "esModuleInterop": true
    }
}

at the minimum for my application to compile... it is react ts. also i would be great to compile to ES6... the current setup compiles modules to ES5 I belive? I modified the old tsconfig to compile everything to ES6 and the bundle went from 5MiB to 500KiB

Yea some alternate configs for tsconfig come up in #141. I think the best solution is to have two different tsconfig files, one in the base of src/server and one in src/client. The server one can just be the latest version in this repo, and the client one can be further modified to support react etc. I tried it out briefly a while ago and seemed to work.

I am also not convinced the tsconfig file is the culprit with your error. I tried using your file and it still compiled fine. Can you try a fresh clone of this repo and see if you're able to properly generate the code.gs server file? Just to rule out some global settings.

okay i've added the two tsconfig files one for client and one for server and thats all good, thanks. Now when i build, code.gs I still only has the last exported function from server/index.ts for some reason.

I did clone this repo and made everything one to one except my code and this repo does work as expected so I'm really stuck here trying to figure out why this is happening.

index.ts

import { onOpen, onInstall, openSidebar } from './ui'

// Public functions must be exported as named exports
export { onOpen, onInstall, openSidebar }

ui.js

export const onOpen = () => {
    const menu = SpreadsheetApp.getUi()
        .createAddonMenu()
        .addItem('Open app', 'openSidebar')

    menu.addToUi()
}

export const onInstall = () => {
    const menu = SpreadsheetApp.getUi()
        .createAddonMenu()
        .addItem('Open app', 'openSidebar')

    menu.addToUi()
}

export const openSidebar = () => {
    const html = HtmlService.createHtmlOutputFromFile('app')
    html.setTitle('App')
    SpreadsheetApp.getUi().showSidebar(html)
}

code.gs

var global = this;

function openSidebar() {}

(() => {
    "use strict";
    var __webpack_modules__ = {
        618: (__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
            __webpack_require__.r(__webpack_exports__), __webpack_require__.d(__webpack_exports__, {
                onInstall: () => onInstall,
                onOpen: () => onOpen,
                openSidebar: () => openSidebar
            });
            var onOpen = function() {
                SpreadsheetApp.getUi().createAddonMenu().addItem("Open app", "openSidebar").addToUi();
            }, onInstall = function() {
                SpreadsheetApp.getUi().createAddonMenu().addItem("Open app", "openSidebar").addToUi();
            }, openSidebar = function() {
                var html = HtmlService.createHtmlOutputFromFile("app");
                html.setTitle("App"), SpreadsheetApp.getUi().showSidebar(html);
            };
        }
    }, __webpack_module_cache__ = {};
    function __webpack_require__(moduleId) {
        var cachedModule = __webpack_module_cache__[moduleId];
        if (void 0 !== cachedModule) return cachedModule.exports;
        var module = __webpack_module_cache__[moduleId] = {
            exports: {}
        };
        return __webpack_modules__[moduleId](module, module.exports, __webpack_require__), 
        module.exports;
    }
    __webpack_require__.d = (exports, definition) => {
        for (var key in definition) __webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key) && Object.defineProperty(exports, key, {
            enumerable: !0,
            get: definition[key]
        });
    }, __webpack_require__.o = (obj, prop) => Object.prototype.hasOwnProperty.call(obj, prop), 
    __webpack_require__.r = exports => {
        "undefined" != typeof Symbol && Symbol.toStringTag && Object.defineProperty(exports, Symbol.toStringTag, {
            value: "Module"
        }), Object.defineProperty(exports, "__esModule", {
            value: !0
        });
    };
    var __webpack_exports__ = {};
    (() => {
        var exports = __webpack_exports__;
        Object.defineProperty(exports, "__esModule", {
            value: !0
        }), exports.openSidebar = exports.onInstall = exports.onOpen = void 0;
        var ui_1 = __webpack_require__(618);
        Object.defineProperty(exports, "onOpen", {
            enumerable: !0,
            get: function() {
                return ui_1.onOpen;
            }
        }), Object.defineProperty(exports, "onInstall", {
            enumerable: !0,
            get: function() {
                return ui_1.onInstall;
            }
        }), Object.defineProperty(exports, "openSidebar", {
            enumerable: !0,
            get: function() {
                return ui_1.openSidebar;
            }
        });
    })();
    for (var i in __webpack_exports__) this[i] = __webpack_exports__[i];
    __webpack_exports__.__esModule && Object.defineProperty(this, "__esModule", {
        value: !0
    });
})();

This fixes it in server/index.ts

import * as UIFunctions from './ui'
const { onOpen, onInstall, openSidebar } = UIFunctions
export {
    onOpen,
    onInstall,
    openSidebar,
}

not really sure whats happening lol

Hm yea interesting. Out of curiosity is it the import * ... syntax, the export list separated into different lines, or combo of both that fixes it?

I guess I'm kinda not too surprised. Something about the autoGlobalExportsFiles implementation requires the exports and maybe the imports to look a certain way, but it doesn't exactly follow standard export formatting. I remember I tried to do stuff like export onOpen from './...' and export * from './...' and they didn't work, even if I tried installing the right babel plugins, that's why I left that vague comment in that index.ts file.

Could ask @fossamagna in https://github.com/fossamagna/gas-webpack-plugin if he knows what's going on.

Got another weird one for you... Can you try and install react-router-dom and see if it gives you an error? I can open another issue if you like for discussion just let me know. After installing you can use this client/sidebar-about-page/index.js:

import React from 'react';
import { createRoot } from 'react-dom/client';
import { MemoryRouter, Routes, Route } from 'react-router-dom';

const container = document.getElementById('index');
const root = createRoot(container);
root.render(<App />);

const App = () => {
  return (
    <MemoryRouter>
      <Routes>
        <Route path="/" element={<Home />} />
      </Routes>
    </MemoryRouter>
  );
};

const Home = () => {
  return <h1>Home</h1>;
};

I've been struggling with this for hours, moduleToCdn doesn't work with react-router-dom. I tried adding it to the switch statement in webpack so that it resolves to a minified version in prod build but that results in a blank screen in the side bar. I tried:

    if (
            packageName === 'react-router-dom' ||
            packageName === 'react-router'
        ) {
            return null
        }

so that the packages aren't externalized but still blank screen inside the add-on. If you open the compiled sidebar-about-page.html the page loads fine... so weird.

Any idea how to fix that one? I haven't been able to find/come up with anything yet.

See discussion at #56 and also #34.

Tldr react-router probably won't work in GAS dialogs.

By GAS dialogs do you mean just the dialog window and sidebar? or just the dialog?

Both dialogs and sidebars -- they behave the same. As I understand it GAS's sandboxed environment for dialogs (meaning both sidebars and dialogs) restricts access to certain browser APIs, like the Browser History API. Since react-router relies on these APIs react-router will not work within dialogs. But I can imagine the compiled html file would work fine if you opened it on its own, or in development. Let's open another issue if you still have questions.

Okay so after hours of debugging and constant white screens I was finally able to get everything working the way i want it. I have a few notes for anyone else that comes across these issues you might be able to link them back here to help.

react-router-dom DOES work, but only the MemoryRouter works with GAS's sandbox, and you have to modify webpack because module-to-cdn doesn't work with react-router-dom and the minified production package also caused issues. I did the following to fix it:

// DynamicCdnWebpackPlugin settings
// these settings help us load 'react', 'react-dom' and the packages defined below from a CDN
// see https://github.com/enuchi/React-Google-Apps-Script#adding-new-libraries-and-packages
const DynamicCdnWebpackPluginConfig = {
    // set "verbose" to true to print console logs on CDN usage while webpack builds
    verbose: false,
    resolver: (packageName, packageVersion, options) => {
        const packageSuffix = isProd ? '.min.js' : '.js'
        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
        }

        if (
            packageName === 'react-router-dom' ||
            packageName === 'react-router'
        ) {
            return null
        }
...
//rest of webpack code
}

this isn't ideal but at least now react-router-dom is usable.

Another thing that threw me for a loop was some constants I had defined and exported for use throughout my codebase. I had two consts with very similar names and for some reason it was causing the entire add on to white screen:

export const API_URL = 'https://apiurl.com/api/v3/'
export const API_URL_V4 = 'https://apiurl.com/api/v4/'

I'm not sure what was happening with this one so if anyone figures it out let me know. I simply restructured and renames these to fix it.

commented

Had the same issue. Workaround with this in the index.ts in the server directory worked for me:

import * as UIFunctions from './ui'
import * as SheetFunction from './sheets'

const { onOpen, openSettingsDialog } = UIFunctions
const { getSheetsData, deleteSheet, setActiveSheet } = SheetFunction

// Public functions must be exported as named exports
export {
  onOpen,
  openSettingsDialog,
  getSheetsData,
  deleteSheet,
  setActiveSheet
}

Have to say that i changed a few things before noticing the issue, so it might not be a general issue. Would probably still be nice to know why that happens. Maybe an issue with typescript?