[SOLVED] Use functions from bundled js files in UI / Split UI js into multiple files

Hi,

I’m trying to split my UI js content into multiple files.
The bundling works in theory, if I add the js files to my app.ts in my webpack configuration:

  entry: {
    app: ['./src/app.ts', './src/test.js'] // This is the entry point for our plugin code.
  },

The code of test.js now runs, but with strong limitations.

  1. The js in my ui.html cannot use the functions of the test.js
  2. If I try to use the DOM in my test.js, the plugin crashs
  3. I also tried to use a typescript format instead of js for my test file, so I can use the import command, but DOM related code like “document” is unknown for typescript.

The main goal I want to achieve is simply having a cleaner structure and in best case, referencing my js content across files. :smiley:
I was also taking a look into the Libraries and Bundling documentation and tried it with the script tag and as said with the bundling as well to add more js files to my UI.

Thanks for your help!

This is unrelated to bundling. The plugin has two environments that are separate: UI and backend. How Plugins Run | Plugin API They communicate via postMessage.

Exactly, but the test.js file should be an extension to my UI environment.
The architecture right now is:

backend (all fine)

  • app.ts
  • modules folder with more ts files

frontend

  • ui.html

Frontend how I would like it do have

  • ui.html
  • extension1.js
  • extension2.js

Whats your full webpack config and tsconfig?

webpack config

const path = require('path');
const webpack = require('webpack');
const HtmlInlineScriptPlugin = require('html-inline-script-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = (env, argv) => ({
mode: argv.mode === 'production' ? 'production' : 'development',

// This is necessary because Figma's 'eval' works differently than normal eval
devtool: argv.mode === 'production' ? false : 'inline-source-map',
  entry: {
    ui: ['./src/ui/test.js'],
    app: ['./src/app.ts'] // This is the entry point for our plugin code.
  },
  module: {
    rules: [
      // Converts TypeScript code to JavaScript
      {
        test: /\.tsx?$/,
        use: 'ts-loader',
        exclude: /node_modules/,
      },
    ],
  },
  // Webpack tries these extensions for you if you omit the extension like "import './file'"
  resolve: {
    extensions: ['.ts', '.js'],
  },
  output: {
    filename: '[name].js',
    path: path.resolve(__dirname, 'dist'),
  },
  // Tells Webpack to generate "ui.html" and to inline "ui.ts" into it
  plugins: [
    new webpack.DefinePlugin({
      global: {}, // Fix missing symbol error when running in developer VM
    }),
    new HtmlWebpackPlugin({
      inject: 'body',
      template: './src/ui.html',
      filename: 'ui.html',
      chunks: ['ui']
    }),
    new HtmlInlineScriptPlugin({
      htmlMatchPattern: [/ui.html/],
      scriptMatchPattern: [/.js$/]
    }),
  ]
});

ts config

{
  "compilerOptions": {
    "target": "es6",
    "lib": ["es6"],
    "outDir": "./dist",
    "allowJs": true,
    "esModuleInterop": true,
    "moduleResolution": "node",
    "module": "commonjs",
    "preserveConstEnums": true,
    "strict": true,
    "typeRoots": [
      "./node_modules/@types",
      "./node_modules/@figma"
    ]
  },
  "include": ["src/**/*.ts"]
}

Wait I think I understand.

I don’t think you are supposed to list multiple files here. The app has only one entry point. To use code from multiple files you simply need to import one file into another, e.g. inside of app.ts write:

import testFunction from './test.ts'; 

and in test.ts write:

export default function testFunction() {
  console.log('my test function')
}

But maybe I’m not fully understanding the issue.

The backend works like a charm. The last webpack entries you have quoted was from a previous post. In my full webpack config, you will see the latest entries:

entry: {
    ui: ['./src/ui/test.js'],
    app: ['./src/app.ts'] // This is the entry point for our plugin code.
  }

The problem I have is that I don’t know how to use multiple js files for my UI environment. I have drawn an abstract example. Everything in green is already working. Everything in red is not working.

Here is an old webpack config from one of my plugins. It worked back in 2019 but I’m not sure about now. Maybe you can compare and see any useful bits in here:

const HtmlWebpackInlineSourcePlugin = require('html-webpack-inline-source-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const path = require('path')

module.exports = (env, argv) => ({
  // This is necessary because Figma's 'eval' works differently than normal eval
  devtool: argv.mode === 'production' ? false : 'inline-source-map',

  entry: {
    ui: './src/ui.ts', // The entry point for your UI code
    code: './src/code.ts', // The entry point for your plugin code
  },

  module: {
    rules: [
      // Converts TypeScript code to JavaScript
      { test: /\.tsx?$/, use: 'ts-loader', exclude: /node_modules/ },

      // Enables including CSS by doing "import './file.css'" in your TypeScript code
      { test: /\.css$/, loader: [{ loader: 'style-loader' }, { loader: 'css-loader' }] },

      // Allows you to use "<%= require('./file.svg') %>" in your HTML code to get a data URI
      { test: /\.(png|jpg|gif|webp|svg)$/, loader: [{ loader: 'url-loader' }] },
    ],
  },

  // Webpack tries these extensions for you if you omit the extension like "import './file'"
  resolve: { extensions: ['.tsx', '.ts', '.jsx', '.js'] },

  output: {
    filename: '[name].js',
    path: path.resolve(__dirname, 'dist'), // Compile into a folder called "dist"
  },

  // Tells Webpack to generate "ui.html" and to inline "ui.ts" into it
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/ui.html',
      filename: 'ui.html',
      inlineSource: '.(js)$',
      chunks: ['ui'],
    }),
    new HtmlWebpackInlineSourcePlugin(),
  ],
})

Additional questions:

  1. How do you import script files in UI? One thing about my plugin above is that there are no import statements in UI, everything is imported in the ui.ts file only and the generated JS file just turns into an inline script in the HTML afterwards.

  2. What does compiled HTML look like in dist folder? Does it have the imports that you are doing there? Does it have JS script code at all?

Thanks for sharing your config!

The loaders were helpful to integrate the css. I was able to load the css via import into my test.js file (used in the UI). Unfortunately, the css was not executed in the html. I checked the ui.html in the dist folder and the css from the extra file was there. But with no impact.
To try out another method, I was loading the css by using the HTMLInlineCSSWebpackPlugin in combination with the MiniCssExtractPlugin. The plugins cause that linked css files will be extracted into the html. But even there, the css had no effect on the html. The css test is pretty simple. I have an html text element with the id="test"and the css is coloring the text in red.

Similar experience for the javascript, when loading it into the html. The js code from the extra file is in the compiled ui.html, but gets not executed. For testing purposes, I was setting up a console.log inside and outside a DOMContentLoaded function.

  1. How do you import script files in UI? One thing about my plugin above is that there are no import statements in UI, everything is imported in the ui.ts file only and the generated JS file just turns into an inline script in the HTML afterwards.

Interesting, how did you manage to write DOM-related code in your ts file? When I was testing to have the test.jsfile as a typescript file and let it compile to js in the end, typescript was beefing that elements like document are not defined.
Right now, I’m loading the extra js code into the html via webpack by having an extra entry point ui: ['./src/ui/test.js'] and naming it as a chunk in the HtmlWebpackPlugin:

new HtmlWebpackPlugin({
      inject: 'body',
      template: './src/ui.html',
      filename: 'ui.html',
      inlineSource: '.(js)$',
      chunks: ['ui']
    })

But as said, the code gets ignored when the plugin runs.

  1. What does compiled HTML look like in dist folder? Does it have the imports that you are doing there? Does it have JS script code at all?

The css in the dist/ui.html looks like this
(the css code seems greyed out in Figma’s code preview, but it’s not a comment in VSCode.

<head>
  <title></title>
  <link rel="stylesheet" href="./ui/styles.css">
<style type="text/css">/*!*********************************************************************!*\
  !*** css ./node_modules/css-loader/dist/cjs.js!./src/ui/styles.css ***!
  \*********************************************************************/
#test {
  color: red;
}

/*# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidWkuY3NzIiwibWFwcGluZ3MiOiI7OztBQUFBO0VBQ0UsVUFBVTtBQUNaLEMiLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly90ZXN0LWV4cG9ydC0wMi8uL3NyYy91aS9zdHlsZXMuY3NzIl0sInNvdXJjZXNDb250ZW50IjpbIiN0ZXN0IHtcbiAgY29sb3I6IHJlZDtcbn0iXSwibmFtZXMiOltdLCJzb3VyY2VSb290IjoiIn0=*/</style></head>

<body>

And my test.js code looks like this in the dist/ui.html

var __webpack_exports__ = {};
// This entry need to be wrapped in an IIFE because it need to be isolated against other modules in the chunk.
(() => {
/*!************************!*\
  !*** ./src/ui/test.js ***!
  \************************/
__webpack_require__.r(__webpack_exports__);
/* harmony import */ var _styles_css__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./styles.css */ "./src/ui/styles.css");
console.log('Hello from test.js');

function logMsg(text) {
  console.log(text)
};

// const test = document.getElementById('test');
// console.log('test.js is using ui.html:', test.innerText)



document.addEventListener('DOMContentLoaded', () => {
  console.log('Hello from test.js');
  const app = document.getElementById('app');
  app.innerHTML = '<h1>Hello, Figma Plugin!</h1>';
});
})();

/******/ })()
;
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidWkuanMiLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7QUFBQTs7Ozs7OztVQ0FBO1VBQ0E7O1VBRUE7VUFDQTtVQUNBO1VBQ0E7VUFDQTtVQUNBO1VBQ0E7VUFDQTtVQUNBO1VBQ0E7VUFDQTtVQUNBO1VBQ0E7O1VBRUE7VUFDQTs7VUFFQTtVQUNBO1VBQ0E7Ozs7O1dDdEJBO1dBQ0E7V0FDQTtXQUNBLHVEQUF1RCxpQkFBaUI7V0FDeEU7V0FDQSxnREFBZ0QsYUFBYTtXQUM3RDs7Ozs7Ozs7Ozs7O0FDTkE7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7O0FBRXNCOztBQUV0QjtBQUNBO0FBQ0E7QUFDQTtBQUNBLENBQUMsRSIsInNvdXJjZXMiOlsid2VicGFjazovL3Rlc3QtZXhwb3J0LTAyLy4vc3JjL3VpL3N0eWxlcy5jc3M/ZTcxNSIsIndlYnBhY2s6Ly90ZXN0LWV4cG9ydC0wMi93ZWJwYWNrL2Jvb3RzdHJhcCIsIndlYnBhY2s6Ly90ZXN0LWV4cG9ydC0wMi93ZWJwYWNrL3J1bnRpbWUvbWFrZSBuYW1lc3BhY2Ugb2JqZWN0Iiwid2VicGFjazovL3Rlc3QtZXhwb3J0LTAyLy4vc3JjL3VpL3Rlc3QuanMiXSwic291cmNlc0NvbnRlbnQiOlsiLy8gZXh0cmFjdGVkIGJ5IG1pbmktY3NzLWV4dHJhY3QtcGx1Z2luXG5leHBvcnQge307IiwiLy8gVGhlIG1vZHVsZSBjYWNoZVxudmFyIF9fd2VicGFja19tb2R1bGVfY2FjaGVfXyA9IHt9O1xuXG4vLyBUaGUgcmVxdWlyZSBmdW5jdGlvblxuZnVuY3Rpb24gX193ZWJwYWNrX3JlcXVpcmVfXyhtb2R1bGVJZCkge1xuXHQvLyBDaGVjayBpZiBtb2R1bGUgaXMgaW4gY2FjaGVcblx0dmFyIGNhY2hlZE1vZHVsZSA9IF9fd2VicGFja19tb2R1bGVfY2FjaGVfX1ttb2R1bGVJZF07XG5cdGlmIChjYWNoZWRNb2R1bGUgIT09IHVuZGVmaW5lZCkge1xuXHRcdHJldHVybiBjYWNoZWRNb2R1bGUuZXhwb3J0cztcblx0fVxuXHQvLyBDcmVhdGUgYSBuZXcgbW9kdWxlIChhbmQgcHV0IGl0IGludG8gdGhlIGNhY2hlKVxuXHR2YXIgbW9kdWxlID0gX193ZWJwYWNrX21vZHVsZV9jYWNoZV9fW21vZHVsZUlkXSA9IHtcblx0XHQvLyBubyBtb2R1bGUuaWQgbmVlZGVkXG5cdFx0Ly8gbm8gbW9kdWxlLmxvYWRlZCBuZWVkZWRcblx0XHRleHBvcnRzOiB7fVxuXHR9O1xuXG5cdC8vIEV4ZWN1dGUgdGhlIG1vZHVsZSBmdW5jdGlvblxuXHRfX3dlYnBhY2tfbW9kdWxlc19fW21vZHVsZUlkXShtb2R1bGUsIG1vZHVsZS5leHBvcnRzLCBfX3dlYnBhY2tfcmVxdWlyZV9fKTtcblxuXHQvLyBSZXR1cm4gdGhlIGV4cG9ydHMgb2YgdGhlIG1vZHVsZVxuXHRyZXR1cm4gbW9kdWxlLmV4cG9ydHM7XG59XG5cbiIsIi8vIGRlZmluZSBfX2VzTW9kdWxlIG9uIGV4cG9ydHNcbl9fd2VicGFja19yZXF1aXJlX18uciA9IChleHBvcnRzKSA9PiB7XG5cdGlmKHR5cGVvZiBTeW1ib2wgIT09ICd1bmRlZmluZWQnICYmIFN5bWJvbC50b1N0cmluZ1RhZykge1xuXHRcdE9iamVjdC5kZWZpbmVQcm9wZXJ0eShleHBvcnRzLCBTeW1ib2wudG9TdHJpbmdUYWcsIHsgdmFsdWU6ICdNb2R1bGUnIH0pO1xuXHR9XG5cdE9iamVjdC5kZWZpbmVQcm9wZXJ0eShleHBvcnRzLCAnX19lc01vZHVsZScsIHsgdmFsdWU6IHRydWUgfSk7XG59OyIsImNvbnNvbGUubG9nKCdIZWxsbyBmcm9tIHRlc3QuanMnKTtcblxuZnVuY3Rpb24gbG9nTXNnKHRleHQpIHtcbiAgY29uc29sZS5sb2codGV4dClcbn07XG5cbi8vIGNvbnN0IHRlc3QgPSBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgndGVzdCcpO1xuLy8gY29uc29sZS5sb2coJ3Rlc3QuanMgaXMgdXNpbmcgdWkuaHRtbDonLCB0ZXN0LmlubmVyVGV4dClcblxuaW1wb3J0ICcuL3N0eWxlcy5jc3MnO1xuXG5kb2N1bWVudC5hZGRFdmVudExpc3RlbmVyKCdET01Db250ZW50TG9hZGVkJywgKCkgPT4ge1xuICBjb25zb2xlLmxvZygnSGVsbG8gZnJvbSB0ZXN0LmpzJyk7XG4gIGNvbnN0IGFwcCA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCdhcHAnKTtcbiAgYXBwLmlubmVySFRNTCA9ICc8aDE+SGVsbG8sIEZpZ21hIFBsdWdpbiE8L2gxPic7XG59KTsiXSwibmFtZXMiOltdLCJzb3VyY2VSb290IjoiIn0=</script></body>

Sorry this all seems very strange, no idea how to solve this, I reposted this to the Discord for visibility so hopefully someone who knows more than me can help you.

I haven’t use webpack since I developed Styler:

I don’t know if I understood the issue: you have problem bundling the css into a single html file?

Looking back to my webconfig, I notice that I’ve used 2 plugins:

plugins: [
    new HtmlWebpackPlugin({
      template: './src/ui/main.html',
      filename: 'ui.html',
      inlineSource: '.(js|css)$',  // I've noticed that you're targeting only js here
      chunks: ['ui'],
      minify: true,
      cache: false,
    }),
    new HtmlWebpackInlineSourcePlugin(HtmlWebpackPlugin),
  ],

@Gleb Thank you for the long support. :slight_smile:

@Andrei_Iancu Thank you for taking a look into my problem. I extended my inlineSource by the css tag. The svelte implementation looks interesting as well. Now I want to test it for another plugin!
Yes, my original issue was that I wasn’t sure how to load css and js files into my compiled ui.html.

After all the hours, I now found the problem. It was kind of stupid and unbelievable. :joy:
I was further exploring why the dist/ui.html was not executing the compiled code and started to play around in the dist/ui.html. When I have noticed that no single change in there was affecting the plugin, even when I cleaned the whole file, I was taking a look into my manifest. Turns out, the manifest was taking the ui.html in the src folder, not the dist folder.
I changed "ui": "src/ui.html", to "ui": "dist/ui.html", and now it works:
:white_check_mark: external js files for my UI
:white_check_mark: external scss files for my UI

1 Like