UNPKG

9.28 kBJavaScriptView Raw
1import postCss from 'postcss';
2import * as path from 'path';
3
4function loadDiagnostic(context, postcssError, filePath) {
5 if (!postcssError || !context) {
6 return;
7 }
8 const level = postcssError.level === 'warning' ? 'warn' : postcssError.level || 'error';
9 const diagnostic = {
10 level,
11 type: 'css',
12 language: 'postcss',
13 header: `postcss ${level}`,
14 code: postcssError.status && postcssError.status.toString(),
15 relFilePath: null,
16 absFilePath: null,
17 messageText: postcssError.reason || postcssError.message || JSON.stringify(postcssError),
18 lines: [],
19 };
20 if (filePath) {
21 diagnostic.absFilePath = filePath;
22 diagnostic.relFilePath = formatFileName(context.config.rootDir, diagnostic.absFilePath);
23 diagnostic.header = formatHeader('postcss', diagnostic.absFilePath, context.config.rootDir, postcssError.line);
24 if (postcssError.line > -1) {
25 try {
26 const sourceText = context.fs.readFileSync(diagnostic.absFilePath);
27 const srcLines = sourceText.split(/(\r?\n)/);
28 const errorLine = {
29 lineIndex: postcssError.line - 1,
30 lineNumber: postcssError.line,
31 text: srcLines[postcssError.line - 1],
32 errorCharStart: postcssError.column,
33 errorLength: 0,
34 };
35 for (let i = errorLine.errorCharStart; i >= 0; i--) {
36 if (STOP_CHARS.indexOf(errorLine.text.charAt(i)) > -1) {
37 break;
38 }
39 errorLine.errorCharStart = i;
40 }
41 for (let j = errorLine.errorCharStart; j <= errorLine.text.length; j++) {
42 if (STOP_CHARS.indexOf(errorLine.text.charAt(j)) > -1) {
43 break;
44 }
45 errorLine.errorLength++;
46 }
47 if (errorLine.errorLength === 0 && errorLine.errorCharStart > 0) {
48 errorLine.errorLength = 1;
49 errorLine.errorCharStart--;
50 }
51 diagnostic.lines.push(errorLine);
52 if (errorLine.lineIndex > 0) {
53 const previousLine = {
54 lineIndex: errorLine.lineIndex - 1,
55 lineNumber: errorLine.lineNumber - 1,
56 text: srcLines[errorLine.lineIndex - 1],
57 errorCharStart: -1,
58 errorLength: -1,
59 };
60 diagnostic.lines.unshift(previousLine);
61 }
62 if (errorLine.lineIndex + 1 < srcLines.length) {
63 const nextLine = {
64 lineIndex: errorLine.lineIndex + 1,
65 lineNumber: errorLine.lineNumber + 1,
66 text: srcLines[errorLine.lineIndex + 1],
67 errorCharStart: -1,
68 errorLength: -1,
69 };
70 diagnostic.lines.push(nextLine);
71 }
72 }
73 catch (e) {
74 console.error(`StylePostcssPlugin loadDiagnostic, ${e}`);
75 }
76 }
77 }
78 context.diagnostics.push(diagnostic);
79}
80function formatFileName(rootDir, fileName) {
81 if (!rootDir || !fileName)
82 return '';
83 fileName = fileName.replace(rootDir, '');
84 if (/\/|\\/.test(fileName.charAt(0))) {
85 fileName = fileName.substr(1);
86 }
87 if (fileName.length > 80) {
88 fileName = '...' + fileName.substr(fileName.length - 80);
89 }
90 return fileName;
91}
92function formatHeader(type, fileName, rootDir, startLineNumber = null, endLineNumber = null) {
93 let header = `${type}: ${formatFileName(rootDir, fileName)}`;
94 if (startLineNumber !== null && startLineNumber > 0) {
95 if (endLineNumber !== null && endLineNumber > startLineNumber) {
96 header += `, lines: ${startLineNumber} - ${endLineNumber}`;
97 }
98 else {
99 header += `, line: ${startLineNumber}`;
100 }
101 }
102 return header;
103}
104const STOP_CHARS = [
105 '',
106 '\n',
107 '\r',
108 '\t',
109 ' ',
110 ':',
111 ';',
112 ',',
113 '{',
114 '}',
115 '.',
116 '#',
117 '@',
118 '!',
119 '[',
120 ']',
121 '(',
122 ')',
123 '&',
124 '+',
125 '~',
126 '^',
127 '*',
128 '$',
129];
130
131function usePlugin(fileName) {
132 return /(\.css|\.pcss|\.postcss)$/i.test(fileName);
133}
134function getRenderOptions(opts, sourceText, context) {
135 const renderOpts = {
136 plugins: opts.plugins || [],
137 };
138 // always set "data" from the source text
139 renderOpts.data = sourceText || '';
140 const injectGlobalPaths = Array.isArray(opts.injectGlobalPaths) ? opts.injectGlobalPaths.slice() : [];
141 if (injectGlobalPaths.length > 0) {
142 // automatically inject each of these paths into the source text
143 const injectText = injectGlobalPaths
144 .map((injectGlobalPath) => {
145 if (!path.isAbsolute(injectGlobalPath)) {
146 // convert any relative paths to absolute paths relative to the project root
147 injectGlobalPath = path.join(context.config.rootDir, injectGlobalPath);
148 }
149 return `@import "${injectGlobalPath}";`;
150 })
151 .join('');
152 renderOpts.data = injectText + renderOpts.data;
153 }
154 return renderOpts;
155}
156function createResultsId(fileName) {
157 // create what the new path is post transform (.css)
158 const pathParts = fileName.split('.');
159 pathParts[pathParts.length - 1] = 'css';
160 return pathParts.join('.');
161}
162
163function postcss(opts = {}) {
164 return {
165 name: 'postcss',
166 pluginType: 'css',
167 transform(sourceText, fileName, context) {
168 if (!opts.hasOwnProperty('plugins') || opts.plugins.length === 0) {
169 return null;
170 }
171 if (!context || !usePlugin(fileName)) {
172 return null;
173 }
174 const renderOpts = getRenderOptions(opts, sourceText, context);
175 const results = {
176 id: createResultsId(fileName),
177 };
178 if (sourceText.trim() === '') {
179 results.code = '';
180 return Promise.resolve(results);
181 }
182 return new Promise((resolve) => {
183 postCss(renderOpts.plugins)
184 .process(renderOpts.data, {
185 from: fileName,
186 })
187 .then((postCssResults) => {
188 const warnings = postCssResults.warnings();
189 if (warnings.length > 0) {
190 // emit diagnostics for each warning
191 warnings.forEach((warn) => {
192 const err = {
193 reason: warn.text,
194 level: warn.type,
195 column: warn.column || -1,
196 line: warn.line || -1,
197 };
198 loadDiagnostic(context, err, fileName);
199 });
200 const mappedWarnings = warnings
201 .map((warn) => {
202 return `${warn.type} ${warn.plugin ? `(${warn.plugin})` : ''}: ${warn.text}`;
203 })
204 .join(', ');
205 results.code = `/** postcss ${mappedWarnings} **/`;
206 resolve(results);
207 }
208 else {
209 results.code = postCssResults.css.toString();
210 results.dependencies = postCssResults.messages
211 .filter((message) => message.type === 'dependency')
212 .map((dependency) => dependency.file);
213 // TODO(#38) https://github.com/ionic-team/stencil-postcss/issues/38
214 // determining how to pass back the dir-dependency message helps
215 // enable JIT behavior, such as Tailwind.
216 //
217 // Pseudocode:
218 // results.dependencies = postCssResults.messages
219 // .filter((message) => message.type === 'dir-dependency')
220 // .map((dependency) => () => dependency.file);
221 // write this css content to memory only so it can be referenced
222 // later by other plugins (autoprefixer)
223 // but no need to actually write to disk
224 context.fs.writeFile(results.id, results.code, { inMemoryOnly: true }).then(() => {
225 resolve(results);
226 });
227 }
228 return results;
229 })
230 .catch((err) => {
231 loadDiagnostic(context, err, fileName);
232 results.code = `/** postcss error${err && err.message ? ': ' + err.message : ''} **/`;
233 resolve(results);
234 });
235 });
236 },
237 };
238}
239
240export { postcss };