# Three Javascript injection

**URL:** <https://forum.inductiveautomation.com/t/three-javascript-injection/106832>\
**Category:** Ignition\
**Tags:** perspective\
**Created:** [May 29, 2025, 7:00pm UTC](https://forum.inductiveautomation.com/t/three-javascript-injection/106832 "2025-05-29T19:00:33Z")\
**Posts on this page:** 11\
**Page:** 1

<div class="post-metadata">

**Author:** ![luis](https://avatars.discourse-cdn.com/v4/letter/l/3e96dc/32.png) [@luis](https://forum.inductiveautomation.com/u/luis)\
**Post date:** [May 29, 2025, 7:00pm UTC](https://forum.inductiveautomation.com/t/three-javascript-injection/106832/1 "2025-05-29T19:00:33Z")

</div>

I am using the Three Javascript library to inject into a markdown component.  
Now I am using an older version of Three since I get error with the newest one:

> <https://github.com/mrdoob/three.js/blob/9b274fef1d2b9ba0fc47410d06593e1f34f4df0e/build/three.min.js>

This older version does not have any **Import/Export** statements, so it works fine.

Now I am planning to use the OrbitControls library on the same repository

> <https://github.com/mrdoob/three.js/blob/9b274fef1d2b9ba0fc47410d06593e1f34f4df0e/examples/jsm/controls/OrbitControls.js>

I minified the library but I am having issues with running it since this has **Import/Export** statements.

Does anybody have any ideas on how to replace this **Import/Export** statements?

---

<div class="post-metadata">

**Author:** ![luis](https://avatars.discourse-cdn.com/v4/letter/l/3e96dc/32.png) [@luis](https://forum.inductiveautomation.com/u/luis)\
**Post date:** [May 29, 2025, 9:23pm UTC](https://forum.inductiveautomation.com/t/three-javascript-injection/106832/2 "2025-05-29T21:23:13Z")

</div>

@victordcq @bmusson maybe you guys have some ideas?

---

<div class="post-metadata">

**Author:** ![bmusson](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/bmusson/32/87530_2.png) [@bmusson](https://forum.inductiveautomation.com/u/bmusson)\
**Post date:** [May 29, 2025, 9:57pm UTC](https://forum.inductiveautomation.com/t/three-javascript-injection/106832/3 "2025-05-29T21:57:49Z")

</div>

You can't just minify the file, you'll need to bundle everything together into a single self-contained script.

The bundler needs to resolve and inline the code from the `Three.js` imports and emit a single browser-compatible file.

I like Rollup.

> **[Rollup](https://rollupjs.org/)**
>
> The JavaScript module bundler

But, my recommendation is to write a module and not do JavaScript injection. If you can understand how JavaScript injection works using the Markdown component, then you are more than qualified to write a proper module (and people around here will be more likely to help you if you're doing something supported).

---

<div class="post-metadata">

**Author:** ![luis](https://avatars.discourse-cdn.com/v4/letter/l/3e96dc/32.png) [@luis](https://forum.inductiveautomation.com/u/luis)\
**Post date:** [May 29, 2025, 10:28pm UTC](https://forum.inductiveautomation.com/t/three-javascript-injection/106832/4 "2025-05-29T22:28:27Z")

</div>

Thanks for the advice!  
I was thinking about getting started with module development, but I had no application on mind before ... Now I have this good use case!

---

<div class="post-metadata">

**Author:** ![victordcq](https://avatars.discourse-cdn.com/v4/letter/v/f6c823/32.png) [@victordcq](https://forum.inductiveautomation.com/u/victordcq)\
**Post date:** [June 2, 2025, 8:57am UTC](https://forum.inductiveautomation.com/t/three-javascript-injection/106832/5 "2025-06-02T08:57:52Z")

</div>

If you miniefied a whole lib, you should definitly put it into either a module,  
or if you are not familiar enough with JAVA you should put the mini.js script into injectables.

Check out this post, here i copied a mini lib to injectable/head.txt. and call its functions in a markdown

> [@Adding moving particles to the background](https://forum.inductiveautomation.com/t/adding-moving-particles-to-the-background/98283/10):
>
> i think i coppied different json than the front page example, and maybe missing some css. this head.txt has to go here(depending on instal folder, might need to create injectables yourself too) C:\Program Files\Inductive Automation\Ignition\data\modules\com.inductiveautomation.perspective\injectables (copy pasted from [https://cdn.jsdelivr.net/npm/particles.js@2.0.0/particles.min.js](https://cdn.jsdelivr.net/npm/particles.js@2.0.0/particles.min.js)) [head\_bottom.txt](https://forum.inductiveautomation.com/uploads/short-url/qcsBo4SN24cig9PP6chJ9O4IHv7.txt) (22.6 KB) Than a simple view with simple js code code = """\<img style='display:none'…

That said a module for three.js is probably something other people are interested in too.

---

<div class="post-metadata">

**Author:** ![luis](https://avatars.discourse-cdn.com/v4/letter/l/3e96dc/32.png) [@luis](https://forum.inductiveautomation.com/u/luis)\
**Post date:** [June 3, 2025, 6:47am UTC](https://forum.inductiveautomation.com/t/three-javascript-injection/106832/6 "2025-06-03T06:47:11Z")

</div>

> [@victordcq](#):
>
> for three.js is probably something other people are interested in too.

I am starting already with the module tutorials. Thank you!

---

<div class="post-metadata">

**Author:** ![bmusson](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/bmusson/32/87530_2.png) [@bmusson](https://forum.inductiveautomation.com/u/bmusson)\
**Post date:** [June 3, 2025, 9:39pm UTC](https://forum.inductiveautomation.com/t/three-javascript-injection/106832/7 "2025-06-03T21:39:10Z")

</div>

Here's a super untested component that will render user-supplied JavaScript as a React Function component. It uses Babel to transpile JSX on the client-side, no idea yet what the performance implications are.

Props are passed in like you'd expect. You also currently have access to the Bable `TransformOptions` through `options.babel`

I spent more time on the component icons than the component itself, so I'm sure this has lifecycle problems somewhere 🤷‍♂️.

The attached view shows an example of loading Three.js from a CDN.

I'm not sure I want to go down the road of including `Three.js` in Periscope... there is simply an endless amount of JavaScript libraries that someone may want to include, and I want to be conscious of Periscope's bundle size. However, I do recognize that loading from a CDN is not feasible for most Ignition installations. I think it's time for inclusion of the `web-library` folder feature.

I don't currently have much time to allocate to this component, which is why I'm shadow dropping it in its current state. Any testing/feedback would be appreciated.

P.S. Still wishing for a way to interact with [NodeEditor.createValueEditor](https://files.inductiveautomation.com/sdk/javadoc/ignition81/8.1.33/com/inductiveautomation/ignition/client/jsonedit/NodeEditor.html).... 🧞 🪔 💭💭

[Embr-Periscope-0.7.4-ReactComponent.modl](https://forum.inductiveautomation.com/uploads/short-url/gxLjm3P7QUx5RtpgsyL7GrtD2iP.modl) (2.5 MB)

> **view.json**
>
> ```python
> {
> "custom": {},
> "params": {},
> "props": {
> "defaultSize": {
> "height": 747,
> "width": 1399
> }
> },
> "root": {
> "children": [
> {
> "meta": {
> "name": "React_1"
> },
> "position": {
> "height": 694,
> "width": 754,
> "y": -0.5
> },
> "props": {
> "component": "() \u003d\u003e {\n const containerRef \u003d React.useRef(null);\n\n React.useEffect(() \u003d\u003e {\n let renderer, camera, scene, cube, frameId;\n let resizeObserver;\n\n const loadAndInit \u003d async () \u003d\u003e {\n const THREE \u003d await import(\u0027https://cdn.jsdelivr.net/npm/three@0.161.0/build/three.module.js\u0027);\n\n const {\n Scene,\n PerspectiveCamera,\n WebGLRenderer,\n BoxGeometry,\n MeshBasicMaterial,\n Mesh,\n Color,\n } \u003d THREE;\n\n scene \u003d new Scene();\n scene.background \u003d new Color(0x202030);\n\n const container \u003d containerRef.current;\n const width \u003d container.clientWidth;\n const height \u003d container.clientHeight;\n\n camera \u003d new PerspectiveCamera(75, width / height, 0.1, 1000);\n camera.position.z \u003d 3;\n\n renderer \u003d new WebGLRenderer({ antialias: true });\n renderer.setSize(width, height);\n container.appendChild(renderer.domElement);\n\n const geometry \u003d new BoxGeometry();\n const material \u003d new MeshBasicMaterial({ color: 0x44aa88, wireframe: true });\n cube \u003d new Mesh(geometry, material);\n scene.add(cube);\n\n const animate \u003d () \u003d\u003e {\n cube.rotation.x +\u003d 0.01;\n cube.rotation.y +\u003d 0.01;\n renderer.render(scene, camera);\n frameId \u003d requestAnimationFrame(animate);\n };\n\n animate();\n\n const handleResize \u003d () \u003d\u003e {\n if (!renderer || !camera) return;\n const newWidth \u003d container.clientWidth;\n const newHeight \u003d container.clientHeight;\n camera.aspect \u003d newWidth / newHeight;\n camera.updateProjectionMatrix();\n renderer.setSize(newWidth, newHeight);\n };\n\n resizeObserver \u003d new ResizeObserver(handleResize);\n resizeObserver.observe(container);\n };\n\n loadAndInit().catch(console.error);\n\n return () \u003d\u003e {\n cancelAnimationFrame(frameId);\n if (resizeObserver) {\n resizeObserver.disconnect();\n }\n if (renderer?.domElement \u0026\u0026 containerRef.current?.contains(renderer.domElement)) {\n containerRef.current.removeChild(renderer.domElement);\n }\n };\n }, []);\n\n return (\n \u003cdiv\n ref\u003d{containerRef}\n style\u003d{{\n width: \u0027100%\u0027,\n height: \u0027100%\u0027,\n overflow: \u0027hidden\u0027,\n margin: 0,\n padding: 0,\n position: \u0027relative\u0027,\n }}\n /\u003e\n );\n}",
> "options": {
> "babel": {
> "presets": [
> "react"
> ]
> }
> },
> "props": {}
> },
> "type": "embr.periscope.embedding.react"
> },
> {
> "meta": {
> "name": "React"
> },
> "position": {
> "height": 32,
> "width": 300,
> "x": 750,
> "y": -0.5
> },
> "props": {
> "component": "(props) \u003d\u003e \u003cdiv\u003e{props.text}\u003c/div\u003e",
> "options": {
> "babel": {
> "presets": [
> "react"
> ]
> }
> },
> "props": {
> "text": "Hello World!"
> }
> },
> "type": "embr.periscope.embedding.react"
> },
> {
> "meta": {
> "name": "React_0"
> },
> "position": {
> "height": 503,
> "width": 300,
> "x": 770,
> "y": 188
> },
> "props": {
> "component": "(props) \u003d\u003e {\n const styles \u003d {\n container: {\n minHeight: \u0027auto\u0027,\n height: \u0027100%\u0027,\n display: \u0027flex\u0027,\n alignItems: \u0027center\u0027,\n justifyContent: \u0027center\u0027,\n backgroundColor: \u0027transparent\u0027\n },\n form: {\n backgroundColor: \u0027#ffffff\u0027,\n padding: \u00272rem\u0027,\n borderRadius: \u00271rem\u0027,\n boxShadow: \u00270 10px 25px rgba(0, 0, 0, 0.1)\u0027,\n width: \u0027100%\u0027,\n maxWidth: \u0027400px\u0027,\n },\n title: {\n fontSize: \u00271.5rem\u0027,\n fontWeight: \u0027bold\u0027,\n marginBottom: \u00271.5rem\u0027,\n color: \u0027#1f2937\u0027,\n textAlign: \u0027center\u0027,\n },\n label: {\n display: \u0027block\u0027,\n color: \u0027#4b5563\u0027,\n marginBottom: \u00270.5rem\u0027,\n },\n input: {\n width: \u0027100%\u0027,\n padding: \u00270.5rem 1rem\u0027,\n border: \u00271px solid #d1d5db\u0027,\n borderRadius: \u00270.5rem\u0027,\n outline: \u0027none\u0027,\n marginBottom: \u00271.5rem\u0027,\n },\n button: {\n width: \u0027100%\u0027,\n backgroundColor: \u0027#2563eb\u0027,\n color: \u0027#ffffff\u0027,\n padding: \u00270.5rem 1rem\u0027,\n borderRadius: \u00270.5rem\u0027,\n border: \u0027none\u0027,\n cursor: \u0027pointer\u0027,\n transition: \u0027background-color 0.2s ease\u0027,\n },\n footerText: {\n textAlign: \u0027center\u0027,\n fontSize: \u00270.875rem\u0027,\n color: \u0027#6b7280\u0027,\n marginTop: \u00271rem\u0027,\n },\n link: {\n color: \u0027#2563eb\u0027,\n textDecoration: \u0027none\u0027,\n },\n };\n\n const handleClick \u003d (message) \u003d\u003e (event) \u003d\u003e {\n event.preventDefault();\n perspective.sendMessage(message, {}, \u0027page\u0027);\n };\n\n return (\n \u003cdiv style\u003d{styles.container}\u003e\n \u003cform style\u003d{styles.form}\u003e\n \u003ch2 style\u003d{styles.title}\u003e{props.title}\u003c/h2\u003e\n\n \u003clabel htmlFor\u003d\"email\" style\u003d{styles.label}\u003eEmail\u003c/label\u003e\n \u003cinput\n type\u003d\"email\"\n id\u003d\"email\"\n placeholder\u003d\"you@example.com\"\n style\u003d{styles.input}\n /\u003e\n\n \u003clabel htmlFor\u003d\"password\" style\u003d{styles.label}\u003ePassword\u003c/label\u003e\n \u003cinput\n type\u003d\"password\"\n id\u003d\"password\"\n placeholder\u003d\"••••••••\"\n style\u003d{styles.input}\n /\u003e\n\n \u003cbutton type\u003d\"button\" onClick\u003d{handleClick(\u0027loginClicked\u0027)} style\u003d{styles.button}\u003e\n Log In\n \u003c/button\u003e\n\n \u003cp style\u003d{styles.footerText}\u003e\n Don’t have an account?{\u0027 \u0027}\n \u003ca href\u003d\"#\" onClick\u003d{handleClick(\u0027signupClicked\u0027)} style\u003d{styles.link}\u003eSign up\u003c/a\u003e\n \u003c/p\u003e\n \u003c/form\u003e\n \u003c/div\u003e\n );\n}",
> "options": {
> "babel": {
> "presets": [
> "react"
> ]
> }
> },
> "props": {
> "title": "New Title!!"
> },
> "style": {
> "overflow": "visible"
> }
> },
> "scripts": {
> "customMethods": [],
> "extensionFunctions": null,
> "messageHandlers": [
> {
> "messageType": "loginClicked",
> "pageScope": true,
> "script": "\tsystem.perspective.print(\u0027Login Clicked!\u0027)",
> "sessionScope": false,
> "viewScope": false
> },
> {
> "messageType": "signupClicked",
> "pageScope": true,
> "script": "\tsystem.perspective.print(\u0027Signup Clicked!\u0027)",
> "sessionScope": false,
> "viewScope": false
> }
> ]
> },
> "type": "embr.periscope.embedding.react"
> }
> ],
> "meta": {
> "name": "root"
> },
> "type": "ia.container.coord"
> }
> }
> 
> ```

Edit: Here's the branch - [GitHub - mussonindustrial/embr at feat/periscope/react-component](https://github.com/mussonindustrial/embr/tree/feat/periscope/react-component)

---

<div class="post-metadata">

**Author:** ![paul-griffith](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/paul-griffith/32/37249_2.png) [@paul-griffith](https://forum.inductiveautomation.com/u/paul-griffith)\
**Post date:** [June 4, 2025, 5:47am UTC](https://forum.inductiveautomation.com/t/three-javascript-injection/106832/8 "2025-06-04T05:47:11Z")

</div>

> [@bmusson](#):
>
> P.S. Still wishing for a way to interact with [NodeEditor.createValueEditor](https://files.inductiveautomation.com/sdk/javadoc/ignition81/8.1.33/com/inductiveautomation/ignition/client/jsonedit/NodeEditor.html).... 🧞 🪔 💭💭

It's not the same, but you could register a design delegate that will show up in the space above the property editor (like the flex container buttons) and put your own RSyntaxTextArea or what have you up there, right?

---

<div class="post-metadata">

**Author:** ![bmusson](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/bmusson/32/87530_2.png) [@bmusson](https://forum.inductiveautomation.com/u/bmusson)\
**Post date:** [June 4, 2025, 11:28am UTC](https://forum.inductiveautomation.com/t/three-javascript-injection/106832/9 "2025-06-04T11:28:35Z")

</div>

Ooh, yeah.  
I think that would be a good solution for this component since it only has the one main script property.

---

<div class="post-metadata">

**Author:** ![luis](https://avatars.discourse-cdn.com/v4/letter/l/3e96dc/32.png) [@luis](https://forum.inductiveautomation.com/u/luis)\
**Post date:** [June 19, 2025, 12:53am UTC](https://forum.inductiveautomation.com/t/three-javascript-injection/106832/10 "2025-06-19T00:53:08Z")

</div>

Thanks for sharing! I did not understand the module in depth but this worked for me. I was able to use some three libraries. I also used a CDN. I wanted to use a local repository but I have issues loading them and I got a bunch of issues with **cors policy no 'access-control-allow-origin'**  
I will update any progress or issues I encounter.

---

<div class="post-metadata">

**Author:** ![bmusson](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/bmusson/32/87530_2.png) [@bmusson](https://forum.inductiveautomation.com/u/bmusson)\
**Post date:** [June 19, 2025, 2:42am UTC](https://forum.inductiveautomation.com/t/three-javascript-injection/106832/11 "2025-06-19T02:42:06Z")

</div>

> [@luis](#):
>
> I also used a CDN. I wanted to use a local repository but I have issues loading them and I got a bunch of issues with **cors policy no 'access-control-allow-origin'**

Yeah, before I will support merging this component into mainline Embr there will have to be a way to include local web dependencies.
