/
plugin.js
113 lines (96 loc) · 3.64 KB
/
plugin.js
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
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
/* eslint-disable no-restricted-syntax, no-shadow */
import fs from 'fs';
import path from 'path';
import fetch from 'node-fetch';
import postcss from 'postcss';
import parseCssUrls from 'css-url-parser';
const notUrl = (url) => url.substr(0, 4) !== 'http';
const notBare = (str) => str.startsWith('/') || str.startsWith('./') || str.startsWith('../');
async function readEikJSONMaps(eikJSONPath) {
try {
const contents = await fs.promises.readFile(eikJSONPath);
const eikJSON = JSON.parse(contents);
if (typeof eikJSON['import-map'] === 'string') return [eikJSON['import-map']];
return eikJSON['import-map'] || [];
} catch (err) {
return [];
}
}
async function fetchImportMaps(urls = []) {
try {
const maps = urls.map((map) => fetch(map).then((result) => {
if (result.status === 404) {
throw new Error('Import map could not be found on server');
} else if (result.status >= 400 && result.status < 500) {
throw new Error('Server rejected client request');
} else if (result.status >= 500) {
throw new Error('Server error');
}
return result.json();
}));
const results = await Promise.all(maps);
const dependencies = results.map((result) => result.imports);
return Object.assign({}, ...dependencies);
} catch (err) {
throw new Error(
`Unable to load import map file from server: ${err.message}`
);
}
}
// @TODO this could be a @eik/import-map-utils package
async function getImportMap({
path: eikPath = path.join(process.cwd(), 'eik.json'),
urls = [],
imports = {},
} = {}) {
const mapping = new Map();
const importmapUrls = await readEikJSONMaps(eikPath);
for (const map of importmapUrls) {
urls.push(map);
}
let imprts = {};
if (urls.length > 0) {
imprts = { ...(await fetchImportMaps(urls)) };
}
Object.assign(imprts, imports);
Object.keys(imprts).forEach((key) => {
const value = Array.isArray(imprts[key]) ? imprts[key][0] : imprts[key];
if (notBare(key)) return;
if (notUrl(value)) throw Error('Target for import specifier must be an absolute URL.');
mapping.set(key, value);
});
return mapping;
}
// The resolve option in postcss-import doesn't support async functions or promises, thus we have to workaround it
let mapping = new Map();
export default postcss.plugin(
'@eik/postcss-import-map',
({ path, urls, imports } = {}) => {
// Work with options here
return async (root) => {
mapping = await getImportMap({ path, urls, imports });
root.walkAtRules('import', (decl) => {
let key;
// First check if it's possibly using syntax like url()
const parsedUrls = parseCssUrls(decl.params);
if (parsedUrls.length > 0) {
// eslint-disable-next-line prefer-destructuring
key = parsedUrls[0];
} else {
// Handle the common cases where it's not wrapped in url() but may have quotes
key = decl.params.replace(/["']/g, '');
}
// Webpack interop
key = key.replace(/^~/, '');
if (mapping.has(key)) {
// eslint-disable-next-line no-param-reassign
decl.params = `'${mapping.get(key)}'`;
}
});
};
}
);
// Useful for integrating with other plugins such as postcss-import
export function filter(url) {
return !mapping.has(url);
}