Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Using the script directly breaks assets #3708

Closed
MichaelDeBoey opened this issue Jan 8, 2018 · 32 comments
Closed

Using the script directly breaks assets #3708

MichaelDeBoey opened this issue Jan 8, 2018 · 32 comments

Comments

@MichaelDeBoey
Copy link
Contributor

MichaelDeBoey commented Jan 8, 2018

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

@MichaelDeBoey MichaelDeBoey changed the title Using the script directly breaks Using the script directly breaks assets Jan 8, 2018
@gaearon
Copy link
Contributor

gaearon commented Jan 8, 2018

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!

@gaearon gaearon closed this as completed Jan 8, 2018
@MichaelDeBoey
Copy link
Contributor Author

MichaelDeBoey commented Jan 8, 2018

@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?

@gaearon
Copy link
Contributor

gaearon commented Jan 8, 2018

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.

@MichaelDeBoey
Copy link
Contributor Author

MichaelDeBoey commented Jan 8, 2018

@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... 😕

@Timer
Copy link
Contributor

Timer commented Jan 8, 2018

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

@MichaelDeBoey
Copy link
Contributor Author

@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

@Timer
Copy link
Contributor

Timer commented Jan 8, 2018

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].

@MichaelDeBoey
Copy link
Contributor Author

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

@MichaelDeBoey
Copy link
Contributor Author

@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 😕

@Timer
Copy link
Contributor

Timer commented Jan 8, 2018

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

@MichaelDeBoey
Copy link
Contributor Author

Too bad 😕

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

@gaearon
Copy link
Contributor

gaearon commented Jan 8, 2018

No, it needs to be embedded in the bundle.

@MichaelDeBoey
Copy link
Contributor Author

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 😕

@gaearon
Copy link
Contributor

gaearon commented Jan 8, 2018

What’s your proposal to fix this?

@MichaelDeBoey
Copy link
Contributor Author

@gaearon Can't think of a good way on how to fix this one right now 😕
I understand why it is like it is, but it's also a bit grim that I have to build the app 2 times, just because of the changed domain...

If it would be a normal app (just visit it in the browser or via an iframe), there wouldn't be any problem and I could just do PUBLIC_URL=/plugin-name/version-number. But because I make the main-script available next to the index.html (by doing cp $(find $PWD/build/static/js -type f -name 'main.*.js') $PWD/build/main.js in the postbuild), it's a whole different story 😕

@gaearon
Copy link
Contributor

gaearon commented Jan 8, 2018

To be honest the whole thing with copying the script sounds like you're stretching the tool to do something it doesn't support. Maybe it's better to use something more flexible (e.g. nwb or Neutrino)?

@MichaelDeBoey
Copy link
Contributor Author

@gaearon The copying of the script is to support both ways

  1. Via an iframe (or just like a normal app, that's the same)
  2. Use it like the index.html of the app uses the script

I don't think it's that weird if you think about it like that isn't it?

@gaearon
Copy link
Contributor

gaearon commented Jan 16, 2018

Maybe. I'm struggling to understand what happens, sorry.

Could you explain it from the beginning: what you need to do, why you have two servers, how you build it, what ends up 404'ing.

Thanks.

@MichaelDeBoey
Copy link
Contributor Author

@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 🙂

@viankakrisna
Copy link
Contributor

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

@MichaelDeBoey
Copy link
Contributor Author

MichaelDeBoey commented Jan 16, 2018

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?

@gaearon
Copy link
Contributor

gaearon commented Jan 17, 2018

@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)?

@MichaelDeBoey
Copy link
Contributor Author

@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.

@gaearon
Copy link
Contributor

gaearon commented Jan 17, 2018

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.

@MichaelDeBoey
Copy link
Contributor Author

MichaelDeBoey commented Jan 17, 2018

@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}/`;
}

@gaearon
Copy link
Contributor

gaearon commented Jan 17, 2018

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

@MichaelDeBoey
Copy link
Contributor Author

@gaearon I found it in their docs 🙂
https://webpack.js.org/guides/public-path/#on-the-fly

@MichaelDeBoey
Copy link
Contributor Author

MichaelDeBoey commented Jan 17, 2018

@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?

@gaearon
Copy link
Contributor

gaearon commented Jan 17, 2018

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

@MichaelDeBoey
Copy link
Contributor Author

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

@MichaelDeBoey
Copy link
Contributor Author

@gaearon Made a proposal in #3834 🙂

@jasonwr
Copy link

jasonwr commented Jun 4, 2018

@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

@lock lock bot locked and limited conversation to collaborators Jan 19, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants