Skip to content

Commit 022a52b

Browse files
committed
Implemented multi entry possibility for themes.
1 parent d27e4f2 commit 022a52b

25 files changed

+299
-100
lines changed

index.js

Lines changed: 45 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,36 @@ class MiniCssThemesWebpackPlugin {
1818
throw new Error(`Default theme '${defaultTheme}' missing from themes definition.`);
1919
}
2020

21-
Object.entries(themes).forEach(([themeKey, file]) => {
22-
const fullPath = path.resolve(file);
21+
const areThemesSingleEntry = typeof themes[defaultTheme] === 'string';
22+
const themeEntryKeys = areThemesSingleEntry ? [] : Object.keys(themes[defaultTheme]);
23+
24+
const validateThemeFile = (themePath, themeKey, entry = null) => {
25+
const fullPath = path.resolve(themePath);
2326
if (!fs.existsSync(fullPath)) {
24-
throw new Error(`Theme '${themeKey}' file not found: ${fullPath}`);
27+
throw new Error(`Theme '${entry ? `${themeKey}.${entry}` : themeKey}' file not found: ${fullPath}`);
28+
}
29+
};
30+
31+
Object.entries(themes).forEach(([themeKey, fileOrFilesObject]) => {
32+
if (areThemesSingleEntry) {
33+
if (typeof fileOrFilesObject !== 'string') {
34+
throw new Error(`All themes value must be a string (path to theme file) as the default theme is also string.`);
35+
}
36+
return validateThemeFile(fileOrFilesObject, themeKey);
37+
}
38+
39+
if (typeof fileOrFilesObject !== 'object') {
40+
throw new Error(`Themes value must be either object or string.`);
41+
}
42+
43+
const currentThemeEntryKeys = Object.keys(fileOrFilesObject);
44+
if (currentThemeEntryKeys.length !== themeEntryKeys.length || themeEntryKeys.filter((k) => !currentThemeEntryKeys.includes(k)).length) {
45+
throw new Error(`Missing or additional theme entries in '${themeKey}'. All themes must match schema of default theme.`);
2546
}
47+
48+
Object.entries(fileOrFilesObject).forEach(([entry, path]) => {
49+
validateThemeFile(path, themeKey, entry);
50+
});
2651
});
2752
}
2853

@@ -32,12 +57,23 @@ class MiniCssThemesWebpackPlugin {
3257
this.defaultTheme = defaultTheme;
3358
this.chunkPrefix = chunkPrefix || 'theme__';
3459

35-
this.defaultImportFilename = path.basename(this.themes[this.defaultTheme]).replace(/\.[a-zA-Z0-9]+$/, '');
3660
this.nonDefaultThemeKeys = Object.keys(this.themes).filter(t => t !== this.defaultTheme);
3761
this.themeChunkNames = this.nonDefaultThemeKeys.map(theme => `${this.chunkPrefix}${theme}`);
3862

39-
this.absolutePathThemes = {};
40-
Object.entries(themes).forEach(([key, themePath]) => this.absolutePathThemes[key] = path.resolve(themePath));
63+
const areThemesSingleEntry = typeof themes[defaultTheme] === 'string';
64+
this.absoluteThemePaths = {};
65+
66+
Object.entries(themes).forEach(([key, fileOrFilesObject]) => {
67+
if (areThemesSingleEntry) {
68+
this.absoluteThemePaths[key] = [path.resolve(fileOrFilesObject)];
69+
} else {
70+
this.absoluteThemePaths[key] = Object.entries(fileOrFilesObject)
71+
.sort((a, b) => a.key > b.key ? -1 : 1)
72+
.reduce((reduced, [,current]) => reduced.push(current) && reduced, []);
73+
}
74+
});
75+
76+
this.defaultImportFilenames = this.absoluteThemePaths[this.defaultTheme].map((themePath) => path.basename(themePath).replace(/\.[a-zA-Z0-9]+$/, ''));
4177
}
4278

4379
apply(compiler) {
@@ -92,9 +128,9 @@ class MiniCssThemesWebpackPlugin {
92128
module.loaders.push({
93129
loader: require.resolve('./loader.js'),
94130
options: {
95-
defaultImportFilename: this.defaultImportFilename,
96-
defaultImportPath: this.absolutePathThemes[this.defaultTheme],
97-
targetImportPath: this.absolutePathThemes[theme[1]],
131+
defaultImportFilenames: this.defaultImportFilenames,
132+
defaultImportPaths: this.absoluteThemePaths[this.defaultTheme],
133+
targetImportPaths: this.absoluteThemePaths[theme[1]],
98134
}
99135
});
100136
}

loader.js

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,28 +5,41 @@ const filterMatchedImports = (importStrings, context, currentImportPath) => {
55
currentImportPath = currentImportPath.replace(/(\.scss|\.sass)$/, '');
66

77
return importStrings.filter(importString => {
8-
const importPath = importString.match(/['"]([^'"]+)(\.scss|\.sass)?['"]/)[1];
9-
return currentImportPath === path.join(context, importPath);
8+
const importPath = importString.match(/['"]([^'"]+)?['"]/)[1].replace(/(\.scss|\.sass)$/, '');
9+
return path.resolve(currentImportPath) === path.join(context, importPath);
1010
});
1111
};
1212

1313
exports.default = function (content) {
1414
const options = loaderUtils.getOptions(this);
1515
const callback = this.async();
16+
this.cacheable(true);
1617

17-
const regexFilename = options.defaultImportFilename.replace(/\./, '\\.');
18-
const importPatterns = `@import\\s+['"]([^'"]+${regexFilename}(\\.scss|\\.sass)?)['"];?`;
19-
const matchedImports = content.match(new RegExp(importPatterns, 'g'));
18+
const replaceForImport = (defaultImportFileName, index) => {
19+
const regexFilename = defaultImportFileName.replace(/\./, '\\.');
20+
const pathMatch = `['"]([^'"]+${regexFilename}(\\.scss|\\.sass)?)['"];?`;
2021

21-
if (!matchedImports) {
22-
callback(null, content);
23-
return;
24-
}
22+
const importPatterns = `@import\\s+${pathMatch}`;
23+
const matchedImports = content.match(new RegExp(importPatterns, 'g'));
24+
if (matchedImports) {
25+
const replaceStrings = filterMatchedImports(matchedImports, this.context, options.defaultImportPaths[index]);
26+
replaceStrings.forEach(importString => {
27+
content = content.replace(importString, `@import '${path.relative(this.context, options.targetImportPaths[index])}';`);
28+
});
29+
}
2530

26-
const replaceImports = filterMatchedImports(matchedImports, this.context, options.defaultImportPath);
27-
replaceImports.forEach(importString => {
28-
content = content.replace(importString, `@import '${path.relative(this.context, options.targetImportPath)}';`)
29-
});
31+
const composesPatterns = `composes\\s*\:\\s*[^\\s]+\\s+from\\s+${pathMatch}`;
32+
const matchedComposes = content.match(new RegExp(composesPatterns, 'g'));
33+
if (matchedComposes) {
34+
const replaceStrings = filterMatchedImports(matchedComposes, this.context, options.defaultImportPaths[index]);
35+
replaceStrings.forEach(importString => {
36+
const replaceString = importString.replace(/['"][^'"]+['"]/, `"${path.relative(this.context, options.targetImportPaths[index])}"`);
37+
content = content.replace(importString, replaceString);
38+
});
39+
}
40+
};
41+
42+
options.defaultImportFilenames.forEach(replaceForImport);
3043

3144
callback(null, content);
3245
};

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
{
22
"name": "mini-css-themes-plugin",
3-
"version": "0.1.1",
3+
"version": "0.2.0",
44
"description": "Webpack plugin to generate separate themes for css modules.",
55
"main": "index.js",
66
"scripts": {
7-
"test": "echo \"Error: no test specified\" && exit 1"
7+
"test": "cd test && npm run test"
88
},
99
"repository": "https://github.com/archfz/mini-css-themes-plugin",
1010
"keywords": [

test/dist-spec-multi-entries/main.css

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/dist-spec-multi-entries/main.css.map

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/dist-spec-multi-entries/main.js

Lines changed: 125 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/dist-spec-multi-entries/main.js.map

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/dist-spec-multi-entries/theme__dark~main.css

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/dist-spec-multi-entries/theme__dark~main.css.map

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)