mirror of
https://github.com/zhigang1992/create-react-app.git
synced 2026-04-24 05:05:53 +08:00
Polish webpack message output (#5174)
* Only install react-scripts in CI mode
* Link locally
* Re-enable all output tests
* 💄 Polish webpack output
* Test sass support message
* Add more tests, but disabled
* Format missing default export error
* Format aliased import
* Why was node-sass required? Odd
* Format webpack rejection error
* Re-enable unknown package test
* Format file not found error and catch module scope plugin error
* Re-disable case sensitive paths
* Intercept and format case sensitive path errors
* Test out of scope message formatting
* Run behavior on macOS
* Run behavior on Node 8 and 10, only Node 8 for macOS
* Add some debugging
* Update matcher
* Only check stderr
* Remove old snapshot
* More debug
* Remove debug
* Add new debug
* Disable test on linux
* Add comment for future
This commit is contained in:
@@ -28,5 +28,8 @@ env:
|
||||
- TEST_SUITE=behavior
|
||||
matrix:
|
||||
include:
|
||||
- os: osx
|
||||
node_js: 8
|
||||
env: TEST_SUITE=behavior
|
||||
- node_js: 4
|
||||
env: TEST_SUITE=old-node
|
||||
|
||||
@@ -1,12 +1,26 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`webpack message formatting formats aliased unknown export 1`] = `
|
||||
Object {
|
||||
"stderr": "Creating an optimized production build...
|
||||
Failed to compile.
|
||||
|
||||
./src/App.js
|
||||
Attempted import error: 'bar' is not exported from './AppUnknownExport' (imported as 'bar2').
|
||||
|
||||
|
||||
",
|
||||
"stdout": "",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`webpack message formatting formats babel syntax error 1`] = `
|
||||
Object {
|
||||
"stderr": "Creating an optimized production build...
|
||||
Failed to compile.
|
||||
|
||||
./src/App.js
|
||||
Syntax error: Unterminated JSX contents (8:12)
|
||||
Syntax error: Unterminated JSX contents (8:13)
|
||||
|
||||
6 | <div>
|
||||
7 | <span>
|
||||
@@ -25,16 +39,16 @@ Syntax error: Unterminated JSX contents (8:12)
|
||||
exports[`webpack message formatting formats css syntax error 1`] = `
|
||||
Object {
|
||||
"stderr": "Creating an optimized production build...
|
||||
[31mFailed to compile.
|
||||
[39m
|
||||
[7m./src/AppCss.css[27m
|
||||
Syntax Error: (3:2) Unexpected }
|
||||
Failed to compile.
|
||||
|
||||
[90m 1 | [39m[33m.App[39m [33m{[39m
|
||||
[90m 2 | [39m color[33m:[39m red[33m;[39m
|
||||
[31m[1m>[22m[39m[90m 3 | [39m[33m}[39m[33m}[39m
|
||||
[90m | [39m [31m[1m^[22m[39m
|
||||
[90m 4 | [39m
|
||||
./src/AppCss.css
|
||||
Syntax error: Unexpected } (3:2)
|
||||
|
||||
1 | .App {
|
||||
2 | color: red;
|
||||
> 3 | }}
|
||||
| ^
|
||||
4 |
|
||||
|
||||
|
||||
",
|
||||
@@ -74,12 +88,58 @@ To ignore, add // eslint-disable-next-line to the line before.
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`webpack message formatting formats file not found error 1`] = `
|
||||
Object {
|
||||
"stderr": "Creating an optimized production build...
|
||||
Failed to compile.
|
||||
|
||||
./src/App.js
|
||||
Cannot find file './ThisFileSouldNotExist' in './src'.
|
||||
|
||||
|
||||
",
|
||||
"stdout": "",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`webpack message formatting formats missing package 1`] = `
|
||||
Object {
|
||||
"stderr": "Creating an optimized production build...
|
||||
[31mFailed to compile.
|
||||
[39m
|
||||
Module not found: Error: Can't resolve 'unknown-package' in '/private/var/folders/c3/vytj6_h56b77f_g72smntm3m0000gn/T/bf26e1d3700ad14275f6eefb5e4417c1/src'
|
||||
Failed to compile.
|
||||
|
||||
./src/App.js
|
||||
Cannot find module: 'unknown-package'. Make sure this package is installed.
|
||||
|
||||
You can install this package by running: yarn add unknown-package.
|
||||
|
||||
|
||||
",
|
||||
"stdout": "",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`webpack message formatting formats no default export 1`] = `
|
||||
Object {
|
||||
"stderr": "Creating an optimized production build...
|
||||
Failed to compile.
|
||||
|
||||
./src/App.js
|
||||
Attempted import error: './ExportNoDefault' does not contain a default export (imported as 'myImport').
|
||||
|
||||
|
||||
",
|
||||
"stdout": "",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`webpack message formatting formats out of scope error 1`] = `
|
||||
Object {
|
||||
"stderr": "Creating an optimized production build...
|
||||
Failed to compile.
|
||||
|
||||
./src/App.js
|
||||
You attempted to import ../OutOfScopeImport which falls outside of the project src/ directory. Relative imports outside of src/ are not supported.
|
||||
You can either move it inside src/, or add a symlink to it from project's node_modules/.
|
||||
|
||||
|
||||
",
|
||||
@@ -93,7 +153,22 @@ Object {
|
||||
Failed to compile.
|
||||
|
||||
./src/App.js
|
||||
1:1677-1680 './AppUnknownExport' does not contain an export named 'bar'.
|
||||
Attempted import error: 'bar' is not exported from './AppUnknownExport'.
|
||||
|
||||
|
||||
",
|
||||
"stdout": "",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`webpack message formatting helps when users tries to use sass 1`] = `
|
||||
Object {
|
||||
"stderr": "Creating an optimized production build...
|
||||
Failed to compile.
|
||||
|
||||
./src/AppSass.scss
|
||||
To import Sass files, you first need to install node-sass.
|
||||
Run \`npm install node-sass\` or \`yarn add node-sass\` inside your workspace.
|
||||
|
||||
|
||||
",
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
const { bootstrap, getOutputProduction } = require('../../utils');
|
||||
const {
|
||||
bootstrap,
|
||||
getOutputDevelopment,
|
||||
getOutputProduction,
|
||||
} = require('../../utils');
|
||||
const fs = require('fs-extra');
|
||||
const path = require('path');
|
||||
const Semaphore = require('async-sema');
|
||||
@@ -19,7 +23,7 @@ describe('webpack message formatting', () => {
|
||||
semaphore.release();
|
||||
});
|
||||
|
||||
xit('formats babel syntax error', async () => {
|
||||
it('formats babel syntax error', async () => {
|
||||
fs.copySync(
|
||||
path.join(__dirname, 'src', 'AppBabel.js'),
|
||||
path.join(testDirectory, 'src', 'App.js')
|
||||
@@ -29,8 +33,7 @@ describe('webpack message formatting', () => {
|
||||
expect(response).toMatchSnapshot();
|
||||
});
|
||||
|
||||
xit('formats css syntax error', async () => {
|
||||
// TODO: fix me!
|
||||
it('formats css syntax error', async () => {
|
||||
fs.copySync(
|
||||
path.join(__dirname, 'src', 'AppCss.js'),
|
||||
path.join(testDirectory, 'src', 'App.js')
|
||||
@@ -40,8 +43,7 @@ describe('webpack message formatting', () => {
|
||||
expect(response).toMatchSnapshot();
|
||||
});
|
||||
|
||||
xit('formats unknown export', async () => {
|
||||
// TODO: fix me!
|
||||
it('formats unknown export', async () => {
|
||||
fs.copySync(
|
||||
path.join(__dirname, 'src', 'AppUnknownExport.js'),
|
||||
path.join(testDirectory, 'src', 'App.js')
|
||||
@@ -51,8 +53,27 @@ describe('webpack message formatting', () => {
|
||||
expect(response).toMatchSnapshot();
|
||||
});
|
||||
|
||||
xit('formats missing package', async () => {
|
||||
// TODO: fix me!
|
||||
it('formats aliased unknown export', async () => {
|
||||
fs.copySync(
|
||||
path.join(__dirname, 'src', 'AppAliasUnknownExport.js'),
|
||||
path.join(testDirectory, 'src', 'App.js')
|
||||
);
|
||||
|
||||
const response = await getOutputProduction({ directory: testDirectory });
|
||||
expect(response).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('formats no default export', async () => {
|
||||
fs.copySync(
|
||||
path.join(__dirname, 'src', 'AppNoDefault.js'),
|
||||
path.join(testDirectory, 'src', 'App.js')
|
||||
);
|
||||
|
||||
const response = await getOutputProduction({ directory: testDirectory });
|
||||
expect(response).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('formats missing package', async () => {
|
||||
fs.copySync(
|
||||
path.join(__dirname, 'src', 'AppMissingPackage.js'),
|
||||
path.join(testDirectory, 'src', 'App.js')
|
||||
@@ -85,4 +106,52 @@ describe('webpack message formatting', () => {
|
||||
const response = await getOutputProduction({ directory: testDirectory });
|
||||
expect(response).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('helps when users tries to use sass', async () => {
|
||||
fs.copySync(
|
||||
path.join(__dirname, 'src', 'AppSass.js'),
|
||||
path.join(testDirectory, 'src', 'App.js')
|
||||
);
|
||||
|
||||
const response = await getOutputProduction({ directory: testDirectory });
|
||||
expect(response).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('formats file not found error', async () => {
|
||||
fs.copySync(
|
||||
path.join(__dirname, 'src', 'AppUnknownFile.js'),
|
||||
path.join(testDirectory, 'src', 'App.js')
|
||||
);
|
||||
|
||||
const response = await getOutputProduction({ directory: testDirectory });
|
||||
expect(response).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('formats case sensitive path error', async () => {
|
||||
fs.copySync(
|
||||
path.join(__dirname, 'src', 'AppIncorrectCase.js'),
|
||||
path.join(testDirectory, 'src', 'App.js')
|
||||
);
|
||||
|
||||
const response = await getOutputDevelopment({ directory: testDirectory });
|
||||
if (process.platform === 'darwin') {
|
||||
expect(response.stderr).toMatch(
|
||||
`Cannot find file: 'export5.js' does not match the corresponding name on disk: './src/Export5.js'.`
|
||||
);
|
||||
} else {
|
||||
expect(response.stderr).not.toEqual(''); // TODO: figure out how we can test this on Linux/Windows
|
||||
// I believe getting this working requires we tap into enhanced-resolve
|
||||
// pipeline, which is debt we don't want to take on right now.
|
||||
}
|
||||
});
|
||||
|
||||
it('formats out of scope error', async () => {
|
||||
fs.copySync(
|
||||
path.join(__dirname, 'src', 'AppOutOfScopeImport.js'),
|
||||
path.join(testDirectory, 'src', 'App.js')
|
||||
);
|
||||
|
||||
const response = await getOutputProduction({ directory: testDirectory });
|
||||
expect(response).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"node-sass": "4.x",
|
||||
"react": "latest",
|
||||
"react-dom": "latest",
|
||||
"react-scripts": "latest"
|
||||
"react-dom": "latest"
|
||||
},
|
||||
"browserslist": [
|
||||
">0.2%"
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import React, { Component } from 'react';
|
||||
import { bar as bar2 } from './AppUnknownExport';
|
||||
|
||||
class App extends Component {
|
||||
componentDidMount() {
|
||||
bar2();
|
||||
}
|
||||
render() {
|
||||
return <div />;
|
||||
}
|
||||
}
|
||||
|
||||
export default App;
|
||||
@@ -0,0 +1,10 @@
|
||||
import React, { Component } from 'react';
|
||||
import five from './export5';
|
||||
|
||||
class App extends Component {
|
||||
render() {
|
||||
return <div className="App">{five}</div>;
|
||||
}
|
||||
}
|
||||
|
||||
export default App;
|
||||
@@ -0,0 +1,10 @@
|
||||
import React, { Component } from 'react';
|
||||
import myImport from './ExportNoDefault';
|
||||
|
||||
class App extends Component {
|
||||
render() {
|
||||
return <div className="App">{myImport}</div>;
|
||||
}
|
||||
}
|
||||
|
||||
export default App;
|
||||
@@ -0,0 +1,10 @@
|
||||
import React, { Component } from 'react';
|
||||
import myImport from '../OutOfScopeImport';
|
||||
|
||||
class App extends Component {
|
||||
render() {
|
||||
return <div className="App">{myImport}</div>;
|
||||
}
|
||||
}
|
||||
|
||||
export default App;
|
||||
10
fixtures/output/webpack-message-formatting/src/AppSass.js
Normal file
10
fixtures/output/webpack-message-formatting/src/AppSass.js
Normal file
@@ -0,0 +1,10 @@
|
||||
import React, { Component } from 'react';
|
||||
import './AppSass.scss';
|
||||
|
||||
class App extends Component {
|
||||
render() {
|
||||
return <div className="App" />;
|
||||
}
|
||||
}
|
||||
|
||||
export default App;
|
||||
@@ -0,0 +1,3 @@
|
||||
.App {
|
||||
color: red;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import React, { Component } from 'react';
|
||||
import DefaultExport from './ThisFileSouldNotExist';
|
||||
|
||||
class App extends Component {
|
||||
render() {
|
||||
return <div className="App" />;
|
||||
}
|
||||
}
|
||||
|
||||
export default App;
|
||||
@@ -0,0 +1 @@
|
||||
export default 5;
|
||||
@@ -0,0 +1 @@
|
||||
export const six = 6;
|
||||
@@ -3,7 +3,6 @@
|
||||
"bootstrap": "4.x",
|
||||
"node-sass": "4.x",
|
||||
"react": "latest",
|
||||
"react-dom": "latest",
|
||||
"react-scripts": "latest"
|
||||
"react-dom": "latest"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
"dva": "2.4.0",
|
||||
"ky": "0.3.0",
|
||||
"react": "latest",
|
||||
"react-dom": "latest",
|
||||
"react-scripts": "latest"
|
||||
"react-dom": "latest"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
"graphql": "14.0.2",
|
||||
"react-apollo": "2.2.1",
|
||||
"react": "latest",
|
||||
"react-dom": "latest",
|
||||
"react-scripts": "latest"
|
||||
"react-dom": "latest"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"react-scripts": "latest"
|
||||
},
|
||||
"dependencies": {},
|
||||
"homepage": "."
|
||||
}
|
||||
|
||||
@@ -6,12 +6,36 @@ const os = require('os');
|
||||
const stripAnsi = require('strip-ansi');
|
||||
|
||||
async function bootstrap({ directory, template }) {
|
||||
const shouldInstallScripts = process.env.CI && process.env.CI !== 'false';
|
||||
await Promise.all(
|
||||
['public/', 'src/', 'package.json'].map(async file =>
|
||||
fs.copy(path.join(template, file), path.join(directory, file))
|
||||
)
|
||||
);
|
||||
if (shouldInstallScripts) {
|
||||
const packageJson = fs.readJsonSync(path.join(directory, 'package.json'));
|
||||
packageJson.dependencies = Object.assign(packageJson.dependencies, {
|
||||
'react-scripts': 'latest',
|
||||
});
|
||||
fs.writeJsonSync(path.join(directory, 'package.json'), packageJson);
|
||||
}
|
||||
await execa('yarnpkg', ['install', '--mutex', 'network'], { cwd: directory });
|
||||
if (!shouldInstallScripts) {
|
||||
fs.ensureSymlinkSync(
|
||||
path.resolve(
|
||||
path.join(
|
||||
__dirname,
|
||||
'..',
|
||||
'packages',
|
||||
'react-scripts',
|
||||
'bin',
|
||||
'react-scripts.js'
|
||||
)
|
||||
),
|
||||
path.join(directory, 'node_modules', '.bin', 'react-scripts')
|
||||
);
|
||||
await execa('yarnpkg', ['link', 'react-scripts'], { cwd: directory });
|
||||
}
|
||||
}
|
||||
|
||||
async function isSuccessfulDevelopment({ directory }) {
|
||||
@@ -43,6 +67,39 @@ async function isSuccessfulProduction({ directory }) {
|
||||
}
|
||||
}
|
||||
|
||||
async function getOutputDevelopment({ directory, env = {} }) {
|
||||
try {
|
||||
const { stdout, stderr } = await execa(
|
||||
'./node_modules/.bin/react-scripts',
|
||||
['start', '--smoke-test'],
|
||||
{
|
||||
cwd: directory,
|
||||
env: Object.assign(
|
||||
{},
|
||||
{
|
||||
BROWSER: 'none',
|
||||
PORT: await getPort(),
|
||||
CI: 'false',
|
||||
FORCE_COLOR: '0',
|
||||
},
|
||||
env
|
||||
),
|
||||
}
|
||||
);
|
||||
return { stdout: stripAnsi(stdout), stderr: stripAnsi(stderr) };
|
||||
} catch (err) {
|
||||
return {
|
||||
stdout: '',
|
||||
stderr: stripAnsi(
|
||||
err.message
|
||||
.split(os.EOL)
|
||||
.slice(2)
|
||||
.join(os.EOL)
|
||||
),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async function getOutputProduction({ directory, env = {} }) {
|
||||
try {
|
||||
const { stdout, stderr } = await execa(
|
||||
@@ -71,5 +128,6 @@ module.exports = {
|
||||
bootstrap,
|
||||
isSuccessfulDevelopment,
|
||||
isSuccessfulProduction,
|
||||
getOutputDevelopment,
|
||||
getOutputProduction,
|
||||
};
|
||||
|
||||
146
packages/react-dev-utils/ModuleNotFoundPlugin.js
vendored
Normal file
146
packages/react-dev-utils/ModuleNotFoundPlugin.js
vendored
Normal file
@@ -0,0 +1,146 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const chalk = require('chalk');
|
||||
const findUp = require('find-up');
|
||||
const path = require('path');
|
||||
|
||||
class ModuleNotFoundPlugin {
|
||||
constructor(appPath, yarnLockFile) {
|
||||
this.appPath = appPath;
|
||||
this.yarnLockFile = yarnLockFile;
|
||||
|
||||
this.useYarnCommand = this.useYarnCommand.bind(this);
|
||||
this.getRelativePath = this.getRelativePath.bind(this);
|
||||
this.prettierError = this.prettierError.bind(this);
|
||||
}
|
||||
|
||||
useYarnCommand() {
|
||||
try {
|
||||
return findUp.sync('yarn.lock', { cwd: this.appPath }) != null;
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
getRelativePath(_file) {
|
||||
let file = path.relative(this.appPath, _file);
|
||||
if (file.startsWith('..')) {
|
||||
file = _file;
|
||||
} else if (!file.startsWith('.')) {
|
||||
file = '.' + path.sep + file;
|
||||
}
|
||||
return file;
|
||||
}
|
||||
|
||||
prettierError(err) {
|
||||
let { details: _details = '', origin } = err;
|
||||
|
||||
if (origin == null) {
|
||||
const caseSensitivity =
|
||||
err.message &&
|
||||
/\[CaseSensitivePathsPlugin\] `(.*?)` .* `(.*?)`/.exec(err.message);
|
||||
if (caseSensitivity) {
|
||||
const [, incorrectPath, actualName] = caseSensitivity;
|
||||
const actualFile = this.getRelativePath(
|
||||
path.join(path.dirname(incorrectPath), actualName)
|
||||
);
|
||||
const incorrectName = path.basename(incorrectPath);
|
||||
err.message = `Cannot find file: '${incorrectName}' does not match the corresponding name on disk: '${actualFile}'.`;
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
const file = this.getRelativePath(origin.resource);
|
||||
let details = _details.split('\n');
|
||||
|
||||
const request = /resolve '(.*?)' in '(.*?)'/.exec(details);
|
||||
if (request) {
|
||||
const isModule = details[1] && details[1].includes('module');
|
||||
const isFile = details[1] && details[1].includes('file');
|
||||
|
||||
let [, target, context] = request;
|
||||
context = this.getRelativePath(context);
|
||||
if (isModule) {
|
||||
const isYarn = this.useYarnCommand();
|
||||
details = [
|
||||
`Cannot find module: '${target}'. Make sure this package is installed.`,
|
||||
'',
|
||||
'You can install this package by running: ' +
|
||||
(isYarn
|
||||
? chalk.bold(`yarn add ${target}`)
|
||||
: chalk.bold(`npm install ${target}`)) +
|
||||
'.',
|
||||
];
|
||||
} else if (isFile) {
|
||||
details = [`Cannot find file '${target}' in '${context}'.`];
|
||||
} else {
|
||||
details = [err.message];
|
||||
}
|
||||
} else {
|
||||
details = [err.message];
|
||||
}
|
||||
err.message = [file, ...details].join('\n').replace('Error: ', '');
|
||||
|
||||
const isModuleScopePluginError =
|
||||
err.error && err.error.__module_scope_plugin;
|
||||
if (isModuleScopePluginError) {
|
||||
err.message = err.message.replace('Module not found: ', '');
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
apply(compiler) {
|
||||
const { prettierError } = this;
|
||||
compiler.hooks.make.intercept({
|
||||
register(tap) {
|
||||
if (
|
||||
!(tap.name === 'MultiEntryPlugin' || tap.name === 'SingleEntryPlugin')
|
||||
) {
|
||||
return tap;
|
||||
}
|
||||
return Object.assign({}, tap, {
|
||||
fn: (compilation, callback) => {
|
||||
tap.fn(compilation, (err, ...args) => {
|
||||
if (err && err.name === 'ModuleNotFoundError') {
|
||||
err = prettierError(err);
|
||||
}
|
||||
callback(err, ...args);
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
compiler.hooks.normalModuleFactory.tap('ModuleNotFoundPlugin', nmf => {
|
||||
nmf.hooks.afterResolve.intercept({
|
||||
register(tap) {
|
||||
if (tap.name !== 'CaseSensitivePathsPlugin') {
|
||||
return tap;
|
||||
}
|
||||
return Object.assign({}, tap, {
|
||||
fn: (compilation, callback) => {
|
||||
tap.fn(compilation, (err, ...args) => {
|
||||
if (
|
||||
err &&
|
||||
err.message &&
|
||||
err.message.includes('CaseSensitivePathsPlugin')
|
||||
) {
|
||||
err = prettierError(err);
|
||||
}
|
||||
callback(err, ...args);
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ModuleNotFoundPlugin;
|
||||
37
packages/react-dev-utils/ModuleScopePlugin.js
vendored
37
packages/react-dev-utils/ModuleScopePlugin.js
vendored
@@ -9,6 +9,7 @@
|
||||
|
||||
const chalk = require('chalk');
|
||||
const path = require('path');
|
||||
const os = require('os');
|
||||
|
||||
class ModuleScopePlugin {
|
||||
constructor(appSrc, allowedFiles = []) {
|
||||
@@ -63,24 +64,28 @@ class ModuleScopePlugin {
|
||||
);
|
||||
})
|
||||
) {
|
||||
callback(
|
||||
new Error(
|
||||
`You attempted to import ${chalk.cyan(
|
||||
request.__innerRequest_request
|
||||
)} which falls outside of the project ${chalk.cyan(
|
||||
const scopeError = 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/'
|
||||
)} 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
|
||||
)} are not supported.` +
|
||||
os.EOL +
|
||||
`You can either move it inside ${chalk.cyan(
|
||||
'src/'
|
||||
)}, or add a symlink to it from project's ${chalk.cyan(
|
||||
'node_modules/'
|
||||
)}.`
|
||||
);
|
||||
Object.defineProperty(scopeError, '__module_scope_plugin', {
|
||||
value: true,
|
||||
writable: false,
|
||||
enumerable: false,
|
||||
});
|
||||
callback(scopeError, request);
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
|
||||
3
packages/react-dev-utils/eslintFormatter.js
vendored
3
packages/react-dev-utils/eslintFormatter.js
vendored
@@ -42,6 +42,9 @@ function formatter(results) {
|
||||
}
|
||||
|
||||
let line = message.line || 0;
|
||||
if (message.column) {
|
||||
line += ':' + message.column;
|
||||
}
|
||||
let position = chalk.bold('Line ' + line + ':');
|
||||
return [
|
||||
'',
|
||||
|
||||
186
packages/react-dev-utils/formatWebpackMessages.js
vendored
186
packages/react-dev-utils/formatWebpackMessages.js
vendored
@@ -7,15 +7,8 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
// WARNING: this code is untranspiled and is used in browser too.
|
||||
// Please make sure any changes are in ES5 or contribute a Babel compile step.
|
||||
|
||||
// Some custom utilities to prettify Webpack output.
|
||||
// This is quite hacky and hopefully won't be needed when Webpack fixes this.
|
||||
// https://github.com/webpack/webpack/issues/2878
|
||||
|
||||
var chalk = require('chalk');
|
||||
var friendlySyntaxErrorLabel = 'Syntax error:';
|
||||
const chalk = require('chalk');
|
||||
const friendlySyntaxErrorLabel = 'Syntax error:';
|
||||
|
||||
function isLikelyASyntaxError(message) {
|
||||
return message.indexOf(friendlySyntaxErrorLabel) !== -1;
|
||||
@@ -24,159 +17,108 @@ function isLikelyASyntaxError(message) {
|
||||
// Cleans up webpack error messages.
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
function formatMessage(message, isError) {
|
||||
var lines = message.split('\n');
|
||||
let lines = message.split('\n');
|
||||
|
||||
// Strip `WorkerError` header off message before parsing
|
||||
// https://github.com/webpack-contrib/thread-loader/blob/6fb5daff313c4839196cf533bdcdf14815a386d2/src/WorkerError.js
|
||||
lines = lines.filter(function(message) {
|
||||
return message.indexOf('Thread Loader (Worker') === -1;
|
||||
// Strip Webpack-added headers off errors/warnings
|
||||
// https://github.com/webpack/webpack/blob/master/lib/ModuleError.js
|
||||
lines = lines.filter(line => !/Module [A-z ]+\(from/.test(line));
|
||||
|
||||
// Transform parsing error into syntax error
|
||||
// TODO: move this to our ESLint formatter?
|
||||
lines = lines.map(line => {
|
||||
const parsingError = /Line (\d+):(?:(\d+):)?\s*Parsing error: (.+)$/.exec(
|
||||
line
|
||||
);
|
||||
if (!parsingError) {
|
||||
return line;
|
||||
}
|
||||
const [, errorLine, errorColumn, errorMessage] = parsingError;
|
||||
return `${friendlySyntaxErrorLabel} ${errorMessage} (${errorLine}:${errorColumn})`;
|
||||
});
|
||||
|
||||
// Add empty line for errors from third-party webpack plugins
|
||||
if (lines.length < 2) {
|
||||
lines[1] = '';
|
||||
}
|
||||
message = lines.join('\n');
|
||||
// Smoosh syntax errors (commonly found in CSS)
|
||||
message = message.replace(
|
||||
/SyntaxError\s+\((\d+):(\d+)\)\s*(.+?)\n/g,
|
||||
`${friendlySyntaxErrorLabel} $3 ($1:$2)\n`
|
||||
);
|
||||
// Remove columns from ESLint formatter output (we added these for more
|
||||
// accurate syntax errors)
|
||||
message = message.replace(/Line (\d+):\d+:/g, 'Line $1:');
|
||||
// Clean up export errors
|
||||
message = message.replace(
|
||||
/^.*export '(.+?)' was not found in '(.+?)'.*$/gm,
|
||||
`Attempted import error: '$1' is not exported from '$2'.`
|
||||
);
|
||||
message = message.replace(
|
||||
/^.*export 'default' \(imported as '(.+?)'\) was not found in '(.+?)'.*$/gm,
|
||||
`Attempted import error: '$2' does not contain a default export (imported as '$1').`
|
||||
);
|
||||
message = message.replace(
|
||||
/^.*export '(.+?)' \(imported as '(.+?)'\) was not found in '(.+?)'.*$/gm,
|
||||
`Attempted import error: '$1' is not exported from '$3' (imported as '$2').`
|
||||
);
|
||||
lines = message.split('\n');
|
||||
|
||||
// Strip `ModuleWarning` head off message before parsing (because of ESLint)
|
||||
// https://github.com/webpack/webpack/blob/c77030573de96b8293c69dd396492f8e2d46561e/lib/ModuleWarning.js
|
||||
var moduleWarningPrefix = 'Module Warning: ';
|
||||
if (lines[1].indexOf(moduleWarningPrefix) === 0) {
|
||||
lines[1] = lines[1].slice(moduleWarningPrefix.length);
|
||||
} else if (lines[1].match(/Module Warning \(from.*?\):/)) {
|
||||
// Remove leading newline
|
||||
if (lines.length > 2 && lines[1].trim() === '') {
|
||||
lines.splice(1, 1);
|
||||
}
|
||||
|
||||
// Strip `ModuleError` header off message before parsing
|
||||
// https://github.com/webpack/webpack/blob/c77030573de96b8293c69dd396492f8e2d46561e/lib/ModuleError.js
|
||||
var moduleErrorPrefix = 'Module Error: ';
|
||||
if (lines[1].indexOf(moduleErrorPrefix) === 0) {
|
||||
lines[1] = lines[1].slice(moduleErrorPrefix.length);
|
||||
} else if (lines[1].match(/Module Error \(from.*?\):/)) {
|
||||
lines.splice(1, 1);
|
||||
}
|
||||
|
||||
// Simplify `ModuleBuildError` before parsing (these may be nested so we use a while loop)
|
||||
// https://github.com/webpack/webpack/blob/c77030573de96b8293c69dd396492f8e2d46561e/lib/ModuleBuildError.js
|
||||
while (
|
||||
lines.length > 2 &&
|
||||
lines[1].match(/Module build failed \(from.*?\):/)
|
||||
) {
|
||||
lines.splice(1, 1);
|
||||
lines[1] = 'Module build failed: ' + lines[1];
|
||||
}
|
||||
|
||||
if (lines.length > 2 && lines[1] === '') {
|
||||
// Remove extra newline.
|
||||
lines.splice(1, 1);
|
||||
}
|
||||
|
||||
// Remove webpack-specific loader notation from filename.
|
||||
// Before:
|
||||
// ./~/css-loader!./~/postcss-loader!./src/App.css
|
||||
// After:
|
||||
// ./src/App.css
|
||||
if (lines[0].lastIndexOf('!') !== -1) {
|
||||
lines[0] = lines[0].substr(lines[0].lastIndexOf('!') + 1);
|
||||
}
|
||||
|
||||
lines = lines.filter(function(line) {
|
||||
// Webpack adds a list of entry points to warning messages:
|
||||
// @ ./src/index.js
|
||||
// @ multi react-scripts/~/react-dev-utils/webpackHotDevClient.js ...
|
||||
// It is misleading (and unrelated to the warnings) so we clean it up.
|
||||
// It is only useful for syntax errors but we have beautiful frames for them.
|
||||
return line.indexOf(' @ ') !== 0;
|
||||
});
|
||||
|
||||
// line #0 is filename
|
||||
// line #1 is the main error message
|
||||
if (!lines[0] || !lines[1]) {
|
||||
return lines.join('\n');
|
||||
}
|
||||
// Clean up file name
|
||||
lines[0] = lines[0].replace(/^(.*) \d+:\d+-\d+$/, '$1');
|
||||
|
||||
// Cleans up verbose "module not found" messages for files and packages.
|
||||
if (lines[1].indexOf('Module not found: ') === 0) {
|
||||
if (lines[1] && lines[1].indexOf('Module not found: ') === 0) {
|
||||
lines = [
|
||||
lines[0],
|
||||
// Clean up message because "Module not found: " is descriptive enough.
|
||||
lines[1]
|
||||
.replace("Cannot resolve 'file' or 'directory' ", '')
|
||||
.replace('Cannot resolve module ', '')
|
||||
.replace('Error: ', '')
|
||||
.replace('[CaseSensitivePathsPlugin] ', ''),
|
||||
.replace('Module not found: Cannot find file:', 'Cannot find file:'),
|
||||
];
|
||||
}
|
||||
|
||||
if (lines[1].match(/Cannot find module.+node-sass/)) {
|
||||
lines[1] =
|
||||
'To import Sass files in this project, you need to install node-sass.\n';
|
||||
// Add helpful message for users trying to use Sass for the first time
|
||||
if (lines[1] && lines[1].match(/Cannot find module.+node-sass/)) {
|
||||
lines[1] = 'To import Sass files, you first need to install node-sass.\n';
|
||||
lines[1] +=
|
||||
'Please run `npm i node-sass --save` or `yarn add node-sass` inside your workspace.';
|
||||
}
|
||||
|
||||
// Cleans up syntax error messages.
|
||||
if (lines[1].indexOf('Module build failed: ') === 0) {
|
||||
lines[1] = lines[1].replace(
|
||||
'Module build failed: Syntax Error ',
|
||||
friendlySyntaxErrorLabel
|
||||
);
|
||||
lines[1] = lines[1].replace(
|
||||
/Module build failed: .*?: /,
|
||||
friendlySyntaxErrorLabel + ' '
|
||||
);
|
||||
lines[1] = lines[1].trim();
|
||||
|
||||
if (lines[1] === friendlySyntaxErrorLabel && lines[2] === '') {
|
||||
lines.splice(2, 1);
|
||||
if (lines.length > 2) {
|
||||
lines[1] += ' ' + lines[2];
|
||||
lines.splice(2, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up export errors.
|
||||
// TODO: we should really send a PR to Webpack for this.
|
||||
var exportError = /\s*(.*?)\s*(?:")?export '(.+?)' was not found in '(.+?)'/;
|
||||
if (lines[1].match(exportError)) {
|
||||
lines[1] = lines[1].replace(
|
||||
exportError,
|
||||
"$1 '$3' does not contain an export named '$2'."
|
||||
);
|
||||
'Run `npm install node-sass` or `yarn add node-sass` inside your workspace.';
|
||||
}
|
||||
|
||||
lines[0] = chalk.inverse(lines[0]);
|
||||
|
||||
// Reassemble the message.
|
||||
message = lines.join('\n');
|
||||
// Internal stacks are generally useless so we strip them... with the
|
||||
// exception of stacks containing `webpack:` because they're normally
|
||||
// from user code generated by WebPack. For more information see
|
||||
// from user code generated by Webpack. For more information see
|
||||
// https://github.com/facebook/create-react-app/pull/1050
|
||||
message = message.replace(
|
||||
/^\s*at\s((?!webpack:).)*:\d+:\d+[\s)]*(\n|$)/gm,
|
||||
''
|
||||
); // at ... ...:x:y
|
||||
message = message.replace(/^\s*at\s<anonymous>(\n|$)/gm, ''); // at <anonymous>
|
||||
lines = message.split('\n');
|
||||
|
||||
// Remove duplicated newlines
|
||||
lines = lines.filter(
|
||||
(line, index, arr) =>
|
||||
index === 0 || line.trim() !== '' || line.trim() !== arr[index - 1].trim()
|
||||
);
|
||||
|
||||
// Reassemble the message
|
||||
message = lines.join('\n');
|
||||
return message.trim();
|
||||
}
|
||||
|
||||
function formatWebpackMessages(json) {
|
||||
var formattedErrors = json.errors.map(function(message) {
|
||||
const formattedErrors = json.errors.map(function(message) {
|
||||
return formatMessage(message, true);
|
||||
});
|
||||
var formattedWarnings = json.warnings.map(function(message) {
|
||||
const formattedWarnings = json.warnings.map(function(message) {
|
||||
return formatMessage(message, false);
|
||||
});
|
||||
var result = {
|
||||
errors: formattedErrors,
|
||||
warnings: formattedWarnings,
|
||||
};
|
||||
const result = { errors: formattedErrors, warnings: formattedWarnings };
|
||||
if (result.errors.some(isLikelyASyntaxError)) {
|
||||
// If there are any syntax errors, show just them.
|
||||
// This prevents a confusing ESLint parsing error
|
||||
// preceding a much more useful Babel syntax error.
|
||||
result.errors = result.errors.filter(isLikelyASyntaxError);
|
||||
}
|
||||
return result;
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
"InterpolateHtmlPlugin.js",
|
||||
"launchEditor.js",
|
||||
"launchEditorEndpoint.js",
|
||||
"ModuleNotFoundPlugin.js",
|
||||
"ModuleScopePlugin.js",
|
||||
"noopServiceWorkerMiddleware.js",
|
||||
"openBrowser.js",
|
||||
@@ -48,6 +49,7 @@
|
||||
"detect-port-alt": "1.1.6",
|
||||
"escape-string-regexp": "1.0.5",
|
||||
"filesize": "3.6.1",
|
||||
"find-up": "3.0.0",
|
||||
"global-modules": "1.0.0",
|
||||
"gzip-size": "5.0.0",
|
||||
"inquirer": "6.2.0",
|
||||
|
||||
@@ -20,6 +20,7 @@ const getClientEnvironment = require('./env');
|
||||
const paths = require('./paths');
|
||||
const ManifestPlugin = require('webpack-manifest-plugin');
|
||||
const getCacheIdentifier = require('react-dev-utils/getCacheIdentifier');
|
||||
const ModuleNotFoundPlugin = require('react-dev-utils/ModuleNotFoundPlugin');
|
||||
|
||||
// Webpack uses `publicPath` to determine where the app is being served from.
|
||||
// In development, we always serve from the root. This makes config easier.
|
||||
@@ -372,6 +373,9 @@ module.exports = {
|
||||
// <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
|
||||
// In development, this will be an empty string.
|
||||
new InterpolateHtmlPlugin(HtmlWebpackPlugin, env.raw),
|
||||
// This gives some necessary context to module not found errors, such as
|
||||
// the requesting resource.
|
||||
new ModuleNotFoundPlugin(paths.appPath),
|
||||
// Makes some environment variables available to the JS code, for example:
|
||||
// if (process.env.NODE_ENV === 'development') { ... }. See `./env.js`.
|
||||
new webpack.DefinePlugin(env.stringified),
|
||||
|
||||
@@ -24,6 +24,7 @@ const getCSSModuleLocalIdent = require('react-dev-utils/getCSSModuleLocalIdent')
|
||||
const paths = require('./paths');
|
||||
const getClientEnvironment = require('./env');
|
||||
const getCacheIdentifier = require('react-dev-utils/getCacheIdentifier');
|
||||
const ModuleNotFoundPlugin = require('react-dev-utils/ModuleNotFoundPlugin');
|
||||
|
||||
// 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.
|
||||
@@ -459,6 +460,9 @@ module.exports = {
|
||||
// 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(HtmlWebpackPlugin, env.raw),
|
||||
// This gives some necessary context to module not found errors, such as
|
||||
// the requesting resource.
|
||||
new ModuleNotFoundPlugin(paths.appPath),
|
||||
// Makes some environment variables available to the JS code, for example:
|
||||
// if (process.env.NODE_ENV === 'production') { ... }. See `./env.js`.
|
||||
// It is absolutely essential that NODE_ENV was set to production here.
|
||||
|
||||
16
packages/react-scripts/scripts/build.js
vendored
16
packages/react-scripts/scripts/build.js
vendored
@@ -141,12 +141,20 @@ function build(previousFileSizes) {
|
||||
let compiler = webpack(config);
|
||||
return new Promise((resolve, reject) => {
|
||||
compiler.run((err, stats) => {
|
||||
let messages;
|
||||
if (err) {
|
||||
return reject(err);
|
||||
if (!err.message) {
|
||||
return reject(err);
|
||||
}
|
||||
messages = formatWebpackMessages({
|
||||
errors: [err.message],
|
||||
warnings: [],
|
||||
});
|
||||
} else {
|
||||
messages = formatWebpackMessages(
|
||||
stats.toJson({ all: false, warnings: true, errors: true })
|
||||
);
|
||||
}
|
||||
const messages = formatWebpackMessages(
|
||||
stats.toJson({ all: false, warnings: true, errors: true })
|
||||
);
|
||||
if (messages.errors.length) {
|
||||
// Only keep the first error. Others are often indicative
|
||||
// of the same problem, but confuse the reader with noise.
|
||||
|
||||
@@ -96,10 +96,10 @@ git clean -df
|
||||
# ******************************************************************************
|
||||
|
||||
# Smoke tests
|
||||
./node_modules/.bin/jest --config fixtures/smoke/jest.config.js
|
||||
CI=true ./node_modules/.bin/jest --config fixtures/smoke/jest.config.js
|
||||
|
||||
# Output tests
|
||||
./node_modules/.bin/jest --config fixtures/output/jest.config.js
|
||||
CI=true ./node_modules/.bin/jest --config fixtures/output/jest.config.js
|
||||
|
||||
# Cleanup
|
||||
cleanup
|
||||
|
||||
Reference in New Issue
Block a user