zoukankan      html  css  js  c++  java
  • 使用 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('
    
    ')));
          }
          if (
            process.env.CI &&
            (typeof process.env.CI !== 'string' ||
              process.env.CI.toLowerCase() !== 'false') &&
            messages.warnings.length
          ) {
            console.log(
              chalk.yellow(
                '
    Treating warnings as errors because process.env.CI = true.
    ' +
                'Most CI servers set it automatically.
    '
              )
            );
            return reject(new Error(messages.warnings.join('
    
    ')));
          }
          // 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

  • 相关阅读:
    一步步学习SPD2010--第六章节--处理数据源(4)--连接RSS Feed服务器端脚本
    一步步学习SPD2010--第六章节--处理数据源(3)--连接一个RSS Feed XML文件
    一步步学习SPD2010--第五章节--处理数据视图(10)--关键点
    一步步学习SPD2010--第六章节--处理数据源
    一步步学习SPD2010--第六章节--处理数据源(1)--使用数据源
    一步步学习SPD2010--第六章节--处理数据源(2)--处理XML数据
    一步步学习SPD2010--第五章节--处理数据视图(7)--使用公式栏目
    一步步学习SPD2010--第五章节--处理数据视图(8)--处理XPath表达式
    《python深度学习》笔记---5.4-1、卷积网络可视化-可视化中间激活
    《python深度学习》笔记---5.3-4、猫狗分类(使用预训练网络-微调模型)
  • 原文地址:https://www.cnblogs.com/Grewer/p/9518146.html
Copyright © 2011-2022 走看看