Using needle.min.js from CDN along with external scripts

Pro License user here.
I am using needle.min.js pretty much grabbed from the CDN included in the project. I’d like to use it along with external typescript scripts.
Im my scenario I have bunch of glb files I’d like present. I’d unrealistic to setup a project for each one and have them built and deployed separately.

So I’d like to use needle.min.js along with external scripts.

I’ve tried to use the following code but it doesn’t work. I have working scripts folder and I just would like to inject them into context after loading glb file. So my code atm looks like this:


<script lang="ts">
  import "./needle.min"; /// needle.min.js from CDN
   // now I know that `Needle` namespace is available in window object so I can inject scripts
 //  but i'm not sure how to do it atm 

console.log(window.Needle); 

  const src: string =
    "https://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Models/master/2.0/DamagedHelmet/glTF-Embedded/DamagedHelmet.gltf";


   
</script>
<!-- needle webcomponent -->
<needle-engine {src}></needle-engine>


Idea is to use <needle-engine> webcomponent and inject external scripts into context on top of it, prefferably with Typescript as well.
We do plan to have scripts distributed among rest of the team in private npm package but for now I’d like to keep it simple and test waters if it’s possible to make it work

Hi Martin! To integrate needle.min.js from the CDN with your external TypeScript scripts, you’ll need to follow these steps to ensure they work together seamlessly.

  1. Load External Scripts: You can dynamically load external scripts after the <needle-engine> component is mounted. You’d generally use the load event on the window object for this.

  2. Inject the Scripts: Create a function to inject your external scripts, which can be executed once the <needle-engine> finishes loading the GLB file.

Here’s how you can modify your code:

<script lang="ts">
  import "./needle.min"; // Import needle.min.js from CDN

  const src: string =
    "https://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Models/master/2.0/DamagedHelmet/glTF-Embedded/DamagedHelmet.gltf";

  // Function to load external scripts
  function loadScript(src: string) {
    return new Promise((resolve, reject) => {
      const script = document.createElement("script");
      script.src = src;
      script.onload = () => resolve(true);
      script.onerror = () => reject(new Error(`Script load error for ${src}`));
      document.body.appendChild(script);
    });
  }

  // Load external scripts after needle-engine is ready
  async function onNeedleEngineReady() {
    try {
      // Replace with your actual script URLs
      await loadScript("path/to/your/external-script1.js");
      await loadScript("path/to/your/external-script2.js");
      console.log("External scripts loaded successfully");
      
      // Now you can access your external scripts here
    } catch (error) {
      console.error(error);
    }
  }

  // Automatically call this function when the component mounts
  onMount(() => {
    // You might need to listen to an event or a state change indicating the engine is ready
    // For example, you might consider a custom event or Observable pattern for this
    const needleEngine = document.querySelector("needle-engine");
    if (needleEngine) {
      needleEngine.addEventListener('loadComplete', onNeedleEngineReady);
    }
  });
</script>

<!-- needle webcomponent -->
<needle-engine {src}></needle-engine>

Key Points:

  • Dynamic Script Loading: The loadScript function loads external scripts by creating a script element in the DOM.
  • Event Listener: Listen for a custom event like loadComplete (if available) or define your mechanism to identify when the GLB file is loaded. You may need to implement a similar event dispatcher in the NeedleEngine component if it doesn’t have one.
  • TypeScript: Ensure that the external scripts are compatible with TypeScript, or include TypeScript definitions where necessary.

Next Steps:

  • Adapt the onNeedleEngineReady function to correspond with how your <needle-engine> component signals readiness.
  • Test the integration with your existing scripts.

Feel free to ask if you have more specific scenarios or issues!

1 replies left - Get a PRO license for more AI replies.
thread_RX6MHUocEppHrwnMgoTdoSA0
Reply to this message to continue the conversation

Hello,

Could you provide more details about what part is not working?
Are you using any frontend framework or bundler in your setup?
Did you check the browser console to make sure all files are loaded as expected?

We have a minimal example for this on stackblitz here: Needle Engine: Vanilla HTML - StackBlitz
Maybe this already helps in figuring out what’s not working in your project

alright, sure

I’m working with @vydymil on the project, where he’s processing and correcting models.

We did some test where I tried to include engine from npm as package but this turned out to be cumbersome for many reasons, one of them is that we already have legacy renderer that depends on version of three package, another one is that this way expect us to heavily modify vite config to make it work

So we tried bundle whole needle engine as svelte library for our main project to consume as a dependency via private npm registry. This did not work out, but I had idea when skimming through docs that we can just import minfied build from cdn and it should work, in similar way as three or video.js are imported from cdn.

This worked out for loading glb model from url, which is 50% of the work, now all that’s left is to figure out how to inject custom scripts into engine and keep typescript happy.

Thanks for the additional info.

Which three.js version do you depend on?

We don’t expect you to heavily modify your vite config, do you have an example of what you mean? All that should be necessary is to register the Needle Vite plugin to the plugins array.

const { needlePlugins } = await import("@needle-tools/engine/plugins/vite/index.js");

...
plugins: [
   ...
   needlePlugins(command),
]

I’d also be interested in more info about the svelte library blockers if you can provide more details. If you don’t want to share it publicly feel free to DM or send an email to hi@needle.tools.

Regarding the current approach:

You could simply use one of the lifecycle hooks to access the Needle Engine context/scene to add or remove components to objects in the scene. See Creating and using Components | Needle Engine Documentation
Custom components are written as usual with Needle Engine.

Do your glTF files already contain custom scripts (e.g. through being exported by Unity?).

I understand your point. In our case, we would like to add scripts not during build step, but rather at runtime where we can add some scripts to engine context.

here are my attempts:


<script lang="ts">
  import { onMount } from "svelte";
  import "./needle.min"; // Import needle.min.js from CDN

  const src: string =
    "https://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Models/master/2.0/DamagedHelmet/glTF-Embedded/DamagedHelmet.gltf";

  // Load external scripts after needle-engine is ready
  async function onNeedleEngineReady() {
    try {
      console.log("External scripts loaded successfully");

      // Now you can access your external scripts here
    } catch (error) {
      console.error(error);
    }
  }

  // Automatically call this function when the component mounts
  onMount(() => {
    // You might need to listen to an event or a state change indicating the engine is ready
    // For example, you might consider a custom event or Observable pattern for this
    const needleEngine = document.querySelector("needle-engine");
    if (needleEngine) {
      needleEngine.addEventListener("loadComplete", onNeedleEngineReady);
    }
  });
</script>

<!-- needle webcomponent -->
<needle-engine loadfinished={onNeedleEngineReady} {src}></needle-engine>

and setting it up like this

<needle-engine loadfinished={onNeedleEngineReady} {src}></needle-engine> 

is not working either

Okay, we have made some progress meanwhile, I’ve managed to setup vite config to stop complaining about missing packages and tried to disable most of the dev tools from config according to the source like so:


 needlePlugins(command, needleConfig, {
        noDependencyWatcher: true,
        noAsap: true,
        noReload: true,
        allowHotReload: false,
        noBuildPipeline: true,
        dontInstallEditor: true,
        buildPipeline: {
          enabled: false
        },
        noPoster: true
      })

that allows me to access context in page.svelte like so:

<script lang="ts">
  import { Context, ContextRegistry } from "@needle-tools/engine";
  import { onMount } from "svelte";
  import "./needle.min"; // Import needle.min.js from CDN

  let context: Context;
  const model = "https://192.168.88.149:4000/Needle2.glb"; /// serve from server
  // Automatically call this function when the component mounts
  onMount(() => {
    ContextRegistry.addContextCreatedCallback((_context) => {
      context = _context.context as Context;
      console.log("Needle Engine context ready with context:", context);
    });
  });
</script>

<needle-engine src={model}></needle-engine>

But I don’t know how to register our model handler, which is defined in our ModelSettings.ts

import { Behaviour, serializable } from '@needle-tools/engine';
import { Mesh, Color, Object3D, Vector3, Group, Euler } from 'three';
import { type EffectsOptions } from './Effects';
import { Model } from './Model';
import { Interactivity } from './Interactivity';

export class ModelSettings extends Behaviour {

///overrides some update() functions and so ...
}

I know that in the ‘basic’ settings, needle is bundling every typescript file from ./src/scripts as one giant .js that is just included in in HTML file, but I just cannot figure out how to add these typescript custom files to runtime.

Try to load needle engine async during onMount since it can not run server-side. You can checkout our sveltekit template as a refernece (assuming you use sveltekit - maybe it’s just svelte).

The code you posted here seems to just wait for the component to finish loading. To add components at runtime (which is perfectly valid too) you need to get hold of the needle-engine context. Either from the web component directly (Creating and using Components | Needle Engine Documentation) or by using one of the lifecycle hooks: Creating and using Components | Needle Engine Documentation (which i think is easier to use)

Just in case it’s useful here are more examples on stackblitz that all add stuff at runtime Needle Engine by marwie - StackBlitz

Here’s a simple example:

onStart(context => {
  const instance = context.scene.addComponent(ModelSettings, { <init props> });
  // alternative: 
  // const instance = new ModelSettings();
  // instance.myProp = 42;
  // context.scene.addComponent(instance);
});

Is your project setup to use typescript? If you’re using sveltekit what I would do is to create a client.ts file in your sveltekit project (e.g. inside the lib/ folder) and then import this file in onMount(() => import("lib/client.ts")

Inside the client.ts file put your onStart(...) code and also import any component types you created (or just define the components in the client.ts file, up to you how to structure your project, the important bit is just that the file is imported in onMount to not be included during any serverside rendering)

Alright, thanks for quick replies . I’ll give it a go tomorrow

No problem, let me know how it goes.

Btw we also added some more AI support to https://cloud.needle.tools recently which might be also helpful. It’s available with your Needle Account.

Alright, I’ve read through what you posted and I came to conclusion that we still are talking about two different things, so let me try to clarify that even more

As far as I understand there are two ways to use Needle Engine in your project: install NPM package or use CDN. As we are not making Needle application with SvelteKit, but we want to use Needle Engine in our project, we tried to use the CDN approach, which worked for loading our models but didn’t work for loading additional scripts.
So we decided to install Needle NPM package and use it in our SvelteKit project for getting typescript definitions and to be able to use Needle Engine in our project. Unfortunately, when importing Contenxt and ContextRegistry from Needle NPM package, we get errors like this in dev mode:

✘ [ERROR] Could not read from file: /Users/martinblasko/Code/LMS-FE/node_modules/three-mesh-bvh/src/workers/generateMeshBVH.worker.js?worker&inline

    ../../node_modules/@needle-tools/engine/lib/engine/physics/workers/mesh-bvh/GenerateMeshBVHWorker.js:5:34:
      5 │ import generateMeshBVHWorker from "three-mesh-bvh/src/workers/generateMeshBVH.worker.js?worker&inline";
        ╵                                   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

so I took a wild guess and reworked my vite.config.ts to include needle config. Now this works in dev mode but it doesn’t work in production mode, because it will get stuck on building. From what I understand from terminal, it’s because Needle NPM package is intended to be used to develop ‘Needle’ applications. I fully understand the reasoning behind a fully automated build process with bundling scripts from the ./src/scripts folder and bundling all assets together for better DX and ease of development. But this is not what we are after.

So, back to the CDN approach. When we dynamically import needle-engine component for ‘needle.min.js’ (not from cdn), it works both in dev mode, production mode and can load our models. If we try to add scripts as props, it does not work because from what I read in docs, it assumes that all scripts are loaded via <script> tag in HTML file so in that case, they should probably be in global scope, right? Unfortunately, I cannot load those scripts even if I’d do it like in the example you provided, because they are:

  1. not in global scope
  2. are .ts files so browser cannot interpret them as they are
  3. I cannot translate them to .js files because when I do try to transpile with TypeScript just our files, it will error out with issues with three-mesh-bvh packages as mentioned above.

So, at this moment, I’m not sure what to do with this.

Do you think you could give me access to your project or send me a project which mirrors what you have right now so we can look into it and better understand the issue you’re facing?

That unfortunately is not possible, because it’s huge monorepo hosted on in private azure repo.

What we can do on the other hand is to provide a simple repro repo. I’ll try to put it together and send you DM, is that okay?

Yes that sounds good

Here’s a little example with a vanilla js script: Needle Engine: Vanilla HTML with minimal script - StackBlitz

Hello @MartinBspheroid just wanted to followup if you had the time to checkout the stackblitz example or put together a repro?

Hi there Marcel. I’m currently rushing the implementation of the version that works just work. Basically, we are just building Needle app and adding index.js to the <head> with <svelte:head> directive. It’s crude, but it works for now.

I’ll definitely have to come back to this once the dust settles, your proposed solution is more in line with what I had in mind.

I’ll try to work on this next week, but as I said I’m swamped at work atm. Thanks for your time, I really appreciate your insights and help.

Ok good luck with your projects and thanks for the quick reply for now!