将 TypeScript 与 Svelte 集成

Avatar of Adam Rackis
Adam Rackis

DigitalOcean 为您旅程的每个阶段提供云产品。立即开始使用 200 美元的免费额度!

Svelte 是较新的 JavaScript 框架之一,并且正在迅速普及。它是一个基于模板的框架,但允许在模板绑定中使用任意 JavaScript;它具有出色的响应式特性,简单、灵活且有效;并且作为一个提前 (AOT) 编译的框架,它拥有令人印象深刻的性能和包大小。这篇文章将重点介绍如何在 Svelte 模板中配置 TypeScript。如果您不熟悉 Svelte,我建议您查看 入门教程文档

如果您想跟随代码(或者您想调试在您自己的项目中可能缺少的内容),您可以 克隆仓库。我设置了分支来演示我将要介绍的各个部分。

注意:虽然我们将手动集成 Svelte 和 Typescript,但如果您正在启动一个新的项目,您可能会考虑使用 官方的 Svelte 模板,它可以实现相同的功能。无论哪种方式,这篇文章都涵盖了 TypeScript 配置,即使您使用模板,它仍然相关。

基本的 TypeScript 和 Svelte 设置

让我们来看一下基础设置。如果您转到仓库中的 initial-setup 分支,则设置了一个基本的 Svelte 项目,并使用了 TypeScript。需要明确的是,TypeScript 仅在独立的 .ts 文件中工作。它没有以任何方式集成到 Svelte 中。**实现 TypeScript 集成是本文的目的。**

我将介绍一些使 Svelte 和 TypeScript 协同工作的部分,主要是因为我稍后会更改它们,以便向 Svelte 模板添加 TypeScript 支持。

首先,我有一个 tsconfig.json 文件

{
  "compilerOptions": {
    "module": "esNext",
    "target": "esnext",
    "moduleResolution": "node"
  },
  "exclude": ["./node_modules"]
}

此文件告诉 TypeScript 我想要使用现代 JavaScript、使用 Node 解析以及从编译中排除 node_modules

然后,在 typings/index.d.ts 中,我有以下内容

declare module "*.svelte" {
  const value: any;
  export default value;
}

这允许 TypeScript 与 Svelte 共存。如果没有它,TypeScript 每次使用导入语句加载 Svelte 文件时都会发出错误。最后,我们需要告诉 webpack 处理我们的 Svelte 文件,我们通过 webpack.config.js 中的此规则来实现

{
  test: /\.(html|svelte)$/,
  use: [
    { loader: "babel-loader" },
    {
      loader: "svelte-loader",
      options: {
        emitCss: true,
      },
    },
  ],
}

所有这些都是使用 Svelte 组件和 TypeScript 文件的项目的的基本设置。要确认一切都能构建,请打开几个终端并在其中一个中运行 npm start,这将启动 webpack 监视,并在另一个中运行 npm run tscw,以启动 TypeScript 监视任务。希望两者都能在没有错误的情况下运行。要真正验证 TypeScript 检查是否正在运行,您可以更改

let x: number = 12;

…在 index.ts 中为

let x: number = "12";

…并查看 TypeScript 监视中出现的错误。如果您想实际运行它,您可以在第三个终端中运行 node server(我推荐 iTerm2,它允许您在同一窗口的选项卡中运行这些终端),然后点击 localhost:3001

向 Svelte 添加 TypeScript

让我们直接向 Svelte 组件添加 TypeScript,然后查看我们需要进行哪些配置更改才能使其工作。首先转到 Helper.svelte,并在 script 标签中添加 lang="ts"。这告诉 Svelte script 中有 TypeScript。现在让我们实际添加一些 TypeScript。让我们通过 export let val: number;val 属性检查为数字。整个组件现在如下所示

<script lang="ts">
  export let val: number;
</script>

<h1>Value is: {val}</h1>

我们的 webpack 窗口现在应该出现错误,但这是预期的。

Showing the terminal with an error output.

我们需要告诉 Svelte 加载程序如何处理 TypeScript。让我们安装以下内容

npm i svelte-preprocess svelte-check --save

现在,让我们转到我们的 webpack 配置文件并获取 svelte-preprocess

const sveltePreprocess = require("svelte-preprocess");

…并将其添加到我们的 svelte-loader 中

{
  test: /\.(html|svelte)$/,
  use: [
    { loader: "babel-loader" },
    {
      loader: "svelte-loader",
      options: {
        emitCss: true,
        preprocess: sveltePreprocess({})
      },
    },
  ],
}

好的,让我们重新启动 webpack 进程,它应该可以构建。

添加检查

到目前为止,我们拥有的内容可以构建,但它没有进行检查。如果我们的 Svelte 组件中存在无效代码,我们希望它生成错误。因此,让我们转到 App.svelte,在 script 标签中添加相同的 lang="ts",然后为 val 属性传递一个无效值,如下所示

<Helper val={"3"} />

如果我们查看 TypeScript 窗口,没有错误,但应该有。事实证明,我们没有使用普通的 tsc 编译器来检查 Svelte 模板的类型,而是使用我们之前安装的 svelte-check 实用程序。让我们停止我们的 TypeScript 监视,并在该终端中运行 npm run svelte-check。这将在监视模式下启动 svelte-check 进程,我们应该会看到我们期望的错误。

Showing the terminal with a caught error.

现在,删除 3 周围的引号,错误应该消失

Showing the same terminal window, but no errors.

不错!

在实践中,我们希望同时运行 svelte-check 和 tsc,以便我们在 TypeScript 文件和 Svelte 模板中捕获所有错误。npm 上有很多实用程序可以做到这一点,或者我们可以使用 iTerm2,它能够在同一窗口中拆分多个终端。我在这里使用它来运行服务器、webpack 构建、tsc 构建和 svelte-check 构建。

Showing iTerm2 window with four open terminals in a two-by-two grid.

此设置位于仓库的 basic-checking 分支中。

捕获缺少的属性

我们还需要解决一个问题。如果我们省略了必需的属性,例如我们刚刚看到的 val 属性,我们仍然不会收到错误,但我们应该收到,因为我们没有在 Helper.svelte 中为它分配默认值,因此它是必需的。

<Helper /> // missing `val` prop

要告诉 TypeScript 将此报告为错误,让我们回到我们的 tsconfig,并添加两个新值

"strict": true,
"noImplicitAny": false 

第一个启用默认情况下禁用的许多 TypeScript 检查。第二个 noImplicitAny 将其中一项严格检查关闭。如果没有第二行,任何缺少类型的变量(隐式类型为 any)现在都会被报告为错误(没有隐式 any,明白了吗?)

关于是否应将 noImplicitAny 设置为 true,意见**存在很大分歧**。我个人认为它过于严格,但很多人不同意。进行实验并得出自己的结论。

无论如何,有了新的配置,我们应该能够重新启动 svelte-check 任务并看到我们期望的错误。

Showing terminal with a caught error.

此设置位于仓库的 better-checking 分支中。

其他细节

需要注意的是,如果组件曾经引用过 $$props$$restProps,那么 TypeScript 用于捕获不正确属性的机制将立即且不可逆地关闭。例如,如果您要将一个未声明的属性(例如 junk)传递给 Helper 组件,您将收到预期的错误,因为该组件没有 junk 属性。但是,如果 Helper 组件引用了 $$props$$restProps,则此错误将立即消失。前者允许您动态访问任何属性,而无需为其进行显式声明,而 $$restProps 用于动态访问未声明的属性。

当您考虑它时,这是有道理的。这些构造的目的是动态地即时访问属性,通常用于某种元编程,或者将属性任意地传递给 html 元素,这在 UI 库中很常见。它们的存在意味着对可能未声明的组件的任意访问。

$$props 还有另一种常见用法,那就是访问声明为保留字的属性。class 是一个常见的例子。例如

const className = $$props.class;

…因为

export let class = "";

…是无效的。class 是 JavaScript 中的保留字,但在此特定情况下有一个解决方法。以下也是声明同一属性的有效方法——感谢 Rich Harris 提供帮助。

let className;
export { className as class };

如果您唯一使用 $$props 的目的是访问名称为保留字的属性,您可以使用此替代方法,并为您的组件维护更好的类型检查。

结束语

Svelte 是我使用过的最有前途、最高效,坦率地说也是最有趣的 JavaScript 框架之一。可以相对轻松地添加 TypeScript 就像锦上添花。让 TypeScript 及早为您捕获错误可以真正提高生产力。希望这篇文章对实现这一目标有所帮助。