forked from FredKSchott/snowpack
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathplugin.js
126 lines (117 loc) · 4.31 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
114
115
116
117
118
119
120
121
122
123
124
125
126
const fs = require('fs');
const path = require('path');
const execa = require('execa');
const npmRunPath = require('npm-run-path');
const IMPORT_REGEX = /\@(use|import)\s*['"](.*?)['"]/g;
function stripFileExtension(filename) {
return filename.split('.').slice(0, -1).join('.');
}
function scanSassImports(fileContents, filePath, fileExt) {
// TODO: Replace with matchAll once Node v10 is out of TLS.
// const allMatches = [...result.matchAll(new RegExp(HTML_JS_REGEX))];
const allMatches = [];
let match;
const regex = new RegExp(IMPORT_REGEX);
while ((match = regex.exec(fileContents))) {
allMatches.push(match);
}
// return all imports, resolved to true files on disk.
return allMatches
.map((match) => match[2])
.filter((s) => s.trim())
.map((s) => {
return path.resolve(path.dirname(filePath), s);
});
}
module.exports = function sassPlugin(_, {native, compilerOptions = {}} = {}) {
/** A map of partially resolved imports to the files that imported them. */
const importedByMap = new Map();
function addImportsToMap(filePath, sassImport) {
const importedBy = importedByMap.get(sassImport);
if (importedBy) {
importedBy.add(filePath);
} else {
importedByMap.set(sassImport, new Set([filePath]));
}
}
return {
name: '@snowpack/plugin-sass',
resolve: {
input: ['.scss', '.sass'],
output: ['.css'],
},
/**
* If any files imported the given file path, mark them as changed.
* @private
*/
_markImportersAsChanged(filePath) {
if (importedByMap.has(filePath)) {
const importedBy = importedByMap.get(filePath);
importedByMap.delete(filePath);
for (const importerFilePath of importedBy) {
this.markChanged(importerFilePath);
}
}
},
/**
* When a file changes, also mark it's importers as changed.
* Note that Sass has very lax matching of imports -> files.
* Follow these rules to find a match: https://sass-lang.com/documentation/at-rules/use
*/
onChange({filePath}) {
const filePathNoExt = stripFileExtension(filePath);
// check exact: "_index.scss" (/a/b/c/foo/_index.scss)
this._markImportersAsChanged(filePath);
// check no ext: "_index" (/a/b/c/foo/_index)
this._markImportersAsChanged(filePathNoExt);
// check no underscore: "index.scss" (/a/b/c/foo/index.scss)
this._markImportersAsChanged(filePath.replace(/([\\\/])_/, '$1'));
// check no ext, no underscore: "index" (/a/b/c/foo/index)
this._markImportersAsChanged(filePathNoExt.replace(/([\\\/])_/, '$1'));
// check folder import: "foo" (/a/b/c/foo)
if (filePathNoExt.endsWith('_index')) {
const folderPathNoIndex = filePathNoExt.substring(0, filePathNoExt.length - 7);
this._markImportersAsChanged(folderPathNoIndex);
}
},
/** Load the Sass file and compile it to CSS. */
async load({filePath, isDev}) {
const fileExt = path.extname(filePath);
const contents = fs.readFileSync(filePath, 'utf8');
// During development, we need to track changes to Sass dependencies.
if (isDev) {
const sassImports = scanSassImports(contents, filePath, fileExt);
sassImports.forEach((imp) => addImportsToMap(filePath, imp));
}
// If file is `.sass`, use YAML-style. Otherwise, use default.
const args = ['--stdin', '--load-path', path.dirname(filePath)];
if (fileExt === '.sass') {
args.push('--indented');
}
// Pass in user-defined options
Object.entries(compilerOptions).forEach(([flag, value]) => {
let flagName = flag.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`); // convert camelCase to kebab-case
switch (typeof value) {
case 'boolean': {
args.push(`--${value === false ? 'no-' : ''}${flagName}`);
break;
}
case 'string':
case 'number': {
args.push(`--${flagName}=${value}`);
break;
}
}
});
// Build the file.
const {stdout, stderr} = await execa('sass', args, {
input: contents,
env: native ? undefined : npmRunPath.env(),
extendEnv: native ? true : false,
});
// Handle the output.
if (stderr) throw new Error(stderr);
if (stdout) return stdout;
},
};
};