乐趣区

关于typescript:发布传输和安装现代-JavaScript-以实现更快的应用程序

本文首发于微信公众号:大迁世界, 我的微信:qq449245884,我会第一工夫和你分享前端行业趋势,学习路径等等。
更多开源作品请看 GitHub https://github.com/qq449245884/xiaozhi,蕴含一线大厂面试残缺考点、材料以及我的系列文章。

超过 90% 的浏览器可能运行古代 JavaScript,但传统 JavaScript 的风行依然是当今 Web 性能问题的最大起因之一。

当今的 Web 受到传统 JavaScript 限度,没有任何繁多优化能够像应用 ES2017 语法编写、公布和传输网页或软件包那样进步性能。

古代 JavaScript

古代 JavaScript 的特色不是应用特定的 ECMAScript 标准版本编写代码,而是应用所有古代浏览器都反对的语法。Chrome、Edge、Firefox 和 Safari 等古代网络浏览器占据浏览器市场的 90% 以上,依赖雷同底层渲染引擎的其余浏览器占另外 5%。这意味着寰球 95% 的 Web 流量所来自的浏览器反对过来 10 年来最宽泛应用的 JavaScript 语言个性,包含:

  • 类 (ES2015)
  • 箭头函数 (ES2015)
  • 生成器 (ES2015)
  • 块范畴 (ES2015)
  • 解构 (ES2015)
  • 残余和开展参数 (ES2015)
  • 对象速记 (ES2015)
  • 异步 / 期待 (ES2017)

较新版本的语言标准中的个性在古代浏览器中取得的反对通常不太统一。例如,许多 ES2020 和 ES2021 个性仅在 70% 的浏览器市场取得反对 — 依然是大多数浏览器,但还不够平安,不能间接依赖这些个性。这意味着只管“古代”JavaScript 是一个流动指标,但 ES2017 领有最宽泛的浏览器兼容性,同时蕴含大多数罕用的古代语法个性。换句话说,ES2017 目前最靠近古代语法

传统 JavaScript

传统 JavaScript 是明确防止应用上述所有语言个性的代码。大多数开发人员应用古代语法编写源代码,但将所有内容编译为传统语法以减少浏览器反对。编译为传统语法的确会减少浏览器反对,但成果通常比咱们设想的小。在许多状况下,反对度从 95% 左右减少到 98%,但同时产生了大量老本:

  • 传统 JavaScript 通常比等效的古代代码大 20% 左右,而且速度更慢。工具缺点和谬误配置通常会进一步扩充这一差距。
  • 装置的库占典型生产 JavaScript 代码的 90%。库代码会因为 polyfillhelper 反复而产生更高的传统 JavaScript 开销,而公布古代代码能够防止这个问题。

npm 上的古代 JavaScript

Node.js 标准化了一个 "exports" 字段来定义软件包的入口点:

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

"exports" 字段援用的模块意味着 Node 版本至多为 12.8,它反对 ES2019。这意味着应用 "exports" 字段援用的任何模块都能够应用古代 JavaScript 编写。软件包使用者必须假设具备 "exports" 字段的模块蕴含古代代码并在必要时进行转换。

仅古代

如果要公布采纳古代代码的软件包,并让使用者在将其用作依赖项时解决转换,则仅应用 "exports" 字段。

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

不举荐这种办法。在完满的世界中,每个开发人员都曾经将编译系统配置为将所有依赖项 (node_modules) 转换为所需语法。然而,目前状况并非如此,仅应用古代语法公布软件包将使其无奈在通过旧版浏览器拜访的应用程序中应用。

具备传统回退的古代代码

"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)依赖该字段来利用模块个性和实现摇树优化。这依然是一个传统捆绑包,不蕴含除了 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,同时依然反对旧版浏览器,例如 Optimize PluginBabelEsmPlugin

Optimize Plugin

Optimize Plugin 是一个 webpack 插件,它能够将最终的捆绑代码从古代 JavaScript 转换为传统 JavaScript,而不是独自的源文件。它是一个自蕴含设置,容许 webpack 配置假设所有内容都是古代 JavaScript,没有针对多个输入或语法的非凡分支。

因为 Optimize Plugin 针对捆绑包而不是单个模块进行操作,因而它会平等解决利用程序代码和依赖项。这样便能够平安地应用 npm 中的古代 JavaScript 依赖项,因为它们的代码将被捆绑并转换为正确的语法。它还能够比波及两个编译步骤的传统解决方案更快,同时依然为古代和旧版浏览器生成独自的捆绑包。这两套捆绑包设计为应用模块 / 无模块模式加载。

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

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

Optimize Plugin 能够比自定义 webpack 配置更快、更高效,后者通常独自捆绑古代和传统代码。它还能够解决运行中的 Babel,并应用 Terser 以独自的针对古代和传统输入优化的设置,使捆绑包最小化。最初,生成的传统捆绑包所需的 polyfill 将提取到一个专用脚本中,这样在较新的浏览器中不会复制或不必要地加载它们。

BabelEsmPlugin

BabelEsmPlugin 是一个 webpack 插件,它与 @babel/preset-env 一起工作来生成现有捆绑包的古代版本,以将更少的转换代码传输到古代浏览器。它是 Next.js 和 Preact 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 插件和我的项目配置中定义的预设来转换您本人的第一方代码。这不会为模块 / 无模块设置生成古代和传统捆绑包,但能够装置和应用蕴含古代 JavaScript 的 npm 软件包,而不会毁坏旧版浏览器体验。

webpack-plugin-modern-npm 应用这种技术来编译在 package.json 中具备 "exports" 字段的 npm 依赖项,因为它们可能蕴含古代语法:

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

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

或者,能够通过在解析模块时查看 package.json 中是否存在 "exports" 字段,在 webpack 配置中手动实现该技术。为简洁起见而省略缓存,自定义实现可能如下所示:

// 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'],
          },
        },
      },
    ],
  },
};

应用此办法时,您须要确保放大器反对古代语法。Terser 和 uglify-es 都有指定 {ecma: 2017} 的选项,以便在压缩和格式化期间保留 ES2017 语法并在某些状况下生成该语法。

Rollup

Rollup 外部反对生成多组捆绑包作为单个版本的一部分,并默认生成古代代码。因而,能够将 Rollup 配置为通过您可能曾经在应用的官网插件生成古代和传统捆绑包。

@rollup/plugin-babel

如果应用 Rollup,getBabelOutputPlugin() 办法(由 Rollup 的官网 Babel 插件提供)会转换生成的捆绑包中的代码,而不是单个源模块。Rollup 外部反对生成多组捆绑包作为单个版本的一部分,每个捆绑包都有本人的插件。您能够通过不同的 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 语法。还有更高级的构建工具更偏向于常规和默认值,而不是配置,例如 Parcel、Snowpack、Vite 和 WMR。这些工具中的大多数假设 npm 依赖项可能蕴含古代语法,并在生产编译时将它们转换为适当的语法级别。

除了 webpack 和 Rollup 的专用插件,还能够应用 devolution 将具备传统回退的古代 JavaScript 捆绑包增加到任何我的项目中。Devolution 是一个独立的工具,可转换编译系统的输入以生成传统 JavaScript 变体,从而容许捆绑和转换采纳古代输入指标。

起源:https://web.dev/publish-moder…

编辑中可能存在的 bug 没法实时晓得,预先为了解决这些 bug, 花了大量的工夫进行 log 调试,这边顺便给大家举荐一个好用的 BUG 监控工具 Fundebug。

交换

有幻想,有干货,微信搜寻 【大迁世界】 关注这个在凌晨还在刷碗的刷碗智。

本文 GitHub https://github.com/qq449245884/xiaozhi 已收录,有一线大厂面试残缺考点、材料以及我的系列文章。

退出移动版