packages/rollup-css-plugin/css-plugin.js (139 lines of code) (raw):

/* eslint-disable import/no-extraneous-dependencies */ const path = require('path'); const fs = require('fs/promises'); const cssNano = require('cssnano'); const loadPostcssConfig = require('postcss-load-config'); const postcss = require('postcss'); const postcssImport = require('postcss-import'); const postcssUrl = require('postcss-url'); const cssModules = require('postcss-modules'); const FileSystemLoader = require('postcss-modules/build/FileSystemLoader').default; const {createFilter} = require('@rollup/pluginutils'); const {DependencyGraph} = require('./css-plugin-dependencies'); function removeDuplicatePaths(paths) { const seenPaths = new Set(); return paths.filter(p => { if (seenPaths.has(p)) { return false; } seenPaths.add(p); return true; }); } const postcssConfigPromise = loadPostcssConfig(); module.exports = function cssPlugin(options = {}) { const filter = createFilter(options.include || '**/*.css', options.exclude); const extractPath = options.extract || 'bundle.css'; const log = options.log || (() => {}); const depGraph = new DependencyGraph(); const sourcesList = []; const compiledCodeMap = new Map(); return { name: 'css', async transform(code, id) { if (!filter(id)) { return null; } let plugins = []; const cssModulesCache = new Map(); sourcesList.push(id); const postcssConfig = await postcssConfigPromise; async function compileIfNeeded(resolvedPath) { if (compiledCodeMap.has(resolvedPath)) { return; } const fileContent = await fs.readFile(resolvedPath, 'utf-8'); const result = await postcss(plugins).process(fileContent, { from: resolvedPath, to: resolvedPath, map: false, }); log('compileIfNeeded.compiled', resolvedPath); compiledCodeMap.set(resolvedPath, result.css); } plugins = [ postcssImport({ resolve: (fileId, basedir) => { const resolvedPath = path.resolve(basedir, fileId); log('postcssImport.resolve', fileId, basedir, resolvedPath); depGraph.addDependency(id, resolvedPath); return resolvedPath; }, load: async filename => { await compileIfNeeded(filename); return `/* "@import" of "${path.basename(filename)}" extracted */\n`; }, }), ...(postcssConfig.plugins || []), postcssUrl({ includeUriFragment: true, url: 'inline', }), cssModules({ generateScopedName(name, filename) { return `ring-${path.basename(filename, '.css')}-${name}`; }, getJSON: (filepath, json) => { cssModulesCache.set(filepath, json); }, // Override Loader to prevent css-modules from inlining content of imported files for @value bar form '../foo.css' syntax // See https://github.com/madyankin/postcss-modules/blob/bd64c71ddfd81b615104b0727ce9a623da0eaef6/src/FileSystemLoader.js#L68 Loader: class CustomLoader extends FileSystemLoader { async fetch(file, relativeTo, _trace) { const res = await super.fetch(file, relativeTo, _trace); const normalizedFile = file.replace(/^["']|["']$/g, ''); const resolvedPath = path.join(path.dirname(relativeTo), normalizedFile); await compileIfNeeded(resolvedPath); depGraph.addDependency(id, resolvedPath); this.sources[resolvedPath] = `/* "@value" import of "${path.basename(resolvedPath)}" extracted */\n`; return res; } }, resolve: async (file, importer) => { const resolvedPath = path.resolve(path.dirname(importer), file); log('cssModules.resolve', file, importer, resolvedPath); await compileIfNeeded(resolvedPath); depGraph.addDependency(id, resolvedPath); return resolvedPath; }, }), ]; const result = await postcss(plugins).process(code, { from: id, to: id, map: false, }); log('source.compiled', id); compiledCodeMap.set(id, result.css); return { code: `export default ${JSON.stringify(cssModulesCache.get(id))};`, map: result.map, moduleSideEffects: 'no-treeshake', }; }, async generateBundle() { const orderedFiles = removeDuplicatePaths([...depGraph.topologicalSort(), ...sourcesList]); const sources = orderedFiles .map(filePath => { if (!compiledCodeMap.has(filePath)) { throw new Error(`No compiled code for ${filePath}`); } return `/* ${path.basename(filePath)} */\n${compiledCodeMap.get(filePath)}`; }) .filter(Boolean); let combinedCSS = sources.join('\n'); if (options.minimize) { log('Minimizing...'); const result = await postcss(cssNano()).process(combinedCSS, { from: extractPath, to: extractPath, map: false, }); log('Done minimizing'); combinedCSS = result.css; } this.emitFile({ type: 'asset', fileName: extractPath, source: combinedCSS, }); }, }; };