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
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 0 additions & 26 deletions packages/react-scripts/config/env.js

This file was deleted.

9 changes: 6 additions & 3 deletions packages/react-scripts/config/paths.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'),
Expand All @@ -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'),
Expand All @@ -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'),
Expand Down
44 changes: 21 additions & 23 deletions packages/react-scripts/config/webpack.config.dev.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.
{
Expand All @@ -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'],
}
}
]
},
Expand All @@ -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(),
Expand Down
63 changes: 34 additions & 29 deletions packages/react-scripts/config/webpack.config.prod.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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.
Expand Down Expand Up @@ -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.
{
Expand All @@ -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'],
}
}
]
},
Expand All @@ -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,
Expand All @@ -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),
Expand Down
1 change: 0 additions & 1 deletion packages/react-scripts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,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",
Expand Down
12 changes: 11 additions & 1 deletion packages/react-scripts/scripts/build.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -175,3 +178,10 @@ function build(previousSizeMap) {
}
});
}

function copyPublicFolder() {
fs.copySync(paths.appPublic, paths.appBuild, {
dereference: true,
filter: file => file !== paths.appHtml
});
}
2 changes: 2 additions & 0 deletions packages/react-scripts/scripts/eject.js
Original file line number Diff line number Diff line change
Expand Up @@ -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')
];
Expand Down
32 changes: 15 additions & 17 deletions packages/react-scripts/scripts/start.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions packages/react-scripts/scripts/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
*/

process.env.NODE_ENV = 'test';
process.env.PUBLIC_URL = '';

const createJestConfig = require('./utils/createJestConfig');
const jest = require('jest');
Expand Down
43 changes: 43 additions & 0 deletions packages/react-scripts/scripts/utils/InterpolateHtmlPlugin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// @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;
Loading