@eik/webpack-plugin 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/LICENSE +21 -0
- package/README.md +200 -0
- package/package.json +51 -0
- package/src/builders.cjs +44 -0
- package/src/loader.cjs +58 -0
- package/src/parsers.cjs +8 -0
- package/src/utils.cjs +50 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2022 FINN.no
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
# @eik/webpack-plugin
|
|
2
|
+
|
|
3
|
+
Plugin to rewrite bare imports to URLs as defined in import maps
|
|
4
|
+
|
|
5
|
+
WebPack [Eik](https://eik.dev/) plugin to support the use of import maps to map "bare" import specifiers in ES modules.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
$ npm install @eik/webpack-plugin
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
```js
|
|
16
|
+
export default {
|
|
17
|
+
experiments: {
|
|
18
|
+
outputModule: true,
|
|
19
|
+
},
|
|
20
|
+
entry: '/src/input.js',
|
|
21
|
+
mode: 'production',
|
|
22
|
+
output: {
|
|
23
|
+
environment: {
|
|
24
|
+
module: true,
|
|
25
|
+
},
|
|
26
|
+
filename: 'bundle.js',
|
|
27
|
+
path: '/out/',
|
|
28
|
+
},
|
|
29
|
+
module: {
|
|
30
|
+
rules: [
|
|
31
|
+
{
|
|
32
|
+
test: /\.js$/,
|
|
33
|
+
use: {
|
|
34
|
+
loader: '@eik/webpack-plugin',
|
|
35
|
+
options: {
|
|
36
|
+
path: '/path/to/eik-json-folder'
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Description
|
|
46
|
+
|
|
47
|
+
This plugin transforms "bare" import specifiers to absolute URL specifiers in
|
|
48
|
+
ES modules. The module refered to by the "bare" import specifier will be
|
|
49
|
+
treated as external and its source will not be included in the bundle but
|
|
50
|
+
refered to by URL.
|
|
51
|
+
|
|
52
|
+
The plugin will attempt to read import map URLs from `eik.json` if present.
|
|
53
|
+
|
|
54
|
+
The path to the location of an `eik.json` file can be specified with the `path` option.
|
|
55
|
+
|
|
56
|
+
```js
|
|
57
|
+
export default {
|
|
58
|
+
//...
|
|
59
|
+
module: {
|
|
60
|
+
rules: [
|
|
61
|
+
{
|
|
62
|
+
test: /\.js$/,
|
|
63
|
+
use: {
|
|
64
|
+
loader: '@eik/webpack-plugin',
|
|
65
|
+
options: {
|
|
66
|
+
path: '/path/to/eik-json-folder'
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
],
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
The plugin can also be told which URLs to load import maps from directly using the `urls` option.
|
|
76
|
+
|
|
77
|
+
```js
|
|
78
|
+
export default {
|
|
79
|
+
//...
|
|
80
|
+
module: {
|
|
81
|
+
rules: [
|
|
82
|
+
{
|
|
83
|
+
test: /\.js$/,
|
|
84
|
+
use: {
|
|
85
|
+
loader: '@eik/webpack-plugin',
|
|
86
|
+
options: {
|
|
87
|
+
urls: ['http://myserver/import-map']
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
],
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Additionally, individual mappings can be specified using the `maps` option.
|
|
97
|
+
|
|
98
|
+
```js
|
|
99
|
+
export default {
|
|
100
|
+
//...
|
|
101
|
+
module: {
|
|
102
|
+
rules: [
|
|
103
|
+
{
|
|
104
|
+
test: /\.js$/,
|
|
105
|
+
use: {
|
|
106
|
+
loader: '@eik/webpack-plugin',
|
|
107
|
+
options: {
|
|
108
|
+
maps: [{
|
|
109
|
+
imports: {
|
|
110
|
+
"lit-element": "https://cdn.eik.dev/lit-element/v2",
|
|
111
|
+
}
|
|
112
|
+
}],
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
],
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
If several of these options are used, `maps` takes precedence over `urls` which takes precedence over values loaded from an `eik.json` file.
|
|
122
|
+
|
|
123
|
+
ie. in the following example
|
|
124
|
+
|
|
125
|
+
```js
|
|
126
|
+
export default {
|
|
127
|
+
//...
|
|
128
|
+
module: {
|
|
129
|
+
rules: [
|
|
130
|
+
{
|
|
131
|
+
test: /\.js$/,
|
|
132
|
+
use: {
|
|
133
|
+
loader: '@eik/webpack-plugin',
|
|
134
|
+
options: {
|
|
135
|
+
path: '/path/to/eik-json-folder',
|
|
136
|
+
urls: ['http://myserver/import-map'],
|
|
137
|
+
maps: [{
|
|
138
|
+
imports: {
|
|
139
|
+
"lit-element": "https://cdn.eik.dev/lit-element/v2",
|
|
140
|
+
}
|
|
141
|
+
}],
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
],
|
|
146
|
+
},
|
|
147
|
+
};
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Any import map URLs in `eik.json` will be loaded first, then merged with (and overridden if necessary by) the result of fetching from `http://myserver/import-map` before finally being merged with (and overriden if necessary by) specific mappings defined in `maps`. (In this case `lit-element`)
|
|
151
|
+
|
|
152
|
+
### Plugin result
|
|
153
|
+
|
|
154
|
+
Bundles will have bare imports mapped to absolute URLs.
|
|
155
|
+
|
|
156
|
+
Ie. Something like this...
|
|
157
|
+
|
|
158
|
+
```js
|
|
159
|
+
import { LitElement, html, css } from "lit-element";
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Will be mapped to something like this...
|
|
163
|
+
|
|
164
|
+
```js
|
|
165
|
+
import { LitElement, html, css } from "https://cdn.eik.dev/lit-element/v2";
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## Options
|
|
169
|
+
|
|
170
|
+
This plugin takes the following options:
|
|
171
|
+
|
|
172
|
+
| option | default | type | required | details |
|
|
173
|
+
| ------- | -------------- | -------- | -------- | ----------------------------------------------------------- |
|
|
174
|
+
| path | `cwd/eik.json` | `string` | `false` | Path to eik.json file. |
|
|
175
|
+
| urls | `[]` | `array` | `false` | Array of import map URLs to fetch from. |
|
|
176
|
+
| maps | `[]` | `array` | `false` | Array of import map as objects. |
|
|
177
|
+
|
|
178
|
+
## Note on ESM with WebPack
|
|
179
|
+
|
|
180
|
+
This plugin will only apply import maps to ESM modules. Due to this its more or less given that the source of your build must be ESM and that your build output is ESM. WebPack does __not__ by default output ESM so this needs to be configured.
|
|
181
|
+
|
|
182
|
+
You enable ESM output in WebPack as follows ([reference](https://webpack.js.org/configuration/output/#outputmodule)):
|
|
183
|
+
|
|
184
|
+
```js
|
|
185
|
+
export default {
|
|
186
|
+
//...
|
|
187
|
+
experiments: {
|
|
188
|
+
outputModule: true,
|
|
189
|
+
},
|
|
190
|
+
output: {
|
|
191
|
+
environment: {
|
|
192
|
+
module: true,
|
|
193
|
+
},
|
|
194
|
+
},
|
|
195
|
+
};
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
## License
|
|
199
|
+
|
|
200
|
+
See license file.
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@eik/webpack-plugin",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "WebPack plugin for loading import maps from an Eik server and applying the mapping to ECMAScript modules in preparation for upload to the same server.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./src/loader.cjs",
|
|
7
|
+
"files": [
|
|
8
|
+
"src"
|
|
9
|
+
],
|
|
10
|
+
"scripts": {
|
|
11
|
+
"test": "tap --no-coverage",
|
|
12
|
+
"test:snapshot": "TAP_SNAPSHOT=1 tap --no-coverage",
|
|
13
|
+
"lint": "eslint . --ext=js,cjs",
|
|
14
|
+
"lint:fix": "eslint . --fix --ext=js,cjs"
|
|
15
|
+
},
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "git@github.com:eik-lib/webpack-plugin.git"
|
|
19
|
+
},
|
|
20
|
+
"keywords": [
|
|
21
|
+
"webpack-plugin",
|
|
22
|
+
"webpack.js",
|
|
23
|
+
"webpack",
|
|
24
|
+
"import",
|
|
25
|
+
"url",
|
|
26
|
+
"esm"
|
|
27
|
+
],
|
|
28
|
+
"author": "Finn.no",
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"bugs": {
|
|
31
|
+
"url": "https://github.com/eik-lib/webpack-plugin/issues"
|
|
32
|
+
},
|
|
33
|
+
"homepage": "https://github.com/eik-lib/webpack-plugin#readme",
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@semantic-release/changelog": "6.0.1",
|
|
36
|
+
"@semantic-release/git": "10.0.1",
|
|
37
|
+
"webpack": "5.67.0",
|
|
38
|
+
"webpack-cli": "4.9.2",
|
|
39
|
+
"eslint": "8.7.0",
|
|
40
|
+
"eslint-config-airbnb-base": "15.0.0",
|
|
41
|
+
"eslint-plugin-import": "2.25.4",
|
|
42
|
+
"fastify": "3.27.0",
|
|
43
|
+
"semantic-release": "19.0.2",
|
|
44
|
+
"tap": "15.1.6",
|
|
45
|
+
"memfs": "3.4.1"
|
|
46
|
+
},
|
|
47
|
+
"dependencies": {
|
|
48
|
+
"@eik/common": "3.0.0",
|
|
49
|
+
"undici": "4.12.2"
|
|
50
|
+
}
|
|
51
|
+
}
|
package/src/builders.cjs
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
const parsers = require('./parsers.cjs');
|
|
2
|
+
|
|
3
|
+
const RX_SIDE_EFFECTS_IMPORT = parsers.sideEffectsImport();
|
|
4
|
+
const RX_STANDARD_IMPORT = parsers.standardImport();
|
|
5
|
+
const RX_DYNAMIC_IMPORT = parsers.dynamicImport();
|
|
6
|
+
|
|
7
|
+
const standardImport = ({ dictionary = new Map(), source = '' }) => {
|
|
8
|
+
const result = source.replace(RX_STANDARD_IMPORT, (replacer, g1, g2, g3, g4) => {
|
|
9
|
+
const dep = dictionary.get(g4) || g4;
|
|
10
|
+
return `${g1} ${g2} ${g3} '${dep}'`;
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
return {
|
|
14
|
+
source: result,
|
|
15
|
+
dictionary,
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
module.exports.standardImport = standardImport;
|
|
19
|
+
|
|
20
|
+
const dynamicImport = ({ dictionary = new Map(), source = '' }) => {
|
|
21
|
+
const result = source.replace(RX_DYNAMIC_IMPORT, (replacer, g1, g2) => {
|
|
22
|
+
const dep = dictionary.get(g2) || g2;
|
|
23
|
+
return `${g1}('${dep}')`;
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
source: result,
|
|
28
|
+
dictionary,
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
module.exports.dynamicImport = dynamicImport;
|
|
32
|
+
|
|
33
|
+
const sideEffectsImport = ({ dictionary = new Map(), source = '' }) => {
|
|
34
|
+
const result = source.replace(RX_SIDE_EFFECTS_IMPORT, (replacer, g1, g2) => {
|
|
35
|
+
const dep = dictionary.get(g2) || g2;
|
|
36
|
+
return `${g1} '${dep}'`;
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
source: result,
|
|
41
|
+
dictionary,
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
module.exports.sideEffectsImport = sideEffectsImport;
|
package/src/loader.cjs
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
const { helpers } = require('@eik/common');
|
|
2
|
+
const utils = require('./utils.cjs');
|
|
3
|
+
const { standardImport, dynamicImport, sideEffectsImport } = require('./builders.cjs');
|
|
4
|
+
|
|
5
|
+
const dictionary = new Map();
|
|
6
|
+
let cold = true;
|
|
7
|
+
|
|
8
|
+
async function loader(source) {
|
|
9
|
+
const options = this.getOptions();
|
|
10
|
+
const callback = this.async();
|
|
11
|
+
|
|
12
|
+
if (cold) {
|
|
13
|
+
const pPath = options.path ? options.path : process.cwd();
|
|
14
|
+
const pMaps = Array.isArray(options.maps) ? options.maps : [options.maps];
|
|
15
|
+
const pUrls = Array.isArray(options.urls) ? options.urls : [options.urls];
|
|
16
|
+
|
|
17
|
+
// Filter out any empty (undefined, null) values in the option arrays
|
|
18
|
+
const urls = pUrls.filter((item) => {
|
|
19
|
+
if (item) return true;
|
|
20
|
+
return false;
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
const maps = pMaps.filter((item) => {
|
|
24
|
+
if (item) return true;
|
|
25
|
+
return false;
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
// Load eik config from eik.json or package.json
|
|
29
|
+
const eikConfig = await helpers.getDefaults(pPath);
|
|
30
|
+
|
|
31
|
+
// Merge map from eik config and the plugin options and Fetch all import maps over http
|
|
32
|
+
const fetchedMaps = await utils.fetchImportMaps([...eikConfig.map, ...urls]);
|
|
33
|
+
|
|
34
|
+
// Validate each import map and push each import statement into a dictionary
|
|
35
|
+
maps.concat(fetchedMaps).map((item) => utils.validate(item)).forEach((item) => {
|
|
36
|
+
item.forEach((obj) => {
|
|
37
|
+
dictionary.set(obj.key, obj.value);
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// Loading of config and import maps should only happen once
|
|
42
|
+
cold = false;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
new Promise((resolve) => {
|
|
46
|
+
resolve({
|
|
47
|
+
dictionary,
|
|
48
|
+
source,
|
|
49
|
+
});
|
|
50
|
+
})
|
|
51
|
+
.then(standardImport)
|
|
52
|
+
.then(dynamicImport)
|
|
53
|
+
.then(sideEffectsImport)
|
|
54
|
+
.then((obj) => {
|
|
55
|
+
callback(null, obj.source);
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
module.exports = loader;
|
package/src/parsers.cjs
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
const standardImport = (flags = 'sgm') => new RegExp('(import)\\s*(.+?)\\s*(from)\\s*[\'"](.+?)[\'"]', flags);
|
|
2
|
+
module.exports.standardImport = standardImport;
|
|
3
|
+
|
|
4
|
+
const dynamicImport = (flags = 'gm') => new RegExp('(import)[(][\'"](.+?)[\'"][)]', flags);
|
|
5
|
+
module.exports.dynamicImport = dynamicImport;
|
|
6
|
+
|
|
7
|
+
const sideEffectsImport = (flags = 'gm') => new RegExp('(import)\\s*[\'"](.+?)[\'"]', flags);
|
|
8
|
+
module.exports.sideEffectsImport = sideEffectsImport;
|
package/src/utils.cjs
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
const { request } = require('undici');
|
|
2
|
+
|
|
3
|
+
const isBare = (str) => {
|
|
4
|
+
if (str.startsWith('/') || str.startsWith('./') || str.startsWith('../') || str.substr(0, 7) === 'http://' || str.substr(0, 8) === 'https://') {
|
|
5
|
+
return false;
|
|
6
|
+
}
|
|
7
|
+
return true;
|
|
8
|
+
};
|
|
9
|
+
module.exports.isBare = isBare;
|
|
10
|
+
|
|
11
|
+
const isString = (str) => typeof str === 'string';
|
|
12
|
+
module.exports.isString = isString;
|
|
13
|
+
|
|
14
|
+
const validate = (map) => Object.keys(map.imports).map((key) => {
|
|
15
|
+
const value = map.imports[key];
|
|
16
|
+
|
|
17
|
+
if (isBare(value)) {
|
|
18
|
+
throw Error(`Import specifier can NOT be mapped to a bare import statement. Import specifier "${key}" is being wrongly mapped to "${value}"`);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return { key, value };
|
|
22
|
+
});
|
|
23
|
+
module.exports.validate = validate;
|
|
24
|
+
|
|
25
|
+
const fetchImportMaps = async (urls = []) => {
|
|
26
|
+
try {
|
|
27
|
+
const maps = urls.map(async (map) => {
|
|
28
|
+
const {
|
|
29
|
+
statusCode,
|
|
30
|
+
body,
|
|
31
|
+
} = await request(map, { maxRedirections: 2 });
|
|
32
|
+
|
|
33
|
+
if (statusCode === 404) {
|
|
34
|
+
throw new Error('Import map could not be found on server');
|
|
35
|
+
} else if (statusCode >= 400 && statusCode < 500) {
|
|
36
|
+
throw new Error('Server rejected client request');
|
|
37
|
+
} else if (statusCode >= 500) {
|
|
38
|
+
throw new Error('Server error');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return body.json();
|
|
42
|
+
});
|
|
43
|
+
return await Promise.all(maps);
|
|
44
|
+
} catch (err) {
|
|
45
|
+
throw new Error(
|
|
46
|
+
`Unable to load import map file from server: ${err.message}`,
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
module.exports.fetchImportMaps = fetchImportMaps;
|