记一次 Vue CLI 项目修改打包资源输出目录


theme: juejin
highlight: atom-one-dark

问题背景

某天同事在上架浙政钉应用的时候碰到个问题,Vue 项目打包默认输出资源路径的目录结构不符合上架要求,要求是输出的所有资源需要打平放在在 build 根目录下。如下示例:

打包默认输出路径的目录结构为

需要改成如下图

问题分析

项目是基于 Vue CLI 生成的 Vue 2 项目,依赖 webpack 4.x 版本。使用 Vue CLI 创建的模板项目中,通过 package.json 可以看到安装了依赖包 @vue/cli-service

@vue/cli-service 这个包集成了一套 webpack 的默认配置,用户可以通过配置文件 vue.config.js 进行配置,通过它提供的命令 vue-cli-service,去启动本地开发服务器(vue-cli-service serve)、打包(vue-cli-service build)等。

我们知道 webpack 处理各种资源需要配置相应 loader 去做处理,我们先查看 @vue/cli-service 中 webpack 相关的默认配置,以此作为参考,然后在配置文件 vue.config.js 中添加或修改相关配置,实现我们的目的。

怎么查看 @vue/cli-service 内置的 webpack 配置呢?可以通过它提供的 inspect 命令输出 webpack 配置信息,也可通过源码查看 webpack 相关配置。

vue inspect 命令

@vue/cli-service 提供了一个 inspect 命令来审查一个 Vue CLI 项目的 webpack 配置,我们可以通过该命令将配置信息输出到 output.js 文件中。

vue inspect > output.js--mode=production
# 或者
npx @vue/cli-service inspect > output.js --mode=production

相关文档:Vue CLI – 审查项目的 webpack 配置

输出的 output.js 如下图,注意它输出的并不是一个有效的 webpack 配置文件,而是一个用于审查的被序列化的格式。

从 output.js 文件中,可以看到 js、css、图片等资源输出的相关配置。

js 文件输出路径在 js 目录下;

css 文件输出路径在 css 目录下;

图片、字体等静态资源,输出路径在 img、media、fonts 目录下;

@vue/cli-service 相关源码

@vue/cli-service 中相关 webpack 配置在 lib/config 目录;涉及到的相关文件如下:

  • app.js 文件;里面包含 js 资源输出相关配置,可以看到默认配置输出在 js 目录下。app.js 源码

  • base.js 文件;里面包含静态资源相关配置,可以看到默认配置不同类型资源输出在 imgmediafonts 目录下,使用 file-loaderurl-loader 去处理。base.js 源码

    PS: 最新源码升级到 webpack 5,将这块配置抽离成了 assets.js 文件,具体见 PR:feat!: support and use webpack 5 as default
    并且不再使用 file-loaderurl-loader 去处理,而是改成通过 webpack 5 的 Assets Modules 配置,静态资源内联条件由之前配置小于 4k 变成小于 8 k(webpack 5 的默认配置),具体见 PR:feat!: remove url-loader and file-loader in favor of asset modules

  • css.js 文件;里面包含 css 相关配置,可以看到默认配置输出在 css 目录下。css.js 源码

问题解决

了解了 @vue/cli-service 内置的 webpack 相关配置,接下来一个个处理问题,在配置文件 vue.config.js 修改或添加配置。

  1. 处理输出目录名为 build;通过配置项 [outputDir](https://cli.vuejs.org/zh/config/#outputdir "outputDir") 将它配置为 build 即可。

      // vue.config.js
      module.exports = {
         outputDir: 'build',
         // ... 其他配置
      }
    
  2. 处理 js 文件资源输出路径;默认配置是输出在 js 目录下,我们通过配置项 chainWepack 添加如下配置,也可以通过 configureWepack 配置。

    // vue.config.js
    module.exports = {
      chainWebpack: (config) => {
        config.output
          .filename('[name].[hash:8].js')
          .chunkFilename('[name].[hash:8].js');
      },
      // ... 其他配置
    }
    
    // 或者
    module.exports = {
      configureWebpack: {
         output: {
           filename: "[name].[hash:8].js",
           chunkFilename: "[name].[hash:8].js",
         },
      },
      // ... 其他配置
    }
    
  3. 处理 css 文件资源输出路径;默认配置是输出在 css 目录下,我们通过 css.extract 配置项添加如下配置。

    // vue.config.js
    module.exports = {
      css: {
        extract: {
          filename: "[name].[hash:8].css",
          chunkFilename: "[name].[hash:8].css",
        },
      },
    }
    
  4. 处理图片等静态资源;参考内置的默认配置,添加如下配置;

    • webpack 4 采用如下配置

      // vue.config.js with webpack 4
      
      const filename = '[name].[hash:8].[ext]';
      const genUrlLoaderOptions = (dir) => {
        return {
          limit: 4096,
          // use explicit fallback to avoid regression in url-loader>=1.1.0
          fallback: {
            loader: require.resolve("file-loader"),
            options: {
              name: filename,
            },
          },
        };
      };
      
      module.exports = {
        chainWebpack: (config) => {
          config.module
            .rule("images")
            .test(/\.(png|jpe?g|gif|webp)(\?.*)?$/)
            .use("url-loader")
            .loader(require.resolve("url-loader"))
            .options(genUrlLoaderOptions());
      
          // do not base64-inline SVGs.
          // https://github.com/facebookincubator/create-react-app/pull/1180
          config.module
            .rule("svg")
            .test(/\.(svg)(\?.*)?$/)
            .use("file-loader")
            .loader(require.resolve("file-loader"))
            .options({
              name: filename,
            });
      
          config.module
            .rule("media")
            .test(/\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/)
            .use("url-loader")
            .loader(require.resolve("url-loader"))
            .options(genUrlLoaderOptions());
      
          config.module
            .rule("fonts")
            .test(/\.(woff2?|eot|ttf|otf)(\?.*)?$/i)
            .use("url-loader")
            .loader(require.resolve("url-loader"))
            .options(genUrlLoaderOptions());
        },
      };
      
    • wepack 5 采用如下配置

      // vue.config.js with webpack 5
      
      module.exports = {
        chainWebpack: (config) => {
      
          const filename = `[name].[hash:8].[ext]`
      
          config.module
            .rule("svg")
            .test(/\.(svg)(\?.*)?$/)
            // do not base64-inline SVGs.
            // https://github.com/facebookincubator/create-react-app/pull/1180
            .set("type", "asset/resource")
            .set("generator", {
              filename,
            });
      
          config.module
            .rule("images")
            .test(/\.(png|jpe?g|gif|webp|avif)(\?.*)?$/)
            .set("type", "asset")
            .set("generator", {
              filename,
            });
      
          config.module
            .rule("media")
            .test(/\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/)
            .set("type", "asset")
            .set("generator", {
              filename,
            });
      
          config.module
            .rule("fonts")
            .test(/\.(woff2?|eot|ttf|otf)(\?.*)?$/i)
            .set("type", "asset")
            .set("generator", {
              filename,
            });
        },
      }
      

添加以上配置就可以解决问题了,最后汇总在配置文件 vue.config.js 添加的所有配置如下;

  • webpack 4 采用如下配置

    // vue.config.js with webpack 4
    const filename = '[name].[hash:8].[ext]';
    const genUrlLoaderOptions = (dir) => {
      return {
        limit: 4096,
        // use explicit fallback to avoid regression in url-loader>=1.1.0
        fallback: {
          loader: require.resolve("file-loader"),
          options: {
            name: filename,
          },
        },
      };
    };
    
    module.exports = {
      outputDir: 'build',
      css: {
        extract: {
          filename: "[name].[hash:8].css",
          chunkFilename: "[name].[hash:8].css",
        },
      },
      chainWebpack: (config) => {
        config.module
          .rule("images")
          .test(/\.(png|jpe?g|gif|webp)(\?.*)?$/)
          .use("url-loader")
          .loader(require.resolve("url-loader"))
          .options(genUrlLoaderOptions());
    
        // do not base64-inline SVGs.
        // https://github.com/facebookincubator/create-react-app/pull/1180
        config.module
          .rule("svg")
          .test(/\.(svg)(\?.*)?$/)
          .use("file-loader")
          .loader(require.resolve("file-loader"))
          .options({
            name: filename,
          });
    
        config.module
          .rule("media")
          .test(/\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/)
          .use("url-loader")
          .loader(require.resolve("url-loader"))
          .options(genUrlLoaderOptions());
    
        config.module
          .rule("fonts")
          .test(/\.(woff2?|eot|ttf|otf)(\?.*)?$/i)
          .use("url-loader")
          .loader(require.resolve("url-loader"))
          .options(genUrlLoaderOptions());
      },
    }
    
  • webpack 5 采用如下配置

    // vue.config.js with webpack 5
    const { defineConfig } = require("@vue/cli-service");
    
    module.exports = defineConfig({
      outputDir: "build",
      css: {
        extract: {
          filename: "[name].[hash:8].css",
          chunkFilename: "[name].[hash:8].css",
        },
      },
      chainWebpack: (config) => {
        // js file
        config.output
          .filename('[name].[hash:8].js')
          .chunkFilename('[name].[hash:8].js');
    
        const filename = `[name].[hash:8].[ext]`
    
        config.module
          .rule("svg")
          .test(/\.(svg)(\?.*)?$/)
          // do not base64-inline SVGs.
          // https://github.com/facebookincubator/create-react-app/pull/1180
          .set("type", "asset/resource")
          .set("generator", {
            filename,
          });
    
        config.module
          .rule("images")
          .test(/\.(png|jpe?g|gif|webp|avif)(\?.*)?$/)
          .set("type", "asset")
          .set("generator", {
            filename,
          });
    
        config.module
          .rule("media")
          .test(/\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/)
          .set("type", "asset")
          .set("generator", {
            filename,
          });
    
        config.module
          .rule("fonts")
          .test(/\.(woff2?|eot|ttf|otf)(\?.*)?$/i)
          .set("type", "asset")
          .set("generator", {
            filename,
          });
      },
    });
    

总结

首先我们需要分析下项目使用的相关工具,知道了项目是使用 Vue CLI 生成的,本地开发、打包都是基于命令 vue-cli-service,该命令是由包 @vue/cli-service 提供,从文档中了解到它是”基于 webpack 构建,并带有合理的默认配置。“,并且提供了 inspect 命令用于审查项目的 webpack 配置。接下来通过查看 inspect 命令输出的配置信息和 @vue/cli-service 源码中的有关配置,参考它的配置,然后在配置文件 vue.config.js 添加相应配置,实现修改打包输出的资源路径。

© 版权声明
THE END
喜欢就支持一下吧
点赞8 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容