frontacles 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -4
- package/README.md +61 -24
- package/package.json +3 -4
- package/src/url/email.js +18 -1
- package/types/index.d.ts +9 -2
package/CHANGELOG.md
CHANGED
|
@@ -8,11 +8,29 @@ Nothing for now.
|
|
|
8
8
|
|
|
9
9
|
<!-- ⚠️ Before a new release, make sure the documentation doesn't contain any **unreleased** mention. -->
|
|
10
10
|
|
|
11
|
-
Compare with [last published version](https://github.com/frontacles/frontacles/compare/0.
|
|
11
|
+
<!-- Compare with [last published version](https://github.com/frontacles/frontacles/compare/0.4.0...main). -->
|
|
12
|
+
|
|
13
|
+
## v0.4.0 (2025-03-08)
|
|
14
|
+
|
|
15
|
+
Compare with [previous version](https://github.com/frontacles/frontacles/compare/0.3.0...0.4.0).
|
|
16
|
+
|
|
17
|
+
### New
|
|
18
|
+
|
|
19
|
+
- Add [`isEmail`](./README.md#isemail) to validate emails.
|
|
20
|
+
|
|
21
|
+
### Documentation
|
|
22
|
+
|
|
23
|
+
- It is now more explicit in types that `Email.canParse` expects a `string` or a `Stringable` (changed from `any|Email` to `any|string|Email|Stringable`).
|
|
24
|
+
- Rephrase email documentation.
|
|
25
|
+
|
|
26
|
+
### Under the hood
|
|
27
|
+
|
|
28
|
+
- Centralize all benchmarks in [`./benchs`](./benchs).
|
|
29
|
+
- Benchmark [`Email`](./benchs/url).
|
|
12
30
|
|
|
13
31
|
## v0.3.0 (2025-03-06)
|
|
14
32
|
|
|
15
|
-
Compare with [
|
|
33
|
+
Compare with [previous version](https://github.com/frontacles/frontacles/compare/0.2.2...0.3.0).
|
|
16
34
|
|
|
17
35
|
### New
|
|
18
36
|
|
|
@@ -32,7 +50,7 @@ Compare with [last published version](https://github.com/frontacles/frontacles/c
|
|
|
32
50
|
### Under the hood
|
|
33
51
|
|
|
34
52
|
- Shorten `round` by a couple of Bytes.
|
|
35
|
-
- Benchmark [`round` implementations](./
|
|
53
|
+
- Benchmark [`round` implementations](./benchs/math)
|
|
36
54
|
- Add [Valibot test suite](./src/url/test-utils/valibot-suite.js) to `Email` (all tests are passing!).
|
|
37
55
|
|
|
38
56
|
### Documentation
|
|
@@ -43,7 +61,7 @@ Compare with [last published version](https://github.com/frontacles/frontacles/c
|
|
|
43
61
|
|
|
44
62
|
## v0.2.2 (2025-03-01)
|
|
45
63
|
|
|
46
|
-
Compare with [
|
|
64
|
+
Compare with [previous version](https://github.com/frontacles/frontacles/compare/0.2.1...0.2.2).
|
|
47
65
|
|
|
48
66
|
### Breaking
|
|
49
67
|
|
package/README.md
CHANGED
|
@@ -12,8 +12,9 @@ We love tiny bits (using brotli compression):
|
|
|
12
12
|
| math | [`clamp`](#clamp) | 35 B |
|
|
13
13
|
| math | [`round`](#round) | 38 B |
|
|
14
14
|
| string | [`capitalize`](#capitalize) | 40 B |
|
|
15
|
+
| url | [`isEmail`](#isemail) | 86 B |
|
|
15
16
|
| url | [`Email`](#email) | 173 B |
|
|
16
|
-
| | **everything** |
|
|
17
|
+
| | **everything** | 328 B |
|
|
17
18
|
|
|
18
19
|
## Math utils
|
|
19
20
|
|
|
@@ -46,10 +47,9 @@ clamp(Infinity, 0, 10) // 10
|
|
|
46
47
|
> [!NOTE]
|
|
47
48
|
> `clamp` mostly follows [`Math.clamp` TC39 proposal](https://github.com/tc39/proposal-math-clamp), except it doesn’t throw if you flip the order of the _min_ (2nd parameter) and _max_ (3rd parameter) numbers.
|
|
48
49
|
|
|
49
|
-
|
|
50
50
|
### `round`
|
|
51
51
|
|
|
52
|
-
Round a number to the (optionally) provided precision.
|
|
52
|
+
Round a number to the (optionally) provided decimal precision. The default precision is 0 (no decimal).
|
|
53
53
|
|
|
54
54
|
```js
|
|
55
55
|
import { round } from 'frontacles/math'
|
|
@@ -66,7 +66,7 @@ round(687.3456, -1) // 690
|
|
|
66
66
|
round(687.3456, -2) // 700
|
|
67
67
|
```
|
|
68
68
|
|
|
69
|
-
|
|
69
|
+
Using `Infinity` is also possible:
|
|
70
70
|
|
|
71
71
|
```js
|
|
72
72
|
round(Infinity, -2) // Infinity
|
|
@@ -97,11 +97,40 @@ capitalize('صحراء') // 'صحراء' (Arabic)
|
|
|
97
97
|
|
|
98
98
|
## URL utils
|
|
99
99
|
|
|
100
|
+
### `isEmail`
|
|
101
|
+
|
|
102
|
+
Tells whether a string is a valid email.
|
|
103
|
+
|
|
104
|
+
```js
|
|
105
|
+
isEmail('someone@domain.tld') // true
|
|
106
|
+
isEmail('invalid@email.com:3000') // false
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
> [!TIP]
|
|
110
|
+
> Should I use `isEmail` or [`Email.canParse`](#emailcanparse) to validate emails?
|
|
111
|
+
>
|
|
112
|
+
> Short answer: use `isEmail`.
|
|
113
|
+
>
|
|
114
|
+
> <details>
|
|
115
|
+
> <summary>Nuanced answer</summary>
|
|
116
|
+
>
|
|
117
|
+
> Your use case:
|
|
118
|
+
>
|
|
119
|
+
> - If you **only need to validate** email addresses, use `isEmail`.
|
|
120
|
+
> - If you also need to be able to get or set an email username or hostname **independently**, use `Email.canParse`.
|
|
121
|
+
>
|
|
122
|
+
> When using the `Email` class, you can still use `isEmail` if you want ultra-performance (e.g. your Node API validates tons of emails per seconds) because `isEmail` is 6✕ faster, at the cost of a bit less than 100 Bytes (compressed).
|
|
123
|
+
>
|
|
124
|
+
> The reason `isEmail` is faster is that it relies on a single RegExp while `Email.canParse` uses the browser built-in, which results in a bit more of computation, but with less code. For now, it’s not planned to use `isEmail` implementation in `Email.canParse` as it would increase its size by 50 Bytes.
|
|
125
|
+
>
|
|
126
|
+
> Keep in mind that **`Email.canParse` is fast enough** for the 99% use cases. Despite their implementation difference, both behave the same and pass the same tests.
|
|
127
|
+
> </details>
|
|
128
|
+
|
|
100
129
|
### `Email`
|
|
101
130
|
|
|
102
|
-
A class to instantiate an `Email` object or validate email addresses.
|
|
131
|
+
A class to instantiate an `Email` object or validate email addresses. It extends the [`URL` object](https://developer.mozilla.org/en-US/docs/Web/API/URL) and has similar predictable behaviors.
|
|
103
132
|
|
|
104
|
-
|
|
133
|
+
#### `Email.constructor`
|
|
105
134
|
|
|
106
135
|
```js
|
|
107
136
|
import { Email } from 'frontacles/url/email'
|
|
@@ -109,7 +138,23 @@ import { Email } from 'frontacles/url/email'
|
|
|
109
138
|
const email = new Email('someone@domain.tld')
|
|
110
139
|
```
|
|
111
140
|
|
|
112
|
-
|
|
141
|
+
Trying to instantiate an Email with an invalid address will throw. This behaviour is similar to the [`URL` constructor](https://developer.mozilla.org/en-US/docs/Web/API/URL/URL), since `Email` relies on it under the hood.
|
|
142
|
+
|
|
143
|
+
```js
|
|
144
|
+
new Email('double@at@sign.com') // ❌ throw TypeError
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Another behaviour from `URL`: passing an `Email` object to the `Email` constructor or to [`Email.canParse`](#emailcanparse) is possible.
|
|
148
|
+
|
|
149
|
+
```js
|
|
150
|
+
const email = new Email('someone@domain.tld')
|
|
151
|
+
const alsoEmail = new Email(email) // ✅ a new Email object!
|
|
152
|
+
Email.canParse(email) // ✅ true
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
#### `.username` and `.hostname`
|
|
156
|
+
|
|
157
|
+
Get or set the email username and hostname separately.
|
|
113
158
|
|
|
114
159
|
```js
|
|
115
160
|
email.username // 'someone'
|
|
@@ -121,35 +166,27 @@ email.hostname = 'newdomain.tld' // ✅ domain migrated
|
|
|
121
166
|
const { username, hostname } = new Email('someone@domain.tld')
|
|
122
167
|
```
|
|
123
168
|
|
|
124
|
-
|
|
169
|
+
#### `.toString`
|
|
170
|
+
|
|
171
|
+
In a string context, an `Email` object is automatically converted to a string, or manually by calling the `toString` method.
|
|
125
172
|
|
|
126
173
|
```js
|
|
127
174
|
console.log(`email: ${email}`) // 'email: someone@newdomain.tld'
|
|
128
175
|
console.log(email.toString()) // 'someone@newdomain.tld'
|
|
129
176
|
```
|
|
130
177
|
|
|
131
|
-
|
|
178
|
+
#### `Email.canParse`
|
|
132
179
|
|
|
133
|
-
|
|
134
|
-
Email.canParse('someone@domain.tld') // true
|
|
135
|
-
Email.canParse('invalid@email.com:3000') // false
|
|
136
|
-
```
|
|
180
|
+
Validate an email address with `Email.canParse`.
|
|
137
181
|
|
|
138
|
-
|
|
182
|
+
Unlike most libraries using [RegExp to validate a string is an email](https://github.com/colinhacks/zod/blob/e2b9a5f9ac67d13ada61cd8e4b1385eb850c7592/src/types.ts#L648-L663) (which is prone to [bugs](https://github.com/colinhacks/zod/issues/3913)), Frontacles `Email` relies on the built-in `URL` mechanisms, making it robust, and very likely RFC compliant. It passes [popular libraries test suites](./src/url/test-utils), and beyond.
|
|
139
183
|
|
|
140
184
|
```js
|
|
141
|
-
|
|
185
|
+
Email.canParse('someone@domain.tld') // true
|
|
186
|
+
Email.canParse('invalid@email.com:3000') // false
|
|
142
187
|
```
|
|
143
188
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
```js
|
|
147
|
-
const email = new Email('someone@domain.tld')
|
|
148
|
-
|
|
149
|
-
const alsoEmail = new Email(email) // ✅ a new Email object!
|
|
150
|
-
|
|
151
|
-
Email.canParse(email) // ✅ true
|
|
152
|
-
```
|
|
189
|
+
If `canParse` is all you need from the `Email` class, consider using [isEmail](#isemail) instead.
|
|
153
190
|
|
|
154
191
|
## Changelog
|
|
155
192
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "frontacles",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Front-end utilities for artisans",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -21,17 +21,16 @@
|
|
|
21
21
|
"test:types": "npm exec tsd",
|
|
22
22
|
"test:ui": "vitest --ui --coverage.enabled --coverage.exclude=types",
|
|
23
23
|
"coverage": "vitest run --coverage --coverage.exclude=types",
|
|
24
|
-
"bench": "vitest bench",
|
|
25
24
|
"watch": "vitest watch",
|
|
26
25
|
"build": "echo \"Nothing to build, this command is only here to please size-limit GitHub action\" && exit 0",
|
|
27
26
|
"size": "size-limit",
|
|
28
27
|
"lint": "eslint",
|
|
29
|
-
"lint
|
|
28
|
+
"lint:fix": "eslint --fix",
|
|
29
|
+
"lint:inspect": "eslint --inspect-config "
|
|
30
30
|
},
|
|
31
31
|
"files": [
|
|
32
32
|
"CHANGELOG.md",
|
|
33
33
|
"src/**/*.js",
|
|
34
|
-
"!src/**/bench",
|
|
35
34
|
"!src/**/test-utils",
|
|
36
35
|
"!src/**/*.test.js",
|
|
37
36
|
"types/index.d.ts"
|
package/src/url/email.js
CHANGED
|
@@ -17,6 +17,9 @@ export class Email extends URL {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
|
+
* The email address (`username@domain.tld`) as a string.
|
|
21
|
+
*
|
|
22
|
+
* (maintainer comment)
|
|
20
23
|
* Replace the string representation of the top-level class (`URL`) to be an
|
|
21
24
|
* email address instead of `ftp://username@domain.tld`. It is needed for
|
|
22
25
|
* situation where type casting to string is involved (`console.log`…).
|
|
@@ -38,7 +41,7 @@ export class Email extends URL {
|
|
|
38
41
|
/**
|
|
39
42
|
* Whether or not an email address is parsable and valid.
|
|
40
43
|
*
|
|
41
|
-
* @param {any|Email} address
|
|
44
|
+
* @param {any|string|Email|Stringable} address
|
|
42
45
|
*/
|
|
43
46
|
static canParse(address) {
|
|
44
47
|
try {
|
|
@@ -49,3 +52,17 @@ export class Email extends URL {
|
|
|
49
52
|
}
|
|
50
53
|
}
|
|
51
54
|
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Whether or not an email address is parsable and valid.
|
|
58
|
+
*
|
|
59
|
+
* It uses WHATWG recommended RegExp: https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address
|
|
60
|
+
*
|
|
61
|
+
* @param {any|string|Stringable} address
|
|
62
|
+
*/
|
|
63
|
+
export const isEmail = address => /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/.test(address)
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* @typedef {Object} Stringable
|
|
67
|
+
* @property {function(): string} toString - The object as a string.
|
|
68
|
+
*/
|
package/types/index.d.ts
CHANGED
|
@@ -17,14 +17,21 @@ export class Email extends URL {
|
|
|
17
17
|
/**
|
|
18
18
|
* Whether or not an email address is parsable and valid.
|
|
19
19
|
*
|
|
20
|
-
* @param {any|Email} address
|
|
20
|
+
* @param {any|string|Email|Stringable} address
|
|
21
21
|
*/
|
|
22
|
-
static canParse(address: any | Email): boolean;
|
|
22
|
+
static canParse(address: any | string | Email | Stringable): boolean;
|
|
23
23
|
/**
|
|
24
24
|
* @param {string|Email} address An email address like `someone@domain.tld`.
|
|
25
25
|
*/
|
|
26
26
|
constructor(address: string | Email);
|
|
27
27
|
#private;
|
|
28
28
|
}
|
|
29
|
+
export function isEmail(address: any | string | Stringable): boolean;
|
|
30
|
+
export type Stringable = {
|
|
31
|
+
/**
|
|
32
|
+
* - The object as a string.
|
|
33
|
+
*/
|
|
34
|
+
toString: () => string;
|
|
35
|
+
};
|
|
29
36
|
|
|
30
37
|
export {};
|