vue-cli4 全面配置--vue.config.js

2,824 阅读2分钟

  细致全面的 vue-cli4 配置信息。涵盖了使用 vue-cli 开发过程中大部分配置需求。

  不建议直接拉取此项目作为模板,希望能按照此教程按需配置,或者复制 vue.config.js 增删配置,并自行安装所需依赖。

✅ 配置多环境变量

  通过在 package.json 里的 scripts 配置项中添加--mode xxx 来选择不同环境

  只有以 VUE_APP 开头的变量会被 webpack.DefinePlugin 静态嵌入到客户端侧的包中,代码中可以通过 process.env.VUE_APP_BASE_API 访问

  NODE_ENV 和 BASE_URL 是两个特殊变量,在代码中始终可用

配置

在项目根目录中新建.env, .env.production, .env.analyz 等文件

.env

 serve 默认的本地开发环境配置

NODE_ENV = "development"
BASE_URL = "./"
VUE_APP_PUBLIC_PATH = "./"
VUE_APP_API = "https://test.staven630.com/api"

.env.production

build 默认的环境配置(正式服务器)

NODE_ENV = "production"
BASE_URL = "https://crm.staven630.com/"
VUE_APP_PUBLIC_PATH = "https://crm.oss.com/staven-blog"
VUE_APP_API = "https://crm.staven630.com/api"

ACCESS_KEY_ID = "xxxxxxxxxxxxx"
ACCESS_KEY_SECRET = "xxxxxxxxxxxxx"
REGION = "oss-cn-hangzhou"
BUCKET = "staven-crm"
PREFIX = "staven-blog"

IS_ANALYZE = true;

修改package.json

"scripts": {
  "build": "vue-cli-service build",
  "crm": "vue-cli-service build --mode crm"
}

使用环境变量

<template>
  <div class="home">
    <!-- template中使用环境变量 -->
     API: {{ api }}
  </div>
</template>

<script>
export default {
  name: "home",
  data() {
    return {
      api: process.env.VUE_APP_API
    };
  },
  mounted() {
    // js代码中使用环境变量
    console.log("BASE_URL: ", process.env.BASE_URL);
    console.log("VUE_APP_API: ", process.env.VUE_APP_API);
  }
};
</script>

✅ 配置基础 vue.config.js

const IS_PROD = ["production", "prod"].includes(process.env.NODE_ENV);

module.exports = {
  publicPath: IS_PROD ? process.env.VUE_APP_PUBLIC_PATH : "./", // 默认'/',部署应用包时的基本 URL
  // outputDir: process.env.outputDir || 'dist', // 'dist', 生产环境构建文件的目录
  // assetsDir: "", // 相对于outputDir的静态资源(js、css、img、fonts)目录
  lintOnSave: false,
  runtimeCompiler: true, // 是否使用包含运行时编译器的 Vue 构建版本
  productionSourceMap: !IS_PROD, // 生产环境的 source map
  parallel: require("os").cpus().length > 1,
  pwa: {}
};

✅ 配置 proxy 代理解决跨域问题

假设 mock 接口为www.easy-mock.com/mock/5bc75b…

module.exports = {
  devServer: {
    // overlay: { // 让浏览器 overlay 同时显示警告和错误
    //   warnings: true,
    //   errors: true
    // },
    // open: false, // 是否打开浏览器
    // host: "localhost",
    // port: "8080", // 代理断就
    // https: false,
    // hotOnly: false, // 热更新
    proxy: {
      "/api": {
        target:
          "https://www.easy-mock.com/mock/5bc75b55dc36971c160cad1b/sheets", // 目标代理接口地址
        secure: false,
        changeOrigin: true, // 开启代理,在本地创建一个虚拟服务端
        // ws: true, // 是否启用websockets
        pathRewrite: {
          "^/api": "/"
        }
      }
    }
  }
};

访问

<script>
import axios from "axios";
export default {
  mounted() {
    axios.get("/api/1").then(res => {
      console.log('proxy:', res);
    });
  }
};
</script>

✅ 修复 HMR(热更新)失效

如果热更新失效,如下操作:

module.exports = {
  chainWebpack: config => {
    // 修复HMR
    config.resolve.symlinks(true);
  }
};

✅ 修复 Lazy loading routes Error: Cyclic dependency github.com/vuejs/vue-c…

module.exports = {
  chainWebpack: config => {
    // 如果使用多页面打包,使用vue inspect --plugins查看html是否在结果数组中
    config.plugin("html").tap(args => {
      // 修复 Lazy loading routes Error
      args[0].chunksSortMode = "none";
      return args;
    });
  }
};

✅ 添加别名 alias

const path = require("path");
const resolve = dir => path.join(__dirname, dir);
const IS_PROD = ["production", "prod"].includes(process.env.NODE_ENV);

module.exports = {
  chainWebpack: config => {
    // 添加别名
    config.resolve.alias
      .set("vue$", "vue/dist/vue.esm.js")
      .set("@", resolve("src"))
      .set("@assets", resolve("src/assets"))
      .set("@scss", resolve("src/assets/scss"))
      .set("@components", resolve("src/components"))
      .set("@plugins", resolve("src/plugins"))
      .set("@views", resolve("src/views"))
      .set("@router", resolve("src/router"))
      .set("@store", resolve("src/store"))
      .set("@layouts", resolve("src/layouts"))
      .set("@static", resolve("src/static"));
  }
};

✅ 压缩图片

npm i -D image-webpack-loader

 在某些版本的 OSX 上安装可能会因缺少 libpng 依赖项而引发错误。可以通过安装最新版本的 libpng 来解决。

brew install libpng
module.exports = {
 chainWebpack: config => {
   if (IS_PROD) {
     config.module
       .rule("images")
       .use("image-webpack-loader")
       .loader("image-webpack-loader")
       .options({
         mozjpeg: { progressive: true, quality: 65 },
         optipng: { enabled: false },
         pngquant: { quality: [0.65, 0.9], speed: 4 },
         gifsicle: { interlaced: false }
         // webp: { quality: 75 }
       });
   }
 }
};

✅ 自动生成雪碧图

  默认 src/assets/icons 中存放需要生成雪碧图的 png 文件。首次运行 npm run serve/build 会生成雪碧图,并在跟目录生成 icons.json 文件。再次运行命令时,会对比 icons 目录内文件与 icons.json 的匹配关系,确定是否需要再次执行 webpack-spritesmith 插件。

npm i -D webpack-spritesmith
let has_sprite = true;
let files = [];
const icons = {};

try {
fs.statSync(resolve("./src/assets/icons"));
files = fs.readdirSync(resolve("./src/assets/icons"));
files.forEach(item => {
 let filename = item.toLocaleLowerCase().replace(/_/g, "-");
 icons[filename] = true;
});

} catch (error) {
fs.mkdirSync(resolve("./src/assets/icons"));
}

if (!files.length) {
has_sprite = false;
} else {
try {
 let iconsObj = fs.readFileSync(resolve("./icons.json"), "utf8");
 iconsObj = JSON.parse(iconsObj);
 has_sprite = files.some(item => {
   let filename = item.toLocaleLowerCase().replace(/_/g, "-");
   return !iconsObj[filename];
 });
 if (has_sprite) {
   fs.writeFileSync(resolve("./icons.json"), JSON.stringify(icons, null, 2));
 }
} catch (error) {
 fs.writeFileSync(resolve("./icons.json"), JSON.stringify(icons, null, 2));
 has_sprite = true;
}
}

// 雪碧图样式处理模板
const SpritesmithTemplate = function(data) {
// pc
let icons = {};
let tpl = `.ico { 
display: inline-block; 
background-image: url(${data.sprites[0].image}); 
background-size: ${data.spritesheet.width}px ${data.spritesheet.height}px; 
}`;

data.sprites.forEach(sprite => {
 const name = "" + sprite.name.toLocaleLowerCase().replace(/_/g, "-");
 icons[`${name}.png`] = true;
 tpl = `${tpl} 
.ico-${name}{
width: ${sprite.width}px; 
height: ${sprite.height}px; 
background-position: ${sprite.offset_x}px ${sprite.offset_y}px;
}
`;
});
return tpl;
};

module.exports = {
configureWebpack: config => {
 const plugins = [];
 if (has_sprite) {
   plugins.push(
     new SpritesmithPlugin({
       src: {
         cwd: path.resolve(__dirname, "./src/assets/icons/"), // 图标根路径
         glob: "**/*.png" // 匹配任意 png 图标
       },
       target: {
         image: path.resolve(__dirname, "./src/assets/images/sprites.png"), // 生成雪碧图目标路径与名称
         // 设置生成CSS背景及其定位的文件或方式
         css: [
           [
             path.resolve(__dirname, "./src/assets/scss/sprites.scss"),
             {
               format: "function_based_template"
             }
           ]
         ]
       },
       customTemplates: {
         function_based_template: SpritesmithTemplate
       },
       apiOptions: {
         cssImageRef: "../images/sprites.png" // css文件中引用雪碧图的相对位置路径配置
       },
       spritesmithOptions: {
         padding: 2
       }
     })
   );
 }

 config.plugins = [...config.plugins, ...plugins];
}
};

✅ SVG 转 font 字体

npm i -D svgtofont

  根目录新增 scripts 目录,并新建 svg2font.js 文件:

const svgtofont = require("svgtofont");
const path = require("path");
const pkg = require("../package.json");

svgtofont({
src: path.resolve(process.cwd(), "src/assets/svg"), // svg 图标目录路径
dist: path.resolve(process.cwd(), "src/assets/fonts"), // 输出到指定目录中
fontName: "icon", // 设置字体名称
css: true, // 生成字体文件
startNumber: 20000, // unicode起始编号
svgicons2svgfont: {
  fontHeight: 1000,
  normalize: true
},
// website = null, 没有演示html文件
website: {
  title: "icon",
  logo: "",
  version: pkg.version,
  meta: {
    description: "",
    keywords: ""
  },
  description: ``,
  links: [
    {
      title: "Font Class",
      url: "index.html"
    },
    {
      title: "Unicode",
      url: "unicode.html"
    }
  ],
  footerInfo: ``
}
}).then(() => {
console.log("done!");
});

添加 package.json scripts 配置

"prebuild": "npm run font",
"font": "node scripts/svg2font.js",

  执行:

npm run font

✅ 使用 SVG 组件

npm i -D svg-sprite-loader

  新增 SvgIcon 组件。

<template>
  <svg class="svg-icon"
       aria-hidden="true">
    <use :xlink:href="iconName" />
  </svg>
</template>

<script>
export default {
  name: 'SvgIcon',
  props: {
    iconClass: {
      type: String,
      required: true
    }
  },
  computed: {
    iconName() {
      return `#icon-${this.iconClass}`
    }
  }
}
</script>

<style scoped>
.svg-icon {
  width: 1em;
  height: 1em;
  vertical-align: -0.15em;
  fill: currentColor;
  overflow: hidden;
}
</style>

  在 src 文件夹中创建 icons 文件夹。icons 文件夹中新增 svg 文件夹(用来存放 svg 文件)与 index.js 文件:

import SvgIcon from "@/components/SvgIcon";
import Vue from "vue";

// 注册到全局
Vue.component("svg-icon", SvgIcon);

const requireAll = requireContext => requireContext.keys().map(requireContext);
const req = require.context("./svg", false, /\.svg$/);
requireAll(req);

  在 main.js 中导入 icons/index.js

import "@/icons";

  修改 vue.config.js

const path = require("path");
const resolve = dir => path.join(__dirname, dir);

module.exports = {
  chainWebpack: config => {
    const svgRule = config.module.rule("svg");
    svgRule.uses.clear();
    svgRule.exclude.add(/node_modules/);
    svgRule
      .test(/\.svg$/)
      .use("svg-sprite-loader")
      .loader("svg-sprite-loader")
      .options({
        symbolId: "icon-[name]"
      });

    const imagesRule = config.module.rule("images");
    imagesRule.exclude.add(resolve("src/icons"));
    config.module.rule("images").test(/\.(png|jpe?g|gif|svg)(\?.*)?$/);
  }
};

✅ 去除多余无效的 css

 注:谨慎使用。可能出现各种样式丢失现象。

  • 方案一:@fullhuman/postcss-purgecss
npm i -D postcss-import @fullhuman/postcss-purgecss

  更新 postcss.config.js

const autoprefixer = require("autoprefixer");
const postcssImport = require("postcss-import");
const purgecss = require("@fullhuman/postcss-purgecss");
const IS_PROD = ["production", "prod"].includes(process.env.NODE_ENV);
let plugins = [];
if (IS_PROD) {
 plugins.push(postcssImport);
 plugins.push(
   purgecss({
     content: [
       "./layouts/**/*.vue",
       "./components/**/*.vue",
       "./pages/**/*.vue"
     ],
     extractors: [
       {
         extractor: class Extractor {
           static extract(content) {
             const validSection = content.replace(
               /<style([\s\S]*?)<\/style>+/gim,
               ""
             );
             return (
               validSection.match(/[A-Za-z0-9-_/:]*[A-Za-z0-9-_/]+/g) || []
             );
           }
         },
         extensions: ["html", "vue"]
       }
     ],
     whitelist: ["html", "body"],
     whitelistPatterns: [
       /el-.*/,
       /-(leave|enter|appear)(|-(to|from|active))$/,
       /^(?!cursor-move).+-move$/,
       /^router-link(|-exact)-active$/
     ],
     whitelistPatternsChildren: [/^token/, /^pre/, /^code/]
   })
 );
}
module.exports = {
 plugins: [...plugins, autoprefixer]
};

* 方案二:purgecss-webpack-plugin

npm i -D glob-all purgecss-webpack-plugin



const path = require("path"); const glob = require("glob-all"); const PurgecssPlugin = require("purgecss-webpack-plugin"); const resolve = dir => path.join(__dirname, dir); const IS_PROD = ["production", "prod"].includes(process.env.NODE_ENV);

module.exports = { configureWebpack: config => { const plugins = []; if (IS_PROD) { plugins.push( new PurgecssPlugin({ paths: glob.sync([resolve("./**/.vue")]), extractors: [ { extractor: class Extractor { static extract(content) { const validSection = content.replace( /<style([\s\S]?)</style>+/gim, "" ); return ( validSection.match(/[A-Za-z0-9-/:]*[A-Za-z0-9-/]+/g) || [] ); } }, extensions: ["html", "vue"] } ], whitelist: ["html", "body"], whitelistPatterns: [ /el-.*/, /-(leave|enter|appear)(|-(to|from|active))/,/(?!cursormove).+move/, /^(?!cursor-move).+-move/, /^router-link(|-exact)-active$/ ], whitelistPatternsChildren: [/^token/, /^pre/, /^code/] }) ); } config.plugins = [...config.plugins, ...plugins]; } };

# ✅ 添加打包分析

const BundleAnalyzerPlugin = require("webpack-bundle-analyzer") .BundleAnalyzerPlugin;

module.exports = { chainWebpack: config => { // 打包分析 if (IS_PROD) { config.plugin("webpack-report").use(BundleAnalyzerPlugin, [ { analyzerMode: "static" } ]); } } }