Created by: gaearon
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:
-
[ROOT-NAMES]
Some files (e.g.favicon.ico
ormanifest.json
) must exist at top level. Default approach of putting them intostatic/
and adding a content hash doesn’t work for them and we have to add exceptions. (https://github.com/facebookincubator/create-react-app/issues/558, https://github.com/facebookincubator/create-react-app/issues/697) -
[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 theircontent
, but we can’t be sure which ones. (https://github.com/facebookincubator/create-react-app/issues/618) -
[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. (https://github.com/facebookincubator/create-react-app/issues/585#issuecomment-244809341) -
[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. (https://github.com/facebookincubator/create-react-app/issues/625, https://github.com/facebookincubator/create-react-app/issues/573#issuecomment-245934473)
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 customhomepage
inpackage.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 https://github.com/facebookincubator/create-react-app/pull/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
ormanifest.json
) must exist at top level. Default approach of putting them intostatic/
and adding a content hash doesn’t work for them and we have to add exceptions. (https://github.com/facebookincubator/create-react-app/issues/558, https://github.com/facebookincubator/create-react-app/issues/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 theircontent
, but we can’t be sure which ones. (https://github.com/facebookincubator/create-react-app/issues/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. (https://github.com/facebookincubator/create-react-app/issues/585#issuecomment-244809341)
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. (https://github.com/facebookincubator/create-react-app/issues/625, https://github.com/facebookincubator/create-react-app/issues/573#issuecomment-245934473)
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 customhomepage
inpackage.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: import
ing 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.
Activity
Created by: gaearon
One thing I'm still not sure about is whether
index.html
should live inpublic
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 fromsrc
there but I'm not sure if it's that important.Created by: vjeux
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.
Created by: donpark
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, assumepublic
folder mode.
Upsides are it's backward compatible and makes more reasonable sense than having
index.html
at root including resources inpublic
folder without intervening/public
path segment.Downside is additional complexity which I'm not in the position to properly measure.
Created by: clessg
One thing I'm still not sure about is whether
index.html
should live inpublic
or stay at the top level.Putting
index.html
inpublic/
seems more intuitive to me. Ifindex.html
is kept in root, what would happen if somebody accidentally creates a secondaryindex.html
inpublic/
? 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]
.Created by: ForbesLindesay
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 thestatic
folder, which would improve performance by default.Created by: gaearon
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.
Created by: gaearon
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.