打包
您可以使用 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。- 类型定义(
d.ts
文件),这些文件是为 Svelte、JavaScript 和 TypeScript 文件生成的。您需要安装typescript >= 4.0.0
才能使用此功能。类型定义放置在其实现旁边,手动编写的d.ts
文件按原样复制。您可以禁用生成,但我们强烈建议不要这样做——使用您库的人可能使用 TypeScript,为此他们需要这些类型定义文件。
@sveltejs/package
版本 1 生成了一个package.json
。这种情况不再发生,它现在将使用您项目中的package.json
并验证其是否正确。如果您仍在使用版本 1,请参阅此 PR以获取迁移说明。
package.json 的结构
由于您现在正在构建供公众使用的库,因此您的 package.json
的内容将变得更加重要。通过它,您可以配置包的入口点、哪些文件发布到 npm 以及您的库有哪些依赖项。让我们逐一了解最重要的字段。
name
这是您包的名称。其他人可以使用此名称安装它,并且可以在 https://npmjs.net.cn/package/<name>
上看到它。
{
"name": "your-library"
}
在此处了解更多信息here。
license
每个包都应该有一个 license 字段,以便人们知道他们被允许如何使用它。一个非常流行的许可证,在分发和重用方面也非常宽松且没有保证的是 MIT
。
{
"license": "MIT"
}
在此处了解更多信息here。请注意,您还应该在您的包中包含一个 LICENSE
文件。
files
这告诉 npm 它将打包并上传到 npm 的哪些文件。它应该包含您的输出文件夹(默认情况下为 dist
)。您的 package.json
和 README
和 LICENSE
将始终包含在内,因此您无需指定它们。
{
"files": ["dist"]
}
要排除不必要的文件(例如单元测试或仅从 src/routes
等导入的模块),您可以将它们添加到 .npmignore
文件中。这将导致包更小,安装速度更快。
在此处了解更多信息here。
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 存储库),您可以将此条件替换为default
。
以前的
@sveltejs/package
版本也添加了package.json
导出。这不再是模板的一部分,因为所有工具现在都可以处理未明确导出的package.json
。
您可以根据自己的喜好调整 exports
并提供更多入口点。例如,如果您不想使用重新导出组件的 src/lib/index.js
文件,而是希望直接公开 src/lib/Foo.svelte
组件,您可以创建以下导出映射...
{
"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';
请注意,如果您提供类型定义,则执行此操作需要额外的注意。在此处阅读有关注意事项的更多信息here
通常,导出映射的每个键都是用户必须用来从您的包中导入某些内容的路径,而值是要导入的文件的路径或导出条件的映射,而导出条件又包含这些文件路径。
在此处阅读有关 exports
的更多信息here。
svelte
这是一个使工具能够识别 Svelte 组件库的旧字段。在使用 svelte
导出条件 时,它不再是必需的,但为了与尚未了解导出条件的旧版工具向后兼容,最好保留它。它应该指向您的根入口点。
{
"svelte": "./dist/index.js"
}
sideEffects
package.json
中的 sideEffects
字段由捆绑器用于确定模块是否可能包含具有副作用的代码。如果模块在导入时进行了一些从模块外部的其他脚本中可以观察到的更改,则该模块被认为具有副作用。例如,副作用包括修改全局变量或内置 JavaScript 对象的原型。由于副作用可能会影响应用程序其他部分的行为,因此无论应用程序中是否使用了这些文件的导出,这些文件/模块都将包含在最终捆绑包中。最佳实践是在您的代码中避免副作用。
在 package.json
中设置 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 默认情况下 *不会* 为类似 { "./foo": { "types": "./dist/foo.d.ts", ... }}
的导出解析 types
条件。相反,它将搜索库根目录(即 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 能够实际查看导出映射并正确解析类型。
第二个选项是(滥用)TypeScript 的 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 如果使用的 TypeScript 版本大于 4(在实践中应该始终为真),则检查内部映射。内部映射告诉 TypeScript your-library/foo
的类型定义位于 ./dist/foo.d.ts
中,这基本上复制了 exports
条件。您还可以使用 *
作为通配符来一次提供许多类型定义,而无需重复自己。请注意,如果您选择使用 typesVersions
,则必须通过它声明所有类型导入,包括根导入(定义为 "index.d.ts": [..]
)。
您可以在此处阅读有关该功能的更多信息here。
最佳实践
您应该避免在您的包中使用 SvelteKit 特定的模块,例如 $app/environment
,除非您希望它们只能被其他 SvelteKit 项目使用。例如,与其使用 import { browser } from '$app/environment'
,您可以使用 import { BROWSER } from 'esm-env'
(参见 esm-env 文档)。您可能也希望将当前 URL 或导航操作作为 props 传递,而不是直接依赖 $app/stores
、$app/navigation
等。以这种更通用的方式编写您的应用程序也将使设置测试、UI 演示等工具更容易。
确保您通过 svelte.config.js
(而不是 vite.config.js
或 tsconfig.json
)添加 别名,以便 svelte-package
处理它们。
您应该仔细考虑对包所做的更改是错误修复、新功能还是重大更改,并相应地更新包版本。请注意,如果您从现有的库中删除了 exports
中的任何路径或其中的任何 export
条件,则应将其视为重大更改。
{
"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"
}
}
}
选项
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)之外的所有文件都按原样复制。