What I need to do:
Create a shared runtime environment so that my excel custom functions can have access to the workbook information.
Current Setup: The way the project seems to have been set up is, using the yeoman office generator to generate a custom function addin and then using vue-cli to generate a vue app to use as the taskpane.
Folder Structure:
RootFolder
Add-In
dist
node_modules
src
config.json
manifest.xml
jest.config.js
package.json
tsconfig.json
webpack.config.js
Taskpane
dist
public
node_modules
src
assets
modules
plugins
router
store
views
App.vue
main.ts
shims-vue.d.ts
shims.tsx.d.ts
shims.vuetify.d.ts
.browserlistrc
.eslintrc.js
babel.config.js
package.json
tsconfig.json
vue.config.js
Webpack config for addin prior to changes:
module.exports = async (env, options) => {
const dev = options.mode === "development";
const buildType = dev ? "dev" : "prod";
const config = {
node: {
fs: 'empty' // https://github.com/webpack-contrib/css-loader/issues/447
},
devtool: "source-map",
entry: {
functions: "./src/functions/functions.ts",
polyfill: "@babel/polyfill",
commands: "./src/commands/commands.ts"
},
resolve: {
extensions: [".ts", ".tsx", ".html", ".js"]
},
module: {
rules: [
{
test: /\.ts$/,
exclude: /node_modules/,
use: "babel-loader"
},
{
test: /\.tsx?$/,
exclude: /node_modules/,
use: "ts-loader"
},
{
test: /\.html$/,
exclude: /node_modules/,
use: "html-loader"
},
{
test: /\.(png|jpg|jpeg|gif)$/,
loader: "file-loader",
options: {
name: '[path][name].[ext]',
}
}
]
},
plugins: [
new CleanWebpackPlugin({
cleanOnceBeforeBuildPatterns: dev ? [] : ["**/*"]
}),
new CustomFunctionsMetadataPlugin({
output: "functions.json",
input: "./src/functions/functions.ts"
}),
new EnvironmentPlugin({
NODE_ENV: env.NODE_ENV || 'local'
}),
new HtmlWebpackPlugin({
filename: "functions.html",
template: "./src/functions/functions.html",
chunks: ["polyfill", "functions"]
}),
new CopyWebpackPlugin([
{
to: "[name]." + buildType + ".[ext]",
from: "manifest*.xml",
transform(content) {
if (dev) {
return content;
} else {
return content.toString().replace(new RegExp(urlDev, "g"), urlProd);
}
}
}
]),
new HtmlWebpackPlugin({
filename: "commands.html",
template: "./src/commands/commands.html",
chunks: ["polyfill", "commands"]
})
],
devServer: {
headers: {
"Access-Control-Allow-Origin": "*"
},
https: (options.https !== undefined) ? options.https : await devCerts.getHttpsServerOptions(),
port: process.env.npm_package_config_dev_server_port || 4000
}
};
return config;
};
Main.ts File:
import Vue from "vue";
import App from "./App.vue";
import router from "./router";
import store from "./store/index";
import vuetify from "./plugins/vuetify";
Vue.config.productionTip = false;
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
(window as any).Office.onReady(() => {
new Vue({
router,
store,
vuetify,
render: (h) => h(App),
}).$mount("#app");
});
tsconfig.json - add-in
{
"compilerOptions": {
"allowJs": true,
"baseUrl": ".",
"esModuleInterop": true,
"experimentalDecorators": true,
"jsx": "react",
"module": "commonjs",
"noEmitOnError": true,
"outDir": "lib",
"resolveJsonModule": true,
"sourceMap": true,
"target": "es5",
"lib": [
"es2015",
"dom"
]
},
"exclude": [
"node_modules",
"dist",
"lib",
"lib-amd"
]
}
tsconfig.json - taskpane
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"strict": true,
"jsx": "preserve",
"importHelpers": true,
"moduleResolution": "node",
"experimentalDecorators": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"sourceMap": true,
"baseUrl": ".",
"types": [
"webpack-env",
"node"
],
"paths": {
"@/*": [
"src/*"
]
},
"lib": [
"esnext",
"dom",
"dom.iterable",
"scripthost"
]
},
"include": [
"src/**/*.ts",
"src/**/*.tsx",
"src/**/*.vue",
"tests/**/*.ts",
"tests/**/*.tsx"
],
"exclude": [
"node_modules"
]
}
The Add-In folder holds the custom functions and the Taskpane folder holds the Vuejs app. I have left out other default files and added these because I believe they will help communicate my possible issue.
Steps To Merge:
I have edited Manifest.xml following the steps provided on Microsofts documentation. Updated name to use SharedRuntime
<Set Name="SharedRuntime" MinVersion="1.1"/>
Added Runtimes tags within the Host tag
<Runtimes>
<Runtime resid="Taskpane.Url" lifetime="long" />
</Runtimes>
Updated the Page tag to use Taskpane.Url
<Page>
<SourceLocation resid="Taskpane.Url"/>
</Page>
Updated the FunctionFile tag with Taskpane.Url
<FunctionFile resid="Taskpane.Url"/>
In webpack.config.js: I have removed the HtmlWebpackPlugin references and replaced with the following.
new HtmlWebpackPlugin({
filename: "index.html",
template: "../taskpane/public/index.html",
chunks: ["polyfill", "taskpane", "commands", "functions"]
})
Steps I did not provided in the Documentation: Since the project was created using the yeoman office generator with the customfunctions option selected, I believe I needed to add the following to the webpack.config.js file's entry object:
entry: {
functions: "./src/functions/functions.ts",
polyfill: "@babel/polyfill",
commands: "./src/commands/commands.ts",
taskpane: "../taskpane/src/main.ts" <----- new addition
Problem: When I try to build the project it throws errors such as:
ERROR in ../taskpane/src/main.ts Module build failed Error: TypeScript emitted no output for main.ts
Module not found errors
Property doesn't exist errors
More Info:
I'm thinking (but would love to confirm) that the steps I took to create the shared runtime provided by the documentation are indeed correct, along with adding the taskpane file to the entry object in the webpack config. But I just can't figure out why it won't compile. I'm thinking it has to do with the different tsconfig.json configurations. The addin is using target: "es5" while taskpane is using target: "esnext". There are other differences as well. If somebody can help me with this and needs that information, I will be more than happy to add it.