Description
What problem does this feature solve?
When creating a "default" app with the vue CLI, it will configure webpack to output the build artefacts into folders like "css/", "js/", "media/". This structure can't be directly controlled with simple configuration options; only through chain-/configureWebpack. Especially the css-extract setup with the mini-css-extract-plugin complicates this a lot.
Flattening the output folder might not be a feature most people use but exposing a couple more path options might not be too hard and could really help a lot.
Example Use Case:
We have a rather bothersome CMS (not a custom thing) which serves it's content on a dynamic path, so setting the "publicPath" option to "" (empty string) solves that part but it also requires a flat directory structure as it can't handle directories so we have to flatten the output in "dist" completely.
What does the proposed API look like?
In my vue CLI plugin, the following index.js creates a fully flat dist structure (can also be used in a vue.config.js):
module.exports = (api, vueConfig) => {
// Build flat '/dist' structure
api.chainWebpack((config) => {
if (process.env.NODE_ENV === 'production') {
config
.plugin('extract-css')
.tap((args) => {
// Flatten .css output: ./css/*.css -> ./*.css
args[0].filename = '[name].[contenthash:8].css';
args[0].chunkFilename = '[name].[contenthash:8].css';
return args;
})
.end()
.module
.rule('images')
.use('url-loader')
.tap((args) => {
// Flatten bitmap img output: ./img/* -> ./*
args.fallback.options.name = '[name].[hash:8].[ext]';
return args;
})
.end()
.end()
.rule('svg')
.use('file-loader')
.tap((args) => {
// Flatten svg output: ./img/* -> ./*
args.name = '[name].[hash:8].[ext]';
return args;
})
.end()
.end()
.rule('media')
.use('url-loader')
.tap((args) => {
// Flatten AV output: ./media/* -> ./*
args.fallback.options.name = '[name].[hash:8].[ext]';
return args;
})
.end()
.end()
.rule('fonts')
.use('url-loader')
.tap((args) => {
// Flatten fonts output: ./fonts/* -> ./*
args.fallback.options.name = '[name].[hash:8].[ext]';
return args;
})
.end()
.end()
.end();
}
});
api.configureWebpack((config) => {
if (process.env.NODE_ENV === 'production') {
// Flatten .js output: ./js/*.js -> ./*.js
config.output.filename = '[name].[contenthash:8].js';
config.output.chunkFilename = '[name].[contenthash:8].js';
// Fix relative url() in CSS
// Otherwise Vue CLI will generate '../' as the publicPath for the
// 'mini-css-extract-plugin' which is wrong as we flatten everything
// See: https://github.com/vuejs/vue-cli/blob/b156ff17e520d0653d5dbcc8e18d19ecd2ab4798/packages/%40vue/cli-service/lib/config/css.js#L64
config.module.rules
.flatMap((rule) => rule.oneOf)
.filter((oneOf) => !!oneOf)
.flatMap((oneOf) => oneOf.use)
.filter((use) => use.loader.includes('mini-css-extract-plugin'))
.forEach((use) => use.options.publicPath = './');
}
});
}
Most of it is not too complicated but a bit verbose. The only real problem is that the generation of the options for the mini-css-extract-plugin loader. It will disregard any publicPath settings or css-extract filenames. Setting the project to "lib" does not seem a viable option.
The vue.config.js options already has an "assetsDir" option which goes into the opposite direction, further nesting the static assets.
Introducing something similar e.g. "assetsPaths" might be an option to control and generalise this in a more simple way:
assetsPaths : {
assetsDir: '',
js: 'js/',
css: 'css/',
img: 'img/',
media: 'media/',
font: 'fonts/',
}
Additionally adjusting the generation of the "cssPublicPath" for the css-extract options here could be generalised to respect the extract-css filename, currently it only does so to generate deeper paths with ../