ES5模块中支持ES6导入

8

我为我的一年级学生提供了一个简单的基于ES5的库,使用了揭示模块模式进行编写。以下是“main”模块/命名空间的片段,它将包含其他扩展:

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

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

这对于几乎99.9%的新手学生来说是有效的,他们没有使用类似ES6与Webpack或Babel之类的高级技术。
0.1%的学生现在要求我提供一个基于ES6的版本,可以正确地导入。我很乐意提供这个版本,但我卡在了如何最好地解决这个问题上。
显然,我想保留ES5的方式,这样我的学生就可以只使用一个脚本标签包含文件,并在需要时键入“Library.SomeExtension.aFunction();”。此外,一些扩展依赖于jQuery,它以与上面代码片段类似的方式注入。
现在,我正在寻找一种可维护的方法,以在有jQuery依赖性的情况下同时实现两者的优点。我想给99.9%的人一个“window.Library”,而我想给0.1%的人一种使用“import Library from 'library'” 的方法。
我能用一个JS文件同时完成这两个任务吗?还是我需要一个特殊的ES6版本(这不是问题)?最重要的是:我应该如何重新排列我的代码(所有代码都类似于上面的代码片段),以便我可以支持这两种情况?
任何和所有的指针都将不胜感激!
编辑:作为一个旁注,我已经有一个“gulpfile.js”可以运行这个库通过“Babel”,“minifiers”和其他东西。所以扩展它来解决上述问题不是问题!

可能最好的解决方法是自己使用ES6和一个生成全局暴露模块的转译器/捆绑器。然后将您的源模块提供给0.1%的用户。所以是的,一个单独的源文件,但是有两个分布式文件。 - Bergi
我希望得到一个答案,即“我能用一个JS文件来完成这个任务吗?” - 如果可能的话,它可能不可能,那么脚本会是什么样子? - Snow
@Snow 目前正在尝试使用答案中提供的资源。需要再花一些时间才能得到满意的结果,但如果您在构建脚本时有点创意,似乎是可行的!在我的情况下,我真的想保持 Library.SubLibrary.functionName 的设置,并且在不同的文件中,即使有人将其导入为 ES6 或其他任何东西。(这主要是因为扩展程序也可以相互使用,使用标准的 ES6 设置会导致循环引用)。 - Lennard Fonteijn
是的,使用单独的文件轻松实现这一点:一个使用“export”,另一个分配给“window”。我曾经想过在单个文件中完成两者都可以做到,因为那会感觉更加优雅,但是“export”关键字似乎只能在“type=module”内部工作。我不知道是否有解决方法,或者它是否不可能实现。 - Snow
@LennardFonteijn 在 ES6 模块中,循环依赖完全没有问题,只需确保仅声明导出功能,并且不要在顶层运行初始化代码(这取决于模块评估顺序)。我建议不要使用嵌套命名空间对象,这在 ES6 中相当不规范。 - Bergi
3个回答

5

经过数个晚上的移动代码和安装多个gulp包,我终于找到了一个自己能够接受的解决方案。首先回答一下我的问题,@Bergi 在评论中也支持我的看法:

简短回答:目前无法将 ES6 语法与 ES5(模块)混合在一个文件中运行,因为浏览器会在使用 export 时报错。

长回答:从技术上讲,你可以将脚本元素类型设置为“module”,这样浏览器将接受 export 关键字。但是,你的代码将会被执行两次(一次在加载时,一次在导入后)。如果你可以接受这种情况,那么就算是可以的。

那么我最终做了什么呢?首先声明我真的很想保留现有的 IIFE 模块设置(暂且不变)在 ES5 输出文件中。我将代码库更新为纯 ES6 并尝试了各种插件组合(包括 @David Bradshaw 提到的 Rollup.js),但我仍然无法让它们正常工作,或者完全破坏输出,以至于浏览器无法加载模块,或者取消源映射支持。由于学生正在进行的项目即将结束,我已经浪费了足够的空余时间用于 0.1% 的学生,所以希望下一年能有一个“更好”的解决方案。

  1. 我基于 jQuery 实现了 UMD 模式(如 @Sly_cardinal 所述),并将该代码放入 Library.umd.js 中。对于 ES6 部分,我在其中只添加了 export default Library

  2. 在这两个文件中,我在想要注入连接后的未压缩 Library.js 的地方添加了一个多行注释 /*[Library.js]*/

  3. 我修改了现有的 Gulp 任务,仅使用 concatBabel 构建 Library.js 并将其存储在临时位置。在此过程中,我选择牺牲源映射支持,因为连接的代码在输出中非常易读。从技术上讲,源映射支持“可以”正常工作,但它仍然会使调试器出现太多问题。

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

最终结果是两个文件:

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

虽然我对结果感到满意,但我不喜欢Gulp任务链和第一阶段中源映射支持的丢失。如前所述,我已经尝试了许多Gulp插件以实现相同的效果(例如gulp-inject),但它们要么无法正常工作,要么对源映射进行奇怪的操作(即使它们声称支持它)。

对于下一个尝试,我可能会尝试与Gulp不同的东西,或者编写自己的插件来完成上述任务,但是要做得更好:P


0

你可以使用Rollup.js将你的代码打包成ES5库以及相应的ES6模块。

Rollup内置了对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捆绑包通常是最简单的。


这看起来棒极了,正是我所需要的!我会试用一下并向您汇报 :) - Lennard Fonteijn

-1

有一种通用的JS加载器模式,可以让您以多种不同的方式导出代码。

;(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模块语法。 - Bergi
使用 WebPack 运行良好。 - David Bradshaw
Webpack很快就会成为一匹死马,就像jQuery一样。模块已经出现在浏览器中,随之而来的是打包工具的必然死亡。 - connexo
直到所有旧版浏览器都消失了才行。 - David Bradshaw
1
@connexo 如果我错了,请纠正我,但浏览器模块的问题在于每个导入的资源都需要连接性,并且随着单一责任原则和大型应用程序的增加,这很快就会使事情变得相当困难(或者至少显然是低效的),对吧?使用构建过程而不是使用本机模块也允许编译,这将可能存在,只要语言不断添加新功能即可。(还有一些需要先转换为JS的东西,例如TypeScript和JSX) - Snow

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接