Commit 9e074bbf authored by Maël Nison's avatar Maël Nison Committed by Dan Abramov
Browse files

Plug'n'Play support (#5136)

* Adds the PnP plugin for Webpack to find dependencies when working under PnP

* Adds configuration for jest

* Adds an e2e test for when using PnP

* Avoids cra from crashing at the engine check

* Avoids cra from crashing when initializing react-scripts

* Makes the ownPath portable

* Fixes linting

* Bumps to pnp-webpack-plugin@1.1.0, removes symlinks: false

* Adds a --use-pnp option

* Pin version
parent f59165fc
3 merge requests!12191Lim.Pisey.168:/Identified - We are currently investigating reports of missing build logs. The issue has been identified and a resolution is in progress. We will provide a further update when available.Mar 21, 09:02 UTC,!12853brikk,!5717Automatically extract project file structure from build bundle file
Showing with 116 additions and 22 deletions
+116 -22
...@@ -9,7 +9,7 @@ cache: ...@@ -9,7 +9,7 @@ cache:
directories: directories:
- .npm - .npm
before_install: before_install:
- curl -o- -L https://yarnpkg.com/install.sh | bash - curl -o- -L https://yarnpkg.com/install.sh | bash -s -- --nightly
- export PATH="$HOME/.yarn/bin:$PATH" - export PATH="$HOME/.yarn/bin:$PATH"
install: true install: true
script: script:
......
...@@ -37,7 +37,7 @@ platform: ...@@ -37,7 +37,7 @@ platform:
install: install:
- ps: Install-Product node $env:nodejs_version $env:platform - ps: Install-Product node $env:nodejs_version $env:platform
- ps: | - ps: |
(New-Object Net.WebClient).DownloadFile("https://yarnpkg.com/latest.msi", "$env:temp\yarn.msi") (New-Object Net.WebClient).DownloadFile("https://nightly.yarnpkg.com/latest.msi", "$env:temp\yarn.msi")
cmd /c start /wait msiexec.exe /i $env:temp\yarn.msi /quiet /qn /norestart cmd /c start /wait msiexec.exe /i $env:temp\yarn.msi /quiet /qn /norestart
build: off build: off
......
...@@ -76,6 +76,7 @@ const program = new commander.Command(packageJson.name) ...@@ -76,6 +76,7 @@ const program = new commander.Command(packageJson.name)
'use a non-standard version of react-scripts' 'use a non-standard version of react-scripts'
) )
.option('--use-npm') .option('--use-npm')
.option('--use-pnp')
.allowUnknownOption() .allowUnknownOption()
.on('--help', () => { .on('--help', () => {
console.log(` Only ${chalk.green('<project-directory>')} is required.`); console.log(` Only ${chalk.green('<project-directory>')} is required.`);
...@@ -178,10 +179,11 @@ createApp( ...@@ -178,10 +179,11 @@ createApp(
program.verbose, program.verbose,
program.scriptsVersion, program.scriptsVersion,
program.useNpm, program.useNpm,
program.usePnp,
hiddenProgram.internalTestingTemplate hiddenProgram.internalTestingTemplate
); );
function createApp(name, verbose, version, useNpm, template) { function createApp(name, verbose, version, useNpm, usePnp, template) {
const root = path.resolve(name); const root = path.resolve(name);
const appName = path.basename(root); const appName = path.basename(root);
...@@ -241,7 +243,16 @@ function createApp(name, verbose, version, useNpm, template) { ...@@ -241,7 +243,16 @@ function createApp(name, verbose, version, useNpm, template) {
version = 'react-scripts@0.9.x'; version = 'react-scripts@0.9.x';
} }
} }
run(root, appName, version, verbose, originalDirectory, template, useYarn); run(
root,
appName,
version,
verbose,
originalDirectory,
template,
useYarn,
usePnp
);
} }
function shouldUseYarn() { function shouldUseYarn() {
...@@ -253,7 +264,7 @@ function shouldUseYarn() { ...@@ -253,7 +264,7 @@ function shouldUseYarn() {
} }
} }
function install(root, useYarn, dependencies, verbose, isOnline) { function install(root, useYarn, usePnp, dependencies, verbose, isOnline) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let command; let command;
let args; let args;
...@@ -263,6 +274,9 @@ function install(root, useYarn, dependencies, verbose, isOnline) { ...@@ -263,6 +274,9 @@ function install(root, useYarn, dependencies, verbose, isOnline) {
if (!isOnline) { if (!isOnline) {
args.push('--offline'); args.push('--offline');
} }
if (usePnp) {
args.push('--enable-pnp');
}
[].push.apply(args, dependencies); [].push.apply(args, dependencies);
// Explicitly set cwd() to work around issues like // Explicitly set cwd() to work around issues like
...@@ -287,6 +301,12 @@ function install(root, useYarn, dependencies, verbose, isOnline) { ...@@ -287,6 +301,12 @@ function install(root, useYarn, dependencies, verbose, isOnline) {
'--loglevel', '--loglevel',
'error', 'error',
].concat(dependencies); ].concat(dependencies);
if (usePnp) {
console.log(chalk.yellow("NPM doesn't support PnP."));
console.log(chalk.yellow('Falling back to the regular installs.'));
console.log();
}
} }
if (verbose) { if (verbose) {
...@@ -313,7 +333,8 @@ function run( ...@@ -313,7 +333,8 @@ function run(
verbose, verbose,
originalDirectory, originalDirectory,
template, template,
useYarn useYarn,
usePnp
) { ) {
const packageToInstall = getInstallPackage(version, originalDirectory); const packageToInstall = getInstallPackage(version, originalDirectory);
const allDependencies = ['react', 'react-dom', packageToInstall]; const allDependencies = ['react', 'react-dom', packageToInstall];
...@@ -336,23 +357,34 @@ function run( ...@@ -336,23 +357,34 @@ function run(
); );
console.log(); console.log();
return install(root, useYarn, allDependencies, verbose, isOnline).then( return install(
() => packageName root,
); useYarn,
usePnp,
allDependencies,
verbose,
isOnline
).then(() => packageName);
}) })
.then(packageName => { .then(async packageName => {
checkNodeVersion(packageName); checkNodeVersion(packageName);
setCaretRangeForRuntimeDeps(packageName); setCaretRangeForRuntimeDeps(packageName);
const scriptsPath = path.resolve( const pnpPath = path.resolve(process.cwd(), '.pnp.js');
process.cwd(),
'node_modules', const nodeArgs = fs.existsSync(pnpPath) ? ['--require', pnpPath] : [];
packageName,
'scripts', await executeNodeScript(
'init.js' {
cwd: process.cwd(),
args: nodeArgs,
},
[root, appName, verbose, originalDirectory, template],
`
var init = require('${packageName}/scripts/init.js');
init.apply(null, JSON.parse(process.argv[1]));
`
); );
const init = require(scriptsPath);
init(root, appName, verbose, originalDirectory, template);
if (version === 'react-scripts@0.9.x') { if (version === 'react-scripts@0.9.x') {
console.log( console.log(
...@@ -540,6 +572,11 @@ function checkNodeVersion(packageName) { ...@@ -540,6 +572,11 @@ function checkNodeVersion(packageName) {
packageName, packageName,
'package.json' 'package.json'
); );
if (!fs.existsSync(packageJsonPath)) {
return;
}
const packageJson = require(packageJsonPath); const packageJson = require(packageJsonPath);
if (!packageJson.engines || !packageJson.engines.node) { if (!packageJson.engines || !packageJson.engines.node) {
return; return;
...@@ -794,3 +831,23 @@ function checkIfOnline(useYarn) { ...@@ -794,3 +831,23 @@ function checkIfOnline(useYarn) {
}); });
}); });
} }
function executeNodeScript({ cwd, args }, data, source) {
return new Promise((resolve, reject) => {
const child = spawn(
process.execPath,
[...args, '-e', source, '--', JSON.stringify(data)],
{ cwd, stdio: 'inherit' }
);
child.on('close', code => {
if (code !== 0) {
reject({
command: `node ${args.join(' ')}`,
});
return;
}
resolve();
});
});
}
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
const path = require('path'); const path = require('path');
const webpack = require('webpack'); const webpack = require('webpack');
const PnpWebpackPlugin = require('pnp-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin'); const HtmlWebpackPlugin = require('html-webpack-plugin');
const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin'); const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin');
const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin'); const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin');
...@@ -150,6 +151,9 @@ module.exports = { ...@@ -150,6 +151,9 @@ module.exports = {
'react-native': 'react-native-web', 'react-native': 'react-native-web',
}, },
plugins: [ plugins: [
// Adds support for installing with Plug'n'Play, leading to faster installs and adding
// guards against forgotten dependencies and such.
PnpWebpackPlugin,
// Prevents users from importing files from outside of src/ (or node_modules/). // 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. // 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, // To fix this, we prevent you from importing files out of src/ -- if you'd like to,
...@@ -158,6 +162,13 @@ module.exports = { ...@@ -158,6 +162,13 @@ module.exports = {
new ModuleScopePlugin(paths.appSrc, [paths.appPackageJson]), new ModuleScopePlugin(paths.appSrc, [paths.appPackageJson]),
], ],
}, },
resolveLoader: {
plugins: [
// Also related to Plug'n'Play, but this time it tells Webpack to load its loaders
// from the current package.
PnpWebpackPlugin.moduleLoader(module),
],
},
module: { module: {
strictExportPresence: true, strictExportPresence: true,
rules: [ rules: [
......
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
const path = require('path'); const path = require('path');
const webpack = require('webpack'); const webpack = require('webpack');
const PnpWebpackPlugin = require('pnp-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin'); const HtmlWebpackPlugin = require('html-webpack-plugin');
const InlineChunkHtmlPlugin = require('react-dev-utils/InlineChunkHtmlPlugin'); const InlineChunkHtmlPlugin = require('react-dev-utils/InlineChunkHtmlPlugin');
const TerserPlugin = require('terser-webpack-plugin'); const TerserPlugin = require('terser-webpack-plugin');
...@@ -214,6 +215,9 @@ module.exports = { ...@@ -214,6 +215,9 @@ module.exports = {
'react-native': 'react-native-web', 'react-native': 'react-native-web',
}, },
plugins: [ plugins: [
// Adds support for installing with Plug'n'Play, leading to faster installs and adding
// guards against forgotten dependencies and such.
PnpWebpackPlugin,
// Prevents users from importing files from outside of src/ (or node_modules/). // 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. // 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, // To fix this, we prevent you from importing files out of src/ -- if you'd like to,
...@@ -222,6 +226,13 @@ module.exports = { ...@@ -222,6 +226,13 @@ module.exports = {
new ModuleScopePlugin(paths.appSrc, [paths.appPackageJson]), new ModuleScopePlugin(paths.appSrc, [paths.appPackageJson]),
], ],
}, },
resolveLoader: {
plugins: [
// Also related to Plug'n'Play, but this time it tells Webpack to load its loaders
// from the current package.
PnpWebpackPlugin.moduleLoader(module),
],
},
module: { module: {
strictExportPresence: true, strictExportPresence: true,
rules: [ rules: [
......
...@@ -47,8 +47,11 @@ ...@@ -47,8 +47,11 @@
"html-webpack-plugin": "4.0.0-alpha.2", "html-webpack-plugin": "4.0.0-alpha.2",
"identity-obj-proxy": "3.0.0", "identity-obj-proxy": "3.0.0",
"jest": "23.6.0", "jest": "23.6.0",
"jest-pnp-resolver": "1.0.1",
"jest-resolve": "23.6.0",
"mini-css-extract-plugin": "0.4.3", "mini-css-extract-plugin": "0.4.3",
"optimize-css-assets-webpack-plugin": "5.0.1", "optimize-css-assets-webpack-plugin": "5.0.1",
"pnp-webpack-plugin": "1.1.0",
"postcss-flexbugs-fixes": "4.1.0", "postcss-flexbugs-fixes": "4.1.0",
"postcss-loader": "3.0.0", "postcss-loader": "3.0.0",
"postcss-preset-env": "6.0.6", "postcss-preset-env": "6.0.6",
......
...@@ -81,9 +81,9 @@ module.exports = function( ...@@ -81,9 +81,9 @@ module.exports = function(
originalDirectory, originalDirectory,
template template
) { ) {
const ownPackageName = require(path.join(__dirname, '..', 'package.json')) const ownPath = path.dirname(
.name; require.resolve(path.join(__dirname, '..', 'package.json'))
const ownPath = path.join(appPath, 'node_modules', ownPackageName); );
const appPackage = require(path.join(appPath, 'package.json')); const appPackage = require(path.join(appPath, 'package.json'));
const useYarn = fs.existsSync(path.join(appPath, 'yarn.lock')); const useYarn = fs.existsSync(path.join(appPath, 'yarn.lock'));
......
...@@ -22,7 +22,8 @@ module.exports = (resolve, rootDir, isEjecting) => { ...@@ -22,7 +22,8 @@ module.exports = (resolve, rootDir, isEjecting) => {
// in Jest configs. We need help from somebody with Windows to determine this. // in Jest configs. We need help from somebody with Windows to determine this.
const config = { const config = {
collectCoverageFrom: ['src/**/*.{js,jsx}'], collectCoverageFrom: ['src/**/*.{js,jsx}'],
setupFiles: ['react-app-polyfill/jsdom'], resolver: require.resolve('jest-pnp-resolver'),
setupFiles: [require.resolve('react-app-polyfill/jsdom')],
setupTestFrameworkScriptFile: setupTestsFile, setupTestFrameworkScriptFile: setupTestsFile,
testMatch: [ testMatch: [
'<rootDir>/src/**/__tests__/**/*.{js,jsx}', '<rootDir>/src/**/__tests__/**/*.{js,jsx}',
......
...@@ -229,5 +229,16 @@ npx create-react-app test-app-nested-paths-t3/aa/bb/cc/dd ...@@ -229,5 +229,16 @@ npx create-react-app test-app-nested-paths-t3/aa/bb/cc/dd
cd test-app-nested-paths-t3/aa/bb/cc/dd cd test-app-nested-paths-t3/aa/bb/cc/dd
yarn start --smoke-test yarn start --smoke-test
# ******************************************************************************
# Test when PnP is enabled
# ******************************************************************************
cd "$temp_app_path"
npx create-react-app test-app-pnp --use-pnp
cd test-app-pnp
! exists node_modules
exists .pnp.js
yarn start --smoke-test
yarn build
# Cleanup # Cleanup
cleanup cleanup
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