发布、发布和安装新型 JavaScript,加快应用速度

通过启用新型 JavaScript 依赖项和输出来提升性能。

超过 90% 的浏览器能够运行现代 JavaScript,但 越来越多的旧版 JavaScript 仍然是性能问题的主要根源 。

现代 JavaScript

现代 JavaScript 的特点不是用特定的 ECMAScript 编写的代码 而是所有现代语言都支持的语法 。Chrome、Edge、Firefox 和 Safari 等现代网络浏览器构成 超过 90% 的浏览器市场,并且 依赖于相同底层呈现引擎的不同浏览器构成了 5%。也就是说,全球 95% 的网络流量来自浏览器, 支持过去 10 年中最常用的 JavaScript 语言功能。 其中包括:

  • 类 (ES2015)
  • 箭头函数 (ES2015)
  • 发电机 (ES2015)
  • 块范围 (ES2015)
  • 解构 (ES2015)
  • 休息和分散参数 (ES2015)
  • 对象简写 (ES2015)
  • async/await (ES2017)

较新版本的语言规范 为各种现代浏览器提供一致的支持例如,许多 ES2020 和 ES2021 但只有 70% 的浏览器市场支持此类功能, 不过,直接依赖这些功能还不够安全。这个 意味着虽然“现代”JavaScript 是一个移动目标,但 ES2017 最广泛的浏览器兼容性 同时加入了大多数常用的现代语法功能。 换句话说,ES2017 是当今最接近现代语法的

旧版 JavaScript

旧版 JavaScript 代码特别避免使用上述所有语言 功能。大多数开发者都使用现代语法编写源代码,但 将所有内容编译为旧语法,以增强浏览器支持。正在编译 使用旧版语法确实可以增加浏览器的支持, 在很多情况下 但会带来可观的成本:

  • 与旧版 JavaScript 相比,旧版 JavaScript 的大小通常约为原来的 20%, 等效的现代代码。工具缺陷和配置错误经常发生 进一步扩大了这一差距。

  • 安装的库占典型生产环境的 90% JavaScript 代码。库代码会导致版本更高的旧版 JavaScript 因 polyfill 和辅助程序重复而导致的开销(可以避免) 来提升应用性能

npm 上的现代 JavaScript

最近,Node.js 对 "exports" 字段进行了标准化,以定义 软件包的入口点

{
  "exports": "./index.js"
}

"exports" 字段引用的模块表示节点版本至少为 12.8,支持 ES2019。这意味着,使用 可以使用新型 JavaScript 编写 "exports" 字段。软件包使用方必须 假定具有 "exports" 字段的模块包含现代代码和转译,如果 。

仅现代

如果您想使用新式代码发布软件包,并将其留给 使用者在将其用作依赖项时需要进行转译,因此仅使用 "exports" 字段。

{
  "name": "foo",
  "exports": "./modern.js"
}

现代与旧版回退

如需发布软件包,请使用 "exports" 字段和 "main" 使用现代代码,但还包含针对旧版的 ES5 + CommonJS 回退 。

{
  "name": "foo",
  "exports": "./modern.js",
  "main": "./legacy.cjs"
}

现代化,采用旧版回退和 ESM 捆绑器优化

除了定义回退 CommonJS 入口点之外,"module" 字段还可以 用于指向一个类似的旧版后备软件包,但使用 JavaScript 模块语法(importexport)。

{
  "name": "foo",
  "exports": "./modern.js",
  "main": "./legacy.cjs",
  "module": "./module.js"
}

Webpack 和 Rollup 等许多捆绑器依赖此字段 模块功能并启用 Tree shaking。 这仍然是一个旧版 bundle,不含任何现代代码 import/export 语法,因此使用这种方法在开发现代代码时需要使用 仍针对捆绑进行了优化的旧版后备方案

应用中的现代 JavaScript

在典型的生产环境中,绝大部分都是第三方依赖项 Web 应用中的 JavaScript 代码。以前,npm 依赖项 已作为旧版 ES5 语法发布,但这不再是可靠的假设, 依赖项更新可能会破坏应用中的浏览器支持。

随着越来越多的 npm 软件包迁移到现代 JavaScript, 请务必确保已设置构建工具来处理这些事件。这里有 很有可能您依赖的一些 npm 软件包已经使用新型 语言功能。有许多选项可用来使用现代代码 使用 npm 的同时不破坏旧版浏览器中的应用,但一般 让构建系统将依赖项转译为相同的语法 作为源代码的目标

Webpack

从 webpack 5 开始,你可以配置 webpack 将使用的语法 。这并不会翻译 它只会影响webpack 生成的代码。 要指定浏览器支持目标,请将 browserslist 配置 添加到您的项目中,也可以直接在您的 webpack 配置中执行此操作:

module.exports = {
  target: ['web', 'es2017'],
};

您也可以通过配置 webpack 生成经过优化的包, 以现代 ES 模块为目标时,省略不必要的封装容器函数 环境这还会将 webpack 配置为使用 <script type="module">

module.exports = {
  target: ['web', 'es2017'],
  output: {
    module: true,
  },
  experiments: {
    outputModule: true,
  },
};

我们提供了许多 Webpack 插件 编译和交付现代 JavaScript,同时仍支持旧版浏览器, 例如优化工具插件和 BabelEsmPlugin。

优化工具插件

优化工具插件是一个 Webpack 用于将最终捆绑代码从新式 JavaScript 转换为旧版 JavaScript 的插件 而不是每个源文件它是一个独立的设置 您的 webpack 配置假定所有内容都是现代 JavaScript 针对多个输出或语法的特殊分支。

由于优化工具插件是针对软件包(而不是单个模块)运行的,因此它 以同等方式处理应用的代码和依赖项。这样, 可以放心地使用来自 npm 的现代 JavaScript 依赖项 捆绑并转译为正确的语法。其速度也可能比 涉及两个编译步骤的传统解决方案, 分别针对现代浏览器和旧版浏览器提供了单独的捆绑包。这两组软件包 使用 module/nomodule pattern

// webpack.config.js
const OptimizePlugin = require('optimize-plugin');

module.exports = {
  // ...
  plugins: [new OptimizePlugin()],
};

与自定义 Webpack 相比,Optimize Plugin 可能更快、更高效 配置,通常将新式代码和旧版代码分开捆绑。它 还会为您运行 Babel,并缩减大小 使用 Terser 的软件包,并为 现代输出和旧输出。最后,生成的 Pod 所需的 系统会将旧版软件包提取到专用脚本中, 在较新版本的浏览器中会重复加载或不必要地加载错误。

<ph type="x-smartling-placeholder">
比较:转译源模块两次与转译生成的软件包。

BabelEsmPlugin

BabelEsmPlugin 是一个 Webpack 可与 Web 服务器搭配使用的插件 @babel/preset-env 生成现有软件包的现代版本,将较少转译的代码 现代浏览器。它是最受欢迎的现成解决方案, module/nomodule,由 Next.jsPreact CLI

// webpack.config.js
const BabelEsmPlugin = require('babel-esm-plugin');

module.exports = {
  //...
  module: {
    rules: [
      // your existing babel-loader configuration:
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env'],
          },
        },
      },
    ],
  },
  plugins: [new BabelEsmPlugin()],
};

BabelEsmPlugin 支持各种 webpack 配置,因为它 运行应用的两个大致独立的版本。如果编译两次 对于大型应用来说,会额外增加一点时间 BabelEsmPlugin,可无缝集成到现有的 Webpack 配置中 并使其成为最方便易用的选项之一

配置 babel-loader 以转译 node_modules

如果您使用的是 babel-loader,但未使用前两个插件中的任何一个, 要使用新式 JavaScript npm 模块。定义两个单独的 babel-loader 配置即可实现 可以自动编译 node_modules 中的现代语言功能, ES2017,同时仍使用 Babel 转译您自己的第一方代码 项目配置中定义的插件和预设。这并不 为 module/nomodule 设置生成新软件包和旧版软件包,但 可以安装和使用包含现代 JavaScript 的 npm 软件包 而不会破坏旧版浏览器

webpack-plugin-modern-npm 使用此方法编译具有 "exports" 字段的 npm 依赖项 package.json 中,因为这些可能包含现代语法:

// webpack.config.js
const ModernNpmPlugin = require('webpack-plugin-modern-npm');

module.exports = {
  plugins: [
    // auto-transpile modern stuff found in node_modules
    new ModernNpmPlugin(),
  ],
};

或者,你也可以在 Webpack 中手动实施该技术 通过检查 package.json"exports" 字段来配置 更新模块。为简洁起见,省略缓存, 的实现代码如下所示:

// webpack.config.js
module.exports = {
  module: {
    rules: [
      // Transpile for your own first-party code:
      {
        test: /\.js$/i,
        loader: 'babel-loader',
        exclude: /node_modules/,
      },
      // Transpile modern dependencies:
      {
        test: /\.js$/i,
        include(file) {
          let dir = file.match(/^.*[/\\]node_modules[/\\](@.*?[/\\])?.*?[/\\]/);
          try {
            return dir && !!require(dir[0] + 'package.json').exports;
          } catch (e) {}
        },
        use: {
          loader: 'babel-loader',
          options: {
            babelrc: false,
            configFile: false,
            presets: ['@babel/preset-env'],
          },
        },
      },
    ],
  },
};

使用此方法时,您需要确保 Google Cloud 支持新式语法, 您的缩减器。两个 Terseruglify-es 可以选择指定 {ecma: 2017} 以保留 在压缩和格式化过程中生成 ES2017 语法。

分组

Rollup 内置支持生成多组内容包, 单个 build,并默认生成现代代码。因此,总览媒体资源可以 进行配置,以生成包含官方插件的现代软件包和旧版软件包 自定义架构

@rollup/plugin-babel

如果您使用 Rollup, getBabelOutputPlugin() 方法 (由 Rollup's 官方 Babel 插件) 将代码转换为生成的软件包(而不是单个源模块)中的代码。 Rollup 内置支持生成多组内容包, 一个 build,每个 build 都有自己的插件。您可以使用此文件生成 分别通过不同的方法传递不同的内容包, Babel 输出插件配置:

// rollup.config.js
import {getBabelOutputPlugin} from '@rollup/plugin-babel';

export default {
  input: 'src/index.js',
  output: [
    // modern bundles:
    {
      format: 'es',
      plugins: [
        getBabelOutputPlugin({
          presets: [
            [
              '@babel/preset-env',
              {
                targets: {esmodules: true},
                bugfixes: true,
                loose: true,
              },
            ],
          ],
        }),
      ],
    },
    // legacy (ES5) bundles:
    {
      format: 'amd',
      entryFileNames: '[name].legacy.js',
      chunkFileNames: '[name]-[hash].legacy.js',
      plugins: [
        getBabelOutputPlugin({
          presets: ['@babel/preset-env'],
        }),
      ],
    },
  ],
};

其他构建工具

Rollup 和 Webpack 具有高度可配置性,这通常意味着每个项目 必须更新其配置,在依赖项中启用现代 JavaScript 语法。 还有一些更高级的构建工具,更青睐惯例和默认值,而不是 配置,例如 ParcelSnowpackViteWMR。其中大部分工具 我们假设 npm 依赖项可能包含新式语法,并会将其转译为 适当的语法级别。

除了针对 webpack 和 Rollup 的专用插件外,现代 JavaScript 使用旧版回退机制可将具有旧版回退的软件包添加到任何项目中 devolution。进化是 独立工具,可将构建系统的输出转换为 JavaScript 变体,使捆绑和转换具有现代感, 输出目标。