Using webpack, I have a multiple page application which works with multiple entries. I have a project tree like this:
-- webpack.config.js
-- src/
-- first/
-- main.ts
-- helper.ts
-- main.css
-- index.html
-- second/
-- main.ts
-- main.css
-- index.html
-- assets/
-- myAsset1.png
-- myAsset2.png
-- myAsset3.png
I wanted something tidy and organised by folder. To do so, I add [name]/...
each time for the name
or the filename
like this:
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const path = require('path');
module.exports = {
entry: {
first: './src/first/main.ts',
second: './src/second/main.ts',
},
output: {
path: path.resolve(__dirname, 'dist'), // output directory
filename: '[name]/script.js', // name of the generated bundle
publicPath: '/',
},
module: {
rules: [
// TypeScript
{
test: /\.ts$/,
loader: 'ts-loader',
options: {
onlyCompileBundledFiles: true,
},
},
// Style
{
test: /\.s?[ac]ss$/,
use: [
MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
},
{
loader: 'postcss-loader',
options: {
sourceMap: true,
},
},
{
loader: 'resolve-url-loader',
},
{
loader: 'sass-loader',
},
],
},
// Fonts & Images
{
test: /\.(woff(2)?|ttf|eot|jpg|png|svg|md)(\?v=\d+\.\d+\.\d+)?$/,
use: [
{
loader: 'file-loader',
options: {
limit: 8192,
name: '[name].[ext]',
},
},
],
},
],
},
resolve: {
modules: ['node_modules', path.resolve(process.cwd(), 'src')],
extensions: ['.ts', '.js'],
},
plugins: [
new HtmlWebpackPlugin({
template: path.join(process.cwd(), 'src/first/index.html'),
inject: true,
chunks: ['first'],
filename: 'first/index.html',
}),
new HtmlWebpackPlugin({
template: './src/second/index.html',
inject: true,
chunks: ['second'],
filename: 'second/index.html',
}),
new MiniCssExtractPlugin({
filename: '[name]/style.css',
chunkFilename: '[name]/style.css',
}),
],
};
The result will be:
-- dist/
-- first/
-- script.js
-- style.css
-- index.html
-- second/
-- script.js
-- style.css
-- index.html
-- myAsset1.png
-- myAsset2.png
-- myAsset3.png
However, I would like the assets in the folder name according to the entry from where it is imported. Indeed, myAssets1.png
and myAssets2.png
are imported from main.ts
of first
folder and myAssets3.png
is imported from main.ts
of second
folder
-- dist/
-- first/
-- script.js
-- style.css
-- index.html
-- myAsset1.png
-- myAsset2.png
-- second/
-- script.js
-- style.css
-- index.html
-- myAsset3.png
How could I set the path for my assets according to the entry name?
In order to specify that the assets that file-loader is handling will be saved in your required place few things should happend:
resourceQuery
resourceQuery
in the name
function of the file-loader
to specify the path.In order to achieve 1,2 you can create a custom loader, which will extract the issuer & attach the query.
// entry-dir-injector-loader.js
const path = require('path');
function recursiveIssuer(m) {
if (m.issuer && m.issuer.context) {
return recursiveIssuer(m.issuer);
} else if (m.context) {
return m.context;
} else {
return false;
}
}
module.exports = function (content) {
const entry = recursiveIssuer(this._module);
const entryRelativePath = path.relative(this.rootContext, entry).replace('src/', '');
this.resourceQuery = `?entryDir=${entryRelativePath}`;
return content;
};
Then specify the name
as a function, extract the query.
module.exports = {
module: {
rules: [
{
test: /\.(woff(2)?|ttf|eot|jpg|png|svg|md)(\?v=\d+\.\d+\.\d+)?$/,
use: [
{
loader: 'file-loader',
options: {
limit: 8192,
name(resourcePath, resourceQuery) {
const entryDir = resourceQuery
.substring(1)
.split('&')
.filter((key) => key.startsWith('entryDir='))
.map((key) => key.split('=').pop())
.join('');
return path.join(entryDir, `[name].[ext]`);
},
},
},
{
loader: './entry-dir-injector-loader.js',
},
],
},
],
},
};