vite+vue3 多页面打包 及配置优化

2,487 阅读3分钟
多页面配置

目录结构

vite-vue3
│
├─ config
│   ├─ base.ts
│   └─ index.ts
├─ dist
├─ node_modules
├─ plugins
│   └─ inject-html-plugin.js
├─ public
├─ src
│   ├─ assets
│   ├─ components
│   ├─ pages
│   │    ├─ simpleTest
│   │    │    ├─ api
│   │    │    ├─ assets
│   │    │    ├─ components
│   │    │    ├─ router
│   │    │    ├─ stores
│   │    │    ├─ views
│   │    │    ├─ App.vue
│   │    │    ├─ index.html
│   │    │    └─ main.ts
│   │    └─ index.html
│   ├─ utils
│   └─ global.d.ts
│
└─ vite.config.ts

基础配置 base.ts

const path = require('path');

export default {
  // 多页配置
  isMultiplePage: true,
  // 文件目录
  assetsRoot: path.resolve(__dirname, '../src'),
  // 生成目录
  buildRoot: path.resolve(__dirname, '../dist'),
  // 生成路径
  publicPath: '/',
  //	开启 includeProject
  isInclude: true,
  //	会监听的项目
  includeProject: [
    /simpleTest/
  ],
  // 本地开发端口
  port: 8007,
  // 本地开发代理
  proxy: {}
}

build配置 index.ts

const
	path = require('path'),
	Glob = require('glob').Glob,
	base = require('./base');
	const fs = require('fs');

const { EXCLUDED_BUILD_PAGE_REGEXPS=[], includeProject, isInclude, assetsRoot, buildRoot} = base.default;
function getPath(...args: string[]) {
  return path.join(assetsRoot, ...args);
}

// build
const build = {
  outDir: buildRoot,// 指定输出路径(相对于 项目根目录)
  sourcemap: false, // 构建后是否生成 source map 文件
  chunkSizeWarningLimit: 1500, // 规定触发警告的 chunk(文件块) 大小
  minify: 'esbuild',
  rollupOptions: {  // 自定义底层的 Rollup 打包配置
    input: getEntrySetting(), 
    output: {
      entryFileNames: "assets/js/[name]-[hash].js",
      chunkFileNames: "assets/js/[name]-[hash].js",
      assetFileNames: "assets/[ext]/[name]-[hash].[ext]",
      compact: true,
      // 文档 https://cn.rollupjs.org/configuration-options/#output-manualchunks
      manualChunks: (id: string) => {
        if(id.includes("node_modules")) {
          // return id.toString().split('node_modules/')[1].split('/')[0].toString(); // 拆分多个vendors
          return 'vendor'; // 合成一个vendors
        }
      }
    }
  },
  emptyOutDir: true,
  target: 'es2015',
}

// getEntrySetting  处理多个入口文件
function getEntrySetting() {
  const result: { [x: string]: any; } = {};

  new Glob('!(_)*/**/!(_)*.html', {
    cwd: getPath('pages'),
    sync: true
  }).found.forEach((file: string) => {
    const pageName = file.split('/').slice(0, -1).join('/')
    let entryUrl;

    if(isInclude){
      if (includeProject.filter((regexp: { test: (arg0: any) => any; }) => regexp.test(pageName)).length == 0) return;
    }else{
      if (EXCLUDED_BUILD_PAGE_REGEXPS.filter((regexp: { test: (arg0: any) => any; }) => regexp.test(pageName)).length > 0) return;
    }
    const tempUrl = `src/pages/${pageName}`
    const url = path.join(process.cwd(),tempUrl,'index.html');

    if(fs.existsSync(url)){ //增加ts入口
      entryUrl = getPath('pages', pageName, 'index.html');
    }else{
      entryUrl = getPath('pages', pageName, 'index.html');
    }

    result[pageName] = entryUrl;
  });

  return result;
}

const css = {
  postcss: {
    plugins: [
      autoprefixer({
        overrideBrowserslist: ['Android 4.1', 'iOS 7.1', 'Chrome > 31', 'ff > 31', 'ie >= 8'],
      }),
      // 兼容pc,不至于过大 a, /*no*/ 加这个不好使,
      // b, 也不好使   https://github.com/gaofant101/blog/issues/15
      // c, 使用selectorBlackList解决
      postCssPxToRem({
        // 自适应,px>rem转换
        rootValue: 37.5, // 75表示750设计稿,37.5表示375设计稿
        propList: ['*'], // 需要转换的属性,这里选择全部都进行转换
        selectorBlackList: ['norem'], // 过滤掉norem-开头的class,不进行rem转换
      }),
      postCssRTLCSS(), // 参考 https://juejin.cn/post/6989055383486758919
    ],
  },
  preprocessorOptions: {
    less: {
      // 一些配置项
    }
  }
}

const server = {
  host: '0.0.0.0',      // 指定服务器应该监听哪个 IP 地址
  port: base.default.port,               // 端口
  strictPort: false, // 若端口已被占用,尝试下移一格端口
  open: false,
  proxy: base.default.proxy,
}

export default {
  entry: getEntrySetting(),
  build: build,
  baseConfig: base.default,
  devServer: server,
  css: css
};

vite.config.ts

import { fileURLToPath, URL } from 'node:url'
import { defineConfig, loadEnv } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'
import eslintPlugin from 'vite-plugin-eslint' //导入包
import config from './config/index'
import legacy from '@vitejs/plugin-legacy'

// 代码体积优化  https://blog.csdn.net/qq_41581588/article/details/132023477
// 代码体积分析
import { visualizer } from 'rollup-plugin-visualizer'



// https://vitejs.dev/config/
export default defineConfig(({command, mode}) => {
  const env = loadEnv(mode, process.cwd())
  // console.log('env', env)
  return {
    // root: 'src/pages', // 不能改成这个读取.env 文件有问题。import.meta.env   访问需加 /src/pages/xxx
    base: config.baseConfig.publicPath,
    define: { 'process.env': {
      ...env
    } },
    plugins: [
      visualizer({ open: true }),
      vue(),
      vueJsx(),
      // 增加下面的配置项,这样在运行时就能检查eslint规范
      eslintPlugin({
        include: ['src/**/*.js', 'src/**/*.vue', 'src/*.js', 'src/*.vue'],
        exclude: ['./node_modules/**', './src/types/**'],
        cache: false
      }),
      // https://juejin.cn/post/7114586736201580575
      legacy({
        targets: ['defaults', 'chrome 42', 'safari 11.0', 'ios >= 9', 'android >= 4.2'],
        additionalLegacyPolyfills: ['regenerator-runtime/runtime'],
        renderLegacyChunks: true,
        modernPolyfills: ['es.global-this'], // 解决浏览器端 globalThis is not defined 报错
        polyfills: [
          'es.symbol',
          'es.promise',
          'es.promise.finally',
          'es/map',
          'es/set',
          'es.array.filter',
          'es.array.for-each',
          'es.array.flat-map',
          'es.object.define-properties',
          'es.object.define-property',
          'es.object.get-own-property-descriptor',
          'es.object.get-own-property-descriptors',
          'es.object.keys',
          'es.object.to-string',
          'web.dom-collections.for-each',
          'esnext.global-this',
          'esnext.string.match-all'
        ]
      }),
    ],
    resolve: {
      alias: {
        '@': fileURLToPath(new URL('./src', import.meta.url))
      }
    },
    css: config.css,
    server: config.devServer,
    build: {
      ...config.build
    }
  }
})

优化

参考: mp.weixin.qq.com/s/GYtd3k0JX…

将公共文件提取出来,cdn引入

// 第一步:提取
import externalGlobals from 'rollup-plugin-external-globals'//CDN加载

build:{
   rollupOptions: {
        external: ['vue', 'vue-router',, 'xxx-base-common'],
        plugins: [
          externalGlobals({
            vue: 'Vue',
            'vue-router': 'VueRouter',
            'xxx-base-common': 'xxxBaseCommon'
          })
        ]
    }
}

// 第二步: 引入
// 自定义插件,导入cdn  transformIndexHtml(html)  根据标签直接替换
// inject-html-plugin.js
export default function injectHtmlPlugin(options) {
  return {
    name: 'inject-html-plugin',
    transformIndexHtml(html) {
      html = html.replace(
        '<vuescript></vuescript>',
        `
        <script src="https://cdn.jsdelivr.net/npm/vue@3.3.4/dist/vue.global.min.js"></script>
      `
      );
      html = html.replace(
        '<vuerouterscript></vuerouterscript>',
        `
        <script src="https://unpkg.com/vue-router@4.2.4/dist/vue-router.global.js"></script>
      `
      );
      return html;
    }
  };
}

// 使用
import injectHtmlPlugin from './plugins/inject-html-plugin.js';
plugins: [
    injectHtmlPlugin(),
]