-
Notifications
You must be signed in to change notification settings - Fork 2
/
index.js
106 lines (88 loc) · 2.92 KB
/
index.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
'use strict';
const path = require('path');
const fs = require('fs');
const os = require('os');
const acorn = require('acorn');
const walk = require('acorn/dist/walk');
const esutils = require('esutils');
function extractExports(source) {
const exportedValues = {};
const ast = acorn.parse(source, { sourceType: 'module' });
walk.simple(ast, {
AssignmentExpression(node) {
const isValuesExport =
node.left.type === 'MemberExpression' &&
node.left.object.name === 'exports' &&
node.left.property.name === 'locals';
if (isValuesExport && node.right.type === 'ObjectExpression') {
for (const property of node.right.properties) {
// The key can be an identifier or a string literal
const key =
property.type === 'Identifier'
? property.key.name
: property.key.value;
// Filter out invalid identifiers. Depending on the configuration, css-loader might not remove the original non-camelcased keys.
// https://github.com/webpack-contrib/css-loader#camelcase
if (!esutils.keyword.isIdentifierNameES6(key)) {
continue;
}
// Filter out reserved keywords
if (esutils.keyword.isReservedWordES6(key, true)) {
continue;
}
exportedValues[key] = true;
}
}
}
});
return exportedValues;
}
module.exports = function loader(source, map) {
const callback = this.async();
const { dir, base } = path.parse(this.resourcePath);
const definitionPath = path.join(dir, `${base}.d.ts`);
this.addDependency(definitionPath);
const exportedValues = extractExports(source);
const hasExports = Object.keys(exportedValues).length;
const definitionSource = hasExports
? `${Object.keys(exportedValues)
.map(val => `export const ${val}: string;`)
.join(os.EOL)}${os.EOL}`
: // TS will treat this as a module when imported as * and no exported values exist
`declare let emptyCSSModule: void;${
os.EOL
}export default emptyCSSModule;${os.EOL}`;
fs.stat(definitionPath, (err, stats) => {
if (err && err.code !== 'ENOENT') {
return callback(err);
}
if (stats && stats.isFile()) {
fs.readFile(definitionPath, 'utf-8', (err, content) => {
if (err) {
return callback(err);
}
if (definitionSource !== content) {
return fs.writeFile(
definitionPath,
definitionSource,
'utf-8',
err => {
if (err) {
return callback(err);
}
callback(null, source, map);
}
);
}
callback(null, source, map);
});
} else {
fs.writeFile(definitionPath, definitionSource, 'utf-8', err => {
if (err) {
return callback(err);
}
callback(null, source, map);
});
}
});
};