@webhandle/external-resource-manager 1.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/README.md +203 -0
- package/create-application-javascript-renderer.mjs +23 -0
- package/create-attributes.mjs +17 -0
- package/create-css-renderer.mjs +19 -0
- package/escape-attribute-value.mjs +23 -0
- package/external-resource-manager.mjs +138 -0
- package/importmap-generator-creator.mjs +36 -0
- package/initialize-webhandle-component.mjs +42 -0
- package/package.json +29 -0
- package/resource.mjs +50 -0
package/README.md
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
# @webhandle/external-resource-manager
|
|
2
|
+
|
|
3
|
+
Coordinates which external resources like scripts and css get put on
|
|
4
|
+
a page.
|
|
5
|
+
|
|
6
|
+
## Install
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
npm install @webhandle/external-resource-manager
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Purpose
|
|
13
|
+
|
|
14
|
+
Integrating components written by different people at different times is messy.
|
|
15
|
+
|
|
16
|
+
One approach is the one Wordpress uses: every component includes its own css files
|
|
17
|
+
and scripts and there's a dependency system to make sure dependencies get loaded and
|
|
18
|
+
get loaded before the dependents. This is kinda complicated and results in every page
|
|
19
|
+
having a million little files that the browser has to fetch.
|
|
20
|
+
|
|
21
|
+
Another approach is to use Webpack or Browserify to compile all the small files into
|
|
22
|
+
one larger file, and just have the page load that. This also works, but now the complication
|
|
23
|
+
is that you have to figure out all of the files to be compiled and if a compoment adds a
|
|
24
|
+
file, now the page is broken.
|
|
25
|
+
|
|
26
|
+
External Resource Manager represents a third approach, allowing the component flexibility
|
|
27
|
+
to include what it wants and the ability to combine files for performance.
|
|
28
|
+
|
|
29
|
+
## Usage
|
|
30
|
+
|
|
31
|
+
The two main setup functions are:
|
|
32
|
+
|
|
33
|
+
```js
|
|
34
|
+
externalResourceManager.includeResource({
|
|
35
|
+
mimeType: type
|
|
36
|
+
, url: url
|
|
37
|
+
})
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
and
|
|
41
|
+
|
|
42
|
+
```js
|
|
43
|
+
externalResourceManager.provideResource({
|
|
44
|
+
mimeType: type
|
|
45
|
+
, url: url
|
|
46
|
+
, name: name
|
|
47
|
+
, resourceType: subType
|
|
48
|
+
})
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
`includeResource` is a directive to include a resource in the page. It will result in a
|
|
52
|
+
`link` element or `script` element or whatever is appropriate for its type.
|
|
53
|
+
|
|
54
|
+
`provideResource` lets components state that a resource is available for use, if somebody
|
|
55
|
+
wants to use it. It does not necessarily result in anything getting loaded by the browser.
|
|
56
|
+
|
|
57
|
+
Components all given a chance to call these function with middleware that they add to the
|
|
58
|
+
express routers.
|
|
59
|
+
|
|
60
|
+
Later, when html for the page is being created, `externalResourceManager.render()` can be
|
|
61
|
+
called, which creates html to include the resource. It's safe to call `render` multiple
|
|
62
|
+
times because subsequent calls will only create html for resources added since the last call
|
|
63
|
+
to `render`. This means templates themselves can include resesources on the page, so long
|
|
64
|
+
as those resources can tolerate being included in the body.
|
|
65
|
+
|
|
66
|
+
To specify the additional attributes that are sometimes used with elements, you can call like:
|
|
67
|
+
|
|
68
|
+
```js
|
|
69
|
+
externalResourceManager.includeResource({
|
|
70
|
+
mimeType: 'application/javascript'
|
|
71
|
+
, url: '/js/pages.mjs'
|
|
72
|
+
, attributes: {
|
|
73
|
+
defer: undefined
|
|
74
|
+
}
|
|
75
|
+
, cachable: false
|
|
76
|
+
})
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Any attribute with a `null` or `undefined` value will/should be rendered to the page as an
|
|
80
|
+
attribute without a value like:
|
|
81
|
+
|
|
82
|
+
```html
|
|
83
|
+
<script src="/js/pages.mjs" defer ></script>
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
As you can see in the example above, resources may also have a `cachable` attribute, which is
|
|
87
|
+
true by default. Whether or not this resource ever actually has cache headers or version
|
|
88
|
+
url parameters applied to it depends on the renderers being used. The attribute just signifies
|
|
89
|
+
this resource is eligible to be used with whatever caching mechanism the site is using and isn't
|
|
90
|
+
some sort of url which resolves to dynamic code and only looks like a file URL.
|
|
91
|
+
|
|
92
|
+
## How does this help with dependencies?
|
|
93
|
+
|
|
94
|
+
`ExternalResourceManager` guarantees that it will not include a url twice, so any component
|
|
95
|
+
is free to explicitly add a resource or call a function from the dependency which adds a
|
|
96
|
+
resource without fear that it will be included multiple times. Resources are rendered in the
|
|
97
|
+
order added, so as long as a component includes its dependencies first, everything should work
|
|
98
|
+
out. This avoids having named resources and a dependency tree at the small cost of having code
|
|
99
|
+
which should understand its dependencies call a function on them.
|
|
100
|
+
|
|
101
|
+
Further, `ExternalResourceManager` makes use of javascript modules and importmap scripts. This
|
|
102
|
+
allows a component to provide functionality to the page which will get loaded ONLY if it's needed.
|
|
103
|
+
To provide a module to a page, we call like:
|
|
104
|
+
|
|
105
|
+
```js
|
|
106
|
+
externalResourceManager.provideResource({
|
|
107
|
+
url: '/js/one.js'
|
|
108
|
+
, mimeType: 'application/javascript'
|
|
109
|
+
, resourceType: 'module'
|
|
110
|
+
, name: '@webhandle/moduleone'
|
|
111
|
+
})
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
This will produce an entry in an importmap so that any javascript which makes a call like:
|
|
115
|
+
|
|
116
|
+
```js
|
|
117
|
+
import myImportantFunction from "@webhandle/moduleone"
|
|
118
|
+
```
|
|
119
|
+
will cause the browser to load the module from the server. In this way you can add substantial
|
|
120
|
+
libraries of modules to the page with a performance hit since they're only loaded at need.
|
|
121
|
+
Additionally, because the importmap is parsed by the browser before any of the modules are actually
|
|
122
|
+
loaded, it doesn't matter what order the modules are added via `provideResource`.
|
|
123
|
+
|
|
124
|
+
A note: at the time of this writing, Firefox does not support multiple importmaps, so if possible,
|
|
125
|
+
components which provide resources should try to do so before rendering starts. Since providing a
|
|
126
|
+
resource does not result in any extra requests to the server, this should be safe to do in middle
|
|
127
|
+
for any request which might even potentially make use of the component.
|
|
128
|
+
|
|
129
|
+
## How does this get rid of the million requests problem?
|
|
130
|
+
|
|
131
|
+
Inlcuded resources are able to say that they "satisfy" other included URLs. For instance, let's say
|
|
132
|
+
individual components have included css files that they need.
|
|
133
|
+
|
|
134
|
+
```js
|
|
135
|
+
externalResourceManager.includeResource({
|
|
136
|
+
mimeType: 'text/css'
|
|
137
|
+
, url: '/small/package/level.css'
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
externalResourceManager.includeResource({
|
|
141
|
+
mimeType: 'text/css'
|
|
142
|
+
, url: '/small/package/level-too.css'
|
|
143
|
+
})
|
|
144
|
+
```
|
|
145
|
+
We can at any time, either before those components include their resources or after, have code
|
|
146
|
+
like the following:
|
|
147
|
+
|
|
148
|
+
```js
|
|
149
|
+
externalResourceManager.includeResource({
|
|
150
|
+
mimeType: 'text/css'
|
|
151
|
+
, url: '/css/compiled.css'
|
|
152
|
+
, satisfies: [
|
|
153
|
+
'/small/package/level.css'
|
|
154
|
+
, '/small/package/level-too.css'
|
|
155
|
+
]
|
|
156
|
+
})
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
This will cause `/css/compiled.css` to get included on the page while `/small/package/level.css`
|
|
160
|
+
and `/small/package/level-too.css` will not.
|
|
161
|
+
|
|
162
|
+
This is "satisfies" instead of "contains" because `/css/compiled.css` may not actually have the
|
|
163
|
+
code from `level.css` in it at all. However, it claims to be functionally equivalent. This lets
|
|
164
|
+
us replace styles we don't like with styles we do like, as well as just bundling thing up for
|
|
165
|
+
performance reasons.
|
|
166
|
+
|
|
167
|
+
This works so long as all these call are made before `render` is called.
|
|
168
|
+
|
|
169
|
+
In this way, we can get the best of both worlds for very little cost. We can add a new component
|
|
170
|
+
to the site and just have it work. After adding too many new components, we can set up builds to
|
|
171
|
+
combine those small files and just have those loaded (and not even have to worry about trying to
|
|
172
|
+
tell the component not to load its resources). And when we add a new component, no problem,
|
|
173
|
+
this small dependencies will get added to the page until we include them in the build.
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
## Usage in Webhandle
|
|
177
|
+
|
|
178
|
+
In Webhandle, every response has its own `ExternalResourceManager` at `res.locals.externalResourceManager`
|
|
179
|
+
|
|
180
|
+
That's kind of a weird place to put it, I know, but this allows any template to include a resource
|
|
181
|
+
and allows any template to call `render`.
|
|
182
|
+
|
|
183
|
+
### Resource Type Support
|
|
184
|
+
|
|
185
|
+
Currently there's code to include mime types `application/javascript` and `text/css`. There's also
|
|
186
|
+
code to create an importmap based on mime type `application/javascript` with resource type `module`.
|
|
187
|
+
`ExternalResourceManager` works internally by having code registered to handle different mime types,
|
|
188
|
+
so extensability is really easy if you wanted to add `meta` element or `title` element handling
|
|
189
|
+
(or something).
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
### Integration
|
|
193
|
+
|
|
194
|
+
As you'd expect, this can be integrated into webhandle by calling:
|
|
195
|
+
|
|
196
|
+
```js
|
|
197
|
+
import setup from "@webhandle/external-resource-manager/initialize-webhandle-component.mjs"
|
|
198
|
+
await setup(myWebhandleInstance, options)
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import createAttributes from "./create-attributes.mjs"
|
|
2
|
+
import escapeAttributeValue from "./escape-attribute-value.mjs"
|
|
3
|
+
|
|
4
|
+
export default function createApplicationJavascriptRenderer(webhandle) {
|
|
5
|
+
function renderApplicationJS(resource) {
|
|
6
|
+
|
|
7
|
+
let vrsc = ''
|
|
8
|
+
if (!webhandle.development && resource.cachable) {
|
|
9
|
+
vrsc = '/vrsc/' + webhandle.resourceVersion
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
let html = `<script src="${vrsc}${escapeAttributeValue(resource.url)}" `
|
|
13
|
+
|
|
14
|
+
html += createAttributes(resource.attributes)
|
|
15
|
+
if ('type' in resource.attributes === false && resource.resourceType === 'module') {
|
|
16
|
+
html += ' type="module"'
|
|
17
|
+
}
|
|
18
|
+
html += '></script>'
|
|
19
|
+
return html
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return renderApplicationJS
|
|
23
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import escapeAttributeValue from "./escape-attribute-value.mjs"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Creates an html attributes string based on the members of an object
|
|
5
|
+
* @param {object} attributes
|
|
6
|
+
*/
|
|
7
|
+
export default function createAttributes(attributes) {
|
|
8
|
+
let html = Object.entries(attributes).map(entries => {
|
|
9
|
+
if (entries[1] === null || entries[1] === undefined) {
|
|
10
|
+
return entries[0]
|
|
11
|
+
}
|
|
12
|
+
else {
|
|
13
|
+
return entries[0] + '="' + escapeAttributeValue(entries[1]) + '"'
|
|
14
|
+
}
|
|
15
|
+
}).join(' ')
|
|
16
|
+
return html
|
|
17
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import createAttributes from "./create-attributes.mjs"
|
|
2
|
+
import escapeAttributeValue from "./escape-attribute-value.mjs"
|
|
3
|
+
export default function createTextCssRenderer(webhandle) {
|
|
4
|
+
function renderTextCss(resource) {
|
|
5
|
+
|
|
6
|
+
let vrsc = ''
|
|
7
|
+
if(!webhandle.development && resource.cachable) {
|
|
8
|
+
vrsc = '/vrsc/' + webhandle.resourceVersion
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
let html = `<link href="${vrsc}${escapeAttributeValue(resource.url)}" rel="stylesheet" `
|
|
12
|
+
|
|
13
|
+
html += createAttributes(resource.attributes)
|
|
14
|
+
html += '/>'
|
|
15
|
+
return html
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return renderTextCss
|
|
19
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
|
|
2
|
+
/**
|
|
3
|
+
*
|
|
4
|
+
* @param {string} s the value to be escaped
|
|
5
|
+
* @param {boolen} preserveCR If true ' ' will be used instead of \n
|
|
6
|
+
* @returns
|
|
7
|
+
*/
|
|
8
|
+
export default function escapeAttributeValue(s, preserveCR) {
|
|
9
|
+
preserveCR = preserveCR ? ' ' : '\n';
|
|
10
|
+
return ('' + s) /* Forces the conversion to string. */
|
|
11
|
+
.replace(/&/g, '&') /* This MUST be the 1st replacement. */
|
|
12
|
+
.replace(/'/g, ''') /* The 4 other predefined entities, required. */
|
|
13
|
+
.replace(/"/g, '"')
|
|
14
|
+
.replace(/</g, '<')
|
|
15
|
+
.replace(/>/g, '>')
|
|
16
|
+
/*
|
|
17
|
+
You may add other replacements here for HTML only
|
|
18
|
+
(but it's not necessary).
|
|
19
|
+
Or for XML, only if the named entities are defined in its DTD.
|
|
20
|
+
*/
|
|
21
|
+
.replace(/\r\n/g, preserveCR) /* Must be before the next replacement. */
|
|
22
|
+
.replace(/[\r\n]/g, preserveCR)
|
|
23
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import Resource from "./resource.mjs"
|
|
2
|
+
|
|
3
|
+
export default class ExternalResourceManager {
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Resources for which we have a directive to include on the page
|
|
7
|
+
*/
|
|
8
|
+
includedResources = new Set()
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Resources which are available to provide content
|
|
12
|
+
*/
|
|
13
|
+
providedResources = []
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Code which can generate html based on all of the entries of the mananger.
|
|
17
|
+
* Good place form importmap generation or preload generation.
|
|
18
|
+
*/
|
|
19
|
+
preTypeRenderers = []
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Renderers by mime type
|
|
23
|
+
*/
|
|
24
|
+
renderers = {}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* The set of urls which are known to have been satisfied by some other resource
|
|
28
|
+
*/
|
|
29
|
+
alreadySatisfied = new Set()
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Resources that we were directed to include, but are not going to, because
|
|
33
|
+
* some other resource satisfies the url.
|
|
34
|
+
*/
|
|
35
|
+
differentlySatisfiedResource = []
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* To make sure we don't render a resource to the page twice, we'll keep track
|
|
39
|
+
* of the URLs we've already rendered here.
|
|
40
|
+
*/
|
|
41
|
+
alreadyRenderedUrls = new Set()
|
|
42
|
+
|
|
43
|
+
constructor(options) {
|
|
44
|
+
Object.assign(this, options)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
_noteSatisfies(resource) {
|
|
49
|
+
if(resource.satisfies && Array.isArray(resource.satisfies)) {
|
|
50
|
+
for(let url of resource.satisfies) {
|
|
51
|
+
this.alreadySatisfied.add(url)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
includeResource(resource) {
|
|
57
|
+
if(!resource) {
|
|
58
|
+
return
|
|
59
|
+
}
|
|
60
|
+
if(typeof resource === 'object' && resource instanceof Resource === false) {
|
|
61
|
+
resource = new Resource(resource)
|
|
62
|
+
}
|
|
63
|
+
this._noteSatisfies(resource)
|
|
64
|
+
if(this.alreadySatisfied.has(resource.url)) {
|
|
65
|
+
// We've already got a resource which satisfies this url
|
|
66
|
+
this.differentlySatisfiedResource.push(resource)
|
|
67
|
+
return
|
|
68
|
+
}
|
|
69
|
+
if(resource.satisfies && Array.isArray(resource.satisfies)) {
|
|
70
|
+
// We're going to remove any previously included resource which is satisfied
|
|
71
|
+
// by this resource
|
|
72
|
+
for(let included of this.includedResources) {
|
|
73
|
+
if(resource.satisfies.includes(included.url)) {
|
|
74
|
+
this.includedResources.delete(included)
|
|
75
|
+
this.differentlySatisfiedResource.push(resource)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
this._noteSatisfies(resource)
|
|
79
|
+
}
|
|
80
|
+
this.includedResources.add(resource)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
provideResource(resource) {
|
|
84
|
+
this.providedResources.push(resource)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Adds a piece of code which knows how to turn a resource into html.
|
|
89
|
+
* @param {string} mimeType
|
|
90
|
+
* @param {function} handler Takes a `Resource` object and turns it into a bit of html that can
|
|
91
|
+
* be included on the page.
|
|
92
|
+
*/
|
|
93
|
+
addTypeHandler(mimeType, handler) {
|
|
94
|
+
this.renderers[mimeType] = handler
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Adds code which will generate html needed before the types are processed individually. Good
|
|
99
|
+
* place for code which generates importmaps or preload statements.
|
|
100
|
+
* @param {function} handler Takes a `ExternalResourceManager` object and turns it into a bit of html that can
|
|
101
|
+
* be included on the page.
|
|
102
|
+
*/
|
|
103
|
+
addPreTypeRender(handler) {
|
|
104
|
+
this.preTypeRenderers.push(handler)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
render() {
|
|
108
|
+
let result = ''
|
|
109
|
+
for(let renderer of this.preTypeRenderers) {
|
|
110
|
+
result += renderer(this)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
for(let resource of this.includedResources) {
|
|
114
|
+
if(this.alreadyRenderedUrls.has(resource.url) || this.alreadySatisfied.has(resource.url)) {
|
|
115
|
+
// A late in the game catch to make sure we don't output to the page a url that has
|
|
116
|
+
// already been used or a url which we know gets satisfied elsewhere
|
|
117
|
+
resource.rendered = true
|
|
118
|
+
continue
|
|
119
|
+
}
|
|
120
|
+
let renderer = this.renderers[resource.mimeType]
|
|
121
|
+
if(renderer && !resource.rendered) {
|
|
122
|
+
result += '\n'
|
|
123
|
+
result += renderer(resource)
|
|
124
|
+
resource.rendered = true
|
|
125
|
+
}
|
|
126
|
+
this.alreadyRenderedUrls.add(resource.url)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
result += '\n'
|
|
130
|
+
for(let provided of this.providedResources) {
|
|
131
|
+
provided.rendered = true
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return result
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
|
|
2
|
+
export default function createImportmapGenerator(webhandle) {
|
|
3
|
+
function importmapGenerator(manager) {
|
|
4
|
+
|
|
5
|
+
let imports = {}
|
|
6
|
+
|
|
7
|
+
let found = false
|
|
8
|
+
for (let resource of manager.providedResources) {
|
|
9
|
+
if(resource.rendered) {
|
|
10
|
+
continue
|
|
11
|
+
}
|
|
12
|
+
found = true
|
|
13
|
+
let vrsc = ''
|
|
14
|
+
if(!webhandle.development && resource.cachable) {
|
|
15
|
+
vrsc = '/vrsc/' + webhandle.resourceVersion
|
|
16
|
+
}
|
|
17
|
+
if (resource.mimeType === 'application/javascript' && resource.resourceType === 'module') {
|
|
18
|
+
imports[resource.name] = vrsc + resource.url
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
let data = {
|
|
23
|
+
imports
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
let dataText = webhandle.development ? JSON.stringify(data, null, '\t') : JSON.stringify(data)
|
|
27
|
+
|
|
28
|
+
let template = found ?
|
|
29
|
+
`<script type="importmap">
|
|
30
|
+
${dataText}
|
|
31
|
+
</script>` : ''
|
|
32
|
+
return template
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return importmapGenerator
|
|
36
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import createInitializeWebhandleComponent from "@webhandle/initialize-webhandle-component/create-initialize-webhandle-component.mjs"
|
|
2
|
+
import ComponentManager from "@webhandle/initialize-webhandle-component/component-manager.mjs"
|
|
3
|
+
import path from "node:path"
|
|
4
|
+
import ExternalResourceManager from "./external-resource-manager.mjs"
|
|
5
|
+
import createImportmapGenerator from "./importmap-generator-creator.mjs"
|
|
6
|
+
import createTextCssRenderer from "./create-css-renderer.mjs"
|
|
7
|
+
import createApplicationJavascriptRenderer from "./create-application-javascript-renderer.mjs"
|
|
8
|
+
|
|
9
|
+
let initializeWebhandleComponent = createInitializeWebhandleComponent()
|
|
10
|
+
|
|
11
|
+
initializeWebhandleComponent.componentName = 'externalResourceManager'
|
|
12
|
+
initializeWebhandleComponent.componentDir = import.meta.dirname
|
|
13
|
+
initializeWebhandleComponent.defaultConfig = {}
|
|
14
|
+
initializeWebhandleComponent.staticFilePaths = ['public']
|
|
15
|
+
initializeWebhandleComponent.templatePaths = ['views']
|
|
16
|
+
|
|
17
|
+
initializeWebhandleComponent.setup = async function (webhandle, config) {
|
|
18
|
+
let compmanager = new ComponentManager()
|
|
19
|
+
|
|
20
|
+
let generator = createImportmapGenerator(webhandle)
|
|
21
|
+
let cssRender = createTextCssRenderer(webhandle)
|
|
22
|
+
let jsRender = createApplicationJavascriptRenderer(webhandle)
|
|
23
|
+
|
|
24
|
+
webhandle.routers.preParmParse.use((req, res, next) => {
|
|
25
|
+
let externalResourceManager = new ExternalResourceManager()
|
|
26
|
+
|
|
27
|
+
externalResourceManager.preTypeRenderers.push(generator)
|
|
28
|
+
externalResourceManager.renderers['text/css'] = cssRender
|
|
29
|
+
externalResourceManager.renderers['application/javascript'] = jsRender
|
|
30
|
+
|
|
31
|
+
res.locals.externalResourceManager = externalResourceManager
|
|
32
|
+
|
|
33
|
+
next()
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
compmanager.handlers = {
|
|
37
|
+
generator, cssRender, jsRender
|
|
38
|
+
}
|
|
39
|
+
return compmanager
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export default initializeWebhandleComponent
|
package/package.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@webhandle/external-resource-manager",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "",
|
|
5
|
+
"main": "external-resource-manager.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "node test/all.mjs"
|
|
8
|
+
},
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/EmergentIdeas/webhandle-external-resource-manager.git"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [],
|
|
14
|
+
"author": "",
|
|
15
|
+
"license": "ISC",
|
|
16
|
+
"type": "module",
|
|
17
|
+
"bugs": {
|
|
18
|
+
"url": "https://github.com/EmergentIdeas/webhandle-external-resource-manager/issues"
|
|
19
|
+
},
|
|
20
|
+
"homepage": "https://github.com/EmergentIdeas/webhandle-external-resource-manager#readme",
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"@webhandle/initialize-webhandle-component": "^1.0.1"
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"/*.mjs",
|
|
26
|
+
"README.md",
|
|
27
|
+
"*.mjs"
|
|
28
|
+
]
|
|
29
|
+
}
|
package/resource.mjs
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
|
|
2
|
+
export default class Resource {
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The mime type of this resource e.g. application/javascript, text/css
|
|
6
|
+
*/
|
|
7
|
+
mimeType
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* This might be a sub-type or some sort of indication of how to handle this
|
|
11
|
+
* resource
|
|
12
|
+
*/
|
|
13
|
+
resourceType
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* The name by which this resource is known, if there is one
|
|
17
|
+
*/
|
|
18
|
+
name
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* The URL of the resource
|
|
22
|
+
*/
|
|
23
|
+
url
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Other URLs unnecessary if this resource is included
|
|
27
|
+
*/
|
|
28
|
+
satisfies = []
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Atributes that may be needed for the html element
|
|
32
|
+
*/
|
|
33
|
+
attributes = []
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* True if this resource can be set as indefinitely cachable in line with
|
|
38
|
+
* webhandle's resource version scheme
|
|
39
|
+
*/
|
|
40
|
+
cachable = true
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* True if the resource has been added to the page
|
|
44
|
+
*/
|
|
45
|
+
rendered = false
|
|
46
|
+
|
|
47
|
+
constructor(options) {
|
|
48
|
+
Object.assign(this, options)
|
|
49
|
+
}
|
|
50
|
+
}
|