UNPKG

@stencil/postcss

Version:

The Stencil PostCSS Plugin

241 lines (236 loc) 9.28 kB
import postCss from 'postcss'; import * as path from 'path'; function loadDiagnostic(context, postcssError, filePath) { if (!postcssError || !context) { return; } const level = postcssError.level === 'warning' ? 'warn' : postcssError.level || 'error'; const diagnostic = { level, type: 'css', language: 'postcss', header: `postcss ${level}`, code: postcssError.status && postcssError.status.toString(), relFilePath: null, absFilePath: null, messageText: postcssError.reason || postcssError.message || JSON.stringify(postcssError), lines: [], }; if (filePath) { diagnostic.absFilePath = filePath; diagnostic.relFilePath = formatFileName(context.config.rootDir, diagnostic.absFilePath); diagnostic.header = formatHeader('postcss', diagnostic.absFilePath, context.config.rootDir, postcssError.line); if (postcssError.line > -1) { try { const sourceText = context.fs.readFileSync(diagnostic.absFilePath); const srcLines = sourceText.split(/(\r?\n)/); const errorLine = { lineIndex: postcssError.line - 1, lineNumber: postcssError.line, text: srcLines[postcssError.line - 1], errorCharStart: postcssError.column, errorLength: 0, }; for (let i = errorLine.errorCharStart; i >= 0; i--) { if (STOP_CHARS.indexOf(errorLine.text.charAt(i)) > -1) { break; } errorLine.errorCharStart = i; } for (let j = errorLine.errorCharStart; j <= errorLine.text.length; j++) { if (STOP_CHARS.indexOf(errorLine.text.charAt(j)) > -1) { break; } errorLine.errorLength++; } if (errorLine.errorLength === 0 && errorLine.errorCharStart > 0) { errorLine.errorLength = 1; errorLine.errorCharStart--; } diagnostic.lines.push(errorLine); if (errorLine.lineIndex > 0) { const previousLine = { lineIndex: errorLine.lineIndex - 1, lineNumber: errorLine.lineNumber - 1, text: srcLines[errorLine.lineIndex - 1], errorCharStart: -1, errorLength: -1, }; diagnostic.lines.unshift(previousLine); } if (errorLine.lineIndex + 1 < srcLines.length) { const nextLine = { lineIndex: errorLine.lineIndex + 1, lineNumber: errorLine.lineNumber + 1, text: srcLines[errorLine.lineIndex + 1], errorCharStart: -1, errorLength: -1, }; diagnostic.lines.push(nextLine); } } catch (e) { console.error(`StylePostcssPlugin loadDiagnostic, ${e}`); } } } context.diagnostics.push(diagnostic); } function formatFileName(rootDir, fileName) { if (!rootDir || !fileName) return ''; fileName = fileName.replace(rootDir, ''); if (/\/|\\/.test(fileName.charAt(0))) { fileName = fileName.substr(1); } if (fileName.length > 80) { fileName = '...' + fileName.substr(fileName.length - 80); } return fileName; } function formatHeader(type, fileName, rootDir, startLineNumber = null, endLineNumber = null) { let header = `${type}: ${formatFileName(rootDir, fileName)}`; if (startLineNumber !== null && startLineNumber > 0) { if (endLineNumber !== null && endLineNumber > startLineNumber) { header += `, lines: ${startLineNumber} - ${endLineNumber}`; } else { header += `, line: ${startLineNumber}`; } } return header; } const STOP_CHARS = [ '', '\n', '\r', '\t', ' ', ':', ';', ',', '{', '}', '.', '#', '@', '!', '[', ']', '(', ')', '&', '+', '~', '^', '*', '$', ]; function usePlugin(fileName) { return /(\.css|\.pcss|\.postcss)$/i.test(fileName); } function getRenderOptions(opts, sourceText, context) { const renderOpts = { plugins: opts.plugins || [], }; // always set "data" from the source text renderOpts.data = sourceText || ''; const injectGlobalPaths = Array.isArray(opts.injectGlobalPaths) ? opts.injectGlobalPaths.slice() : []; if (injectGlobalPaths.length > 0) { // automatically inject each of these paths into the source text const injectText = injectGlobalPaths .map((injectGlobalPath) => { if (!path.isAbsolute(injectGlobalPath)) { // convert any relative paths to absolute paths relative to the project root injectGlobalPath = path.join(context.config.rootDir, injectGlobalPath); } return `@import "${injectGlobalPath}";`; }) .join(''); renderOpts.data = injectText + renderOpts.data; } return renderOpts; } function createResultsId(fileName) { // create what the new path is post transform (.css) const pathParts = fileName.split('.'); pathParts[pathParts.length - 1] = 'css'; return pathParts.join('.'); } function postcss(opts = {}) { return { name: 'postcss', pluginType: 'css', transform(sourceText, fileName, context) { if (!opts.hasOwnProperty('plugins') || opts.plugins.length === 0) { return null; } if (!context || !usePlugin(fileName)) { return null; } const renderOpts = getRenderOptions(opts, sourceText, context); const results = { id: createResultsId(fileName), }; if (sourceText.trim() === '') { results.code = ''; return Promise.resolve(results); } return new Promise((resolve) => { postCss(renderOpts.plugins) .process(renderOpts.data, { from: fileName, }) .then((postCssResults) => { const warnings = postCssResults.warnings(); if (warnings.length > 0) { // emit diagnostics for each warning warnings.forEach((warn) => { const err = { reason: warn.text, level: warn.type, column: warn.column || -1, line: warn.line || -1, }; loadDiagnostic(context, err, fileName); }); const mappedWarnings = warnings .map((warn) => { return `${warn.type} ${warn.plugin ? `(${warn.plugin})` : ''}: ${warn.text}`; }) .join(', '); results.code = `/** postcss ${mappedWarnings} **/`; resolve(results); } else { results.code = postCssResults.css.toString(); results.dependencies = postCssResults.messages .filter((message) => message.type === 'dependency') .map((dependency) => dependency.file); // TODO(#38) https://github.com/ionic-team/stencil-postcss/issues/38 // determining how to pass back the dir-dependency message helps // enable JIT behavior, such as Tailwind. // // Pseudocode: // results.dependencies = postCssResults.messages // .filter((message) => message.type === 'dir-dependency') // .map((dependency) => () => dependency.file); // write this css content to memory only so it can be referenced // later by other plugins (autoprefixer) // but no need to actually write to disk context.fs.writeFile(results.id, results.code, { inMemoryOnly: true }).then(() => { resolve(results); }); } return results; }) .catch((err) => { loadDiagnostic(context, err, fileName); results.code = `/** postcss error${err && err.message ? ': ' + err.message : ''} **/`; resolve(results); }); }); }, }; } export { postcss };