OfficeDev / Office-Addin-TaskPane-React

Template to get start started writing a TaskPane Office Add-in for the React framework using TypeScript
Other
53 stars 34 forks source link

Hot reload doesn't work with custom functions #157

Open sheldoncoates opened 3 months ago

sheldoncoates commented 3 months ago

Hot reload breaks when you have custom functions.

Expected behavior

Hot reload works and doesn't show a custom function error.

Current behavior

I've taken the webpack.config.js in this repo and added the necessary pieces to it from Excel-Custom-Functions in order to get custom functions to work since the webpacks seem to be out of sync.

For some reason when hot reload happens I get an error along the lines of: TS2304: Cannot find name 'CUSTOMFUNCTION'. Pointing to various typescript files of no relation to the custom function code.

Steps to Reproduce

  1. Start a fresh task pane project
  2. Add the required custom function code to webpack.config.js in order to make the custom function accessible
  3. Create a simple custom function
  4. Run the add-in and verify that the custom function is accessible in the spreadsheet
  5. Make a change to the react code and save to force a hot-reload
  6. Experience an error

Context

Failure Logs

TS2304: Cannot find name 'CUSTOMFUNCTION'.

sheldoncoates commented 3 months ago

I've progressed this issue but am now hung up on the following error:

undefined is not an object (evaluating 'currentUpdate[moduleId] = moreModules[moduleId]')
@https://localhost:3000/functions.js:8143:28
global code@https://localhost:3000/taskpane.a66e3b35ae31727f8c56.hot-update.js:2:52

Haven't been able to get around it. It would be great to have an example using custom functions in this repo.

AlexJerabek commented 3 months ago

Hi @sheldoncoates,

Sorry to hear you're having issues. @MiaofeiWang, could you please investigate?

sheldoncoates commented 3 months ago

hey @AlexJerabek @MiaofeiWang - I actually sorted this out. It was an issue with webpack when merging the two webpack.config.js files from this repo and the custom function repo. I'll close this issue since it's resolved, but I think it would be useful to keep both repos in sync to avoid some confusion.

akrantz commented 3 months ago

There was an issue in custom-functions-metadata package which could result in this which was fixed. The Office-Addin-TaskPane-React project has been updated so it should not occur. But there is a potential that other problems could occur with hot reloading, so please raise any issues you encounter.

sheldoncoates commented 3 months ago

I'm running into this issue again for some reason.

undefined is not an object (evaluating 'currentUpdateRuntime.push')
@https://localhost:3000/functions.js:9093:45
global code@https://localhost:3000/polyfill.cb7bcb5a63e9d4030998.hot-update.js:2:52

And

undefined is not an object (evaluating 'currentUpdate[moduleId] = moreModules[moduleId]')
@https://localhost:3000/functions.js:9089:28
global code@https://localhost:3000/taskpane.cb7bcb5a63e9d4030998.hot-update.js:2:52

My prod builds seem fine it's just the hot reload thats cooked. I can share webpack config if needed just let me know.

Rick-Kirkham commented 3 months ago

Maybe related: 158

sheldoncoates commented 2 months ago

Hi @Rick-Kirkham I went through the issue after the webpack.config.js was posted and synced mine with it, adding custom function related code to it as well and I can't seem to get around this error:

Screenshot 2024-05-26 at 9 52 38 PM

Is there anyway we can get a full working webpack for custom functions with a working taskpane + hot reload React 18? Thanks

millerds commented 2 months ago

That last error is with the taskpane and not custom functions.

I'm not sure custom functions are compatible with hot reloading in general. They are run in a JS only runtime that I believe requires ES5 syntax and I believe hot reloading and webpack use a more modern syntax that would break custom functions. This is why the custom functions project serves up the dist folder directly and the manifest points to that file (i.e. localhost:3000/public/functions.js) so that webpack doesn't inject its own incompatible code. The taskpane should be configurable for hot reloading, but the function shouldn't be part of that.

Note . . . creating templates for each combination of features and frameworks quickly becomes very large and hard to maintain. We provide basic examples of functionality, a bit of guidance, and a bit of help to figure out problems, but leave it up to developers to apply thing to their personal setup an requirements.

sheldoncoates commented 2 months ago

@millerds ive tried to get this to work based on this thread and the function repo and the other issues linked here and although I can hot reload my taskpane now - my custom functions no longer work (not sure if this is specific to the mac client because online using chrome seems to work). Here is the webpack im using:

/* eslint-disable no-undef */

const devCerts = require('office-addin-dev-certs')
const CustomFunctionsMetadataPlugin = require('custom-functions-metadata-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const webpack = require('webpack')

async function getHttpsOptions() {
    const httpsOptions = await devCerts.getHttpsServerOptions()
    return {
        ca: httpsOptions.ca,
        key: httpsOptions.key,
        cert: httpsOptions.cert,
    }
}

module.exports = async (env, options) => {
    const config = {
        devtool: 'source-map',
        entry: {
            polyfill: ['core-js/stable', 'regenerator-runtime/runtime'],
            vendor: ['react', 'react-dom', 'core-js'],
            taskpane: [
                './src/taskpane/index.tsx',
                './src/taskpane/taskpane.html',
            ],
            functions: [
                './src/functions/function.ts',
                './src/functions/function-two.ts',
                './src/functions/function-three.ts',
                './src/functions/function-utils.ts',
                './src/functions/function-utils-two.ts',
                './src/functions/constants.ts',
            ],
        },
        output: {
            clean: true,
        },
        resolve: {
            extensions: ['.ts', '.tsx', '.html', '.js'],
        },
        module: {
            rules: [
                {
                    test: /\.ts$/,
                    exclude: /node_modules/,
                    use: {
                        loader: 'babel-loader',
                        options: {
                            presets: ['@babel/preset-typescript'],
                        },
                    },
                },
                {
                    test: /\.tsx?$/,
                    exclude: /node_modules/,
                    use: ['ts-loader'],
                },
                {
                    test: /\.html$/,
                    exclude: /node_modules/,
                    use: 'html-loader',
                },
                {
                    test: /\.(png|jpg|jpeg|gif|ico)$/,
                    type: 'asset/resource',
                    generator: {
                        filename: 'assets/[name][ext][query]',
                    },
                },
                // we could add support for scss here
                {
                    test: /\.css$/,
                    use: ['style-loader', 'css-loader'],
                },
            ],
        },
        plugins: [
            new CustomFunctionsMetadataPlugin({
                output: 'functions.json',
                input: [
                    './src/functions/function.ts',
                    './src/functions/function-two.ts',
                    './src/functions/function-three.ts',
                ],
            }),
            new HtmlWebpackPlugin({
                filename: 'functions.html',
                template: './src/functions/functions.html',
                chunks: ['polyfill', 'functions'],
            }),
            new HtmlWebpackPlugin({
                filename: 'taskpane.html',
                template: './src/taskpane/taskpane.html',
                chunks: ['polyfill', 'vendor', 'taskpane'],
            }),
            new webpack.ProvidePlugin({
                Promise: ['es6-promise', 'Promise'],
            }),
        ],
        devServer: {
            hot: true,
            headers: {
                'Access-Control-Allow-Origin': '*',
            },
            server: {
                type: 'https',
                options:
                    env.WEBPACK_BUILD || options.https !== undefined
                        ? options.https
                        : await getHttpsOptions(),
            },
            port: process.env.npm_package_config_dev_server_port || 3000,
        },
    }
    return config
}

To get the function working as expected the following works:

/* eslint-disable no-undef */

const devCerts = require('office-addin-dev-certs')
const CustomFunctionsMetadataPlugin = require('custom-functions-metadata-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const webpack = require('webpack')

async function getHttpsOptions() {
    const httpsOptions = await devCerts.getHttpsServerOptions()
    return {
        ca: httpsOptions.ca,
        key: httpsOptions.key,
        cert: httpsOptions.cert,
    }
}

module.exports = async (env, options) => {
    const config = {
        devtool: 'source-map',
        entry: {
            polyfill: ['core-js/stable', 'regenerator-runtime/runtime'],
            vendor: ['react', 'react-dom', 'core-js'],
            taskpane: [
                './src/taskpane/index.tsx',
                './src/taskpane/taskpane.html',
            ],
            functions: [
                './src/functions/function.ts',
                './src/functions/function-two.ts',
                './src/functions/function-three.ts',
                './src/functions/function-utils.ts',
                './src/functions/function-utils-two.ts',
                './src/functions/constants.ts',
            ],
        },
        output: {
            clean: true,
        },
        resolve: {
            extensions: ['.ts', '.tsx', '.html', '.js'],
        },
        module: {
            rules: [
                {
                    test: /\.ts$/,
                    exclude: /node_modules/,
                    use: {
                        loader: 'babel-loader',
                        options: {
                            presets: ['@babel/preset-typescript'],
                        },
                    },
                },
                {
                    test: /\.tsx?$/,
                    exclude: /node_modules/,
                    use: ['ts-loader'],
                },
                {
                    test: /\.html$/,
                    exclude: /node_modules/,
                    use: 'html-loader',
                },
                {
                    test: /\.(png|jpg|jpeg|gif|ico)$/,
                    type: 'asset/resource',
                    generator: {
                        filename: 'assets/[name][ext][query]',
                    },
                },
                // we could add support for scss here
                {
                    test: /\.css$/,
                    use: ['style-loader', 'css-loader'],
                },
            ],
        },
        plugins: [
            new CustomFunctionsMetadataPlugin({
                output: 'functions.json',
                input: [
                    './src/functions/function.ts',
                    './src/functions/function-two.ts',
                    './src/functions/function-three.ts',
                ],
            }),
            new HtmlWebpackPlugin({
                filename: 'taskpane.html',
                template: './src/taskpane/taskpane.html',
                chunks: ['polyfill', 'vendor', 'taskpane', 'functions'],
            }),
            new webpack.ProvidePlugin({
                Promise: ['es6-promise', 'Promise'],
            }),
        ],
        devServer: {
            hot: true,
            headers: {
                'Access-Control-Allow-Origin': '*',
            },
            server: {
                type: 'https',
                options:
                    env.WEBPACK_BUILD || options.https !== undefined
                        ? options.https
                        : await getHttpsOptions(),
            },
            port: process.env.npm_package_config_dev_server_port || 3000,
        },
    }
    return config
}

but this breaks hot reload, can you please guide me to the correct webpack configuration to have this work as expected? Thanks.

millerds commented 2 months ago

It would depend on what's in the manifest (and possibly what's in the two html files).

sheldoncoates commented 2 months ago

My manifest is identical to this https://github.com/OfficeDev/Excel-Custom-Functions/commit/76710075a6cd4ada26eb81d2958be9992ece0b51 prior to the revert - it uses the shared runtime.

functions.html is the same as well but i use https://appsforoffice.microsoft.com/lib/1/hosted/custom-functions-runtime.js to always be on the latest version - please let me know if this is no longer correct.

which other html file are you referring too? i dont use commands

sheldoncoates commented 2 months ago

ive also noticed that when i have my webpack as follows:

            new HtmlWebpackPlugin({
                filename: 'functions.html',
                template: './src/functions/functions.html',
                chunks: ['polyfill', 'functions'],
            }),
            new HtmlWebpackPlugin({
                filename: 'taskpane.html',
                template: './src/taskpane/taskpane.html',
                chunks: ['polyfill', 'vendor', 'taskpane'],
            }),

I get the error on taskpane code change:

undefined is not an object (evaluating 'currentUpdateRuntime.push')
@https://localhost:3000/taskpane.js:160459:45
global code@https://localhost:3000/vendor.06a5c675b6bf06c3aabc.hot-update.js:2:52

but if a remove the change to the taskpane code, save, make the change again, save - hot reload works?

millerds commented 2 months ago

If you are using the shared runtime then you should be using https://github.com/OfficeDev/Excel-Custom-Functions-Shared as a model.

The html files I'm referring to are taskpane.html and functions.html. The manifest has settings that indicate which files to use for custom functions. The shared runtime template was designed to use taskpane.html as the functions html so when you change that in webpack the manifest also needs to be updated to look for the other one.

Also note that the entry point for the taskpane code in webpack includes the html file . . . this is to trigger hot reloading related to that file. None of the entry points you have include the functions html file so hot reloading isn't triggered for that file.