-
Notifications
You must be signed in to change notification settings - Fork 9
/
Copy pathimport-assert.ts
118 lines (97 loc) · 4.01 KB
/
import-assert.ts
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
114
115
116
117
118
import path from 'path';
import { Plugin } from 'rollup';
import convert from 'string-to-template-literal';
function getObjects(obj: any, key: string, val: string): any[] {
let objects = [];
for (let prop in obj) {
if (!obj.hasOwnProperty(prop)) {
continue;
}
if (typeof obj[prop] == 'object') {
objects = objects.concat(getObjects(obj[prop], key, val));
} else
if (prop == key && obj[prop] == val || prop == key && val == '') { //
objects.push(obj);
} else if (obj[prop] == val && key == ''){
if (objects.lastIndexOf(obj) == -1){
objects.push(obj);
}
}
}
return objects;
}
type Assertion = { type: 'css'|'json' };
const assertionMap = new Map<string, Assertion>();
const filePattern = /\.(js|ts|jsx|tsx)$/;
const getImportPath = (id: string, source: string): string => path.resolve(path.dirname(id), source);
export function importAssertionsPlugin(): Plugin {
return {
name: 'rollup-plugin-import-assert',
transform(data: string, id: string) {
let code = data;
/** If the file is a JS-like file, continue */
if (filePattern.exec(id)) {
/** Get the AST data, must be using acorn-import-assertions for this to work */
const ast = this.parse(data);
/** @ts-ignore this does exist apparently */
const { body } = ast;
const importDeclarations = getObjects(body, 'type', 'ImportDeclaration');
const importExpressions = getObjects(body, 'type', 'ImportExpression');
importDeclarations.forEach(node => {
if (node.assertions) {
const [ assertion ] = node.assertions as any;
const assert: Assertion = { type: assertion.value.value };
const importPath = getImportPath(id, node.source.value);
assertionMap.set(importPath, assert);
}
});
importExpressions.forEach(node => {
// Skip dynamic imports with expressions
// @example: import(`./foo/${bar}.js`); // NOK
// @example: import(`./foo/bar.js`); // OK
if(node.source.type === "TemplateLiteral" && node.source.quasis.length > 1) {
return;
}
// @example: `import(foo);` NOK
if(!node.source.value) return;
const source = node.source.value || node.source.quasis[0].value.raw;
const importPath = getImportPath(id, source);
// TODO: We can still make this better
if (node.hasOwnProperty('arguments') && getObjects(node, 'name', 'assert')) {
const assert: Assertion = { type: node.arguments[0].properties[0]?.value?.properties[0].value.value };
assertionMap.set(importPath, assert);
const matches = code.match(/import\(.*\)/gi);
const replacements = matches.map(match => match.replace(/\{(\s?)assert:(\s?)\{.*\}/gi, ''));
if (matches) {
matches.forEach((match, index) =>
code = code.replace(match, replacements[index])
);
code.match(/import\(.*(\s?),(\s?)\)/gi).forEach(match => {
code = code.replace(match,
match.replace(',', '')
);
});
}
}
});
}
const assertion = assertionMap.get(id);
/** If an import assertion exists for the file, parse it differently */
if (assertion) {
const { type } = assertion;
let code = data;
if (type === 'css') {
/** Parse files asserted as CSS to use constructible stylesheets */
code = `const sheet = new CSSStyleSheet();sheet.replaceSync(${convert(data)});export default sheet;`;
} else if (type === 'json') {
/** Parse files asserted as JSON as a JS object */
code = `export default ${data}`;
}
/** Return the new data */
return { code, map: {mappings: ""} };
}
/** If none of the above exists, just continue as normal */
return { code, map: null };
}
}
}