diff --git a/README.md b/README.md index f3a46706..81d995eb 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,8 @@ module.exports = { > ℹ️ If you want `webpack-dev-server` to write files to the output directory during development, you can force it with the [`writeToDisk`](https://github.com/webpack/webpack-dev-middleware#writetodisk) option or the [`write-file-webpack-plugin`](https://github.com/gajus/write-file-webpack-plugin). +> ℹ️ You can get the original source filename from [Asset Objects](https://webpack.js.org/api/stats/#asset-objects). + ## Options The plugin's signature: diff --git a/package-lock.json b/package-lock.json index 24afd90e..9fb628d4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5976,6 +5976,15 @@ "flat-cache": "^2.0.1" } }, + "file-loader": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-6.1.1.tgz", + "integrity": "sha512-Klt8C4BjWSXYQAfhpYYkG4qHNTna4toMHEbWrI5IuVoxbU6uiDKeKAP99R8mmbJi3lvewn/jQBOgU4+NS3tDQw==", + "requires": { + "loader-utils": "^2.0.0", + "schema-utils": "^3.0.0" + } + }, "file-uri-to-path": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", @@ -12241,8 +12250,7 @@ }, "webpack": { "version": "5.1.3", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.1.3.tgz", - "integrity": "sha512-bNBF5EOpt5a6NeCBFu0+8KJtG61cVmOb2b/a5tPNRLz3OWgDpHMbmnDkaSm3nf/UQ6ufw4PWYGVsVOAi8UfL2A==", + "resolved": "github:webpack/webpack#4d0db65e6409bbac90378c8104f0846c9c4e73a6", "dev": true, "requires": { "@types/eslint-scope": "^3.7.0", diff --git a/package.json b/package.json index 42c31d58..f8ebd7a3 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "dependencies": { "cacache": "^15.0.5", "fast-glob": "^3.2.4", + "file-loader": "^6.1.1", "find-cache-dir": "^3.3.1", "glob-parent": "^5.1.1", "globby": "^11.0.1", diff --git a/src/index.js b/src/index.js index 9ece267a..407f8204 100644 --- a/src/index.js +++ b/src/index.js @@ -99,12 +99,13 @@ class CopyPlugin { pattern.to = path.normalize( typeof pattern.to !== 'undefined' ? pattern.to : '' ); + pattern.compilerContext = compiler.context; pattern.context = path.normalize( typeof pattern.context !== 'undefined' ? !path.isAbsolute(pattern.context) - ? path.join(compiler.options.context, pattern.context) + ? path.join(pattern.compilerContext, pattern.context) : pattern.context - : compiler.options.context + : pattern.compilerContext ); logger.debug(`processing from "${pattern.from}" to "${pattern.to}"`); @@ -303,7 +304,11 @@ class CopyPlugin { logger.log(`determined that "${from}" should write to "${webpackTo}"`); - return { absoluteFrom, relativeFrom, webpackTo }; + const sourceFilename = normalizePath( + path.relative(pattern.compilerContext, absoluteFrom) + ); + + return { absoluteFrom, sourceFilename, relativeFrom, webpackTo }; }); return Promise.all( @@ -527,6 +532,7 @@ class CopyPlugin { .filter(Boolean) .forEach((asset) => { const { + sourceFilename, absoluteFrom, targetPath, webpackTo, @@ -551,7 +557,7 @@ class CopyPlugin { `force updating "${webpackTo}" to compilation assets from "${absoluteFrom}"` ); - const info = { copied: true }; + const info = { copied: true, sourceFilename }; if (asset.immutable) { info.immutable = true; @@ -573,7 +579,7 @@ class CopyPlugin { `writing "${webpackTo}" to compilation assets from "${absoluteFrom}"` ); - const info = { copied: true }; + const info = { copied: true, sourceFilename }; if (asset.immutable) { info.immutable = true; diff --git a/test/CopyPlugin.test.js b/test/CopyPlugin.test.js index 898f0607..3e6d2e3c 100644 --- a/test/CopyPlugin.test.js +++ b/test/CopyPlugin.test.js @@ -2,6 +2,7 @@ import path from 'path'; import webpack from 'webpack'; import del from 'del'; +import { createFsFromVolume, Volume } from 'memfs'; import CopyPlugin from '../src'; @@ -380,6 +381,70 @@ describe('CopyPlugin', () => { .then(done) .catch(done); }); + + it('should work with multi compiler mode', async () => { + const compiler = webpack([ + { + mode: 'development', + context: path.resolve(__dirname, './fixtures'), + entry: path.resolve(__dirname, './helpers/enter.js'), + output: { + path: path.resolve(__dirname, './outputs/multi-compiler/dist/a'), + }, + stats: { + source: true, + }, + plugins: [ + new CopyPlugin({ + patterns: [ + { + from: path.resolve(__dirname, './fixtures/directory'), + }, + ], + }), + ], + }, + { + mode: 'development', + entry: path.resolve(__dirname, './helpers/enter.js'), + output: { + path: path.resolve(__dirname, './outputs/multi-compiler/dist/b'), + }, + stats: { + source: true, + }, + plugins: [ + new CopyPlugin({ + patterns: [ + { + context: path.resolve(__dirname, './fixtures'), + from: path.resolve(__dirname, './fixtures/directory'), + }, + ], + }), + ], + }, + ]); + + compiler.compilers.forEach((item) => { + const outputFileSystem = createFsFromVolume(new Volume()); + // Todo remove when we drop webpack@4 support + outputFileSystem.join = path.join.bind(path); + + // eslint-disable-next-line no-param-reassign + item.outputFileSystem = outputFileSystem; + }); + + const { stats } = await compile(compiler); + + stats.stats.forEach((item, index) => { + expect(item.compilation.errors).toMatchSnapshot('errors'); + expect(item.compilation.warnings).toMatchSnapshot('warnings'); + expect(readAssets(compiler.compilers[index], item)).toMatchSnapshot( + 'assets' + ); + }); + }); }); describe('watch mode', () => { @@ -747,6 +812,57 @@ describe('CopyPlugin', () => { }); }); + describe('stats', () => { + it('should work have assets info', async () => { + const compiler = getCompiler({ + entry: path.resolve(__dirname, './helpers/enter-with-asset-modules.js'), + }); + + new CopyPlugin({ + patterns: [ + { + from: path.resolve(__dirname, './fixtures/directory'), + }, + ], + }).apply(compiler); + + const { stats } = await compile(compiler); + + expect(stats.compilation.warnings).toMatchSnapshot('warnings'); + expect(stats.compilation.errors).toMatchSnapshot('errors'); + expect(readAssets(compiler, stats)).toMatchSnapshot('assets'); + + const assetsInfo = []; + + for (const [name, info] of stats.compilation.assetsInfo.entries()) { + assetsInfo.push({ + name, + info: { + // Workaround for `file-loader` + // eslint-disable-next-line no-undefined + immutable: info.immutable === false ? undefined : info.immutable, + copied: info.copied, + sourceFilename: info.sourceFilename, + }, + }); + } + + expect( + assetsInfo.sort((a, b) => { + if (a.name < b.name) { + return -1; + } + + if (a.name > b.name) { + return 1; + } + + return 0; + }) + ).toMatchSnapshot('assets info'); + }); + }); + describe('logging', () => { it('should logging when "from" is a file', (done) => { const expectedAssetKeys = ['file.txt']; diff --git a/test/__snapshots__/CopyPlugin.test.js.snap b/test/__snapshots__/CopyPlugin.test.js.snap index 9f77923e..e236d790 100644 --- a/test/__snapshots__/CopyPlugin.test.js.snap +++ b/test/__snapshots__/CopyPlugin.test.js.snap @@ -1,5 +1,33 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`CopyPlugin basic should work with multi compiler mode: assets 1`] = ` +Object { + ".dottedfile": "dottedfile contents +", + "directoryfile.txt": "new", + "nested/deep-nested/deepnested.txt": "", + "nested/nestedfile.txt": "", +} +`; + +exports[`CopyPlugin basic should work with multi compiler mode: assets 2`] = ` +Object { + ".dottedfile": "dottedfile contents +", + "directoryfile.txt": "new", + "nested/deep-nested/deepnested.txt": "", + "nested/nestedfile.txt": "", +} +`; + +exports[`CopyPlugin basic should work with multi compiler mode: errors 1`] = `Array []`; + +exports[`CopyPlugin basic should work with multi compiler mode: errors 2`] = `Array []`; + +exports[`CopyPlugin basic should work with multi compiler mode: warnings 1`] = `Array []`; + +exports[`CopyPlugin basic should work with multi compiler mode: warnings 2`] = `Array []`; + exports[`CopyPlugin cache should work with the "filesystem" cache: assets 1`] = ` Object { ".dottedfile": "dottedfile contents @@ -132,3 +160,71 @@ Object { ], } `; + +exports[`CopyPlugin stats should work have assets info: assets 1`] = ` +Object { + ".dottedfile": "dottedfile contents +", + "asset-modules/deepnested.txt": "", + "directoryfile.txt": "new", + "nested/deep-nested/deepnested.txt": "", + "nested/nestedfile.txt": "", +} +`; + +exports[`CopyPlugin stats should work have assets info: assets info 1`] = ` +Array [ + Object { + "info": Object { + "copied": true, + "immutable": undefined, + "sourceFilename": "directory/.dottedfile", + }, + "name": ".dottedfile", + }, + Object { + "info": Object { + "copied": undefined, + "immutable": undefined, + "sourceFilename": undefined, + }, + "name": "asset-modules/deepnested.txt", + }, + Object { + "info": Object { + "copied": true, + "immutable": undefined, + "sourceFilename": "directory/directoryfile.txt", + }, + "name": "directoryfile.txt", + }, + Object { + "info": Object { + "copied": undefined, + "immutable": undefined, + "sourceFilename": undefined, + }, + "name": "main.js", + }, + Object { + "info": Object { + "copied": true, + "immutable": undefined, + "sourceFilename": "directory/nested/deep-nested/deepnested.txt", + }, + "name": "nested/deep-nested/deepnested.txt", + }, + Object { + "info": Object { + "copied": true, + "immutable": undefined, + "sourceFilename": "directory/nested/nestedfile.txt", + }, + "name": "nested/nestedfile.txt", + }, +] +`; + +exports[`CopyPlugin stats should work have assets info: errors 1`] = `Array []`; + +exports[`CopyPlugin stats should work have assets info: warnings 1`] = `Array []`; diff --git a/test/helpers/enter-with-asset-modules.js b/test/helpers/enter-with-asset-modules.js new file mode 100644 index 00000000..a2bb5b30 --- /dev/null +++ b/test/helpers/enter-with-asset-modules.js @@ -0,0 +1,3 @@ +import txtURL from '../fixtures/directory/nested/deep-nested/deepnested.txt'; + +export default txtURL; diff --git a/test/helpers/getCompiler.js b/test/helpers/getCompiler.js index 242e970b..fdccde90 100644 --- a/test/helpers/getCompiler.js +++ b/test/helpers/getCompiler.js @@ -11,13 +11,28 @@ export default (config = {}) => { output: { path: path.resolve(__dirname, '../build'), }, + module: { + rules: [ + webpack.version[0] === '5' + ? { + test: /\.txt/, + type: 'asset/resource', + generator: { + filename: 'asset-modules/[name][ext]', + }, + } + : { + test: /\.txt/, + loader: 'file-loader', + options: { + name: 'asset-modules/[name].[ext]', + }, + }, + ], + }, ...config, }; - if (webpack.version[0] === 5) { - fullConfig.stats.source = true; - } - const compiler = webpack(fullConfig); if (!config.outputFileSystem) {