1 | const Asset = require('../Asset');
|
2 | const localRequire = require('../utils/localRequire');
|
3 | const {promisify} = require('@parcel/utils');
|
4 | const path = require('path');
|
5 | const os = require('os');
|
6 | const Resolver = require('../Resolver');
|
7 | const parseCSSImport = require('../utils/parseCSSImport');
|
8 |
|
9 | class SASSAsset extends Asset {
|
10 | constructor(name, options) {
|
11 | super(name, options);
|
12 | this.type = 'css';
|
13 | }
|
14 |
|
15 | async parse(code) {
|
16 |
|
17 | let sass = await getSassRuntime(this.name);
|
18 | let render = promisify(sass.render.bind(sass));
|
19 | const resolver = new Resolver({
|
20 | extensions: ['.scss', '.sass'],
|
21 | rootDir: this.options.rootDir
|
22 | });
|
23 |
|
24 | let opts =
|
25 | (await this.getConfig(['.sassrc', '.sassrc.js'], {packageKey: 'sass'})) ||
|
26 | {};
|
27 | opts.includePaths = (opts.includePaths
|
28 | ? opts.includePaths.map(includePath => path.resolve(includePath))
|
29 | : []
|
30 | ).concat(path.dirname(this.name));
|
31 | opts.data = opts.data ? opts.data + os.EOL + code : code;
|
32 | let type = this.options.rendition
|
33 | ? this.options.rendition.type
|
34 | : path
|
35 | .extname(this.name)
|
36 | .toLowerCase()
|
37 | .replace('.', '');
|
38 | opts.indentedSyntax =
|
39 | typeof opts.indentedSyntax === 'boolean'
|
40 | ? opts.indentedSyntax
|
41 | : type === 'sass';
|
42 |
|
43 | opts.importer = opts.importer || [];
|
44 | opts.importer = Array.isArray(opts.importer)
|
45 | ? opts.importer
|
46 | : [opts.importer];
|
47 | opts.importer.push((url, prev, done) => {
|
48 | url = url.replace(/^file:\/\//, '');
|
49 | url = parseCSSImport(url);
|
50 | resolver
|
51 | .resolve(url, prev === 'stdin' ? this.name : prev)
|
52 | .then(resolved => resolved.path)
|
53 | .catch(() => url)
|
54 | .then(file => done({file}))
|
55 | .catch(err => done(normalizeError(err)));
|
56 | });
|
57 |
|
58 | if (this.options.sourceMaps) {
|
59 | opts.sourceMap = true;
|
60 | opts.file = this.name;
|
61 | opts.outFile = this.name;
|
62 | opts.omitSourceMapUrl = true;
|
63 | opts.sourceMapContents = true;
|
64 | }
|
65 |
|
66 | try {
|
67 | return await render(opts);
|
68 | } catch (err) {
|
69 |
|
70 | if (err.formatted) {
|
71 | throw sassToCodeFrame(err);
|
72 | }
|
73 |
|
74 | throw err;
|
75 | }
|
76 | }
|
77 |
|
78 | collectDependencies() {
|
79 | for (let dep of this.ast.stats.includedFiles) {
|
80 | this.addDependency(dep, {includedInParent: true});
|
81 | }
|
82 | }
|
83 |
|
84 | generate() {
|
85 | return [
|
86 | {
|
87 | type: 'css',
|
88 | value: this.ast ? this.ast.css.toString() : '',
|
89 | map:
|
90 | this.ast && this.ast.map
|
91 | ? JSON.parse(this.ast.map.toString())
|
92 | : undefined
|
93 | }
|
94 | ];
|
95 | }
|
96 | }
|
97 |
|
98 | module.exports = SASSAsset;
|
99 |
|
100 | async function getSassRuntime(searchPath) {
|
101 | try {
|
102 | return await localRequire('node-sass', searchPath, true);
|
103 | } catch (e) {
|
104 |
|
105 | return localRequire('sass', searchPath);
|
106 | }
|
107 | }
|
108 |
|
109 | function sassToCodeFrame(err) {
|
110 | let error = new Error(err.message);
|
111 | error.codeFrame = err.formatted;
|
112 | error.stack = err.stack;
|
113 | error.fileName = err.file;
|
114 | error.loc = {
|
115 | line: err.line,
|
116 | column: err.column
|
117 | };
|
118 | return error;
|
119 | }
|
120 |
|
121 |
|
122 | function normalizeError(err) {
|
123 | let message = 'Unknown error';
|
124 |
|
125 | if (err) {
|
126 | if (err instanceof Error) {
|
127 | return err;
|
128 | }
|
129 |
|
130 | message = err.stack || err.message || err;
|
131 | }
|
132 |
|
133 | return new Error(message);
|
134 | }
|