Skip to content

Commit 2adb576

Browse files
committed
basic double dist setup
1 parent ff19e0a commit 2adb576

File tree

4 files changed

+114
-14
lines changed

4 files changed

+114
-14
lines changed

packages/babel-preset-react-app/create.js

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ const validateBoolOption = (name, value, defaultValue) => {
2020
return value;
2121
};
2222

23+
const legacyTargets = { ie: 9 };
24+
const modernTargets = { esmodules: true };
25+
2326
module.exports = function(api, opts, env) {
2427
if (!opts) {
2528
opts = {};
@@ -46,6 +49,11 @@ module.exports = function(api, opts, env) {
4649
opts.absoluteRuntime,
4750
true
4851
);
52+
// TODO: this needs to change.
53+
// At this point we only create one babel config for all builds.
54+
// We should be able to provide a babelconfig for every build.
55+
// Does this mean we should move this to webpack or specify some BABEL_ENV?
56+
var isModern = validateBoolOption('modern', opts.modern, false);
4957

5058
var absoluteRuntimePath = undefined;
5159
if (useAbsoluteRuntime) {
@@ -81,15 +89,13 @@ module.exports = function(api, opts, env) {
8189
{
8290
// We want Create React App to be IE 9 compatible until React itself
8391
// no longer works with IE 9
84-
targets: {
85-
ie: 9,
86-
},
92+
targets: isModern ? modernTargets : legacyTargets,
8793
// Users cannot override this behavior because this Babel
8894
// configuration is highly tuned for ES5 support
8995
ignoreBrowserslistConfig: true,
9096
// If users import all core-js they're probably not concerned with
9197
// bundle size. We shouldn't rely on magic to try and shrink it.
92-
useBuiltIns: false,
98+
useBuiltIns: isModern ? 'entry' : false,
9399
// Do not transform modules to CJS
94100
modules: false,
95101
// Exclude transforms that make all code slower
@@ -157,7 +163,7 @@ module.exports = function(api, opts, env) {
157163
{
158164
corejs: false,
159165
helpers: areHelpersEnabled,
160-
regenerator: true,
166+
regenerator: !isModern,
161167
// https://babeljs.io/docs/en/babel-plugin-transform-runtime#useesmodules
162168
// We should turn this on once the lowest version of Node LTS
163169
// supports ES Modules.

packages/react-scripts/config/webpack.config.js

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ const typescriptFormatter = require('react-dev-utils/typescriptFormatter');
3434
// @remove-on-eject-begin
3535
const getCacheIdentifier = require('react-dev-utils/getCacheIdentifier');
3636
// @remove-on-eject-end
37+
const HtmlWebpackEsmodulesPlugin = require('./webpack/html-webpack-esmodules-plugin');
3738

3839
// Source maps are resource heavy and can cause out of memory issue for large source files.
3940
const shouldUseSourceMap = process.env.GENERATE_SOURCEMAP !== 'false';
@@ -52,7 +53,10 @@ const sassModuleRegex = /\.module\.(scss|sass)$/;
5253

5354
// This is the production and development configuration.
5455
// It is focused on developer experience, fast rebuilds, and a minimal bundle.
55-
module.exports = function(webpackEnv) {
56+
module.exports = function(
57+
webpackEnv,
58+
{ shouldBuildModernAndLegacy, isModernOutput } = {}
59+
) {
5660
const isEnvDevelopment = webpackEnv === 'development';
5761
const isEnvProduction = webpackEnv === 'production';
5862

@@ -161,11 +165,15 @@ module.exports = function(webpackEnv) {
161165
// There will be one main bundle, and one file per asynchronous chunk.
162166
// In development, it does not produce real files.
163167
filename: isEnvProduction
164-
? 'static/js/[name].[contenthash:8].js'
168+
? `static/js/[name].[contenthash:8]${
169+
isModernOutput ? '.modern' : ''
170+
}.js`
165171
: isEnvDevelopment && 'static/js/bundle.js',
166172
// There are also additional JS chunk files if you use code splitting.
167173
chunkFilename: isEnvProduction
168-
? 'static/js/[name].[contenthash:8].chunk.js'
174+
? `static/js/[name].[contenthash:8]${
175+
isModernOutput ? '.modern' : ''
176+
}.chunk.js`
169177
: isEnvDevelopment && 'static/js/[name].chunk.js',
170178
// We inferred the "public path" (such as / or /my-project) from homepage.
171179
// We use "/" in development.
@@ -194,7 +202,7 @@ module.exports = function(webpackEnv) {
194202
ecma: 8,
195203
},
196204
compress: {
197-
ecma: 5,
205+
ecma: isModernOutput ? 6 : 5,
198206
warnings: false,
199207
// Disabled because of an issue with Uglify breaking seemingly valid code:
200208
// https://github.com/facebook/create-react-app/issues/2376
@@ -211,7 +219,7 @@ module.exports = function(webpackEnv) {
211219
safari10: true,
212220
},
213221
output: {
214-
ecma: 5,
222+
ecma: isModernOutput ? 6 : 5,
215223
comments: false,
216224
// Turned on because emoji and regex is not minified properly using default
217225
// https://github.com/facebook/create-react-app/issues/2488
@@ -353,7 +361,12 @@ module.exports = function(webpackEnv) {
353361
// @remove-on-eject-begin
354362
babelrc: false,
355363
configFile: false,
356-
presets: [require.resolve('babel-preset-react-app')],
364+
presets: [
365+
[
366+
require.resolve('babel-preset-react-app'),
367+
{ modern: isModernOutput },
368+
],
369+
],
357370
// Make sure we have a unique cache identifier, erring on the
358371
// side of caution.
359372
// We remove this when the user ejects because the default
@@ -542,6 +555,7 @@ module.exports = function(webpackEnv) {
542555
: undefined
543556
)
544557
),
558+
shouldBuildModernAndLegacy && new HtmlWebpackEsmodulesPlugin(),
545559
// Inlines the webpack runtime script. This script is too small to warrant
546560
// a network request.
547561
isEnvProduction &&
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
'use strict';
2+
3+
const path = require('path');
4+
const HtmlWebpackPlugin = require('html-webpack-plugin');
5+
const fs = require('fs-extra');
6+
7+
const ID = 'html-webpack-esmodules-plugin';
8+
9+
const safariFix = `(function(){var d=document;var c=d.createElement('script');if(!('noModule' in c)&&'onbeforeload' in c){var s=!1;d.addEventListener('beforeload',function(e){if(e.target===c){s=!0}else if(!e.target.hasAttribute('nomodule')||!s){return}e.preventDefault()},!0);c.type='module';c.src='.';d.head.appendChild(c);c.remove()}}())`;
10+
11+
class HtmlWebpackEsmodulesPlugin {
12+
constructor() {}
13+
14+
apply(compiler) {
15+
compiler.hooks.compilation.tap(ID, compilation => {
16+
HtmlWebpackPlugin.getHooks(compilation).alterAssetTagGroups.tapAsync(
17+
ID,
18+
({ plugin, bodyTags: body }, cb) => {
19+
const targetDir = compiler.options.output.path;
20+
// get stats, write to disk
21+
const htmlName = path.basename(plugin.options.filename);
22+
// Watch out for output files in sub directories
23+
const htmlPath = path.dirname(plugin.options.filename);
24+
const tempFilename = path.join(
25+
targetDir,
26+
htmlPath,
27+
`assets-${htmlName}.json`
28+
);
29+
30+
if (!fs.existsSync(tempFilename)) {
31+
fs.mkdirpSync(path.dirname(tempFilename));
32+
const newBody = body.filter(
33+
a => a.tagName === 'script' && a.attributes
34+
);
35+
newBody.forEach(a => (a.attributes.nomodule = ''));
36+
fs.writeFileSync(tempFilename, JSON.stringify(newBody));
37+
return cb();
38+
}
39+
40+
const legacyAssets = JSON.parse(
41+
fs.readFileSync(tempFilename, 'utf-8')
42+
);
43+
// TODO: to discuss, an improvement would be to
44+
// Inject these into the head tag together with the
45+
// Safari script.
46+
body.forEach(tag => {
47+
if (tag.tagName === 'script' && tag.attributes) {
48+
tag.attributes.type = 'module';
49+
}
50+
});
51+
52+
body.push({
53+
tagName: 'script',
54+
closeTag: true,
55+
innerHTML: safariFix,
56+
});
57+
58+
body.push(...legacyAssets);
59+
fs.removeSync(tempFilename);
60+
cb();
61+
}
62+
);
63+
64+
HtmlWebpackPlugin.getHooks(compilation).beforeEmit.tap(ID, data => {
65+
data.html = data.html.replace(/\snomodule="">/g, ' nomodule>');
66+
});
67+
});
68+
}
69+
}
70+
71+
module.exports = HtmlWebpackEsmodulesPlugin;

packages/react-scripts/scripts/build.js

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,17 @@ if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) {
6363
// Process CLI arguments
6464
const argv = process.argv.slice(2);
6565
const writeStatsJson = argv.indexOf('--stats') !== -1;
66+
const buildModern = argv.indexOf('--legacy') === -1;
6667

6768
// Generate configuration
68-
const config = configFactory('production');
69+
const modernConfig = configFactory('production', {
70+
shouldBuildModernAndLegacy: buildModern,
71+
isModernOutput: true,
72+
});
73+
const leagcyConfig = configFactory('production', {
74+
shouldBuildModernAndLegacy: buildModern,
75+
isModernOutput: false,
76+
});
6977

7078
// We require that you explicitly set browsers and do not fall back to
7179
// browserslist defaults.
@@ -116,7 +124,7 @@ checkBrowsers(paths.appPath, isInteractive)
116124

117125
const appPackage = require(paths.appPackageJson);
118126
const publicUrl = paths.publicUrl;
119-
const publicPath = config.output.publicPath;
127+
const publicPath = leagcyConfig.output.publicPath;
120128
const buildFolder = path.relative(process.cwd(), paths.appBuild);
121129
printHostingInstructions(
122130
appPackage,
@@ -142,8 +150,9 @@ checkBrowsers(paths.appPath, isInteractive)
142150
// Create the production build and print the deployment instructions.
143151
function build(previousFileSizes) {
144152
console.log('Creating an optimized production build...');
153+
const configs = [buildModern && modernConfig, leagcyConfig].filter(Boolean);
145154

146-
let compiler = webpack(config);
155+
let compiler = webpack(configs);
147156
return new Promise((resolve, reject) => {
148157
compiler.run((err, stats) => {
149158
let messages;

0 commit comments

Comments
 (0)