Is a blunt implementation, asynchronous components of webpack 4 have not been tested Webpack is generated repeatedly, which will slow down the build time.
Vue-cli3 already has this way, looking forward to the official command of react-script
Solve the problem of css, but the code size of es5 will not be printed
Although there is a specification now, the JS of the module must add the mjs suffix, but if you do this, you can't run the HTML file after the build locally, you must run it on the server, otherwise you will get an error.
Failed to load module script: The server responded with a non-JavaScript MIME type of "application/octet-stream". Strict MIME type checking is enforced for module scripts per HTML spec.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no">
<meta name="theme-color" content="#000000">
<link rel="manifest" href="./manifest.json">
<link rel="shortcut icon" href="./favicon.ico">
<title>React App</title>
<script>!function () {
var t = document.createElement("script");
if (!("noModule" in t) && "onbeforeload" in t) {
var n = !1;
document.addEventListener("beforeload", function (e) {
if (e.target === t) n = !0; else if (!e.target.hasAttribute("nomodule") || !n) return;
e.preventDefault()
}, !0), t.type = "module", t.src = ".", document.head.appendChild(t), t.remove()
}
}()</script>
<link href="./static/css/main.c17080f1.css" rel="stylesheet">
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<script src="./static/js/main.es5.bfc0d013.js" nomodule></script>
<script src="./static/js/main.eee0168c.js" type="module"></script>
</body>
</html>
The two main versions differ only in the translation of async/await and polyfill: Main.js :123k Main.es5.js :220k
The two chunks differ by one async/await translation: Es6: 0.chunk.js : 362b = 0.29k Es5: 0.chunk.js : 2k
Listed below:
- "babel-core": "^6.26.0"
- "babel-plugin-syntax-dynamic-import": "^6.18.0"
- "babel-plugin-transform-class-properties": "^6.24.1"
- "babel-polyfill": "^6.26.0"
- "babel-preset-env": "^1.7.0"
- "babel-preset-react": "^6.24.1"
- "html-webpack-add-module-plugin": "^1.0.3"
- "uglifyjs-webpack-plugin": "^1.2.7"
Remove the babel parameter in package.json
Copy /config/webpack.config.prod.js in the current directory, named webpack.config.prod.es5.js
Add a reference:
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
const htmlWebpackAddModulePlugin = require('html-webpack-add-module-plugin')
const fs = require('fs')
Description:
UglifyJsPlugin is because webpack.optimize.UglifyJsPlugin can't compress es6 or above code, so the plugin is needed
htmlWebpackAddModulePlugin is a plugin that can convert generated scripts to module or nomodule
Fs is a series of operations on the file, here is only used to determine whether the file exists
Modify the code:
Modify test: /\.(js|jsx|mjs)$/
in oneOf to change its options to
options: {
presets: [
['env', {
modules: false,
useBuiltIns: true,
targets: {
browsers: [
'Chrome >= 60',
'Safari >= 10.1',
'iOS >= 10.3',
'Firefox >= 54',
'Edge >= 15',
]
},
}],
"react",
],
plugins: ["transform-class-properties", "syntax-dynamic-import"],
compact: true
}
You can remove include: paths.appSrc
(note that if you do this, it may cause some errors)
Add a plugin to the plugins:
new htmlWebpackAddModulePlugin({
module: 'all',
}),
new UglifyJsPlugin(),
Comment webpack.optimize.UglifyJsPlugin plugin:
// new webpack.optimize.UglifyJsPlugin({
//compress: {
// warnings: false,
// // Disabled because of an issue with Uglify breaking seemingly valid code:
// // https://github.com/facebookincubator/create-react-app/issues/2376
// // Pending further investigation:
// // https://github.com/mishoo/UglifyJS2/issues/2011
// comparisons: false,
// },
// mangle: {
// safari10: true,
// },
// output: {
// comments: false,
// // Turned on because emoji and regex is not minified properly using default
// // https://github.com/facebookincubator/create-react-app/issues/2488
// ascii_only: true,
// },
// sourceMap: shouldUseSourceMap,
// }),
Modify the HtmlWebpackPlugin plugin to:
new HtmlWebpackPlugin({
inject: true,
template: fs.existsSync(`${paths.appBuild}/index.html`) ? `${paths.appBuild}/index.html` : paths.appHtml,
minify: {
removeComments: true,
collapseWhitespace: true,
removeRedundantAttributes: true,
useShortDoctype: true,
removeEmptyAttributes: true,
removeStyleLinkTypeAttributes: true,
keepClosingSlash: true,
minifyJS: true,
minifyCSS: true,
minifyURLs: true,
},
}),
webpack.config.prod.js has been modified so far
Add package reference:
const htmlWebpackAddModulePlugin = require('html-webpack-add-module-plugin')
Modify the entry name:
entry: {
'main.es5': [require.resolve('./polyfills'),"babel-polyfill", paths.appIndexJs]
},
Modify the babel loader options in oneOf as before:
options: {
presets: [
['env', {
modules: false,
useBuiltIns: true,
targets: {
browsers: [
"> 1%",
'last 2 version',
'firefox ESR'
]
},
}],
"react"
],
plugins: ["transform-class-properties", "syntax-dynamic-import"],
compact: true,
},
Add a plugin:
new htmlWebpackAddModulePlugin({
nomodule: 'all',
removeCSS: 'main'
}),
webpack.config.prod.es5.js has been modified so far
Add a reference to the es5 config file:
const es5config = require('../config/webpack.config.prod.es5');
Add a function before the build function:
function compiler(config, previousFileSizes, prevResult) {
return new Promise((resolve, reject) => {
config.run((err, stats) => {
if (err) {
return reject(err);
}
const messages = formatWebpackMessages(stats.toJson({}, true));
if (messages.errors.length) {
// Only keep the first error. Others are often indicative
// of the same problem, but confuse the reader with noise.
if (messages.errors.length > 1) {
messages.errors.length = 1;
}
return reject(new Error(messages.errors.join('\n\n')));
}
if (
process.env.CI &&
(typeof process.env.CI !== 'string' ||
process.env.CI.toLowerCase() !== 'false') &&
messages.warnings.length
) {
console.log(
chalk.yellow(
'\nTreating warnings as errors because process.env.CI = true.\n' +
'Most CI servers set it automatically.\n'
)
);
return reject(new Error(messages.warnings.join('\n\n')));
}
// console.log(stats)
let result = {
stats,
previousFileSizes,
warnings: messages.warnings,
}
if (prevResult) {
result.prevResult = prevResult
}
return resolve(result);
});
});
}
Modify the just build function to:
async function build(previousFileSizes) {
console.log('Creating an optimized production build...');
let modernConfig = webpack(config);
let es5Config = webpack(es5config)
let result = await compiler(es5Config, previousFileSizes);
// remove main.es5.css
let arr = Object.keys(result.stats.compilation.assets)
const path = arr.find(v => v.indexOf('css') > -1 && v.indexOf('main') > -1)
await fs.remove(result.previousFileSizes.root + '/' + path)
result = await compiler(modernConfig, previousFileSizes, result);
return result
}
Add after
in /public/index.html :<script>
(function() {
var check = document.createElement('script');
if (!('noModule' in check) && 'onbeforeload' in check) {
var support = false;
document.addEventListener('beforeload', function(e) {
if (e.target === check) {
support = true;
} else if (!e.target.hasAttribute('nomodule') || !support) {
return;
}
e.preventDefault();
}, true);
check.type = 'module';
check.src = '.';
document.head.appendChild(check);
check.remove();
}
}());
</script>
Solve the problem of repeated loading of safari
The basic changes are here, then run the command: npm run build
算是一种生硬的实现方案,webpack 4的异步组件还未测试 webpack 重复生成,会减慢 build 的时间
vue-cli3 已经有了这种方式,期待下 react-script 的官方指令
解决 css 的问题,但是 es5 的代码大小不会打印出来
虽然现在有一个规范,模块的JS必须添加mjs后缀,但是如果这样做,你不能在本地构建后运行HTML文件,你必须在服务器上运行它,否则你报错:
Failed to load module script: The server responded with a non-JavaScript MIME type of "application/octet-stream". Strict MIME type checking is enforced for module scripts per HTML spec.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no">
<meta name="theme-color" content="#000000">
<link rel="manifest" href="./manifest.json">
<link rel="shortcut icon" href="./favicon.ico">
<title>React App</title>
<script>!function () {
var t = document.createElement("script");
if (!("noModule" in t) && "onbeforeload" in t) {
var n = !1;
document.addEventListener("beforeload", function (e) {
if (e.target === t) n = !0; else if (!e.target.hasAttribute("nomodule") || !n) return;
e.preventDefault()
}, !0), t.type = "module", t.src = ".", document.head.appendChild(t), t.remove()
}
}()</script>
<link href="./static/css/main.c17080f1.css" rel="stylesheet">
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<script src="./static/js/main.es5.bfc0d013.js" nomodule></script>
<script src="./static/js/main.eee0168c.js" type="module"></script>
</body>
</html>
两个 main 版本只相差 async/await 和 polyfill 的转译:
main.js :123k
main.es5.js :220k
两个 chunk 相差一个 async/await 的转译:
es6:
0.chunk.js : 362b = 0.29k
es5:
0.chunk.js : 2k
下面列出:
- "babel-core": "^6.26.0"
- "babel-plugin-syntax-dynamic-import": "^6.18.0"
- "babel-plugin-transform-class-properties": "^6.24.1"
- "babel-polyfill": "^6.26.0"
- "babel-preset-env": "^1.7.0"
- "babel-preset-react": "^6.24.1"
- "html-webpack-add-module-plugin": "^1.0.3"
- "uglifyjs-webpack-plugin": "^1.2.7"
去除 package.json 中的 babel 参数
复制 /config/webpack.config.prod.js 一份在当前目录, 命名为 webpack.config.prod.es5.js
添加引用:
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
const htmlWebpackAddModulePlugin = require('html-webpack-add-module-plugin')
const fs = require('fs')
说明:
UglifyJsPlugin 是因为 webpack.optimize.UglifyJsPlugin 无法压缩 es6 以上的代码所以需要该插件 htmlWebpackAddModulePlugin 是可以将 生成的 script 转换为 module 或者 nomodule 的插件
fs 是可以对于文件进行一系列操作,这里只是用来判断文件是否存在
修改代码:
修改 oneOf 中的 test: /\.(js|jsx|mjs)$/
该 loader 将其 options 改为
options: {
presets: [
['env', {
modules: false,
useBuiltIns: true,
targets: {
browsers: [
'Chrome >= 60',
'Safari >= 10.1',
'iOS >= 10.3',
'Firefox >= 54',
'Edge >= 15',
]
},
}],
"react",
],
plugins: ["transform-class-properties", "syntax-dynamic-import"],
compact: true
}
可以将 include: paths.appSrc
去除(注意,如果这样做,可能会引起某些错误)
在 plugins 中添加插件:
new htmlWebpackAddModulePlugin({
module: 'all',
}),
new UglifyJsPlugin(),
注释 webpack.optimize.UglifyJsPlugin 插件:
// new webpack.optimize.UglifyJsPlugin({
// compress: {
// warnings: false,
// // Disabled because of an issue with Uglify breaking seemingly valid code:
// // https://github.com/facebookincubator/create-react-app/issues/2376
// // Pending further investigation:
// // https://github.com/mishoo/UglifyJS2/issues/2011
// comparisons: false,
// },
// mangle: {
// safari10: true,
// },
// output: {
// comments: false,
// // Turned on because emoji and regex is not minified properly using default
// // https://github.com/facebookincubator/create-react-app/issues/2488
// ascii_only: true,
// },
// sourceMap: shouldUseSourceMap,
// }),
修改 HtmlWebpackPlugin 插件为:
new HtmlWebpackPlugin({
inject: true,
template: fs.existsSync(`${paths.appBuild}/index.html`) ? `${paths.appBuild}/index.html` : paths.appHtml,
minify: {
removeComments: true,
collapseWhitespace: true,
removeRedundantAttributes: true,
useShortDoctype: true,
removeEmptyAttributes: true,
removeStyleLinkTypeAttributes: true,
keepClosingSlash: true,
minifyJS: true,
minifyCSS: true,
minifyURLs: true,
},
}),
webpack.config.prod.js的修改到此为止
添加包引用:
const htmlWebpackAddModulePlugin = require('html-webpack-add-module-plugin')
修改入口名:
entry: {
'main.es5': [require.resolve('./polyfills'),"babel-polyfill", paths.appIndexJs]
},
与之前一样的修改 oneOf 中的 babel loader 的 options:
options: {
presets: [
['env', {
modules: false,
useBuiltIns: true,
targets: {
browsers: [
"> 1%",
'last 2 version',
'firefox ESR'
]
},
}],
"react"
],
plugins: ["transform-class-properties", "syntax-dynamic-import"],
compact: true,
},
添加插件:
new htmlWebpackAddModulePlugin({
nomodule: 'all',
removeCSS: 'main'
}),
webpack.config.prod.es5.js的修改到此为止
添加 es5 config 文件的引用:
const es5config = require('../config/webpack.config.prod.es5');
在 build 函数之前添加函数:
function compiler(config, previousFileSizes, prevResult) {
return new Promise((resolve, reject) => {
config.run((err, stats) => {
if (err) {
return reject(err);
}
const messages = formatWebpackMessages(stats.toJson({}, true));
if (messages.errors.length) {
// Only keep the first error. Others are often indicative
// of the same problem, but confuse the reader with noise.
if (messages.errors.length > 1) {
messages.errors.length = 1;
}
return reject(new Error(messages.errors.join('\n\n')));
}
if (
process.env.CI &&
(typeof process.env.CI !== 'string' ||
process.env.CI.toLowerCase() !== 'false') &&
messages.warnings.length
) {
console.log(
chalk.yellow(
'\nTreating warnings as errors because process.env.CI = true.\n' +
'Most CI servers set it automatically.\n'
)
);
return reject(new Error(messages.warnings.join('\n\n')));
}
// console.log(stats)
let result = {
stats,
previousFileSizes,
warnings: messages.warnings,
}
if (prevResult) {
result.prevResult = prevResult
}
return resolve(result);
});
});
}
修改刚刚的 build 函数为:
async function build(previousFileSizes) {
console.log('Creating an optimized production build...');
let modernConfig = webpack(config);
let es5Config = webpack(es5config)
let result = await compiler(es5Config, previousFileSizes);
// remove main.es5.css
let arr = Object.keys(result.stats.compilation.assets)
const path = arr.find(v => v.indexOf('css') > -1 && v.indexOf('main') > -1)
await fs.remove(result.previousFileSizes.root + '/' + path)
result = await compiler(modernConfig, previousFileSizes, result);
return result
}
在 /public/index.html 中的
后面添加:<script>
(function() {
var check = document.createElement('script');
if (!('noModule' in check) && 'onbeforeload' in check) {
var support = false;
document.addEventListener('beforeload', function(e) {
if (e.target === check) {
support = true;
} else if (!e.target.hasAttribute('nomodule') || !support) {
return;
}
e.preventDefault();
}, true);
check.type = 'module';
check.src = '.';
document.head.appendChild(check);
check.remove();
}
}());
</script>
解决 safari 的重复加载问题
基础的修改到此为止了,接下来运行指令 : npm run build
即可