diff --git a/packages/react-scripts/config/jest/babelTransform.js b/packages/react-scripts/config/jest/babelTransform.js index bee55b1b156..27db7b33ffe 100644 --- a/packages/react-scripts/config/jest/babelTransform.js +++ b/packages/react-scripts/config/jest/babelTransform.js @@ -12,5 +12,5 @@ const babelJest = require('babel-jest'); module.exports = babelJest.createTransformer({ presets: [require.resolve('babel-preset-react-app')], - babelrc: false, + babelrc: true, }); diff --git a/packages/react-scripts/config/locales.js b/packages/react-scripts/config/locales.js new file mode 100644 index 00000000000..dd39ccdb5bd --- /dev/null +++ b/packages/react-scripts/config/locales.js @@ -0,0 +1,22 @@ +'use strict'; + +const fs = require('fs'); +const path = require('path'); +const paths = require('./paths'); + +const DEFAULT_LOCALE = 'en-gb'; +const LOCALE_DIR = paths.appIntl; + +// By looking at our `i18n` we can get an idea of which languages and locales we need to be +// concerned with. As we add translations for different languages we'll automatically have +// the right data to help with whitelisting those languages/locales in npm modules. + +const locales = [ + DEFAULT_LOCALE, + ...fs.readdirSync(LOCALE_DIR).map(f => path.basename(f, path.extname(f))) // fr-ca.json -> fr-ca +]; + +module.exports = { + locales, + languages: locales.map(l => l.replace(/(-.*)$/, '')) // en-gb -> en +}; diff --git a/packages/react-scripts/config/paths.js b/packages/react-scripts/config/paths.js index 94e399f0f4e..8c2ba599572 100644 --- a/packages/react-scripts/config/paths.js +++ b/packages/react-scripts/config/paths.js @@ -55,8 +55,12 @@ module.exports = { appPublic: resolveApp('public'), appHtml: resolveApp('public/index.html'), appIndexJs: resolveApp('src/index.js'), + appBrowserUpdateHtml: resolveApp('public/browser-update.html'), + appBrowserUpdateJs: resolveApp('src/assets/scripts/browser-detect.js'), appPackageJson: resolveApp('package.json'), appSrc: resolveApp('src'), + appEnv: resolveApp('env'), + appIntl: resolveApp('src/i18n'), yarnLockFile: resolveApp('yarn.lock'), testsSetup: resolveApp('src/setupTests.js'), appNodeModules: resolveApp('node_modules'), @@ -75,8 +79,12 @@ module.exports = { appPublic: resolveApp('public'), appHtml: resolveApp('public/index.html'), appIndexJs: resolveApp('src/index.js'), + appBrowserUpdateHtml: resolveApp('public/browser-update.html'), + appBrowserUpdateJs: resolveApp('src/assets/scripts/browser-detect.js'), appPackageJson: resolveApp('package.json'), appSrc: resolveApp('src'), + appEnv: resolveApp('env'), + appIntl: resolveApp('src/i18n'), yarnLockFile: resolveApp('yarn.lock'), testsSetup: resolveApp('src/setupTests.js'), appNodeModules: resolveApp('node_modules'), diff --git a/packages/react-scripts/config/webpack.config.dev.js b/packages/react-scripts/config/webpack.config.dev.js index e9f985909da..223164dee8d 100644 --- a/packages/react-scripts/config/webpack.config.dev.js +++ b/packages/react-scripts/config/webpack.config.dev.js @@ -10,7 +10,12 @@ // @remove-on-eject-end 'use strict'; -const autoprefixer = require('autoprefixer'); +const cssnext = require('postcss-cssnext'); +const cssimport = require('postcss-import'); +const cssFluidGrid = require('postcss-grid-fluid'); + +const HivehomeWebappFaviconsWebpackPlugin = require('@connected-home/hivehome-webapp-favicons-webpack-plugin'); + const path = require('path'); const webpack = require('webpack'); const HtmlWebpackPlugin = require('html-webpack-plugin'); @@ -22,6 +27,8 @@ const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin'); const getClientEnvironment = require('./env'); const paths = require('./paths'); +const { languages, locales } = require('./locales'); + // Webpack uses `publicPath` to determine where the app is being served from. // In development, we always serve from the root. This makes config easier. const publicPath = '/'; @@ -60,6 +67,7 @@ module.exports = { require.resolve('react-error-overlay'), // Finally, this is your app's code: paths.appIndexJs, + paths.appBrowserUpdateJs, // We include the app code last so that if there is a runtime error during // initialization, it doesn't blow up the WebpackDevServer client, and // changing JS code would still trigger a refresh. @@ -173,7 +181,7 @@ module.exports = { loader: require.resolve('babel-loader'), options: { // @remove-on-eject-begin - babelrc: false, + babelrc: true, presets: [require.resolve('babel-preset-react-app')], // @remove-on-eject-end // This is a feature of `babel-loader` for webpack (not Babel itself). @@ -204,16 +212,22 @@ module.exports = { // https://github.com/facebookincubator/create-react-app/issues/2677 ident: 'postcss', plugins: () => [ - require('postcss-flexbugs-fixes'), - autoprefixer({ + cssimport({ + path: [paths.appSrc + '/assets/stylesheets/common'], + onImport: function(files) { + files.forEach(this.addDependency); + }.bind(this), + }), + cssnext({ browsers: [ '>1%', 'last 4 versions', 'Firefox ESR', 'not ie < 9', // React doesn't support IE8 anyway ], - flexbox: 'no-2009', }), + cssFluidGrid(), + require('postcss-flexbugs-fixes'), ], }, }, @@ -256,7 +270,39 @@ module.exports = { new webpack.NamedModulesPlugin(), // 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), + new webpack.DefinePlugin( + Object.assign({}, env.stringified, { + 'process.locales': JSON.stringify(locales), + }) + ), + // Favicons and tile icons etc + new HivehomeWebappFaviconsWebpackPlugin({ + title: 'Hive Home', + prefix: 'static/media/[hash]-', + platforms: { + generic: { + source: path.join(paths.appSrc, 'assets/icons', 'favicon.png'), + }, + ios: { + source: path.join(paths.appSrc, 'assets/icons', 'app-icon.png'), + statusBar: 'black-translucent', + }, + android: { + source: path.join(paths.appSrc, 'assets/icons', 'app-icon.png'), + themeColor: '#ff8600', + backgroundColor: '#ffffff', + }, + windows: { + source: path.join(paths.appSrc, 'assets/icons', 'app-icon.png'), + tileColor: '#ff8600', + }, + }, + }), + // Whitelist `react-intl` language files we know we'll need + new webpack.ContextReplacementPlugin( + /react-intl[/\\]locale-data$/, + new RegExp(`^\\.[/\\\\](${languages.join('|')})$`) + ), // This is necessary to emit hot updates (currently CSS only): new webpack.HotModuleReplacementPlugin(), // Watcher doesn't work well if you mistype casing in a path so we use diff --git a/packages/react-scripts/config/webpack.config.prod.js b/packages/react-scripts/config/webpack.config.prod.js index 44964346107..2fe5c372420 100644 --- a/packages/react-scripts/config/webpack.config.prod.js +++ b/packages/react-scripts/config/webpack.config.prod.js @@ -10,7 +10,14 @@ // @remove-on-eject-end 'use strict'; -const autoprefixer = require('autoprefixer'); +const cssnext = require('postcss-cssnext'); +const cssimport = require('postcss-import'); +const cssFluidGrid = require('postcss-grid-fluid'); + +const { languages, locales } = require('./locales'); + +const HivehomeWebappFaviconsWebpackPlugin = require('@connected-home/hivehome-webapp-favicons-webpack-plugin'); + const path = require('path'); const webpack = require('webpack'); const HtmlWebpackPlugin = require('html-webpack-plugin'); @@ -66,7 +73,11 @@ module.exports = { // You can exclude the *.map files from the build during deployment. devtool: shouldUseSourceMap ? 'source-map' : false, // In production, we only want to load the polyfills and the app code. - entry: [require.resolve('./polyfills'), paths.appIndexJs], + entry: [ + require.resolve('./polyfills'), + paths.appIndexJs, + paths.appBrowserUpdateJs, + ], output: { // The build folder. path: paths.appBuild, @@ -176,7 +187,7 @@ module.exports = { loader: require.resolve('babel-loader'), options: { // @remove-on-eject-begin - babelrc: false, + babelrc: true, presets: [require.resolve('babel-preset-react-app')], // @remove-on-eject-end compact: true, @@ -216,16 +227,22 @@ module.exports = { // https://github.com/facebookincubator/create-react-app/issues/2677 ident: 'postcss', plugins: () => [ - require('postcss-flexbugs-fixes'), - autoprefixer({ + cssimport({ + path: [paths.appSrc + '/assets/stylesheets/common'], + onImport: function(files) { + files.forEach(this.addDependency); + }.bind(this), + }), + cssnext({ browsers: [ '>1%', 'last 4 versions', 'Firefox ESR', 'not ie < 9', // React doesn't support IE8 anyway ], - flexbox: 'no-2009', }), + cssFluidGrid(), + require('postcss-flexbugs-fixes'), ], }, }, @@ -285,7 +302,39 @@ module.exports = { // if (process.env.NODE_ENV === 'production') { ... }. See `./env.js`. // It is absolutely essential that NODE_ENV was set to production here. // Otherwise React will be compiled in the very slow development mode. - new webpack.DefinePlugin(env.stringified), + new webpack.DefinePlugin( + Object.assign({}, env.stringified, { + 'process.locales': JSON.stringify(locales), + }) + ), + // Favicons and tile icons etc + new HivehomeWebappFaviconsWebpackPlugin({ + title: 'Hive Home', + prefix: 'static/media/[hash]-', + platforms: { + generic: { + source: path.join(paths.appSrc, 'assets/icons', 'favicon.png'), + }, + ios: { + source: path.join(paths.appSrc, 'assets/icons', 'app-icon.png'), + statusBar: 'black-translucent', + }, + android: { + source: path.join(paths.appSrc, 'assets/icons', 'app-icon.png'), + themeColor: '#ff8600', + backgroundColor: '#ffffff', + }, + windows: { + source: path.join(paths.appSrc, 'assets/icons', 'app-icon.png'), + tileColor: '#ff8600', + }, + }, + }), + // Whitelist `react-intl` language files we know we'll need + new webpack.ContextReplacementPlugin( + /react-intl[/\\]locale-data$/, + new RegExp(`^\\.[/\\\\](${languages.join('|')})$`) + ), // Minify the code. new webpack.optimize.UglifyJsPlugin({ compress: { diff --git a/packages/react-scripts/config/webpackDevServer.config.js b/packages/react-scripts/config/webpackDevServer.config.js index 2a351e668bb..76946a51385 100644 --- a/packages/react-scripts/config/webpackDevServer.config.js +++ b/packages/react-scripts/config/webpackDevServer.config.js @@ -18,6 +18,20 @@ const paths = require('./paths'); const protocol = process.env.HTTPS === 'true' ? 'https' : 'http'; const host = process.env.HOST || '0.0.0.0'; +function envMiddleware(req, res) { + const options = { + root: paths.appEnv + }; + + const fileName = `${process.env.DIST_ENV}.js`; + res.sendFile(fileName, options, err => { + if (err) { + console.log(err); + res.status(err.status).end(); + } + }); +} + module.exports = function(proxy, allowedHost) { return { // WebpackDevServer 2.4.3 introduced a security fix that prevents remote @@ -97,6 +111,8 @@ module.exports = function(proxy, allowedHost) { // it used the same host and port. // https://github.com/facebookincubator/create-react-app/issues/2272#issuecomment-302832432 app.use(noopServiceWorkerMiddleware()); + + app.use('/env.js', envMiddleware); }, }; }; diff --git a/packages/react-scripts/package.json b/packages/react-scripts/package.json index bf6c6be48f2..6837569cbe5 100644 --- a/packages/react-scripts/package.json +++ b/packages/react-scripts/package.json @@ -1,6 +1,6 @@ { - "name": "react-scripts", - "version": "1.0.11", + "name": "@connected-home/react-scripts", + "version": "1.0.11-4", "description": "Configuration and scripts for Create React App.", "repository": "facebookincubator/create-react-app", "license": "BSD-3-Clause", @@ -21,10 +21,11 @@ "react-scripts": "./bin/react-scripts.js" }, "dependencies": { + "@connected-home/hivehome-webapp-favicons-webpack-plugin": "^3.0.0", "autoprefixer": "7.1.2", "babel-core": "6.25.0", "babel-eslint": "7.2.3", - "babel-jest": "20.0.3", + "babel-jest": "^16.0.0", "babel-loader": "7.1.1", "babel-preset-react-app": "^3.0.2", "babel-runtime": "6.23.0", @@ -43,9 +44,12 @@ "file-loader": "0.11.2", "fs-extra": "3.0.1", "html-webpack-plugin": "2.29.0", - "jest": "20.0.4", + "jest": "^16.0.1", "object-assign": "4.1.1", + "postcss-cssnext": "^3.0.2", "postcss-flexbugs-fixes": "3.2.0", + "postcss-grid-fluid": "^0.1.17", + "postcss-import": "^10.0.0", "postcss-loader": "2.0.6", "promise": "8.0.1", "react-dev-utils": "^3.1.0", diff --git a/packages/react-scripts/scripts/start.js b/packages/react-scripts/scripts/start.js index b86943b4d9d..21d8b7e1162 100644 --- a/packages/react-scripts/scripts/start.js +++ b/packages/react-scripts/scripts/start.js @@ -13,6 +13,7 @@ // Do this as the first thing so that any code reading it knows the right env. process.env.BABEL_ENV = 'development'; process.env.NODE_ENV = 'development'; +process.env.DIST_ENV = process.env.DIST_ENV ? process.env.DIST_ENV : 'local-dev'; // Makes the script crash on unhandled rejections instead of silently // ignoring them. In the future, promise rejections that are not handled will diff --git a/packages/react-scripts/scripts/utils/createJestConfig.js b/packages/react-scripts/scripts/utils/createJestConfig.js index bc4ebf82271..d12ca52a7f4 100644 --- a/packages/react-scripts/scripts/utils/createJestConfig.js +++ b/packages/react-scripts/scripts/utils/createJestConfig.js @@ -26,6 +26,7 @@ module.exports = (resolve, rootDir, isEjecting) => { collectCoverageFrom: ['src/**/*.{js,jsx}'], setupFiles: [resolve('config/polyfills.js')], setupTestFrameworkScriptFile: setupTestsFile, + testRegex: 'jest\\.js$', testMatch: [ '/src/**/__tests__/**/*.js?(x)', '/src/**/?(*.)(spec|test).js?(x)', @@ -42,6 +43,9 @@ module.exports = (resolve, rootDir, isEjecting) => { transformIgnorePatterns: ['[/\\\\]node_modules[/\\\\].+\\.(js|jsx)$'], moduleNameMapper: { '^react-native$': 'react-native-web', + '^.+\\.css$': '/scripts/utils/css-stub.js', + '^.+\\.(jpg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm)$': + '/scripts/utils/file-stub.js', }, moduleFileExtensions: ['web.js', 'js', 'json', 'web.jsx', 'jsx', 'node'], };