Commit bc6392af authored by Dan Abramov's avatar Dan Abramov Committed by GitHub
Browse files

Add support for public/ folder (#703)

parent 5b85a362
No related merge requests found
Showing with 202 additions and 74 deletions
+202 -74
......@@ -38,7 +38,8 @@ var nodePaths = (process.env.NODE_PATH || '')
// config after eject: we're in ./config/
module.exports = {
appBuild: resolveApp('build'),
appHtml: resolveApp('index.html'),
appPublic: resolveApp('public'),
appHtml: resolveApp('public/index.html'),
appIndexJs: resolveApp('src/index.js'),
appPackageJson: resolveApp('package.json'),
appSrc: resolveApp('src'),
......@@ -56,7 +57,8 @@ function resolveOwn(relativePath) {
// config before eject: we're in ./node_modules/react-scripts/config/
module.exports = {
appBuild: resolveApp('build'),
appHtml: resolveApp('index.html'),
appPublic: resolveApp('public'),
appHtml: resolveApp('public/index.html'),
appIndexJs: resolveApp('src/index.js'),
appPackageJson: resolveApp('package.json'),
appSrc: resolveApp('src'),
......@@ -71,7 +73,8 @@ module.exports = {
// @remove-on-publish-begin
module.exports = {
appBuild: resolveOwn('../../../build'),
appHtml: resolveOwn('../template/index.html'),
appPublic: resolveOwn('../template/public'),
appHtml: resolveOwn('../template/public/index.html'),
appIndexJs: resolveOwn('../template/src/index.js'),
appPackageJson: resolveOwn('../package.json'),
appSrc: resolveOwn('../template/src'),
......
......@@ -14,9 +14,20 @@ var autoprefixer = require('autoprefixer');
var webpack = require('webpack');
var HtmlWebpackPlugin = require('html-webpack-plugin');
var CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin');
var InterpolateHtmlPlugin = require('../scripts/utils/InterpolateHtmlPlugin');
var WatchMissingNodeModulesPlugin = require('../scripts/utils/WatchMissingNodeModulesPlugin');
var getClientEnvironment = require('../scripts/utils/getClientEnvironment');
var paths = require('./paths');
var env = require('./env');
// Webpack uses `publicPath` to determine where the app is being served from.
// In development, we always serve from the root. This makes config easier.
var publicPath = '/';
// `publicUrl` is just like `publicPath`, but we will provide it to our app
// as %PUBLIC_URL% in `index.html` and `process.env.PUBLIC_URL` in JavaScript.
// Omit trailing shlash as %PUBLIC_PATH%/xyz looks better than %PUBLIC_PATH%xyz.
var publicUrl = '';
// Get enrivonment variables to inject into our app.
var env = getClientEnvironment(publicUrl);
// This is the development configuration.
// It is focused on developer experience and fast rebuilds.
......@@ -63,8 +74,8 @@ module.exports = {
// served by WebpackDevServer in development. This is the JS bundle
// containing code from all our entry points, and the Webpack runtime.
filename: 'static/js/bundle.js',
// In development, we always serve from the root. This makes config easier.
publicPath: '/'
// This is the URL that app is served from. We use "/" in development.
publicPath: publicPath
},
resolve: {
// This allows you to set a fallback for where Webpack should look for modules.
......@@ -129,21 +140,11 @@ module.exports = {
// In production, they would get copied to the `build` folder.
{
test: /\.(ico|jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2)(\?.*)?$/,
exclude: /\/favicon.ico$/,
loader: 'file',
query: {
name: 'static/media/[name].[hash:8].[ext]'
}
},
// A special case for favicon.ico to place it into build root directory.
{
test: /\/favicon.ico$/,
include: [paths.appSrc],
loader: 'file',
query: {
name: 'favicon.ico?[hash:8]'
}
},
// "url" loader works just like "file" loader but it also embeds
// assets smaller than specified size as data URLs to avoid requests.
{
......@@ -153,15 +154,6 @@ module.exports = {
limit: 10000,
name: 'static/media/[name].[hash:8].[ext]'
}
},
// "html" loader is used to process template page (index.html) to resolve
// resources linked with <link href="./relative/path"> HTML tags.
{
test: /\.html$/,
loader: 'html',
query: {
attrs: ['link:href'],
}
}
]
},
......@@ -186,13 +178,19 @@ module.exports = {
];
},
plugins: [
// Makes the public URL available as %PUBLIC_URL% in index.html, e.g.:
// <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
// In development, this will be an empty string.
new InterpolateHtmlPlugin({
PUBLIC_URL: publicUrl
}),
// Generates an `index.html` file with the <script> injected.
new HtmlWebpackPlugin({
inject: true,
template: paths.appHtml,
}),
// Makes some environment variables available to the JS code, for example:
// if (process.env.NODE_ENV === 'development') { ... }. See `env.js`.
// if (process.env.NODE_ENV === 'development') { ... }.
new webpack.DefinePlugin(env),
// This is necessary to emit hot updates (currently CSS only):
new webpack.HotModuleReplacementPlugin(),
......
......@@ -16,12 +16,18 @@ var HtmlWebpackPlugin = require('html-webpack-plugin');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var url = require('url');
var paths = require('./paths');
var env = require('./env');
var InterpolateHtmlPlugin = require('../scripts/utils/InterpolateHtmlPlugin');
var getClientEnvironment = require('../scripts/utils/getClientEnvironment');
// Assert this just to be safe.
// Development builds of React are slow and not intended for production.
if (env['process.env.NODE_ENV'] !== '"production"') {
throw new Error('Production builds must have NODE_ENV=production.');
function ensureSlash(path, needsSlash) {
var hasSlash = path.endsWith('/');
if (hasSlash && !needsSlash) {
return path.substr(path, path.length - 1);
} else if (!hasSlash && needsSlash) {
return path + '/';
} else {
return path;
}
}
// We use "homepage" field to infer "public path" at which the app is served.
......@@ -30,10 +36,21 @@ if (env['process.env.NODE_ENV'] !== '"production"') {
// We can't use a relative path in HTML because we don't want to load something
// like /todos/42/static/js/bundle.7289d.js. We have to know the root.
var homepagePath = require(paths.appPackageJson).homepage;
var publicPath = homepagePath ? url.parse(homepagePath).pathname : '/';
if (!publicPath.endsWith('/')) {
// If we don't do this, file assets will get incorrect paths.
publicPath += '/';
var homepagePathname = homepagePath ? url.parse(homepagePath).pathname : '/';
// Webpack uses `publicPath` to determine where the app is being served from.
// It requires a trailing slash, or the file assets will get an incorrect path.
var publicPath = ensureSlash(homepagePathname, true);
// `publicUrl` is just like `publicPath`, but we will provide it to our app
// as %PUBLIC_URL% in `index.html` and `process.env.PUBLIC_URL` in JavaScript.
// Omit trailing shlash as %PUBLIC_PATH%/xyz looks better than %PUBLIC_PATH%xyz.
var publicUrl = ensureSlash(homepagePathname, false);
// Get enrivonment variables to inject into our app.
var env = getClientEnvironment(publicUrl);
// Assert this just to be safe.
// Development builds of React are slow and not intended for production.
if (env['process.env.NODE_ENV'] !== '"production"') {
throw new Error('Production builds must have NODE_ENV=production.');
}
// This is the production configuration.
......@@ -139,21 +156,11 @@ module.exports = {
// When you `import` an asset, you get its filename.
{
test: /\.(ico|jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2)(\?.*)?$/,
exclude: /\/favicon.ico$/,
loader: 'file',
query: {
name: 'static/media/[name].[hash:8].[ext]'
}
},
// A special case for favicon.ico to place it into build root directory.
{
test: /\/favicon.ico$/,
include: [paths.appSrc],
loader: 'file',
query: {
name: 'favicon.ico?[hash:8]'
}
},
// "url" loader works just like "file" loader but it also embeds
// assets smaller than specified size as data URLs to avoid requests.
{
......@@ -163,15 +170,6 @@ module.exports = {
limit: 10000,
name: 'static/media/[name].[hash:8].[ext]'
}
},
// "html" loader is used to process template page (index.html) to resolve
// resources linked with <link href="./relative/path"> HTML tags.
{
test: /\.html$/,
loader: 'html',
query: {
attrs: ['link:href'],
}
}
]
},
......@@ -198,6 +196,13 @@ module.exports = {
];
},
plugins: [
// Makes the public URL available as %PUBLIC_URL% in index.html, e.g.:
// <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
// In production, it will be an empty string unless you specify "homepage"
// in `package.json`, in which case it will be the pathname of that URL.
new InterpolateHtmlPlugin({
PUBLIC_URL: publicUrl
}),
// Generates an `index.html` file with the <script> injected.
new HtmlWebpackPlugin({
inject: true,
......@@ -216,7 +221,7 @@ module.exports = {
}
}),
// Makes some environment variables available to the JS code, for example:
// if (process.env.NODE_ENV === 'production') { ... }. See `env.js`.
// if (process.env.NODE_ENV === 'production') { ... }.
// It is absolutely essential that NODE_ENV was set to production here.
// Otherwise React will be compiled in the very slow development mode.
new webpack.DefinePlugin(env),
......
......@@ -46,7 +46,6 @@
"find-cache-dir": "0.1.1",
"fs-extra": "0.30.0",
"gzip-size": "3.0.0",
"html-loader": "0.4.3",
"html-webpack-plugin": "2.22.0",
"http-proxy-middleware": "0.17.1",
"jest": "15.1.1",
......
......@@ -13,7 +13,7 @@
process.env.NODE_ENV = 'production';
var chalk = require('chalk');
var fs = require('fs');
var fs = require('fs-extra');
var path = require('path');
var filesize = require('filesize');
var gzipSize = require('gzip-size').sync;
......@@ -70,6 +70,9 @@ recursive(paths.appBuild, (err, fileNames) => {
// Start the webpack build
build(previousSizeMap);
// Merge with the public folder
copyPublicFolder();
});
// Print a detailed summary of build files.
......@@ -175,3 +178,10 @@ function build(previousSizeMap) {
}
});
}
function copyPublicFolder() {
fs.copySync(paths.appPublic, paths.appBuild, {
dereference: true,
filter: file => file !== paths.appHtml
});
}
......@@ -46,6 +46,8 @@ prompt(
path.join('scripts', 'start.js'),
path.join('scripts', 'utils', 'checkRequiredFiles.js'),
path.join('scripts', 'utils', 'chrome.applescript'),
path.join('scripts', 'utils', 'getClientEnvironment.js'),
path.join('scripts', 'utils', 'InterpolateHtmlPlugin.js'),
path.join('scripts', 'utils', 'prompt.js'),
path.join('scripts', 'utils', 'WatchMissingNodeModulesPlugin.js')
];
......
......@@ -255,23 +255,21 @@ function runDevServer(port, protocol) {
// Silence WebpackDevServer's own logs since they're generally not useful.
// It will still show compile warnings and errors with this setting.
clientLogLevel: 'none',
// By default WebpackDevServer also serves files from the current directory.
// This might be useful in legacy apps. However we already encourage people
// to use Webpack for importing assets in the code, so we don't need to
// additionally serve files by their filenames. Otherwise, even if it
// works in development, those files will be missing in production, unless
// we explicitly copy them. But even if we copy all the files into
// the build output (which doesn't seem to be wise because it may contain
// private information such as files with API keys, for example), we would
// still have a problem. Since the filenames would be the same every time,
// browsers would cache their content, and updating file content would not
// work correctly. This is easily solved by importing assets through Webpack
// because if it can then append content hashes to filenames in production,
// just like it does for JS and CSS. And because we configured "html" loader
// to be used for HTML files, even <link href="./src/something.png"> would
// get resolved correctly by Webpack and handled both in development and
// in production without actually serving it by that path.
contentBase: [],
// By default WebpackDevServer serves physical files from current directory
// in addition to all the virtual build products that it serves from memory.
// This is confusing because those files won’t automatically be available in
// production build folder unless we copy them. However, copying the whole
// project directory is dangerous because we may expose sensitive files.
// Instead, we establish a convention that only files in `public` directory
// get served. Our build script will copy `public` into the `build` folder.
// In `index.html`, you can get URL of `public` folder with %PUBLIC_PATH%:
// <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
// In JavaScript code, you can access it with `process.env.PUBLIC_URL`.
// Note that we only recommend to use `public` folder as an escape hatch
// for files like `favicon.ico`, `manifest.json`, and libraries that are
// for some reason broken when imported through Webpack. If you just want to
// use an image, put it in `src` and `import` it from JavaScript instead.
contentBase: paths.appPublic,
// Enable hot reloading server. It will provide /sockjs-node/ endpoint
// for the WebpackDevServer client so it can learn when the files were
// updated. The WebpackDevServer client is included as an entry point
......
......@@ -8,6 +8,7 @@
*/
process.env.NODE_ENV = 'test';
process.env.PUBLIC_URL = '';
const createJestConfig = require('./utils/createJestConfig');
const jest = require('jest');
......
// @remove-on-eject-begin
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
// @remove-on-eject-end
// This Webpack plugin lets us interpolate custom variables into `index.html`.
// Usage: `new InterpolateHtmlPlugin({ 'MY_VARIABLE': 42 })`
// Then, you can use %MY_VARIABLE% in your `index.html`.
// It works in tandem with HtmlWebpackPlugin.
// Learn more about creating plugins like this:
// https://github.com/ampedandwired/html-webpack-plugin#events
'use strict';
class InterpolateHtmlPlugin {
constructor(replacements) {
this.replacements = replacements;
}
apply(compiler) {
compiler.plugin('compilation', compilation => {
compilation.plugin('html-webpack-plugin-before-html-processing',
(data, callback) => {
// Run HTML through a series of user-specified string replacements.
Object.keys(this.replacements).forEach(key => {
const value = this.replacements[key];
data.html = data.html.replace('%' + key + '%', value);
});
callback(null, data);
}
);
});
}
}
module.exports = InterpolateHtmlPlugin;
......@@ -13,14 +13,26 @@
// injected into the application via DefinePlugin in Webpack configuration.
var REACT_APP = /^REACT_APP_/i;
var NODE_ENV = JSON.stringify(process.env.NODE_ENV || 'development');
module.exports = Object
.keys(process.env)
.filter(key => REACT_APP.test(key))
.reduce((env, key) => {
env['process.env.' + key] = JSON.stringify(process.env[key]);
return env;
}, {
'process.env.NODE_ENV': NODE_ENV
});
function getClientEnvironment(publicUrl) {
return Object
.keys(process.env)
.filter(key => REACT_APP.test(key))
.reduce((env, key) => {
env['process.env.' + key] = JSON.stringify(process.env[key]);
return env;
}, {
// Useful for determining whether we’re running in production mode.
// Most importantly, it switches React into the correct mode.
'process.env.NODE_ENV': JSON.stringify(
process.env.NODE_ENV || 'development'
),
// Useful for resolving the correct path to static assets in `public`.
// For example, <img src={process.env.PUBLIC_URL + '/img/logo.png'} />.
// This should only be used as an escape hatch. Normally you would put
// images into the `src` and `import` them in code to get their paths.
'process.env.PUBLIC_URL': JSON.stringify(publicUrl)
});
}
module.exports = getClientEnvironment;
......@@ -3,7 +3,16 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="shortcut icon" href="./src/favicon.ico">
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
<!--
Notice the use of %PUBLIC_URL% in the tag above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favico.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
</head>
<body>
......@@ -15,7 +24,7 @@
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` in this folder.
To begin the development, run `npm start`.
To create a production bundle, use `npm run build`.
-->
</body>
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment