diff --git a/src/index.js b/src/index.js index 9e462b8..3cceb8e 100644 --- a/src/index.js +++ b/src/index.js @@ -4,6 +4,7 @@ import { getOptions, interpolateName } from 'loader-utils'; import { validate } from 'schema-utils'; import schema from './options.json'; +import { normalizePath } from './utils'; export default function loader(content) { const options = getOptions(this); @@ -73,6 +74,10 @@ export default function loader(content) { } } + assetInfo.sourceFilename = normalizePath( + path.relative(this.rootContext, this.resourcePath) + ); + this.emitFile(outputPath, content, null, assetInfo); } diff --git a/src/utils.js b/src/utils.js new file mode 100644 index 0000000..61cd6a6 --- /dev/null +++ b/src/utils.js @@ -0,0 +1,38 @@ +function normalizePath(path, stripTrailing) { + if (path === '\\' || path === '/') { + return '/'; + } + + const len = path.length; + + if (len <= 1) { + return path; + } + + // ensure that win32 namespaces has two leading slashes, so that the path is + // handled properly by the win32 version of path.parse() after being normalized + // https://msdn.microsoft.com/library/windows/desktop/aa365247(v=vs.85).aspx#namespaces + let prefix = ''; + + if (len > 4 && path[3] === '\\') { + // eslint-disable-next-line prefer-destructuring + const ch = path[2]; + + if ((ch === '?' || ch === '.') && path.slice(0, 2) === '\\\\') { + // eslint-disable-next-line no-param-reassign + path = path.slice(2); + prefix = '//'; + } + } + + const segs = path.split(/[/\\]+/); + + if (stripTrailing !== false && segs[segs.length - 1] === '') { + segs.pop(); + } + + return prefix + segs.join('/'); +} + +// eslint-disable-next-line import/prefer-default-export +export { normalizePath }; diff --git a/test/name-option.test.js b/test/name-option.test.js index f842e38..e50d389 100644 --- a/test/name-option.test.js +++ b/test/name-option.test.js @@ -157,4 +157,47 @@ describe('"name" option', () => { } } }); + + it('should work and add "sourceFilename" to asset info', async () => { + expect.assertions(1); + + const compiler = getCompiler('simple.js'); + const stats = await compile(compiler); + + for (const [name, info] of stats.compilation.assetsInfo) { + if (name.endsWith('.png')) { + expect(info.sourceFilename).toBe('file.png'); + } + } + }); + + it('should work and add "sourceFilename" to asset info #2', async () => { + expect.assertions(1); + + const compiler = getCompiler('simple.js', { + name: '[name].asset.[ext]?foo=[contenthash]', + }); + const stats = await compile(compiler); + + for (const [name, info] of stats.compilation.assetsInfo) { + if (name.startsWith('file.asset.png')) { + expect(info.sourceFilename).toBe('file.png'); + } + } + }); + + it('should work and add "sourceFilename" to asset info #3', async () => { + expect.assertions(1); + + const compiler = getCompiler('cdn.js', { + name: '[name].asset.[ext]', + }); + const stats = await compile(compiler); + + for (const [name, info] of stats.compilation.assetsInfo) { + if (name.startsWith('file.asset.png')) { + expect(info.sourceFilename).toBe('nested/file.png'); + } + } + }); });