From a61be9c619c46d07c5f0b191286a818cc3dd1090 Mon Sep 17 00:00:00 2001 From: Joe Haddad Date: Wed, 17 May 2017 06:19:52 -0500 Subject: [PATCH] Add module scope plugin (#2189) * Add module scope plugin * Oops * Add comments * Check windows seps too * More descriptive error * Document it --- packages/react-dev-utils/ModuleScopePlugin.js | 68 +++++++++++++++++++ packages/react-dev-utils/README.md | 20 ++++++ packages/react-dev-utils/package.json | 1 + .../config/webpack.config.dev.js | 9 +++ .../config/webpack.config.prod.js | 9 +++ 5 files changed, 107 insertions(+) create mode 100644 packages/react-dev-utils/ModuleScopePlugin.js diff --git a/packages/react-dev-utils/ModuleScopePlugin.js b/packages/react-dev-utils/ModuleScopePlugin.js new file mode 100644 index 00000000000..fd70a2f408a --- /dev/null +++ b/packages/react-dev-utils/ModuleScopePlugin.js @@ -0,0 +1,68 @@ +/** + * 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. + */ + +'use strict'; + +const chalk = require('chalk'); +const path = require('path'); + +class ModuleScopePlugin { + constructor(appSrc) { + this.appSrc = appSrc; + } + + apply(resolver) { + const { appSrc } = this; + resolver.plugin('file', (request, callback) => { + // Unknown issuer, probably webpack internals + if (!request.context.issuer) { + return callback(); + } + if ( + // If this resolves to a node_module, we don't care what happens next + request.descriptionFileRoot.indexOf('/node_modules/') !== -1 || + request.descriptionFileRoot.indexOf('\\node_modules\\') !== -1 || + // Make sure this request was manual + !request.__innerRequest_request + ) { + return callback(); + } + // Resolve the issuer from our appSrc and make sure it's one of our files + // Maybe an indexOf === 0 would be better? + const relative = path.relative(appSrc, request.context.issuer); + // If it's not in src/ or a subdirectory, not our request! + if (relative[0] === '.') { + return callback(); + } + // Find path from src to the requested file + const requestRelative = path.relative( + appSrc, + path.resolve( + path.dirname(request.context.issuer), + request.__innerRequest_request + ) + ); + // Error if in a parent directory of src/ + if (requestRelative[0] === '.') { + callback( + new Error( + `You attempted to import ${chalk.cyan(request.__innerRequest_request)} which falls outside of the project ${chalk.cyan('src/')} directory. ` + + `Relative imports outside of ${chalk.cyan('src/')} are not supported. ` + + `You can either move it inside ${chalk.cyan('src/')}, or add a symlink to it from project's ${chalk.cyan('node_modules/')}.` + ), + request + ); + } else { + callback(); + } + }); + } +} + +module.exports = ModuleScopePlugin; diff --git a/packages/react-dev-utils/README.md b/packages/react-dev-utils/README.md index ef720449356..4a7effe217a 100644 --- a/packages/react-dev-utils/README.md +++ b/packages/react-dev-utils/README.md @@ -56,6 +56,26 @@ module.exports = { } ``` + +#### `new ModuleScopePlugin(appSrc: string)` + +This Webpack plugin ensures that relative imports from app's source directory don't reach outside of it. + +```js +var path = require('path'); +var ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin'); + + +module.exports = { + // ... + plugins: [ + new ModuleScopePlugin(paths.appSrc), + // ... + ], + // ... +} +``` + #### `new WatchMissingNodeModulesPlugin(nodeModulesPath: string)` This Webpack plugin ensures `npm install ` forces a project rebuild.
diff --git a/packages/react-dev-utils/package.json b/packages/react-dev-utils/package.json index d52fe30fd86..0a22499ca01 100644 --- a/packages/react-dev-utils/package.json +++ b/packages/react-dev-utils/package.json @@ -21,6 +21,7 @@ "getProcessForPort.js", "InterpolateHtmlPlugin.js", "launchEditor.js", + "ModuleScopePlugin.js", "openBrowser.js", "openChrome.applescript", "prepareProxy.js", diff --git a/packages/react-scripts/config/webpack.config.dev.js b/packages/react-scripts/config/webpack.config.dev.js index f90ca6bd09e..3d198d28a34 100644 --- a/packages/react-scripts/config/webpack.config.dev.js +++ b/packages/react-scripts/config/webpack.config.dev.js @@ -18,6 +18,7 @@ const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin'); const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin'); const WatchMissingNodeModulesPlugin = require('react-dev-utils/WatchMissingNodeModulesPlugin'); const eslintFormatter = require('react-dev-utils/eslintFormatter'); +const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin'); const getClientEnvironment = require('./env'); const paths = require('./paths'); @@ -106,6 +107,14 @@ module.exports = { // https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/ 'react-native': 'react-native-web', }, + plugins: [ + // Prevents users from importing files from outside of src/ (or node_modules/). + // This often causes confusion because we only process files within src/ with babel. + // To fix this, we prevent you from importing files out of src/ -- if you'd like to, + // please link the files into your node_modules/ and let module-resolution kick in. + // Make sure your source files are compiled, as they will not be processed in any way. + new ModuleScopePlugin(paths.appSrc), + ], }, module: { strictExportPresence: true, diff --git a/packages/react-scripts/config/webpack.config.prod.js b/packages/react-scripts/config/webpack.config.prod.js index 182105c756a..9742e7ff50a 100644 --- a/packages/react-scripts/config/webpack.config.prod.js +++ b/packages/react-scripts/config/webpack.config.prod.js @@ -18,6 +18,7 @@ const ExtractTextPlugin = require('extract-text-webpack-plugin'); const ManifestPlugin = require('webpack-manifest-plugin'); const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin'); const eslintFormatter = require('react-dev-utils/eslintFormatter'); +const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin'); const paths = require('./paths'); const getClientEnvironment = require('./env'); @@ -103,6 +104,14 @@ module.exports = { // https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/ 'react-native': 'react-native-web', }, + plugins: [ + // Prevents users from importing files from outside of src/ (or node_modules/). + // This often causes confusion because we only process files within src/ with babel. + // To fix this, we prevent you from importing files out of src/ -- if you'd like to, + // please link the files into your node_modules/ and let module-resolution kick in. + // Make sure your source files are compiled, as they will not be processed in any way. + new ModuleScopePlugin(paths.appSrc), + ], }, module: { strictExportPresence: true,