From 6b5bb7c08fd4b975e43ab0987dee944d7f28782c Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Mon, 25 Jul 2016 22:31:28 +0100 Subject: [PATCH] Unroll indirection in paths --- 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 00000000000..b0f94588adb --- /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 5503096179f..db68112c45c 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 89f50f764bb..335f46dd9e4 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 7acb199652d..693ace0f687 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 efdff07400c..b3d7b4ae60c 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 b821bae58ef..1a94eb5df1f 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 8753c34d68d..81bed9f098e 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()) {