@cocreate/plugins 1.9.0 → 2.0.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/.github/FUNDING.yml +1 -1
- package/.github/workflows/automated.yml +18 -7
- package/CHANGELOG.md +73 -38
- package/CONTRIBUTING.md +6 -6
- package/README.md +60 -48
- package/docs/index.html +4 -4
- package/package.json +5 -5
- package/src/index.js +71 -105
- package/test/result-storage.test.js +212 -0
- /package/{release.config.js → release.config.cjs} +0 -0
package/.github/FUNDING.yml
CHANGED
|
@@ -12,16 +12,27 @@ jobs:
|
|
|
12
12
|
- name: Checkout repository
|
|
13
13
|
uses: actions/checkout@v7
|
|
14
14
|
|
|
15
|
-
- name: Sync package.json description to GitHub
|
|
15
|
+
- name: Sync package.json description and keywords to GitHub repository
|
|
16
16
|
env:
|
|
17
|
-
|
|
17
|
+
GH_TOKEN: ${{ secrets.GITHUB }}
|
|
18
18
|
run: |
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
if [ ! -f package.json ]; then
|
|
20
|
+
echo "No package.json found; skipping repository metadata sync."
|
|
21
|
+
exit 0
|
|
22
|
+
fi
|
|
23
|
+
|
|
24
|
+
DESCRIPTION=$(jq -r '.description // empty' package.json)
|
|
25
|
+
if [ -n "$DESCRIPTION" ]; then
|
|
26
|
+
gh api --method PATCH "repos/${GITHUB_REPOSITORY}" -f description="$DESCRIPTION"
|
|
27
|
+
else
|
|
28
|
+
echo "No package description found; leaving GitHub description unchanged."
|
|
29
|
+
fi
|
|
30
|
+
|
|
31
|
+
if jq -e '.keywords | type == "array"' package.json >/dev/null; then
|
|
32
|
+
TOPICS=$(jq -c '.keywords | map(ascii_downcase | gsub("[^a-z0-9-]+"; "-") | gsub("(^-+|-+$)"; "") | .[0:50]) | map(select(length > 0)) | unique | .[0:20]' package.json)
|
|
33
|
+
printf '{"names":%s}' "$TOPICS" | gh api --method PUT -H "Accept: application/vnd.github+json" "repos/${GITHUB_REPOSITORY}/topics" --input -
|
|
23
34
|
else
|
|
24
|
-
echo "No
|
|
35
|
+
echo "No package keywords found; leaving GitHub topics unchanged."
|
|
25
36
|
fi
|
|
26
37
|
|
|
27
38
|
release:
|
package/CHANGELOG.md
CHANGED
|
@@ -1,115 +1,150 @@
|
|
|
1
|
-
|
|
1
|
+
# [2.0.0](https://github.com/CoCreateJS/plugins/compare/v1.4.2...v2.0.0) (2026-11-02)
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
### Bug Fixes
|
|
5
5
|
|
|
6
|
-
*
|
|
6
|
+
* add Deno specific entries to .gitignore ([2e77744](https://github.com/CoCreateJS/plugins/commit/2e77744e33cab2968d7f9c2bf41f9dae38f25455))
|
|
7
|
+
* await pending plugin initialization ([d500cd2](https://github.com/CoCreateJS/plugins/commit/d500cd23245ddbbca0f31951dd8356888981ee4a))
|
|
8
|
+
* bump [@cocreate](https://github.com/cocreate) dependencies ([7893b9e](https://github.com/CoCreateJS/plugins/commit/7893b9ea09c130488553d33bdb4a7c6dd71e68f0))
|
|
9
|
+
* bump versions ([e70fdfa](https://github.com/CoCreateJS/plugins/commit/e70fdfadbbebb13880dc1cd2c40d5ade30092f37))
|
|
10
|
+
* bump versions ([321063f](https://github.com/CoCreateJS/plugins/commit/321063f9c223b5107629dcf516ea18380e0a5ad5))
|
|
11
|
+
* execute plugin configurations directly through AST ([f507b05](https://github.com/CoCreateJS/plugins/commit/f507b05fa7a831f9a7686067f1eaf506543b29e9))
|
|
12
|
+
* improve dynamic plugin initialization ([f834366](https://github.com/CoCreateJS/plugins/commit/f834366dda96db9aaf678780f14edbca159f6fb4))
|
|
13
|
+
* prevent duplicate plugin initialization ([7e36818](https://github.com/CoCreateJS/plugins/commit/7e368188c7cfd8804b7401cca609989dcfe99d2e))
|
|
14
|
+
* remove trailing slash from node_modules entry in .gitignore ([77ec08a](https://github.com/CoCreateJS/plugins/commit/77ec08a37696cba91aea9fcde55a1bd0e644e02c))
|
|
15
|
+
* stabilize dynamic plugin initialization ([4bd1763](https://github.com/CoCreateJS/plugins/commit/4bd176386f1e18a5ae865b7df1c19f43c5948b24))
|
|
16
|
+
* update git url ([06fdd2d](https://github.com/CoCreateJS/plugins/commit/06fdd2d13142a4614e0f983c0248753df2f0250d))
|
|
17
|
+
* update GitHub Actions to use latest versions and sync package.json description ([bf390e2](https://github.com/CoCreateJS/plugins/commit/bf390e24d9dbcb1738358fa6beba2d0546ce94ac))
|
|
18
|
+
* update string escape for plugin name checks ([2d99094](https://github.com/CoCreateJS/plugins/commit/2d9909486f23a6b6e38efbd107faa475893c03a4))
|
|
7
19
|
|
|
8
|
-
|
|
20
|
+
|
|
21
|
+
### chore
|
|
22
|
+
|
|
23
|
+
* record package identity migration as breaking ([9926af9](https://github.com/CoCreateJS/plugins/commit/9926af94d90d5330def0245d22348f6616d31188))
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
### Features
|
|
27
|
+
|
|
28
|
+
* execute plugin configs through AST ([4e4d3a5](https://github.com/CoCreateJS/plugins/commit/4e4d3a509ea742fcc6b4c62902760438fa9c220c))
|
|
29
|
+
* replace uuid import and usage with utils ([f92a5a3](https://github.com/CoCreateJS/plugins/commit/f92a5a33ad9bcdfde6fb75d56dac8f0ff7a6b679))
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
### BREAKING CHANGES
|
|
33
|
+
|
|
34
|
+
* Consumers must install @cocreate/plugins and use the renamed CoCreateJS repository identity; the previous package and repository identities are no longer supported.
|
|
35
|
+
|
|
36
|
+
## [1.4.2](https://github.com/CoCreateJS/CoCreate-plugins/compare/v1.4.1...v1.4.2) (2026-07-19)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
### Bug Fixes
|
|
40
|
+
|
|
41
|
+
* set default branch to main ([6252e49](https://github.com/CoCreateJS/CoCreate-plugins/commit/6252e49ac94bf14ba903943788cc443c5a5fd113))
|
|
42
|
+
|
|
43
|
+
## [1.4.1](https://github.com/CoCreateJS/CoCreate-plugins/compare/v1.4.0...v1.4.1) (2026-07-17)
|
|
9
44
|
|
|
10
45
|
|
|
11
46
|
### Bug Fixes
|
|
12
47
|
|
|
13
|
-
* bump dependencies ([0bcc639](https://github.com/
|
|
48
|
+
* bump dependencies ([0bcc639](https://github.com/CoCreateJS/CoCreate-plugins/commit/0bcc6392a3f033f26f9d8d1145e10a6df7de7ab2))
|
|
14
49
|
|
|
15
|
-
# [1.4.0](https://github.com/
|
|
50
|
+
# [1.4.0](https://github.com/CoCreateJS/CoCreate-plugins/compare/v1.3.1...v1.4.0) (2026-07-17)
|
|
16
51
|
|
|
17
52
|
|
|
18
53
|
### Features
|
|
19
54
|
|
|
20
|
-
* enhance plugin initialization and resource preloading logic ([ee17bc1](https://github.com/
|
|
21
|
-
* improve plugin processing and operator execution logic ([486faa8](https://github.com/
|
|
22
|
-
* update automated workflow and release configuration for improved semantic release handling ([06e65c9](https://github.com/
|
|
55
|
+
* enhance plugin initialization and resource preloading logic ([ee17bc1](https://github.com/CoCreateJS/CoCreate-plugins/commit/ee17bc12b47c09085cd2527ef18a6fce36f21123))
|
|
56
|
+
* improve plugin processing and operator execution logic ([486faa8](https://github.com/CoCreateJS/CoCreate-plugins/commit/486faa81839a49d4958ccb75721b161102cc0168))
|
|
57
|
+
* update automated workflow and release configuration for improved semantic release handling ([06e65c9](https://github.com/CoCreateJS/CoCreate-plugins/commit/06e65c9eddd29398f3bd824e295089722faf9fad))
|
|
23
58
|
|
|
24
|
-
## [1.3.1](https://github.com/
|
|
59
|
+
## [1.3.1](https://github.com/CoCreateJS/CoCreate-plugins/compare/v1.3.0...v1.3.1) (2026-07-11)
|
|
25
60
|
|
|
26
61
|
|
|
27
62
|
### Bug Fixes
|
|
28
63
|
|
|
29
|
-
* bump package versions ([3f992ea](https://github.com/
|
|
30
|
-
* clean attribute name for method matching in processPlugin function ([7389d12](https://github.com/
|
|
31
|
-
* correct minor typos in code comments and variable names for clarity. ([7e43622](https://github.com/
|
|
32
|
-
* demo plugin initialization syntax and remove redundant tests ([c126bc8](https://github.com/
|
|
33
|
-
* removed post install and add add packages to apprved list ([548be97](https://github.com/
|
|
34
|
-
* semantic version handling ([7606d54](https://github.com/
|
|
64
|
+
* bump package versions ([3f992ea](https://github.com/CoCreateJS/CoCreate-plugins/commit/3f992ea145328efac344e4f470fe19a87d5209e9))
|
|
65
|
+
* clean attribute name for method matching in processPlugin function ([7389d12](https://github.com/CoCreateJS/CoCreate-plugins/commit/7389d12bd6f4206c506456d64341ceef4b304313))
|
|
66
|
+
* correct minor typos in code comments and variable names for clarity. ([7e43622](https://github.com/CoCreateJS/CoCreate-plugins/commit/7e4362289c9f51ac6673873802d5b58a7992638e))
|
|
67
|
+
* demo plugin initialization syntax and remove redundant tests ([c126bc8](https://github.com/CoCreateJS/CoCreate-plugins/commit/c126bc87edfb9669ea6c8a79e13a0cd0c09a1bd3))
|
|
68
|
+
* removed post install and add add packages to apprved list ([548be97](https://github.com/CoCreateJS/CoCreate-plugins/commit/548be977ba710af907ad8e95f7ebcd3a75462326))
|
|
69
|
+
* semantic version handling ([7606d54](https://github.com/CoCreateJS/CoCreate-plugins/commit/7606d54cd447a3cf10c3f86d54bca1f0aa7fe1a1))
|
|
35
70
|
|
|
36
|
-
# [1.3.0](https://github.com/
|
|
71
|
+
# [1.3.0](https://github.com/CoCreateJS/CoCreate-plugins/compare/v1.2.1...v1.3.0) (2026-03-11)
|
|
37
72
|
|
|
38
73
|
|
|
39
74
|
### Bug Fixes
|
|
40
75
|
|
|
41
|
-
* Update demo/index.html to test sequential execution of attributes as logic ([d71e0a5](https://github.com/
|
|
76
|
+
* Update demo/index.html to test sequential execution of attributes as logic ([d71e0a5](https://github.com/CoCreateJS/CoCreate-plugins/commit/d71e0a562bc48337746583a04bf230f84134f6c7))
|
|
42
77
|
|
|
43
78
|
|
|
44
79
|
### Features
|
|
45
80
|
|
|
46
|
-
* Refactor plugin initialization to sequentially load resources and execute attributes in DOM order. Add support for direct $this references in attributes. Improve error handling and logging during plugin execution. ([08ecdb2](https://github.com/
|
|
81
|
+
* Refactor plugin initialization to sequentially load resources and execute attributes in DOM order. Add support for direct $this references in attributes. Improve error handling and logging during plugin execution. ([08ecdb2](https://github.com/CoCreateJS/CoCreate-plugins/commit/08ecdb2b2bb8bba2cf3950eb2ef5e7ac77916203))
|
|
47
82
|
|
|
48
|
-
## [1.2.1](https://github.com/
|
|
83
|
+
## [1.2.1](https://github.com/CoCreateJS/CoCreate-plugins/compare/v1.2.0...v1.2.1) (2026-03-02)
|
|
49
84
|
|
|
50
85
|
|
|
51
86
|
### Bug Fixes
|
|
52
87
|
|
|
53
|
-
* Handle case-insensitive plugin global mapping and add support for function reference assignments in plugin properties. ([904a947](https://github.com/
|
|
88
|
+
* Handle case-insensitive plugin global mapping and add support for function reference assignments in plugin properties. ([904a947](https://github.com/CoCreateJS/CoCreate-plugins/commit/904a947abc868937b6e650b62e080d00341272a1))
|
|
54
89
|
|
|
55
|
-
# [1.2.0](https://github.com/
|
|
90
|
+
# [1.2.0](https://github.com/CoCreateJS/CoCreate-plugins/compare/v1.1.1...v1.2.0) (2026-02-15)
|
|
56
91
|
|
|
57
92
|
|
|
58
93
|
### Bug Fixes
|
|
59
94
|
|
|
60
|
-
* improve key validation in executeGenericPlugin function ([447f6d2](https://github.com/
|
|
95
|
+
* improve key validation in executeGenericPlugin function ([447f6d2](https://github.com/CoCreateJS/CoCreate-plugins/commit/447f6d209adea010110c3bc52a17f8b45cb302ea))
|
|
61
96
|
|
|
62
97
|
|
|
63
98
|
### Features
|
|
64
99
|
|
|
65
|
-
* enhance demo page with improved layout and Bootstrap integration ([a74615e](https://github.com/
|
|
66
|
-
* refactor plugin management and enhance dynamic loading capabilities ([4d91958](https://github.com/
|
|
100
|
+
* enhance demo page with improved layout and Bootstrap integration ([a74615e](https://github.com/CoCreateJS/CoCreate-plugins/commit/a74615e27be7c2f40529c5242e6c6e9b839ad020))
|
|
101
|
+
* refactor plugin management and enhance dynamic loading capabilities ([4d91958](https://github.com/CoCreateJS/CoCreate-plugins/commit/4d919582934418cfb7bfa9497b6d0dcaf3bf8d10))
|
|
67
102
|
|
|
68
|
-
## [1.1.1](https://github.com/
|
|
103
|
+
## [1.1.1](https://github.com/CoCreateJS/CoCreate-plugins/compare/v1.1.0...v1.1.1) (2026-02-09)
|
|
69
104
|
|
|
70
105
|
|
|
71
106
|
### Bug Fixes
|
|
72
107
|
|
|
73
|
-
* change init function to default export and update parameter handling ([654b623](https://github.com/
|
|
108
|
+
* change init function to default export and update parameter handling ([654b623](https://github.com/CoCreateJS/CoCreate-plugins/commit/654b623760446cbd9db55cc216e25a07fe7865e3))
|
|
74
109
|
|
|
75
|
-
# [1.1.0](https://github.com/
|
|
110
|
+
# [1.1.0](https://github.com/CoCreateJS/CoCreate-plugins/compare/v1.0.3...v1.1.0) (2026-01-15)
|
|
76
111
|
|
|
77
112
|
|
|
78
113
|
### Features
|
|
79
114
|
|
|
80
|
-
* add marker to indicate where css files will be inserted ([6b4b030](https://github.com/
|
|
115
|
+
* add marker to indicate where css files will be inserted ([6b4b030](https://github.com/CoCreateJS/CoCreate-plugins/commit/6b4b0301acdabff19b8cca5968cc67619e417c92))
|
|
81
116
|
|
|
82
|
-
## [1.0.3](https://github.com/
|
|
117
|
+
## [1.0.3](https://github.com/CoCreateJS/CoCreate-plugins/compare/v1.0.2...v1.0.3) (2025-12-26)
|
|
83
118
|
|
|
84
119
|
|
|
85
120
|
### Bug Fixes
|
|
86
121
|
|
|
87
|
-
* update worklow ([8976d08](https://github.com/
|
|
122
|
+
* update worklow ([8976d08](https://github.com/CoCreateJS/CoCreate-plugins/commit/8976d080f8972e840ff5530d5dfb15f839e9ac5b))
|
|
88
123
|
|
|
89
|
-
## [1.0.2](https://github.com/
|
|
124
|
+
## [1.0.2](https://github.com/CoCreateJS/CoCreate-plugins/compare/v1.0.1...v1.0.2) (2025-12-25)
|
|
90
125
|
|
|
91
126
|
|
|
92
127
|
### Bug Fixes
|
|
93
128
|
|
|
94
|
-
* workflow ([3da40b3](https://github.com/
|
|
129
|
+
* workflow ([3da40b3](https://github.com/CoCreateJS/CoCreate-plugins/commit/3da40b3572bdc84f1c555b2be4ae5e1fcc1a4533))
|
|
95
130
|
|
|
96
|
-
## [1.0.1](https://github.com/
|
|
131
|
+
## [1.0.1](https://github.com/CoCreateJS/CoCreate-plugins/compare/v1.0.0...v1.0.1) (2025-12-25)
|
|
97
132
|
|
|
98
133
|
|
|
99
134
|
### Bug Fixes
|
|
100
135
|
|
|
101
|
-
* workflow ([b1478b3](https://github.com/
|
|
102
|
-
* workflow ([dbab32c](https://github.com/
|
|
136
|
+
* workflow ([b1478b3](https://github.com/CoCreateJS/CoCreate-plugins/commit/b1478b3b6e014add5a7cb25fe83ce420147450f0))
|
|
137
|
+
* workflow ([dbab32c](https://github.com/CoCreateJS/CoCreate-plugins/commit/dbab32cc403943faad190acf2f1b3284f14965b7))
|
|
103
138
|
|
|
104
139
|
# 1.0.0 (2025-12-25)
|
|
105
140
|
|
|
106
141
|
|
|
107
142
|
### Bug Fixes
|
|
108
143
|
|
|
109
|
-
* workflow ([044ec07](https://github.com/
|
|
110
|
-
* workflow ([cb2ec43](https://github.com/
|
|
144
|
+
* workflow ([044ec07](https://github.com/CoCreateJS/CoCreate-plugins/commit/044ec07b501298a17a61b604f37b9d5e76f1756c))
|
|
145
|
+
* workflow ([cb2ec43](https://github.com/CoCreateJS/CoCreate-plugins/commit/cb2ec438b2b1e69f19f0fe29bf11791631d7fb67))
|
|
111
146
|
|
|
112
147
|
|
|
113
148
|
### Features
|
|
114
149
|
|
|
115
|
-
* Initial Release ([4d46a70](https://github.com/
|
|
150
|
+
* Initial Release ([4d46a70](https://github.com/CoCreateJS/CoCreate-plugins/commit/4d46a70880c086733fa175531096d20f04a7f149))
|
package/CONTRIBUTING.md
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
# Contributing to CoCreate-plugins
|
|
2
2
|
|
|
3
|
-
This project is work of [many contributors](https://github.com/
|
|
4
|
-
You're encouraged to submit [pull requests](https://github.com/
|
|
5
|
-
[propose features and discuss issues](https://github.com/
|
|
3
|
+
This project is work of [many contributors](https://github.com/CoCreateJS/plugins/graphs/contributors).
|
|
4
|
+
You're encouraged to submit [pull requests](https://github.com/CoCreateJS/plugins/pulls),
|
|
5
|
+
[propose features and discuss issues](https://github.com/CoCreateJS/plugins/issues).
|
|
6
6
|
|
|
7
7
|
In the examples below, substitute your Github username for `contributor` in URLs.
|
|
8
8
|
|
|
9
9
|
## Fork the Project
|
|
10
10
|
|
|
11
|
-
Fork the [project on Github](https://github.com/
|
|
11
|
+
Fork the [project on Github](https://github.com/CoCreateJS/plugins) and check out your copy.
|
|
12
12
|
|
|
13
13
|
```
|
|
14
14
|
git clone https://github.com/contributor/CoCreate-plugins.git
|
|
15
15
|
cd CoCreate-plugins
|
|
16
|
-
git remote add upstream https://
|
|
16
|
+
git remote add upstream https://github.com/CoCreateJS/plugins.git
|
|
17
17
|
```
|
|
18
18
|
|
|
19
19
|
## Create a Topic Branch
|
|
@@ -74,7 +74,7 @@ git push origin my-feature-branch
|
|
|
74
74
|
|
|
75
75
|
## Make a Pull Request
|
|
76
76
|
|
|
77
|
-
Go to [https://github.com/
|
|
77
|
+
Go to [https://github.com/CoCreateJS/plugins](https://github.com/CoCreateJS/plugins) and select your feature branch.
|
|
78
78
|
Click the 'Pull Request' button and fill out the form. Pull requests are usually reviewed within a few days.
|
|
79
79
|
|
|
80
80
|
## Rebase
|
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@ A declarative browser plugin loader and runtime for CoCreate. It loads registere
|
|
|
6
6
|
|
|
7
7
|
## Documentation
|
|
8
8
|
|
|
9
|
-
For interactive examples, visit the [CoCreate Plugins documentation](https://
|
|
9
|
+
For interactive examples, visit the [CoCreate Plugins documentation](https://CoCreateJS.com/docs/plugins).
|
|
10
10
|
|
|
11
11
|
## Table of Contents
|
|
12
12
|
|
|
@@ -27,13 +27,32 @@ For interactive examples, visit the [CoCreate Plugins documentation](https://coc
|
|
|
27
27
|
|
|
28
28
|
---
|
|
29
29
|
|
|
30
|
+
## Explicit instance storage
|
|
31
|
+
|
|
32
|
+
Plugin results are not automatically assigned to the host element. To retain a
|
|
33
|
+
returned instance, explicitly assign it using AST:
|
|
34
|
+
|
|
35
|
+
```html
|
|
36
|
+
<div plugin='{
|
|
37
|
+
"$this.myInstance": {
|
|
38
|
+
"$ExamplePlugin": ["$this", {}]
|
|
39
|
+
}
|
|
40
|
+
}'></div>
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Here `$this` identifies the host element and `myInstance` names the property to
|
|
44
|
+
write. The nested initializer executes first, then its returned value is
|
|
45
|
+
assigned to that property. A global plugin call that needs no retained result
|
|
46
|
+
can run without this assignment. Code that previously relied on automatic `element.PluginName`
|
|
47
|
+
storage must explicitly assign and reference the instance instead.
|
|
48
|
+
|
|
30
49
|
## Architectural Overview
|
|
31
50
|
|
|
32
51
|
`@cocreate/plugins` connects three parts of the browser runtime:
|
|
33
52
|
|
|
34
53
|
1. A plugin definition in `window.CoCreateConfig.plugins` identifies the JavaScript and CSS resources.
|
|
35
54
|
2. A DOM element's `plugin` attribute contains an AST configuration.
|
|
36
|
-
3.
|
|
55
|
+
3. AST executes the configuration. Results are stored only where the configuration explicitly assigns them.
|
|
37
56
|
|
|
38
57
|
```text
|
|
39
58
|
CoCreateConfig.plugins
|
|
@@ -46,10 +65,10 @@ window.PluginName
|
|
|
46
65
|
├── AST executes plugin configuration
|
|
47
66
|
│
|
|
48
67
|
▼
|
|
49
|
-
|
|
68
|
+
result (optionally assigned through AST)
|
|
50
69
|
```
|
|
51
70
|
|
|
52
|
-
Plugin
|
|
71
|
+
Plugin operations execute sequentially. Plugin names continue to refer to their global libraries; constructor results do not automatically replace those references or attach to the element.
|
|
53
72
|
|
|
54
73
|
The module observes `[plugin]` elements automatically, including elements added or updated after the initial page load.
|
|
55
74
|
|
|
@@ -72,7 +91,7 @@ yarn add @cocreate/plugins
|
|
|
72
91
|
### CDN
|
|
73
92
|
|
|
74
93
|
```html
|
|
75
|
-
<script src="https://cdn.
|
|
94
|
+
<script src="https://cdn.CoCreateJS.com/plugins/latest/CoCreate-plugins.min.js"></script>
|
|
76
95
|
```
|
|
77
96
|
|
|
78
97
|
When using the package as a module:
|
|
@@ -141,12 +160,12 @@ Registration describes where a library is loaded from. Initialization behavior b
|
|
|
141
160
|
|
|
142
161
|
### Constructor or Function
|
|
143
162
|
|
|
144
|
-
|
|
163
|
+
Use an explicit AST call to initialize a plugin and a dotted destination to retain its result on the element.
|
|
145
164
|
|
|
146
165
|
```html
|
|
147
166
|
<div
|
|
148
167
|
plugin='{
|
|
149
|
-
"SimpleBar": ["$this", {}]
|
|
168
|
+
"$this.SimpleBar": { "$SimpleBar": ["$this", {}] }
|
|
150
169
|
}'
|
|
151
170
|
style="max-height: 300px">
|
|
152
171
|
</div>
|
|
@@ -165,11 +184,13 @@ For a global function that receives a single options object:
|
|
|
165
184
|
```html
|
|
166
185
|
<button
|
|
167
186
|
plugin='{
|
|
168
|
-
"
|
|
169
|
-
"
|
|
170
|
-
|
|
187
|
+
"$this.toast": {
|
|
188
|
+
"$Toastify": {
|
|
189
|
+
"text": "Saved successfully",
|
|
190
|
+
"duration": 3000
|
|
191
|
+
}
|
|
171
192
|
},
|
|
172
|
-
"
|
|
193
|
+
"$this.toast.showToast": []
|
|
173
194
|
}'>
|
|
174
195
|
Show notification
|
|
175
196
|
</button>
|
|
@@ -183,21 +204,18 @@ Some libraries expose constructors as properties of a global namespace. A single
|
|
|
183
204
|
<div
|
|
184
205
|
id="calendar"
|
|
185
206
|
plugin='{
|
|
186
|
-
"FullCalendar": {
|
|
187
|
-
"Calendar": [
|
|
207
|
+
"$this.FullCalendar": {
|
|
208
|
+
"$FullCalendar.Calendar": [
|
|
188
209
|
"$this",
|
|
189
|
-
{
|
|
190
|
-
"initialView": "dayGridMonth",
|
|
191
|
-
"editable": true
|
|
192
|
-
}
|
|
210
|
+
{ "initialView": "dayGridMonth", "editable": true }
|
|
193
211
|
]
|
|
194
212
|
},
|
|
195
|
-
"FullCalendar.render": []
|
|
213
|
+
"$this.FullCalendar.render": []
|
|
196
214
|
}'>
|
|
197
215
|
</div>
|
|
198
216
|
```
|
|
199
217
|
|
|
200
|
-
The first key creates the instance:
|
|
218
|
+
The first key explicitly creates and stores the instance:
|
|
201
219
|
|
|
202
220
|
```javascript
|
|
203
221
|
element.FullCalendar = new window.FullCalendar.Calendar(element, options);
|
|
@@ -226,8 +244,8 @@ The attribute can contain an array. Each object is processed in order:
|
|
|
226
244
|
```html
|
|
227
245
|
<div
|
|
228
246
|
plugin='[
|
|
229
|
-
{ "FirstPlugin": ["$this"] },
|
|
230
|
-
{ "SecondPlugin": ["$this", {}] }
|
|
247
|
+
{ "$FirstPlugin": ["$this"] },
|
|
248
|
+
{ "$SecondPlugin": ["$this", {}] }
|
|
231
249
|
]'>
|
|
232
250
|
</div>
|
|
233
251
|
```
|
|
@@ -236,29 +254,23 @@ The attribute can contain an array. Each object is processed in order:
|
|
|
236
254
|
|
|
237
255
|
## AST Execution Model
|
|
238
256
|
|
|
239
|
-
Each configuration object is
|
|
257
|
+
Each JSON configuration object is passed unchanged to `astAsync`, once, with
|
|
258
|
+
the registered libraries and `$this` in its sandbox and the host element as
|
|
259
|
+
`node`. The plugin loader does not wrap individual entries or rewrite ordinary
|
|
260
|
+
keys into calls. Sibling entries share AST's evaluation scope, including values
|
|
261
|
+
created by earlier entries. Configuration arrays execute one block at a time.
|
|
262
|
+
The bare-name shorthand is expanded to `{ "$PluginName": [] }`.
|
|
240
263
|
|
|
241
264
|
```json
|
|
242
265
|
{
|
|
243
|
-
"FullCalendar": {
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
"FullCalendar.render": []
|
|
266
|
+
"instance": { "$FullCalendar.Calendar": ["$this", {}] },
|
|
267
|
+
"$this.calendar": "$instance",
|
|
268
|
+
"$instance.render": []
|
|
247
269
|
}
|
|
248
270
|
```
|
|
249
271
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
```json
|
|
253
|
-
{
|
|
254
|
-
"FullCalendar": {
|
|
255
|
-
"$FullCalendar.Calendar": ["$this", {}]
|
|
256
|
-
},
|
|
257
|
-
"FullCalendar.render": {
|
|
258
|
-
"$FullCalendar.render": []
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
```
|
|
272
|
+
JSON configs that previously relied on implicit plugin calls must use explicit
|
|
273
|
+
`$PluginName` or `$PluginName.method` keys. Ordinary keys declare AST data.
|
|
262
274
|
|
|
263
275
|
Execution follows these rules:
|
|
264
276
|
|
|
@@ -269,22 +281,22 @@ Execution follows these rules:
|
|
|
269
281
|
- Native classes are instantiated with `new`.
|
|
270
282
|
- Functions are called normally.
|
|
271
283
|
- A resolved top-level value becomes available to subsequent keys.
|
|
272
|
-
-
|
|
273
|
-
-
|
|
284
|
+
- No result is automatically attached to the element, including `$return` results.
|
|
285
|
+
- Use an explicit AST assignment to retain an instance on `$this` or another destination.
|
|
274
286
|
|
|
275
287
|
Ordinary non-plugin keys remain part of the AST program, so they can store intermediate results or participate in later operations.
|
|
276
288
|
|
|
277
289
|
```html
|
|
278
290
|
<div
|
|
279
291
|
plugin='{
|
|
280
|
-
"ExamplePlugin": ["$this"],
|
|
281
|
-
"items": { "$
|
|
292
|
+
"instance": { "$ExamplePlugin": ["$this"] },
|
|
293
|
+
"items": { "$instance.getItems": [] },
|
|
282
294
|
"count": "$items.length"
|
|
283
295
|
}'>
|
|
284
296
|
</div>
|
|
285
297
|
```
|
|
286
298
|
|
|
287
|
-
|
|
299
|
+
Top-level result keys do not automatically become element properties. Use an explicit assignment to store values on the element.
|
|
288
300
|
|
|
289
301
|
---
|
|
290
302
|
|
|
@@ -414,19 +426,19 @@ AST configuration controls how an already trusted plugin is initialized; it does
|
|
|
414
426
|
|
|
415
427
|
## Announcements
|
|
416
428
|
|
|
417
|
-
Updates are documented in the project [CHANGELOG](https://github.com/
|
|
429
|
+
Updates are documented in the project [CHANGELOG](https://github.com/CoCreateJS/plugins/blob/master/CHANGELOG.md) and [releases](https://github.com/CoCreateJS/plugins/releases).
|
|
418
430
|
|
|
419
431
|
## Roadmap
|
|
420
432
|
|
|
421
|
-
See the open [issues](https://github.com/
|
|
433
|
+
See the open [issues](https://github.com/CoCreateJS/plugins/issues) and [pull requests](https://github.com/CoCreateJS/plugins/pulls) for current development priorities.
|
|
422
434
|
|
|
423
435
|
## How to Contribute
|
|
424
436
|
|
|
425
|
-
Contributions are welcome. See [CONTRIBUTING.md](https://github.com/
|
|
437
|
+
Contributions are welcome. See [CONTRIBUTING.md](https://github.com/CoCreateJS/plugins/blob/master/CONTRIBUTING.md) before opening an issue or pull request.
|
|
426
438
|
|
|
427
439
|
## License
|
|
428
440
|
|
|
429
441
|
This software is dual-licensed under the GNU Affero General Public License version 3 (AGPLv3) and a commercial license.
|
|
430
442
|
|
|
431
|
-
- **Open-source use:** Modifications and derivative works must comply with the AGPLv3. See the [LICENSE](https://github.com/
|
|
432
|
-
- **Commercial use:** A commercial license is available from [CoCreate](https://
|
|
443
|
+
- **Open-source use:** Modifications and derivative works must comply with the AGPLv3. See the [LICENSE](https://github.com/CoCreateJS/plugins/blob/master/LICENSE).
|
|
444
|
+
- **Commercial use:** A commercial license is available from [CoCreate](https://CoCreateJS.com).
|
package/docs/index.html
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
rel="icon"
|
|
10
10
|
type="image/png"
|
|
11
11
|
sizes="32x32"
|
|
12
|
-
href="https://
|
|
12
|
+
href="https://CoCreateJS.com/images/favicon.ico" />
|
|
13
13
|
<meta
|
|
14
14
|
name="description"
|
|
15
15
|
content="A simple HTML5 and pure javascript component. Easy configuration using HTML5 attributes or Javscript api and highly styleable." />
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
share-width="500"
|
|
60
60
|
share-media="https://via.placeholder.com/300/09f/fff.png">
|
|
61
61
|
<a
|
|
62
|
-
href="https://github.com/
|
|
62
|
+
href="https://github.com/CoCreateJS/CoCreate-plugins"
|
|
63
63
|
target="_blank"
|
|
64
64
|
class="margin-right:15px"
|
|
65
65
|
><i src="/assets/svg/github.svg"></i
|
|
@@ -103,7 +103,7 @@
|
|
|
103
103
|
<pre
|
|
104
104
|
class="margin-top:15px"><code class="language-javascript">npm install cocreate-plugins</code></pre>
|
|
105
105
|
<p class="padding:10px_0px">Or you can use cdn link:</p>
|
|
106
|
-
<pre><code class="language-javascript">https://cdn.
|
|
106
|
+
<pre><code class="language-javascript">https://cdn.CoCreateJS.com/js/CoCreate-plugins.min.js</code></pre>
|
|
107
107
|
|
|
108
108
|
<h2
|
|
109
109
|
class="border-bottom:1px_solid_lightgrey margin-top:20px padding:5px_0px">
|
|
@@ -247,6 +247,6 @@
|
|
|
247
247
|
</div>
|
|
248
248
|
</main>
|
|
249
249
|
|
|
250
|
-
<script src="https://
|
|
250
|
+
<script src="https://CoCreateJS.com/dist/CoCreate.js"></script>
|
|
251
251
|
</body>
|
|
252
252
|
</html>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cocreate/plugins",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "CoCreate plugins",
|
|
5
5
|
"author": "CoCreate LLC",
|
|
6
6
|
"license": "AGPL-3.0",
|
|
@@ -14,15 +14,15 @@
|
|
|
14
14
|
},
|
|
15
15
|
"repository": {
|
|
16
16
|
"type": "git",
|
|
17
|
-
"url": "git+https://
|
|
17
|
+
"url": "git+https://github.com/CoCreateJS/plugins.git"
|
|
18
18
|
},
|
|
19
19
|
"bugs": {
|
|
20
|
-
"url": "https://github.com/
|
|
20
|
+
"url": "https://github.com/CoCreateJS/plugins/issues"
|
|
21
21
|
},
|
|
22
|
-
"homepage": "https://
|
|
22
|
+
"homepage": "https://CoCreateJS.com/docs/plugins",
|
|
23
23
|
"funding": {
|
|
24
24
|
"type": "GitHub Sponsors ❤",
|
|
25
|
-
"url": "https://github.com/sponsors/
|
|
25
|
+
"url": "https://github.com/sponsors/CoCreateJS"
|
|
26
26
|
},
|
|
27
27
|
"type": "module",
|
|
28
28
|
"main": "./src/index.js",
|
package/src/index.js
CHANGED
|
@@ -4,6 +4,7 @@ import { astAsync } from '@cocreate/utils';
|
|
|
4
4
|
const scriptCache = new Map();
|
|
5
5
|
const pendingElements = new WeakSet();
|
|
6
6
|
const initializedPlugins = new WeakMap();
|
|
7
|
+
const initializingPlugins = new WeakMap();
|
|
7
8
|
const cssMarker = typeof document !== 'undefined'
|
|
8
9
|
? document.head.querySelector('meta[name="viewport"]') || document.head.lastChild
|
|
9
10
|
: null;
|
|
@@ -11,6 +12,8 @@ const cssMarker = typeof document !== 'undefined'
|
|
|
11
12
|
/**
|
|
12
13
|
* Retrieves the global plugin configuration object from window.CoCreateConfig.
|
|
13
14
|
* @returns {Object}
|
|
15
|
+
* @private
|
|
16
|
+
* @environment client
|
|
14
17
|
*/
|
|
15
18
|
function getPluginsConfig() {
|
|
16
19
|
if (typeof window !== 'undefined' && window.CoCreateConfig && window.CoCreateConfig.plugins) {
|
|
@@ -23,6 +26,8 @@ function getPluginsConfig() {
|
|
|
23
26
|
* Dynamically registers or updates a plugin definition in global config.
|
|
24
27
|
* @param {string} name - Plugin identifier
|
|
25
28
|
* @param {Object} config - Plugin configuration object (js, css, etc.)
|
|
29
|
+
* @public
|
|
30
|
+
* @environment client
|
|
26
31
|
*/
|
|
27
32
|
export function registerPlugin(name, config) {
|
|
28
33
|
if (typeof window !== 'undefined') {
|
|
@@ -37,6 +42,8 @@ export function registerPlugin(name, config) {
|
|
|
37
42
|
* Sets crossOrigin="anonymous" by default for simple string URLs.
|
|
38
43
|
* @param {string|Object} item - Script URL or object with configuration
|
|
39
44
|
* @returns {Promise<void>}
|
|
45
|
+
* @private
|
|
46
|
+
* @environment client
|
|
40
47
|
*/
|
|
41
48
|
function loadScript(item) {
|
|
42
49
|
const src = typeof item === 'string' ? item : item?.src;
|
|
@@ -76,6 +83,8 @@ function loadScript(item) {
|
|
|
76
83
|
* Sets crossOrigin="anonymous" by default for simple string URLs.
|
|
77
84
|
* @param {string|Object} item - Stylesheet URL or object with configuration
|
|
78
85
|
* @returns {Promise<void>}
|
|
86
|
+
* @private
|
|
87
|
+
* @environment client
|
|
79
88
|
*/
|
|
80
89
|
function loadCSS(item) {
|
|
81
90
|
const href = typeof item === 'string' ? item : item?.href;
|
|
@@ -115,6 +124,8 @@ function loadCSS(item) {
|
|
|
115
124
|
* Loads each plugin's JavaScript resources in declared order while allowing
|
|
116
125
|
* independent plugins and stylesheets to load in parallel.
|
|
117
126
|
* @param {Array<string>} pluginNames - Names of target plugins to preload
|
|
127
|
+
* @private
|
|
128
|
+
* @environment client
|
|
118
129
|
*/
|
|
119
130
|
async function preloadResources(pluginNames) {
|
|
120
131
|
const plugins = getPluginsConfig();
|
|
@@ -152,6 +163,8 @@ async function preloadResources(pluginNames) {
|
|
|
152
163
|
* @param {number} retries - Number of retries
|
|
153
164
|
* @param {number} delay - Retry interval in ms
|
|
154
165
|
* @returns {Promise<Function|Object|null>}
|
|
166
|
+
* @private
|
|
167
|
+
* @environment client
|
|
155
168
|
*/
|
|
156
169
|
async function waitForPluginOnWindow(pluginName, retries = 5, delay = 50) {
|
|
157
170
|
for (let i = 0; i < retries; i++) {
|
|
@@ -166,6 +179,8 @@ async function waitForPluginOnWindow(pluginName, retries = 5, delay = 50) {
|
|
|
166
179
|
* Helper to resolve and normalize plugin library constructor from window object.
|
|
167
180
|
* @param {string} pluginName - The target plugin identifier
|
|
168
181
|
* @returns {Function|Object|null}
|
|
182
|
+
* @private
|
|
183
|
+
* @environment client
|
|
169
184
|
*/
|
|
170
185
|
function getPluginLibrary(pluginName) {
|
|
171
186
|
if (typeof window === 'undefined') return null;
|
|
@@ -199,87 +214,32 @@ function getPluginNames(configBlock) {
|
|
|
199
214
|
return pluginNames;
|
|
200
215
|
}
|
|
201
216
|
|
|
202
|
-
function isClassLike(value) {
|
|
203
|
-
if (typeof value !== 'function') return false;
|
|
204
|
-
|
|
205
|
-
const source = Function.prototype.toString.call(value);
|
|
206
|
-
if (/^class\s|^class{/.test(source)) return true;
|
|
207
|
-
|
|
208
|
-
return !!value.prototype && Object.getOwnPropertyNames(value.prototype)
|
|
209
|
-
.some(member => member !== 'constructor');
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
function getPluginMember(library, path) {
|
|
213
|
-
let value = library;
|
|
214
|
-
|
|
215
|
-
for (const property of path) {
|
|
216
|
-
if (value === null || value === undefined || !(property in Object(value))) {
|
|
217
|
-
return undefined;
|
|
218
|
-
}
|
|
219
|
-
value = value[property];
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
return value;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
function createPluginOperations(configBlock, pluginNames, libraries) {
|
|
226
|
-
const operations = [];
|
|
227
|
-
|
|
228
|
-
for (const [key, value] of Object.entries(configBlock)) {
|
|
229
|
-
const normalizedKey = key.replace(/^\$/, '');
|
|
230
|
-
const pluginName = normalizedKey.split('.')[0];
|
|
231
|
-
|
|
232
|
-
if (!pluginNames.has(pluginName)) {
|
|
233
|
-
operations.push({ ast: { [key]: value } });
|
|
234
|
-
continue;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
let operator = normalizedKey;
|
|
238
|
-
let parameters = value;
|
|
239
|
-
const library = libraries['$' + pluginName];
|
|
240
|
-
const memberPath = normalizedKey.split('.').slice(1);
|
|
241
|
-
const pluginMember = getPluginMember(library, memberPath);
|
|
242
|
-
const members = value && typeof value === 'object' && !Array.isArray(value)
|
|
243
|
-
? Object.entries(value)
|
|
244
|
-
: [];
|
|
245
|
-
|
|
246
|
-
if (
|
|
247
|
-
!normalizedKey.includes('.') &&
|
|
248
|
-
typeof library !== 'function' &&
|
|
249
|
-
members.length === 1
|
|
250
|
-
) {
|
|
251
|
-
const [member, memberParameters] = members[0];
|
|
252
|
-
operator = `${pluginName}.${member.replace(/^\$/, '')}`;
|
|
253
|
-
parameters = memberParameters;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
operations.push({
|
|
257
|
-
ast: { ['$' + operator]: parameters },
|
|
258
|
-
pluginName,
|
|
259
|
-
attach: !normalizedKey.includes('.') || isClassLike(pluginMember)
|
|
260
|
-
});
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
return operations;
|
|
264
|
-
}
|
|
265
|
-
|
|
266
217
|
/**
|
|
267
218
|
* Parses element 'plugin' attribute, executes AST configuration via astAsync,
|
|
268
|
-
*
|
|
219
|
+
* with result storage controlled explicitly by the AST configuration.
|
|
269
220
|
* @param {Element} el - DOM element with plugin attribute
|
|
221
|
+
* @private
|
|
222
|
+
* @environment client
|
|
270
223
|
*/
|
|
271
|
-
|
|
272
|
-
if (!el || !el.attributes) return;
|
|
224
|
+
function processPlugin(el) {
|
|
225
|
+
if (!el || !el.attributes) return Promise.resolve();
|
|
273
226
|
|
|
274
227
|
const pluginAttrNode = el.attributes['plugin'] || el.attributes.getNamedItem?.('plugin');
|
|
275
228
|
const rawPluginAttr = pluginAttrNode?.value?.trim();
|
|
276
|
-
if (!rawPluginAttr) return;
|
|
229
|
+
if (!rawPluginAttr) return Promise.resolve();
|
|
277
230
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
if (initializedPlugins.get(el) === rawPluginAttr) return;
|
|
281
|
-
|
|
231
|
+
const pending = initializingPlugins.get(el);
|
|
232
|
+
if (pending?.value === rawPluginAttr) return pending.promise;
|
|
233
|
+
if (initializedPlugins.get(el) === rawPluginAttr) return Promise.resolve();
|
|
234
|
+
|
|
235
|
+
const promise = initializePlugin(el, rawPluginAttr).finally(() => {
|
|
236
|
+
if (initializingPlugins.get(el)?.promise === promise) initializingPlugins.delete(el);
|
|
237
|
+
});
|
|
238
|
+
initializingPlugins.set(el, { value: rawPluginAttr, promise });
|
|
239
|
+
return promise;
|
|
240
|
+
}
|
|
282
241
|
|
|
242
|
+
async function initializePlugin(el, rawPluginAttr) {
|
|
283
243
|
try {
|
|
284
244
|
let configs = [];
|
|
285
245
|
|
|
@@ -287,7 +247,7 @@ async function processPlugin(el) {
|
|
|
287
247
|
const parsedConfig = JSON.parse(rawPluginAttr);
|
|
288
248
|
configs = Array.isArray(parsedConfig) ? parsedConfig : [parsedConfig];
|
|
289
249
|
} else {
|
|
290
|
-
configs = [{ [rawPluginAttr]: [] }];
|
|
250
|
+
configs = [{ ['$' + rawPluginAttr.replace(/^\$/, '')]: [] }];
|
|
291
251
|
}
|
|
292
252
|
|
|
293
253
|
const configPlans = configs.map(configBlock => ({
|
|
@@ -313,39 +273,13 @@ async function processPlugin(el) {
|
|
|
313
273
|
|
|
314
274
|
const scope = { ...libraries, $this: el };
|
|
315
275
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
});
|
|
322
|
-
|
|
323
|
-
if (value !== undefined && pluginNames.size === 1) {
|
|
324
|
-
const [pluginName] = pluginNames;
|
|
325
|
-
el[pluginName] = value;
|
|
326
|
-
}
|
|
327
|
-
continue;
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
const operations = createPluginOperations(
|
|
331
|
-
configBlock,
|
|
332
|
-
pluginNames,
|
|
333
|
-
libraries
|
|
334
|
-
);
|
|
335
|
-
|
|
336
|
-
for (const operation of operations) {
|
|
337
|
-
const value = await astAsync({
|
|
338
|
-
executeAST: operation.ast,
|
|
339
|
-
sandbox: scope,
|
|
340
|
-
node: el
|
|
341
|
-
});
|
|
342
|
-
|
|
343
|
-
if (operation.attach && value !== undefined) {
|
|
344
|
-
scope['$' + operation.pluginName] = value;
|
|
345
|
-
el[operation.pluginName] = value;
|
|
346
|
-
}
|
|
347
|
-
}
|
|
276
|
+
await astAsync({
|
|
277
|
+
executeAST: configBlock,
|
|
278
|
+
sandbox: scope,
|
|
279
|
+
node: el
|
|
280
|
+
});
|
|
348
281
|
}
|
|
282
|
+
initializedPlugins.set(el, rawPluginAttr);
|
|
349
283
|
} catch (err) {
|
|
350
284
|
initializedPlugins.delete(el);
|
|
351
285
|
console.error('[Plugin System] Failed to parse and execute plugin configuration:', err, rawPluginAttr);
|
|
@@ -355,6 +289,8 @@ async function processPlugin(el) {
|
|
|
355
289
|
/**
|
|
356
290
|
* Initializes plugins on single elements, collections, or selector query matches.
|
|
357
291
|
* @param {Element|NodeList|Array|string} elements - Target element(s) or DOM query selector
|
|
292
|
+
* @public
|
|
293
|
+
* @environment client
|
|
358
294
|
*/
|
|
359
295
|
export function init(elements) {
|
|
360
296
|
if (!elements) return Promise.resolve([]);
|
|
@@ -402,3 +338,33 @@ if (typeof document !== 'undefined') {
|
|
|
402
338
|
}
|
|
403
339
|
|
|
404
340
|
export default { init, registerPlugin };
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
*
|
|
344
|
+
* @name plugin
|
|
345
|
+
* @kind member
|
|
346
|
+
* @attribute
|
|
347
|
+
* @environment client
|
|
348
|
+
*/
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* CoCreate plugins
|
|
352
|
+
*
|
|
353
|
+
* @module plugins
|
|
354
|
+
* @environment client
|
|
355
|
+
*/
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Declarative selectors recognized by this module.
|
|
359
|
+
*
|
|
360
|
+
* @name declarative-selectors
|
|
361
|
+
* @kind member
|
|
362
|
+
* @selectors
|
|
363
|
+
* @environment client
|
|
364
|
+
* @example
|
|
365
|
+
* ```json
|
|
366
|
+
* [
|
|
367
|
+
* "[plugin]"
|
|
368
|
+
* ]
|
|
369
|
+
* ```
|
|
370
|
+
*/
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import vm from 'node:vm';
|
|
4
|
+
import test from 'node:test';
|
|
5
|
+
import { astAsync } from '@cocreate/utils';
|
|
6
|
+
|
|
7
|
+
const code = fs.readFileSync(new URL('../src/index.js', import.meta.url), 'utf8')
|
|
8
|
+
.replace(/^import .*;\r?\n/gm, '')
|
|
9
|
+
.replace(/^export /gm, '')
|
|
10
|
+
.replace('default { init, registerPlugin };', 'globalThis.pluginInit = init;');
|
|
11
|
+
|
|
12
|
+
class Element {
|
|
13
|
+
constructor(config) {
|
|
14
|
+
this.attributes = { plugin: { value: typeof config === 'string' ? config : JSON.stringify(config) } };
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function runtime(libraries, evaluate = astAsync) {
|
|
19
|
+
const errors = [];
|
|
20
|
+
const context = {
|
|
21
|
+
astAsync: evaluate, Element,
|
|
22
|
+
window: { ...libraries, Object },
|
|
23
|
+
console: { error: (...args) => errors.push(args), warn: (...args) => errors.push(args) }
|
|
24
|
+
};
|
|
25
|
+
vm.runInNewContext(code, context);
|
|
26
|
+
return {
|
|
27
|
+
async init(element) {
|
|
28
|
+
await context.pluginInit(element);
|
|
29
|
+
assert.deepEqual(errors, []);
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
test('bare plugin initialization does not attach its returned instance', async () => {
|
|
35
|
+
let calls = 0;
|
|
36
|
+
class ExamplePlugin {
|
|
37
|
+
constructor() { calls++; }
|
|
38
|
+
}
|
|
39
|
+
const app = runtime({ ExamplePlugin });
|
|
40
|
+
const el = new Element('ExamplePlugin');
|
|
41
|
+
await app.init(el);
|
|
42
|
+
await app.init(el);
|
|
43
|
+
assert.equal(calls, 1);
|
|
44
|
+
assert.equal(Object.hasOwn(el, 'ExamplePlugin'), false);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test('nested constructors and $return results do not attach automatically', async () => {
|
|
48
|
+
class Widget {}
|
|
49
|
+
const app = runtime({ Library: { Widget } });
|
|
50
|
+
for (const config of [
|
|
51
|
+
{ '$Library.Widget': [] },
|
|
52
|
+
{ $return: { '$Library.Widget': [] } }
|
|
53
|
+
]) {
|
|
54
|
+
const el = new Element(config);
|
|
55
|
+
await app.init(el);
|
|
56
|
+
assert.equal(Object.hasOwn(el, 'Library'), false);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test('AST can explicitly store a returned instance on $this under a chosen key', async () => {
|
|
61
|
+
class ExamplePlugin {}
|
|
62
|
+
const app = runtime({ ExamplePlugin });
|
|
63
|
+
const el = new Element({
|
|
64
|
+
'$this.customInstance': { $ExamplePlugin: [] }
|
|
65
|
+
});
|
|
66
|
+
await app.init(el);
|
|
67
|
+
assert.ok(el.customInstance instanceof ExamplePlugin);
|
|
68
|
+
assert.equal(Object.hasOwn(el, 'ExamplePlugin'), false);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test('AST can store results elsewhere and call methods on explicit references', async () => {
|
|
72
|
+
class ExamplePlugin {
|
|
73
|
+
render() { this.rendered = true; }
|
|
74
|
+
}
|
|
75
|
+
const destination = {};
|
|
76
|
+
const app = runtime({ ExamplePlugin, destination });
|
|
77
|
+
const el = new Element({
|
|
78
|
+
instance: { $ExamplePlugin: [] },
|
|
79
|
+
'$instance.render': [],
|
|
80
|
+
$return: { '$destination': { '$Object.assign': ['$destination', { instance: '$instance' }] } }
|
|
81
|
+
});
|
|
82
|
+
await app.init(el);
|
|
83
|
+
assert.ok(destination.instance instanceof ExamplePlugin);
|
|
84
|
+
assert.equal(destination.instance.rendered, true);
|
|
85
|
+
assert.equal(Object.hasOwn(el, 'ExamplePlugin'), false);
|
|
86
|
+
assert.equal(Object.hasOwn(el, 'instance'), false);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test('subsequent operations keep referencing a global library after a call returns a value', async () => {
|
|
90
|
+
let calls = 0;
|
|
91
|
+
function GlobalPlugin() { return {}; }
|
|
92
|
+
GlobalPlugin.highlightAll = () => { calls++; };
|
|
93
|
+
const app = runtime({ GlobalPlugin });
|
|
94
|
+
const el = new Element({ '$GlobalPlugin': [], '$GlobalPlugin.highlightAll': [] });
|
|
95
|
+
await app.init(el);
|
|
96
|
+
assert.equal(calls, 1);
|
|
97
|
+
assert.equal(Object.hasOwn(el, 'GlobalPlugin'), false);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test('passes the entire unchanged configuration to AST once with the host and library scope', async () => {
|
|
101
|
+
const config = {
|
|
102
|
+
created: { $ExamplePlugin: ['$this'] },
|
|
103
|
+
'$this.instance': '$created',
|
|
104
|
+
'$this.instance.render': []
|
|
105
|
+
};
|
|
106
|
+
class ExamplePlugin {
|
|
107
|
+
constructor(host) { this.host = host; }
|
|
108
|
+
render() { this.rendered = true; }
|
|
109
|
+
}
|
|
110
|
+
const calls = [];
|
|
111
|
+
const app = runtime({ ExamplePlugin }, async options => {
|
|
112
|
+
calls.push(options);
|
|
113
|
+
return astAsync(options);
|
|
114
|
+
});
|
|
115
|
+
const el = new Element(config);
|
|
116
|
+
await app.init(el);
|
|
117
|
+
assert.equal(calls.length, 1);
|
|
118
|
+
assert.deepEqual(JSON.parse(JSON.stringify(calls[0].executeAST)), config);
|
|
119
|
+
assert.equal(calls[0].sandbox.$this, el);
|
|
120
|
+
assert.equal(calls[0].sandbox.$ExamplePlugin, ExamplePlugin);
|
|
121
|
+
assert.equal(calls[0].node, el);
|
|
122
|
+
assert.ok(el.instance instanceof ExamplePlugin);
|
|
123
|
+
assert.equal(el.instance.host, el);
|
|
124
|
+
assert.equal(el.instance.rendered, true);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test('plain keys store data rather than being rewritten into plugin calls', async () => {
|
|
128
|
+
let calls = 0;
|
|
129
|
+
function ExamplePlugin() { calls++; }
|
|
130
|
+
const app = runtime({ ExamplePlugin });
|
|
131
|
+
const el = new Element({
|
|
132
|
+
ExamplePlugin: ['configuration data'],
|
|
133
|
+
'$this.saved': '$ExamplePlugin'
|
|
134
|
+
});
|
|
135
|
+
await app.init(el);
|
|
136
|
+
assert.equal(calls, 0);
|
|
137
|
+
assert.equal(el.saved, ExamplePlugin);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
test('configuration blocks execute in order without special treatment for $return', async () => {
|
|
141
|
+
const order = [];
|
|
142
|
+
function ExamplePlugin(value) { order.push(value); return value; }
|
|
143
|
+
const app = runtime({ ExamplePlugin });
|
|
144
|
+
const el = new Element([
|
|
145
|
+
{ '$this.first': { $ExamplePlugin: ['first'] } },
|
|
146
|
+
{ $return: { '$this.second': { $ExamplePlugin: ['$this.first'] } } }
|
|
147
|
+
]);
|
|
148
|
+
await app.init(el);
|
|
149
|
+
assert.deepEqual(order, ['first', 'first']);
|
|
150
|
+
assert.equal(el.first, 'first');
|
|
151
|
+
assert.equal(el.second, 'first');
|
|
152
|
+
assert.equal(Object.hasOwn(el, 'ExamplePlugin'), false);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
test('concurrent initialization calls await the same pending setup', async () => {
|
|
156
|
+
let release;
|
|
157
|
+
let entered;
|
|
158
|
+
let calls = 0;
|
|
159
|
+
const gate = new Promise(resolve => { release = resolve; });
|
|
160
|
+
const started = new Promise(resolve => { entered = resolve; });
|
|
161
|
+
const app = runtime({ ExamplePlugin() {} }, async args => {
|
|
162
|
+
calls++;
|
|
163
|
+
entered();
|
|
164
|
+
await gate;
|
|
165
|
+
return astAsync(args);
|
|
166
|
+
});
|
|
167
|
+
const element = new Element('ExamplePlugin');
|
|
168
|
+
const first = app.init(element);
|
|
169
|
+
await started;
|
|
170
|
+
let secondFinished = false;
|
|
171
|
+
const second = app.init(element).then(() => { secondFinished = true; });
|
|
172
|
+
await Promise.resolve();
|
|
173
|
+
await Promise.resolve();
|
|
174
|
+
assert.equal(secondFinished, false);
|
|
175
|
+
assert.equal(calls, 1);
|
|
176
|
+
release();
|
|
177
|
+
await Promise.all([first, second]);
|
|
178
|
+
assert.equal(secondFinished, true);
|
|
179
|
+
await app.init(element);
|
|
180
|
+
assert.equal(calls, 1);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
test('a plugin can await another initializer before connecting its result', async () => {
|
|
184
|
+
let release;
|
|
185
|
+
let entered;
|
|
186
|
+
let connected = false;
|
|
187
|
+
const gate = new Promise(resolve => { release = resolve; });
|
|
188
|
+
const started = new Promise(resolve => { entered = resolve; });
|
|
189
|
+
const markdown = new Element({ '$this.parser': { $Parser: [] } });
|
|
190
|
+
const CoCreate = { plugins: { init: () => app.init(markdown) } };
|
|
191
|
+
const app = runtime({
|
|
192
|
+
CoCreate,
|
|
193
|
+
async Parser() {
|
|
194
|
+
entered();
|
|
195
|
+
await gate;
|
|
196
|
+
return { ready: true };
|
|
197
|
+
},
|
|
198
|
+
connect() {
|
|
199
|
+
assert.equal(markdown.parser.ready, true);
|
|
200
|
+
connected = true;
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
const prism = new Element({ '$CoCreate.plugins.init': ['#markdown'], $connect: [] });
|
|
204
|
+
const first = app.init(markdown);
|
|
205
|
+
await started;
|
|
206
|
+
const second = app.init(prism);
|
|
207
|
+
await Promise.resolve();
|
|
208
|
+
assert.equal(connected, false);
|
|
209
|
+
release();
|
|
210
|
+
await Promise.all([first, second]);
|
|
211
|
+
assert.equal(connected, true);
|
|
212
|
+
});
|
|
File without changes
|