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

Add support for public/ folder #703

Merged
merged 1 commit into from Sep 22, 2016
Merged

Add support for public/ folder #703

merged 1 commit into from Sep 22, 2016

Conversation

gaearon
Copy link
Contributor

@gaearon gaearon commented Sep 22, 2016

I’ve been thinking about this for a few weeks and I have a solution for static assets that satisfies me.
It is not exactly what we discussed before so please bear with me and give it five minutes.

Problem

Currently, the only way to use an image or another resource is to import it from JavaScript.
This works really well for most use cases because Webpack takes care of:

  • Raising a compilation error if the file is missing.
  • Adding a content hash to the filename so if file changes, browser cache is busted.

However there are a few problems with this approach.
Let me group them into a few buckets:

This means we need an alternative way of including some assets into the build.

Constraints

  • [PROD-WORKS] If it works in development, it must work in production too.
  • [HOMEPAGE] User may specify a custom homepage in package.json, and we should respect that.
  • [ROUTING] User may use client-side routing, and app may be served from an arbitrary subpath.
  • [SECRETS] It should be obvious to the user which files or folders will end up in the build output.
  • [PIT-OF-SUCCESS] There should be one relatively obvious way to do it.

Prior Solutions

Implicitly Serve Everything in Development

This is how it worked prior to 0.4.0.
It wasn’t intentional, but WebpackDevServer defaults to this behavior.

This means <img src="/src/logo.svg"> or <link rel="shortcut icon" href="/favicon.ico"> happened to work in development. However once you compile them with npm run build, the links would be broken because module system knew nothing about those files.

Verdict: solves all problems but violates [PROD-WORKS].

Auto-Detect Assets in HTML

This is the current solution that was added as a stop-gap measure in 0.4.0. Any <link href> attribute is being parsed, and relative URLs like ./favicon.ico get processed through Webpack.

Verdict: does not violate the constraints but also does not solve [ROOT-NAMES] unless we keep an extensive whitelist. JSON files make it nearly impossible to keep a whitelist like this because they should be treated differently depending on whether they’re imported from HTML or JS (and Webpack doesn’t support this). Similarly, [ANY-ATTRIBUTES], [DYNAMIC-FILES], and [SKIP-BUNDLER] are unsolved.

Introduce a Static Folder and Serve It

This is the approach described in #226. If an additional /static folder exists, it is served under /static and is merged with /build/static on build.

Verdict: it does not solve [ROOT-NAMES] unless we merge static/* directly into build/* output. However, in that case, for something to be served from /static/, you’d have to create static/static which is confusing and violates [PIT-OF-SUCCESS]. It also violates [PIT-OF-SUCCESS] because to refer to something, you’d have to use /static/* paths in your HTML, but to many people, this would imply that /src/* or /img/* would work the same way. Most importantly, it violates [HOMEPAGE] because there is no way for us to fix /static/ path to have the right absolute prefix without parsing HTML and applying some brittle heuristics. It would also be confusing that /static/ paths get “auto-fixed” in HTML while other paths are kept intact. Finally, if we proposed that user types relative static/* in HTML instead, it would violate [ROUTING].

Proposed Solution

There is a new top-level folder called public. We move index.html and favicon.ico there.

We introduce a new concept called “public URL”. It roughly corresponds to “public path” in Webpack.
It is already configurable via homepage field in package.json, but now we also expose it to the user.

In HTML:

<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">

In JavaScript:

render() {
  // Escape hatch! Normally you’d just import them.
  const src = `${process.env.PUBLIC_URL}/img/team_/${this.props.teamId}.png`;
  return <img src={src} alt='Team' />;
}

Only files inside public folder are accessible by public URL. You can’t refer to things from node_modules or src. The public folder contents gets merged with the build output.

Let’s see how this proposal addresses each of the problems and constraints.

Addressed Problems

[ROOT-NAMES] Some files (e.g. favicon.ico or manifest.json) must exist at top level. Default approach of putting them into static/ and adding a content hash doesn’t work for them and we have to add exceptions. (#558, #697)

With public folder, you have full control over how to call your files.

[ANY-ATTRIBUTES] HTML needs to be able to refer to asset paths inside any attributes. For example, some <meta> tags may want to refer to an image URL in their content, but we can’t be sure which ones. (#618)

With %PUBLIC_URL%, you have full control over attributes.

[DYNAMIC-FILES] Sometimes you have hundreds of image files that rarely change, and you want to do something dynamic instead of making hundreds of imports. (#585 (comment))

We expose process.env.PUBLIC_URL so you can do that now.

[SKIP-BUNDLER] Sometimes you really want to load some JS or CSS independently of the bundler, for example, to show a spinner, or to use a script that somehow breaks Webpack. (#625, #573 (comment))

Again, since we don’t process files in public folder with webpack, this will work.

Addressed Constraints

[PROD-WORKS] If it works in development, it must work in production too.

The public folder gets merged with the build output, so references both from HTML and JS will keep working in production.

[HOMEPAGE] User may specify a custom homepage in package.json, and we should respect that.

Both %PUBLIC_URL% and process.env.PUBLIC_URL take homepage into account, as they directly correspond to publicPath used by Webpack.

[ROUTING] User may use client-side routing, and app may be served from an arbitrary subpath.

Both %PUBLIC_URL% and process.env.PUBLIC_URL provide safety against that, as they are absolute and know about homepage.

[SECRETS] It should be obvious to the user which files or folders will end up in the build output.

The mental model is simple: only public files are merged with build output.

[PIT-OF-SUCCESS] There should be one relatively obvious way to do it.

You can’t accidentally refer to a file in src or node_modules because it will not work. We show two preferred methods out of the box: importing images in App.js, and using %PUBLIC_URL% in index.html. Using process.env.PUBLIC_URL is an escape hatch so we will only use mention in documentation for people who need it.


Overall I feel like it’s a solid, explicit solution, and I intend to release it as part of 0.5.0.

@gaearon
Copy link
Contributor Author

gaearon commented Sep 22, 2016

One thing I'm still not sure about is whether index.html should live in public or stay at the top level. There is no big difference. I think that putting it into the public folder helps communicate that you can't load things from src there but I'm not sure if it's that important.

@vjeux
Copy link
Contributor

vjeux commented Sep 22, 2016

One thing I'm still not sure about is whether index.html should live in public or stay at the top level.

I would rather have App.js or index.js at the top level because this is the one we want people to change. The fact that index.html is the first time to see when you have a new project is weird imo.

@gaearon gaearon merged commit bc6392a into master Sep 22, 2016
@gaearon
Copy link
Contributor Author

gaearon commented Sep 22, 2016

We'll look at file structure again after adding proper support for absolute imports. For now merging to avoid conflicts in other PRs as this is strictly better than what we have now.

@donpark
Copy link

donpark commented Sep 22, 2016

Looks reasonable to me given the complex weave of constraints and needs.

One thing I'm still not sure about is whether index.html should live in public or stay at the top level.

I recommend 'both', meaning:

  • if it's at project root, assume import everything via JS mode.
  • if it's in public folder, assume public folder mode.

Upsides are it's backward compatible and makes more reasonable sense than having index.html at root including resources in public folder without intervening /public path segment.

Downside is additional complexity which I'm not in the position to properly measure.

@clessg
Copy link

clessg commented Sep 23, 2016

One thing I'm still not sure about is whether index.html should live in public or stay at the top level.

Putting index.html in public/ seems more intuitive to me. If index.html is kept in root, what would happen if somebody accidentally creates a secondary index.html in public/? In production, it would override the post-build version unless this filter is kept, and who knows what would happen in development.

So, index.html at root could violate [PIT-OF-SUCCESS] and potentially [PROD-WORKS].

@gaearon
Copy link
Contributor Author

gaearon commented Sep 23, 2016

Good catch regarding second index.html. Yea, I like it being in public better as well.

@ForbesLindesay
Copy link
Contributor

One concern is that you have both [SKIP-BUNDLER] and [ROOT-NAMES] but no way to have [ROOT-NAMES] + [BUNDLER]. If I add a service worker, I will want that to be served at the root of the project, but I will also want it processed by webpack as a JavaScript file so I can import modules into it.

One possible solution to this would be to add a special src/serviceworker.js file that is automatically loaded in the correct place. We could even add a simple default serviceworker that permanently cached everything in the static folder, which would improve performance by default.

@gaearon
Copy link
Contributor Author

gaearon commented Sep 23, 2016

Yep, supporting bundling for something like service worker is a separate feature that I'm happy to discuss in another issue. Indeed I think a separate conventional entry point be the best way to go. Also if I'm not mistaken Webpack provides a separate target for workers.

gaearon added a commit that referenced this pull request Sep 23, 2016
@gaearon gaearon added this to the 0.5.0 milestone Sep 23, 2016
@gaearon
Copy link
Contributor Author

gaearon commented Sep 23, 2016

This is now supported in 0.5.0.

Read about using the new public folder.

See also migration instructions and breaking changes in 0.5.0.

feiqitian pushed a commit to feiqitian/create-react-app that referenced this pull request Oct 25, 2016
feiqitian pushed a commit to feiqitian/create-react-app that referenced this pull request Oct 25, 2016
@lock lock bot locked and limited conversation to collaborators Jan 22, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants