themes-switch
Version:
Toolset for switch multiple themes in application based on webpack
482 lines (399 loc) • 17.7 kB
JavaScript
;
function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); }
function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
function _iterableToArray(iter) { if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter); }
function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) return _arrayLikeToArray(arr); }
function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; }
var path = require('path');
var fs = require('fs-extra');
var webpack = require('webpack');
var EntryPlugin = require('webpack/lib/SingleEntryPlugin');
var MiniCssExtractPlugin = require('mini-css-extract-plugin');
var HtmlWebpackPlugin = require('html-webpack-plugin');
var _require = require('./utils'),
collectFiles = _require.collectFiles,
randomNum = _require.randomNum,
recursiveIssuer = _require.recursiveIssuer;
var MiniCssExtractPluginOptions;
try {
MiniCssExtractPluginOptions = require('mini-css-extract-plugin/dist/plugin-options.json');
} catch (e) {
console.log('Can\'t find plugin-options.json in dist of mini-css-extract-plugin');
}
var isMiniCssOldVer = typeof MiniCssExtractPluginOptions === 'undefined' || typeof MiniCssExtractPluginOptions.properties.moduleFilename !== 'undefined';
var DEFAULT_INGORED_LIST = ['.DS_Store', '.git'];
var TEMP_DIR = path.resolve(process.cwd(), 'temp');
var TEMP_THEMES_DIR_NAME = 'themes';
var TEMP_THEMES_DIR = path.resolve(TEMP_DIR, TEMP_THEMES_DIR_NAME);
var DEFAULT_STYLE_NAME = 'default';
var pluginInfo = {
name: 'ThemesGeneratorPlugin'
};
var DEFAULT_THEME_OUTPUT_DIR = 'static/theme/';
var DEFAULT_CSS_OUTPUT_NAME = '[name]-[contenthash].css';
var ThemesGeneratorPlugin = /*#__PURE__*/function () {
function ThemesGeneratorPlugin(options) {
_classCallCheck(this, ThemesGeneratorPlugin);
this.options = options;
}
_createClass(ThemesGeneratorPlugin, [{
key: "apply",
value: function apply(compiler) {
var _this = this;
var _this$options = this.options,
_this$options$clearTe = _this$options.clearTemp,
clearTemp = _this$options$clearTe === void 0 ? true : _this$options$clearTe,
disable = _this$options.disable,
srcDir = _this$options.srcDir,
themesDir = _this$options.themesDir,
outputDir = _this$options.outputDir,
_this$options$enableH = _this$options.enableHotReload,
enableHotReload = _this$options$enableH === void 0 ? false : _this$options$enableH;
if (disable) {
return;
}
if (!srcDir) {
console.log('themes-switch will not work, srcDir can not be empty');
return;
}
if (!themesDir) {
console.log('themes-switch will not work, themesDir can not be empty');
return;
}
if (!outputDir) {
console.log('themes-switch will not work, outputDir can not be empty');
return;
}
ThemesGeneratorPlugin.clearTemp();
console.log('Themes generating started...');
var themeList = this.getThemes();
hookThemesOptions({
compiler: compiler,
themeList: themeList,
outputDir: outputDir
});
hookOnBeforeRun({
compiler: compiler,
themeList: themeList
});
var shouldUseProcessAssets = typeof compiler.webpack !== 'undefined' && typeof compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL !== 'undefined';
if (!shouldUseProcessAssets) {
hookOnEmit({
compiler: compiler,
themeList: themeList
});
} else {
compiler.hooks.thisCompilation.tap(pluginInfo, function (compilation) {
if (shouldUseProcessAssets) {
hookOnProcessAssets({
compiler: compiler,
themeList: themeList,
compilation: compilation
});
}
});
}
if (!enableHotReload || !process.env.WEBPACK_DEV_SERVER) {
this.generateThemes(themeList);
} else {
compiler.hooks.watchRun.tap(pluginInfo, function (comp) {
var changedTimes = comp.watchFileSystem.watcher.mtimes;
if (Object.keys(changedTimes).length > 0) {
var filename = Object.keys(changedTimes)[0]; // eslint-disable-line prefer-destructuring
if (filename && filename.startsWith(TEMP_THEMES_DIR)) {
return;
}
}
_this.generateThemes(themeList);
});
}
var onDone = function onDone() {
if (clearTemp) {
if (enableHotReload && process.env.WEBPACK_DEV_SERVER) {
return;
}
ThemesGeneratorPlugin.clearTemp();
}
};
compiler.hooks.done.tap(pluginInfo, onDone);
}
}, {
key: "getThemes",
value: function getThemes() {
var useStaticThemeName = this.options.useStaticThemeName;
var themeFileNames = this.collectOrgThemeFiles();
var themeList = [];
themeFileNames.forEach(function (fileName) {
var index = fileName.lastIndexOf('.');
var key = index > -1 ? fileName.substring(0, index) : fileName;
themeList.push({
key: "theme-".concat(key),
path: path.resolve(TEMP_THEMES_DIR, fileName),
outputName: useStaticThemeName ? "theme-".concat(key, ".css") : "theme-".concat(key, "-").concat(randomNum(10000000, 99999999), ".css"),
orgFile: fileName
});
});
return themeList;
}
}, {
key: "collectOrgThemeFiles",
value: function collectOrgThemeFiles() {
var _this$options2 = this.options,
themesDir = _this$options2.themesDir,
defaultStyleName = _this$options2.defaultStyleName,
_this$options2$ignore = _this$options2.ignoredFilesInThemesDir,
ignoredFilesInThemesDir = _this$options2$ignore === void 0 ? [] : _this$options2$ignore;
var defaultStyle = defaultStyleName || DEFAULT_STYLE_NAME;
var ignoredList = DEFAULT_INGORED_LIST.concat(ignoredFilesInThemesDir);
var orgFiles = fs.readdirSync(themesDir);
if (!orgFiles || orgFiles.length === 0) {
console.warn('No themes found');
return [];
}
return orgFiles.filter(function (file) {
return defaultStyle !== file && ignoredList.indexOf(file) < 0;
});
}
}, {
key: "generateThemes",
value: function generateThemes(themeList) {
var _this$options3 = this.options,
srcDir = _this$options3.srcDir,
themesDir = _this$options3.themesDir,
defaultStyleName = _this$options3.defaultStyleName,
_this$options3$usePur = _this$options3.usePureCSS,
usePureCSS = _this$options3$usePur === void 0 ? false : _this$options3$usePur;
var defaultStyle = defaultStyleName || DEFAULT_STYLE_NAME;
ThemesGeneratorPlugin.clearTemp();
fs.ensureDirSync(TEMP_THEMES_DIR);
var themesDependencies = [];
if (usePureCSS) {
themeList.forEach(function (theme) {
var fileContent = fs.readFileSync(path.join(themesDir, theme.orgFile)).toString();
fs.writeFileSync(path.join(TEMP_THEMES_DIR, theme.orgFile), fileContent);
});
return;
}
var importPattern = new RegExp("@import (.+)".concat(defaultStyle, "(.+)"));
var urlFilePattern = new RegExp(/(.+):(.*)url\((.+)\);/g);
collectFiles(srcDir, themesDependencies, function (file) {
var fileContent = fs.readFileSync(file).toString();
return importPattern.test(fileContent);
});
if (themesDependencies.length < 1) {
console.warn('No theme files are used');
return;
}
var importContent = '';
themesDependencies.forEach(function (d) {
var newFile = path.join(TEMP_THEMES_DIR, d);
fs.ensureFileSync(newFile);
fs.copyFileSync(path.join(process.cwd(), d), newFile);
var fileContent = fs.readFileSync(newFile).toString();
var newContent = fileContent.replace(importPattern, '');
var urlFiles = newContent.match(urlFilePattern);
if (urlFiles && urlFiles.length > 0) {
urlFiles.forEach(function (file) {
var urlInfo = file.substring(file.indexOf('url(') + 4, file.lastIndexOf(')'));
var styleFile = path.resolve(process.cwd(), d);
var urlFile = path.join(styleFile.substring(0, styleFile.lastIndexOf('/')), urlInfo);
fs.copySync(urlFile, urlFile.replace(process.cwd(), TEMP_THEMES_DIR));
});
}
fs.writeFileSync(newFile, newContent);
importContent += "@import '".concat(path.posix.join('./', d), "';\n");
});
themeList.forEach(function (theme) {
var fileContent = fs.readFileSync(path.join(themesDir, theme.orgFile)).toString();
fs.writeFileSync(path.join(TEMP_THEMES_DIR, theme.orgFile), importAfterVariables(theme.orgFile) ? "".concat(fileContent, "\n").concat(importContent) : "".concat(importContent).concat(fileContent));
});
}
}], [{
key: "clearTemp",
value: function clearTemp() {
fs.removeSync(TEMP_DIR);
}
}]);
return ThemesGeneratorPlugin;
}();
function hookThemesOptions(_ref) {
var compiler = _ref.compiler,
themeList = _ref.themeList,
outputDir = _ref.outputDir;
var onEntryOption = function onEntryOption(context) {
var themesUrl = {};
var themesFilename = {};
var publicPath = compiler.options.output && compiler.options.output.publicPath ? compiler.options.output.publicPath : '';
if (themeList && themeList.length > 0) {
if (!compiler.options.optimization) {
compiler.options.optimization = {};
}
if (!compiler.options.optimization.splitChunks) {
compiler.options.optimization.splitChunks = {};
}
if (!compiler.options.optimization.splitChunks.cacheGroups) {
compiler.options.optimization.splitChunks.cacheGroups = {};
}
themeList.forEach(function (theme) {
var entryPlugin = new EntryPlugin(context, theme.path, theme.key);
entryPlugin.apply(compiler);
compiler.options.optimization.splitChunks.cacheGroups[theme.key] = {
test: function test(m, c) {
var entry = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : theme.key;
return m.constructor.name === 'CssModule' && recursiveIssuer(m) === entry;
},
chunks: 'all',
enforce: true
};
var finalOutputDir = outputDir || DEFAULT_THEME_OUTPUT_DIR;
finalOutputDir = finalOutputDir.endsWith('/') ? finalOutputDir : "".concat(finalOutputDir, "/");
themesFilename[theme.key] = "".concat(finalOutputDir).concat(theme.outputName);
if (publicPath === '') {
themesUrl[theme.key] = "".concat(finalOutputDir).concat(theme.outputName);
} else {
themesUrl[theme.key] = "".concat(publicPath.endsWith('/') ? publicPath : "".concat(publicPath, "/")).concat(finalOutputDir).concat(theme.outputName);
}
});
}
var definePlugin = new webpack.DefinePlugin({
process: {
themes: JSON.stringify(themesUrl)
}
});
definePlugin.apply(compiler);
var orgMiniCssExtractPlugin;
var orgFilename;
if (compiler.options.plugins && compiler.options.plugins.length > 0) {
compiler.options.plugins.forEach(function (plugin) {
if (plugin instanceof MiniCssExtractPlugin) {
orgMiniCssExtractPlugin = plugin;
}
});
}
var filenameField = isMiniCssOldVer ? 'moduleFilename' : 'filename';
if (orgMiniCssExtractPlugin) {
orgFilename = orgMiniCssExtractPlugin.options[filenameField];
}
var moduleFilenameFunc = function moduleFilenameFunc(pathData) {
var name = isMiniCssOldVer ? pathData.name : pathData.chunk.name;
if (themesFilename[name]) {
return themesFilename[name];
}
var fileName = DEFAULT_CSS_OUTPUT_NAME;
if (orgMiniCssExtractPlugin && orgFilename) {
if (typeof orgFilename !== 'function') {
fileName = orgFilename;
} else {
fileName = orgFilename(pathData);
}
}
return fileName;
};
if (orgMiniCssExtractPlugin) {
orgMiniCssExtractPlugin.options[filenameField] = moduleFilenameFunc;
} else {
var miniCssExtractPlugin = new MiniCssExtractPlugin(_defineProperty({}, filenameField, moduleFilenameFunc));
miniCssExtractPlugin.apply(compiler);
}
};
compiler.hooks.entryOption.tap(pluginInfo, onEntryOption);
}
function hookOnBeforeRun(_ref2) {
var compiler = _ref2.compiler,
themeList = _ref2.themeList;
var onBeforeRun = function onBeforeRun(c) {
if (!c.options.plugins && c.options.plugins.length < 1) {
return;
}
var excludeThemeChunks = themeList.map(function (theme) {
return theme.key;
});
c.options.plugins.forEach(function (plugin) {
if (plugin instanceof HtmlWebpackPlugin) {
if (plugin.options.excludeChunks) {
plugin.options.excludeChunks = [].concat(_toConsumableArray(excludeThemeChunks), _toConsumableArray(plugin.options.excludeChunks));
} else {
plugin.options.excludeChunks = excludeThemeChunks;
}
}
});
};
compiler.hooks.beforeRun.tap(pluginInfo, onBeforeRun);
}
function hookOnEmit(_ref3) {
var compiler = _ref3.compiler,
themeList = _ref3.themeList;
var onEmit = function onEmit(compilation, callback) {
var stats = compilation.getStats().toJson();
if (themeList && themeList.length > 0) {
themeList.forEach(function (theme) {
var outputByThemes = stats.assetsByChunkName[theme.key];
var pattern = getUnusedFilePattern({
key: theme.key,
compiler: compiler
});
if (outputByThemes) {
if (Array.isArray(outputByThemes)) {
outputByThemes.forEach(function (fileName) {
if (pattern.test(fileName) && compilation.assets[fileName]) {
delete compilation.assets[fileName];
}
});
} else if (typeof outputByThemes === 'string') {
if (pattern.test(outputByThemes) && compilation.assets[outputByThemes]) {
delete compilation.assets[outputByThemes];
}
}
}
});
}
if (callback && typeof callback === 'function') {
callback();
}
};
compiler.hooks.emit.tapAsync(pluginInfo, onEmit);
}
function hookOnProcessAssets(_ref4) {
var compiler = _ref4.compiler,
themeList = _ref4.themeList,
compilation = _ref4.compilation;
var onProcessAssets = function onProcessAssets(assets) {
var keys = Object.keys(assets);
if (themeList && themeList.length > 0) {
themeList.forEach(function (theme) {
var pattern = getUnusedFilePattern({
key: theme.key,
compiler: compiler
});
keys.forEach(function (key) {
if (pattern.test(key)) {
compilation.deleteAsset(key);
}
});
});
}
};
compilation.hooks.processAssets.tap({
name: pluginInfo.name,
stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL
}, onProcessAssets);
}
function getUnusedFilePattern(_ref5) {
var key = _ref5.key,
compiler = _ref5.compiler;
var outputFilename = compiler.options.output && compiler.options.output.filename ? compiler.options.output.filename : '';
var outputDir = outputFilename.substring(0, outputFilename.lastIndexOf('/'));
if (!outputDir) {
return new RegExp("^".concat(key, "(.*).js"));
}
return new RegExp("^".concat(outputDir, "/").concat(key, "(.*).js"));
}
function importAfterVariables(file) {
return file.lastIndexOf('.scss') === file.length - 5 || file.lastIndexOf('.sass') === file.length - 5;
}
module.exports = ThemesGeneratorPlugin;