您可以使用 SvelteKit 构建应用程序以及组件库,使用`@sveltejs/package`包(`npx sv create`有一个选项为您设置此配置)。 当你创建一个应用程序时,`src/routes`的内容是面向公众的部分;[`src/lib`]($lib)包含你的应用程序的内部库。 组件库的结构与 SvelteKit 应用完全相同,只是`src/lib`是面向公众的部分,而你的根`package.json`用于发布包。`src/routes`可能是一个伴随库的文档或演示网站,或者它可能只是你在开发过程中使用的沙盒。 运行来自 `@sveltejs/package` 的 `svelte-package` 命令将 `src/lib` 的内容生成一个包含以下内容的 `dist` 目录(可以 [配置](#Options)): * 所有位于 `src/lib` 的文件都将被预处理,Svelte 组件将被预编译,TypeScript 文件将被转换为 JavaScript。 * 类型定义(`d.ts` 文件)是为 Svelte、JavaScript 和 TypeScript 文件生成的。您需要安装 `typescript >= 4.0.0` 才能使用此功能。类型定义放置在其实现旁边,手写的 `d.ts` 文件直接复制。您可以[禁用生成](#Options),但我们强烈建议不要这样做——使用您库的人可能会使用 TypeScript,他们需要这些类型定义文件。 > \[!注意\] `@sveltejs/package` 版本 1 生成了一个 `package.json`。现在不再是这样,它将现在使用您项目中的 `package.json` 并验证其正确性。如果您仍在使用版本 1,请参阅 [此 PR](https://github.com/sveltejs/kit/pull/8922) 了解迁移说明。 ## 包.json 结构 由于你现在正在为公众构建一个库,你的`package.json`的内容将变得更加重要。通过它,你可以配置你的包的入口点,哪些文件发布到 npm,以及你的库有哪些依赖。让我们逐一查看最重要的字段。 ### 姓名 这是您包的名称。它将可以使用该名称供其他人安装,并在 `https://npmjs.com/package/` 上可见。 `{ "name": "你的库" }` 了解更多信息 [这里](https://docs.npmjs.com/cli/v9/configuring-npm/package-json#name)。 ### 授权 每个软件包都应该有一个许可字段,这样人们就知道如何使用它。一个非常流行的许可,它在分发和重用方面非常宽松,不提供任何保证,是`MIT`。 `{ "license": "MIT" }` 阅读更多关于它的信息 [这里](https://docs.npmjs.com/cli/v9/configuring-npm/package-json#license)。请注意,您还应在您的包中包含一个 `LICENSE` 文件。 ### 文件 这告诉 npm 它将打包哪些文件并上传到 npm。它应该包含您的输出文件夹(默认为`dist`)。您的`package.json`、`README`和`LICENSE`文件始终会被包含,因此您不需要指定它们。 `{ "files": ["dist"] }` 要排除不必要的文件(例如单元测试或仅从 `src/routes` 等导入的模块等),可以将它们添加到 `.npmignore` 文件中。这将导致生成的包更小,安装速度更快。 了解更多信息 [这里](https://docs.npmjs.com/cli/v9/configuring-npm/package-json#files)。 ### 出口 The `"exports"` 字段包含包的入口点。如果您通过 `npx sv create` 创建一个新的库项目,它被设置为单个导出,即包的根目录: ```json { "exports": { ".": { "types": "./dist/index.d.ts", "svelte": "./dist/index.js" } } } ``` 这告诉打包器和工具,您的包只有一个入口点,即根目录,所有内容都应通过该入口点导入,如下所示: ```js // @errors: 2307 import { Something } from 'your-library'; ``` The `types` 和 `svelte` 键是 [导出条件](https://nodejs.org/api/packages.html#conditional-exports)。它们告诉工具在查找 `your-library` 导入时应该导入哪个文件。 * TypeScript 看到`类型`条件并查找类型定义文件。如果您不发布类型定义,请省略此条件。 * Svelte-aware 工具看到 `svelte` 条件并知道这是一个 Svelte 组件库。如果您发布了一个不导出任何 Svelte 组件且也可以在非 Svelte 项目中工作的库(例如 Svelte 存储库),则可以将此条件替换为 `default`。 > \[注意\] 之前版本的 `@sveltejs/package` 也添加了 `package.json` 导出。由于现在所有工具都可以处理未明确导出的 `package.json`,这不再是模板的一部分。 您可以根据喜好调整`exports`并提供更多入口点。例如,如果您想直接暴露一个`src/lib/Foo.svelte`组件,而不是使用一个重新导出组件的`src/lib/index.js`文件,您可以创建以下导出映射... ```json { "exports": { "./Foo.svelte": { "types": "./dist/Foo.svelte.d.ts", "svelte": "./dist/Foo.svelte" } } } ``` ...并且您的库的消费者可以像这样导入组件: ```js // @filename: ambient.d.ts declare module 'your-library/Foo.svelte'; // @filename: index.js // ---cut--- import Foo from 'your-library/Foo.svelte'; ``` > \[!注意\] 如果您提供类型定义,进行此操作将需要额外小心。有关注意事项的更多信息,请参阅[此处](#TypeScript) 通常,导出映射的每个键是用户导入您的包中内容的路径,值是导入的文件路径或包含这些文件路径的导出条件映射。 阅读更多关于`exports`[这里](https://nodejs.org/docs/latest-v18.x/api/packages.html#package-entry-points)的信息。 ### svelte 这是一个遗留字段,它使工具能够识别 Svelte 组件库。在使用`svelte`[导出条件](#Anatomy-of-a-package.json-exports)时不再需要,但为了与尚未了解导出条件的旧工具保持向后兼容,保留它是好的。它应指向您的根入口点。 ```json { "svelte": "./dist/index.js" } ``` ### 副作用 代码`sideEffects`字段在`package.json`中,由打包器用来确定一个模块是否可能包含具有副作用代码。如果一个模块在导入时对其他脚本(模块外部)产生了可观察的变化,则认为该模块具有副作用。例如,副作用包括修改全局变量或内置 JavaScript 对象的原型。由于副作用可能会影响应用程序其他部分的行为,因此无论这些文件/模块的导出是否在应用程序中使用,这些文件/模块都将包含在最终的包中。避免在代码中产生副作用是一种最佳实践。 设置 `sideEffects` 字段在 `package.json` 中可以帮助打包器更积极地消除最终包中未使用的导出,这个过程称为摇树优化。这导致包更小、更高效。不同的打包器以不同的方式处理 `sideEffects`。虽然对于 Vite 不是必需的,但我们建议库声明所有 CSS 文件都有副作用,以便您的库与 [webpack](https://webpack.js.org/guides/tree-shaking/#mark-the-file-as-side-effect-free) 兼容。这是新创建项目附带的自定义配置: ```json /// file: package.json { "sideEffects": ["**/*.css"] } ``` > 如果您的库中的脚本有副作用,请确保更新 `sideEffects` 字段。所有脚本在新创建的项目中默认标记为无副作用。如果一个有副作用的文件被错误地标记为无副作用,可能会导致功能损坏。 如果您的包包含具有副作用文件,您可以在数组中指定它们: ```json /// file: package.json { "sideEffects": [ "**/*.css", "./dist/sideEffectfulFile.js" ] } ``` 这只会将指定的文件视为具有副作用。 ## TypeScript 您应该为您的库提供类型定义,即使您自己不使用 TypeScript,以便使用您库的人在使用时能够获得适当的智能感知。`@sveltejs/package`使生成类型的流程对您来说大部分是透明的。默认情况下,当打包您的库时,类型定义会自动为 JavaScript、TypeScript 和 Svelte 文件生成。您需要确保的是,在 [exports](#Anatomy-of-a-package.json-exports) 映射中,`types` 条件指向正确的文件。通过 `npx sv create` 初始化库项目时,这会自动设置根导出。 如果您有除了根导出之外的内容——例如提供`your-library/foo`导入——您需要提供类型定义时格外小心。不幸的是,TypeScript 默认情况下将不会解析类似于 `{ "./foo": { "types": "./dist/foo.d.ts", ... }}` 的导出的`类型`条件。相反,它将在您的库根目录中搜索`foo.d.ts`(即`your-library/foo.d.ts`而不是`your-library/dist/foo.d.ts`)。为了解决这个问题,您有两个选择: 第一种选项是要求使用您库的人将 `moduleResolution` 选项设置在他们的 `tsconfig.json`(或 `jsconfig.json`)中为 `bundler`(自 TypeScript 5 起可用,未来最佳和推荐选项),`node16` 或 `nodenext`。这将使 TypeScript 真正查看导出映射并正确解析类型。 第二种选项是(滥用)TypeScript 中的`typesVersions`功能来连接类型。这是 TypeScript 在`package.json`中使用的字段,用于根据 TypeScript 版本检查不同的类型定义,并且还包含路径映射功能。我们利用这个路径映射功能来获取我们想要的内容。对于上面提到的`foo`导出,相应的`typesVersions`看起来像这样: ```json { "exports": { "./foo": { "types": "./dist/foo.d.ts", "svelte": "./dist/foo.js" } }, "typesVersions": { ">4.0": { "foo": ["./dist/foo.d.ts"] } } } ``` `>4.0` 告诉 TypeScript 检查内部映射,如果使用的 TypeScript 版本大于 4(实际上始终应该是这样)。内部映射告诉 TypeScript,`your-library/foo` 的类型定义位于 `./dist/foo.d.ts` 中,这本质上复制了 `exports` 条件。您还可以使用 `*` 作为通配符,一次提供多个类型定义,而不必重复。请注意,如果您选择使用 `typesVersions`,则必须通过它声明所有类型导入,包括根导入(定义为 `"index.d.ts": [...]`)。 您可以在[这里](https://www.typescriptlang.org/docs/handbook/declaration-files/publishing.html#version-selection-with-typesversions)了解更多关于该功能的信息。 ## 最佳实践 您应避免在您的包中使用 SvelteKit 特定的模块,如`$app/environment`,除非您打算仅让其他 SvelteKit 项目使用它们。例如,您可以使用 `import { BROWSER } from 'esm-env'` 而不是 `import { browser } from '$app/environment'` ([查看 esm-env 文档](https://github.com/benmccann/esm-env))。您还可以选择将当前 URL 或导航操作作为属性传递,而不是直接依赖`$app/state`、`$app/navigation`等,以这种方式编写您的应用程序将使设置测试工具、UI 演示等变得更加容易。 确保通过`svelte.config.js`(而不是`vite.config.js`或`tsconfig.json`)添加[别名](configuration#alias),以便它们能被`svelte-package`处理。 您应该仔细考虑您对包所做的更改是错误修复、新功能还是破坏性更改,并相应地更新包版本。请注意,如果您从现有库中删除了任何路径从 `exports` 或其内部的任何 `export` 条件,这应被视为破坏性更改。 ```json { "exports": { ".": { "types": "./dist/index.d.ts", // changing `svelte` to `default` is a breaking change: --- "svelte": "./dist/index.js"--- +++ "default": "./dist/index.js"+++ }, // removing this is a breaking change: --- "./foo": { "types": "./dist/foo.d.ts", "svelte": "./dist/foo.js", "default": "./dist/foo.js" },--- // adding this is ok: +++ "./bar": { "types": "./dist/bar.d.ts", "svelte": "./dist/bar.js", "default": "./dist/bar.js" }+++ } } ``` ## 源映射 您可以通过在您的 `tsconfig.json` 中设置 `"declarationMap": true` 来创建所谓的声明映射(`d.ts.map` 文件)。这将允许编辑器如 VS Code 在使用如 *转到定义* 等功能时访问原始的 `.ts` 或 `.svelte` 文件。这意味着您还需要以使声明文件中的相对路径指向磁盘上的文件的方式,将源文件与 dist 文件夹一起发布。假设您像 Svelte 的 CLI 建议的那样,将所有库代码放在 `src/lib` 中,那么只需在您的 `package.json` 中将 `src/lib` 添加到 `files` 即可: ```json { "files": [ "dist", "!dist/**/*.test.*", "!dist/**/*.spec.*", +++"src/lib", "!src/lib/**/*.test.*", "!src/lib/**/*.spec.*"+++ ] } ``` ## 选项 `سلت-پکج` 接受以下选项: * `-w`/`--watch` — 监视 `src/lib` 目录中的文件变化并重新构建软件包 * `-i`/`--input` — 包含所有文件输入目录。默认为 `src/lib` * `-o`/`--output` — 存储处理后的文件的输出目录。您的 `package.json` 的 `exports` 应指向该目录内的文件,并且 `files` 数组应包含该文件夹。默认为 `dist` * `-t`/`--types` — 是否创建类型定义(`d.ts` 文件)。我们强烈建议这样做,因为它有助于提升生态系统库的质量。默认为 `true` * `--tsconfig` - tsconfig 或 jsconfig 的路径。当未提供时,在工作区路径中搜索下一个 tsconfig/jsconfig。 ## 发布 发布生成的包: `npm 发布` ## 注意事项 所有相关文件导入必须完全指定,遵循 Node 的 ESM 算法。这意味着对于像`src/lib/something/index.js`这样的文件,你必须包含带有扩展名的文件名: ```js // @errors: 2307 import { something } from './something+++/index.js+++'; ``` 如果您正在使用 TypeScript,则需要以相同的方式导入 `.ts` 文件,但使用 `.js` 文件扩展名,*而不是* `.ts` 文件扩展名。(这是 TypeScript 的设计决策,我们无法控制。)在您的 `tsconfig.json` 或 `jsconfig.json` 中设置 `"moduleResolution": "NodeNext"` 将有助于您完成此操作。 所有文件(除了预处理的 Svelte 文件和转换为 JavaScript 的 TypeScript 文件)都按原样复制。