HumanSignal / label-studio-frontend

Data labeling react app that is backend agnostic and can be embedded into your applications — distributed as an NPM package
https://labelstud.io/
Apache License 2.0
422 stars 319 forks source link

Error while importing package in Next.js #502

Open Rugz007 opened 2 years ago

Rugz007 commented 2 years ago

Hey I tried using this npm package in my next.js project and am getting error:

Automatic publicPath is not supported in this browser

This is my code:

import React from "react";

import LabelStudio from '@heartexlabs/label-studio'

export default function App() {
  return (
    <div className="App">
      <LabelStudioWrapper />
    </div>
  );
}

const LabelStudioWrapper = (props) => {
  // we need a reference to a DOM node here so LSF knows where to render
  const rootRef = React.useRef();
  // this reference will be populated when LSF initialized and can be used somewhere else
  const lsfRef = React.useRef();
  // we're running an effect on component mount and rendering LSF inside rootRef node
  React.useEffect(() => {
    if (rootRef.current) {
      lsfRef.current = new LabelStudio(rootRef.current, {
        /* all the options according to the docs */
        config: `
        <View style="padding: 25px;
               box-shadow: 2px 2px 8px #AAA">
    <Header value="Label the image with polygons"/>
    <Image name="img" value="https://app.heartex.ai/static/samples/sample.jpg"/>
    <Text name="text1"
          value="Select label, start to click on image"/>

    <PolygonLabels name="tag" toName="img">
      <Label value="Airbus" background="blue"/>
      <Label value="Boeing" background="red"/>
    </PolygonLabels>
  </View>
  `,
      });
    }
  }, []);
  // just a wrapper node to place LSF into
  return <div className="label-studio-root" ref={rootRef}></div>;
}
nicholasrq commented 2 years ago

hi, @Rugz007

do you receive an error on a build phase or in the runtume? we actually never faced it before, so it needs further investigation

giorgosera commented 2 years ago

Hey there, I am actually facing the same issue while trying to run tests with React testing library.

Automatic publicPath is not supported in this browser

It looks like an issue with Webpack but I do not have more info than this!

AleshaOleg commented 2 years ago

Is there any news? Facing same issue right now, while running tests with Jest

carloseam94 commented 1 year ago

Facing the same issue on Nuxt2 and Nuxt3.

nicholasrq commented 1 year ago

we've been receiving similar reports about Next.js lately. unfortunately, this integration currently appears to be broken without a known workaround

we're currently experiencing two different issues:

the team is currently exploring possible solutions to these issues. we'll keep you posted

sorry for the inconvenience

eensander commented 1 year ago

Running into the same problem here (with Nuxt 3). From what I've read I think it's worth considering setting webpack's output publicPath to an empty string or a string consisting of a space. I don't know if there is a reason for not setting this value. (similar issues: https://github.com/cypress-io/cypress/issues/18435, https://stackoverflow.com/a/64715069)

As such, what I attempted: webpack.config-builder.js:

  // ...
  const result = {
    filename: "[name]-[contenthash].js",
    chunkFilename: "[name]-[contenthash]-[id].chunk.js",
    publicPath: '' // added
  };
  // ...

However, I am running into problems when attempting to building the bundle and using it in my project. It would be nice if someone could validate this.

epeters3 commented 1 year ago

@nicholasrq regarding this issue:

official LS + React integration instruction results in an empty screen

I was able to modify the official instruction to get it to work. Here is the working code:

import LabelStudio from '@heartexlabs/label-studio';
import '@heartexlabs/label-studio/build/static/css/main.css';
import React, { useEffect, useRef } from 'react';

const lsContainerId = 'label-studio';

/**
 * Turns a `LabelStudio` front-end instance into a React component. Sources:
 *  - https://react.dev/reference/react/useEffect#controlling-a-non-react-widget
 *  - https://labelstud.io/guide/frontend.html#React-integration
 */
export const ReactLabelStudio = (options) => {
  const containerRef = useRef<HTMLDivElement | null>(null);
  const lsRef = useRef<LabelStudio>();

  useEffect(() => {
    if (!lsRef.current) {
      lsRef.current = new LabelStudio(lsContainerId, options);
    }
  }, [options]);

  return <div id={lsContainerId} ref={containerRef} />;
};

However, regarding this issue:

inability to import LS into existing Next.js applications (probably due to webpack/package.json setup)

I am also experiencing this error, but when trying to test the above component using react-testing-library (RTL). RTL uses jsdom, and jsdom doesn't have a document.currentScript property, which Webpack uses to power the "Automatic publicPath" feature.

eensander commented 1 year ago

Using the inspector, it seems that the error is thrown from the following:

var scriptUrl;
if (__webpack_require__.g.importScripts) scriptUrl = __webpack_require__.g.location + "";
var document = __webpack_require__.g.document;
if (!scriptUrl && document) {
    if (document.currentScript)
        scriptUrl = document.currentScript.src
    if (!scriptUrl) {
        var scripts = document.getElementsByTagName("script");
        if(scripts.length) scriptUrl = scripts[scripts.length - 1].src
    }
}
// When supporting browsers where an automatic publicPath is not supported you must specify an output.publicPath manually via configuration
// or pass an empty string ("") and set the __webpack_public_path__ variable from your code to use your own logic.
if (!scriptUrl) throw new Error("Automatic publicPath is not supported in this browser");

As a very hacky workaround, without altering the browsers document.currentScript, I create a new script tag with a non-empty/non-falsy src, which will (hopefully) be the last result of getElementsByTagName("script"):

const script = document.createElement('script');
script.src = "data:text/javascript,void(0);";
document.body.appendChild(script);

For me this seems to work. However, hopefully it will be fixed in the future with a more definite solution.

richardcoder commented 8 months ago

Using the inspector, it seems that the error is thrown from the following:

var scriptUrl;
if (__webpack_require__.g.importScripts) scriptUrl = __webpack_require__.g.location + "";
var document = __webpack_require__.g.document;
if (!scriptUrl && document) {
  if (document.currentScript)
      scriptUrl = document.currentScript.src
  if (!scriptUrl) {
      var scripts = document.getElementsByTagName("script");
      if(scripts.length) scriptUrl = scripts[scripts.length - 1].src
  }
}
// When supporting browsers where an automatic publicPath is not supported you must specify an output.publicPath manually via configuration
// or pass an empty string ("") and set the __webpack_public_path__ variable from your code to use your own logic.
if (!scriptUrl) throw new Error("Automatic publicPath is not supported in this browser");

As a very hacky workaround, without altering the browsers document.currentScript, I create a new script tag with a non-empty/non-falsy src, which will (hopefully) be the last result of getElementsByTagName("script"):

const script = document.createElement('script');
script.src = "data:text/javascript,void(0);";
document.body.appendChild(script);

For me this seems to work. However, hopefully it will be fixed in the future with a more definite solution.

@eensander Sorry to bother, but may I know where do you add the three lines of codes in? Because everytime I refresh the page, it will trigger the "Automatic publicPath is not supported in this browser" error, and the document is still undefined. Thanks in advance for your help! image image

richardcoder commented 8 months ago

Running into the same problem here (with Nuxt 3). From what I've read I think it's worth considering setting webpack's output publicPath to an empty string or a string consisting of a space. I don't know if there is a reason for not setting this value. (similar issues: cypress-io/cypress#18435, https://stackoverflow.com/a/64715069)

As such, what I attempted: webpack.config-builder.js:

  // ...
  const result = {
    filename: "[name]-[contenthash].js",
    chunkFilename: "[name]-[contenthash]-[id].chunk.js",
    publicPath: '' // added
  };
  // ...

However, I am running into problems when attempting to building the bundle and using it in my project. It would be nice if someone could validate this. @eensander I also tried this solution, and build a new bundle. But it seems not to work. Thanks. image

Aiden1024 commented 8 months ago

Here is the solution I tried, and I think it works. It is not about the script or automatic public path, it is about Server Side Render not having access to document and window. Therefore, you have to use client-side render. To achieve this, it is a bit tricky, as Label Studio is a class constructor/function, not a React Component. Here is how I have done it on Next js.

First you must have 2 file.

  1. index.js
  2. LFS.js

In LFS.js, you import "Label Studio", just like what you would do in React

import LabelStudio from '@heartexlabs/label-studio'; import 'label-studio/build/static/css/main.css';

// Initialize Label Studio as usual...

In index.js

import dynamic from 'next/dynamic'

const AnnotateMain = dynamic(() => import('./LFS.js'), {
  ssr: false,
})

const index = () => {
  return (
    <div className=' max-w-screen-xl flex flex-col h-full  mx-auto  '>
        <AnnotateMain/>
    </div>
  )
}

export default index

In this way, you import the LFS.js in client side render, therefore the Label Studio can behave like in react. Hope this help *wink @richardcoder