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 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.2.3...main).
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 [last published version](https://github.com/frontacles/frontacles/compare/0.2.2...0.2.3).
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](./src/math/bench)
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 [last published version](https://github.com/frontacles/frontacles/compare/0.2.1...0.2.2).
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** | 252 B |
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
- Trying to round `Infinity` or to round a number to an _infinite_ precision is also possible:
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
- Unlike most libraries using [RegEx 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 same mechanism as your browser, making it robust, and very likely RFC compliant.
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
- Get or set the username and the hostname separately.
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
- An `Email` object is converted to a string when used along another string, or by directly calling `toString`.
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
- Validate an email address with `Email.canParse`. It passes the complete Zod test suites, and beyond.
178
+ #### `Email.canParse`
132
179
 
133
- ```js
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
- 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.
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
- new Email('double@at@sign.com') // ❌ throw TypeError
185
+ Email.canParse('someone@domain.tld') // true
186
+ Email.canParse('invalid@email.com:3000') // false
142
187
  ```
143
188
 
144
- Another behaviour from the `URL` class: you can pass an `Email` object to the `Email` constructor (or to `Email.canParse`, but it doesn’t really make sense).
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.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-fix": "eslint --fix"
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 {};