几个月前看到了这篇文章 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 中:

添加引用:

  1. const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
  2. const htmlWebpackAddModulePlugin = require('html-webpack-add-module-plugin')
  3. const fs = require('fs')

说明:

UglifyJsPlugin 是因为 webpack.optimize.UglifyJsPlugin 无法压缩 es6 以上的代码所以需要该插件

htmlWebpackAddModulePlugin 是可以将 生成的 script 转换为 module 或者 nomodule 的插件

fs 是可以对于文件进行一系列操作,这里只是用来判断文件是否存在

修改代码:

修改 oneOf 中的 test: /\.(js|jsx|mjs)$/ 该 loader 将其 options 改为

  1. options: {
  2. presets: [
  3. ['env', {
  4. modules: false,
  5. useBuiltIns: true,
  6. targets: {
  7. browsers: [
  8. 'Chrome >= 60',
  9. 'Safari >= 10.1',
  10. 'iOS >= 10.3',
  11. 'Firefox >= 54',
  12. 'Edge >= 15',
  13. ]
  14. },
  15. }],
  16. "react",
  17. ],
  18. plugins: ["transform-class-properties", "syntax-dynamic-import"],
  19. compact: true
  20. }

可以将 include: paths.appSrc 去除(注意,如果这样做,可能会引起某些错误)

在 plugins 中添加插件:

  1. new htmlWebpackAddModulePlugin({
  2. module: 'all',
  3. }),
  4. new UglifyJsPlugin(),

注释 webpack.optimize.UglifyJsPlugin 插件:

  1. // new webpack.optimize.UglifyJsPlugin({
  2. // compress: {
  3. // warnings: false,
  4. // // Disabled because of an issue with Uglify breaking seemingly valid code:
  5. // // https://github.com/facebookincubator/create-react-app/issues/2376
  6. // // Pending further investigation:
  7. // // https://github.com/mishoo/UglifyJS2/issues/2011
  8. // comparisons: false,
  9. // },
  10. // mangle: {
  11. // safari10: true,
  12. // },
  13. // output: {
  14. // comments: false,
  15. // // Turned on because emoji and regex is not minified properly using default
  16. // // https://github.com/facebookincubator/create-react-app/issues/2488
  17. // ascii_only: true,
  18. // },
  19. // sourceMap: shouldUseSourceMap,
  20. // }),

修改 HtmlWebpackPlugin 插件为:

  1. new HtmlWebpackPlugin({
  2. inject: true,
  3. template: fs.existsSync(`${paths.appBuild}/index.html`) ? `${paths.appBuild}/index.html` : paths.appHtml,
  4. minify: {
  5. removeComments: true,
  6. collapseWhitespace: true,
  7. removeRedundantAttributes: true,
  8. useShortDoctype: true,
  9. removeEmptyAttributes: true,
  10. removeStyleLinkTypeAttributes: true,
  11. keepClosingSlash: true,
  12. minifyJS: true,
  13. minifyCSS: true,
  14. minifyURLs: true,
  15. },
  16. }),

webpack.config.prod.js的修改到此为止


在 webpack.config.prod.es5.js 中修改

添加包引用:

  1. const htmlWebpackAddModulePlugin = require('html-webpack-add-module-plugin')

修改入口名:

  1. entry: {
  2. 'main.es5': [require.resolve('./polyfills'),"babel-polyfill", paths.appIndexJs]
  3. },

与之前一样的修改 oneOf 中的 babel loader 的 options:

  1. options: {
  2. presets: [
  3. ['env', {
  4. modules: false,
  5. useBuiltIns: true,
  6. targets: {
  7. browsers: [
  8. "> 1%",
  9. 'last 2 version',
  10. 'firefox ESR'
  11. ]
  12. },
  13. }],
  14. "react"
  15. ],
  16. plugins: ["transform-class-properties", "syntax-dynamic-import"],
  17. compact: true,
  18. },

添加插件:

  1. new htmlWebpackAddModulePlugin({
  2. nomodule: 'all',
  3. removeCSS: 'main'
  4. }),

webpack.config.prod.es5.js的修改到此为止


开始修改 /scripts/build.js 文件:

添加 es5 config 文件的引用:

  1. const es5config = require('../config/webpack.config.prod.es5');

在 build 函数之前添加函数:

  1. function compiler(config, previousFileSizes, prevResult) {
  2. return new Promise((resolve, reject) => {
  3. config.run((err, stats) => {
  4. if (err) {
  5. return reject(err);
  6. }
  7. const messages = formatWebpackMessages(stats.toJson({}, true));
  8. if (messages.errors.length) {
  9. // Only keep the first error. Others are often indicative
  10. // of the same problem, but confuse the reader with noise.
  11. if (messages.errors.length > 1) {
  12. messages.errors.length = 1;
  13. }
  14. return reject(new Error(messages.errors.join('\n\n')));
  15. }
  16. if (
  17. process.env.CI &&
  18. (typeof process.env.CI !== 'string' ||
  19. process.env.CI.toLowerCase() !== 'false') &&
  20. messages.warnings.length
  21. ) {
  22. console.log(
  23. chalk.yellow(
  24. '\nTreating warnings as errors because process.env.CI = true.\n' +
  25. 'Most CI servers set it automatically.\n'
  26. )
  27. );
  28. return reject(new Error(messages.warnings.join('\n\n')));
  29. }
  30. // console.log(stats)
  31. let result = {
  32. stats,
  33. previousFileSizes,
  34. warnings: messages.warnings,
  35. }
  36. if (prevResult) {
  37. result.prevResult = prevResult
  38. }
  39. return resolve(result);
  40. });
  41. });
  42. }

修改刚刚的 build 函数为:

  1. async function build(previousFileSizes) {
  2. console.log('Creating an optimized production build...');
  3. let modernConfig = webpack(config);
  4. let es5Config = webpack(es5config)
  5. let result = await compiler(es5Config, previousFileSizes);
  6. // remove main.es5.css
  7. let arr = Object.keys(result.stats.compilation.assets)
  8. const path = arr.find(v => v.indexOf('css') > -1 && v.indexOf('main') > -1)
  9. await fs.remove(result.previousFileSizes.root + '/' + path)
  10. result = await compiler(modernConfig, previousFileSizes, result);
  11. return result
  12. }

在 /public/index.html 中的

后面添加:

  1. <script>
  2. (function() {
  3. var check = document.createElement('script');
  4. if (!('noModule' in check) && 'onbeforeload' in check) {
  5. var support = false;
  6. document.addEventListener('beforeload', function(e) {
  7. if (e.target === check) {
  8. support = true;
  9. } else if (!e.target.hasAttribute('nomodule') || !support) {
  10. return;
  11. }
  12. e.preventDefault();
  13. }, true);
  14. check.type = 'module';
  15. check.src = '.';
  16. document.head.appendChild(check);
  17. check.remove();
  18. }
  19. }());
  20. </script>

解决 safari 的重复加载问题

基础的修改到此为止了,接下来运行指令 : npm run build 即可

Build 注意点

虽然现在有一个规范,模块的JS必须添加mjs后缀,但是如果这样做,你不能在本地构建后运行HTML文件,你必须在服务器上运行它,否则你报错:

  1. 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 结果

  1. <!doctype html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="utf-8">
  5. <meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no">
  6. <meta name="theme-color" content="#000000">
  7. <link rel="manifest" href="./manifest.json">
  8. <link rel="shortcut icon" href="./favicon.ico">
  9. <title>React App</title>
  10. <script>!function () {
  11. var t = document.createElement("script");
  12. if (!("noModule" in t) && "onbeforeload" in t) {
  13. var n = !1;
  14. document.addEventListener("beforeload", function (e) {
  15. if (e.target === t) n = !0; else if (!e.target.hasAttribute("nomodule") || !n) return;
  16. e.preventDefault()
  17. }, !0), t.type = "module", t.src = ".", document.head.appendChild(t), t.remove()
  18. }
  19. }()</script>
  20. <link href="./static/css/main.c17080f1.css" rel="stylesheet">
  21. </head>
  22. <body>
  23. <noscript>You need to enable JavaScript to run this app.</noscript>
  24. <div id="root"></div>
  25. <script src="./static/js/main.es5.bfc0d013.js" nomodule></script>
  26. <script src="./static/js/main.eee0168c.js" type="module"></script>
  27. </body>
  28. </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 以上的兼容的更多相关文章

  1. HTML5中script的async属性异步加载JS

    HTML5中script的async属性异步加载JS     HTML4.01为script标签定义了5个属性: charset 可选.指定src引入代码的字符集,大多数浏览器忽略该值.defer 可 ...

  2. html5中script的async属性

    html5中script的async属性 我兴奋于html5的原因之一是一些久久未能实现的特性现在可以真正运用于实际项目中了. 如我们使用placeholder效果蛮久了但是那需要javascript ...

  3. 如何将 JavaScript 代码添加到网页中,以及 <script> 标签的属性

    Hello, world! 本教程的这一部分内容是关于 JavaScript 语言本身的. 但是,我们需要一个工作环境来运行我们的脚本,由于本教程是在线的,所以浏览器是一个不错的选择.我们会尽可能少地 ...

  4. 前后端分离 导致的 静态页面 加载 <script type="module" > 报CORS 跨域错误,提示 blocked by CORS policy

    1.前言 静态页面 加载 <script type="module" > 报CORS 跨域错误,提示Access to script at ftp:///xxx.js ...

  5. script标签crossorigin属性及同源策略和跨域方法

    首先介绍(同源策略) 同源策略是浏览器最核心且基本的安全约定,要求协议.域名.端口都相同为同源,如果非同源时请求数据浏览器会在控制台抛出跨域异常错误,同源策略是浏览器的行为,即使客户端请求发送了,服务 ...

  6. module.exports输出的属性被ES6如何引用的

    阮一峰的ES6教程里有讲: import 命令加载 CommonJS 模块 Node 采用 CommonJS 模块格式,模块的输出都定义在module.exports这个属性上面.在 Node 环境中 ...

  7. NodeJS的exports、module.exports与ES6的export、export default深入详解

    前言 决定开始重新规范的学习一下node编程.但是引入模块我看到用 require的方式,再联想到咱们的ES6各种export .export default. 阿西吧,头都大了.... 头大完了,那 ...

  8. 让script的type属性等于text/html

    type属性为text/html的时候,<script>片断中定义一个被JS调用的代码,代码不会在页面上显示 <script id="commentTemplate&quo ...

  9. script 有哪个属性可以让它不立即执行 defer,async

    .async 和 defer 属性 http://blog.csdn.net/qq_34986769/article/details/52155871 1. defer 属性<script sr ...

随机推荐

  1. Qt 安装与配置记录

    一 安装的时候得选一个Qt安装啊!!不要忘了展开这一项,而只安装Qt creator 展开之后会发现有很多版本,为了方便,选自带编译器mingw,就不需要麻烦的配置了 二 打开Qt creator 后 ...

  2. Python爬虫 爬取Web页面图片

    从网页页面上批量下载jpg格式图片,并按照数字递增命名保存到指定的文件夹 Web地址:http://news.weather.com.cn/2017/12/2812347.shtml 打开网页,点击F ...

  3. 7-10 公路村村通(30 分)(最小生成树Prim算法)

    7-10 公路村村通(30 分) 现有村落间道路的统计数据表中,列出了有可能建设成标准公路的若干条道路的成本,求使每个村落都有公路连通所需要的最低成本. 输入格式: 输入数据包括城镇数目正整数N(≤1 ...

  4. php.ini中date.timezone设置分析

    date.timezone设置php5默认date.timezone为utc,改为date.timezone = PRC即可解决时间相差八小时的问题,但我在php的官方文档中看了半天也没找到这个参数啊 ...

  5. redis运维相关(基本数据库命令)【十四】

    -----------------------------运维相关------------------------- redis持久化,两种方式1.rdb快照方式2.aof日志方式 --------- ...

  6. onclick方法和$("").click()有不一样的地方

    话说是这样的...昨天写了一个文件上传的功能,是这样的,用fastdfs上传成功后会有一个url... 然后我自己测试上传,包括在文件服务器里都能找到.. 然后就自己打包发版了,都很正常也没报错... ...

  7. [bzoj3160]万径人踪灭_FFT_Manacher

    万径人踪灭 bzoj-3160 题目大意:给定一个ab串.求所有的子序列满足:位置和字符都关于某条对称轴对称而且不连续. 注释:$1\le n\le 10^5$. 想法: 看了大爷的题解,OrzOrz ...

  8. [bzoj2194]快速傅立叶之二_FFT

    快速傅立叶之二 bzoj-2194 题目大意:给定两个长度为$n$的序列$a$和$b$.求$c$序列,其中:$c_i=\sum\limits_{j=i}^{n-1} a_j\times b_{j-i} ...

  9. 我的arcgis培训照片13

    来自:http://www.cioiot.com/successview-535-1.html

  10. Linux学习系列之lvs+keepalived

    LVS简介 LVS介绍 LVS是Linux Virtual Server的缩写,意即Linux虚拟服务器,是一个虚拟的服务器集群系统,属于4层负载均衡 ipvs和ipvsadm的关系 我们使用配置LV ...