添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

之前给 webpack-cdn-plugin 提PR时,遇到这样一个问题,入口文件使用了部分 ES6 特性,而作者不想直接提供转译版本,这就导致在低版本 Node.js 环境无法直接运行。

首先想到的办法是,能不能提供多个版本。但是 package.json 中的 main 只能支持单文件,无法提供多入口供使用者根据自身环境选择。

后来采用的做法是在入口文件中判断当前运行环境,使用动态 require 来选择转译或非转译版本。等于说模块开发者需要判断使用者当前的运行环境,总感觉不太优雅。

前段时间刚好看到 这一系列文章 ,里面针对这个问题给出了较为全面的解决方案。

多种模块格式版本

我们都知道模块格式包括了以下几种:

  • AMD 浏览器端异步模块机制
  • CJS Node.js同步模块机制,浏览器端想使用必须先使用类似 webpack 之类的工具编译成异步的
  • ESM ES6提出的内置机制,支持同步和异步,部分浏览器已支持,Node.js 计划2018年实现
  • 作为模块提供者,我们自然希望兼容更多模块格式,还好 UMD 提供了一系列兼容 AMD 和 CJS 的方案,例如支持 在 Node.js 中写 AMD 标准的代码 。这些模版范式为模块提供者提供了极大便利。

    是时候说说 ESM 了。它提供了统一的 import/export ,真正统一了浏览器和 Node.js 端的模块标准。而且配合 Webpack 和 Rollup 提供的 Tree-shaking 技术可以最大程度精简代码。那么问题来了,在 ESM 一统天下之前,如何提供使用 ESM 编写的代码给先进的打包工具,同时又不至于完全失去兼容性。换句话说, package.json 中真的只有 main 这一个暴露模块入口文件的属性吗?

    显然不是,来看看这两个属性吧:

  • module,还 处于提案阶段 ,提供使用 ESM 编写的版本
  • browser,提供仅针对浏览器环境版本
  • 对于打包工具来说,就不能只考虑 main 入口了。 在 webpack 中,可以通过 resolve.mainFields 来指定模块查找优先级。 而这个优先级又是根据目标环境确定的,例如针对浏览器环境,即 target: 'web' ,默认的优先级为: ["browser", "module", "main"] 。而服务端渲染中常用的 target: 'node' 查找优先级为 ["module", "main"] 。可以看出,webpack 会优先使用 ESM 标准的代码。

    看起来支持不同模块格式的问题解决了。

    转译和非转译版本

    除了支持不同的模块格式,代码中使用了新特性,是否需要转译也是一个问题。

    Angular 提出在 package.json 中新增 es2015 属性,用来指定使用 ES6 的非转译代码入口。

    当然,最好能参考 babel-preset-env 的做法,也根据运行环境决定是否需要使用转译版本的代码。

    babel-preset-env

    首先看看 babel-preset-env 是如何使用的。针对浏览器环境:

    "babel": {
        "presets": [
                "env",
                    "targets": {
                        "browsers": ["last 2 versions", "ie >= 7"]
    

    针对 Node.js 环境:

    "babel": {
        "presets": [
                "env",
                    "targets": {
                        "node": "current"
    

    其他重要的参数包括:

  • modules 默认CJS,不转译false
  • useBuiltIns 使用polyfill,注入类似import "core-js/modules/es7.string.pad-start";的代码
  • esnext

    增加一个新的入口esnext。直接提供支持 stage4 以上特性的代码,不使用转译,使用ESM:

    "main": "main.js", "esnext": { "main": "main-esnext.js", "browser": "browser-specific-main-esnext.js"

    具体做法是:

  • 对于模块开发者,提供非转译版本的esnext
  • 对于使用者,通过resolve.mainFieldsesnext加入,赋予最高优先级,同时告知 babel-loader 转译提供esnext的代码。剩下的就交给babel-preset-env根据提供的配置环境自动引入需要的插件。
  • 全部转译,耗时但是配置简单。webpack 插件大多采用这种方式。
  • 配置 babel-loader,只转译提供了 module 字段的依赖
  • 根据文件后缀决定,.js不转译,.esm转译
  • 作为模块提供者,我会采用esnext的方式,即额外提供非转译版本。然后让使用者通过配置,根据自身运行环境选择使用不同的版本。