使用 script 的 module 属性实现 es6 以上的兼容
几个月前看到了这篇文章 https://philipwalton.com/articles/deploying-es2015-code-in-production-today/,给了我很大的启发,本来是想使用 vue 来当实验对象的,但是在 vue-cli3 的测试版中就有了这个内容,所以这次使用 react 来实验, 现在 cra 中还未采用该方法;
作用:
借用 vue-cli3 中文档的几句话来说明下他的作用:
- 现代版的包会通过
<script type="module">在被支持的浏览器中加载 (他的语法是 es6 以上的,可以直接运行) - 旧版的包会通过
<script nomodule>加载,并会被支持 ES modules 的浏览器忽略。
修改过程:
首先下载需要的包:
下面列出:
- "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
在 prod.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的修改到此为止
在 webpack.config.prod.es5.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的修改到此为止
开始修改 /scripts/build.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 即可
Build 注意点
虽然现在有一个规范,模块的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.
Build 结果
<!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-polyfill 的):
| Version | Parse/eval time (individual runs) | Parse/eval time (avg) |
|---|---|---|
| ES2015+ (main.mjs) | 184ms, 164ms, 166ms | 172ms |
| ES5 (main.es5.js) | 389ms, 351ms, 360ms | 367ms |
结论
算是一种生硬的实现方案, webpack 4的异步组件还未测试
缺点是 webpack 重复生成,会减慢 build 的时间
vue-cli3 已经有了这种方式,期待下 react-script 的官方指令
解决 css 的问题,但是 es5 的代码大小不会打印出来
这是修改实例: https://github.com/Grewer/react-add-module#chinese
使用 script 的 module 属性实现 es6 以上的兼容的更多相关文章
- HTML5中script的async属性异步加载JS
HTML5中script的async属性异步加载JS HTML4.01为script标签定义了5个属性: charset 可选.指定src引入代码的字符集,大多数浏览器忽略该值.defer 可 ...
- html5中script的async属性
html5中script的async属性 我兴奋于html5的原因之一是一些久久未能实现的特性现在可以真正运用于实际项目中了. 如我们使用placeholder效果蛮久了但是那需要javascript ...
- 如何将 JavaScript 代码添加到网页中,以及 <script> 标签的属性
Hello, world! 本教程的这一部分内容是关于 JavaScript 语言本身的. 但是,我们需要一个工作环境来运行我们的脚本,由于本教程是在线的,所以浏览器是一个不错的选择.我们会尽可能少地 ...
- 前后端分离 导致的 静态页面 加载 <script type="module" > 报CORS 跨域错误,提示 blocked by CORS policy
1.前言 静态页面 加载 <script type="module" > 报CORS 跨域错误,提示Access to script at ftp:///xxx.js ...
- script标签crossorigin属性及同源策略和跨域方法
首先介绍(同源策略) 同源策略是浏览器最核心且基本的安全约定,要求协议.域名.端口都相同为同源,如果非同源时请求数据浏览器会在控制台抛出跨域异常错误,同源策略是浏览器的行为,即使客户端请求发送了,服务 ...
- module.exports输出的属性被ES6如何引用的
阮一峰的ES6教程里有讲: import 命令加载 CommonJS 模块 Node 采用 CommonJS 模块格式,模块的输出都定义在module.exports这个属性上面.在 Node 环境中 ...
- NodeJS的exports、module.exports与ES6的export、export default深入详解
前言 决定开始重新规范的学习一下node编程.但是引入模块我看到用 require的方式,再联想到咱们的ES6各种export .export default. 阿西吧,头都大了.... 头大完了,那 ...
- 让script的type属性等于text/html
type属性为text/html的时候,<script>片断中定义一个被JS调用的代码,代码不会在页面上显示 <script id="commentTemplate&quo ...
- script 有哪个属性可以让它不立即执行 defer,async
.async 和 defer 属性 http://blog.csdn.net/qq_34986769/article/details/52155871 1. defer 属性<script sr ...
随机推荐
- json数据的格式,JavaScript、jQuery读取json数据
JSON:JavaScript 对象表示法(JavaScript Object Notation). JSON的特点: JSON 是纯文本 JSON 具有“自我描述性”(人类可读) JSON 具有层级 ...
- AndroidSweetSheet:从底部弹出面板(1)
AndroidSweetSheet:从底部弹出面板(1) AndroidSweetSheet又是一个从底部弹出面板的开源项目.我在以前写的文章中介绍了不少这些项目,见附录文章5,6,7,8.现在 ...
- BFS简单迷宫
常见迷宫: 输入迷宫 启点 终点 然后求最短路径 BFS例题 用dist[][]数组来记录 启点到每个点的最短路径 #include <iostream> #include <fst ...
- 组队训练1 回放(转载至cxhscst2's blog)
第一场组队训练……意料之中的爆炸. 开场先看题,H是斐波那契水题,qw把H切了. 同时czy看I题(排列),cst继续读其他题. czy尝试交I,PE. cst发现K是水题. cst上来敲K,WA ...
- 【进击后端】Ubuntu 命令行 安装nginx
一.安装nginx apt-get install nginx 安装路径为:/etc/nginx/conf.d 二.配置nginx,在conf.d目录下新建test.conf 新建文件的命令是vi t ...
- P1765 手机_NOI导刊2010普及(10)
P1765 手机_NOI导刊2010普及(10) 题目描述 一般的手机的键盘是这样的: 1 2 abc 3 def 4 ghi 5 jkl 6 mno 7 pqrs 8 tuv 9 wxyz * 0 ...
- Swap Nodes in Pairs(链表操作)
Given a linked list, swap every two adjacent nodes and return its head. For example,Given 1->2-&g ...
- list去重精简代码版
List<String> list = new ArrayList<>(); list.add("111"); list.add("111&quo ...
- 洛谷 P1122 最大子树和
P1122 最大子树和 题目描述 小明对数学饱有兴趣,并且是个勤奋好学的学生,总是在课后留在教室向老师请教一些问题.一天他早晨骑车去上课,路上见到一个老伯正在修剪花花草草,顿时想到了一个有关修剪花卉的 ...
- Windows 10+Ubuntu 16.04在MBR分区上安装双系统之后没有Windows 10的启动菜单解决方法
背景: 硬盘分区方式:MBR 硬盘容量256,Windows 100,Ubuntu 156,其中主分区安装的是Windows,Ubuntu安装在逻辑分区上,文件系统为Ext4,整个Ubuntu就挂载在 ...