Skip to content

Add control over output folder layout #4584

Open
@jls-tschanzc

Description

@jls-tschanzc

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 ../

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions