Skip to content

Commit cb36619

Browse files
committed
bug #561 Add extra checks to the copyFiles feature (Lyrkan)
This PR was merged into the master branch. Discussion ---------- Add extra checks to the copyFiles feature This PR adds some extra checks to make sure that files *really* match the pattern used in `copyFiles(...)` before the `file-loader` is called (closes #556). For a full description of the issue see #556 (comment), but here is a TL;DR: Currently, if you have a folder that contains a `foo.js` file and calls `copyFile(...)` with a pattern that excludes `.js` files, that file will still be copied. This is caused by the `resolve.extensions` mechanism of Webpack that also applies to `require.context(...)` calls (on which this feature relies) and includes `./foo` (without the extensions) to the list of files that should be imported. Commits ------- eea91a6 Add extra checks to the copyFiles feature
2 parents 97d7cec + eea91a6 commit cb36619

File tree

4 files changed

+100
-8
lines changed

4 files changed

+100
-8
lines changed

fixtures/copy/foo.png

Lines changed: 1 addition & 0 deletions
Loading

lib/config-generator.js

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -183,14 +183,23 @@ class ConfigGenerator {
183183
copyTo = this.webpackConfig.useVersioning ? '[path][name].[hash:8].[ext]' : '[path][name].[ext]';
184184
}
185185

186-
const copyFilesLoader = require.resolve('./webpack/copy-files-loader');
187-
const fileLoader = require.resolve('file-loader');
188-
const copyContext = entry.context ? path.resolve(this.webpackConfig.getContext(), entry.context) : copyFrom;
189-
const requireContextParam = `!${copyFilesLoader}!${fileLoader}?context=${copyContext}&name=${copyTo}!${copyFrom}`;
186+
const copyFilesLoaderPath = require.resolve('./webpack/copy-files-loader');
187+
const copyFilesLoaderConfig = `${copyFilesLoaderPath}?${JSON.stringify({
188+
// file-loader options
189+
context: entry.context ? path.resolve(this.webpackConfig.getContext(), entry.context) : copyFrom,
190+
name: copyTo,
191+
192+
// custom copy-files-loader options
193+
// the patternSource is base64 encoded in case
194+
// it contains characters that don't work with
195+
// the "inline loader" syntax
196+
patternSource: Buffer.from(entry.pattern.source).toString('base64'),
197+
patternFlags: entry.pattern.flags,
198+
})}`;
190199

191200
return buffer + `
192201
const context_${index} = require.context(
193-
'${stringEscaper(requireContextParam)}',
202+
'${stringEscaper(`!${copyFilesLoaderConfig}!${copyFrom}`)}',
194203
${!!entry.includeSubdirectories},
195204
${entry.pattern}
196205
);

lib/webpack/copy-files-loader.js

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,13 @@
1010
'use strict';
1111

1212
const LoaderDependency = require('webpack/lib/dependencies/LoaderDependency');
13+
const fileLoader = require('file-loader');
14+
const loaderUtils = require('loader-utils');
15+
const path = require('path');
1316

14-
module.exports = function loader(source) {
17+
module.exports.raw = true; // Needed to avoid corrupted binary files
18+
19+
module.exports.default = function loader(source) {
1520
// This is a hack that allows `Encore.copyFiles()` to support
1621
// JSON files using the file-loader (which is not something
1722
// that is supported in Webpack 4, see https://github.com/symfony/webpack-encore/issues/535)
@@ -39,5 +44,31 @@ module.exports = function loader(source) {
3944
this._module.parser = factory.getParser(requiredType);
4045
}
4146

42-
return source;
47+
const options = loaderUtils.getOptions(this);
48+
49+
// Retrieve the real path of the resource, relative
50+
// to the context used by copyFiles(...)
51+
const context = options.context;
52+
const resourcePath = this.resourcePath;
53+
const relativeResourcePath = path.relative(context, resourcePath);
54+
55+
// Retrieve the pattern used in copyFiles(...)
56+
// The "source" part of the regexp is base64 encoded
57+
// in case it contains characters that don't work with
58+
// the "inline loader" syntax
59+
const pattern = new RegExp(
60+
Buffer.from(options.patternSource, 'base64').toString(),
61+
options.patternFlags
62+
);
63+
64+
// If the pattern does not match the ressource's path
65+
// it probably means that the import was resolved using the
66+
// "resolve.extensions" parameter of Webpack (for instance
67+
// if "./test.js" was matched by "./test").
68+
if (!pattern.test(relativeResourcePath)) {
69+
return 'module.exports = "";';
70+
}
71+
72+
// If everything is OK, let the file-loader do the copy
73+
return fileLoader.bind(this)(source);
4374
};

test/functional.js

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2056,9 +2056,10 @@ module.exports = {
20562056
'foo.css',
20572057
'foo.js',
20582058
'foo.json',
2059+
'foo.png',
20592060
]);
20602061

2061-
for (const file of ['foo.css', 'foo.js', 'foo.json']) {
2062+
for (const file of ['foo.css', 'foo.js', 'foo.json', 'foo.png']) {
20622063
webpackAssert.assertOutputFileContains(
20632064
file,
20642065
'This is an invalid content to check that the file is still copied'
@@ -2068,6 +2069,56 @@ module.exports = {
20682069
done();
20692070
});
20702071
});
2072+
2073+
it('Do not copy files excluded by a RegExp', (done) => {
2074+
const config = createWebpackConfig('www/build', 'production');
2075+
config.addEntry('main', './js/no_require');
2076+
config.setPublicPath('/build');
2077+
2078+
// foo.css and foo.js should match this rule
2079+
// and be versioned
2080+
config.copyFiles({
2081+
from: './copy',
2082+
to: './[path][name]-[hash].[ext]',
2083+
pattern: /\.(css|js)$/,
2084+
});
2085+
2086+
// foo.css and foo.js should *not* match this rule
2087+
config.copyFiles({
2088+
from: './copy',
2089+
to: './[path][name].[ext]',
2090+
pattern: /\.(?!(css|js)$)([^.]+$)/
2091+
});
2092+
2093+
// By default the optimize-css-assets-webpack-plugin will
2094+
// run on ALL emitted CSS files, which includes the ones
2095+
// handled by `Encore.copyFiles()`.
2096+
// We disable it for this test since our CSS file will
2097+
// not be valid and can't be handled by this plugin.
2098+
config.configureOptimizeCssPlugin(options => {
2099+
options.assetNameRegExp = /^$/;
2100+
});
2101+
2102+
testSetup.runWebpack(config, (webpackAssert) => {
2103+
expect(config.outputPath).to.be.a.directory()
2104+
.with.files([
2105+
'entrypoints.json',
2106+
'runtime.js',
2107+
'main.js',
2108+
'manifest.json',
2109+
2110+
// 1st rule
2111+
'foo-40095734b7c5293c04603aa78333c23e.css',
2112+
'foo-40095734b7c5293c04603aa78333c23e.js',
2113+
2114+
// 2nd rule
2115+
'foo.json',
2116+
'foo.png',
2117+
]);
2118+
2119+
done();
2120+
});
2121+
});
20712122
});
20722123

20732124
describe('entrypoints.json & splitChunks()', () => {

0 commit comments

Comments
 (0)