Packaging
您可以使用 SvelteKit 来构建应用程序和组件库,使用 @sveltejs/package
包(npx sv create
提供了设置此功能的选项)。
在创建应用程序时,src/routes
的内容是对外公开的部分;src/lib
包含应用程序的内部库。
组件库的结构与 SvelteKit 应用程序完全相同,区别在于 src/lib
是对外公开的部分,而根目录下的 package.json
用于发布包。src/routes
可能是随库附带的文档或演示站点,也可能只是开发时使用的沙箱。
运行 @sveltejs/package
提供的 svelte-package
命令会将 src/lib
的内容生成到一个 dist
目录中(可以配置),其中包括以下内容:
src/lib
中的所有文件。Svelte 组件会被预处理,TypeScript 文件会被转译为 JavaScript。- 为 Svelte、JavaScript 和 TypeScript 文件生成类型定义(
d.ts
文件)。您需要安装typescript >= 4.0.0
来支持此功能。类型定义文件会被放置在实现文件旁边,手动编写的d.ts
文件将原样复制。您可以禁用生成,但我们强烈建议不要这样做 —— 使用您库的用户可能会需要这些文件来支持 TypeScript。
[!注意]
@sveltejs/package
的第 1 版会生成一个package.json
。现在不再如此,它会使用项目中的package.json
并验证其正确性。如果您仍然使用第 1 版,请查看此 PR 获取迁移说明。
package.json 的结构
因为您现在正在为公共使用构建一个库,因此 package.json
的内容变得更为重要。通过它,您可以配置包的入口点、发布到 npm 的文件以及库的依赖。我们将逐一介绍最重要的字段。
name
这是您包的名称,其他人可以使用该名称安装您的包,并可在 https://npmjs.com/package/<name>
网站上看到它。
{
"name": "your-library"
}
在此处阅读关于它的更多内容。
license
每个包都应有一个 license 字段,以告知人们如何使用它。目前非常流行的一种许可证是 MIT
,它在分发和复用方面非常宽松且无需担保。
{
"license": "MIT"
}
在此处阅读关于它的更多内容。请注意,应在包中包含一个 LICENSE
文件。
files
该字段告诉 npm 哪些文件将被打包并上传到 npm。它应包含输出文件夹(默认为 dist
)。您的 package.json
、README
和 LICENSE
文件会始终被包括在内,因此您不需要指定它们。
{
"files": ["dist"]
}
要排除不必要的文件(如单元测试,或者仅从 src/routes
导入的模块等)可以将它们添加到 .npmignore
文件中。这将导致包更小,安装速度更快。
在此处阅读关于它的更多内容。
exports
"exports"
字段包含包的入口点。如果您通过 npx sv create
设置了一个新的库项目,它会设置为单一出口,即包的根目录:
{
"exports": {
".": {
"types": "./dist/index.d.ts",
"svelte": "./dist/index.js"
}
}
}
这告诉打包工具和工具链,您的包只有一个入口点,即根目录,所有内容应通过以下方式导入:
import { import Something
Something } from 'your-library';
types
和 svelte
键是导出条件,它们告诉工具在查找 your-library
导入时应引入哪个文件:
- TypeScript 看到
types
条件,会查找类型定义文件。如果您不发布类型定义,请忽略此条件。 - 支持 Svelte 的工具会看到
svelte
条件,知道这是一个 Svelte 组件库。如果您发布的库不导出任何 Svelte 组件,并且也可以在非 Svelte 项目中使用(如 Svelte store 库),您可以将此条件替换为default
。
[!注意] 早期版本的
@sveltejs/package
还添加了一个package.json
导出。这不再是模板的一部分,因为所有工具都可以处理没有明确导出的package.json
。
您可以根据需要调整 exports
并提供更多入口点。例如,如果您想直接暴露 src/lib/Foo.svelte
组件而不是通过 src/lib/index.js
文件重新导出组件,您可以创建以下导出映射……
{
"exports": {
"./Foo.svelte": {
"types": "./dist/Foo.svelte.d.ts",
"svelte": "./dist/Foo.svelte"
}
}
}
……然后您的库的使用者可以用如下方式导入该组件:
import module "your-library/Foo.svelte"
Foo from 'your-library/Foo.svelte';
[!注意] 请注意,如果您提供类型定义,采用此方式可能需要额外处理。在此处阅读关于此问题的更多详细信息。
通常,exports
映射的每个键都是用户从您的包中导入某些内容的路径。而值则是将被导入的文件的路径或包含这些文件路径的导出条件映射。
在此处阅读关于 exports
的更多内容。
svelte
这是一个遗留字段,用于让工具识别 Svelte 组件库。如果使用 svelte
导出条件,它已不再必要,但为了向尚未了解导出条件的过时工具提供兼容性,建议保留它。它应指向您的根入口点。
{
"svelte": "./dist/index.js"
}
sideEffects
package.json
中的 sideEffects
字段用于让打包工具判断模块是否可能包含副作用。如果模块在被导入时对其他脚本可见的行为产生变化(例如修改全局变量或内置 JavaScript 对象的原型),则视为有副作用。由于副作用可能会影响应用程序的其他部分,这些文件/模块无论其导出是否在应用程序中使用,都会被包括在最终的打包文件中。
在 sideEffects
字段中指定的模块会帮助打包工具更积极地从最终的打包文件中剔除未使用的导出(即 tree-shaking),从而生成更小更高效的打包文件。不同的打包工具以不同的方式处理 sideEffects
。尽管 Vite 不需要此配置,但建议为库声明所有 CSS 文件具有副作用,以保持与 webpack 兼容。新创建的项目中的默认配置如下:
{
"sideEffects": ["**/*.css"]
}
如果您的库中的脚本存在副作用,请确保更新
sideEffects
字段。在新创建的项目中,所有脚本默认标记为无副作用。如果错误地将包含副作用的文件标记为没有副作用,可能会导致功能异常。
如果您的包中有副作用的文件,可以通过数组指定这些文件:
{
"sideEffects": ["**/*.css", "./dist/sideEffectfulFile.js"]
}
这样只会将指定的文件视为有副作用的文件。
TypeScript
即使您自己不使用 TypeScript,也应为您的库提供类型定义,这样使用您库的人可以获得正确的智能提示。@sveltejs/package
让生成类型的过程对您来说基本上是透明的。默认情况下,在打包您的库时,会为 JavaScript、TypeScript 和 Svelte 文件自动生成类型定义。您只需要确保 exports 映射中的 types
条件指向正确的文件。当通过 npx sv create
初始化库项目时,会自动设置为根导出。
然而,如果您除了根导出还有其他内容,例如提供 your-library/foo
导入,您需要额外注意提供类型定义。不幸的是,默认情况下 TypeScript 不会 为这种导出解析 types
条件,比如 { "./foo": { "types": "./dist/foo.d.ts", ... }}
。相反,它会从库的根目录(即 your-library/foo.d.ts
而不是 your-library/dist/foo.d.ts
)查找 foo.d.ts
文件。为了解决这个问题,您有两种选择:
第一种选择是要求使用您库的人在其 tsconfig.json
(或 jsconfig.json
)中将 moduleResolution
选项设置为 bundler
(从 TypeScript 5 开始可用,未来是最佳推荐选项)、node16
或 nodenext
。这会使 TypeScript 实际查看 exports 映射并正确解析这些类型。
第二种选择是滥用 TypeScript 的 typesVersions
特性连接类型。typesVersions
是 package.json
中的一个字段,TypeScript 根据 TypeScript 版本检查不同类型定义,同时也包含路径映射功能。我们利用该路径映射功能来满足需求。对于上面提到的 foo
导出,相应的 typesVersions
定义如下:
{
"exports": {
"./foo": {
"types": "./dist/foo.d.ts",
"svelte": "./dist/foo.js"
}
},
"typesVersions": {
">4.0": {
"foo": ["./dist/foo.d.ts"]
}
}
}
>4.0
表示如果使用的 TypeScript 版本大于 4,则 TypeScript 会检查内部映射。内部映射告诉 TypeScript your-library/foo
的类型定义在 ./dist/foo.d.ts
中,这实际上是对 exports
条件的复制。您还可以使用 *
通配符一次性提供多个类型定义而无需重复。如果选择使用 typesVersions
,您需要通过它声明所有类型导入,包括根导入(定义为 "index.d.ts": [..]
)。
您可以在此处 阅读有关该功能的更多信息。
最佳实践
除非您计划将包仅供其他 SvelteKit 项目使用,否则应避免在包中使用 SvelteKit 特定模块(如 $app/environment
)。例如,与其使用 import { browser } from '$app/environment'
,不如使用 import { BROWSER } from 'esm-env'
(参见 esm-env 文档)。您可能还希望将当前 URL 或导航操作作为 prop 传入,而不是直接依赖 $app/state
、$app/navigation
等。这种更通用的编写方式还会使测试、UI 演示等工具的设置变得更加容易。
在 svelte.config.js
(而非 vite.config.js
或 tsconfig.json
)中通过 aliases 添加别名,以便它们被 svelte-package
处理。
应仔细考虑对包的更改是错误修复、新功能还是重大更改,并相应地更新包版本。注意,如果从现有库中移除任何 exports
路径或其内的任何 export
条件,应将其视为重大更改。
{
"exports": {
".": {
"types": "./dist/index.d.ts",
// 将 `svelte` 更改为 `default` 是重大更改:
"svelte": "./dist/index.js"
"default": "./dist/index.js"
},
// 移除此项是重大更改:
"./foo": {
"types": "./dist/foo.d.ts",
"svelte": "./dist/foo.js",
"default": "./dist/foo.js"
},
// 添加此项是可以的:
"./bar": {
"types": "./dist/bar.d.ts",
"svelte": "./dist/bar.js",
"default": "./dist/bar.js"
}
}
}
选项
svelte-package
接受以下选项:
-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 publish
限制
所有的相对文件导入需要完全指定路径,遵守 Node 的 ESM 算法。这意味着对于像 src/lib/something/index.js
这样的文件,必须包括文件名和扩展名:
import { import something
something } from './something/index.js';
如果您使用 TypeScript,您需要以同样的方式导入 .ts
文件,但使用 .js
文件后缀而不是 .ts
文件后缀。(这是一个 TypeScript 的设计决策,超出我们的控制范围。)在您的 tsconfig.json
或 jsconfig.json
中设置 "moduleResolution": "NodeNext"
将有助于解决这个问题。
除 Svelte 文件(预处理)和 TypeScript 文件(转换为 JavaScript)外,所有文件都按原样复制。