From dfecfea6baf64662800b8c3260242271764a3f70 Mon Sep 17 00:00:00 2001
From: Dan Abramov <dan.abramov@gmail.com>
Date: Mon, 25 Jul 2016 22:45:27 +0100
Subject: [PATCH] Unroll indirection in paths (#191)

---
 config/paths.js               | 64 +++++++++++++++++++++++++++++++++++
 config/webpack.config.dev.js  | 38 +++++++--------------
 config/webpack.config.prod.js | 36 +++++++-------------
 scripts/build.js              | 14 ++------
 scripts/eject.js              | 48 +++++++++++++-------------
 scripts/init.js               | 32 +++++++++---------
 scripts/start.js              |  4 +--
 7 files changed, 134 insertions(+), 102 deletions(-)
 create mode 100644 config/paths.js

diff --git a/config/paths.js b/config/paths.js
new file mode 100644
index 000000000..b0f94588a
--- /dev/null
+++ b/config/paths.js
@@ -0,0 +1,64 @@
+/**
+ * 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.
+ */
+
+// TODO: we can split this file into several files (pre-eject, post-eject, test)
+// and use those instead. This way we don't need to branch here.
+
+var path = require('path');
+
+// True when used as a dependency, false after ejecting
+var isInNodeModules = (
+  'node_modules' ===
+  path.basename(path.resolve(path.join(__dirname, '..', '..')))
+);
+
+// Are we developing create-react-app locally?
+var isInCreateReactAppSource = (
+  process.argv.some(arg => arg.indexOf('--debug-template') > -1)
+);
+
+function resolve(relativePath) {
+  return path.resolve(__dirname, relativePath);
+}
+
+if (isInCreateReactAppSource) {
+  // create-react-app development: we're in ./config/
+  module.exports = {
+    appBuild: resolve('../build'),
+    appHtml: resolve('../template/index.html'),
+    appFavicon: resolve('../template/favicon.ico'),
+    appPackageJson: resolve('../package.json'),
+    appSrc: resolve('../template/src'),
+    appNodeModules: resolve('../node_modules'),
+    ownNodeModules: resolve('../node_modules')
+  };
+} else if (isInNodeModules) {
+  // before eject: we're in ./node_modules/react-scripts/config/
+  module.exports = {
+    appBuild: resolve('../../../build'),
+    appHtml: resolve('../../../index.html'),
+    appFavicon: resolve('../../../favicon.ico'),
+    appPackageJson: resolve('../../../package.json'),
+    appSrc: resolve('../../../src'),
+    appNodeModules: resolve('../..'),
+    // this is empty with npm3 but node resolution searches higher anyway:
+    ownNodeModules: resolve('../node_modules')
+  };
+} else {
+  // after eject: we're in ./config/
+  module.exports = {
+    appBuild: resolve('../build'),
+    appHtml: resolve('../index.html'),
+    appFavicon: resolve('../favicon.ico'),
+    appPackageJson: resolve('../package.json'),
+    appSrc: resolve('../src'),
+    appNodeModules: resolve('../node_modules'),
+    ownNodeModules: resolve('../node_modules')
+  };
+}
diff --git a/config/webpack.config.dev.js b/config/webpack.config.dev.js
index 550309617..db68112c4 100644
--- a/config/webpack.config.dev.js
+++ b/config/webpack.config.dev.js
@@ -11,35 +11,18 @@ var path = require('path');
 var autoprefixer = require('autoprefixer');
 var webpack = require('webpack');
 var HtmlWebpackPlugin = require('html-webpack-plugin');
-
-// TODO: hide this behind a flag and eliminate dead code on eject.
-// This shouldn't be exposed to the user.
-var isInNodeModules = 'node_modules' ===
-  path.basename(path.resolve(path.join(__dirname, '..', '..')));
-var relativePath = isInNodeModules ? '../../..' : '..';
-var isInDebugMode = process.argv.some(arg =>
-  arg.indexOf('--debug-template') > -1
-);
-if (isInDebugMode) {
-  relativePath = '../template';
-}
-var srcPath = path.resolve(__dirname, relativePath, 'src');
-var rootNodeModulesPath = path.resolve(__dirname, relativePath, 'node_modules');
-var nodeModulesPath = path.join(__dirname, '..', 'node_modules');
-var indexHtmlPath = path.resolve(__dirname, relativePath, 'index.html');
-var faviconPath = path.resolve(__dirname, relativePath, 'favicon.ico');
-var buildPath = path.join(__dirname, isInNodeModules ? '../../..' : '..', 'build');
+var paths = require('./paths');
 
 module.exports = {
   devtool: 'eval',
   entry: [
     require.resolve('webpack-dev-server/client') + '?http://localhost:3000',
     require.resolve('webpack/hot/dev-server'),
-    path.join(srcPath, 'index')
+    path.join(paths.appSrc, 'index')
   ],
   output: {
     // Next line is not used in dev but WebpackDevServer crashes without it:
-    path: buildPath,
+    path: paths.appBuild,
     pathinfo: true,
     filename: 'bundle.js',
     publicPath: '/'
@@ -48,7 +31,7 @@ module.exports = {
     extensions: ['', '.js'],
   },
   resolveLoader: {
-    root: nodeModulesPath,
+    root: paths.ownNodeModules,
     moduleTemplates: ['*-loader']
   },
   module: {
@@ -56,31 +39,34 @@ module.exports = {
       {
         test: /\.js$/,
         loader: 'eslint',
-        include: srcPath,
+        include: paths.appSrc,
       }
     ],
     loaders: [
       {
         test: /\.js$/,
-        include: srcPath,
+        include: paths.appSrc,
         loader: 'babel',
         query: require('./babel.dev')
       },
       {
         test: /\.css$/,
-        include: [srcPath, rootNodeModulesPath],
+        include: [paths.appSrc, paths.appNodeModules],
         loader: 'style!css!postcss'
       },
       {
         test: /\.json$/,
+        include: [paths.appSrc, paths.appNodeModules],
         loader: 'json'
       },
       {
         test: /\.(jpg|png|gif|eot|svg|ttf|woff|woff2)$/,
+        include: [paths.appSrc, paths.appNodeModules],
         loader: 'file',
       },
       {
         test: /\.(mp4|webm)$/,
+        include: [paths.appSrc, paths.appNodeModules],
         loader: 'url?limit=10000'
       }
     ]
@@ -95,8 +81,8 @@ module.exports = {
   plugins: [
     new HtmlWebpackPlugin({
       inject: true,
-      template: indexHtmlPath,
-      favicon: faviconPath,
+      template: paths.appHtml,
+      favicon: paths.appFavicon,
     }),
     new webpack.DefinePlugin({ 'process.env.NODE_ENV': '"development"' }),
     // Note: only CSS is currently hot reloaded
diff --git a/config/webpack.config.prod.js b/config/webpack.config.prod.js
index 89f50f764..335f46dd9 100644
--- a/config/webpack.config.prod.js
+++ b/config/webpack.config.prod.js
@@ -13,22 +13,9 @@ var webpack = require('webpack');
 var HtmlWebpackPlugin = require('html-webpack-plugin');
 var ExtractTextPlugin = require('extract-text-webpack-plugin');
 var url = require('url');
+var paths = require('./paths');
 
-// TODO: hide this behind a flag and eliminate dead code on eject.
-// This shouldn't be exposed to the user.
-var isInNodeModules = 'node_modules' ===
-  path.basename(path.resolve(path.join(__dirname, '..', '..')));
-var relativePath = isInNodeModules ? '../../..' : '..';
-if (process.argv[2] === '--debug-template') {
-  relativePath = '../template';
-}
-var srcPath = path.resolve(__dirname, relativePath, 'src');
-var rootNodeModulesPath = path.resolve(__dirname, relativePath, 'node_modules');
-var nodeModulesPath = path.join(__dirname, '..', 'node_modules');
-var indexHtmlPath = path.resolve(__dirname, relativePath, 'index.html');
-var faviconPath = path.resolve(__dirname, relativePath, 'favicon.ico');
-var buildPath = path.join(__dirname, isInNodeModules ? '../../..' : '..', 'build');
-var homepagePath = require(path.resolve(__dirname, relativePath, 'package.json')).homepage;
+var homepagePath = require(paths.appPackageJson).homepage;
 var publicPath = homepagePath ? url.parse(homepagePath).pathname : '/';
 if (!publicPath.endsWith('/')) {
   // Prevents incorrect paths in file-loader
@@ -38,9 +25,9 @@ if (!publicPath.endsWith('/')) {
 module.exports = {
   bail: true,
   devtool: 'source-map',
-  entry: path.join(srcPath, 'index'),
+  entry: path.join(paths.appSrc, 'index'),
   output: {
-    path: buildPath,
+    path: paths.appBuild,
     filename: '[name].[chunkhash].js',
     chunkFilename: '[name].[chunkhash].chunk.js',
     publicPath: publicPath
@@ -49,7 +36,7 @@ module.exports = {
     extensions: ['', '.js'],
   },
   resolveLoader: {
-    root: nodeModulesPath,
+    root: paths.ownNodeModules,
     moduleTemplates: ['*-loader']
   },
   module: {
@@ -57,19 +44,19 @@ module.exports = {
       {
         test: /\.js$/,
         loader: 'eslint',
-        include: srcPath
+        include: paths.appSrc
       }
     ],
     loaders: [
       {
         test: /\.js$/,
-        include: srcPath,
+        include: paths.appSrc,
         loader: 'babel',
         query: require('./babel.prod')
       },
       {
         test: /\.css$/,
-        include: [srcPath, rootNodeModulesPath],
+        include: [paths.appSrc, paths.appNodeModules],
         // Disable autoprefixer in css-loader itself:
         // https://github.com/webpack/css-loader/issues/281
         // We already have it thanks to postcss.
@@ -77,14 +64,17 @@ module.exports = {
       },
       {
         test: /\.json$/,
+        include: [paths.appSrc, paths.appNodeModules],
         loader: 'json'
       },
       {
         test: /\.(jpg|png|gif|eot|svg|ttf|woff|woff2)$/,
+        include: [paths.appSrc, paths.appNodeModules],
         loader: 'file',
       },
       {
         test: /\.(mp4|webm)$/,
+        include: [paths.appSrc, paths.appNodeModules],
         loader: 'url?limit=10000'
       }
     ]
@@ -101,8 +91,8 @@ module.exports = {
   plugins: [
     new HtmlWebpackPlugin({
       inject: true,
-      template: indexHtmlPath,
-      favicon: faviconPath,
+      template: paths.appHtml,
+      favicon: paths.appFavicon,
       minify: {
         removeComments: true,
         collapseWhitespace: true,
diff --git a/scripts/build.js b/scripts/build.js
index 7acb19965..693ace0f6 100644
--- a/scripts/build.js
+++ b/scripts/build.js
@@ -9,20 +9,12 @@
 
 process.env.NODE_ENV = 'production';
 
-var path = require('path');
 var rimrafSync = require('rimraf').sync;
 var webpack = require('webpack');
 var config = require('../config/webpack.config.prod');
+var paths = require('../config/paths');
 
-var isInNodeModules = 'node_modules' ===
-  path.basename(path.resolve(path.join(__dirname, '..', '..')));
-var relative = isInNodeModules ? '../../..' : '..';
-if (process.argv[2] === '--debug-template') {
-  relative = '../template';
-}
-var packageJsonPath = path.join(__dirname, relative, 'package.json');
-var buildPath = path.join(__dirname, relative, 'build');
-rimrafSync(buildPath);
+rimrafSync(paths.appBuild);
 
 webpack(config).run(function(err, stats) {
   if (err) {
@@ -32,7 +24,7 @@ webpack(config).run(function(err, stats) {
   }
 
   var openCommand = process.platform === 'win32' ? 'start' : 'open';
-  var homepagePath = require(packageJsonPath).homepage;
+  var homepagePath = require(paths.appPackageJson).homepage;
   console.log('Successfully generated a bundle in the build folder!');
   if (homepagePath) {
     console.log('You can now deploy it to ' + homepagePath + '.');
diff --git a/scripts/eject.js b/scripts/eject.js
index efdff0740..b3d7b4ae6 100644
--- a/scripts/eject.js
+++ b/scripts/eject.js
@@ -12,6 +12,7 @@ var path = require('path');
 var rl = require('readline');
 var rimrafSync = require('rimraf').sync;
 var spawnSync = require('cross-spawn').sync;
+var paths = require('../config/paths');
 
 var prompt = function(question, cb) {
   var rlInterface = rl.createInterface({
@@ -37,14 +38,15 @@ prompt('Are you sure you want to eject? This action is permanent. [y/N]', functi
   console.log('Ejecting...');
   console.log();
 
-  var selfPath = path.join(__dirname, '..');
-  var hostPath = path.join(selfPath, '..', '..');
+  var ownPath = path.join(__dirname, '..');
+  var appPath = path.join(ownPath, '..', '..');
   var files = [
     path.join('config', 'babel.dev.js'),
     path.join('config', 'babel.prod.js'),
     path.join('config', 'flow', 'css.js.flow'),
     path.join('config', 'flow', 'file.js.flow'),
     path.join('config', 'eslint.js'),
+    path.join('config', 'paths.js'),
     path.join('config', 'webpack.config.dev.js'),
     path.join('config', 'webpack.config.prod.js'),
     path.join('scripts', 'build.js'),
@@ -52,9 +54,9 @@ prompt('Are you sure you want to eject? This action is permanent. [y/N]', functi
     path.join('scripts', 'openChrome.applescript')
   ];
 
-  // Ensure that the host folder is clean and we won't override any files
+  // Ensure that the app folder is clean and we won't override any files
   files.forEach(function(file) {
-    if (fs.existsSync(path.join(hostPath, file))) {
+    if (fs.existsSync(path.join(appPath, file))) {
       console.error(
         '`' + file + '` already exists in your app folder. We cannot ' +
         'continue as you would lose all the changes in that file or directory. ' +
@@ -66,58 +68,58 @@ prompt('Are you sure you want to eject? This action is permanent. [y/N]', functi
   });
 
   // Copy the files over
-  fs.mkdirSync(path.join(hostPath, 'config'));
-  fs.mkdirSync(path.join(hostPath, 'config', 'flow'));
-  fs.mkdirSync(path.join(hostPath, 'scripts'));
+  fs.mkdirSync(path.join(appPath, 'config'));
+  fs.mkdirSync(path.join(appPath, 'config', 'flow'));
+  fs.mkdirSync(path.join(appPath, 'scripts'));
 
   files.forEach(function(file) {
-    console.log('Copying ' + file + ' to ' + hostPath);
+    console.log('Copying ' + file + ' to ' + appPath);
     var content = fs
-      .readFileSync(path.join(selfPath, file), 'utf8')
+      .readFileSync(path.join(ownPath, file), 'utf8')
       // Remove license header from JS
       .replace(/^\/\*\*(\*(?!\/)|[^*])*\*\//, '')
       // Remove license header from AppleScript
       .replace(/^--.*\n/gm, '')
       .trim() + '\n';
-    fs.writeFileSync(path.join(hostPath, file), content);
+    fs.writeFileSync(path.join(appPath, file), content);
   });
   console.log();
 
-  var selfPackage = require(path.join(selfPath, 'package.json'));
-  var hostPackage = require(path.join(hostPath, 'package.json'));
+  var ownPackage = require(path.join(ownPath, 'package.json'));
+  var appPackage = require(path.join(appPath, 'package.json'));
 
   console.log('Removing dependency: react-scripts');
-  delete hostPackage.devDependencies['react-scripts'];
+  delete appPackage.devDependencies['react-scripts'];
 
-  Object.keys(selfPackage.dependencies).forEach(function (key) {
+  Object.keys(ownPackage.dependencies).forEach(function (key) {
     // For some reason optionalDependencies end up in dependencies after install
-    if (selfPackage.optionalDependencies[key]) {
+    if (ownPackage.optionalDependencies[key]) {
       return;
     }
     console.log('Adding dependency: ' + key);
-    hostPackage.devDependencies[key] = selfPackage.dependencies[key];
+    appPackage.devDependencies[key] = ownPackage.dependencies[key];
   });
 
   console.log('Updating scripts');
-  Object.keys(hostPackage.scripts).forEach(function (key) {
-    hostPackage.scripts[key] = 'node ./scripts/' + key + '.js'
+  Object.keys(appPackage.scripts).forEach(function (key) {
+    appPackage.scripts[key] = 'node ./scripts/' + key + '.js'
   });
-  delete hostPackage.scripts['eject'];
+  delete appPackage.scripts['eject'];
 
   // explicitly specify ESLint config path for editor plugins
-  hostPackage.eslintConfig = {
+  appPackage.eslintConfig = {
     extends: './config/eslint.js',
   };
 
   console.log('Writing package.json');
   fs.writeFileSync(
-    path.join(hostPath, 'package.json'),
-    JSON.stringify(hostPackage, null, 2)
+    path.join(appPath, 'package.json'),
+    JSON.stringify(appPackage, null, 2)
   );
   console.log();
 
   console.log('Running npm install...');
-  rimrafSync(selfPath);
+  rimrafSync(ownPath);
   spawnSync('npm', ['install'], {stdio: 'inherit'});
   console.log('Ejected successfully!');
   console.log();
diff --git a/scripts/init.js b/scripts/init.js
index b821bae58..1a94eb5df 100644
--- a/scripts/init.js
+++ b/scripts/init.js
@@ -11,40 +11,40 @@ var fs = require('fs-extra');
 var path = require('path');
 var spawn = require('cross-spawn');
 
-module.exports = function(hostPath, appName, verbose, originalDirectory) {
-  var selfPath = path.join(hostPath, 'node_modules', 'react-scripts');
+module.exports = function(appPath, appName, verbose, originalDirectory) {
+  var ownPath = path.join(appPath, 'node_modules', 'react-scripts');
 
-  var hostPackage = require(path.join(hostPath, 'package.json'));
-  var selfPackage = require(path.join(selfPath, 'package.json'));
+  var appPackage = require(path.join(appPath, 'package.json'));
+  var ownPackage = require(path.join(ownPath, 'package.json'));
 
   // Copy over some of the devDependencies
-  hostPackage.dependencies = hostPackage.dependencies || {};
+  appPackage.dependencies = appPackage.dependencies || {};
   ['react', 'react-dom'].forEach(function (key) {
-    hostPackage.dependencies[key] = selfPackage.devDependencies[key];
+    appPackage.dependencies[key] = ownPackage.devDependencies[key];
   });
 
   // Setup the script rules
-  hostPackage.scripts = {};
+  appPackage.scripts = {};
   ['start', 'build', 'eject'].forEach(function(command) {
-    hostPackage.scripts[command] = 'react-scripts ' + command;
+    appPackage.scripts[command] = 'react-scripts ' + command;
   });
 
   // explicitly specify ESLint config path for editor plugins
-  hostPackage.eslintConfig = {
+  appPackage.eslintConfig = {
     extends: './node_modules/react-scripts/config/eslint.js',
   };
 
   fs.writeFileSync(
-    path.join(hostPath, 'package.json'),
-    JSON.stringify(hostPackage, null, 2)
+    path.join(appPath, 'package.json'),
+    JSON.stringify(appPackage, null, 2)
   );
 
   // Copy the files for the user
-  fs.copySync(path.join(selfPath, 'template'), hostPath);
+  fs.copySync(path.join(ownPath, 'template'), appPath);
 
   // Rename gitignore after the fact to prevent npm from renaming it to .npmignore
   // See: https://github.com/npm/npm/issues/1862
-  fs.move(path.join(hostPath, 'gitignore'), path.join(hostPath, '.gitignore'), []);
+  fs.move(path.join(appPath, 'gitignore'), path.join(appPath, '.gitignore'), []);
 
   // Run another npm install for react and react-dom
   console.log('Installing react and react-dom from npm...');
@@ -65,13 +65,13 @@ module.exports = function(hostPath, appName, verbose, originalDirectory) {
     // backward compatibility with old global-cli's.
     var cdpath;
     if (originalDirectory &&
-        path.join(originalDirectory, appName) === hostPath) {
+        path.join(originalDirectory, appName) === appPath) {
       cdpath = appName;
     } else {
-      cdpath = hostPath;
+      cdpath = appPath;
     }
 
-    console.log('Success! Created ' + appName + ' at ' + hostPath + '.');
+    console.log('Success! Created ' + appName + ' at ' + appPath + '.');
     console.log('Inside that directory, you can run several commands:');
     console.log();
     console.log('  * npm start: Starts the development server.');
diff --git a/scripts/start.js b/scripts/start.js
index 8753c34d6..81bed9f09 100644
--- a/scripts/start.js
+++ b/scripts/start.js
@@ -20,9 +20,7 @@ var opn = require('opn');
 // TODO: hide this behind a flag and eliminate dead code on eject.
 // This shouldn't be exposed to the user.
 var handleCompile;
-var isSmokeTest = process.argv.some(arg =>
-  arg.indexOf('--smoke-test') > -1
-);
+var isSmokeTest = process.argv.some(arg => arg.indexOf('--smoke-test') > -1);
 if (isSmokeTest) {
   handleCompile = function (err, stats) {
     if (err || stats.hasErrors() || stats.hasWarnings()) {
-- 
GitLab