Skip to content

Commit 2630658

Browse files
committed
refactor(core): move all rendering and cleanup logic into core
1 parent be4748e commit 2630658

File tree

14 files changed

+159
-107
lines changed

14 files changed

+159
-107
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ package-lock.json
1212
yarn.lock
1313

1414
# generated typing output
15-
types
15+
dist
1616
*.tsbuildinfo
1717

1818
# copied documentation

CONTRIBUTING.md

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,20 @@ The module is released automatically from the `main` and `next` branches using [
1717

1818
This repository uses `pnpm` as its package manager. See the `pnpm` [installation guide](https://pnpm.io/installation) to set it up through whatever method you prefer.
1919

20-
After cloning the repository, use the `setup` script to install dependencies and run all checks:
20+
After cloning the repository, use the `setup` script to install dependencies, build, and run all checks:
2121

2222
```shell
2323
pnpm run setup
2424
```
2525

26+
### Build
27+
28+
To build types and docs:
29+
30+
```shell
31+
pnpm run build
32+
```
33+
2634
### Lint and format
2735

2836
Run auto-formatting to ensure any changes adhere to the code style of the repository:
@@ -64,13 +72,7 @@ pnpm run install:3
6472
pnpm run all:legacy
6573
```
6674

67-
### Docs
68-
69-
Use the `docs` script to ensure the README's table of contents is up to date:
70-
71-
```shell
72-
pnpm run docs
73-
```
75+
### Contributors
7476

7577
Use `contributors:add` to add a contributor to the README:
7678

eslint.config.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ export default tseslint.config(
3737
},
3838
{
3939
name: 'ignores',
40-
ignores: ['**/coverage/**', '**/types/**'],
40+
ignores: ['**/coverage/**', '**/dist/**'],
4141
},
4242
{
4343
name: 'simple-import-sort',
@@ -68,6 +68,7 @@ export default tseslint.config(
6868
name: 'extras',
6969
rules: {
7070
'unicorn/prevent-abbreviations': 'off',
71+
'unicorn/prefer-dom-node-append': 'off',
7172
},
7273
},
7374
{

package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
"scripts": {
77
"all": "pnpm contributors:generate && pnpm build && pnpm format && pnpm test:all && pnpm typecheck",
88
"all:legacy": "pnpm build:types && pnpm test:all:legacy && pnpm typecheck:legacy",
9-
"docs": "",
109
"lint": "prettier . --check && eslint .",
1110
"format": "prettier . --write && eslint . --fix",
1211
"setup": "pnpm install:5 && pnpm all",
@@ -21,7 +20,7 @@
2120
"test:all": "pnpm test:vitest:jsdom && pnpm test:vitest:happy-dom && pnpm test:jest && pnpm test:examples",
2221
"test:all:legacy": "pnpm test:vitest:jsdom && pnpm test:vitest:happy-dom && pnpm test:jest",
2322
"build": "pnpm build:types && pnpm build:docs",
24-
"build:types": "tsc --build && cp packages/svelte/src/component-types.d.ts packages/svelte/types",
23+
"build:types": "tsc --build && cp packages/svelte/src/core/types.d.ts packages/svelte/dist/core",
2524
"build:docs": "remark --output --use remark-toc --use remark-code-import --use unified-prettier README.md examples && cp -f README.md packages/svelte",
2625
"contributors:add": "all-contributors add",
2726
"contributors:generate": "all-contributors generate",

packages/svelte/package.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,24 +5,24 @@
55
"main": "src/index.js",
66
"exports": {
77
".": {
8-
"types": "./types/index.d.ts",
8+
"types": "./dist/index.d.ts",
99
"default": "./src/index.js"
1010
},
1111
"./svelte5": {
12-
"types": "./types/index.d.ts",
12+
"types": "./dist/index.d.ts",
1313
"default": "./src/index.js"
1414
},
1515
"./vitest": {
16-
"types": "./types/vitest.d.ts",
16+
"types": "./dist/vitest.d.ts",
1717
"default": "./src/vitest.js"
1818
},
1919
"./vite": {
20-
"types": "./types/vite.d.ts",
20+
"types": "./dist/vite.d.ts",
2121
"default": "./src/vite.js"
2222
}
2323
},
2424
"type": "module",
25-
"types": "types/index.d.ts",
25+
"types": "dist/index.d.ts",
2626
"license": "MIT",
2727
"homepage": "https://github.com/testing-library/svelte-testing-library#readme",
2828
"repository": {

packages/svelte/src/core/index.js

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,6 @@
55
* Will switch to legacy, class-based mounting logic
66
* if it looks like we're in a Svelte <= 4 environment.
77
*/
8-
export { addCleanupTask, cleanup } from './cleanup.js'
9-
export { mount } from './mount.js'
10-
export {
11-
UnknownSvelteOptionsError,
12-
validateOptions,
13-
} from './validate-options.js'
8+
export * from './cleanup.js'
9+
export * from './mount.js'
10+
export * from './setup.js'

packages/svelte/src/core/mount.js

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,7 @@ import * as Svelte from 'svelte'
55

66
import { addCleanupTask, removeCleanupTask } from './cleanup.js'
77
import { createProps } from './props.svelte.js'
8-
9-
/** Whether we're using Svelte >= 5. */
10-
const IS_MODERN_SVELTE = typeof Svelte.mount === 'function'
11-
12-
/** Allowed options to the `mount` call or legacy component constructor. */
13-
const ALLOWED_MOUNT_OPTIONS = IS_MODERN_SVELTE
14-
? ['target', 'anchor', 'props', 'events', 'context', 'intro']
15-
: ['target', 'accessors', 'anchor', 'props', 'hydrate', 'intro', 'context']
8+
import { IS_MODERN_SVELTE } from './svelte-version.js'
169

1710
/** Mount a modern Svelte 5 component into the DOM. */
1811
const mountModern = (Component, options) => {
@@ -78,7 +71,7 @@ const mountComponent = IS_MODERN_SVELTE ? mountModern : mountLegacy
7871
* rerender: (props: Partial<import('./types.js').Props<C>>) => Promise<void>
7972
* }}
8073
*/
81-
const mount = (Component, options = {}) => {
74+
const mount = (Component, options) => {
8275
const { component, unmount, rerender } = mountComponent(Component, options)
8376

8477
return {
@@ -92,4 +85,4 @@ const mount = (Component, options = {}) => {
9285
}
9386
}
9487

95-
export { ALLOWED_MOUNT_OPTIONS, mount }
88+
export { mount }

packages/svelte/src/core/setup.js

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/** Set up the document to render a component. */
2+
import { addCleanupTask } from './cleanup.js'
3+
import { IS_MODERN_SVELTE } from './svelte-version.js'
4+
5+
/** Allowed options to the `mount` call or legacy component constructor. */
6+
const ALLOWED_MOUNT_OPTIONS = IS_MODERN_SVELTE
7+
? ['target', 'anchor', 'props', 'events', 'context', 'intro']
8+
: ['target', 'accessors', 'anchor', 'props', 'hydrate', 'intro', 'context']
9+
10+
class UnknownSvelteOptionsError extends TypeError {
11+
constructor(unknownOptions) {
12+
super(`Unknown options.
13+
14+
Unknown: [ ${unknownOptions.join(', ')} ]
15+
Allowed: [ ${ALLOWED_MOUNT_OPTIONS.join(', ')} ]
16+
17+
To pass both Svelte options and props to a component,
18+
or to use props that share a name with a Svelte option,
19+
you must place all your props under the \`props\` key:
20+
21+
render(Component, { props: { /** props here **/ } })
22+
`)
23+
this.name = 'UnknownSvelteOptionsError'
24+
}
25+
}
26+
27+
/**
28+
* Validate a component's mount options.
29+
*
30+
* @template {import('./types.js').Component} C
31+
* @param {import('./types.js').ComponentOptions<C>} options - props or mount options
32+
* @returns {Partial<import('./types.js').MountOptions<C>>}
33+
*/
34+
const validateOptions = (options) => {
35+
const isProps = !Object.keys(options).some((option) =>
36+
ALLOWED_MOUNT_OPTIONS.includes(option)
37+
)
38+
39+
if (isProps) {
40+
return { props: options }
41+
}
42+
43+
// Check if any props and Svelte options were accidentally mixed.
44+
const unknownOptions = Object.keys(options).filter(
45+
(option) => !ALLOWED_MOUNT_OPTIONS.includes(option)
46+
)
47+
48+
if (unknownOptions.length > 0) {
49+
throw new UnknownSvelteOptionsError(unknownOptions)
50+
}
51+
52+
return options
53+
}
54+
55+
/**
56+
* Set up the document to render a component.
57+
*
58+
* @template {import('./types.js').Component} C
59+
* @param {import('./types.js').ComponentOptions<C>} componentOptions - props or mount options
60+
* @param {{ baseElement?: HTMLElement | undefined }} setupOptions - base element of the document to bind any queries
61+
* @returns {{
62+
* baseElement: HTMLElement,
63+
* target: HTMLElement,
64+
* mountOptions: import('./types.js).MountOptions<C>
65+
* }}
66+
*/
67+
const setup = (componentOptions, setupOptions) => {
68+
const mountOptions = validateOptions(componentOptions)
69+
70+
const baseElement =
71+
setupOptions.baseElement ?? mountOptions.target ?? document.body
72+
73+
const target =
74+
mountOptions.target ??
75+
baseElement.appendChild(document.createElement('div'))
76+
77+
addCleanupTask(() => {
78+
if (target.parentNode === document.body) {
79+
target.remove()
80+
}
81+
})
82+
83+
return { baseElement, target, mountOptions: { ...mountOptions, target } }
84+
}
85+
86+
export { setup, UnknownSvelteOptionsError }
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/** Detect which version of Svelte we're using */
2+
import * as Svelte from 'svelte'
3+
4+
/** Whether we're using Svelte >= 5. */
5+
const IS_MODERN_SVELTE = typeof Svelte.mount === 'function'
6+
7+
export { IS_MODERN_SVELTE }

packages/svelte/src/component-types.d.ts renamed to packages/svelte/src/core/types.d.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
11
/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-deprecated */
2+
/**
3+
* Component and utility types.
4+
*
5+
* Supports components from Svelte 3, 4, and 5.
6+
*/
27
import type {
38
Component as ModernComponent,
49
ComponentConstructorOptions as LegacyConstructorOptions,
@@ -59,3 +64,8 @@ export type Exports<C> = IS_MODERN_SVELTE extends true
5964
export type MountOptions<C extends Component> = IS_MODERN_SVELTE extends true
6065
? Parameters<typeof mount<Props<C>, Exports<C>>>[1]
6166
: LegacyConstructorOptions<Props<C>>
67+
68+
/** A component's props or some of its mount options. */
69+
export type ComponentOptions<C extends Component> =
70+
| Props<C>
71+
| Partial<MountOptions<C>>

0 commit comments

Comments
 (0)