vue file configuration

vue-cli4 comprehensive configuration

  Detailed and comprehensive vue-cli4 configuration information. Covers most of the configuration requirements in the development process using vue-cli.

  It is not recommended to use it directly as a template. I hope you can follow this tutorial to configure it as needed, or copy vue.config.js to add or delete configurations, and install the required dependencies yourself.

Sample image

Configure multiple environment variables

  Select different environments by adding –mode xxx to the scripts configuration item in package.json

  Only variables starting with VUE_APP will be statically embedded into the client-side package by webpack.DefinePlugin, and can be accessed in the code through process.env.VUE_APP_BASE_API

  NODE_ENV and BASE_URL are two special variables that are always available in the code

Configuration

  Create new .env, .env.production, .env.analyz and other files in the project root directory

  • .env

  serve default local development environment configuration

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

  build default environment configuration (official server)

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

ACCESS_KEY_ID = "xxxxxxxxxxxxx"
ACCESS_KEY_SECRET = "xxxxxxxxxxxxx"
REGION = "oss-cn-hangzhou"
BUCKET = "staven-prod"
PREFIX = "staven-blog"
  • .env.crm

  Customized build environment configuration (pre-release server)

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;

  Modify package.json

"scripts": {
  "build": "vue-cli-service build",
  "crm": "vue-cli-service build --mode crm"
}
Use environment variables
<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>

Configure basic 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: {
    
    }
};

Configure proxy proxy to solve cross-domain problems

  Assume that the mock interface is https://www.easy-mock.com/mock/5bc75b55dc36971c160cad1b/sheets/1

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": "/"
        }
      }
    }
  }
};

  access

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

Fix HMR (hot update) failure

  If the hot update fails, do the following:

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

修复 Lazy loading routes Error: Cyclic dependency https://github.com/vuejs/vue-cli/issues/1669

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

Add alias 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"));
  }
};

Compress Pictures

npm i -D image-webpack-loader

  Installation on some versions of OSX may throw errors due to missing libpng dependency. This can be solved by installing the latest version of 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 }
        });
    }
  }
};

Automatically generate sprite images

  The png files that need to generate sprite images are stored in src/assets/icons by default. Running npm run serve/build for the first time will generate a sprite image and the icons.json file in the following directory. When the command is run again, the matching relationship between the files in the icons directory and icons.json will be compared to determine whether the webpack-spritesmith plug-in needs to be executed again.

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 to font font

npm i -D svgtofont

  Add a new scripts directory to the root directory and create a new svg2font.js file:

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!");
});

  Add package.json scripts configuration:

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

  implement:

npm run font

Sample image

Using SVG components

npm i -D svg-sprite-loader

  Added SvgIcon component.

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

  Create icons folder in src folder. Add the svg folder (used to store svg files) and index.js file in the icons folder:

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);

  Import icons/index.js in main.js

import "@/icons";

  Modify 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)(\?.*)?$/);
  }
};

Remove redundant and invalid css

  Note: Use with caution. Various styles may be lost.

  • Option 1: @fullhuman/postcss-purgecss
npm i -D postcss-import @fullhuman/postcss-purgecss

  Update 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]
};
  • Option 2: 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))$/,
            /^(?!cursor-move).+-move$/,
            /^router-link(|-exact)-active$/
          ],
          whitelistPatternsChildren: [/^token/, /^pre/, /^code/]
        })
      );
    }
    config.plugins = [...config.plugins, ...plugins];
  }
};

Add packaging analysis

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

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

Configure externals to introduce cdn resources

  Prevent certain imported packages from being packaged into bundles, and instead obtain these extended dependencies from the outside at runtime.

module.exports = {
    
    
  configureWebpack: config => {
    
    
    config.externals = {
    
    
      vue: "Vue",
      "element-ui": "ELEMENT",
      "vue-router": "VueRouter",
      vuex: "Vuex",
      axios: "axios"
    };
  },
  chainWebpack: config => {
    
    
    const cdn = {
    
    
      // 访问https://unpkg.com/element-ui/lib/theme-chalk/index.css获取最新版本
      css: ["//unpkg.com/[email protected]/lib/theme-chalk/index.css"],
      js: [
        "//unpkg.com/[email protected]/dist/vue.min.js", // 访问https://unpkg.com/vue/dist/vue.min.js获取最新版本
        "//unpkg.com/[email protected]/dist/vue-router.min.js",
        "//unpkg.com/[email protected]/dist/vuex.min.js",
        "//unpkg.com/[email protected]/dist/axios.min.js",
        "//unpkg.com/[email protected]/lib/index.js"
      ]
    };

    // 如果使用多页面打包,使用vue inspect --plugins查看html是否在结果数组中
    config.plugin("html").tap(args => {
    
    
      // html中添加cdn
      args[0].cdn = cdn;
      return args;
    });
  }
};

  add in html

<!-- 使用CDN的CSS文件 -->
<% for (var i in htmlWebpackPlugin.options.cdn &&
htmlWebpackPlugin.options.cdn.css) { %>
<link rel="stylesheet" href="<%= htmlWebpackPlugin.options.cdn.css[i] %>" />
<% } %>

<!-- 使用CDN的JS文件 -->
<% for (var i in htmlWebpackPlugin.options.cdn &&
htmlWebpackPlugin.options.cdn.js) { %>
<script
  type="text/javascript"
  src="<%= htmlWebpackPlugin.options.cdn.js[i] %>"
></script>
<% } %>

Multi-page packaging multi-page

  For multi-entry page packaging, it is recommended to create a new pages directory under the src directory to store multi-page modules.

  • pages.config.js

   Configure multi-page information. The src/main.js file corresponds to the main field, and other fields refer to the root path of pages. as follows:

module.exports = {
    
    
  'admin': {
    
    
    template: 'public/index.html',
    filename: 'admin.html',
    title: '后台管理',
  },
  'mobile': {
    
    
    template: 'public/index.html',
    filename: 'mobile.html',
    title: '移动端',
  },
  'pc/crm': {
    
    
    template: 'public/index.html',
    filename: 'pc-crm.html',
    title: '预发服务',
  }
}
  • view.config.js

  The pages field of vue.config.js provides configuration for multiple pages

const glob = require("glob");
const pagesInfo = require("./pages.config");
const pages = {
    
    };

glob.sync('./src/pages/**/main.js').forEach(entry => {
    
    
  let chunk = entry.match(/\.\/src\/pages\/(.*)\/main\.js/)[1];
  const curr = pagesInfo[chunk];
  if (curr) {
    
    
    pages[chunk] = {
    
    
      entry,
      ...curr,
      chunk: ["chunk-vendors", "chunk-common", chunk]
    }
  }
})

module.exports = {
    
    
  chainWebpack: config => {
    
    
    // 防止多页面打包卡顿
    config => config.plugins.delete("named-chunks");
    return config;
  },
  pages
};

  If you need to use CDN for multi-page packaging, use vue inspect --plugins to check whether the html is in the result array. In the above example, 'html-main', 'html-pages/admin', 'html-pages/mobile' exist in the plugins list, but there is no 'html'. Therefore config.plugin("html") can no longer be used.

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

const glob = require("glob");
const pagesInfo = require("./pages.config");
const pages = {
    
    };

glob.sync('./src/pages/**/main.js').forEach(entry => {
    
    
  let chunk = entry.match(/\.\/src\/pages\/(.*)\/main\.js/)[1];
  const curr = pagesInfo[chunk];
  if (curr) {
    
    
    pages[chunk] = {
    
    
      entry,
      ...curr,
      chunk: ["chunk-vendors", "chunk-common", chunk]
    }
  }
});

module.exports = {
    
    
  publicPath: IS_PROD ? process.env.VUE_APP_PUBLIC_PATH : "./", //
  configureWebpack: config => {
    
    
    config.externals = {
    
    
      vue: "Vue",
      "element-ui": "ELEMENT",
      "vue-router": "VueRouter",
      vuex: "Vuex",
      axios: "axios"
    };
  },
  chainWebpack: config => {
    
    
    const cdn = {
    
    
      // 访问https://unpkg.com/element-ui/lib/theme-chalk/index.css获取最新版本
      css: ["//unpkg.com/[email protected]/lib/theme-chalk/index.css"],
      js: [
        "//unpkg.com/[email protected]/dist/vue.min.js", // 访问https://unpkg.com/vue/dist/vue.min.js获取最新版本
        "//unpkg.com/[email protected]/dist/vue-router.min.js",
        "//unpkg.com/[email protected]/dist/vuex.min.js",
        "//unpkg.com/[email protected]/dist/axios.min.js",
        "//unpkg.com/[email protected]/lib/index.js"
      ]
    };

    // 防止多页面打包卡顿
    config => config.plugins.delete("named-chunks");

    // 多页面cdn添加
    Object.keys(pagesInfo).forEach(page => {
    
    
      config.plugin(`html-${
      
      page}`).tap(args => {
    
    
        // html中添加cdn
        args[0].cdn = cdn;

        // 修复 Lazy loading routes Error
        args[0].chunksSortMode = "none";
        return args;
      });
    });
    return config;
  },
  pages
};

Delete moment language pack

  To delete moment language packages other than the zh-cn Chinese package, there is no need to manually introduce the zh-cn language package in the code.

const webpack = require("webpack");

module.exports = {
    
    
  chainWebpack: config => {
    
    
    config
      .plugin("ignore")
      .use(
        new webpack.ContextReplacementPlugin(/moment[/\\]locale$/, /zh-cn$/)
      );

    return config;
  }
};

Remove console.log

Method 1: Use babel-plugin-transform-remove-console plug-in
npm i -D babel-plugin-transform-remove-console

Configure in babel.config.js

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

const plugins = [];
if (IS_PROD) {
  plugins.push("transform-remove-console");
}

module.exports = {
  presets: ["@vue/app", { useBuiltIns: "entry" }],
  plugins
};
Method Two:
const UglifyJsPlugin = require("uglifyjs-webpack-plugin");
module.exports = {
    
    
  configureWebpack: config => {
    
    
    if (IS_PROD) {
    
    
      const plugins = [];
      plugins.push(
        new UglifyJsPlugin({
    
    
          uglifyOptions: {
    
    
            compress: {
    
    
              warnings: false,
              drop_console: true,
              drop_debugger: false,
              pure_funcs: ["console.log"] //移除console
            }
          },
          sourceMap: false,
          parallel: true
        })
      );
      config.plugins = [...config.plugins, ...plugins];
    }
  }
};

  If you use uglifyjs-webpack-plugin, an error will be reported. There may be some dependencies in node_modules that require babel translation.

The transpileDependencies configuration   of vue-cli defaults to [], and babel-loader will ignore all files in node_modules. If you want to explicitly translate a dependency through Babel, you can list it in this option. Configure third-party libraries that need to be translated.

Use splitChunks to package third-party modules separately

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

module.exports = {
    
    
  configureWebpack: config => {
    
    
    if (IS_PROD) {
    
    
      config.optimization = {
    
    
        splitChunks: {
    
    
          cacheGroups: {
    
    
            common: {
    
    
              name: "chunk-common",
              chunks: "initial",
              minChunks: 2,
              maxInitialRequests: 5,
              minSize: 0,
              priority: 1,
              reuseExistingChunk: true,
              enforce: true
            },
            vendors: {
    
    
              name: "chunk-vendors",
              test: /[\\/]node_modules[\\/]/,
              chunks: "initial",
              priority: 2,
              reuseExistingChunk: true,
              enforce: true
            },
            elementUI: {
    
    
              name: "chunk-elementui",
              test: /[\\/]node_modules[\\/]element-ui[\\/]/,
              chunks: "all",
              priority: 3,
              reuseExistingChunk: true,
              enforce: true
            },
            echarts: {
    
    
              name: "chunk-echarts",
              test: /[\\/]node_modules[\\/](vue-)?echarts[\\/]/,
              chunks: "all",
              priority: 4,
              reuseExistingChunk: true,
              enforce: true
            }
          }
        }
      };
    }
  },
  chainWebpack: config => {
    
    
    if (IS_PROD) {
    
    
      config.optimization.delete("splitChunks");
    }
    return config;
  }
};

Enable gzip compression

npm i -D compression-webpack-plugin
const CompressionWebpackPlugin = require("compression-webpack-plugin");

const IS_PROD = ["production", "prod"].includes(process.env.NODE_ENV);
const productionGzipExtensions = /\.(js|css|json|txt|html|ico|svg)(\?.*)?$/i;

module.exports = {
    
    
  configureWebpack: config => {
    
    
    const plugins = [];
    if (IS_PROD) {
    
    
      plugins.push(
        new CompressionWebpackPlugin({
    
    
          filename: "[path].gz[query]",
          algorithm: "gzip",
          test: productionGzipExtensions,
          threshold: 10240,
          minRatio: 0.8
        })
      );
    }
    config.plugins = [...config.plugins, ...plugins];
  }
};

  You can also enable Zopfli compression, which has a better experience than gzip. For details, see https://webpack.js.org/plugins/compression-webpack-plugin

npm i -D @gfx/zopfli brotli-webpack-plugin
const CompressionWebpackPlugin = require("compression-webpack-plugin");
const zopfli = require("@gfx/zopfli");
const BrotliPlugin = require("brotli-webpack-plugin");

const IS_PROD = ["production", "prod"].includes(process.env.NODE_ENV);
const productionGzipExtensions = /\.(js|css|json|txt|html|ico|svg)(\?.*)?$/i;

module.exports = {
    
    
  configureWebpack: config => {
    
    
    const plugins = [];
    if (IS_PROD) {
    
    
      plugins.push(
        new CompressionWebpackPlugin({
    
    
          algorithm(input, compressionOptions, callback) {
    
    
            return zopfli.gzip(input, compressionOptions, callback);
          },
          compressionOptions: {
    
    
            numiterations: 15
          },
          minRatio: 0.99,
          test: productionGzipExtensions
        })
      );
      plugins.push(
        new BrotliPlugin({
    
    
          test: productionGzipExtensions,
          minRatio: 0.99
        })
      );
    }
    config.plugins = [...config.plugins, ...plugins];
  }
};

Turn on stylelint to detect scss and css syntax

npm i -D stylelint stylelint-config-standard stylelint-config-prettier stylelint-webpack-plugin

Create stylelint.config.js in the folder, detailed configuration is here

module.exports = {
    
    
  ignoreFiles: ["**/*.js", "src/assets/css/element-variables.scss", "theme/"], 
  extends: ["stylelint-config-standard", "stylelint-config-prettier"],
  rules: {
    
    
    "no-empty-source": null,
    "at-rule-no-unknown": [
      true,
      {
    
    
        ignoreAtRules: ["extend"]
      }
    ]
  }
};

Enable webpack configuration

const StylelintPlugin = require("stylelint-webpack-plugin");

module.exports = {
    
    
  configureWebpack: config => {
    
    
    const plugins = [];
    if (IS_DEV) {
    
    
      plugins.push(
        new StylelintPlugin({
    
    
          files: ["src/**/*.vue", "src/assets/**/*.scss"],
          fix: true //打开自动修复(谨慎使用!注意上面的配置不要加入js或html文件,会发生问题,js文件请手动修复)
        })
      );
    }
    config.plugins = [...config.plugins, ...plugins];
  }
}

Provide global styles and global variables for sass

You can mount the configuration information in the environment variable by Vue.prototype. src = process . env . VUEAPPPUBLICPATH;   in main.js , and then use src = process.env.VUE_APP_PUBLIC_PATH; in js to mount the configuration information in the environment variable. Then use it in jssrc=process.env.VUEAPPPUBLICPATH;Mount the configuration information in the environment variable , and then use src to access it in js .

  In css, you can use injected sass variables to access configuration information in environment variables.

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

module.exports = {
    
    
  css: {
    
    
    extract: IS_PROD,
    sourceMap: false,
    loaderOptions: {
    
    
      scss: {
    
    
        // 向全局sass样式传入共享的全局变量, $src可以配置图片cdn前缀
        // 详情: https://cli.vuejs.org/guide/css.html#passing-options-to-pre-processor-loaders
        prependData: `
        @import "@scss/variables.scss";
        @import "@scss/mixins.scss";
        @import "@scss/function.scss";
        $src: "${
      
      process.env.VUE_APP_OSS_SRC}";
        `
      }
    }
  }
};

Referenced in scss

.home {
  background: url($src+"/images/500.png");
}

Provide global styles and global variables for less

npm i -D less less-loader

  Create a new variables.less file in the src/assets/less directory and define the global less variable

@primary-color: #1890ff;
@normal-color: #d9d9d9;
@text-color: #303753;

  Add the corresponding less configuration to vue.config.js.

const path = require('path')
const fs = require('fs')
const postcss = require('postcss')
const resolve = dir => path.resolve(__dirname, dir)

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

function getLessVaribles(fileUrl, list = {
     
     }) {
    
    
  if (!fs.existsSync(fileUrl)) return {
    
    };
  let lessFile = fs.readFileSync(fileUrl, 'utf8');
  return postcss.parse(lessFile).nodes.reduce((acc, curr) => {
    
    
    acc[`${
      
      curr.name.replace(/\:/, '')}`] = `${
      
      curr.params}`;
    return acc;
  }, list);
}

const modifyVars = getLessVaribles(resolve('./src/assets/less/variables.less'));

module.exports = {
    
    
  css: {
    
    
    extract: IS_PROD,
    // sourceMap: false,
    loaderOptions: {
    
    
      less: {
    
    
        modifyVars,
        javascriptEnabled: true,
      }
    }
  }
}

Provide global variables for stylus

npm i -D style-resources-loader
const path = require("path");
const resolve = dir => path.resolve(__dirname, dir);
const addStylusResource = rule => {
    
    
  rule
    .use("style-resouce")
    .loader("style-resources-loader")
    .options({
    
    
      patterns: [resolve("src/assets/stylus/variable.styl")]
    });
};
module.exports = {
    
    
  chainWebpack: config => {
    
    
    const types = ["vue-modules", "vue", "normal-modules", "normal"];
    types.forEach(type =>
      addStylusResource(config.module.rule("stylus").oneOf(type))
    );
  }
};

\prerenderprerender-spa-plugin

npm i -D prerender-spa-plugin
const PrerenderSpaPlugin = require("prerender-spa-plugin");
const path = require("path");
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 PrerenderSpaPlugin({
    
    
          staticDir: resolve("dist"),
          routes: ["/"],
          postProcess(ctx) {
    
    
            ctx.route = ctx.originalRoute;
            ctx.html = ctx.html.split(/>[\s]+</gim).join("><");
            if (ctx.route.endsWith(".html")) {
    
    
              ctx.outputPath = path.join(__dirname, "dist", ctx.route);
            }
            return ctx;
          },
          minify: {
    
    
            collapseBooleanAttributes: true,
            collapseWhitespace: true,
            decodeEntities: true,
            keepClosingSlash: true,
            sortAttributes: true
          },
          renderer: new PrerenderSpaPlugin.PuppeteerRenderer({
    
    
            // 需要注入一个值,这样就可以检测页面当前是否是预渲染的
            inject: {
    
    },
            headless: false,
            // 视图组件是在API请求获取所有必要数据后呈现的,因此我们在dom中存在“data view”属性后创建页面快照
            renderAfterDocumentEvent: "render-event"
          })
        })
      );
    }
    config.plugins = [...config.plugins, ...plugins];
  }
};

  Add document.dispatchEvent(new Event('render-event')) to mounted()

new Vue({
    
    
  router,
  store,
  render: h => h(App),
  mounted() {
    
    
    document.dispatchEvent(new Event("render-event"));
  }
}).$mount("#app");

Sample image

Add custom title, description, content to custom pre-rendered page
  • Delete the meta tags about description and content in public/index.html. Keep title tag

  • Configure router-config.js

module.exports = {
    
    
  "/": {
    
    
    title: "首页",
    keywords: "首页关键词",
    description: "这是首页描述"
  },
  "/about.html": {
    
    
    title: "关于我们",
    keywords: "关于我们页面关键词",
    description: "关于我们页面关键词描述"
  }
};
  • view.config.js
const path = require("path");
const PrerenderSpaPlugin = require("prerender-spa-plugin");
const routesConfig = require("./router-config");
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 PrerenderSpaPlugin({
    
    
          staticDir: resolve("dist"),
          routes: Object.keys(routesConfig),
          postProcess(ctx) {
    
    
            ctx.route = ctx.originalRoute;
            ctx.html = ctx.html.split(/>[\s]+</gim).join("><");
            ctx.html = ctx.html.replace(
              /<title>(.*?)<\/title>/gi,
              `<title>${
      
      
                routesConfig[ctx.route].title
              }</title><meta name="keywords" content="${
      
      
                routesConfig[ctx.route].keywords
              }" /><meta name="description" content="${
      
      
                routesConfig[ctx.route].description
              }" />`
            );
            if (ctx.route.endsWith(".html")) {
    
    
              ctx.outputPath = path.join(__dirname, "dist", ctx.route);
            }
            return ctx;
          },
          minify: {
    
    
            collapseBooleanAttributes: true,
            collapseWhitespace: true,
            decodeEntities: true,
            keepClosingSlash: true,
            sortAttributes: true
          },
          renderer: new PrerenderSpaPlugin.PuppeteerRenderer({
    
    
            // 需要注入一个值,这样就可以检测页面当前是否是预渲染的
            inject: {
    
    },
            headless: false,
            // 视图组件是在API请求获取所有必要数据后呈现的,因此我们在dom中存在“data view”属性后创建页面快照
            renderAfterDocumentEvent: "render-event"
          })
        })
      );
    }

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

Add IE compatibility

  Add in main.js

import 'core-js/stable'; 
import 'regenerator-runtime/runtime';

Configure babel.config.js

const plugins = [];

module.exports = {
    
    
  presets: [["@vue/app", {
    
     useBuiltIns: "entry" }]],
  plugins: plugins
};

Static resources are automatically packaged and uploaded to Alibaba oss and Huawei obs

  To enable file upload to ali oss, you need to change the publicPath to the ali oss resource url prefix, that is, modify VUE_APP_PUBLIC_PATH. For specific configuration, please refer to Alibaba oss plug-in webpack-oss and Huawei obs plug-in huawei-obs-plugin.

npm i -D webpack-oss
const AliOssPlugin = require("webpack-oss");
const IS_PROD = ["production", "prod"].includes(process.env.NODE_ENV);

const format = AliOssPlugin.getFormat();

module.exports = {
    
    
  publicPath: IS_PROD ? `${
      
      process.env.VUE_APP_PUBLIC_PATH}/${
      
      format}` : "./", // 默认'/',部署应用包时的基本 URL
  configureWebpack: config => {
    
    
    const plugins = [];

    if (IS_PROD) {
    
    
      plugins.push(
        new AliOssPlugin({
    
    
          accessKeyId: process.env.ACCESS_KEY_ID,
          accessKeySecret: process.env.ACCESS_KEY_SECRET,
          region: process.env.REGION,
          bucket: process.env.BUCKET,
          prefix: process.env.PREFIX,
          exclude: /.*\.html$/,
          format
        })
      );
    }
    config.plugins = [...config.plugins, ...plugins];
  }
};

full configuration

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

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

const glob = require('glob')
const pagesInfo = require('./pages.config')
const pages = {
    
    }

glob.sync('./src/pages/**/main.js').forEach(entry => {
    
    
  let chunk = entry.match(/\.\/src\/pages\/(.*)\/main\.js/)[1];
  const curr = pagesInfo[chunk];
  if (curr) {
    
    
    pages[chunk] = {
    
    
      entry,
      ...curr,
      chunk: ["chunk-vendors", "chunk-common", chunk]
    }
  }
})

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 = {
    
    
  publicPath: IS_PROD ? process.env.VUE_APP_PUBLIC_PATH : "./", // 默认'/',部署应用包时的基本 URL
  // outputDir: process.env.outputDir || 'dist', // 'dist', 生产环境构建文件的目录
  // assetsDir: "", // 相对于outputDir的静态资源(js、css、img、fonts)目录
  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.externals = {
    
    
      vue: "Vue",
      "element-ui": "ELEMENT",
      "vue-router": "VueRouter",
      vuex: "Vuex",
      axios: "axios"
    };

    config.plugins = [...config.plugins, ...plugins];
  },
  chainWebpack: config => {
    
    
    // 修复HMR
    config.resolve.symlinks(true);

    // config.plugins.delete('preload');
    // config.plugins.delete('prefetch');

    config
      .plugin("ignore")
      .use(
        new webpack.ContextReplacementPlugin(/moment[/\\]locale$/, /zh-cn$/)
      );

    // 添加别名
    config.resolve.alias
      .set("vue$", "vue/dist/vue.esm.js")
      .set("@", resolve("src"))
      .set("@apis", resolve("src/apis"))
      .set("@assets", resolve("src/assets"))
      .set("@scss", resolve("src/assets/scss"))
      .set("@components", resolve("src/components"))
      .set("@middlewares", resolve("src/middlewares"))
      .set("@mixins", resolve("src/mixins"))
      .set("@plugins", resolve("src/plugins"))
      .set("@router", resolve("src/router"))
      .set("@store", resolve("src/store"))
      .set("@utils", resolve("src/utils"))
      .set("@views", resolve("src/views"))
      .set("@layouts", resolve("src/layouts"));

    const cdn = {
    
    
      // 访问https://unpkg.com/element-ui/lib/theme-chalk/index.css获取最新版本
      css: ["//unpkg.com/[email protected]/lib/theme-chalk/index.css"],
      js: [
        "//unpkg.com/[email protected]/dist/vue.min.js", // 访问https://unpkg.com/vue/dist/vue.min.js获取最新版本
        "//unpkg.com/[email protected]/dist/vue-router.min.js",
        "//unpkg.com/[email protected]/dist/vuex.min.js",
        "//unpkg.com/[email protected]/dist/axios.min.js",
        "//unpkg.com/[email protected]/lib/index.js"
      ]
    };

    // 如果使用多页面打包,使用vue inspect --plugins查看html是否在结果数组中
    // config.plugin("html").tap(args => {
    
    
    //   // html中添加cdn
    //   args[0].cdn = cdn;

    //   // 修复 Lazy loading routes Error
    //   args[0].chunksSortMode = "none";
    //   return args;
    // });

    // 防止多页面打包卡顿
    config => config.plugins.delete('named-chunks')

    // 多页面cdn添加
    Object.keys(pagesInfo).forEach(page => {
    
    
      config.plugin(`html-${
      
      page}`).tap(args => {
    
    
        // html中添加cdn
        args[0].cdn = cdn;

        // 修复 Lazy loading routes Error
        args[0].chunksSortMode = "none";
        return args;
      });
    })

    if (IS_PROD) {
    
    
      // 压缩图片
      config.module
        .rule("images")
        .test(/\.(png|jpe?g|gif|svg)(\?.*)?$/)
        .use("image-webpack-loader")
        .loader("image-webpack-loader")
        .options({
    
    
          mozjpeg: {
    
     progressive: true, quality: 65 },
          optipng: {
    
     enabled: false },
          pngquant: {
    
     quality: [0.65, 0.90], speed: 4 },
          gifsicle: {
    
     interlaced: false }
        });

      // 打包分析
      config.plugin("webpack-report").use(BundleAnalyzerPlugin, [
        {
    
    
          analyzerMode: "static"
        }
      ]);
    }

    // 使用svg组件
    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)(\?.*)?$/);

    return config;
  },
  pages,
  css: {
    
    
    extract: IS_PROD,
    sourceMap: false,
    loaderOptions: {
    
    
      scss: {
    
    
        // 向全局sass样式传入共享的全局变量, $src可以配置图片cdn前缀
        // 详情: https://cli.vuejs.org/guide/css.html#passing-options-to-pre-processor-loaders
        prependData: `
          @import "@scss/variables.scss";
          @import "@scss/mixins.scss";
          @import "@scss/function.scss";
          $src: "${
      
      process.env.VUE_APP_BASE_API}";
          `
      }
    }
  },
  lintOnSave: false,
  runtimeCompiler: true, // 是否使用包含运行时编译器的 Vue 构建版本
  productionSourceMap: !IS_PROD, // 生产环境的 source map
  parallel: require("os").cpus().length > 1,
  pwa: {
    
    },
  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": "/"
        }
      }
    }
  }
};

Guess you like

Origin blog.csdn.net/weixin_44885062/article/details/120690703