diff --git a/src/utils.js b/src/utils.js index a674bfb3..88e84186 100644 --- a/src/utils.js +++ b/src/utils.js @@ -311,6 +311,30 @@ function escapeLocalIdent(localident) { ); } +function resolveFolderTemplate(loaderContext, localIdentName, options) { + const { context } = options; + let { resourcePath } = loaderContext; + const parsed = path.parse(loaderContext.resourcePath); + + if (parsed.dir) { + resourcePath = parsed.dir + path.sep; + } + + let directory = path + .relative(context, `${resourcePath}_`) + .replace(/\\/g, "/") + .replace(/\.\.(\/)?/g, "_$1"); + directory = directory.substr(0, directory.length - 1); + + let folder = ""; + + if (directory.length > 1) { + folder = path.basename(directory); + } + + return localIdentName.replace(/\[folder\]/gi, () => folder); +} + function defaultGetLocalIdent( loaderContext, localIdentName, @@ -387,19 +411,33 @@ function defaultGetLocalIdent( }; // eslint-disable-next-line no-underscore-dangle - let result = loaderContext._compilation.getPath(localIdentName, data); + let interpolatedFilename = loaderContext._compilation.getPath( + localIdentName, + data + ); if (options.regExp) { const match = loaderContext.resourcePath.match(options.regExp); if (match) { match.forEach((matched, i) => { - result = result.replace(new RegExp(`\\[${i}\\]`, "ig"), matched); + interpolatedFilename = interpolatedFilename.replace( + new RegExp(`\\[${i}\\]`, "ig"), + matched + ); }); } } - return result; + if (localIdentName.includes("[folder]")) { + interpolatedFilename = resolveFolderTemplate( + loaderContext, + interpolatedFilename, + options + ); + } + + return interpolatedFilename; } function fixedEncodeURIComponent(str) { diff --git a/test/modules-option.test.js b/test/modules-option.test.js index d658af16..2e92b094 100644 --- a/test/modules-option.test.js +++ b/test/modules-option.test.js @@ -1909,6 +1909,46 @@ describe('"modules" option', () => { expect(getErrors(stats)).toMatchSnapshot("errors"); }); + it("should work with [folder]", async () => { + const compiler = getCompiler("./modules/localIdentName/localIdentName.js", { + modules: { localIdentName: "[local]-[folder]-[name]" }, + }); + const stats = await compile(compiler); + + expect( + getModuleSource("./modules/localIdentName/localIdentName.css", stats) + ).toMatchSnapshot("module"); + expect(getExecutedCode("main.bundle.js", compiler, stats)).toMatchSnapshot( + "result" + ); + expect(getWarnings(stats)).toMatchSnapshot("warnings"); + expect(getErrors(stats)).toMatchSnapshot("errors"); + }); + + it("should work with [folder] 2", async () => { + const compiler = getCompiler("./modules/localIdentName/localIdentName.js", { + modules: { + localIdentName: "[local]-[folder][name]", + localIdentContext: path.resolve( + __dirname, + "fixtures", + "modules", + "localIdentName" + ), + }, + }); + const stats = await compile(compiler); + + expect( + getModuleSource("./modules/localIdentName/localIdentName.css", stats) + ).toMatchSnapshot("module"); + expect(getExecutedCode("main.bundle.js", compiler, stats)).toMatchSnapshot( + "result" + ); + expect(getWarnings(stats)).toMatchSnapshot("warnings"); + expect(getErrors(stats)).toMatchSnapshot("errors"); + }); + it('should work and prefer relative for "composes"', async () => { const compiler = getCompiler("./modules/prefer-relative/source.js", { modules: { mode: "local" },