Skip to content

Using the script directly breaks assets #3708

Closed

Description

@MichaelDeBoey

Hi there!

So I'm having the following situation:

I created an app using create-react-app (this repo), which has some assets.

The app is used to work as a plugin on many different sites/apps.
So the purpose is to provide it in 2 possible ways:

  1. Use inside an iframe and pass parameters via the URL
<iframe src="https://plugin.domain.com/plugin-name/version-number?param1=value1&param2=value2"></iframe>
  1. Use a div with the correct id and a separate <script> tag that points to the script (which is duplicated from static/js/main.*.js to main.js in the root) and pass params via the dataset
<div id="plugin-name" data-param1="value1" data-param2="value2"></div>

<script type="text/javascript" src="https://plugin.domain.com/plugin-name/version-number/main.js"></script>

When using the iframe, everything works like expected. But when using the div + script, I get the following error:

Failed to load resource: the server responded with a status of 404 (Not Found)

Any idea how I can fix this or what the preferred way of doing something like this is?

CC: @gaearon @Timer

Activity

  1. changed the title [-]Using the script directly breaks[/-] [+]Using the script directly breaks assets[/+] on Jan 8, 2018
  2. gaearon commented on Jan 8, 2018

    @gaearon
    Contributor

    I don’t see why this question would be related to CRA. Open the Network tab and check which request is getting a 404. Then you need to figure out why the server isn’t serving what you expect at the path you expect. Hope this helps!

  3. MichaelDeBoey commented on Jan 8, 2018

    @MichaelDeBoey
    ContributorAuthor

    @gaearon The error I get is because it's searching for /static/media/logo.2e151009.png on the parent server (that implements the div + script) like https://parent.com/static/media/logo.2e151009.png instead of at the server where the CRA is located (https://plugin.domain.com/plugin-name/version-number/static/media/logo.2e151009.png)

    But main.js is (like I said) just a copy of static/js/main.*.js.
    I think the problem is, it uses relative paths?

  4. gaearon commented on Jan 8, 2018

    @gaearon
    Contributor

    It uses an absolute path but absolute paths are calculated from the host that served the page.

    If you want to hardcode a specific URL to calculate them against, you need to set PUBLIC_URL environment variable during the build. You can see an example in #937.

  5. MichaelDeBoey commented on Jan 8, 2018

    @MichaelDeBoey
    ContributorAuthor

    @gaearon
    Yeah I already looked at PUBLIC_URL, but then I need to build the app 2 times?

    1. Build with PUBLIC_URL=https://plugin.staging-domain.com/plugin-name/version-number/ in staging
    2. Build with PUBLIC_URL=https://plugin.domain.com/plugin-name/version-number/ in production

    And that's what I'm trying to avoid... 😕

  6. Timer commented on Jan 8, 2018

    @Timer
    Contributor

    Do you require routing? If not, you can try to set the homepage to .; this will build it for relative paths.

  7. MichaelDeBoey commented on Jan 8, 2018

    @MichaelDeBoey
    ContributorAuthor

    @Timer Yeah we use react-router in the project unfortunately 😕

    I'm open to any given solution that could help us. 🙂

    The way we deploy right now is:

    • Build the app
    • Publish it to npm
    • npm i on our staging server and test it on the staging server environment (equal to production server)
    • fix bugs if any are present due to environment (which is almost never the problem, but you never know)
    • npm i on our production server
  8. Timer commented on Jan 8, 2018

    @Timer
    Contributor

    Your best solution is as described above, use PUBLIC_URL and build twice -- there't not really another way unless that plugin-name/version-number/ is always static [but if you sub it out with abc/1.0.0/ the point is moot].

  9. MichaelDeBoey commented on Jan 8, 2018

    @MichaelDeBoey
    ContributorAuthor

    @Timer
    Yeah plugin-name & version-number will be replaced with the actual name of the plugin/app & with the npm version number

  10. MichaelDeBoey commented on Jan 8, 2018

    @MichaelDeBoey
    ContributorAuthor

    @Timer so for both staging and production, you'll have the same plugin-name & version-number, so that's not the case, the only difference is staging-domain vs. domain.

    But the problem is that the script is loaded @ parent-domain, so if I just set PUBLIC_URL to /plugin-name/version-number, the script will still look @ parent-domain/plugin-name/version-number 😕

  11. Timer commented on Jan 8, 2018

    @Timer
    Contributor

    This is going to be your best solution: #3708 (comment)

  12. MichaelDeBoey commented on Jan 8, 2018

    @MichaelDeBoey
    ContributorAuthor

    Too bad 😕

    There's also not an option to set the PUBLIC_URL at runtime?

  13. gaearon commented on Jan 8, 2018

    @gaearon
    Contributor

    No, it needs to be embedded in the bundle.

  14. MichaelDeBoey commented on Jan 8, 2018

    @MichaelDeBoey
    ContributorAuthor

    Too bad 😕

    It's just stupid to build it 2 times, when the code is exactly the same, except for the PUBLIC_URL, because it's on another environment 😕

  15. 5 remaining items

  16. MichaelDeBoey commented on Jan 16, 2018

    @MichaelDeBoey
    ContributorAuthor

    @gaearon OK I'll try to explain it as clear as possible 🙂

    I'm building a react app (using CRA) that's going to be used as a plugin.
    The idea of the app is that the owner has a database of files and the plugin is used to show all these files (+ metadata).
    So instead of offering an API for getting al the files, metadata, ... the plugin will be embedded inside the client's site.

    This need to be possible in 2 different ways:

    1. Inside an iframe and pass parameters via the URL
    <iframe src="https://plugin.domain.com/plugin-name/version-number?param1=value1&param2=value2"></iframe>
    1. Using a div with the correct id and a separate <script> tag that points to the script (which is duplicated from static/js/main.*.js to main.js in the root) and pass params via the dataset
    <div id="plugin-name" data-param1="value1" data-param2="value2"></div>
    
    <script type="text/javascript" src="https://plugin.domain.com/plugin-name/version-number/main.js"></script>

    So in order to get this behavior, I use the default build script (react-scripts build) and added a postbuild script (cp $(find $PWD/build/static/js -type f -name 'main.*.js') $PWD/build/main.js").

    When releasing a new version, I use the following workflow to deploy the app:

    1. Build the app (npm run build)
    2. Publish it to the npm repository (as a private repo)
    3. npm i on the staging server and test it on the staging server environment (equal to production server)
    4. fix bugs if any are present due to environment (which is almost never the problem, but you never know)
    5. npm i on our production server

    When then using the div+ script version, I get an error when loading local assets, because it's searching for /static/media/logo.2e151009.png on the parent server (so https://parent.com/static/media/logo.2e151009.png) instead of at the server where the CRA is located (https://plugin.domain.com/plugin-name/version-number/static/media/logo.2e151009.png).

    Building the app 2 times with a different PUBLIC_URL (1 time for staging and 1 time for the production server, like you and @Timer suggested) will solve the 'problem'.
    But that implies that I would need to publish both versions to npm.
    That is a bit grim and cumbersome in my opinion, since both builds are exactly the same, except for the PUBLIC_URL.

    I hope you understand my use case a bit more.
    If you still have some questions, I'm happy to answer them offcourse 🙂

  17. viankakrisna commented on Jan 16, 2018

    @viankakrisna
    Contributor

    @MichaelDeBoey maybe setting PUBLIC_URL=__STUB__URL__ and running search & replace on the generated assets will solve your problem?

  18. MichaelDeBoey commented on Jan 16, 2018

    @MichaelDeBoey
    ContributorAuthor

    setting PUBLIC_URL=__STUB__URL__ and running search & replace on the generated assets

    @viankakrisna That could maybe be a solution indeed. 🙂


    I'm also looking into setting __webpack_public_path__ on the fly (like described in the docs), by having something like

    // src/index.js
    
    import './public-path';
    
    // ...
    // src/public-path.js
    
    __webpack_public_path__ = resolvePublicPath();
    
    function resolvePublicPath() {
      // check the file path of the current executing script and derive the root-folder
      // Ref: https://stackoverflow.com/questions/2255689/how-to-get-the-file-path-of-the-currently-executing-javascript-code
    }

    And dropping the PUBLIC_URL variable.
    @gaearon This solution could maybe be embedded inside CRA as an opt-in option?

  19. gaearon commented on Jan 17, 2018

    @gaearon
    Contributor

    @gaearon This solution could maybe be embedded inside CRA as an opt-in option?

    Tbh this sounds a bit too esoteric to me. I think it’s cool if this workaround is working for you but I don’t think we’ll be embracing something like this as a first-class feature. Usually when you need something like this you end up needing even more customizations, and at that point ejecting, forking or “rewiring” but be better anyway.

    When then using the div+ script version, I get an error when loading local assets, because it's searching for /static/media/logo.2e151009.png on the parent server (so https://parent.com/static/media/logo.2e151009.png) instead of at the server where the CRA is located (https://plugin.domain.com/plugin-name/version-number/static/media/logo.2e151009.png).

    I think I understand the problem you're describing with parent.com website. But how is this related to the staging/production difference you also mention? Do you mean you only use div + script on staging, but iframe in production? Or do you have two separate problems (one caused by iframe/script difference, and another by production/staging difference)?

  20. MichaelDeBoey commented on Jan 17, 2018

    @MichaelDeBoey
    ContributorAuthor

    @gaearon Well I sort of have 2 problems indeed, but they're related

    Problem 1 is the breaking assets when using the div + script.
    This can be solved by building the app with a correct PUBLIC_URL (like PUBLIC_URL=https://plugin.domain.com/plugin-name/version-number).

    But solving it right that causes a new problem, which is that I have to build the app 2 times, because the PUBLIC_URL needs to be set different for the staging/production server.

  21. gaearon commented on Jan 17, 2018

    @gaearon
    Contributor

    The only other solution I can see is #3708 (comment).

    It won't work for you if you use pushState routing because then if the user loads http://plugin.com/plugin-name/my/client-side/path then the assets will be requested relative to that. But I’m not sure you’re using client-side routing like this?

    Other than that I don’t really see other good options.

  22. MichaelDeBoey commented on Jan 17, 2018

    @MichaelDeBoey
    ContributorAuthor

    @gaearon I'm using client-side routing (react-router), so setting the homepage won't work either 😕

    I've already looked into setting __webpack_public_path__ to the file-path of the current running script (ref: StackOverflow), which would be possible by calling document.currentScript (ref: MDN) and having this answer as a fallback, which would be a possible solution to set it dynamically on the fly...

    I really think this could be the default implementation for the PUBLIC_URL param, since it will always be correct I think?

    Implementation would be something like

    // src/index.js
    
    import './public-path';
    
    // ...
    // src/public-path.js
    
    __webpack_public_path__ = resolvePublicPath();
    
    function resolvePublicPath() {
      const currentScript = document.currentScript || getCurrentScriptViaFallback();
      const publicPath = currentScript.split('/static/js')[0] // app root directory
        .split('main.')[0]; // add support for script in app root directory
      
      return `${publicPath}/`;
    }
  23. gaearon commented on Jan 17, 2018

    @gaearon
    Contributor

    Maybe. I'm afraid I'm out of suggestions because I don't know how __webpack_public_path__ works in webpack.

  24. MichaelDeBoey commented on Jan 17, 2018

    @MichaelDeBoey
    ContributorAuthor
  25. MichaelDeBoey commented on Jan 17, 2018

    @MichaelDeBoey
    ContributorAuthor

    @gaearon I don't know how PUBLIC_URL is implemented right now, but I think that my solution could be a possible default for it?
    So instead of using a relative path, use an absolute path (the app-root directory).

    I think this will probably (as a default) be the best solution instead of deriving it from homepage (in package.json) or by setting the PUBLIC_URL manually in most cases.
    We can still keep those 2 as a way to set it manually if we want?

  26. gaearon commented on Jan 17, 2018

    @gaearon
    Contributor

    I'm not sure. If you can write a proposal in another issue with how it should work instead we can take a look.

  27. MichaelDeBoey commented on Jan 17, 2018

    @MichaelDeBoey
    ContributorAuthor

    @gaearon OK I'll try to submit one asap 🙂

  28. MichaelDeBoey commented on Jan 17, 2018

    @MichaelDeBoey
    ContributorAuthor

    @gaearon Made a proposal in #3834 🙂

  29. JSONRice commented on Jun 4, 2018

    @JSONRice

    @MichaelDeBoey you might want to have a look at this: https://itnext.io/so-you-want-to-host-your-single-age-react-app-on-github-pages-a826ab01e48

    If you want to pre-deploy or deploy you could even have a bash script to take care of the PUBLIC_URL parameters.

    FYI @gaearon

  30. locked and limited conversation to collaborators on Jan 19, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions