@doist/react-interpolate 0.3.0 → 0.3.9
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/README.md +49 -48
- package/dist/react-interpolate.cjs +17 -17
- package/dist/react-interpolate.mjs +17 -17
- package/package.json +73 -65
- package/src/constants.js +10 -10
- package/src/index.js +3 -8
- package/src/interpolate.js +13 -20
- package/src/lexer.js +8 -10
- package/src/node.js +11 -11
- package/src/parser.js +7 -8
- package/src/syntax.js +9 -14
- package/.eslintrc.js +0 -35
- package/.github/workflows/nodejs.yml +0 -24
- package/.prettierrc.json +0 -9
- package/CONTRIBUTING.md +0 -7
- package/__test__/Interpolate.test.js +0 -203
- package/__test__/parser.test.js +0 -45
- package/babel.config.js +0 -18
- package/dist/react-interpolate.min.cjs.gz +0 -0
- package/dist/react-interpolate.min.mjs.gz +0 -0
- package/jest.config.js +0 -3
- package/rollup.config.js +0 -17
package/README.md
CHANGED
|
@@ -1,21 +1,19 @@
|
|
|
1
1
|
# react-interpolate
|
|
2
2
|
|
|
3
|
-
[](http://img.badgesize.io/https://cdn.jsdelivr.net/gh/Doist/react-interpolate/dist/react-interpolate.min.cjs?compression=gzip)
|
|
4
|
-
[](https://github.com/Doist/react-interpolate/actions)
|
|
5
|
-
|
|
3
|
+
[](http://img.badgesize.io/https://cdn.jsdelivr.net/gh/Doist/react-interpolate/dist/react-interpolate.min.cjs?compression=gzip) [](https://github.com/Doist/react-interpolate/actions)
|
|
6
4
|
|
|
7
5
|
A string interpolation component that formats and interpolates a template string in a safe way.
|
|
8
6
|
|
|
9
7
|
```jsx
|
|
10
|
-
import Interpolate from
|
|
8
|
+
import Interpolate from '@doist/react-interpolate'
|
|
11
9
|
|
|
12
10
|
function Greeting() {
|
|
13
11
|
return (
|
|
14
12
|
<Interpolate
|
|
15
13
|
string="<h1>Hello {name}. Here is <a>your order info</a></h1>"
|
|
16
14
|
mapping={{
|
|
17
|
-
name:
|
|
18
|
-
a: <a href="https://orderinfo.com"
|
|
15
|
+
name: 'William',
|
|
16
|
+
a: <a href="https://orderinfo.com" />,
|
|
19
17
|
}}
|
|
20
18
|
/>
|
|
21
19
|
)
|
|
@@ -28,21 +26,22 @@ Would render the following HTML
|
|
|
28
26
|
<h1>Hello William. Here is <a href="https://orderinfo.com">your order info</a></h1>
|
|
29
27
|
```
|
|
30
28
|
|
|
31
|
-
|
|
32
29
|
## Component API
|
|
33
30
|
|
|
34
31
|
`<Interpolate>` component accepts the following props
|
|
35
32
|
|
|
36
33
|
#### `string`
|
|
37
|
-
The template string to be interpolated. Required.
|
|
38
34
|
|
|
39
|
-
|
|
35
|
+
The template string to be interpolated. Required.
|
|
36
|
+
|
|
37
|
+
Please see the [Interpolation syntax](./#interpolation-syntax) section below for more detail.
|
|
40
38
|
|
|
41
|
-
#### `mapping`
|
|
42
|
-
An object that defines the values to be injected for placeholder and tags defined in the template string. Optional.
|
|
39
|
+
#### `mapping`
|
|
43
40
|
|
|
44
|
-
|
|
45
|
-
|
|
41
|
+
An object that defines the values to be injected for placeholder and tags defined in the template string. Optional.
|
|
42
|
+
|
|
43
|
+
- For placeholder or self-closing tag, the mapping value could be any valid element value
|
|
44
|
+
- For open & close tag, the mapping value could be either renderer function or an element.
|
|
46
45
|
|
|
47
46
|
```jsx
|
|
48
47
|
<Interpolate
|
|
@@ -50,40 +49,33 @@ An object that defines the values to be injected for placeholder and tags define
|
|
|
50
49
|
Please contact <supportLink>support</supportLink> for help"
|
|
51
50
|
mapping={{
|
|
52
51
|
// you can map placholder and self-closing tag to any valid element value
|
|
53
|
-
name:
|
|
52
|
+
name: 'William',
|
|
54
53
|
hr: <hr className="break" />,
|
|
55
54
|
|
|
56
55
|
// you can map open & close tag to a rendering function
|
|
57
|
-
orderLink: text =>
|
|
58
|
-
<a href="https://orderinfo.com">{text}</a>
|
|
59
|
-
),
|
|
56
|
+
orderLink: (text) => <a href="https://orderinfo.com">{text}</a>,
|
|
60
57
|
|
|
61
58
|
// or you can map open & close tag to an element
|
|
62
|
-
supportLink: <a href="https://orderinfo.com"
|
|
59
|
+
supportLink: <a href="https://orderinfo.com" />,
|
|
63
60
|
}}
|
|
64
61
|
/>
|
|
65
62
|
```
|
|
66
63
|
|
|
64
|
+
#### `graceful`
|
|
67
65
|
|
|
68
|
-
|
|
69
|
-
A boolean flag indicates how string syntax error or mapping error should be handled. When true, the raw string value from the prop `string` would be rendered as a fallback in the error scenario. When false, error would be thrown instead.
|
|
66
|
+
A boolean flag indicates how string syntax error or mapping error should be handled. When true, the raw string value from the prop `string` would be rendered as a fallback in the error scenario. When false, error would be thrown instead.
|
|
70
67
|
|
|
71
68
|
Optional. `true` by default.
|
|
72
69
|
|
|
73
|
-
|
|
74
70
|
```jsx
|
|
75
71
|
// would render "an invalid string with unclose tag <h1>"
|
|
76
|
-
<Interpolate
|
|
77
|
-
graceful
|
|
78
|
-
string="an invalid string with unclose tag <h1>"
|
|
79
|
-
/>
|
|
72
|
+
<Interpolate graceful string="an invalid string with unclose tag <h1>" />
|
|
80
73
|
```
|
|
81
74
|
|
|
82
|
-
#### `syntax`
|
|
75
|
+
#### `syntax`
|
|
83
76
|
|
|
84
77
|
Optional. `syntax` props allow use of react-Interpolate with different string formatting syntax. Please see the ["Custom syntax support"](#custom-syntax-support) section for more detail.
|
|
85
78
|
|
|
86
|
-
|
|
87
79
|
## Interpolation syntax
|
|
88
80
|
|
|
89
81
|
Here is interpolation syntax you can use in your `string`.
|
|
@@ -91,7 +83,7 @@ Here is interpolation syntax you can use in your `string`.
|
|
|
91
83
|
#### Placeholder
|
|
92
84
|
|
|
93
85
|
```jsx
|
|
94
|
-
|
|
86
|
+
'hello {user_name}'
|
|
95
87
|
```
|
|
96
88
|
|
|
97
89
|
Placeholder name should be alphanumeric (`[A-Za-z0-9_]`). Placeholders could be mapped to any valid element value.
|
|
@@ -99,18 +91,18 @@ Placeholder name should be alphanumeric (`[A-Za-z0-9_]`). Placeholders could be
|
|
|
99
91
|
#### Open & close tags
|
|
100
92
|
|
|
101
93
|
```jsx
|
|
102
|
-
|
|
94
|
+
'Here is <a>your order info</a>'
|
|
103
95
|
|
|
104
96
|
// tag name could be any alphanumeric string
|
|
105
|
-
|
|
97
|
+
'Here is <link>your order info</link>'
|
|
106
98
|
|
|
107
99
|
// you can nest tag and placeholder
|
|
108
|
-
|
|
100
|
+
'Here is <a><b>you order info {name}</b></a>'
|
|
109
101
|
```
|
|
110
102
|
|
|
111
|
-
Tag name should be alphanumeric (`[A-Za-z0-9_]`).
|
|
103
|
+
Tag name should be alphanumeric (`[A-Za-z0-9_]`).
|
|
112
104
|
|
|
113
|
-
Open & close tag could be mapped to an element value.
|
|
105
|
+
Open & close tag could be mapped to an element value.
|
|
114
106
|
|
|
115
107
|
```jsx
|
|
116
108
|
<Interpolate
|
|
@@ -135,64 +127,60 @@ Open & close tag could be mapped to an element value.
|
|
|
135
127
|
/>
|
|
136
128
|
```
|
|
137
129
|
|
|
138
|
-
Open & close tag could be mapped to a rendering function, which would take a single argument that contains the enclosing text.
|
|
130
|
+
Open & close tag could be mapped to a rendering function, which would take a single argument that contains the enclosing text.
|
|
139
131
|
|
|
140
132
|
```jsx
|
|
141
133
|
<Interpolate
|
|
142
134
|
string="Here is <a>your order info</a>"
|
|
143
135
|
mapping={{
|
|
144
|
-
a: text => (
|
|
136
|
+
a: (text) => (
|
|
145
137
|
<a href="https://orderinfo.com">
|
|
146
138
|
<b>{text}</b>
|
|
147
139
|
<br />
|
|
148
140
|
</a>
|
|
149
|
-
)
|
|
141
|
+
),
|
|
150
142
|
}}
|
|
151
143
|
/>
|
|
152
144
|
```
|
|
153
145
|
|
|
154
|
-
|
|
155
|
-
|
|
156
146
|
Unclosed tag or incorrect nesting of tag would result in syntax error.
|
|
157
147
|
|
|
158
148
|
```js
|
|
159
149
|
// bad: no close tag
|
|
160
|
-
|
|
150
|
+
'Here is <a>your order info'
|
|
161
151
|
|
|
162
152
|
// bad: incorrect tag structure
|
|
163
|
-
|
|
153
|
+
'Here is <a><b>your order info</a></b>'
|
|
164
154
|
```
|
|
165
155
|
|
|
166
|
-
#### Self closing tag
|
|
156
|
+
#### Self closing tag
|
|
167
157
|
|
|
168
158
|
```js
|
|
169
|
-
|
|
159
|
+
'Hello.<br/>Here is your order'
|
|
170
160
|
```
|
|
171
161
|
|
|
172
162
|
Tag name should be alphanumeric (`[A-Za-z0-9_]`). Self closing tags could be mapped to any valid element value.
|
|
173
163
|
|
|
174
|
-
|
|
175
164
|
## Auto tag element creation
|
|
176
165
|
|
|
177
|
-
When tags are used the string but there are no correponding mapped value, it would by default create the corresponding HTML element by default.
|
|
166
|
+
When tags are used the string but there are no correponding mapped value, it would by default create the corresponding HTML element by default.
|
|
178
167
|
|
|
179
168
|
```jsx
|
|
180
169
|
// would render: <h1>Hellow</h1><br/>World
|
|
181
|
-
<Interpolate string="<h1>Hello</h1><br/>world"/>
|
|
170
|
+
<Interpolate string="<h1>Hello</h1><br/>world" />
|
|
182
171
|
```
|
|
183
172
|
|
|
184
|
-
|
|
185
|
-
|
|
186
173
|
## Custom syntax support
|
|
187
174
|
|
|
188
175
|
You may already be using a formatting syntax in your string that is different than the built-in syntax support from Interpolate. You can configure Interpolate so that it could recognize the formatting syntax that you use.
|
|
189
176
|
|
|
190
177
|
For instance, you may be using [i18next](https://www.i18next.com/) which has a slightly different placeholder syntax.
|
|
178
|
+
|
|
191
179
|
```
|
|
192
180
|
hello {{name}}
|
|
193
181
|
```
|
|
194
182
|
|
|
195
|
-
You can define the formatting syntax of your string via
|
|
183
|
+
You can define the formatting syntax of your string via `syntax` props.
|
|
196
184
|
|
|
197
185
|
```jsx
|
|
198
186
|
import Interpolate, { TOKEN_PLACEHOLDER } from "react-interpolate"
|
|
@@ -225,3 +213,16 @@ import { SYNTAX_I18NEXT } from "react-interpolate"
|
|
|
225
213
|
/>
|
|
226
214
|
```
|
|
227
215
|
|
|
216
|
+
# Releasing
|
|
217
|
+
|
|
218
|
+
A new version of @doist/react-interpolate is published both on npm and GitHub Package Registry whenever a new release on GitHub is created.
|
|
219
|
+
|
|
220
|
+
To update the version in both `package.json` and `package-lock.json` run:
|
|
221
|
+
|
|
222
|
+
```sh
|
|
223
|
+
npm --no-git-tag-version version <major|minor|patch>
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
Once these changes have been pushed and merged, create a release on GitHub.
|
|
227
|
+
|
|
228
|
+
A GitHub Action will automatically perform all the necessary steps and will release the version number that's specified inside the `package.json`'s `version` field so make sure that the release tag reflects the version you want to publish.
|
|
@@ -16,16 +16,16 @@ var _classCallCheck__default = /*#__PURE__*/_interopDefaultLegacy(_classCallChec
|
|
|
16
16
|
var _createClass__default = /*#__PURE__*/_interopDefaultLegacy(_createClass);
|
|
17
17
|
var _objectWithoutProperties__default = /*#__PURE__*/_interopDefaultLegacy(_objectWithoutProperties);
|
|
18
18
|
|
|
19
|
-
var TOKEN_PLACEHOLDER =
|
|
20
|
-
var TOKEN_OPEN_TAG =
|
|
21
|
-
var TOKEN_CLOSE_TAG =
|
|
22
|
-
var TOKEN_SELF_TAG =
|
|
23
|
-
var TOKEN_TEXT =
|
|
24
|
-
var NODE_FRAGMENT =
|
|
25
|
-
var NODE_TAG_ELEMENT =
|
|
26
|
-
var NODE_VOID_ELEMENT =
|
|
27
|
-
var NODE_PLACEHOLDER =
|
|
28
|
-
var NODE_TEXT =
|
|
19
|
+
var TOKEN_PLACEHOLDER = 'TOKEN_PLACEHOLDER';
|
|
20
|
+
var TOKEN_OPEN_TAG = 'TOKEN_OPEN_TAG';
|
|
21
|
+
var TOKEN_CLOSE_TAG = 'TOKEN_CLOSE_TAG';
|
|
22
|
+
var TOKEN_SELF_TAG = 'TOKEN_SELF_TAG';
|
|
23
|
+
var TOKEN_TEXT = 'TOKEN_TEXT';
|
|
24
|
+
var NODE_FRAGMENT = 'NODE_FRAGMENT';
|
|
25
|
+
var NODE_TAG_ELEMENT = 'NODE_TAG_ELEMENT';
|
|
26
|
+
var NODE_VOID_ELEMENT = 'NODE_VOID_ELEMENT';
|
|
27
|
+
var NODE_PLACEHOLDER = 'NODE_PLACEHOLDER';
|
|
28
|
+
var NODE_TEXT = 'NODE_TEXT';
|
|
29
29
|
|
|
30
30
|
var _excluded = ["type", "children"];
|
|
31
31
|
|
|
@@ -193,7 +193,7 @@ function lexer(string, syntax) {
|
|
|
193
193
|
});
|
|
194
194
|
var text = string.substring(start);
|
|
195
195
|
|
|
196
|
-
if (text !==
|
|
196
|
+
if (text !== '') {
|
|
197
197
|
textTokens.push({
|
|
198
198
|
type: TOKEN_TEXT,
|
|
199
199
|
string: text,
|
|
@@ -217,10 +217,10 @@ function parser(string, syntax) {
|
|
|
217
217
|
var p = new Parser(tokens);
|
|
218
218
|
return p.parse();
|
|
219
219
|
}
|
|
220
|
-
var SYNTAX_ERROR =
|
|
220
|
+
var SYNTAX_ERROR = 'Syntax error. Please check if each open tag is closed correctly'; // A special token representing end of the tream
|
|
221
221
|
|
|
222
222
|
var EPSILON = {
|
|
223
|
-
type:
|
|
223
|
+
type: 'EPSILON'
|
|
224
224
|
};
|
|
225
225
|
/*
|
|
226
226
|
* A recursive descent parser that construct a syntax tree represeting
|
|
@@ -413,7 +413,7 @@ var createElement = function createElement(node, mapping, keyPrefix) {
|
|
|
413
413
|
{
|
|
414
414
|
var val = mapping[node.name];
|
|
415
415
|
|
|
416
|
-
if (typeof val ===
|
|
416
|
+
if (typeof val === 'function') {
|
|
417
417
|
return val();
|
|
418
418
|
}
|
|
419
419
|
|
|
@@ -428,13 +428,13 @@ var createElement = function createElement(node, mapping, keyPrefix) {
|
|
|
428
428
|
return /*#__PURE__*/React__default["default"].createElement(node.name, null, children);
|
|
429
429
|
}
|
|
430
430
|
|
|
431
|
-
if (typeof _val ===
|
|
431
|
+
if (typeof _val === 'function') {
|
|
432
432
|
return _val(children);
|
|
433
433
|
}
|
|
434
434
|
|
|
435
435
|
if ( /*#__PURE__*/React__default["default"].isValidElement(_val)) {
|
|
436
436
|
if (React__default["default"].Children.count(_val.props.children) !== 0) {
|
|
437
|
-
throw new Error(
|
|
437
|
+
throw new Error('when passing an element as value, the element should not contains children');
|
|
438
438
|
}
|
|
439
439
|
|
|
440
440
|
return /*#__PURE__*/React__default["default"].cloneElement(_val, {
|
|
@@ -454,7 +454,7 @@ var createElement = function createElement(node, mapping, keyPrefix) {
|
|
|
454
454
|
return node.string;
|
|
455
455
|
}
|
|
456
456
|
|
|
457
|
-
if (typeof _val2 ===
|
|
457
|
+
if (typeof _val2 === 'function') {
|
|
458
458
|
return _val2();
|
|
459
459
|
}
|
|
460
460
|
|
|
@@ -4,16 +4,16 @@ import _classCallCheck from '@babel/runtime/helpers/classCallCheck';
|
|
|
4
4
|
import _createClass from '@babel/runtime/helpers/createClass';
|
|
5
5
|
import _objectWithoutProperties from '@babel/runtime/helpers/objectWithoutProperties';
|
|
6
6
|
|
|
7
|
-
var TOKEN_PLACEHOLDER =
|
|
8
|
-
var TOKEN_OPEN_TAG =
|
|
9
|
-
var TOKEN_CLOSE_TAG =
|
|
10
|
-
var TOKEN_SELF_TAG =
|
|
11
|
-
var TOKEN_TEXT =
|
|
12
|
-
var NODE_FRAGMENT =
|
|
13
|
-
var NODE_TAG_ELEMENT =
|
|
14
|
-
var NODE_VOID_ELEMENT =
|
|
15
|
-
var NODE_PLACEHOLDER =
|
|
16
|
-
var NODE_TEXT =
|
|
7
|
+
var TOKEN_PLACEHOLDER = 'TOKEN_PLACEHOLDER';
|
|
8
|
+
var TOKEN_OPEN_TAG = 'TOKEN_OPEN_TAG';
|
|
9
|
+
var TOKEN_CLOSE_TAG = 'TOKEN_CLOSE_TAG';
|
|
10
|
+
var TOKEN_SELF_TAG = 'TOKEN_SELF_TAG';
|
|
11
|
+
var TOKEN_TEXT = 'TOKEN_TEXT';
|
|
12
|
+
var NODE_FRAGMENT = 'NODE_FRAGMENT';
|
|
13
|
+
var NODE_TAG_ELEMENT = 'NODE_TAG_ELEMENT';
|
|
14
|
+
var NODE_VOID_ELEMENT = 'NODE_VOID_ELEMENT';
|
|
15
|
+
var NODE_PLACEHOLDER = 'NODE_PLACEHOLDER';
|
|
16
|
+
var NODE_TEXT = 'NODE_TEXT';
|
|
17
17
|
|
|
18
18
|
var _excluded = ["type", "children"];
|
|
19
19
|
|
|
@@ -181,7 +181,7 @@ function lexer(string, syntax) {
|
|
|
181
181
|
});
|
|
182
182
|
var text = string.substring(start);
|
|
183
183
|
|
|
184
|
-
if (text !==
|
|
184
|
+
if (text !== '') {
|
|
185
185
|
textTokens.push({
|
|
186
186
|
type: TOKEN_TEXT,
|
|
187
187
|
string: text,
|
|
@@ -205,10 +205,10 @@ function parser(string, syntax) {
|
|
|
205
205
|
var p = new Parser(tokens);
|
|
206
206
|
return p.parse();
|
|
207
207
|
}
|
|
208
|
-
var SYNTAX_ERROR =
|
|
208
|
+
var SYNTAX_ERROR = 'Syntax error. Please check if each open tag is closed correctly'; // A special token representing end of the tream
|
|
209
209
|
|
|
210
210
|
var EPSILON = {
|
|
211
|
-
type:
|
|
211
|
+
type: 'EPSILON'
|
|
212
212
|
};
|
|
213
213
|
/*
|
|
214
214
|
* A recursive descent parser that construct a syntax tree represeting
|
|
@@ -401,7 +401,7 @@ var createElement = function createElement(node, mapping, keyPrefix) {
|
|
|
401
401
|
{
|
|
402
402
|
var val = mapping[node.name];
|
|
403
403
|
|
|
404
|
-
if (typeof val ===
|
|
404
|
+
if (typeof val === 'function') {
|
|
405
405
|
return val();
|
|
406
406
|
}
|
|
407
407
|
|
|
@@ -416,13 +416,13 @@ var createElement = function createElement(node, mapping, keyPrefix) {
|
|
|
416
416
|
return /*#__PURE__*/React.createElement(node.name, null, children);
|
|
417
417
|
}
|
|
418
418
|
|
|
419
|
-
if (typeof _val ===
|
|
419
|
+
if (typeof _val === 'function') {
|
|
420
420
|
return _val(children);
|
|
421
421
|
}
|
|
422
422
|
|
|
423
423
|
if ( /*#__PURE__*/React.isValidElement(_val)) {
|
|
424
424
|
if (React.Children.count(_val.props.children) !== 0) {
|
|
425
|
-
throw new Error(
|
|
425
|
+
throw new Error('when passing an element as value, the element should not contains children');
|
|
426
426
|
}
|
|
427
427
|
|
|
428
428
|
return /*#__PURE__*/React.cloneElement(_val, {
|
|
@@ -442,7 +442,7 @@ var createElement = function createElement(node, mapping, keyPrefix) {
|
|
|
442
442
|
return node.string;
|
|
443
443
|
}
|
|
444
444
|
|
|
445
|
-
if (typeof _val2 ===
|
|
445
|
+
if (typeof _val2 === 'function') {
|
|
446
446
|
return _val2();
|
|
447
447
|
}
|
|
448
448
|
|
package/package.json
CHANGED
|
@@ -1,67 +1,75 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
"
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
"
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
"
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
"
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
2
|
+
"name": "@doist/react-interpolate",
|
|
3
|
+
"version": "0.3.9",
|
|
4
|
+
"description": "A string interpolation component that formats and interpolates a template string in a safe way",
|
|
5
|
+
"main": "dist/react-interpolate.cjs",
|
|
6
|
+
"module": "dist/react-interpolate.mjs",
|
|
7
|
+
"engines": {
|
|
8
|
+
"node": "16",
|
|
9
|
+
"npm": "8"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"dist",
|
|
13
|
+
"src"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"test": "jest",
|
|
17
|
+
"lint": "eslint ./src ./__test__",
|
|
18
|
+
"build": "del dist && rollup -c && npm run mini-cjs && npm run mini-mjs",
|
|
19
|
+
"mini-cjs": "uglifyjs dist/react-interpolate.cjs --compress --mangle --enclose --output dist/react-interpolate.min.cjs",
|
|
20
|
+
"mini-mjs": "uglifyjs dist/react-interpolate.mjs --compress --mangle --enclose --output dist/react-interpolate.min.mjs",
|
|
21
|
+
"prettify": "prettier --write ."
|
|
22
|
+
},
|
|
23
|
+
"prettier": "@doist/prettier-config",
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "https://github.com/Doist/react-interpolate.git"
|
|
27
|
+
},
|
|
28
|
+
"keywords": [
|
|
29
|
+
"react",
|
|
30
|
+
"interpolate",
|
|
31
|
+
"template",
|
|
32
|
+
"format",
|
|
33
|
+
"text",
|
|
34
|
+
"string"
|
|
35
|
+
],
|
|
36
|
+
"author": "Steven Kao",
|
|
37
|
+
"license": "ISC",
|
|
38
|
+
"peerDependencies": {
|
|
39
|
+
"react": "^17.0.2",
|
|
40
|
+
"react-dom": "^17.0.2"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@babel/cli": "^7.16.0",
|
|
44
|
+
"@babel/core": "^7.16.0",
|
|
45
|
+
"@babel/eslint-parser": "^7.16.3",
|
|
46
|
+
"@babel/plugin-transform-runtime": "^7.16.4",
|
|
47
|
+
"@babel/preset-env": "^7.16.4",
|
|
48
|
+
"@babel/preset-react": "^7.16.0",
|
|
49
|
+
"@doist/eslint-config": "^7.0.0",
|
|
50
|
+
"@doist/prettier-config": "^3.0.5",
|
|
51
|
+
"@testing-library/react": "^12.1.2",
|
|
52
|
+
"babel-jest": "^27.4.1",
|
|
53
|
+
"core-js": "^3.19.2",
|
|
54
|
+
"del-cli": "^4.0.1",
|
|
55
|
+
"eslint": "^8.3.0",
|
|
56
|
+
"eslint-config-prettier": "^8.3.0",
|
|
57
|
+
"eslint-plugin-compat": "^4.0.0",
|
|
58
|
+
"eslint-plugin-import": "^2.25.3",
|
|
59
|
+
"eslint-plugin-jest": "^25.3.0",
|
|
60
|
+
"eslint-plugin-prettier": "^4.0.0",
|
|
61
|
+
"eslint-plugin-react": "^7.27.1",
|
|
62
|
+
"eslint-plugin-react-hooks": "^4.3.0",
|
|
63
|
+
"gzip-cli": "^1.2.0",
|
|
64
|
+
"jest": "^27.4.1",
|
|
65
|
+
"prettier": "^2.5.0",
|
|
66
|
+
"react": "^17.0.2",
|
|
67
|
+
"react-dom": "^17.0.2",
|
|
68
|
+
"rollup": "^2.60.2",
|
|
69
|
+
"rollup-plugin-babel": "^4.4.0",
|
|
70
|
+
"uglify-es": "^3.3.9"
|
|
71
|
+
},
|
|
72
|
+
"dependencies": {
|
|
73
|
+
"@babel/runtime": "^7.16.3"
|
|
74
|
+
}
|
|
67
75
|
}
|
package/src/constants.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
export const TOKEN_PLACEHOLDER =
|
|
2
|
-
export const TOKEN_OPEN_TAG =
|
|
3
|
-
export const TOKEN_CLOSE_TAG =
|
|
4
|
-
export const TOKEN_SELF_TAG =
|
|
5
|
-
export const TOKEN_TEXT =
|
|
1
|
+
export const TOKEN_PLACEHOLDER = 'TOKEN_PLACEHOLDER'
|
|
2
|
+
export const TOKEN_OPEN_TAG = 'TOKEN_OPEN_TAG'
|
|
3
|
+
export const TOKEN_CLOSE_TAG = 'TOKEN_CLOSE_TAG'
|
|
4
|
+
export const TOKEN_SELF_TAG = 'TOKEN_SELF_TAG'
|
|
5
|
+
export const TOKEN_TEXT = 'TOKEN_TEXT'
|
|
6
6
|
|
|
7
|
-
export const NODE_FRAGMENT =
|
|
8
|
-
export const NODE_TAG_ELEMENT =
|
|
9
|
-
export const NODE_VOID_ELEMENT =
|
|
10
|
-
export const NODE_PLACEHOLDER =
|
|
11
|
-
export const NODE_TEXT =
|
|
7
|
+
export const NODE_FRAGMENT = 'NODE_FRAGMENT'
|
|
8
|
+
export const NODE_TAG_ELEMENT = 'NODE_TAG_ELEMENT'
|
|
9
|
+
export const NODE_VOID_ELEMENT = 'NODE_VOID_ELEMENT'
|
|
10
|
+
export const NODE_PLACEHOLDER = 'NODE_PLACEHOLDER'
|
|
11
|
+
export const NODE_TEXT = 'NODE_TEXT'
|
package/src/index.js
CHANGED
|
@@ -1,10 +1,5 @@
|
|
|
1
|
-
import Interpolate from
|
|
1
|
+
import Interpolate from './interpolate'
|
|
2
2
|
export default Interpolate
|
|
3
3
|
|
|
4
|
-
export { SYNTAX_I18NEXT, SYNTAX_BUILT_IN } from
|
|
5
|
-
export {
|
|
6
|
-
TOKEN_PLACEHOLDER,
|
|
7
|
-
TOKEN_OPEN_TAG,
|
|
8
|
-
TOKEN_CLOSE_TAG,
|
|
9
|
-
TOKEN_SELF_TAG
|
|
10
|
-
} from "./constants"
|
|
4
|
+
export { SYNTAX_I18NEXT, SYNTAX_BUILT_IN } from './syntax'
|
|
5
|
+
export { TOKEN_PLACEHOLDER, TOKEN_OPEN_TAG, TOKEN_CLOSE_TAG, TOKEN_SELF_TAG } from './constants'
|
package/src/interpolate.js
CHANGED
|
@@ -1,19 +1,17 @@
|
|
|
1
|
-
import React from
|
|
2
|
-
import PropTypes from
|
|
3
|
-
import parser from
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import PropTypes from 'prop-types'
|
|
3
|
+
import parser from './parser'
|
|
4
4
|
import {
|
|
5
5
|
NODE_FRAGMENT,
|
|
6
6
|
NODE_TAG_ELEMENT,
|
|
7
7
|
NODE_VOID_ELEMENT,
|
|
8
8
|
NODE_PLACEHOLDER,
|
|
9
|
-
NODE_TEXT
|
|
10
|
-
} from
|
|
9
|
+
NODE_TEXT,
|
|
10
|
+
} from './constants'
|
|
11
11
|
|
|
12
12
|
const createElement = (node, mapping, keyPrefix) => {
|
|
13
13
|
const children = node.children.map((c, i) => (
|
|
14
|
-
<React.Fragment key={keyPrefix + i}>
|
|
15
|
-
{createElement(c, mapping, keyPrefix)}
|
|
16
|
-
</React.Fragment>
|
|
14
|
+
<React.Fragment key={keyPrefix + i}>{createElement(c, mapping, keyPrefix)}</React.Fragment>
|
|
17
15
|
))
|
|
18
16
|
|
|
19
17
|
switch (node.type) {
|
|
@@ -25,7 +23,7 @@ const createElement = (node, mapping, keyPrefix) => {
|
|
|
25
23
|
}
|
|
26
24
|
case NODE_VOID_ELEMENT: {
|
|
27
25
|
const val = mapping[node.name]
|
|
28
|
-
if (typeof val ===
|
|
26
|
+
if (typeof val === 'function') {
|
|
29
27
|
return val()
|
|
30
28
|
}
|
|
31
29
|
|
|
@@ -38,14 +36,14 @@ const createElement = (node, mapping, keyPrefix) => {
|
|
|
38
36
|
return React.createElement(node.name, null, children)
|
|
39
37
|
}
|
|
40
38
|
|
|
41
|
-
if (typeof val ===
|
|
39
|
+
if (typeof val === 'function') {
|
|
42
40
|
return val(children)
|
|
43
41
|
}
|
|
44
42
|
|
|
45
43
|
if (React.isValidElement(val)) {
|
|
46
44
|
if (React.Children.count(val.props.children) !== 0) {
|
|
47
45
|
throw new Error(
|
|
48
|
-
|
|
46
|
+
'when passing an element as value, the element should not contains children',
|
|
49
47
|
)
|
|
50
48
|
}
|
|
51
49
|
|
|
@@ -53,7 +51,7 @@ const createElement = (node, mapping, keyPrefix) => {
|
|
|
53
51
|
}
|
|
54
52
|
|
|
55
53
|
throw new Error(
|
|
56
|
-
`Invalid mapping value for "${node.name}". Only element or render function are accepted
|
|
54
|
+
`Invalid mapping value for "${node.name}". Only element or render function are accepted`,
|
|
57
55
|
)
|
|
58
56
|
}
|
|
59
57
|
case NODE_PLACEHOLDER: {
|
|
@@ -63,7 +61,7 @@ const createElement = (node, mapping, keyPrefix) => {
|
|
|
63
61
|
return node.string
|
|
64
62
|
}
|
|
65
63
|
|
|
66
|
-
if (typeof val ===
|
|
64
|
+
if (typeof val === 'function') {
|
|
67
65
|
return val()
|
|
68
66
|
}
|
|
69
67
|
|
|
@@ -74,12 +72,7 @@ const createElement = (node, mapping, keyPrefix) => {
|
|
|
74
72
|
}
|
|
75
73
|
}
|
|
76
74
|
|
|
77
|
-
export default function Interpolate({
|
|
78
|
-
string,
|
|
79
|
-
syntax,
|
|
80
|
-
mapping = {},
|
|
81
|
-
graceful = true
|
|
82
|
-
}) {
|
|
75
|
+
export default function Interpolate({ string, syntax, mapping = {}, graceful = true }) {
|
|
83
76
|
try {
|
|
84
77
|
const tree = parser(string, syntax)
|
|
85
78
|
return createElement(tree, mapping, string)
|
|
@@ -96,5 +89,5 @@ export default function Interpolate({
|
|
|
96
89
|
Interpolate.propTypes = {
|
|
97
90
|
string: PropTypes.string.isRequired,
|
|
98
91
|
mapping: PropTypes.object,
|
|
99
|
-
graceful: PropTypes.bool
|
|
92
|
+
graceful: PropTypes.bool,
|
|
100
93
|
}
|
package/src/lexer.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { TOKEN_TEXT } from
|
|
2
|
-
export { SYNTAX_I18NEXT, SYNTAX_BUILT_IN } from
|
|
1
|
+
import { TOKEN_TEXT } from './constants'
|
|
2
|
+
export { SYNTAX_I18NEXT, SYNTAX_BUILT_IN } from './syntax'
|
|
3
3
|
|
|
4
4
|
/*
|
|
5
5
|
* return a array of token object
|
|
@@ -17,7 +17,7 @@ export default function lexer(string, syntax) {
|
|
|
17
17
|
string: res[0],
|
|
18
18
|
name: res[1],
|
|
19
19
|
start: res.index,
|
|
20
|
-
end: res.index + res[0].length
|
|
20
|
+
end: res.index + res[0].length,
|
|
21
21
|
})
|
|
22
22
|
}
|
|
23
23
|
}
|
|
@@ -26,7 +26,7 @@ export default function lexer(string, syntax) {
|
|
|
26
26
|
const textTokens = []
|
|
27
27
|
|
|
28
28
|
let start = 0
|
|
29
|
-
matches.forEach(match => {
|
|
29
|
+
matches.forEach((match) => {
|
|
30
30
|
if (match.start === start) {
|
|
31
31
|
start = match.end
|
|
32
32
|
return
|
|
@@ -40,25 +40,23 @@ export default function lexer(string, syntax) {
|
|
|
40
40
|
string: text,
|
|
41
41
|
text,
|
|
42
42
|
start,
|
|
43
|
-
end
|
|
43
|
+
end,
|
|
44
44
|
})
|
|
45
45
|
|
|
46
46
|
start = match.end
|
|
47
47
|
})
|
|
48
48
|
|
|
49
49
|
const text = string.substring(start)
|
|
50
|
-
if (text !==
|
|
50
|
+
if (text !== '') {
|
|
51
51
|
textTokens.push({
|
|
52
52
|
type: TOKEN_TEXT,
|
|
53
53
|
string: text,
|
|
54
54
|
start,
|
|
55
|
-
end: string.length
|
|
55
|
+
end: string.length,
|
|
56
56
|
})
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
const tokens = []
|
|
60
|
-
.concat(textTokens, matches)
|
|
61
|
-
.sort((a, b) => a.start - b.start)
|
|
59
|
+
const tokens = [].concat(textTokens, matches).sort((a, b) => a.start - b.start)
|
|
62
60
|
|
|
63
61
|
return tokens
|
|
64
62
|
}
|
package/src/node.js
CHANGED
|
@@ -3,8 +3,8 @@ import {
|
|
|
3
3
|
NODE_TAG_ELEMENT,
|
|
4
4
|
NODE_VOID_ELEMENT,
|
|
5
5
|
NODE_PLACEHOLDER,
|
|
6
|
-
NODE_TEXT
|
|
7
|
-
} from
|
|
6
|
+
NODE_TEXT,
|
|
7
|
+
} from './constants'
|
|
8
8
|
|
|
9
9
|
export default class Node {
|
|
10
10
|
constructor({ type, children, ...fields }) {
|
|
@@ -34,32 +34,32 @@ Node.createTagNode = (token, children) =>
|
|
|
34
34
|
type: NODE_TAG_ELEMENT,
|
|
35
35
|
children,
|
|
36
36
|
name: token.name,
|
|
37
|
-
token
|
|
37
|
+
token,
|
|
38
38
|
})
|
|
39
39
|
|
|
40
|
-
Node.createFragmentNode = children =>
|
|
40
|
+
Node.createFragmentNode = (children) =>
|
|
41
41
|
new Node({
|
|
42
42
|
type: NODE_FRAGMENT,
|
|
43
|
-
children
|
|
43
|
+
children,
|
|
44
44
|
})
|
|
45
45
|
|
|
46
|
-
Node.createVoidNode = token =>
|
|
46
|
+
Node.createVoidNode = (token) =>
|
|
47
47
|
new Node({
|
|
48
48
|
type: NODE_VOID_ELEMENT,
|
|
49
49
|
name: token.name,
|
|
50
|
-
token
|
|
50
|
+
token,
|
|
51
51
|
})
|
|
52
52
|
|
|
53
|
-
Node.createTextNode = token =>
|
|
53
|
+
Node.createTextNode = (token) =>
|
|
54
54
|
new Node({
|
|
55
55
|
type: NODE_TEXT,
|
|
56
56
|
text: token.string,
|
|
57
|
-
token
|
|
57
|
+
token,
|
|
58
58
|
})
|
|
59
59
|
|
|
60
|
-
Node.createPlaceholderNode = token =>
|
|
60
|
+
Node.createPlaceholderNode = (token) =>
|
|
61
61
|
new Node({
|
|
62
62
|
type: NODE_PLACEHOLDER,
|
|
63
63
|
name: token.name,
|
|
64
|
-
token
|
|
64
|
+
token,
|
|
65
65
|
})
|
package/src/parser.js
CHANGED
|
@@ -3,11 +3,11 @@ import {
|
|
|
3
3
|
TOKEN_OPEN_TAG,
|
|
4
4
|
TOKEN_CLOSE_TAG,
|
|
5
5
|
TOKEN_SELF_TAG,
|
|
6
|
-
TOKEN_TEXT
|
|
7
|
-
} from
|
|
8
|
-
import Node from
|
|
9
|
-
import lexer from
|
|
10
|
-
import { SYNTAX_BUILT_IN } from
|
|
6
|
+
TOKEN_TEXT,
|
|
7
|
+
} from './constants'
|
|
8
|
+
import Node from './node.js'
|
|
9
|
+
import lexer from './lexer'
|
|
10
|
+
import { SYNTAX_BUILT_IN } from './syntax'
|
|
11
11
|
|
|
12
12
|
export default function parser(string, syntax) {
|
|
13
13
|
if (!syntax) {
|
|
@@ -19,12 +19,11 @@ export default function parser(string, syntax) {
|
|
|
19
19
|
return p.parse()
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
const SYNTAX_ERROR =
|
|
23
|
-
"Syntax error. Please check if each open tag is closed correctly"
|
|
22
|
+
const SYNTAX_ERROR = 'Syntax error. Please check if each open tag is closed correctly'
|
|
24
23
|
|
|
25
24
|
// A special token representing end of the tream
|
|
26
25
|
const EPSILON = {
|
|
27
|
-
type:
|
|
26
|
+
type: 'EPSILON',
|
|
28
27
|
}
|
|
29
28
|
|
|
30
29
|
/*
|
package/src/syntax.js
CHANGED
|
@@ -1,38 +1,33 @@
|
|
|
1
|
-
import {
|
|
2
|
-
TOKEN_PLACEHOLDER,
|
|
3
|
-
TOKEN_OPEN_TAG,
|
|
4
|
-
TOKEN_CLOSE_TAG,
|
|
5
|
-
TOKEN_SELF_TAG
|
|
6
|
-
} from "./constants"
|
|
1
|
+
import { TOKEN_PLACEHOLDER, TOKEN_OPEN_TAG, TOKEN_CLOSE_TAG, TOKEN_SELF_TAG } from './constants'
|
|
7
2
|
|
|
8
3
|
export const SYNTAX_BUILT_IN = [
|
|
9
4
|
{
|
|
10
5
|
type: TOKEN_PLACEHOLDER,
|
|
11
|
-
regex: /{\s*(\w+)\s*}/g
|
|
6
|
+
regex: /{\s*(\w+)\s*}/g,
|
|
12
7
|
},
|
|
13
8
|
{
|
|
14
9
|
type: TOKEN_OPEN_TAG,
|
|
15
|
-
regex: /<(\w+)>/g
|
|
10
|
+
regex: /<(\w+)>/g,
|
|
16
11
|
},
|
|
17
12
|
{
|
|
18
13
|
type: TOKEN_CLOSE_TAG,
|
|
19
|
-
regex: /<\/(\w+)>/g
|
|
14
|
+
regex: /<\/(\w+)>/g,
|
|
20
15
|
},
|
|
21
|
-
{ type: TOKEN_SELF_TAG, regex: /<(\w+)\s*\/>/g }
|
|
16
|
+
{ type: TOKEN_SELF_TAG, regex: /<(\w+)\s*\/>/g },
|
|
22
17
|
]
|
|
23
18
|
|
|
24
19
|
export const SYNTAX_I18NEXT = [
|
|
25
20
|
{
|
|
26
21
|
type: TOKEN_PLACEHOLDER,
|
|
27
|
-
regex: /{{\s*(\w+)\s*}}/g
|
|
22
|
+
regex: /{{\s*(\w+)\s*}}/g,
|
|
28
23
|
},
|
|
29
24
|
{
|
|
30
25
|
type: TOKEN_OPEN_TAG,
|
|
31
|
-
regex: /<(\w+)>/g
|
|
26
|
+
regex: /<(\w+)>/g,
|
|
32
27
|
},
|
|
33
28
|
{
|
|
34
29
|
type: TOKEN_CLOSE_TAG,
|
|
35
|
-
regex: /<\/(\w+)>/g
|
|
30
|
+
regex: /<\/(\w+)>/g,
|
|
36
31
|
},
|
|
37
|
-
{ type: TOKEN_SELF_TAG, regex: /<(\w+)\s*\/>/g }
|
|
32
|
+
{ type: TOKEN_SELF_TAG, regex: /<(\w+)\s*\/>/g },
|
|
38
33
|
]
|
package/.eslintrc.js
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
const config = {
|
|
2
|
-
extends: [
|
|
3
|
-
"eslint:recommended",
|
|
4
|
-
"plugin:react/recommended",
|
|
5
|
-
"prettier",
|
|
6
|
-
"plugin:compat/recommended"
|
|
7
|
-
],
|
|
8
|
-
env: {
|
|
9
|
-
browser: true,
|
|
10
|
-
"jest/globals": true
|
|
11
|
-
},
|
|
12
|
-
parserOptions: {
|
|
13
|
-
ecmaFeatures: {
|
|
14
|
-
jsx: true,
|
|
15
|
-
impliedStrict: true
|
|
16
|
-
},
|
|
17
|
-
sourceType: "module"
|
|
18
|
-
},
|
|
19
|
-
plugins: ["react", "prettier", "react-hooks", "jest"],
|
|
20
|
-
parser: "@babel/eslint-parser",
|
|
21
|
-
rules: {
|
|
22
|
-
semi: ["error", "never"],
|
|
23
|
-
"arrow-parens": ["error", "as-needed"],
|
|
24
|
-
"prettier/prettier": "error",
|
|
25
|
-
"react-hooks/rules-of-hooks": "error",
|
|
26
|
-
"react-hooks/exhaustive-deps": "error"
|
|
27
|
-
},
|
|
28
|
-
settings: {
|
|
29
|
-
react: {
|
|
30
|
-
version: "16.0"
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
module.exports = config
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
name: CI
|
|
2
|
-
|
|
3
|
-
on: [push, pull_request]
|
|
4
|
-
|
|
5
|
-
jobs:
|
|
6
|
-
build:
|
|
7
|
-
runs-on: ubuntu-latest
|
|
8
|
-
|
|
9
|
-
strategy:
|
|
10
|
-
matrix:
|
|
11
|
-
node-version: [12.x]
|
|
12
|
-
|
|
13
|
-
steps:
|
|
14
|
-
- uses: actions/checkout@v2
|
|
15
|
-
- name: Use Node.js ${{ matrix.node-version }}
|
|
16
|
-
uses: actions/setup-node@v1
|
|
17
|
-
with:
|
|
18
|
-
node-version: ${{ matrix.node-version }}
|
|
19
|
-
- run: npm ci
|
|
20
|
-
- run: npm run build
|
|
21
|
-
- run: npm test
|
|
22
|
-
env:
|
|
23
|
-
CI: true
|
|
24
|
-
- run: npm run lint
|
package/.prettierrc.json
DELETED
package/CONTRIBUTING.md
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
## Dependencies and target environment
|
|
2
|
-
|
|
3
|
-
One core goal this library is to deliver a lightweight solution. Therefore, it comes to critically when it comes to user of dependencies and also polyfill usage.
|
|
4
|
-
|
|
5
|
-
- `react` should be its only dependencies.
|
|
6
|
-
- No polyfill.
|
|
7
|
-
|
|
@@ -1,203 +0,0 @@
|
|
|
1
|
-
/* eslint-disable react/display-name */
|
|
2
|
-
import React from "react"
|
|
3
|
-
import { render } from "@testing-library/react"
|
|
4
|
-
import Interpolate, { SYNTAX_I18NEXT } from "../src"
|
|
5
|
-
|
|
6
|
-
Interpolate.defaultProps = {
|
|
7
|
-
graceful: false
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
const surpressConsole = () => {
|
|
11
|
-
const w = jest.spyOn(console, "warn").mockImplementation()
|
|
12
|
-
const e = jest.spyOn(console, "error").mockImplementation()
|
|
13
|
-
|
|
14
|
-
return () => {
|
|
15
|
-
w.mockRestore()
|
|
16
|
-
e.mockRestore()
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
describe("Interpolate", () => {
|
|
21
|
-
function renderTest({ expected, ...props }) {
|
|
22
|
-
const { container } = render(<Interpolate {...props} />)
|
|
23
|
-
expect(container.innerHTML).toEqual(expected)
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
test("when no mapping is provide", () => {
|
|
27
|
-
const restore = surpressConsole() // Interpolate will output warning when no mapping is provided
|
|
28
|
-
|
|
29
|
-
renderTest({
|
|
30
|
-
string: "<h1>hello <b>{name}</b></h1><br/>. welcome to todoist",
|
|
31
|
-
expected: "<h1>hello <b>{name}</b></h1><br>. welcome to todoist"
|
|
32
|
-
})
|
|
33
|
-
|
|
34
|
-
restore()
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
test("tag mapping", () =>
|
|
38
|
-
renderTest({
|
|
39
|
-
string: "<h1>hello <b>steven</b></h1>. welcome to todoist",
|
|
40
|
-
mapping: {
|
|
41
|
-
b: child => <i>{child}</i>,
|
|
42
|
-
h1: child => <h2>{child}</h2>
|
|
43
|
-
},
|
|
44
|
-
expected: "<h2>hello <i>steven</i></h2>. welcome to todoist"
|
|
45
|
-
}))
|
|
46
|
-
|
|
47
|
-
test("tag mapping: should accept element directly", () =>
|
|
48
|
-
renderTest({
|
|
49
|
-
string: "<h1>hello <b>steven</b></h1>. welcome to todoist",
|
|
50
|
-
mapping: {
|
|
51
|
-
b: <i />,
|
|
52
|
-
h1: <h2 />
|
|
53
|
-
},
|
|
54
|
-
expected: "<h2>hello <i>steven</i></h2>. welcome to todoist"
|
|
55
|
-
}))
|
|
56
|
-
|
|
57
|
-
test("placholder mapping", () =>
|
|
58
|
-
renderTest({
|
|
59
|
-
string: "{greeting} <b>{name}</b>. welcome to todoist",
|
|
60
|
-
mapping: {
|
|
61
|
-
greeting: "hi",
|
|
62
|
-
name: () => <i>steven</i>
|
|
63
|
-
},
|
|
64
|
-
expected: "hi <b><i>steven</i></b>. welcome to todoist"
|
|
65
|
-
}))
|
|
66
|
-
|
|
67
|
-
test("void tag mapping", () =>
|
|
68
|
-
renderTest({
|
|
69
|
-
string: "hello <br/>",
|
|
70
|
-
mapping: {
|
|
71
|
-
br: <hr />
|
|
72
|
-
},
|
|
73
|
-
expected: "hello <hr>"
|
|
74
|
-
}))
|
|
75
|
-
|
|
76
|
-
test("combination of mapping", () =>
|
|
77
|
-
renderTest({
|
|
78
|
-
string: "<h1>hello <b>{name}</b></h1>.<br/> welcome to todoist",
|
|
79
|
-
mapping: {
|
|
80
|
-
h1: child => <h2>{child}</h2>,
|
|
81
|
-
b: child => <i>{child}</i>,
|
|
82
|
-
name: "steven",
|
|
83
|
-
br: <hr />
|
|
84
|
-
},
|
|
85
|
-
expected: "<h2>hello <i>steven</i></h2>.<hr> welcome to todoist"
|
|
86
|
-
}))
|
|
87
|
-
|
|
88
|
-
test("combination of mapping with function component", () => {
|
|
89
|
-
// eslint-disable-next-line
|
|
90
|
-
const Subheader = ({ children }) => {
|
|
91
|
-
return <h2 className="subheader">{children}</h2>
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
renderTest({
|
|
95
|
-
string: "<h1>hello <b>{name}</b></h1>.<br/> welcome to todoist",
|
|
96
|
-
mapping: {
|
|
97
|
-
h1: child => <Subheader>{child}</Subheader>,
|
|
98
|
-
b: child => <i>{child}</i>,
|
|
99
|
-
name: "steven",
|
|
100
|
-
br: <hr />
|
|
101
|
-
},
|
|
102
|
-
expected:
|
|
103
|
-
'<h2 class="subheader">hello <i>steven</i></h2>.<hr> welcome to todoist'
|
|
104
|
-
})
|
|
105
|
-
})
|
|
106
|
-
|
|
107
|
-
test("spacing in the void tag and placeholder should be allowed", () =>
|
|
108
|
-
renderTest({
|
|
109
|
-
string: "hello { name }<br /> welcome to todoist",
|
|
110
|
-
mapping: {
|
|
111
|
-
name: "steven",
|
|
112
|
-
br: <hr />
|
|
113
|
-
},
|
|
114
|
-
expected: "hello steven<hr> welcome to todoist"
|
|
115
|
-
}))
|
|
116
|
-
|
|
117
|
-
test("the mapping value should be interpolate corrected with proper html escape", () => {
|
|
118
|
-
renderTest({
|
|
119
|
-
string: "hello { name }<br /> welcome to todoist",
|
|
120
|
-
mapping: {
|
|
121
|
-
name: "<script>window.xss = 1</script>",
|
|
122
|
-
br: "<script>window.xss = 1</script>"
|
|
123
|
-
},
|
|
124
|
-
expected:
|
|
125
|
-
"hello <script>window.xss = 1</script><script>window.xss = 1</script> welcome to todoist"
|
|
126
|
-
})
|
|
127
|
-
|
|
128
|
-
expect(window.css).toBeUndefined()
|
|
129
|
-
})
|
|
130
|
-
|
|
131
|
-
test("when graceful flag is on and string contains syntax error, interpolate should return the original string and should not throw error", () => {
|
|
132
|
-
const restore = surpressConsole()
|
|
133
|
-
|
|
134
|
-
renderTest({
|
|
135
|
-
string: "</h1>",
|
|
136
|
-
expected: "</h1>",
|
|
137
|
-
graceful: true
|
|
138
|
-
})
|
|
139
|
-
|
|
140
|
-
restore()
|
|
141
|
-
})
|
|
142
|
-
|
|
143
|
-
test("using SYNTAX_I18NEXT", () => {
|
|
144
|
-
renderTest({
|
|
145
|
-
syntax: SYNTAX_I18NEXT,
|
|
146
|
-
string: "<0>hello <b>{{name}}</b></0>.<br/> welcome to todoist",
|
|
147
|
-
mapping: {
|
|
148
|
-
0: child => <h2 className="subheader">{child}</h2>,
|
|
149
|
-
b: child => <i>{child}</i>,
|
|
150
|
-
name: "steven",
|
|
151
|
-
br: <hr />
|
|
152
|
-
},
|
|
153
|
-
expected:
|
|
154
|
-
'<h2 class="subheader">hello <i>steven</i></h2>.<hr> welcome to todoist'
|
|
155
|
-
})
|
|
156
|
-
})
|
|
157
|
-
})
|
|
158
|
-
|
|
159
|
-
describe("Interpolate: error cases", () => {
|
|
160
|
-
let restore
|
|
161
|
-
beforeAll(() => {
|
|
162
|
-
restore = surpressConsole()
|
|
163
|
-
})
|
|
164
|
-
afterAll(() => {
|
|
165
|
-
restore()
|
|
166
|
-
})
|
|
167
|
-
|
|
168
|
-
function renderTest({ expectedError, ...props }) {
|
|
169
|
-
return () => {
|
|
170
|
-
expect(() => render(<Interpolate {...props} />)).toThrow(
|
|
171
|
-
expectedError
|
|
172
|
-
)
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
test(
|
|
177
|
-
"non-closing tag",
|
|
178
|
-
renderTest({
|
|
179
|
-
string: "<b>",
|
|
180
|
-
expectedError:
|
|
181
|
-
"Syntax error. Please check if each open tag is closed correctly"
|
|
182
|
-
})
|
|
183
|
-
)
|
|
184
|
-
|
|
185
|
-
test(
|
|
186
|
-
"mapping value for tag should always be a function",
|
|
187
|
-
renderTest({
|
|
188
|
-
string: "<h1>hello</h1>. welcome to todoist",
|
|
189
|
-
mapping: { h1: "hi" },
|
|
190
|
-
expectedError: "Invalid mapping value for"
|
|
191
|
-
})
|
|
192
|
-
)
|
|
193
|
-
|
|
194
|
-
test(
|
|
195
|
-
"when passing element as value but the element contains children, error should be thrown",
|
|
196
|
-
renderTest({
|
|
197
|
-
string: "<h1>hello</h1>. welcome to todoist",
|
|
198
|
-
mapping: { h1: <h1>hi</h1> },
|
|
199
|
-
expectedError:
|
|
200
|
-
"when passing an element as value, the element should not contains children"
|
|
201
|
-
})
|
|
202
|
-
)
|
|
203
|
-
})
|
package/__test__/parser.test.js
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import parser from "../src/parser"
|
|
2
|
-
|
|
3
|
-
describe("parser", () => {
|
|
4
|
-
const tests = [
|
|
5
|
-
"hello",
|
|
6
|
-
"{steven}",
|
|
7
|
-
"<br />",
|
|
8
|
-
"<div></div>",
|
|
9
|
-
"hello {steven} from taiwan",
|
|
10
|
-
"hello {steven} from taiwan <br/>",
|
|
11
|
-
"<div>hello</div>",
|
|
12
|
-
"<div>hello {steven}</div>",
|
|
13
|
-
"<div>hello <b>{steven}</b></div>",
|
|
14
|
-
"<div>hello <b>mr {steven} from taiwan</b></div>",
|
|
15
|
-
"<div>hello <b>mr {steven} from taiwan</b><br /></div>",
|
|
16
|
-
"<div>hello</div><b>steven</b>"
|
|
17
|
-
]
|
|
18
|
-
|
|
19
|
-
tests.forEach(string => {
|
|
20
|
-
test(string, () => {
|
|
21
|
-
parser(string)
|
|
22
|
-
})
|
|
23
|
-
})
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
describe("parser: error handling", () => {
|
|
27
|
-
const invalids = [
|
|
28
|
-
"<div>hello",
|
|
29
|
-
"hello<div>",
|
|
30
|
-
"<br>",
|
|
31
|
-
"hello {steven}</div>",
|
|
32
|
-
"<div>hello <b>{steven}</div></b>",
|
|
33
|
-
"<div>hello <b>{steven}</b></b></div>"
|
|
34
|
-
]
|
|
35
|
-
|
|
36
|
-
invalids.forEach(string => {
|
|
37
|
-
test(`invalid syntax: ${string}`, () => {
|
|
38
|
-
expect(() => {
|
|
39
|
-
parser(string)
|
|
40
|
-
}).toThrow(
|
|
41
|
-
"Syntax error. Please check if each open tag is closed correctly"
|
|
42
|
-
)
|
|
43
|
-
})
|
|
44
|
-
})
|
|
45
|
-
})
|
package/babel.config.js
DELETED
|
Binary file
|
|
Binary file
|
package/jest.config.js
DELETED
package/rollup.config.js
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import babel from "rollup-plugin-babel"
|
|
2
|
-
import pkg from "./package.json"
|
|
3
|
-
|
|
4
|
-
export default {
|
|
5
|
-
input: "src/index.js",
|
|
6
|
-
output: [
|
|
7
|
-
{
|
|
8
|
-
file: pkg.main,
|
|
9
|
-
format: "cjs"
|
|
10
|
-
},
|
|
11
|
-
{
|
|
12
|
-
file: pkg.module,
|
|
13
|
-
format: "es"
|
|
14
|
-
}
|
|
15
|
-
],
|
|
16
|
-
plugins: [babel({ runtimeHelpers: true })]
|
|
17
|
-
}
|