reactjswebpacktailwind-cssoffice-addinsyeoman-generator

Setup TailwindCSS with Microsoft Office Add-in (React/ts) created with Yeoman Generator


I've found this post, but it's solution is broken, it seems outdated. I've also tried to follow this tutorial, but it didn't work, same problem as above.

Where I feel I'm getting stuck is to find the entry point, since there is no index.html in this project.

Lastly I've found this tutorial, which is the approach I'm trying to get to work.

I think I'm getting configs conflict on webpack.config.js.

// Tutorial config
const path = require('path')

module.exports = {
  mode: 'development',
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'bundle.js',
  },
  devServer: {
    static: {
      directory: path.resolve(__dirname, 'dist'),
    },
    port: 3000,
    open: true,
    hot: true,
    compress: true,
    historyApiFallback: true,
  },
  module: {
    rules: [
      {
        test: /\.css$/i,
        include: path.resolve(__dirname, 'src'),
        use: ['style-loader', 'css-loader', 'postcss-loader'],
      },
    ],
  },
}

// Pre existing config
const devCerts = require("office-addin-dev-certs");
const CopyWebpackPlugin = require("copy-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const webpack = require("webpack");

const urlDev = "https://localhost:3000/";
const urlProd = "https://www.contoso.com/"; // CHANGE THIS TO YOUR PRODUCTION DEPLOYMENT LOCATION

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

module.exports = async (env, options) => {
  const dev = options.mode === "development";
  const config = {
    devtool: "source-map",
    entry: {
      polyfill: ["core-js/stable", "regenerator-runtime/runtime"],
      vendor: ["react", "react-dom", "core-js", "@fluentui/react"],
      taskpane: ["react-hot-loader/patch", "./src/taskpane/index.tsx", "./src/taskpane/taskpane.html"],
      commands: "./src/commands/commands.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: ["react-hot-loader/webpack", "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]",
          },
        },
      ],
    },
    plugins: [
      new CopyWebpackPlugin({
        patterns: [
          {
            from: "assets/*",
            to: "assets/[name][ext][query]",
          },
          {
            from: "manifest*.xml",
            to: "[name]" + "[ext]",
            transform(content) {
              if (dev) {
                return content;
              } else {
                return content.toString().replace(new RegExp(urlDev, "g"), urlProd);
              }
            },
          },
        ],
      }),
      new HtmlWebpackPlugin({
        filename: "taskpane.html",
        template: "./src/taskpane/taskpane.html",
        chunks: ["taskpane", "vendor", "polyfills"],
      }),
      new HtmlWebpackPlugin({
        filename: "commands.html",
        template: "./src/commands/commands.html",
        chunks: ["commands"],
      }),
      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;
};

// My merge attempt

module.exports = async (env, options) => {
  const dev = options.mode === "development";
  const config = {
    mode: "development",
    entry: {
      polyfill: ["core-js/stable", "regenerator-runtime/runtime"],
      vendor: ["react", "react-dom", "core-js", "@fluentui/react"],
      taskpane: ["react-hot-loader/patch", "./src/taskpane/index.tsx", "./src/taskpane/taskpane.html"],
      commands: "./src/commands/commands.ts",
    },
    output: {
      clean: true,
    },
    devtool: "source-map",
    resolve: {
      extensions: [".ts", ".tsx", ".html", ".js"],
    },
    module: {
      rules: [
        // The same ...
        {
          test: /\.css$/i,
          include: path.resolve(__dirname, "src"),
          use: ["style-loader", "css-loader", "postcss-loader"],
        },
      ],
    },
    // plugins: ... The same
    devServer: {
      hot: true,
      headers: {
        "Access-Control-Allow-Origin": "*",
      },
      static: {
        directory: path.resolve(__dirname, "dist"),
      },
      server: {
        type: "https",
        options: env.WEBPACK_BUILD || options.https !== undefined ? options.https : await getHttpsOptions(),
      },
      port: process.env.npm_package_config_dev_server_port || 3000,
      open: true,
      compress: true,
      historyApiFallback: true,
    },
  };

  return config;
};

On all approaches I got stuck on this point. When running npm run build I get this message:

warn - No utility classes were detected in your source files. If this is unexpected, double-check the `content` option in your Tailwind CSS configuration.
warn - https://tailwindcss.com/docs/content-configuration

The project structure is this (before TailwindCSS changes):

enter image description here

The project structure is this (afeter TailwindCSS changes):

enter image description here

Also this is my tailwind.config.js

module.exports = {
  content: ["./dist/*.html"],
  theme: {
    extend: {},
  },
  variants: {
    extend: {},
  },
  plugins: [],
};

Solution

  • I found this tutorial and finally made it work.

    My final webpack.config.js is:

    const devCerts = require('office-addin-dev-certs')
    const CopyWebpackPlugin = require('copy-webpack-plugin')
    const HtmlWebpackPlugin = require('html-webpack-plugin')
    const webpack = require('webpack')
    const path = require('path')
    
    const urlDev = 'https://localhost:3000/'
    const urlProd = 'https://compass-esg.jgp.com.br/'
    
    async function getHttpsOptions() {
      const httpsOptions = await devCerts.getHttpsServerOptions()
      return { ca: httpsOptions.ca, key: httpsOptions.key, cert: httpsOptions.cert }
    }
    
    module.exports = async (env, options) => {
      const dev = options.mode === 'development'
      const config = {
        devtool: 'source-map',
        entry: {
          polyfill: ['core-js/stable', 'regenerator-runtime/runtime'],
          vendor: ['react', 'react-dom', 'core-js'],
          taskpane: [
            'react-hot-loader/patch',
            './src/taskpane/index.tsx',
            './src/taskpane/taskpane.html',
          ],
          commands: './src/commands/commands.ts',
        },
        mode: 'development',
        output: {
          path: path.resolve(__dirname, './dist'),
          filename: '[name].js',
        },
        resolve: {
          extensions: ['.ts', '.tsx', '.html', '.js', '.ts', '.jsx'],
        },
        module: {
          rules: [
            {
              test: /\.ts$/,
              exclude: /node_modules/,
              use: {
                loader: 'babel-loader',
                options: {
                  presets: ['@babel/preset-typescript'],
                },
              },
            },
            {
              test: /\.tsx?$/,
              exclude: /node_modules/,
              use: ['react-hot-loader/webpack', '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]',
              },
            },
            {
              test: /\.(js|jsx)$/,
              include: /node_modules\/react-dom/,
              use: ['react-hot-loader/webpack'],
            },
            {
              test: /\.css$/i,
              use: ['style-loader', 'css-loader', 'postcss-loader'],
            },
          ],
        },
        plugins: [
          new CopyWebpackPlugin({
            patterns: [
              {
                from: 'assets/*',
                to: 'assets/[name][ext][query]',
              },
              {
                from: 'manifest*.xml',
                to: '[name]' + '[ext]',
                transform(content) {
                  if (dev) {
                    return content
                  } else {
                    return content
                      .toString()
                      .replace(new RegExp(urlDev, 'g'), urlProd)
                  }
                },
              },
            ],
          }),
          new HtmlWebpackPlugin({
            filename: 'taskpane.html',
            template: './src/taskpane/taskpane.html',
            chunks: ['taskpane', 'vendor', 'polyfills', 'commands'],
          }),
          new webpack.ProvidePlugin({
            Promise: ['es6-promise', 'Promise'],
          }),
        ],
        devServer: {
          hot: true,
          open: true,
          liveReload: true,
          headers: {
            'Access-Control-Allow-Origin': '*',
          },
          server: {
            type: 'https',
            options:
              env.WEBPACK_BUILD || options.https !== undefined
                ? options.https
                : await getHttpsOptions(),
          },
          static: {
            directory: path.join(__dirname, 'public'),
          },
          port: process.env.npm_package_config_dev_server_port || 3000,
        },
      }
    
      return config
    }