Skip to content

vite 是如何让浏览器识别 .vue 文件的

index.html 中请求了 main.js ,main.js 请求 app.vue 文件,vite 起的服务器会收到浏览器发起的资源请求,如果是要获取 vue 文件的话,会先将其进行 解析,编译成单纯的 js 代码,然后返回文件内容,并且将 content-type 设置成 text/javascript,使得浏览器按 js 的方式执行 vue 文件内容

同理可得,对于 import 的 css 文件来说,它也是将其直接设置成 js 脚本的形式来进行读取,主要是方便热更新、scopedCss 等 css Module 1. module.css 规范命名,表示需要开启 css 模块化 2. 将该文件下的所有类型进行一定的规则替换(.footer => .footer_abc_123) 3. 同时创建一个映射对象 { footer: "footer_abc_123" } 4. 将 module.css 内容替换成 js 脚本 5. 将创建的映射对象在脚本中进行默认导出

配置

typescript
import { path } from 'node:path'
import { defineConfig } from 'vite'

export default defineConfig({
    resolve: {
        alias: {
            '@': path.resove(__dirname, './src'),
        },
    },
    server: {
        proxy: {
            '/api': {
                target: 'http://localhost:3000',
                changeOrigin: true,
                rewrite: (path) => path.replace(/^\/api/, ''),
            },
        },
    },
    css: {
        modules: {
            // localsConvention // 配置对象的展示规则
            scopeBehaviour: 'global', // 是否启用 scoped
            generateScopedName: '[name]_[local]_[hash:5]',
            hashPrefix: 'hello',
            globalModulePaths: ['./a.module.css'], // 是 module 命名规范,但是该文件不想被 module 化
        },
        preprocessorOptions: {
            // 预编译器相关配置,假设使用到了 less,那么就可以新增下面的 less 配置对象,其最终会传到 lessc 中进行编译处理
            less: {
                math: 'alaways',
                globalVars: {
                    //  定义 less 全局变量,则 less 文件中可以直接使用而无需导入
                    mainColor: 'red',
                },
            },
        },
        devSourcemap: true, // 开启之后,css 也能找到源文件
        // postcss 除了这样配置,也可以通过 postcss.config.js 进行配置
        postcss: {
            // postcss 不同于 less 等预处理器,它是目标是完整的参与 css 的编译过程,也就是通过 plugins 的形式,可以无限的去支持想做的事情(原本是可以通过 less-plugins 去支持 less 文件的编译,但是每次 less 的升级导致该 plugin 也需要对应的升级,变得没有必要,就变成了预处理器先处理好,再将结果给到 postcss 进行更多的处理,比如降级,导致 postcss 有点”后处理器“的意思)
            plugins: [], // postcss-preset-env 安装将预设插件后,可以支持语法降级(预设包:相当于一个配套好的环境,包含了全部所需的插件)( 'importFrom': './src/assets/cssVars.css' 那些全局定义的 媒体查询、自定义属性、自定义选择、环境变量等等,不管是 css、 js、json、方法还是传递的对象都需要通过 importFrom 配置项具名导入进来。否则在 .vue 文件中使用的时候插件是找不到这些定义的内容的)
        },
    },
    build: {
        minify: true, // 是否去掉空格之类的
        rollupOptions: {
            // 配置 rollup 的构建策略
            output: {
                assetFileNames: '[hash].[name].[ext]',
                manualChunks(id: string) {
                    // 分包策略
                    if (id.includes('node_modules')) return 'vendor' // 像是 lodash 之类的包,非业务代码,直接打包生成固定的文件,避免 hash 变化每次都要重新下载
                },
            },
        },
        assetsInlineLimit: 2 * 1024, // 小于此阈值的导入或引用资源将内联为 base64 编码,以避免额外的 http 请求。设置为 0 可以完全禁用此项
        outDir: 'dist', // 构建输出目录
        assetsDir: 'assets', // 静态资源所在目录
        emptyOutDir: true, // 构建之前是否先清除之前的输出结果
    },
})

图片加载

javascript
import img from './a.svg?raw' // 直接读取文件内容
import img from './a.svg?url' // 对应的 url 地址

plugins 实现

plugins 的 hook 支持多种类型,如 buildStart 可以改写成以下形式,从而使得它的执行时机修改

javascript
buildStart: {
    sequential: true, // 按顺序的形式进行(默认为同步的形式,那么就无法阻止其他 hook 的执行)
        handler
}
alias
typescript
import { Plugin } from 'vite'
// 通过 config 钩子返回对应的 alias 配置,使得 import 的时候可以支持 alias 路径
function alias(): Plugin {
    return {
        name: 'ViteAlias',
        config: () => ({
            resolve: {
                alias: {
                    // fs.readdirSync 获取全部的文件
                    // fs.statSync 文件信息,判断是否是文件夹
                },
            },
        }),
    }
}
indexHtmlTransfer
typescript
import { Plugin } from 'vite'

function indexHtmlTransfer(options: {
    inject: {
        data: Record<string, any>
    }
}): Plugin {
    return {
        name: 'indexHtmlTransfer',
        transformIndexHtml: {
            enforce: 'pre', // 执行时机,这里需要先将 index.html 中的模板转换了,否则其它函数读取的时候,因为不认识会出现报错
            transform(html, ctx) {
                return html.replaceAll(/<%= title %>/, options.inject.data.title)
            },
        },
    }
}
MockJS
typescript
import { Plugin } from 'vite'

function MockJS(options: {
    inject: {
        data: Record<string, any>
    }
}): Plugin {
    return {
        name: 'MockJS',
        configureServer(server) {
            server.middlewares.use((req, res, next) => {
                // 自定义请求处理...
                if (req.url === '/api/user') {
                    // require mock/index.js,判断里面的配置有没有符合该路径的,然后将符合的数据进行响应
                    const data = {}
                    res.setHeader('content-type', 'application/json') // 避免乱码
                    res.end(JSON.stringify(data))
                } else {
                    next()
                }
            })
        },
    }
}

typescript 使用

vite-plugin-checker // 可以配置错误检查,当出现 ts 错误的时候,直接将异常输出到页面上(引起开发者重视并强制要求解决才能继续开发) tsc --noEmit && vite build // 配置该行可以先进行 ts 检测,完全通过后再执行构建命令

分包

对应项目应用到的静态文件进行抽离,避免重复的打包导致 hash 指纹改变,用户需要重新下载文件

javascript
import {defineConfig} from 'vite'
import vue from 'vue'

export default defineConfig({
	build: {
    rollupOptions:{
      // manualOptions: {
      //   '自定义文件名': ['lodash', 'vue']
			// }
			// or...
      manualOptions(id: string){
        if(id.includes('node_modules')) return 'vendor'
			}
		}
	}
})

打包结构控制

javascript
// js 文件放到 js/ 下,css 放到 css/ 下
import { defineConfig } from 'vite'
import vue from 'vue'

export default defineConfig({
    build: {
        rollupOptions: {
            output: {
                entryFileNames: 'js/[name]-[hash].js', // 针对 js 文件
                chunkFileNames: 'js/[name]-[hash].js', // 动态 js 文件(异步引入的)
                assetFileNames: '[ext]/[name]-[has].[ext]', // 其它文件
            },
        },
    },
})

Server.proxy

javascript
import { defineConfig } from 'vite'
import vue from 'vue'

export default defineConfig({
    server: {
        proxy: {
            '/proxy_go': {
                target: 'https://beta.2tianxin.com', // 决定域名
                changeOrigin: true,
                secure: true,
                rewrite: () => '/vueAdmin/mecord', // 决定访问路径,此处如果返回的 path 包含域名部分的话也会被抛弃
                cookieDomainRewrite: {
                    // 修改 setcookie 的 domain,此处是设置成 localhost,那么其他的第三方接口也能在请求的时候带上了
                    '*': '',
                },
                // 更加详细的修改请求配置
                // configure: (proxy) => {
                //     proxy.on('proxyReq', (proxyReq, req) => {
                //         if (req.headers.cookie) {
                //             proxyReq.setHeader(
                //                 'Cookie',
                //                 'x-a=b5c0e524116d4; table=550b05ef',
                //             )
                //         }
                //     })
                // },
            },
        },
    },
})

构建(库模式)

type LibraryFormats = 'es' | 'cjs' | 'umd' | 'iife' | 'system' 区别

前端库打包格式对比

在 vite 构建中,如果选择为 es lib 模式的话,那么为了保留 pure 注释信息,故不会去掉换行、空格,因为它认为 es 格式的工具是中间级别,会被其它工具引入,当其产物被其它项目引入并构建的时候(需要根据 pure 注释信息进行 tree shaking),才是最终的构建 app 产物;

格式全称特点
esECMAScript 模块标准的ES6模块格式,适合现代浏览器和打包工具
cjsCommonJSNode.js的模块系统,主要用于服务端JavaScript
umd通用模块定义兼容浏览器和Node.js,同时支持AMD和CommonJS
iife立即调用函数表达式自执行函数,为浏览器使用创建隔离作用域
systemSystemJS专为SystemJS模块加载器设计的格式,支持动态ES模块
markdown
核心区别

1. **运行环境**

    - `es`:现代浏览器(原生支持) + 打包工具
    - `cjs`:Node.js环境
    - `umd`:通用环境(浏览器+Node.js)
    - `iife`:仅限浏览器
    - `system`:SystemJS加载器环境

2. **模块加载方式**

    - `es`:静态import/export(ES6标准)
    - `cjs`:同步require()
    - `umd`:自动检测AMD/CommonJS环境
    - `iife`:无模块系统,使用全局作用域
    - `system`:动态模块加载

3. **Tree Shaking支持**

    - `es`格式支持最好
    - 其他格式支持有限或不支持

4. **典型使用场景**
    - `es`:现代前端项目/Vite/Rollup
    - `cjs`:Node.js库/工具
    - `umd`:需要同时支持浏览器和Node.js的库
    - `iife`:直接通过<script>标签引入的库
    - `system`:使用SystemJS加载器的项目

构建 web-components(vue)

ts
import { fileURLToPath } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import jsx from '@vitejs/plugin-vue-jsx'
import tailwindcss from '@tailwindcss/vite'

export default defineConfig({
    resolve: {
        alias: {
            '@': fileURLToPath(new URL('./src', import.meta.url)),
            '~': fileURLToPath(new URL('./', import.meta.url)),
        },
    },
    server: {
        port: 7402,
    },
    plugins: [vue(), jsx(), tailwindcss()],
    base: './',
    define: {
        'process.env': {
            NODE_ENV: 'production',
        },
    },
    build: {
        minify: true,
        lib: {
            entry: fileURLToPath(new URL(`./src/components/index.ts`, import.meta.url)),
            formats: ['iife'], // 基本就是一次性使用产物,所以使用 iife 格式
            name: 'entrypoint',
        },
        outDir: 'dist',
        sourcemap: false,
        emptyOutDir: true,
        copyPublicDir: true,
        rollupOptions: {
            output: {
                entryFileNames: 'entrypoint.js',
            },
        },
    },
})
ts
import { defineCustomElement } from 'vue'
import element from './component.ce.vue'

customElements.define('v-toast', defineCustomElement(element))
vue
<!-- ce.vue 结尾,那么 vite 会识别成 web-component 组件,构建的时候会将 css 也打包到 js 当中-->
<template>
    <div class="font-bold">Hi,this is a vue version web-component</div>
</template>

<style>
@import 'tailwindcss';
</style>
ts
// 挂载到 window 上,实现桥接通信
function useMitt() {
    const mitt = new EventTarget()
    mitt.addEventListener('some-event', () => {
        //  do something
    })
    mitt.dispatchEvent('some-event')
}