diff --git a/fixtures/browser/issue-5234-direct-mjs-package/__snapshots__/index.test.js.snap b/fixtures/browser/issue-5234-direct-mjs-package/__snapshots__/index.test.js.snap
new file mode 100644
index 00000000000..d9822e4559f
--- /dev/null
+++ b/fixtures/browser/issue-5234-direct-mjs-package/__snapshots__/index.test.js.snap
@@ -0,0 +1,9 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`issue #5234 (mjs files are imported as static files) correctly bundles files in development 1`] = `
+Object {
+ "graphql": "undefined",
+ "parse": "function",
+ "test": "/static/media/Test.4478d87c.mjs",
+}
+`;
diff --git a/fixtures/browser/issue-5234-direct-mjs-package/index.test.js b/fixtures/browser/issue-5234-direct-mjs-package/index.test.js
new file mode 100644
index 00000000000..99bf4e95eab
--- /dev/null
+++ b/fixtures/browser/issue-5234-direct-mjs-package/index.test.js
@@ -0,0 +1,29 @@
+const { bootstrap, startDevelopmentServer } = require('../../utils');
+const puppeteer = require('puppeteer');
+
+beforeEach(async () => {
+ await bootstrap({ directory: global.testDirectory, template: __dirname });
+ global.appPort = await startDevelopmentServer({
+ directory: global.testDirectory,
+ });
+ await new Promise(resolve => setTimeout(resolve, 3000));
+});
+
+// https://github.com/facebook/create-react-app/issues/5234
+describe('issue #5234 (mjs files are imported as static files)', () => {
+ it('correctly bundles files in development', async () => {
+ const browser = await puppeteer.launch({ headless: true });
+ try {
+ const page = await browser.newPage();
+ console.log(`http://localhost:${global.appPort}/`);
+ await page.goto(`http://localhost:${global.appPort}/`);
+ await page.waitForSelector('.App-Ready');
+ const output = await page.evaluate(() => {
+ return document.testData;
+ });
+ expect(output).toMatchSnapshot();
+ } finally {
+ browser.close();
+ }
+ });
+});
diff --git a/fixtures/browser/issue-5234-direct-mjs-package/package.json b/fixtures/browser/issue-5234-direct-mjs-package/package.json
new file mode 100644
index 00000000000..94aa62aa03d
--- /dev/null
+++ b/fixtures/browser/issue-5234-direct-mjs-package/package.json
@@ -0,0 +1,7 @@
+{
+ "dependencies": {
+ "react": "^16.5.2",
+ "react-dom": "^16.5.2",
+ "graphql": "14.0.2"
+ }
+}
diff --git a/fixtures/browser/issue-5234-direct-mjs-package/public/index.html b/fixtures/browser/issue-5234-direct-mjs-package/public/index.html
new file mode 100644
index 00000000000..86010b24067
--- /dev/null
+++ b/fixtures/browser/issue-5234-direct-mjs-package/public/index.html
@@ -0,0 +1,9 @@
+
+
+
+ React App
+
+
+
+
+
diff --git a/fixtures/browser/issue-5234-direct-mjs-package/src/Test.mjs b/fixtures/browser/issue-5234-direct-mjs-package/src/Test.mjs
new file mode 100644
index 00000000000..be3d611be2a
--- /dev/null
+++ b/fixtures/browser/issue-5234-direct-mjs-package/src/Test.mjs
@@ -0,0 +1,6 @@
+export function foo() {
+ console.log('fooey');
+}
+export default function bar() {
+ console.log('barrio');
+}
diff --git a/fixtures/browser/issue-5234-direct-mjs-package/src/index.html b/fixtures/browser/issue-5234-direct-mjs-package/src/index.html
new file mode 100644
index 00000000000..86010b24067
--- /dev/null
+++ b/fixtures/browser/issue-5234-direct-mjs-package/src/index.html
@@ -0,0 +1,9 @@
+
+
+
+ React App
+
+
+
+
+
diff --git a/fixtures/browser/issue-5234-direct-mjs-package/src/index.js b/fixtures/browser/issue-5234-direct-mjs-package/src/index.js
new file mode 100644
index 00000000000..1ae2016f67a
--- /dev/null
+++ b/fixtures/browser/issue-5234-direct-mjs-package/src/index.js
@@ -0,0 +1,17 @@
+import React from 'react';
+import ReactDOM from 'react-dom';
+import graphql, { parse } from 'graphql';
+import test from './Test.mjs';
+
+class App extends React.Component {
+ state = { ready: false };
+ async componentDidMount() {
+ document.testData = { graphql: typeof graphql, parse: typeof parse, test };
+ this.setState({ ready: true });
+ }
+ render() {
+ return this.state.ready ? : null;
+ }
+}
+
+ReactDOM.render(, document.getElementById('root'));
diff --git a/fixtures/browser/jest.config.js b/fixtures/browser/jest.config.js
new file mode 100644
index 00000000000..b2f8182ebd9
--- /dev/null
+++ b/fixtures/browser/jest.config.js
@@ -0,0 +1,6 @@
+module.exports = {
+ testEnvironment: 'node',
+ testMatch: ['**/*.test.js'],
+ testPathIgnorePatterns: ['/src/', 'node_modules'],
+ setupTestFrameworkScriptFile: './setupSmokeTests.js',
+};
diff --git a/fixtures/browser/setupSmokeTests.js b/fixtures/browser/setupSmokeTests.js
new file mode 100644
index 00000000000..1d4038de417
--- /dev/null
+++ b/fixtures/browser/setupSmokeTests.js
@@ -0,0 +1,10 @@
+const fs = require('fs-extra');
+const tempy = require('tempy');
+
+beforeEach(() => {
+ global.testDirectory = tempy.directory();
+ jest.setTimeout(1000 * 60 * 5);
+});
+afterEach(() => {
+ fs.removeSync(global.testDirectory);
+});
diff --git a/fixtures/utils.js b/fixtures/utils.js
index bde092f6e3e..08a42535c32 100644
--- a/fixtures/utils.js
+++ b/fixtures/utils.js
@@ -111,6 +111,24 @@ async function getOutputDevelopment({ directory, env = {} }) {
}
}
+async function startDevelopmentServer({ directory, env = {} }) {
+ const port = await getPort();
+ execa('./node_modules/.bin/react-scripts', ['start'], {
+ cwd: directory,
+ env: Object.assign(
+ {},
+ {
+ BROWSER: 'none',
+ PORT: port,
+ CI: 'false',
+ FORCE_COLOR: '0',
+ },
+ env
+ ),
+ });
+ return port;
+}
+
async function getOutputProduction({ directory, env = {} }) {
try {
const { stdout, stderr } = await execa(
@@ -141,5 +159,6 @@ module.exports = {
isSuccessfulProduction,
isSuccessfulTest,
getOutputDevelopment,
+ startDevelopmentServer,
getOutputProduction,
};
diff --git a/package.json b/package.json
index 96d63b4f224..c077728b316 100644
--- a/package.json
+++ b/package.json
@@ -32,6 +32,7 @@
"meow": "^5.0.0",
"multimatch": "^2.1.0",
"prettier": "1.14.3",
+ "puppeteer": "^1.8.0",
"strip-ansi": "^4.0.0",
"svg-term-cli": "^2.1.1",
"tempy": "^0.2.1"
diff --git a/packages/react-scripts/config/webpack.config.dev.js b/packages/react-scripts/config/webpack.config.dev.js
index fe628e912ab..672a21e338e 100644
--- a/packages/react-scripts/config/webpack.config.dev.js
+++ b/packages/react-scripts/config/webpack.config.dev.js
@@ -206,7 +206,6 @@ module.exports = {
// the use of this extension, so we need to tell webpack to fall back
// to auto mode (ES Module interop, allows ESM to import CommonJS).
test: /\.mjs$/,
- include: /node_modules/,
type: 'javascript/auto',
},
{
@@ -271,10 +270,23 @@ module.exports = {
cacheCompression: false,
},
},
+ // Make sure `mjs` files get processed as files, not JS. We don't
+ // support `mjs`, so we deliver a file for now.
+ {
+ test: /\.mjs$/,
+ include: paths.appSrc,
+ loader: require.resolve('file-loader'),
+ options: {
+ name: 'static/media/[name].[hash:8].[ext]',
+ },
+ },
// Process any JS outside of the app with Babel.
// Unlike the application JS, we only compile the standard ES features.
+ // `mjs` needs to be included here because some libraries forcibly
+ // import files with the `mjs` extension, or have set their `browser`
+ // or `module` field to a `mjs` file.
{
- test: /\.js$/,
+ test: /\.m?js$/,
exclude: /@babel(?:\/|\\{1,2})runtime/,
loader: require.resolve('babel-loader'),
options: {
diff --git a/packages/react-scripts/config/webpack.config.prod.js b/packages/react-scripts/config/webpack.config.prod.js
index ef4bc311d58..9600d571197 100644
--- a/packages/react-scripts/config/webpack.config.prod.js
+++ b/packages/react-scripts/config/webpack.config.prod.js
@@ -274,7 +274,6 @@ module.exports = {
// the use of this extension, so we need to tell webpack to fall back
// to auto mode (ES Module interop, allows ESM to import CommonJS).
test: /\.mjs$/,
- include: /node_modules/,
type: 'javascript/auto',
},
{
@@ -284,6 +283,7 @@ module.exports = {
oneOf: [
// "url" loader works just like "file" loader but it also embeds
// assets smaller than specified size as data URLs to avoid requests.
+ // A missing `test` is equivalent to a match.
{
test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
loader: require.resolve('url-loader'),
@@ -337,10 +337,23 @@ module.exports = {
compact: true,
},
},
+ // Make sure `mjs` files get processed as files, not JS. We don't
+ // support `mjs`, so we deliver a file for now.
+ {
+ test: /\.mjs$/,
+ include: paths.appSrc,
+ loader: require.resolve('file-loader'),
+ options: {
+ name: 'static/media/[name].[hash:8].[ext]',
+ },
+ },
// Process any JS outside of the app with Babel.
// Unlike the application JS, we only compile the standard ES features.
+ // `mjs` needs to be included here because some libraries forcibly
+ // import files with the `mjs` extension, or have set their `browser`
+ // or `module` field to a `mjs` file.
{
- test: /\.js$/,
+ test: /\.m?js$/,
exclude: /@babel(?:\/|\\{1,2})runtime/,
loader: require.resolve('babel-loader'),
options: {
diff --git a/tasks/e2e-behavior.sh b/tasks/e2e-behavior.sh
index 4a48b423b35..f4ed77659ad 100755
--- a/tasks/e2e-behavior.sh
+++ b/tasks/e2e-behavior.sh
@@ -95,6 +95,9 @@ git clean -df
# Now that we have published them, run all tests as if they were released.
# ******************************************************************************
+# Browser tests
+CI=true ./node_modules/.bin/jest --config fixtures/browser/jest.config.js
+
# Smoke tests
CI=true ./node_modules/.bin/jest --config fixtures/smoke/jest.config.js