UNPKG

3.58 kBJavaScriptView Raw
1const Asset = require('../Asset');
2const localRequire = require('../utils/localRequire');
3const {promisify} = require('@parcel/utils');
4const path = require('path');
5const os = require('os');
6const Resolver = require('../Resolver');
7const parseCSSImport = require('../utils/parseCSSImport');
8
9class SASSAsset extends Asset {
10 constructor(name, options) {
11 super(name, options);
12 this.type = 'css';
13 }
14
15 async parse(code) {
16 // node-sass or dart-sass should be installed locally in the module that's being required
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 // Format the error so it can be handled by parcel's prettyError
70 if (err.formatted) {
71 throw sassToCodeFrame(err);
72 }
73 // Throw original error if there is no codeFrame
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
98module.exports = SASSAsset;
99
100async function getSassRuntime(searchPath) {
101 try {
102 return await localRequire('node-sass', searchPath, true);
103 } catch (e) {
104 // If node-sass is not used locally, install dart-sass, as this causes no freezing issues
105 return localRequire('sass', searchPath);
106 }
107}
108
109function 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// Ensures an error inherits from Error
122function 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}