Skip to content

Commit b8c997b

Browse files
committed
feature #1398 Remove unmaintained file-loader dependency (Kocal)
This PR was squashed before being merged into the main branch. Discussion ---------- Remove unmaintained file-loader dependency | Q | A | ------------- | --- | Bug fix? | no | New feature? | yes <!-- please update CHANGELOG.md file --> | Deprecations? | no <!-- please update CHANGELOG.md file --> | Issues | Fix #1396 <!-- prefix each issue number with "Fix #", no need to create an issue if none exists, explain below instead --> | License | MIT This PR removes the deprecated file-loader dependency in favor of webpack's Asset Modules. This loader was previously used in `Encore.copyFiles()`. Commits ------- 1200947 Remove unmaintained file-loader dependency
2 parents b0542cf + 1200947 commit b8c997b

File tree

8 files changed

+109
-131
lines changed

8 files changed

+109
-131
lines changed

CHANGELOG.md

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,26 @@
22

33
## 6.0.0
44

5-
* Drop support for Node.js <22.13.0
6-
* Update @nuxt/friendly-errors-webpack-plugin to @kocal/friendly-errors-webpack-plugin, an updated fork of the original plugin
7-
* Update css-minimizer-webpack-plugin to ^8.0.0, see [release notes](https://github.com/webpack/css-minimizer-webpack-plugin/releases/tag/v8.0.0)
5+
This is a new major version that contains several backwards-compatibility breaks, but for the best!
6+
7+
### BC Breaks
8+
9+
* Remove support of Node.js <22.13.0
10+
* Remove support of babel-loader@^9.1.3, see possible BC breaks in [10.0.0 release notes](https://github.com/babel/babel-loader/releases/tag/v10.0.0)
11+
* Remove support of style-loader@^3.3.0, see possible BC breaks in [4.0.0 release notes](https://github.com/webpack/style-loader/releases/tag/v4.0.0)
12+
* Remove support of less-loader@^11.0.0, see possible BC breaks in [12.0.0 release notes](https://github.com/webpack/less-loader/releases/tag/v12.0.0)
13+
* Remove support of postcss-loader@^7.0.0, see possible BC breaks in [8.0.0 release notes](https://github.com/webpack/postcss-loader/releases/tag/v8.0.0)
14+
* Remove support of stylus-loader@^7.0.0, see possible BC breaks in [8.0.0 release notes](https://github.com/webpack/stylus-loader/releases/tag/v8.0.0)
15+
* Remove support of webpack-cli@^5.0.0, see possible BC breaks in [6.0.0 release notes](https://github.com/webpack/webpack-cli/releases/tag/webpack-cli%406.0.0)
16+
* Remove unmaintained file-loader dependency
17+
The `[N]` placeholder (regex capture groups in filename patterns) is no longer supported.
18+
If you were using patterns like `[1]` or `[2]` in your `Encore.copyFiles()` filename option, you will need to restructure your file organization or use a different naming strategy.
19+
20+
### Features
21+
22+
* Update @nuxt/friendly-errors-webpack-plugin to @kocal/friendly-errors-webpack-plugin, a maintained fork of the original plugin
823
* Update webpack from ^5.74.0 to ^5.82.0
9-
* Remove support for babel-loader ^9.1.3, see [10.0.0 release notes](https://github.com/babel/babel-loader/releases/tag/v10.0.0)
10-
* Remove support for style-loader ^3.3.0, see [4.0.0 release notes](https://github.com/webpack/style-loader/releases/tag/v4.0.0)
11-
* Remove support for less-loader ^11.0.0, see [12.0.0 release notes](https://github.com/webpack/less-loader/releases/tag/v12.0.0)
12-
* Remove support for postcss-loader ^7.0.0, see [8.0.0 release notes](https://github.com/webpack/postcss-loader/releases/tag/v8.0.0)
13-
* Remove support for stylus-loader ^7.0.0, see [8.0.0 release notes](https://github.com/webpack/stylus-loader/releases/tag/v8.0.0)
14-
* Remove support for webpack-cli ^5.0.0, see [6.0.0 release notes](https://github.com/webpack/webpack-cli/releases/tag/webpack-cli%406.0.0)
24+
* Update css-minimizer-webpack-plugin to ^8.0.0, see [release notes](https://github.com/webpack/css-minimizer-webpack-plugin/releases/tag/v8.0.0)
1525

1626
## 5.3.0
1727

index.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -584,9 +584,11 @@ class Encore {
584584
* A regular expression (or a string containing one) that
585585
* the filenames must match in order to be copied
586586
* - {string} to (default: [path][name].[ext])
587-
* Where the files must be copied to. You can add all the
588-
* placeholders supported by the file-loader.
589-
* https://github.com/webpack-contrib/file-loader#placeholders
587+
* Where the files must be copied to. Supported placeholders:
588+
* - [name]: filename without extension
589+
* - [ext]: file extension without the leading dot
590+
* - [path]: relative directory path from context
591+
* - [hash:N]: content hash, optionally truncated to N characters
590592
* - {boolean} includeSubdirectories (default: true)
591593
* Whether or not the copy should include subdirectories.
592594
* - {string} context (default: path of the source directory)

lib/config-generator.js

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515

1616
const cssExtractLoaderUtil = require('./loaders/css-extract');
1717
const pathUtil = require('./config/path-util');
18-
const featuresHelper = require('./features');
1918
// loaders utils
2019
const cssLoaderUtil = require('./loaders/css');
2120
const sassLoaderUtil = require('./loaders/sass');
@@ -176,10 +175,6 @@ class ConfigGenerator {
176175
entry[entryName] = entryChunks;
177176
}
178177

179-
if (this.webpackConfig.copyFilesConfigs.length > 0) {
180-
featuresHelper.ensurePackagesExistAndAreCorrectVersion('copy_files');
181-
}
182-
183178
const copyFilesConfigs = this.webpackConfig.copyFilesConfigs.filter(entry => {
184179
const copyFrom = path.resolve(
185180
this.webpackConfig.getContext(),
@@ -216,11 +211,8 @@ class ConfigGenerator {
216211

217212
const copyFilesLoaderPath = require.resolve('./webpack/copy-files-loader');
218213
const copyFilesLoaderConfig = `${copyFilesLoaderPath}?${JSON.stringify({
219-
// file-loader options
220214
context: entry.context ? path.resolve(this.webpackConfig.getContext(), entry.context) : copyFrom,
221-
name: copyTo,
222-
223-
// custom copy-files-loader options
215+
filename: copyTo,
224216
// the patternSource is base64 encoded in case
225217
// it contains characters that don't work with
226218
// the "inline loader" syntax

lib/features.js

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -116,13 +116,6 @@ const features = {
116116
],
117117
description: 'use Vue with JSX support'
118118
},
119-
copy_files: {
120-
method: 'copyFiles()',
121-
packages: [
122-
{ name: 'file-loader', enforce_version: true },
123-
],
124-
description: 'Copy files'
125-
},
126119
notifier: {
127120
method: 'enableBuildNotifications()',
128121
packages: [

lib/webpack/copy-files-loader.js

Lines changed: 75 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -9,39 +9,60 @@
99

1010
'use strict';
1111

12-
const LoaderDependency = require('webpack/lib/dependencies/LoaderDependency');
1312
const path = require('path');
1413

1514
module.exports.raw = true; // Needed to avoid corrupted binary files
1615

17-
module.exports.default = function loader(source) {
18-
// This is a hack that allows `Encore.copyFiles()` to support
19-
// JSON files using the file-loader (which is not something
20-
// that is supported in Webpack 4, see https://github.com/symfony/webpack-encore/issues/535)
21-
//
22-
// Since there is no way to change the module's resource type from a loader
23-
// without using private properties yet we have to use "this._module".
24-
//
25-
// By setting its type to 'javascript/auto' Webpack won't try parsing
26-
// the result of the loader as a JSON object.
27-
//
28-
// For more information:
29-
// https://github.com/webpack/webpack/issues/6572#issuecomment-368376326
30-
// https://github.com/webpack/webpack/issues/7057
31-
const requiredType = 'javascript/auto';
32-
if (this._module.type !== requiredType) {
33-
// Try to retrieve the factory used by the LoaderDependency type
34-
// which should be the NormalModuleFactory.
35-
const factory = this._compilation.dependencyFactories.get(LoaderDependency);
36-
if (factory === undefined) {
37-
throw new Error('Could not retrieve module factory for type LoaderDependency');
38-
}
39-
40-
this._module.type = requiredType;
41-
this._module.generator = factory.getGenerator(requiredType);
42-
this._module.parser = factory.getParser(requiredType);
16+
/**
17+
* Interpolates a filename template with the given data.
18+
*
19+
* Supports the following placeholders (compatible with the legacy file-loader):
20+
* - [name]: filename without extension
21+
* - [ext]: file extension without the leading dot
22+
* - [path]: relative directory path from context, with trailing slash
23+
* - [hash:N] or [contenthash:N]: content hash, optionally truncated to N characters
24+
*
25+
* @param {string} template - The filename template (e.g., '[path][name].[hash:8].[ext]')
26+
* @param {object} data
27+
* @param {string} data.resourcePath - Absolute path to the resource
28+
* @param {string} data.context - Context directory for computing relative paths
29+
* @param {string} data.contentHash - Pre-computed content hash
30+
* @returns {string} The interpolated filename
31+
*/
32+
function interpolateName(template, { resourcePath, context, contentHash }) {
33+
const parsed = path.parse(resourcePath);
34+
const ext = parsed.ext.slice(1); // Remove leading dot for file-loader compatibility
35+
const name = parsed.name;
36+
37+
// Compute relative directory path from context
38+
let relativeDir = path.relative(context, parsed.dir).replace(/\\/g, '/');
39+
if (relativeDir) {
40+
relativeDir += '/';
4341
}
4442

43+
// Replace parent directory markers (..) with underscores for safety
44+
relativeDir = relativeDir.replace(/\.\.(\/)?/g, '_$1');
45+
46+
return template
47+
.replace(/\[name\]/g, name)
48+
.replace(/\[ext\]/g, ext)
49+
.replace(/\[path\]/g, relativeDir)
50+
.replace(/\[(?:content)?hash(?::(\d+))?\]/g, (match, length) => {
51+
return length ? contentHash.slice(0, parseInt(length, 10)) : contentHash;
52+
});
53+
}
54+
55+
/**
56+
* A webpack loader that copies files to the output directory.
57+
*
58+
* This loader replaces the deprecated file-loader by using webpack's
59+
* native this.emitFile() API. It supports filtering files by pattern
60+
* and customizing output paths using template placeholders.
61+
*
62+
* @param {Buffer} source - The raw file content
63+
* @returns {string} A CommonJS module that exports the public URL of the copied file
64+
*/
65+
module.exports.default = function loader(source) {
4566
const options = this.getOptions();
4667

4768
// Retrieve the real path of the resource, relative
@@ -59,33 +80,41 @@ module.exports.default = function loader(source) {
5980
options.patternFlags
6081
);
6182

62-
// If the pattern does not match the ressource's path
83+
// If the pattern does not match the resource's path
6384
// it probably means that the import was resolved using the
6485
// "resolve.extensions" parameter of Webpack (for instance
6586
// if "./test.js" was matched by "./test").
6687
if (!pattern.test(relativeResourcePath)) {
6788
return 'module.exports = "";';
6889
}
6990

70-
// Add the "regExp" option (needed to use the [N] placeholder
71-
// see: https://github.com/webpack-contrib/file-loader#n)
72-
options.regExp = pattern;
73-
74-
// Remove copy-files-loader's custom options (in case the
75-
// file-loader starts looking for thing it doesn't expect)
76-
delete options.patternSource;
77-
delete options.patternFlags;
78-
79-
// Update loader's options.
80-
this.loaders.forEach(loader => {
81-
if (__filename === loader.path) {
82-
loader.options = options;
83-
delete loader.query;
84-
}
91+
// Compute content hash using webpack's built-in hashing utilities
92+
// This uses the hash function configured in webpack (e.g., xxhash64)
93+
const hash = this.utils.createHash();
94+
hash.update(source);
95+
const contentHash = hash.digest('hex');
96+
97+
// Interpolate the output filename using the template
98+
const outputPath = interpolateName(options.filename, {
99+
resourcePath,
100+
context,
101+
contentHash,
85102
});
86103

87-
const fileLoader = require('file-loader');
104+
// Build asset info for webpack
105+
const assetInfo = {
106+
sourceFilename: path.relative(this.rootContext, resourcePath).replace(/\\/g, '/'),
107+
};
88108

89-
// If everything is OK, let the file-loader do the copy
90-
return fileLoader.bind(this)(source);
109+
// Check if filename contains a hash (for immutability hints)
110+
if (/\[(?:content)?hash(?::\d+)?\]/i.test(options.filename)) {
111+
assetInfo.immutable = true;
112+
}
113+
114+
// Emit the file to the output directory
115+
this.emitFile(outputPath, source, null, assetInfo);
116+
117+
// Return a module that exports the public URL
118+
return `module.exports = __webpack_public_path__ + ${JSON.stringify(outputPath)};`;
91119
};
120+

package.json

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,6 @@
6969
"eslint-plugin-jsdoc": "^50.8.0",
7070
"eslint-plugin-mocha": "^11.1.0",
7171
"eslint-plugin-n": "^17.21.3",
72-
"file-loader": "^6.0.0",
7372
"fork-ts-checker-webpack-plugin": "^7.0.0 || ^8.0.0 || ^9.0.0",
7473
"fs-extra": "^10.0.0",
7574
"globals": "^16.3.0",
@@ -114,7 +113,6 @@
114113
"@vue/babel-plugin-jsx": "^1.0.0",
115114
"@vue/babel-preset-jsx": "^1.0.0",
116115
"@vue/compiler-sfc": "^2.6 || ^3.0.2",
117-
"file-loader": "^6.0.0",
118116
"fork-ts-checker-webpack-plugin": "^7.0.0 || ^8.0.0 || ^9.0.0",
119117
"handlebars": "^4.7.7",
120118
"handlebars-loader": "^1.7.0",
@@ -166,9 +164,6 @@
166164
"@vue/compiler-sfc": {
167165
"optional": true
168166
},
169-
"file-loader": {
170-
"optional": true
171-
},
172167
"fork-ts-checker-webpack-plugin": {
173168
"optional": true
174169
},

test/functional.js

Lines changed: 0 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -2527,41 +2527,6 @@ module.exports = {
25272527
});
25282528
});
25292529

2530-
it('Can use the "[N]" placeholder', function(done) {
2531-
const config = createWebpackConfig('www/build', 'production');
2532-
config.addEntry('main', './js/no_require');
2533-
config.setPublicPath('/build');
2534-
config.copyFiles({
2535-
from: './images',
2536-
pattern: /(symfony)_(logo)\.png/,
2537-
to: '[path][2]_[1].[ext]',
2538-
includeSubdirectories: false
2539-
});
2540-
2541-
testSetup.runWebpack(config, (webpackAssert) => {
2542-
expect(config.outputPath).to.be.a.directory()
2543-
.with.files([
2544-
'entrypoints.json',
2545-
'runtime.js',
2546-
'main.js',
2547-
'manifest.json',
2548-
'logo_symfony.png'
2549-
]);
2550-
2551-
webpackAssert.assertManifestPath(
2552-
'build/main.js',
2553-
'/build/main.js'
2554-
);
2555-
2556-
webpackAssert.assertManifestPath(
2557-
'build/symfony_logo.png',
2558-
'/build/logo_symfony.png'
2559-
);
2560-
2561-
done();
2562-
});
2563-
});
2564-
25652530
it('Does not prevent from setting the map option of the manifest plugin', function(done) {
25662531
const config = createWebpackConfig('www/build', 'production');
25672532
config.addEntry('main', './js/no_require');

yarn.lock

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4030,14 +4030,6 @@ file-entry-cache@^8.0.0:
40304030
dependencies:
40314031
flat-cache "^4.0.0"
40324032

4033-
file-loader@^6.0.0:
4034-
version "6.2.0"
4035-
resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-6.2.0.tgz#baef7cf8e1840df325e4390b4484879480eebe4d"
4036-
integrity sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw==
4037-
dependencies:
4038-
loader-utils "^2.0.0"
4039-
schema-utils "^3.0.0"
4040-
40414033
fill-range@^7.1.1:
40424034
version "7.1.1"
40434035
resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292"
@@ -6854,15 +6846,6 @@ scheduler@^0.23.2:
68546846
dependencies:
68556847
loose-envify "^1.1.0"
68566848

6857-
schema-utils@^3.0.0, schema-utils@^3.1.1:
6858-
version "3.3.0"
6859-
resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.3.0.tgz#f50a88877c3c01652a15b622ae9e9795df7a60fe"
6860-
integrity sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==
6861-
dependencies:
6862-
"@types/json-schema" "^7.0.8"
6863-
ajv "^6.12.5"
6864-
ajv-keywords "^3.5.2"
6865-
68666849
"schema-utils@^3.0.0 || ^4.0.0":
68676850
version "4.3.0"
68686851
resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-4.3.0.tgz#3b669f04f71ff2dfb5aba7ce2d5a9d79b35622c0"
@@ -6873,6 +6856,15 @@ schema-utils@^3.0.0, schema-utils@^3.1.1:
68736856
ajv-formats "^2.1.1"
68746857
ajv-keywords "^5.1.0"
68756858

6859+
schema-utils@^3.1.1:
6860+
version "3.3.0"
6861+
resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.3.0.tgz#f50a88877c3c01652a15b622ae9e9795df7a60fe"
6862+
integrity sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==
6863+
dependencies:
6864+
"@types/json-schema" "^7.0.8"
6865+
ajv "^6.12.5"
6866+
ajv-keywords "^3.5.2"
6867+
68766868
schema-utils@^4.0.0, schema-utils@^4.2.0:
68776869
version "4.2.0"
68786870
resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-4.2.0.tgz#70d7c93e153a273a805801882ebd3bff20d89c8b"

0 commit comments

Comments
 (0)