工欲善其事,必先利其器。做好一个项目前期准备工作很重要,前期准备无非就是合理规划项目结构、按需编写构建代码、批量创建入口文件、按需封装工具函数等,而按需封装工具函数是作为一个项目基础的重中之重,是很有必要提前规划的。
这些经常重复使用的工具函数,包括但不限于浏览器类型、格式时间差、URL参数反序列化、过滤XSS等。为了避免应用开发时重复的复制粘贴操作带来不必要的麻烦,有些开发者都会将这些工具函数根据功能划分并统一封装,再发布到Npm公有仓库。每次使用时直接安装,提升开发效率,将时间用在正确的事情中。
配好的配置示例
vite
常规配置
// vite-config.js
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 vueDevTools from 'vite-plugin-vue-devtools'
import viteCompression from 'vite-plugin-compression'
import viteImagemin from 'vite-plugin-imagemin'
import { createHtmlPlugin } from 'vite-plugin-html'
import qiankun from "vite-plugin-qiankun";
// https://vitejs.dev/config/
export default defineConfig((mode) => {
process.env = { ...process.env, ...loadEnv(mode, process.cwd()) }
return {
// base: process.env.NODE_ENV === 'production' ? '/residentdoctor' : './',
// 上线后,必须有固定的路径前缀才能nginx代理
base: '/residentdoctor',
plugins: [
vue(),
vueJsx(),
// vueDevTools(),
// 乾坤配置
qiankun('residentdoctor', { //子应用name,须与子应用中package.json中的name属性相同
useDevMode: false
}),
// 静态资源压缩
viteCompression({
verbose: true, // 默认即可
disable: false, // 开启压缩(不禁用),默认即可
deleteOriginFile: false, // 删除源文件
threshold: 5120, // 压缩前最小文件大小
algorithm: 'gzip', // 压缩算法
ext: '.gz' // 文件类型
}),
// 图片压缩
viteImagemin({
gifsicle: {
optimizationLevel: 7,
interlaced: false
},
optipng: {
optimizationLevel: 7
},
mozjpeg: {
quality: 20
},
pngquant: {
quality: [0.8, 0.9],
speed: 4
},
svgo: {
plugins: [
{
name: 'removeViewBox'
},
{
name: 'removeEmptyAttrs',
active: false
}
]
}
}),
createHtmlPlugin({
inject: {
data: {
title: process.env.VITE_APP_TITLE
}
}
}),
// cssImportPlugin()
],
build: {
// 大资源拆分
chunkSizeWarningLimit: 1500, //加大
rollupOptions: {
output: {
// 静态资源打包做处理
chunkFileNames: 'static/js/[name]-[hash].js',
entryFileNames: 'static/js/[name]-[hash].js',
assetFileNames: 'static/[ext]/[name]-[hash].[ext]',
// 指定哪些模块应该被打包到同一个chunk中,通常只需要指定比较大的包
manualChunks(id) {
if (id.includes('node_modules')) {
//提取*/node_modules/vite/**中node_modules/后在字符串
return id.toString().split('node_modules/')[1].split('/')[0].toString()
}
}
}
},
// 移除console.log&debugger
terserOptions: {
compress: {
drop_console: true,
drop_debugger: true
}
}
},
// 路径映射
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
},
server: {
proxy: {
'/ts-bs-his': {
target: 'http://192.168.208.26:9099',
secure: true,
changeOrigin: true,
configure: (proxy, options) => {
proxy.on('proxyReq', function (proxyReq, req, res, options) {
proxyReq.setHeader('appId', 'trasen')
})
}
},
...
}
},
}
})
qiankun 额外配置
vite-config.js
// vite-config.js
...
export default defineConfig((mode) => {
...,
// base需与entry相同,base必须与nginx代理的前缀相同,项目运行后会变为url/base,乾坤主应用的entry需与base相同
base:'/residentdoctor',
// element-plus的命名空间配置
css: {
preprocessorOptions: {
scss: {
additionalData: `@use "./src/assets/styles/element/variable.scss" as *;`
}
}
}
})
main.js
// main.js
// 引入vue,样式等
...
import App from './App.vue'
import getRouter from './router'
import { useThemeStore } from './stores/theme'
let app;
/**
* @param container 主应用下发的props中的container,也就是子应用的根节点
* 将子应用appendBody的元素,挂载到子应用根元素身上
* 用于解决乾坤子应用开启如下样式隔离方案后,添加到body的元素样式失效
* sandbox: {
* 以下配置项只能开启一个,另一个要为false
* strictStyleIsolation: true, //严格模式,其实就是给子应用根节点改造为shadowRoot,也就是套上shadowDom
* experimentalStyleIsolation: true, //样式隔离,就是给子应用的全局样式加上div[data-qiankun=`${appName}`],会导致scoped中的样式权重变低
* },
*/
const proxy = (container) => {
if (document.body.appendChild.__isProxy__) return;
const revocable = Proxy.revocable(document.body.appendChild, {
apply (target, thisArg, [node]) {
if (container) {
container.appendChild(node);
} else {
target.call(thisArg, node);
}
}
});
if (revocable.proxy) {
document.body.appendChild = revocable.proxy;
}
document.body.appendChild.__isProxy__ = true;
};
/**
* 用于管理主题,有很多种方案,这里仅改变color-primary的值
* 常见方案:
* 1.属性切换法
* 给html/body,写上一个特殊的属性用于切换主题,例如data-theme="dark"
* 设置css变量,例如:
* html[data-theme="theme1"] {
* --color-primary: #f98866;
* --color-secondary: #80bd9e;
* --color-buttons: #89da59;
* --color-typography: #ff320e;
* }
* 写切换主题方法:
* const changeTheme = (theme: string) => {
* document.body?.setAttribute("data-theme", theme);
* };
*
* 2.动态加载法
* 通过rxjs从链接动态请求主题配置,通过link加载,
* 好处是可以将静态资源和主题全部抽离出来作为项目统一管理,方便定制化(oem)
*/
function themeManager(props){
const themeStore = useThemeStore()
try {
if (props.fn.getTheme) {
const themeColor = props.fn.getTheme();
if (themeColor) {
themeStore.setTheme(themeColor);
}
}
props.onGlobalStateChange((state) => {
//更换主题
if (state.action == "changeTheme") {
themeStore.setTheme(state.color);
}
});
} catch (e) {
console.log(e);
}
}
function render(props) {
const { container } = props;
proxy(container)
app = createApp(App);
// 注册指令
directives(app)
// 注册组件
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.component(key, component)
}
const pinia = createPinia()
pinia.use(piniaPluginPersistedstate)
app.provide('getUtils', utils)
app.use(pinia)
// 测试主题变更
// const themeStore = useThemeStore()
// themeStore.setTheme('red'); const router = getRouter(props);
const router = getRouter(props);
app.use(router)
app.use(ElementPlus)
if(container){
const root = container.querySelector('#app');
app.mount(root);
}else{
app.mount('#app')
}
}
// 独立运行时
if (!qiankunWindow.__POWERED_BY_QIANKUN__) {
render({});
}else{
renderWithQiankun({
mount (props) {
console.log("before props")
render(props);
themeManager(props);
console.log("after props")
},
bootstrap () {
console.log("%c ", "color: green;", "app bootstraped");
},
unmount (props) {
app.unmount();
app = null;
},
});
}
App.vue
// App.vue
<template>
<el-config-provider
:locale="zhCn"
namespace="residentdoctor"
:empty-values="[undefined]"
>
<div class="height-100" :style="`--residentdoctor-color-primary: ${themeColor || '#3A77FF'};`">
<RouterView />
</div>
</el-config-provider>
</template>
<script setup>
import { RouterView } from 'vue-router'
import zhCn from 'element-plus/dist/locale/zh-cn.mjs'
import { useUserStore } from '@/stores/user'
import { computed } from 'vue'
import { useThemeStore } from "@/stores/theme.js";
const themeStore = useThemeStore();
const themeColor = computed(() => themeStore.themeColor);
</script>
<style scoped></style>
router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import { qiankunWindow } from 'vite-plugin-qiankun/dist/helper'
import { isEmpty,assign } from 'radash'
const routes= []
function getRouter(props) {
let base;
const routes=_.cloneDeep(Routes);
if (qiankunWindow.__POWERED_BY_QIANKUN__) {
const { activeRule } = props.data;
...
base = activeRule;
}
else {
base = '/residentdoctor'
}
const router = createRouter({
history: createWebHistory(base),
routes
})
router.beforeEach((to, from, next) => {
_.assign(history.state, { current: from.fullPath })
}
next()
})
return router
}
export default getRouter
theme.js
import { defineStore } from 'pinia'
export const useThemeStore = defineStore({
id: 'theme',
state: () => ({
themeColor: '#3a77ff'
}),
actions: {
setTheme(color) {
this.themeColor = color
}
}
})
webpack
统一规范
示例
-->1.安装eslint检查代码格式
//vue2
pnpm i -D eslint@8.57.0 eslint-plugin-import eslint-plugin-vue eslint-config-airbnb-base
//vue3
pnpm i -D eslint@8.57.0 eslint-plugin-import eslint-plugin-vue vite-plugin-eslint eslint-config-airbnb-base
//react
pnpm i -D eslint@8.57.0 eslint-plugin-import eslint-plugin-react vite-plugin-eslint eslint-config-airbnb-base
-->2.安装prettier
pnpm i -D prettier eslint-config-prettier eslint-plugin-prettier
-->3.安装stylelint检查css格式
//css sass scss less
pnpm i -D stylelint stylelint-config-standard stylelint-config-standard-scss stylelint-config-standard-less
//包含vue
pnpm i -D stylelint-config-standard-vue
//包含tailwind
pnpm i -D stylelint-config-tailwindcss
//配置stylelint.config.js
export default {
extends: [下载的config包,例如"stylelint-config-standard"],
rules: {
// 在这里可以自定义的规则,覆盖默认的规则
},
};
-->4.安装husky&lint-staged,并配置,通过husky的pre-commit钩子调用lint-staged校验代码格式
pnpm install -D husky lint-staged
npx mrm@2 lint-staged
//根目录创建 .lintstagedrc.json 文件或在package.json中添加lint-staged配置项 添加以下代码
{
"*.{js,jsx,ts,tsx,vue,md}": [
"eslint --fix",
"prettier --write"
],
"*.{css,scss,less,vue}": [
"stylelint --fix"
]
}
// .husky/pre-commit文件中
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
#!调用lint-staged校验暂存区的代码
npx lint-staged
-->5.安装commitlint校验提交信息,也就是git commit -m "提交信息" 中双引号内容,通过husky的commit-msg钩子,调用commitlint,commitlint只能在commit时校验,--edit校验最后一次提交并在失败时回退
pnpm i -D commitlint @commitlint/config-conventional
//.husky/commit-msg中
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
#!调用commitlint校验提交格式
npx commitlint --edit
//在commitlint.config.js中添加
export default {
"extends": [
"@commitlint/config-conventional"
]
}
//自定义提交消息格式
//在commitlint.config.js中添加
//以下自定义了一个叫commit-message-rule的本地插件,并在rules中定义了它的使用范围
export default {
//extends: ['@commitlint/config-conventional'],
//是否使用默认忽略规则
//defaultIgnores: true,
// 什么条件忽略提交信息
ignores: [(commit) => commit.includes('init')],
//提交验证失败时,将显示此 URL
helpUrl: 'http://172.26.0.17:8090/pages/viewpage.action?pageId=66330',
// [规则名称]:[level, applicable, value]
// level 校验等级
// 0 禁用
// 1 警告
// 2 错误
// applicable 规则匹配模式
// always 正匹配
// never 反匹配
// value 参数值
// 规则可接收参数:
// 规则数组 Array
// 返回规则数组的函数 () => array
// Promise规则数组 Promise《Array》
rules: {
'commit-message-rule': [2, 'always'],
},
plugins: [
{
rules: {
'commit-message-rule': ({ header }) => {
// const noIssueReg = /[N|n][O|o][I|i][S|s][S|s][U|u][E|e]\;\s*[^\s]+\s*/;
const IssueReg = /\s*[I|i][S|s][S|s][U|u][E|e]\s*:\s*[a-zA-Z]+-[0-9]+\s*\;[\s\S]*/;
return [IssueReg.test(header), '您的提交信息不符合规范!正确的格式为(示例):Issue:xxxx-000;xxxxxx'];
},
},
},
],
};
-->XXXXXXX6.安装commitizen辅助提交
//非自定义
pnpm i -g commitizen cz-conventional-changelog
commitizen init cz-conventional-changelog --pnpm --save-dev --save-exact
统一代码风格
- 配置Stylelint可查看Stylelint规则
- 配置Eslint可查看Eslint规则
- 配置TypeScriptEslint可查看TypeScriptEslint规则
- 配置VueEslint可查看VueEslint规则 应用代码规范有三点好处:
- 强制规范团队编码规范可让新旧成员编码习惯一样
- 增加项目代码的可维护性与可接入性,新成员能快速适应项目的架构与需求
- 保障项目整体质量,可减少无用代码、重复代码、错误代码和漏洞代码的产生几率
Lint其实就是编辑器中运行的一个脚本进程,将代码解析为抽象语法树(AST),
遍历抽象语法树并通过预设规则做一些判断与改动,
再将新的抽象语法树转换为正确代码。
可以考虑将配置文件打包为一个npm包,下下来后本地引用配置,或者编辑器配置
eslint
//eslint-config-airbnb-base //(更严格,推荐)提供airbnb风格的代码规范
//eslint-config-standard //(相对宽松)提供standard风格的代码规范
//eslint-plugin-import //用于检查 ES6 的 import/export 语法
//eslint-plugin-vue //帮助检查 Vue 特有的语法和最佳实践
//vite-plugin-eslint //在 Vite 构建过程中集成 ESLint,自动执行代码检查
//保存自动格式化
//@vue/cli-plugin-eslint // Vue CLI的一个插件,用这个vuecli会支持lintOnSave选项,同时会安装eslint及其相关依赖
//vue2
npm i -D eslint@8.57.0 eslint-plugin-import eslint-plugin-vue eslint-config-airbnb-base
//vue3
npm i -D eslint@8.57.0 eslint-plugin-import eslint-plugin-vue vite-plugin-eslint eslint-config-airbnb-base
//react
npm i -D eslint@8.57.0 eslint-plugin-import eslint-plugin-react vite-plugin-eslint eslint-config-airbnb-base
stylelint
校验样式问题
//stylelint //CSS/SCSS/Less代码检查工具
//stylelint-config-recess-order //规定css属性的书写顺序(要折磨自己吗?)
//stylelint-config-standard //Stylelint 的标准配置,包含了一组常见的 CSS 编码最佳实践。
//stylelint-config-standard-vue //在 stylelint-config-standard 的基础上新增了一些 Vue 特有的规则
//stylelint-config-standard-scss //在 stylelint-config-standard 的基础上新增了stylelint-config-recommended-scss的大部分规则,同时适应与sass
//stylelint-config-standard-less //在 stylelint-config-standard 的基础上新增了stylelint-config-recommended-less的大部分规则
//stylelint-config-sass-guidelines //包含了一组针对 仅sass 的最佳实践规则
//stylelint-config-recommended-scss //包含了一组针对 仅SCSS 的最佳实践规则
//stylelint-config-recommended-less //包含了一组针对 仅less 的最佳实践规则
//仅css
npm i -D stylelint stylelint-config-standard
//css sass scss
npm i -D stylelint stylelint-config-standard stylelint-config-standard-scss
//css sass scss less
npm i -D stylelint stylelint-config-standard stylelint-config-standard-scss stylelint-config-standard-less
//包含vue
npm i -D stylelint-config-standard-vue
统一提交规范
//husky //操作 git 钩子的工具
//lint-staged //本地暂存代码检查工具
//commitlint //commit 信息校验工具
//@commitlint/config-conventional //提供一些默认配置
//commitizen //辅助 commit 信息 ,就像这样,通过选择输入,规范提交信息
//全部都要
pnpm install -D husky lint-staged commitlint @commitlint/config-conventional commitizen
npx mrm@2 lint-staged
husky&lint-staged
husky操作 git 钩子的工具,常见钩子如下:
- pre-commit 提交前的钩子
- commit-msg 提交信息钩子
- pre-push push前的钩子 lint-staged : 本地暂存代码检查工具
//此命令将根据项目 `package.json` 依赖项中的代码质量工具安装并配置 husky 和 lint-staged,因此请确保在执行此操作之前安装( `npm install --save-dev` )并配置所有代码质量工具,例如 Prettier 和 ESLint。
npx mrm@2 lint-staged
//根目录创建 .lintstagedrc.json 文件或在package.json中添加lint-staged配置项 添加以下代码
{
"*.{js,jsx,ts,tsx,vue,md}": [
"eslint --fix",
"prettier --write"
],
"*.{css,scss,less,vue}": [
"stylelint --fix"
]
}
commitlint
- **commitlint:**commit 信息校验工具
- @commitlint/config-conventional:提供一些默认配置,标识采用什么规范来执行消息校验, 默认是Angular的提交规范
pnpm i -D commitlint @commitlint/config-conventional
//在 commit-msg的钩子中,添加代码,commitlint只能在commit时校验,--edit校验最后一次提交并在失败时回退
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
#!调用commitlint校验提交格式
npx commitlint --edit
//在commitlint.config.js中添加
export default {
"extends": [
"@commitlint/config-conventional"
]
}
@commitlint/config-conventional注意要加空格
提供Angular提交规范
<type>(<scope>): <short description>
[optional body]
[optional footer(s)]
//如需修改@commitlint/config-conventional的默认配置,看自定义提交规范,用自定义的覆盖它
//添加commitlint.config.js或.commitlintrc.js
module.exports = {
extends: ['@commitlint/config-conventional'],
}
//或者在package.json中添加
"commitlint": {
"extends": ["@commitlint/config-conventional"]
}
类型 | 描述 |
---|---|
feat | 新增功能,迭代项目需求 |
refactor | 重构代码,非新增功能也非修复缺陷 |
perf | 优化相关,比如提升性能、体验 |
fix | 修复缺陷 |
style | 代码格式修改, 注意不是 css 修改 |
revert | 回滚版本,撤销某次代码提交 |
merge | 合并分支,合并分支代码到其他分支 |
docs | 更新文档,仅修改文档不修改代码 |
build | 编译相关的修改,例如发布版本、对项目构建或者依赖的改动 |
chore | 其他修改, 比如改变构建流程、或者增加依赖库、工具等 |
ci | 更新脚本,改动CI或执行脚本配置 |
test | 新增测试,追加测试用例验证代码 |
自定义提交规范
默认提交规范 <type>(<scope>): <subject>
如果自定义提交信息校验就不需要@commitlint/config-conventional
commitlint可以声明本地插件,但是同时只能有一个本地插件
//添加commitlint.config.js或.commitlintrc.js
//以下自定义了一个叫commit-message-rule的本地插件,并在rules中定义了它的使用范围
module.exports = {
//extends: ['@commitlint/config-conventional'],
//是否使用默认忽略规则
//defaultIgnores: true,
// 什么条件忽略提交信息
ignores: [(commit) => commit.includes('init')],
//提交验证失败时,将显示此 URL
helpUrl: 'http://172.26.0.17:8090/pages/viewpage.action?pageId=66330',
// [规则名称]:[level, applicable, value]
// level 校验等级
// 0 禁用
// 1 警告
// 2 错误
// applicable 规则匹配模式
// always 正匹配
// never 反匹配
// value 参数值
// 规则可接收参数:
// 规则数组 Array
// 返回规则数组的函数 () => array
// Promise规则数组 Promise《Array》
rules: {
'commit-message-rule': [2, 'always'],
},
plugins: [
{
rules: {
'commit-message-rule': ({ header }) => {
// const noIssueReg = /[N|n][O|o][I|i][S|s][S|s][U|u][E|e]\;\s*[^\s]+\s*/;
const IssueReg = /\s*[I|i][S|s][S|s][U|u][E|e]\s*:\s*[a-zA-Z]+-[0-9]+\s*\;[\s\S]*/;
return [IssueReg.test(header), '您的提交信息不符合规范!正确的格式为(示例):Issue:xxxx-000;xxxxxx'];
},
},
},
],
};
commitizen
辅助 commit 信息,只支持手动输入git cz命令触发
//cz-conventional-changelog 提供默认的git cz提示
//cz-customizable 自定义git cz的提示
npm i -D commitizen cz-customizable
//指定package.json中 config/commitizen/path 为"./node_modules/cz-customizable"
"config": {
"commitizen": {
"path": "node_modules/cz-customizable"
}
}
项目管理
CI/CD
gitlab提供了 CI/CD功能,gitlab-ci.yml 文件是 GitLab CI/CD 配置的核心文件,常见配置如下:
- stages:定义了流水线的不同阶段(作业),按顺序依次执行。
- variables:定义了全局变量,这些变量可以在整个 gitlab-ci.yml 文件中使用。
- before_script:在每个作业之前运行的命令。
- jobs(可不显示声明,满足job结构的配置项会自动识别为job):定义每个作业做的事情,这里build_job作业就是定义build阶段做的事情。
- stage:定义这个作业对应的流水线阶段
- artifacts:用于定义构建产物,可以在后续阶段或作业中使用。
- paths:构建产物所在目录
- cache:缓存依赖项或构建结果,以加快后续的构建速度。
- key:
- key相当于给缓存命别名,缓存空间具有这个名字的缓存就会使用缓存
- 默认是default,所有未指定缓存键的job将共享同一个缓存空间
- policy:默认 pull-push 表示先恢复缓存,然后更新缓存
- paths:应该缓存的目录
- key:
- dependencies:定义作业之间的依赖关系,也就是这个作业依赖哪个作业。
- services:为作业提供辅助服务,如数据库服务。
cache:
paths:
- node_modules/ #node_modules目录缓存起来
#定义了作业(jobs)执行的顺序
stages: #流水过程,先从build-->deploy
- build
- deploy
#定义的全局变量
variables:
DEFAULT_BRANCH: dev-prd
NAGINX_PATH: /data/middle/base-env/nginx/html/ts-cloud-web-all
IP: 192.168.18.130
#前置脚本,每个作业执行前都会调用的脚本
before_script:
- echo "Setting up the environment..."
npm-build: # 自定义的作业名称
stage: build # 指定这个作业属于哪个阶段
tags: - 'npm-14' # 指定使用哪个GitLab Runner来执行这个作业(通过Runner的tag)
script: # 定义了一系列要执行的命令
- . /root/.nvm/nvm-0.39.3/nvm.sh # 加载NVM脚本,以便能够切换Node.js版本
- nvm use 18.18.0 # 使用NVM切换到Node.js 18.18.0版本
- pnpm install # 使用pnpm安装项目依赖
- pnpm run build:prod # 执行pnpm脚本中定义的build:prod命令来构建生产环境的代码
# 定义作业执行后需要保留的文件或目录
artifacts:
paths:
- dist/ # 指定要保留的目录为dist/,这通常包含构建后的产物
rules:
- if: '$CI_COMMIT_BRANCH == $DEFAULT_BRANCH'
- when: always
- when: never
#only: - dev # 只有当在指定分支上提交更改时,这个作业才会被触发
npm-deploy:
#依赖哪些作业的输出
#dependencies: [build_job]
stage: deploy # 指定这个作业属于deploy阶段
tags: - 'npm-deploy' # 指定使用哪个GitLab Runner来执行这个作业(通过Runner的tag)
script:
# 定义变量current_date,存储当前时间(年-月-日-时-分-秒)
- current_date=$(date +"%Y-%m-%d-%H-%M-%S")
# 打印当前日期和时间
- echo $current_date
# 通过SSH登录到远程服务器,并创建一个以当前日期和时间命名的备份目录
- ssh root@$IP "mkdir $NAGINX_PATH/ts-cloud-web_bak/$current_date"
# 检查目标目录是否为空,如果不为空,则将旧的文件移动到前面创建的备份目录中
- ssh root@$IP "if du -sh '$NAGINX_PATH/ts-cloud-web' | grep -q '0\b'; then echo 'empty'; else mv -f '$NAGINX_PATH/ts-cloud-web'/* '$NAGINX_PATH/ts-cloud-web_bak/$current_date'; fi"
# 使用SCP命令将构建好的dist目录下的所有文件复制到远程服务器的目标目录中
- scp -r dist/* root@192.168.18.130:/data/middle/base-env/nginx/html/ts-cloud-web-all/ts-cloud-web
rules:
- if: '$CI_COMMIT_BRANCH == $DEFAULT_BRANCH'
- when: always
- when: never
#only: - dev # 只有当在指定分支上提交更改时,这个作业才会被触发
切换Npm镜像
npm i nrm -g
命令 | 功能 |
---|---|
nrm add <name> <url> | 新增镜像 |
nrm del <name> | 删除镜像 |
nrm test <name> | 测试镜像 |
nrm use <name> | 切换镜像 |
nrm current | 查看镜像 |
nrm ls | 查看镜像列表 |
切换node版本
npm i nvm -g
命令 | 功能 |
---|---|
nvm ls | 查看本地node版本列表 |
nvm use <version> | 切换版本 |
nvm install <version> | 下载一个指定版本的node |
nvm uninstall <version> | 卸载一个指定版本的node |
node支持ESM
模块方案
在JS发展历程中,主要有六种常见模块方案,分别是IIFE、CJS、AMD、CMD、UMD和ESM。为了方便对比,通过下图展示它们各自的定义与特性:
CJS与ESM
- | CJS | ESM |
---|---|---|
语法类型 | 动态 | 静态 |
关键声明 | require | export与import |
加载方式 | 运行时加载 | 编译时加载 |
加载行为 | 同步加载 | 异步加载 |
书写位置 | 任何位置 | 顶层位置 |
指针指向 | this指向当前模块 | this指向undefined |
执行顺序 | 首次引用时加载模块 再次引用时读取缓存 | 引用时生成只读引用 执行时才是正式取值 |
属性引用 | 基本类型属于复制不共享 引用类型属于浅拷贝且共享 | 所有 |
运行时加载指整体加载模块生成一个对象,再从对象中获取所需的属性方法去加载。最大特性是全部加载,只有运行时才能得到该对象,无法在编译时做静态优化。
编译时加载指直接从模块中获取所需的属性方法去加载。最大特性是按需加载,在编译时就完成模块加载,效率比其他方案高,无法引用模块本身(本身不是对象),但可拓展JS高级语法(宏与类型校验)。
node对ESM的原生支持
2017年10月31日,Node
发布了v8.9.0,从此只要在命令中加上 --experimental-modules
,Node
就可象征性地支持 ESM
了
node --experimental-modules index.js
但 低版本Node
依然无法直接支持 ESM
解析,还需在运行环境中“做些手脚”才行。
接着Node
发布了v13.2.0带来一些新特性,正式取消--experimental-modules启动参数。当然并不是删除--experimental-modules,而是在其原有基础上实现对ESM的实验性支持并默认启动。
--experimental-modules特性包括以下方面。
- 使用
type
指定模块方案- 在
package.json
中指定type
为commonjs
,则使用CJS - 在
package.json
中指定type
为module
,则使用ESM
- 在
- 使用
--input-type
指定入口文件的模块方案,与type
一样- 命令中加上
--input-type=commonjs
,则使用CJS
- 命令中加上
--input-type=module
,则使用ESM
- 命令中加上
- 支持新文件后缀
.cjs
- 文件后缀使用
.cjs
,则使用CJS
- 文件后缀使用
- 使用
--es-module-specifier-resolution
指定文件名称引用方式- 命令中加上
--es-module-specifier-resolution=explicit
,则引用模块时必须使用文件后缀(默认) - 命令中加上
--es-module-specifier-resolution=node
,则引用模块时无需使用文件后缀
- 命令中加上
- 使用
main
根据type
指定模块方案加载文件 - 在
package.json
中指定mian
后会根据type
指定模块方案加载文件
CJS/ESM判断方式
Node
要求使用ESM
的文件采用.mjs
后缀,只要文件中存在import/export
命令就必须使用.mjs
后缀。若不希望修改文件后缀,可在package.json
中指定type
为module
。基于此,若其他文件使用CJS
,就需将其文件后缀改成.cjs
。若在package.json
中未指定type
或指定type
为commonjs
,则以.js
为后缀的文件会被解析为CJS。
简而言之,mjs
文件使用ESM
解析,cjs
文件使用CJS
解析,js
文件使用基于package.json
指定的type
解析 (type=commonjs
使用CJS
,type=module
使用ESM
)。
当然还可通过命令参数处理,不过我认为这样做操作过多,所以就不讨论具体方法了。
刚才说了 Node v13.2.0
在默认情况下,会启动对 ESM
的实验支持,无需在命令中加上 --experimental-modules
参数。那 Node是如何区分CJS与ESM?简而言之,Node会将以下情况视为ESM。
- 文件后缀为
.mjs
- 文件后缀为
.js
且在package.json中
指定type
为module
- 命令中加上
--input-type=module
- 命令中加上
--eval cmd
esm中如何使用 __dirname
和 __filename
import { dirname } from "path";
import { fileURLToPath } from "url";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
console.log(__filename, __dirname,import.meta.url);
示例引用我开源的 @yangzw/bruce-us,其中 NodeType()
用于获取 Node
相关信息。
import { NodeType } from "@yangzw/bruce-us/dist/node";
console.log(NodeType());
查看node对应的npm版本
低版本node兼容
若需兼容 更低版本Node
,可在 package.json
中指定 babel
的 targets
。
{
"babel": {
"presets": [
["@babel/preset-env", { "targets": { "node": "8.0.0" } }]
]
}
}
服务管理
- nodemon适用于开发环境,调试代码更方便;
- forever适用于无需监控且访问量较小的Node服务;
- pm2适用于需监控且访问量较大的Node服务;
工具 | 稳定性 | 运行环境 | 并发量级 | 后台运行 | 代码监听 | 状态监控 | 日志管理 | 集群模式 |
---|---|---|---|---|---|---|---|---|
nodemon | 中等 | 开发环境 | 无 | ❌ | ✔️ | ❌ | ❌ | ❌ |
forever | 中等 | 生产环境 | 较小 | ✔️ | ✔️ | ❌ | ✔️ | ❌ |
pm2 | 较高 | 生产环境 | 较大 | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
pm2
pm2 是node的 process manager(进程管理)的第二大版本
主要功能是进程管理、日志管理、负载均衡、性能监控等
npm install -g pm2
命令 | 作用 |
---|---|
pm2 ecosystem | 创建一个ecosystem.config.js文件, |
pm2 link xxx xxx | 链接远程app.pm2.io网站创建的bucket,用于在线监控 |
pm2 logs/flush | 查看/清空日志 |
pm2 logs/flush 进程名/进程id | 查看/清空单个进程的日志 |
pm2 list/stop/restart/delete all | 查看/停止/重启/删除所有进程 |
pm2 show/stop/restart/delete/monit 进程id | 查看单个进程的状态 |
pm2 save/resurrect | 保存/恢复进程状态 |
pm2 scale 应用程序名/id num/+num/-num | 动态调整进程数,指定个数/新增个数/减少个数 |
pm2 start 进程文件路径 | 启动进程 |
pm2 start 进程文件路径 --max-memory-restart 200M | 启动进程,并限制最大重启内存为200m,超出就自动重启 |
pm2 start app.js -i 0 | 根据CPU核数启动相应数量的进程,0/max表示使用所有可用的CPU核心 |
nodemon自动重启服务
使用 nodemon, 在 package.jso
n中指定 nodemonConfig
相关配置,将 start
命令替换为 nodemon -x babel-node src/index.js
。
{
"nodemonConfig": {
"env": {
"NODE_ENV": "dev"
},
"execMap": {
"js": "node --harmony"
},
"ext": "js json",
"ignore": [
"dist/"
],
"watch": [
"src/"
]
}
}
前端部署
nginx 命令
./nginx #默认配置文件启动
./nginx -t #测试配置是否正确
./nginx -s reload #重启,加载默认配置文件
./nginx -c /usr/local/nginx/conf/nginx.conf #启动指定某个配置文件
./nginx -s stop #停止 #关闭进程
正反向代理
正向代理(服务器不知道被哪台客户端访问):客户端访问代理服务器并告诉它要访问的目标服务器,代理服务器帮客户端请求服务器
反向代理(客户端不知道访问哪台服务器):客户端访问代理服务器,代理服务器根据策略动态选择访问的服务器
查看电脑端口是否被占用
netstat -ano | findStr 80 //cmd命令,查找80端口进程
nginx 文件结构
nginx 安装目录通常位于
/usr/local/nginx
前端资源统一部署在 html 下
-nginx根目录
-config 配置文件,主要是config下的nagix.conf
-html 静态资源,存打包后的dist内的文件
-log 日志文件
部署静态资源
先切换到 nginx 目录, 配置好 nginx 配置, 启动 nginx
再切换到 nginx 的 html 目录, rm rf *清空所有资源
rz 上传资源
unzip 解压 zip 文件
ls 查看
yum install nginx //下载nginx
cd /
ls
cd /etc
ls
cd nginx
ls
//到达nginx目录后
service nginx start //启动nginx服务
vi nginx.conf //用vim编辑器打开nginx配置文件
i //插入
...
http{
...
server{
location / {
alias E:/project/trasen/dist; #打包以后的项目地址
try_files $uri $uri/ /index.html; #解决404问题
# proxy_pass http://192.168.74.237:5173/residentdoctor/; #开发时运行访问地址
# root 默认访问路径 //改成dist目录地址
# index index.html
# vue-router@3官方提供的history模式下404跳首页的配置
# try_files $uri $uri/ /index.html
}
# 代理带/api的请求
location /api {
proxy_pass 目标服务器 # (请求地址/api)时,需要代理到的目标服务器
}
}
}
:wq //保存并退出vim
service nginx restart //重启nginx服务