Vitejs开源项目实践指南(二)

注意:本文以vue-pure-adminvue-vben-admin为例,进行vite配置入门介绍

Vitejs开源项目实践指南系列文章:

本篇预告,Vitejs开源项目实践指南(二),内容包括:

  • esbuild字段配置
  • 预构建配置optimizeDeps
  • 全局变量配置define
  • css及其预处理器配置
  • 其他一般配置项
  • 重要字段的类型源码,都放在附录知识里面折叠隐藏了,可点击展开查看

vitejs字段解析(实例讲解)

esbuild

esbuild主要用于开发环境(npm run dev)(比如依赖预构建、文件编译等),而生产环境(npm run build)使用的是rollup(在build.rollupOptions字段中配置)进行代码构建的,出现此类缘故是esbuild的生态还不够完备。

对于非JSX项目来说,在使用vue3+vite的项目中,esbuild目前可用于在构建生产代码之前删除项目中的debugger和console,对于console来说,可全部清除,也可以特定删除console的部分api(比如console.log、console.error、console.info等),用法如下所示:

const esbuildOptions: ESBuildOptions = {
  // 全部删除代码中的console 和debugger语句
  drop: ['console', 'debugger']
}

const esbuildOptions2: ESBuildOptions = {
  // 仅删除debugger语句和console.log、console.info,其他console不进行删除
  drop: ['debugger'],
  pure: ['console.log', 'console.info']
}

// 最后将esbuildOptions对象添加到上述userConfig的esbuild的值的位置即可

同时,你想使用其他代码压缩功能,vite还集成了js压缩工具库terser,使用build.terserOptions字段进行配置,配置选项如下所示:

// vite.config.ts
import type { Terser, ConfigEnv, UserConfig } from 'vite'

const terserOptions: Terser.MinifyOptions = {
  compress: {
    // 防止将infinite被压缩成 1/0 ,1/0将导致chrome性能问题
    keep_infinity: true,
    // 删除所有的console语句和debugger语句
    // 若想删除特定console语句,则不能使用drop_console,应该使用pure_funcs
    drop_console: true,
    drop_debugger: true,
    // 删除特定的console
    pure_funcs: ['console.log', 'console.info']
  }
}

export default ({command, mode}: ConfigEnv): UserConfig => {
  return {
    build: {
      terserOptions: terserOptions
    }
  }
} 

依赖预构建:在vue项目启动之前,因为vite的开发服务器会将所有代码都视为原生ESM模块,因此vite必须先将用CommonJS、UMD模块发布的依赖项(devDependencies、dependencies)转为ESM模块;同时vite将含有大量的模块依赖关系(通过import进行导入)的模块(比如lodash-es库,内置了大量的常用函数模块)转成单个模块,减少http请求数量,提升页面加载性能。这个过程仅在开发环境下执行。

附录知识: ESBuildOptions类型说明

ESBuildOptions类型说明:

export declare interface ESBuildOptions extends EsbuildTransformOptions {
include?: string | RegExp | string[] | RegExp[];
exclude?: string | RegExp | string[] | RegExp[];
jsxInject?: string;
/**
* This option is not respected. Use `build.minify` instead.
*/
minify?: never;
}
export interface TransformOptions extends CommonOptions {
tsconfigRaw?: string | {
compilerOptions?: {
alwaysStrict?: boolean,
importsNotUsedAsValues?: 'remove' | 'preserve' | 'error',
jsx?: 'react' | 'react-jsx' | 'react-jsxdev' | 'preserve',
jsxFactory?: string,
jsxFragmentFactory?: string,
jsxImportSource?: string,
preserveValueImports?: boolean,
target?: string,
useDefineForClassFields?: boolean,
},
};
sourcefile?: string;
loader?: Loader;
banner?: string;
footer?: string;
}
interface CommonOptions {
/** Documentation: https://esbuild.github.io/api/#sourcemap */
sourcemap?: boolean | 'linked' | 'inline' | 'external' | 'both';
/** Documentation: https://esbuild.github.io/api/#legal-comments */
legalComments?: 'none' | 'inline' | 'eof' | 'linked' | 'external';
/** Documentation: https://esbuild.github.io/api/#source-root */
sourceRoot?: string;
/** Documentation: https://esbuild.github.io/api/#sources-content */
sourcesContent?: boolean;
/** Documentation: https://esbuild.github.io/api/#format */
format?: Format;
/** Documentation: https://esbuild.github.io/api/#global-name */
globalName?: string;
/** Documentation: https://esbuild.github.io/api/#target */
target?: string | string[];
/** Documentation: https://esbuild.github.io/api/#supported */
supported?: Record<string, boolean>;
/** Documentation: https://esbuild.github.io/api/#platform */
platform?: Platform;
/** Documentation: https://esbuild.github.io/api/#mangle-props */
mangleProps?: RegExp;
/** Documentation: https://esbuild.github.io/api/#mangle-props */
reserveProps?: RegExp;
/** Documentation: https://esbuild.github.io/api/#mangle-props */
mangleQuoted?: boolean;
/** Documentation: https://esbuild.github.io/api/#mangle-props */
mangleCache?: Record<string, string | false>;
/** Documentation: https://esbuild.github.io/api/#drop */
drop?: Drop[];
/** Documentation: https://esbuild.github.io/api/#minify */
minify?: boolean;
/** Documentation: https://esbuild.github.io/api/#minify */
minifyWhitespace?: boolean;
/** Documentation: https://esbuild.github.io/api/#minify */
minifyIdentifiers?: boolean;
/** Documentation: https://esbuild.github.io/api/#minify */
minifySyntax?: boolean;
/** Documentation: https://esbuild.github.io/api/#charset */
charset?: Charset;
/** Documentation: https://esbuild.github.io/api/#tree-shaking */
treeShaking?: boolean;
/** Documentation: https://esbuild.github.io/api/#ignore-annotations */
ignoreAnnotations?: boolean;
/** Documentation: https://esbuild.github.io/api/#jsx */
jsx?: 'transform' | 'preserve' | 'automatic';
/** Documentation: https://esbuild.github.io/api/#jsx-factory */
jsxFactory?: string;
/** Documentation: https://esbuild.github.io/api/#jsx-fragment */
jsxFragment?: string;
/** Documentation: https://esbuild.github.io/api/#jsx-import-source */
jsxImportSource?: string;
/** Documentation: https://esbuild.github.io/api/#jsx-development */
jsxDev?: boolean;
/** Documentation: https://esbuild.github.io/api/#jsx-side-effects */
jsxSideEffects?: boolean;
/** Documentation: https://esbuild.github.io/api/#define */
define?: { [key: string]: string };
/** Documentation: https://esbuild.github.io/api/#pure */
pure?: string[];
/** Documentation: https://esbuild.github.io/api/#keep-names */
keepNames?: boolean;
/** Documentation: https://esbuild.github.io/api/#color */
color?: boolean;
/** Documentation: https://esbuild.github.io/api/#log-level */
logLevel?: LogLevel;
/** Documentation: https://esbuild.github.io/api/#log-limit */
logLimit?: number;
/** Documentation: https://esbuild.github.io/api/#log-override */
logOverride?: Record<string, LogLevel>;
}
附录知识: Terser.MinifyOptions类型说明

Terser.MinifyOptions类型说明:

export interface MinifyOptions {
compress?: boolean | CompressOptions
ecma?: ECMA
ie8?: boolean
keep_classnames?: boolean | RegExp
keep_fnames?: boolean | RegExp
mangle?: boolean | MangleOptions
module?: boolean
nameCache?: object
format?: FormatOptions
/** @deprecated use format instead */
output?: FormatOptions
parse?: ParseOptions
safari10?: boolean
sourceMap?: boolean | SourceMapOptions
toplevel?: boolean
}
export interface CompressOptions {
arguments?: boolean
arrows?: boolean
booleans_as_integers?: boolean
booleans?: boolean
collapse_vars?: boolean
comparisons?: boolean
computed_props?: boolean
conditionals?: boolean
dead_code?: boolean
defaults?: boolean
directives?: boolean
drop_console?: boolean
drop_debugger?: boolean
ecma?: ECMA
evaluate?: boolean
expression?: boolean
global_defs?: object
hoist_funs?: boolean
hoist_props?: boolean
hoist_vars?: boolean
ie8?: boolean
if_return?: boolean
inline?: boolean | InlineFunctions
join_vars?: boolean
keep_classnames?: boolean | RegExp
keep_fargs?: boolean
keep_fnames?: boolean | RegExp
keep_infinity?: boolean
loops?: boolean
module?: boolean
negate_iife?: boolean
passes?: number
properties?: boolean
pure_funcs?: string[]
pure_getters?: boolean | 'strict'
reduce_funcs?: boolean
reduce_vars?: boolean
sequences?: boolean | number
side_effects?: boolean
switches?: boolean
toplevel?: boolean
top_retain?: null | string | string[] | RegExp
typeofs?: boolean
unsafe_arrows?: boolean
unsafe?: boolean
unsafe_comps?: boolean
unsafe_Function?: boolean
unsafe_math?: boolean
unsafe_symbols?: boolean
unsafe_methods?: boolean
unsafe_proto?: boolean
unsafe_regexp?: boolean
unsafe_undefined?: boolean
unused?: boolean
}

预构建配置optimizeDeps

optimizeDeps字段:用于配置依赖优化(依赖预构建)的选项。

vite预构建的项目依赖检测:默认情况下vite会抓取项目中的index.html文件检测需要预构建的项目依赖;若vite.config.ts中指定了build.rollupOptions.input字段,则从这里检测;若你想指定自定义的预构建依赖检测入口,可以通过optimizeDeps.entries字段进行配置。

使用optimizeDeps.exclude字段强制排除_一些依赖_进行预构建,注意,所有以 @iconify-icons/ 开头引入的的本地图标模块,都应该加入到下面的 exclude 里,因为平台推荐的使用方式是哪里需要哪里引入而且都是单个的引入,不需要预构建,直接让浏览器加载就好

使用optimizeDeps.include字段可以强制_一些依赖_必须进行预构建,vite 启动时会将include 里的模块,编译成 esm 格式并缓存到 node_modules/.vite 文件夹,页面加载到对应模块时如果浏览器有缓存就读取浏览器缓存,如果没有会读取本地缓存并按需加载。尤其当您禁用浏览器缓存时(这种情况只应该发生在调试阶段)必须将对应模块加入到 include里,否则会遇到开发环境切换页面卡顿的问题(vite 会认为它是一个新的依赖包会重新加载并强制刷新页面),因为它既无法使用浏览器缓存,又没有在本地 node_modules/.vite 里缓存。注意,如果您使用的第三方库是全局引入,也就是引入到 src/main.ts 文件里,就不需要再添加到 include 里了,因为 vite 会自动将它们缓存到 node_modules/.vite

注意,默认情况下node_modules, build.outDir文件夹会被忽略掉,所以可通过include字段对node_modules下的依赖进行强制预构建。

下面是依赖预构建的相关配置:

// optimize.ts
const include = [
"qs",
"mitt",
"xlsx",
"dayjs",
"axios",
"pinia",
"echarts",
// commonJs的依赖应该进行预构建优化,即使他们是嵌套在其他ESM中
// 比如在esm-dep(esm依赖)用到了一个cjs-dep(commonjs依赖)
"esm-dep > cjs-dep"
]
const exclude = [
"@iconify-icons/ep",
"@iconify-icons/ri",
"@pureadmin/theme/dist/browser-utils"
]
// vite.config.ts
import { include, exclude } from './optimize.ts'
import type { Terser, ConfigEnv, UserConfig } from 'vite'
export default ({command, mode}: ConfigEnv): UserConfig => {
return {
optimizeDeps: {
include: include,
exclude: exclude,
// 强制进行依赖预构建,即使依赖已经缓存过、优化过
// 或者删除node_modules/.vite文件夹
force: true
}
}
} 
附录知识: DepOptimizationOptions类型说明

DepOptimizationOptions类型说明:

export declare interface DepOptimizationOptions {
/**
* By default, Vite will crawl your `index.html` to detect dependencies that
* need to be pre-bundled. If `build.rollupOptions.input` is specified, Vite
* will crawl those entry points instead.
*
* If neither of these fit your needs, you can specify custom entries using
* this option - the value should be a fast-glob pattern or array of patterns
* (https://github.com/mrmlnc/fast-glob#basic-syntax) that are relative from
* vite project root. This will overwrite default entries inference.
*/
entries?: string | string[];
/**
* Force optimize listed dependencies (must be resolvable import paths,
* cannot be globs).
*/
include?: string[];
/**
* Do not optimize these dependencies (must be resolvable import paths,
* cannot be globs).
*/
exclude?: string[];
/**
* Options to pass to esbuild during the dep scanning and optimization
*
* Certain options are omitted since changing them would not be compatible
* with Vite's dep optimization.
*
* - `external` is also omitted, use Vite's `optimizeDeps.exclude` option
* - `plugins` are merged with Vite's dep plugin
* - `keepNames` takes precedence over the deprecated `optimizeDeps.keepNames`
*
* https://esbuild.github.io/api
*/
esbuildOptions?: Omit<BuildOptions_2, 'bundle' | 'entryPoints' | 'external' | 'write' | 'watch' | 'outdir' | 'outfile' | 'outbase' | 'outExtension' | 'metafile'>;
/**
* @deprecated use `esbuildOptions.keepNames`
*/
keepNames?: boolean;
/**
* List of file extensions that can be optimized. A corresponding esbuild
* plugin must exist to handle the specific extension.
*
* By default, Vite can optimize `.mjs`, `.js`, and `.ts` files. This option
* allows specifying additional extensions.
*
* @experimental
*/
extensions?: string[];
/**
* Disables dependencies optimizations
* @default false
* @experimental
*/
disabled?: boolean;
}

全局变量配置define

define字段用于定义可在代码中使用的全局常量。该字段不会经过任何的语法分析,而是直接替换文本,故而应只对常量使用define。

如果定义了一个字符串常量,需要被显式的打上引号(比如通过JSON.stringify)。

若使用了typescript进行开发,应该给定义的全局常量在.d.ts文件下添加相应的类型声明,确保其能够获得类型检查和代码提示。

用法如下所示:

// vite.config.ts
import dayjs from 'dayjs'
import type { ConfigEnv, UserConfig } from 'vite'
import package from './package.json'
const { name, version, dependencies, devDependencies } = package
const __APP_INFO__ = {
pkg: { name, version, dependencies, devDependencies },
lastBuildTime: dayjs(new Date()).format('YYYY-MM-DD HH:mm:ss')
}
export default ({command, mode}: ConfigEnv): UserConfig => {
return {
define: {
// 常量不需要使用
__INTLIFY_PROD_DEVTOOLS__: false,
// 对象形式需要使用json格式化
__APP_INFO__: JSON.stringify(__APP_INFO__),
// 下面的形式是不推荐的:
// 使用对象简写
__APP_INFO__
}
}
}
// 类型声明文件需要位于tsconfig.json文件的compilerOptions.typeRoots字段下声明的目录下进行定义才能被ts解析到,比如:
// compilerOptions.typeRoots = ['./types']
// 定义类型声明:./types/env.d.ts
declare global {
const __APP_INFO__: {
pkg: {
name: string;
version: string;
dependencies: Recordable<string>;
devDependencies: Recordable<string>;
};
lastBuildTime: string;
};
const __INTLIFY_PROD_DEVTOOLS__: boolean;
}
// 定义好之后,可在代码中直接使用该变量,比如在main.ts中输出
console.log('app info: ', __APP_INFO__)

常量和变量:常量是具有预定义值的命名数据项,而变量是在程序执行过程中其值可以更改的命名数据项。

css及其预处理器配置

css.preprocessorOptions字段:指定传递给css预处理器的选项,预处理器文件的扩展名作为选项的键名(比如scss, less, styl),键值为各自预处理器支持的选项。

在配置该字段时,需要安装相应的预处理器依赖,比如npm install -D [sass | less | stylus],但不需要想webpack那样安装对于的loader,因为vite提供了对这三者的内置支持。

css.preprocessorOptions字段可以结合css.modules字段一起使用,只需要将对应的.scss后缀改为.module.scss即可

所有预处理器的选项除了支持各自的选项(sass选项, less选项, stylus选项)外,还支持`additionalData选项,用于注入额外的代码(比如全局样式)。

用法如下:

// generateModifyVars.ts
import { generateAntColors, primaryColor } from '../config/themeConfig';
import { getThemeVariables } from 'ant-design-vue/dist/theme';
import { resolve } from 'path';
export function generateModifyVars(dark = false) {
const palettes = generateAntColors(primaryColor);
const primary = palettes[5];
const primaryColorObj: Record<string, string> = {};
for (let index = 0; index < 10; index++) {
primaryColorObj[`primary-${index + 1}`] = palettes[index];
}
const modifyVars = getThemeVariables({ dark });
// 定义less的全局变量
return {
...modifyVars,
// Used for global import to avoid the need to import each style file separately
// reference:  Avoid repeated references
// 全局导入一个文件config.less里面所有的样式,不需要像下面的单个写
hack: `${modifyVars.hack} @import (reference) "${resolve('src/design/config.less')}";`,
'primary-color': primary,
...primaryColorObj,
'info-color': primary,
'processing-color': primary,
'success-color': '#55D187', //  Success color
'error-color': '#ED6F6F', //  False color
'warning-color': '#EFBD47', //   Warning color
'warning-color12': 'red', //   Warning color
//'border-color-base': '#EEEEEE',
'font-size-base': '14px', //  Main font size
'border-radius-base': '2px', //  Component/float fillet
'link-color': primary, //   Link color
'app-content-background': '#fafafa', //   Link color
};
}
// vite.config.ts
import type { ConfigEnv, UserConfig } from 'vite'
import { generateModifyVars } from './generateModifyVars.ts'
export default ({command, mode}: ConfigEnv): UserConfig => {
return {
css: {
preprocessorOptions: {
scss: {
// 允许在css文件中使用JavaScript语法
javascriptEnabled: true,
// 比如引入一个全局样式文件,另外添加一个全局变量
adtionalData: `@import "./golbal.scss"; $primary-color: #eee;`
},
less: {
javascriptEnabled: true,
// 另一种添加全局变量的方式
modifyVars: generateModifyVars()
}
}
}
}
}
// 使用,在一个功能模块中,比如./src/App.vue
<style lang="scss">
.app {
&_main {
color: $primary-color;
}
}
</style>

注意:

  • 只能在scss文件里面使用additionalData里面导入的内容,即在main.ts导入的scss文件内,或者是*.vue文件下的<style lang="scss"></style>内使用additionalData里面的变量。
  • 同时建议additionalData里面导入的内容最好只写scss变量,不要写css变量(运行时属性),参考

提示: 如果官方文档中未对具体的选项做说明,假设css.preprocessorOptions下每个预处理器具体的选项配置没有给出上述所说的文档或出处,同时鼠标悬浮在css这个选项时,点进去看类型也没有具体的类型设置。这时可以查看不同的预处理器的配置选项对应的插件,比如less,可以查看1,以及在./node_modules/less/下搜索less源码对应的选项(比如javascriptEnabled),即可知道其他配置项还有什么

css.modules字段:该字段用于配置导入css模块时的一些行为,比如具名导入(import primaryColor from './variables.module.css)。

css.postcss字段:用于设置postcss配置源的路径。

附录知识: CSSOptions类型说明

CSSOptions类型说明:

export declare interface CSSOptions {
/**
* https://github.com/css-modules/postcss-modules
*/
modules?: CSSModulesOptions | false;
preprocessorOptions?: Record<string, any>;
postcss?: string | (PostCSS.ProcessOptions & {
plugins?: PostCSS.AcceptedPlugin[];
});
/**
* Enables css sourcemaps during dev
* @default false
* @experimental
*/
devSourcemap?: boolean;
}
export declare interface CSSModulesOptions {
getJSON?: (cssFileName: string, json: Record<string, string>, outputFileName: string) => void;
scopeBehaviour?: 'global' | 'local';
globalModulePaths?: RegExp[];
generateScopedName?: string | ((name: string, filename: string, css: string) => string);
hashPrefix?: string;
/**
* default: undefined
*/
localsConvention?: 'camelCase' | 'camelCaseOnly' | 'dashes' | 'dashesOnly' | ((originalClassName: string, generatedClassName: string, inputFile: string) => string);
}
export interface ProcessOptions {
/**
* The path of the CSS source file. You should always set `from`,
* because it is used in source map generation and syntax error messages.
*/
from?: string
/**
* The path where you'll put the output CSS file. You should always set `to`
* to generate correct source maps.
*/
to?: string
/**
* Function to generate AST by string.
*/
parser?: Syntax | Parser
/**
* Class to generate string by AST.
*/
stringifier?: Syntax | Stringifier
/**
* Object with parse and stringify.
*/
syntax?: Syntax
/**
* Source map options
*/
map?: SourceMapOptions | boolean
}
附录知识: less默认配置选项说明

less默认配置选项说明:

function default_1() {
return {
/* Inline Javascript - @plugin still allowed */
javascriptEnabled: false,
/* Outputs a makefile import dependency list to stdout. */
depends: false,
/* (DEPRECATED) Compress using less built-in compression.
* This does an okay job but does not utilise all the tricks of
* dedicated css compression. */
compress: false,
/* Runs the less parser and just reports errors without any output. */
lint: false,
/* Sets available include paths.
* If the file in an @import rule does not exist at that exact location,
* less will look for it at the location(s) passed to this option.
* You might use this for instance to specify a path to a library which
* you want to be referenced simply and relatively in the less files. */
paths: [],
/* color output in the terminal */
color: true,
/* The strictImports controls whether the compiler will allow an @import inside of either
* @media blocks or (a later addition) other selector blocks.
* See: https://github.com/less/less.js/issues/656 */
strictImports: false,
/* Allow Imports from Insecure HTTPS Hosts */
insecure: false,
/* Allows you to add a path to every generated import and url in your css.
* This does not affect less import statements that are processed, just ones
* that are left in the output css. */
rootpath: '',
/* By default URLs are kept as-is, so if you import a file in a sub-directory
* that references an image, exactly the same URL will be output in the css.
* This option allows you to re-write URL's in imported files so that the
* URL is always relative to the base imported file */
rewriteUrls: false,
/* How to process math
*   0 always           - eagerly try to solve all operations
*   1 parens-division  - require parens for division "/"
*   2 parens | strict  - require parens for all operations
*   3 strict-legacy    - legacy strict behavior (super-strict)
*/
math: 1,
/* Without this option, less attempts to guess at the output unit when it does maths. */
strictUnits: false,
/* Effectively the declaration is put at the top of your base Less file,
* meaning it can be used but it also can be overridden if this variable
* is defined in the file. */
globalVars: null,
/* As opposed to the global variable option, this puts the declaration at the
* end of your base file, meaning it will override anything defined in your Less file. */
modifyVars: null,
/* This option allows you to specify a argument to go on to every URL.  */
urlArgs: ''
};
}

其他配置项

// vite.config.ts
import type { ConfigEnv, UserConfig } from 'vite'
export default ({command, mode}: ConfigEnv): UserConfig => {
return {
// 用于加载.env文件的目录,可以是绝对路径,也可以是相对与项目根目录的路径,默认是root
envDir: './envDirs',
// 该字段开头的环境变量可以在代码其他位置通过import.meta.env.xxx读取到,默认是 VITE_
envPrefix: 'SYing_',
// 该字段配置的内容自动会作为静态资源进行处理,返回解析后的路径
assetsInclude: ['**/*.gltf'],
json: {
// 支持按名导入json文件的字段,比如`import { name } from 'package.josn'
namedExports: true,
// 不支持按名导入,而是会将josn导入为默认导入,即`import pkg from 'package.json',开启此项,按名导入会被禁用
stringift: false,
},
resolve: {
// 设置导入时想要省略的扩展名,不建议自行设置,默认值如下:
extensions: ['.mjs', '.js', '.mts', '.ts', '.jsx', '.tsx', '.json']
},
// 值为false时可以避免vite清屏而错过终端中(terminal)打印的某些信息,因为默认情况下每次vite重构建时,会删除之前的信息
clearScreen: false,
}
}

结尾

下篇预告,Vitejs开源项目实践指南(三),内容包括:

  • 插件plugins的引入和配置

一些对你有用的文章索引

参考文档

原文链接:https://juejin.cn/post/7214858677173649465 作者:司营

(0)
上一篇 2023年3月27日 下午4:11
下一篇 2023年3月27日 下午4:21

相关推荐

发表评论

登录后才能评论