【问题标题】:Support for ES6 imports in ES5 module支持 ES5 模块中的 ES6 导入
【发布时间】:2020-03-21 07:56:49
【问题描述】:

对于我一年级的学生,我提供了一个使用显示模块模式编写的简单的基于 ES5 的库。这是“主”模块/命名空间的 sn-p,它将容纳其他扩展:

window.Library = (function ($) {
    if (!$) {
        alert("The Library is dependent on jQuery, which is not loaded!");
    }

    return {};
})(window.jQuery);

这适用于几乎 99.9% 的 Web 开发新手,并且没有将 ES6 等花哨的东西与 Webpack 或 Babel 结合使用。

现在0.1%的人要求我提供一个基于ES6的版本,可以正常导入。我很乐意提供这个,但我有点不知道如何最好地解决这个问题。

我显然想保持 ES5 的方式,所以我的学生可以只使用脚本标签包含文件,然后在他们喜欢的任何地方键入 Library.SomeExtension.aFunction();。最重要的是,一些扩展依赖于 jQuery,它的注入方式与上面的 sn-p 类似。

我现在正在寻找一些可维护的方法来获得两全其美,使用一个代码库,将 jQuery 作为依赖项。我想给 99.9% 一个window.Library,而我想给 0.1% 一个使用import Library from 'library' 的方法。

我可以通过一个 JS 文件来完成这两个任务吗?或者我需要一个特殊的 ES6 版本(不是问题)?最重要的是:我将如何重新调整我的代码(都类似于上面的 sn-p)以支持这两种情况?

任何和所有的指针将不胜感激!

编辑:顺便说一句,我已经有一个gulpfile.js,它通过Babelminifiers 和其他东西运行这个库。所以必须扩展它来解决上述问题不是问题!

【问题讨论】:

  • 最好自己使用 ES6 和生成全局显示模块的编译器/捆绑器来解决。然后将您的源模块提供给 0.1%。所以是的,一个源文件,但是两个分布式文件。
  • 我希望得到一个答案我可以用一个 JS 文件来完成这两者吗? - 如果这是可能的,它可能不是,什么会脚本是什么样子的?
  • @Snow 目前正在使用答案中提供的资源。必须花更多的时间来获得令我满意的东西,但如果您在构建脚本时有点创意,这似乎是可能的!在我的特殊情况下,我真的想保留 Library.SubLibrary.functionName 设置以及单独的文件,即使有人将其导入为 ES6 或其他任何内容。 (这主要是因为扩展也可以相互使用,并且使用标准 ES6 设置会导致循环引用)。
  • 是的,使用单独的文件来完成这项工作很简单:一个使用export,另一个分配给window。我在想如果两者都可以在一个文件中完成会很棒,因为那样会感觉更加优雅,但是export 关键字看起来只能在type=module 中工作。我不知道是否有解决方法,或者这只是不可能的。
  • @LennardFonteijn 循环依赖在 ES6 模块中工作得很好,只要确保你只声明导出的功能并且不要在顶层运行初始化代码(这取决于模块评估顺序)。我建议不要使用嵌套的命名空间对象,这在 ES6 中是非常不习惯的。

标签: javascript ecmascript-6 es6-modules


【解决方案1】:

在移动代码并安装了这么多 gulp 包的几个晚上之后,我什至不记得我尝试过的所有包,我最终确定了一些我比较满意的东西。

首先,回答我自己的问题,@Bergi 在 cmets 中也支持:

简短回答:不,您不能以任何方式(现在)将 ES6 语法与 ES5(模块)混合在一个文件中,因为浏览器会在使用 export 时出错。

长答案:从技术上讲,您可以将脚本元素类型设置为“模块”,这将使浏览器接受 export 关键字。但是,您的代码只会运行两次(加载时一次,导入后一次)。如果你能忍受这一点,那么“否”就变成了“是”。

那么我最终做了什么?让我先说明一下,我真的很想在输出 ES5 文件中保留我(目前)拥有的 IIFE 模块设置。我将我的代码库更新为纯 ES6,并尝试了各种插件组合(包括 @David Bradshaw 提到的 Rollup.js)。但是我根本无法让它们工作,或者它们会以浏览器无法再加载模块或放弃源映射支持的方式完全破坏输出。随着学生们现在正在做的项目接近尾声,我已经为0.1%的学生浪费了足够多的业余时间。所以明年更好的“更好”的解决方案。

  1. 我将我的 UMD 模式(如 @Sly_cardinal 提到的)基于 jQuery,并将该代码放入 Library.umd.js。对于 ES6 部分,我创建了一个 Library.es6.js,其中只有一个 export default Library

  2. 在这两个文件中,我放置了一个多行注释 /*[Library.js]*/,我想在其中注入连接的未缩小的 Library.js。

  3. 我修改了现有的 Gulp 任务,只使用 concatBabel 构建 Library.js 并将其存储在临时位置。我选择在这个过程中牺牲源映射支持,因为连接的代码在输出中是完全可读的。从技术上讲,源映射支持“有效”,但它仍然会过多地摆脱调试器。

  4. 我添加了一个额外的 Gulp 任务来读取 temp Library.js 的内容,然后对于步骤 1 中的每个文件,我会找到并用内容替换多行注释。保存生成的文件,然后通过最终的 concat(例如与 jQuery 捆绑)、terser 和源映射生成将它们扔掉。

最终结果是两个文件:

  • 一个支持 UMD/ES5 浏览器
  • 一个支持 ES6

虽然我对结果感到满意,但我不喜欢 Gulp 任务链以及在流程的前半部分失去源映射支持。如前所述,我尝试了许多 gulp 插件来达到相同的效果(例如gulp-inject),但它们要么无法正常工作,要么对源映射做了奇怪的事情(即使他们声称支持它)。

对于下一次尝试,我可能会研究与 Gulp 不同的东西,或者创建我自己的插件来完成上述工作,但要正确:P

【讨论】:

    【解决方案2】:

    您可以使用Rollup.js 将您的代码捆绑为 ES5 库以及随附的 ES6 模块。

    Rollup 有 built-in support for Gulp,所以这样的事情是一个合理的起点(基于 Rollup 的 Gulp 示例):

        const gulp = require('gulp');
        const rollup = require('rollup');
        const rollupTypescript = require('rollup-plugin-typescript');
    
        gulp.task('bundle', () => {
          return rollup.rollup({
            input: './src/main.ts',
            plugins: [
              rollupTypescript()
            ]
          }).then(bundle => {
              return Promise.all([
                  // Build for ES5
                  bundle.write({
                    file: './dist/library.js',
                    format: 'umd',
                    name: 'Library',
                    sourcemap: true
                }),
    
                // Build for ES6 modules
                bundle.write({
                    file: './dist/library.esm.js',
                    format: 'esm',
                    sourcemap: true
                })
              ]);
          });
        });
    

    您可以查看有关可用的不同输出格式的更多信息:https://rollupjs.org/guide/en/#outputformat

    如果您的库源代码是使用 ES 模块编写的,然后捆绑/转译为 ES5 捆绑包,这通常是最简单的。

    【讨论】:

    • 这看起来很棒,正是我需要的!会给它一个旋转并报告:)
    【解决方案3】:

    他们是Universal JS Loader Patten,允许您以多种不同的方式导出代码。

    ;(function (root, factory) {
    
      if (typeof define === 'function' && define.amd) {
        define(factory);
      } else if (typeof exports === 'object') {
        module.exports = factory();
      } else {
        root.YourModule = factory();
      }
    
    }(this, function () {
      return {};
    }));
    

    【讨论】:

    • 你的意思是通用模块定义模式吗,UMD?同样不,它不支持 ES6 模块语法。
    • 适用于 WebPack
    • Webpack 很快就会变成一匹死马,就像 jQuery 一样。模块已经到了浏览器中,有了它,捆绑将不可避免地消亡。
    • 直到所有旧浏览器都死掉
    • @connexo 如果我错了,请纠正我,但是浏览器内模块的一个问题是它们需要连接每个导入的资源,并且使用单一职责原则和大型应用程序,这很快就会使事情相当困难(或至少明显效率低下),对吧?拥有一个构建过程而不是使用本机模块也允许转译,只要新功能不断添加到语言中,转译就可能存在。(然后有一些东西需要无论如何都要先转译成 JS,比如 Typescript 和 JSX)
    猜你喜欢
    • 2020-12-20
    • 2019-05-19
    • 1970-01-01
    • 1970-01-01
    • 2015-03-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多