rollup-plugin-css-module
Version:
Rollup plugin for CSS modules
269 lines (237 loc) • 9.63 kB
JavaScript
import fs from 'fs';
import path from 'path';
import { createFilter } from 'rollup-pluginutils';
import Core from 'css-modules-loader-core';
import postcssRemoveClasses from 'postcss-remove-classes';
import stringHash from 'string-hash';
import postcss from 'postcss';
import colors from 'colors';
var pluginName = 'rollup-plugin-css-module';
var defaultTreeshakeOpts = {
error: false, // prevent build from continuing
warn: true, // display a warning
remove: false // remove from compiled css
};
var jsReservedNames = ['break', 'do', 'instanceof', 'typeof', 'case', 'else', 'new', 'var', 'catch', 'finally', 'return', 'void', 'continue', 'for', 'switch', 'while', 'debugger', 'function', 'this', 'with', 'default', 'if', 'throw', 'delete', 'in', 'try', 'class', 'enum', 'extends', 'super', 'const', 'export', 'import'];
var logErrs = {
namingWarn: 'NamingWarning: export name {{oldName}} has been changed to {{newName}}. Either because it\'s a Javascript reserved word or it has a \'-\' in it. To turn off this warning set suppressNamingWarning: true',
unusedWarn: 'UnusedWarning: export name {{export}} is not used ({{file}})',
unusedErr: 'UnusedError: stopping build due to unused css exports. If you would like to continue past this error in the future set treeshake.error: false'
};
var REPLACE_WITH_CSS = '{{ROLLUP_PLUGIN_CSS_MODULE_CSS}}';
var cssModuleClassName = '{{ROLLUP_PLUGIN_CSS_MODULE_CLASS_NAME}}';
function log(id) {
var ctx = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
var c = Object.keys(ctx).reduce(function (acc, key) {
return acc.replace('{{' + key + '}}', colors.red(ctx[key]));
}, logErrs[id]);
var msg = colors.yellow(colors.white(pluginName) + ' ' + c);
console.log(msg);
return msg;
}
function absPath(relPath) {
return path.join(process.cwd(), relPath);
}
function getContentsOfFile(filePath) {
return new Promise(function (resolve, reject) {
fs.readFile(filePath, function (err, data) {
if (err) return reject(err);
return resolve(data);
});
});
}
function stringify(css) {
var a = JSON.stringify(css);
return a.substr(1, a.length - 2);
}
function postcssForceAfter(css, plugins) {
if (plugins.length === 0) return css;
return postcss(plugins).process(css).css;
}
function makeLegitExportTokens(result, shouldNotWarn) {
return Object.keys(result.exportTokens).reduce(function (acc, key) {
var str = key;
if (jsReservedNames.indexOf(str) > -1) str = 'reserved_' + str;
str = str.replace(/-(.{1})/gi, function (a, b) {
return b.toUpperCase ? b.toUpperCase() : b;
}) // camelCase dashes (-)
.replace('\'', ''); // replace quotes, postcss adds to vars with a -
if (!shouldNotWarn && str !== key) log('namingWarn', { oldName: key, newName: str });
acc.exportTokens[str] = result.exportTokens[key];
return acc;
}, {
injectableSource: result.injectableSource,
exportTokens: {}
});
}
function processCssModule(instance, code, id, shouldNotWarn) {
var imported = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : {};
return instance.load(code, id, null, function (file) {
var rPath = file.split('"').join('');
var aPath = absPath(rPath);
return getContentsOfFile(rPath).then(function (c) {
return processCssModule(instance, c, aPath, shouldNotWarn, imported);
}).then(function (r) {
imported[aPath] = r.local;
return r.local.exportTokens;
});
}).then(function (r) {
return {
local: makeLegitExportTokens(r, shouldNotWarn),
imported: imported
};
});
}
function concatCss(accumulators) {
var g = Object.keys(accumulators.global).reduce(function (acc, key) {
return acc + accumulators.global[key].injectableSource;
}, '');
var i = Object.keys(accumulators.imported).reduce(function (acc, key) {
return acc + accumulators.imported[key].injectableSource;
}, '');
var l = Object.keys(accumulators.local).reduce(function (acc, key) {
return acc + accumulators.local[key].injectableSource;
}, '');
return g + i + l;
}
function getUnusedCss(source, accumulators) {
var all = Object.assign({}, accumulators.global, accumulators.imported, accumulators.local);
var unused = [];
Object.keys(all).forEach(function (key) {
Object.keys(all[key].exportTokens).forEach(function (key1) {
if (!source.includes(all[key].exportTokens[key1])) {
unused.push({
file: key,
export: key1,
scopedName: all[key].exportTokens[key1]
});
}
});
});
return unused;
}
function logUnused(unused, opts) {
if (opts.error === true || opts.warning === true) {
unused.map(function (e) {
return log('unusedWarn', e);
});
if (opts.error === true) throw Error(log('unusedErr'));
}
}
/* istanbul ignore next */
// Credit: https://github.com/substack/insert-css/blob/master/index.js
// Heavily modified
function insert(css, className) {
var styleElement = document.createElement('style');
styleElement.className = className;
styleElement.setAttribute('type', 'text/css');
document.querySelector('head').appendChild(styleElement);
if (styleElement.styleSheet) {
styleElement.styleSheet.cssText = css;
} else {
styleElement.textContent = css;
}
}
function iife(css, className) {
return '\n (' + insert.toString() + ')(\'' + css + '\', \'' + className + '\');\n ';
}
function generateDependableShortName(ignore, name, filename) {
if (ignore.indexOf(name) > -1) return name;
var sanitisedPath = filename.replace(process.cwd(), '').replace(/\.[^./\\]+$/, '').replace(/[\W_]+/g, '_').replace(/^_|_$/g, '');
var hash = stringHash('' + sanitisedPath + name).toString(36).substr(0, 5);
return '_' + hash;
}
function generateShortName(ignore, name, filename, css) {
if (ignore.indexOf(name) > -1) return name;
var i = css.indexOf('.' + name);
var numLines = css.substr(0, i).split(';').length;
var hash = stringHash('' + name + stringHash(css) + numLines).toString(36).substr(0, 5);
return '_' + hash;
}
function generateLongName(ignore, name, filename, css) {
if (ignore.indexOf(name) > -1) return name;
var sanitisedPath = filename.replace(process.cwd(), '').replace(/\.[^./\\]+$/, '').replace(/[\W_]+/g, '_').replace(/^_|_$/g, '');
var i = css.indexOf('.' + name);
return '_' + sanitisedPath + '__' + i + '__' + name;
}
function cssModule() {
var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
// options
var filter = createFilter(options.include, options.exclude);
var extensions = options.extensions || ['.css'];
var insertMethod = options.insertMethod || 'iife'; // one of: iife, css-module
var fileName = options.fileName || null; // only if insertMethod === 'file'
var insertedClassName = options.insertedClassName || pluginName;
var before = options.before || [];
var after = options.after || [];
var afterForced = options.afterForced || [];
var ignore = options.ignore || [];
var treeshake = options.treeshake || defaultTreeshakeOpts;
var suppressNamingWarning = options.suppressNamingWarning || false;
var globals = options.globals ? options.globals.map(absPath) : [];
// private
// css accumulators
var accumulators = {
global: {},
imported: {},
local: {}
};
// setup core
Core.scope.generateScopedName = options.generateScopedName.bind(null, ignore) || generateShortName.bind(null, ignore);
var plugins = before.concat([Core.values, Core.localByDefault, Core.extractImports, Core.scope]).concat(after);
var coreInstance = new Core(plugins);
// rollup plugin exports
function intro() {
if (insertMethod === 'iife') return REPLACE_WITH_CSS;
return null;
}
function transform(code, id) {
if (!filter(id)) return null;
if (extensions.indexOf(path.extname(id)) === -1) return null;
return processCssModule(coreInstance, code, id, suppressNamingWarning).then(function (result) {
accumulators.imported = Object.assign({}, accumulators.imported, result.imported);
if (globals.indexOf(id) > -1) {
accumulators.global[id] = result.local;
} else {
accumulators.local[id] = result.local;
}
return {
code: Object.keys(result.local.exportTokens).reduce(function (acc, key) {
acc += 'export var ' + key + ' = ' + JSON.stringify(result.local.exportTokens[key]) + ';';
return acc;
}, '')
};
});
}
function transformBundle(source) {
if (treeshake.remove || treeshake.error || treeshake.warn) {
var unusedArr = getUnusedCss(source, accumulators);
if (treeshake.error || treeshake.warn) {
logUnused(unusedArr, treeshake);
}
if (treeshake.remove) {
afterForced.push(postcssRemoveClasses(unusedArr));
}
}
var finalCss = postcssForceAfter(concatCss(accumulators), afterForced);
if (insertMethod === 'iife') {
var iife$$1 = iife(stringify(finalCss), insertedClassName);
source = source.replace(REPLACE_WITH_CSS, iife$$1);
}
if (insertMethod === 'file') {
fs.writeFileSync(absPath(fileName), finalCss);
}
if (insertMethod === 'css-module') {
source = source.replace(cssModuleClassName, insertedClassName);
source = source.replace(REPLACE_WITH_CSS, stringify(finalCss));
}
return source;
}
return {
name: pluginName,
intro: intro,
transform: transform,
transformBundle: transformBundle
};
}
export { generateDependableShortName, generateShortName, generateLongName };export default cssModule;