Skip to content

Vue项目构建优化配置 #54

@webplus

Description

@webplus

vue.config.js

const path = require('path');
const utils = require('./utils');
const swigLoader = require('swig-loader');
const SpriteLoaderPlugin = require('svg-sprite-loader/plugin');
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
const analyze = false;
// 注入配置
swigLoader.resourceQueryCustomizer(function(resourceQuery, templatepath) {
  resourceQuery.config = { env: utils.env };
  return resourceQuery;
});

const pages = utils.htmlPlugin();
const pageKeys = Object.keys(pages);

function resolve(dir) {
  return path.join(__dirname, dir);
}

utils.printEnvLog();

module.exports = function() {
  const config = {
    publicPath: `${utils.env.PUBLIC_PATH}`,
    outputDir: utils.outputDir(),
    lintOnSave: false,
    productionSourceMap: false,
    pages: pages,
    configureWebpack: {
      output: {
        filename: 'assets/js/[name].js?v=[hash:8]',
        chunkFilename: 'assets/js/[name].js?v=[hash:8]',
      },
      performance: {
        hints: utils.env.ENV === 'production' ? 'warning' : false,
        maxEntrypointSize: 300000,
        maxAssetSize: 300000,
      },
      plugins: [new SpriteLoaderPlugin()],
    },
    transpileDependencies: ['vue-echarts', 'resize-detector'],

    chainWebpack: (config) => {
      // 修复热更新失效
      config.resolve.symlinks(true);
      // 打包分析
      if (analyze) {
        config.plugin('webpack-report').use(BundleAnalyzerPlugin, [{ analyzerMode: 'static' }]);
      }
      // 别名
      config.resolve.alias
        .set('@', resolve('src'))
        .set('api', resolve('src/api'))
        .set('assets', resolve('src/assets'))
        .set('components', resolve('src/components'))
        .set('config', resolve('src/config'))
        .set('filters', resolve('src/filters'))
        .set('mixins', resolve('src/mixins'))
        .set('pages', resolve('src/pages'))
        .set('utils', resolve('src/utils'))
        .set('mock', resolve('src/mock'))
        .set('directives', resolve('src/directives'))
        .set('store', resolve('src/store'))
        .set('styles', resolve('src/styles'));

      config.module
        .rule('html')
        .test(/\.html$/)
        .use('swig-loader')
        .loader('swig-loader')
        .end();

      // 压缩图片
      config.module
        .rule('images')
        .use('image-webpack-loader')
        .loader('image-webpack-loader')
        .options({
          // mozjpeg: { progressive: true, quality: 65 },
          mozjpeg: { enabled: false, progressive: true, quality: 65 },
          optipng: { enabled: false },
          pngquant: { enabled: false, quality: [0.65, 0.9], speed: 4 },
          // pngquant: { quality: [0.65, 0.9], speed: 4 },
          gifsicle: { interlaced: false },
          webp: { enabled: false, quality: 75 },
        });

      config.module
        .rule('images')
        .use('url-loader')
        .loader('url-loader')
        .tap((options) => {
          options.limit = 10;
          options.fallback = {
            loader: 'file-loader',
            options: {
              name: 'assets/images/[name].[ext]?v=[hash:8]',
              publicPath: utils.env.PUBLIC_PATH,
            },
          };
          return options;
        })
        .end();

      // 单独处理assets/images/*.svg格式的文件的URL
      config.module
        .rule('svg')
        .exclude.add(resolve('src/assets/svg/icons'))
        .end()
        .use('file-loader')
        .loader('file-loader')
        .options({
          name: 'assets/svg/[name].[ext]?v=[hash:8]',
          publicPath: utils.env.PUBLIC_PATH,
        })
        .end();

      // set fonts
      config.module
        .rule('fonts')
        .test(/\.(woff2?|eot|ttf|otf)(\?.*)?$/)
        .use('url-loader')
        .loader('url-loader')
        .options({
          limit: 100,
          name: 'assets/fonts/[name].[ext]?v=[hash:8]',
          publicPath: utils.env.PUBLIC_PATH,
        })
        .end();

      // set svg-sprite-loader
      config.module
        .rule('svg')
        .exclude.add(resolve('src/assets/svg/icons'))
        .end();

      config.module
        .rule('icons')
        .test(/\.svg$/)
        .include.add(resolve('src/assets/svg/icons'))
        .end()
        .use('svg-sprite-loader')
        .loader('svg-sprite-loader')
        .options({
          symbolId: '[name]',
        })
        .end();

      // set preserveWhitespace
      config.module
        .rule('vue')
        .use('vue-loader')
        .loader('vue-loader')
        .tap((options) => {
          options.compilerOptions.preserveWhitespace = true;
          return options;
        })
        .end();

      // set chunks
      config.optimization.splitChunks({
        cacheGroups: {
          theme: {
            name: 'chunk-vue-ecology',
            test: /[\\/]node_modules\/_?(vue|vuex|vue-router)(@.*)?[\\/]/,
            chunks: 'all',
            priority: -1,
            enforce: true,
          },
          vendors: {
            name: 'chunk-vendors',
            priority: -10,
            chunks: 'initial',
            minChunks: 2,
            test: /[\\/]node_modules[\\/]/,
            enforce: true,
          },
          ...pageKeys.map((key) => ({
            name: `chunk-${key}-vendors`,
            priority: -11,
            chunks: (chunk) => chunk.name === key,
            test: /[\\/]node_modules[\\/]/,
            enforce: true,
          })),
          common: {
            name: 'chunk-common',
            priority: -20,
            chunks: 'initial',
            minChunks: 2,
            reuseExistingChunk: true,
            enforce: true,
          },
        },
      });

      // html插件
      console.log('\n\n');
      for (const name in pages) {
        config.plugins.delete(`preload-${name}`);
        config.plugins.delete(`prefetch-${name}`);
        config.plugin(`html-${name}`).tap((options) => {
          options[0].minify = false;
          console.log(options);
          return options;
        });
      }
      console.log('\n\n');

      config
        .when(
          process.env.NODE_ENV === 'development',
          // https://webpack.js.org/configuration/devtool/#development
          (config) => config.devtool('cheap-module-eval-source-map')
        )
        .end();
    },
    css: {
      extract: {
        filename: 'assets/css/[name].css?v=[hash:8]',
        chunkFilename: 'assets/css/sytle-[name].css?v=[hash:8]',
      },
      sourceMap: false,
      requireModuleExtension: true,
    },
    devServer: {
      overlay: {
        warnings: false,
        errors: true,
      },
      host: 'localhost',
      port: 8080,
      https: false,
      open: true,
      hotOnly: true,
      proxy: {
        api: {
          target: 'https://test-b-fat.pingan.com.cn',
          changeOrigin: true,
          pathRewrite: {
            '^/api': ''
          },
        }
      },
    },
  };
  return config;
};

utils.js

const glob = require('glob');
const chalk = require('chalk');
const RSB_API = process.env.VUE_APP_API_DOMAIN;
const CDN_DOMAIN = process.env.VUE_APP_CDN_DOMAIN;
const PUBLIC_PATH = process.env.VUE_APP_PUBLIC_PATH;
const APP_ENV = process.env.VUE_APP_ENV;
const ENV = process.env.NODE_ENV;

const TIME_STAMP = Date.now();

exports.env = {
  APP_ENV,
  RSB_API,
  CDN_DOMAIN,
  PUBLIC_PATH,
  TIME_STAMP,
  ENV,
};

exports.outputDir = function() {
  if (APP_ENV === 'production') {
    return './release/prd';
  }
  if (APP_ENV === 'staging') {
    return './release/stg';
  }
};

// 生成pages
exports.htmlPlugin = function() {
  const pages = {};
  glob.sync('./src/pages/**/main.js').forEach((filepath) => {
    const fileName = filepath.substring('./src/pages/'.length, filepath.indexOf('/main.js'));
    pages[fileName] = {
      entry: `src/pages/${fileName}/main.js`,
      template: filepath.split('/main.js')[0] + '/index.html',
      filename: `${fileName}.html`,
      chunks: [
        'chunk-vue-ecology',
        'chunk-vendors',
        `chunk-${fileName}-vendors`,
        'chunk-common',
        fileName,
      ],
    };
  });
  return pages;
};

exports.printEnvLog = function() {
  console.log(chalk.blue('Hello!', chalk.green(' The current environment:')));
  console.log(
    chalk.green(
      '\n NODE_ENV ' +
        chalk.red.underline.bold(`${process.env.NODE_ENV}`) +
        '\n APP_ENV ' +
        chalk.red.underline.bold(`${APP_ENV}`) +
        '\n RSB_API ' +
        chalk.red.underline.bold(`${RSB_API}`) +
        '\n PUBLIC_PATH ' +
        chalk.red.underline.bold(`${PUBLIC_PATH}`) +
        '\n CDN_DOMAIN ' +
        chalk.red.underline.bold(`${CDN_DOMAIN}`) +
        '\n'
    )
  );
};

exports.parseParams = function(data = {}) {
  try {
    var tempArr = [];
    for (var i in data) {
      var key = encodeURIComponent(i);
      var value = encodeURIComponent(data[i]);
      tempArr.push(key + '=' + value);
    }
    var urlParamsStr = tempArr.join('&');
    return urlParamsStr;
  } catch (err) {
    return '';
  }
};

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions