什么是幽灵依赖(幻影依赖)
比如我们创建一个全新的vue3项目
然后我们正常地通过npm install
来下载依赖
然后我们发现,node_ modules文件夹下的很多依赖,我们在package.json中明明没去声明却都下载下来了
那么这些没声明却下载的依赖就是幽灵依赖(幻影依赖)
这种现象出现的原因,可能是一个库依赖另一个库
因此,我们可以在项目中去使用一个我们并没有声明的库,比如lodash
但是这样的话,会出现几个问题
版本问题
比如一个项目用的A这个库,版本是v1,
然后A这个库依赖B库,版本也是v1
B这个库就是我们没有声明的一个库,但是我们仍然在项目中去进行使用
但是如果有一天,我们对A这个库进行了升级,比如说升级到了v2的版本,然后它可能需要对应B库的v2版本,于是B库也跟着莫名其妙地进行升级了,而B库升级之后,它里面的api可能会发生变动,那么可能会导致我们之前的用B库的代码出现问题。
这就是幽灵依赖的一种情况,当然,可能会有更复杂的情况
这个时候,我们可能不好排错和处理
依赖丢失
还有一种依赖丢失
的情况
现在A是一个开发时依赖,也就是到了生产环境里,是不会安装A这个依赖的,
那么B也就不会安装,但是在开发的时候却使用了B,但是生成的时候B却没有了,所以这会发生依赖丢失的问题
为什么会产生这个问题
我们开发的时候会用到很多的包,包与包之间会形成一些依赖关系,比如依赖数
,依赖图
但是npm这个包管理器,使用的是文件结构
,而文件结构是一个树结构
,这样的话,结构不匹配。
这样的的话会导致重复的依赖下载下来,比如上面的D
所以后来出现了一个包管理工具yarn
,它通过拍平
的方式解决了这个问题
那么这样怎么体现出包之间的依赖关系呢
nodejs有一个查询包的流程
就是在import或者require的时候,它找不到的话,会往上去找
yarn这样做的确解决了结构问题(现在新的npm也是yarn的处理方式),但是却产生了幽灵依赖
pnpm
pnpm也是一个包管理工具,它解决了幽灵依赖的问题
它把所有的包。存到一个仓库文件夹里面,然后在node_modules里面用正常的树结构,来表达包依赖
但是它这么做,不会有重复项,因为它使用的链接的方式来处理,也就是说
这几个并不占空间,它只是一个链接
这样问题就解决了
现在我们把之前的依赖删除,用pnpm来进行安装
这个.pnpm文件就是它的仓库
.pnpm称为虚拟存储目录,以平铺的形式储存着所有的项目依赖包,每个依赖包都可以通过.pnpm/@/node_modules/路径找到实际位置。
剩下的就是在package.json中声明的依赖,比如vite、vue采用的叫做符号链接
软链接和硬链接
硬链接
我们的文件数据,都是存在磁盘上的,我们创建一个文件,就给它分配一段磁盘空间,文件是一个指针,指针指向这个磁盘空间,可以通过文件A创建一个硬链接文件B,如果是通过硬链接来创建的话,那么B的指针和A是一样的,他也是同样指向这个磁盘空间,这样就是两个文件,共用一块磁盘空间,这样在硬链接的前提下,把文件A干掉了,不会影响文件B
软链接
软链接类似于快捷方式,它和硬链接的区别是,此处的文件A通过软链接创建文件B,文件B指向的是文件A,而不是磁盘空间,相当于B是A的一个快捷方式
pnpm中软链接和硬链接都用到了
pnpm使用符号链接Symbolic link(软链接)来创建依赖项的嵌套结构,将项目的直接依赖符号链接到
node_modules
的根目录,直接依赖的实际位置在.pnpm/<name>@<version>/node_modules/<name>
,依赖包中的每个文件再硬链接(Hard link)到.pnpm store
。
pnpm的基本使用
安装
npm install -g pnpm
通过下述命令查看已安装的pnpm的版本
pnpm -v
安装依赖
pnpm install xxx
运行package.json中定义的scripts脚本
pnpm run xxx
pnpm管理node环境
之前我们切换node版本可能需要借助nvm
等版本管理工具,但是pnpm就可以实现nvm的功能
本地安装并使用:pnpm env use <node版本号>
全局安装并使用:pnpm env use --global <node版本号>
但是在此前,你需要卸载你已有的nodejs,否则你会报这么一个错误
卸载完之后
windows使用PowerShell:
iwr https://get.pnpm.io/install.ps1 -useb | iex
MacOS
curl -fsSL https://get.pnpm.io/install.sh | sh -
执行命令安装成功后后会看到Setup complete. Open a new terminal to start using pnpm.
这时我们需要重新打开命令行终端,输入pnpm -v 即可查看pnpm版本号,在此表示安装成功
安装 LTS(长期稳定版) 版本的 Node:
pnpm env use --global lts
// global可简写为g,即上边命令可简写为:
pnpm env use --g lts
执行命令会得到提示:
Fetching Node.js 18.16.0 ...
Node.js 18.16.0 is activated
C:\Users\xxx\AppData\Local\pnpm\node.exe -> C:\Users\xxx\AppData\Local\pnpm\nodejs\18.16.0\node.exe
这时我们输入node -v
可查看node版本号,即表示node已安装成功
安装指定版本的Node:
pnpm env use --g 16
执行命令会得到提示:
Fetching Node.js 16.20.0 ...
Node.js 16.20.0 is activated
C:\Users\xxx\AppData\Local\pnpm\node.exe -> C:\Users\xxx\AppData\Local\pnpm\nodejs\16.20.0\node.exe
注意:这里输入的版本号为16,则会下载v16的最后一个版本,也就是16.20.0,你也可以指定次版本号,例如pnpm env use --global 16.18.2
,可用的版本号列表执行pnpm env list --remote
命令查看
安装最新版本的 Node.js:
pnpm env use --g latest
查看本地安装的Node版本
pnpm env list
或
pnpm env ls
执行命令会看到:
16.20.0
18.16.0
* 20.3.0
星号表示当前使用的Node版本
查看服务器可用的Node版本
pnpm env list --remote
移除本地已经安装Node版本
pnpm env remove --g 16.20.0
执行命令会提示:
Node.js 16.20.0 is removed
C:\Users\55856\AppData\Local\pnpm\nodejs\16.20.0
这时我们再使用pnpm env list 命令查看,会发现16.20.0已被删除:
18.16.0
* 20.3.0
pnpm对monorepo的支持
这一点在之前的文章中有详细说过
Pnpm实现Monorepo风格项目搭建
使用pnpm创建一个vue3项目
pnpm创建项目
pnpm create vite
集成eslint
pnpm i eslint -D
生成配置文件:.eslint.cjs
npx eslint --init
.eslint.cjs配置文件含义
module.exports = {
//运行环境
"env": {
"browser": true,//浏览器端
"es2021": true,//es2021
},
//规则继承
"extends": [
//全部规则默认是关闭的,这个配置项开启推荐规则,推荐规则参照文档
//比如:函数不能重名、对象不能出现重复key
"eslint:recommended",
//vue3语法规则
"plugin:vue/vue3-essential",
//ts语法规则
"plugin:@typescript-eslint/recommended"
],
//要为特定类型的文件指定处理器
"overrides": [
],
//指定解析器:解析器
//Esprima 默认解析器
//Babel-ESLint babel解析器
//@typescript-eslint/parser ts解析器
"parser": "@typescript-eslint/parser",
//指定解析器选项
"parserOptions": {
"ecmaVersion": "latest",//校验ECMA最新版本
"sourceType": "module"//设置为"script"(默认),或者"module"代码在ECMAScript模块中
},
//ESLint支持使用第三方插件。在使用插件之前,您必须使用npm安装它
//该eslint-plugin-前缀可以从插件名称被省略
"plugins": [
"vue",
"@typescript-eslint"
],
//eslint规则
"rules": {
}
}
vue3环境代码校验插件
# 让所有与prettier规则存在冲突的Eslint rules失效,并使用prettier进行代码检查
"eslint-config-prettier": "^8.6.0",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-node": "^11.1.0",
# 运行更漂亮的Eslint,使prettier规则优先级更高,Eslint优先级低
"eslint-plugin-prettier": "^4.2.1",
# vue.js的Eslint插件(查找vue语法错误,发现错误指令,查找违规风格指南
"eslint-plugin-vue": "^9.9.0",
# 该解析器允许使用Eslint校验所有babel code
"@babel/eslint-parser": "^7.19.1",
安装
pnpm install -D eslint-plugin-import eslint-plugin-vue eslint-plugin-node eslint-plugin-prettier eslint-config-prettier eslint-plugin-node @babel/eslint-parser
修改.eslintrc.cjs配置文件
// @see https://eslint.bootcss.com/docs/rules/
module.exports = {
env: {
browser: true,
es2021: true,
node: true,
jest: true,
},
/* 指定如何解析语法 */
parser: 'vue-eslint-parser',
/** 优先级低于 parse 的语法解析配置 */
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
parser: '@typescript-eslint/parser',
jsxPragma: 'React',
ecmaFeatures: {
jsx: true,
},
},
/* 继承已有的规则 */
extends: [
'eslint:recommended',
'plugin:vue/vue3-essential',
'plugin:@typescript-eslint/recommended',
'plugin:prettier/recommended',
],
plugins: ['vue', '@typescript-eslint'],
/*
* "off" 或 0 ==> 关闭规则
* "warn" 或 1 ==> 打开的规则作为警告(不影响代码执行)
* "error" 或 2 ==> 规则作为一个错误(代码不能执行,界面报错)
*/
rules: {
// eslint(https://eslint.bootcss.com/docs/rules/)
'no-var': 'error', // 要求使用 let 或 const 而不是 var
'no-multiple-empty-lines': ['warn', { max: 1 }], // 不允许多个空行
'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
'no-unexpected-multiline': 'error', // 禁止空余的多行
'no-useless-escape': 'off', // 禁止不必要的转义字符
// typeScript (https://typescript-eslint.io/rules)
'@typescript-eslint/no-unused-vars': 'error', // 禁止定义未使用的变量
'@typescript-eslint/prefer-ts-expect-error': 'error', // 禁止使用 @ts-ignore
'@typescript-eslint/no-explicit-any': 'off', // 禁止使用 any 类型
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/no-namespace': 'off', // 禁止使用自定义 TypeScript 模块和命名空间。
'@typescript-eslint/semi': 'off',
// eslint-plugin-vue (https://eslint.vuejs.org/rules/)
'vue/multi-word-component-names': 'off', // 要求组件名称始终为 “-” 链接的单词
'vue/script-setup-uses-vars': 'error', // 防止<script setup>使用的变量<template>被标记为未使用
'vue/no-mutating-props': 'off', // 不允许组件 prop的改变
'vue/attribute-hyphenation': 'off', // 对模板中的自定义组件强制执行属性命名样式
},
}
eslintignore忽略文件
dist
node_modules
"scripts": {
"lint": "eslint src",
"fix": "eslint src --fix",
}
配置prettier
pnpm install -D eslint-plugin-prettier prettier eslint-config-prettier
prettierrc.json添加规则
“singleQuote”: true 表示使用单引号代替双引号。
“semi”: false 表示不使用分号。
“bracketSpacing”: true 表示在对象字面量声明中在括号之间打印空格。
“htmlWhitespaceSensitivity”: “ignore” 表示在HTML模板中忽略空格敏感度。
“endOfLine”: “auto” 表示自动选择操作系统特定的行结束符。
“trailingComma”: “all” 表示尽可能使用尾随逗号。
“tabWidth”: 2 表示使用两个空格作为一个制表符的宽度。
{
"singleQuote": true,
"semi": false,
"bracketSpacing": true,
"htmlWhitespaceSensitivity": "ignore",
"endOfLine": "auto",
"trailingComma": "all",
"tabWidth": 2
}
prettierignore忽略文件
/dist/*
/html/*
.local
/node_modules/**
**/*.svg
**/*.sh
/public/*
通过pnpm run lint去检测语法,如果出现不规范格式,通过pnpm run fix 修改
配置stylelint
stylelint为css的lint工具。可格式化css代码,检查css语法错误与不合理的写法,指定css书写顺序等。
pnpm add sass sass-loader stylelint postcss postcss-scss postcss-html stylelint-config-prettier stylelint-config-recess-order stylelint-config-recommended-scss stylelint-config-standard stylelint-config-standard-vue stylelint-scss stylelint-order stylelint-config-standard-scss -D
.stylelintrc.cj
s配置文件
// @see https://stylelint.bootcss.com/
module.exports = {
extends: [
'stylelint-config-standard', // 配置stylelint拓展插件
'stylelint-config-html/vue', // 配置 vue 中 template 样式格式化
'stylelint-config-standard-scss', // 配置stylelint scss插件
'stylelint-config-recommended-vue/scss', // 配置 vue 中 scss 样式格式化
'stylelint-config-recess-order', // 配置stylelint css属性书写顺序插件,
'stylelint-config-prettier', // 配置stylelint和prettier兼容
],
overrides: [
{
files: ['**/*.(scss|css|vue|html)'],
customSyntax: 'postcss-scss',
},
{
files: ['**/*.(html|vue)'],
customSyntax: 'postcss-html',
},
],
ignoreFiles: [
'**/*.js',
'**/*.jsx',
'**/*.tsx',
'**/*.ts',
'**/*.json',
'**/*.md',
'**/*.yaml',
],
/**
* null => 关闭该规则
* always => 必须
*/
rules: {
'value-keyword-case': null, // 在 css 中使用 v-bind,不报错
'no-descending-specificity': null, // 禁止在具有较高优先级的选择器后出现被其覆盖的较低优先级的选择器
'function-url-quotes': 'always', // 要求或禁止 URL 的引号 "always(必须加上引号)"|"never(没有引号)"
'no-empty-source': null, // 关闭禁止空源码
'selector-class-pattern': null, // 关闭强制选择器类名的格式
'property-no-unknown': null, // 禁止未知的属性(true 为不允许)
'block-opening-brace-space-before': 'always', //大括号之前必须有一个空格或不能有空白符
'value-no-vendor-prefix': null, // 关闭 属性值前缀 --webkit-box
'property-no-vendor-prefix': null, // 关闭 属性前缀 -webkit-mask
'selector-pseudo-class-no-unknown': [
// 不允许未知的选择器
true,
{
ignorePseudoClasses: ['global', 'v-deep', 'deep'], // 忽略属性,修改element默认样式的时候能使用到
},
],
},
}
.stylelintignore
忽略文件
/node_modules/*
/dist/*
/html/*
/public/*
"scripts": {
"lint:style": "stylelint src/**/*.{css,scss,vue} --cache --fix"
}
最后配置统一的prettier来格式化我们的js和css,html代码
"scripts": {
"dev": "vite --open",
"build": "vue-tsc && vite build",
"preview": "vite preview",
"lint": "eslint src",
"fix": "eslint src --fix",
"format": "prettier --write \"./**/*.{html,vue,ts,js,json,md}\"",
"lint:eslint": "eslint src/**/*.{ts,vue} --cache --fix",
"lint:style": "stylelint src/**/*.{css,scss,vue} --cache --fix"
},
当我们运行pnpm run format的时候,会把代码直接格式化
去除console.log
通过terser工具来去除console.log
正常打包
加上terser配置
这是webpack
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
transpileDependencies: true,
terser:{
terserOptions: {
compress: {
// 移除console.log
drop_console: true,
// 移除debugger
drop_debugger: true
}
}
}
})
这是vite,使用vite之前需要安装rollup-plugin-terser模块:
pnpm install rollup-plugin-terser --save-dev
import { terser } from 'rollup-plugin-terser'
plugins: [vue(), terser()],