Skip to content

工程化

🕒 Published at:

工欲善其事,必先利其器。做好一个项目前期准备工作很重要,前期准备无非就是合理规划项目结构、按需编写构建代码、批量创建入口文件、按需封装工具函数等,而按需封装工具函数是作为一个项目基础的重中之重,是很有必要提前规划的。

这些经常重复使用的工具函数,包括但不限于浏览器类型、格式时间差、URL参数反序列化、过滤XSS等。为了避免应用开发时重复的复制粘贴操作带来不必要的麻烦,有些开发者都会将这些工具函数根据功能划分并统一封装,再发布到Npm公有仓库。每次使用时直接安装,提升开发效率,将时间用在正确的事情中。

配好的配置示例

vite

常规配置

js
// 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

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

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

html
// 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

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

js
import { defineStore } from 'pinia'  
  
export const useThemeStore = defineStore({  
  id: 'theme',  
  state: () => ({  
    themeColor: '#3a77ff'  
  }),  
  actions: {  
    setTheme(color) {  
      this.themeColor = color  
    }  
  }  
})

webpack

统一规范

示例

js

-->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

javascript
//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

校验样式问题

javascript
//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

统一提交规范

js
//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 : 本地暂存代码检查工具
javascript
//此命令将根据项目  `package.json`  依赖项中的代码质量工具安装并配置 husky 和 lint-staged,因此请确保在执行此操作之前安装( `npm install --save-dev` )并配置所有代码质量工具,例如 Prettier 和 ESLint。
npx mrm@2 lint-staged
javascript

//根目录创建 .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的提交规范
javascript
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)]

javascript
//如需修改@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中文文档

如果自定义提交信息校验就不需要@commitlint/config-conventional

commitlint可以声明本地插件,但是同时只能有一个本地插件

javascript
//添加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命令触发

javascript
//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 配置的核心文件,常见配置如下:

  1. stages:定义了流水线的不同阶段(作业),按顺序依次执行。
  2. variables:定义了全局变量,这些变量可以在整个 gitlab-ci.yml 文件中使用。
  3. before_script:在每个作业之前运行的命令。
  4. jobs(可不显示声明,满足job结构的配置项会自动识别为job):定义每个作业做的事情,这里build_job作业就是定义build阶段做的事情。
    1. stage:定义这个作业对应的流水线阶段
    2. artifacts:用于定义构建产物,可以在后续阶段或作业中使用。
      1. paths:构建产物所在目录
    3. cache:缓存依赖项或构建结果,以加快后续的构建速度。
      1. key:
        1. key相当于给缓存命别名,缓存空间具有这个名字的缓存就会使用缓存
        2. 默认是default,所有未指定缓存键的job将共享同一个缓存空间
      2. policy:默认 pull-push 表示先恢复缓存,然后更新缓存
      3. paths:应该缓存的目录
    4. dependencies:定义作业之间的依赖关系,也就是这个作业依赖哪个作业。
    5. services:为作业提供辅助服务,如数据库服务。
sh
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

-CJSESM
语法类型动态静态
关键声明requireexport与import
加载方式运行时加载编译时加载
加载行为同步加载异步加载
书写位置任何位置顶层位置
指针指向this指向当前模块this指向undefined
执行顺序首次引用时加载模块
再次引用时读取缓存
引用时生成只读引用
执行时才是正式取值
属性引用基本类型属于复制不共享
引用类型属于浅拷贝且共享
所有
  • 运行时加载指整体加载模块生成一个对象,再从对象中获取所需的属性方法去加载。最大特性是全部加载,只有运行时才能得到该对象,无法在编译时做静态优化。

  • 编译时加载指直接从模块中获取所需的属性方法去加载。最大特性是按需加载,在编译时就完成模块加载,效率比其他方案高,无法引用模块本身(本身不是对象),但可拓展JS高级语法(宏与类型校验)。

node对ESM的原生支持

2017年10月31日,Node 发布了v8.9.0,从此只要在命令中加上 --experimental-modulesNode 就可象征性地支持 ESM

bash
node --experimental-modules index.js

低版本Node 依然无法直接支持 ESM 解析,还需在运行环境中“做些手脚”才行。

接着Node发布了v13.2.0带来一些新特性,正式取消--experimental-modules启动参数。当然并不是删除--experimental-modules,而是在其原有基础上实现对ESM的实验性支持并默认启动

--experimental-modules特性包括以下方面。

  1. 使用 type 指定模块方案
    1. package.json 中指定 typecommonjs,则使用CJS
    2. package.json 中指定 typemodule,则使用ESM
  2. 使用 --input-type 指定入口文件的模块方案,与 type 一样
    1. 命令中加上 --input-type=commonjs,则使用 CJS
    2. 命令中加上 --input-type=module,则使用 ESM
  3. 支持新文件后缀 .cjs
    1. 文件后缀使用 .cjs,则使用 CJS
  4. 使用 --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中指定typemodule。基于此,若其他文件使用CJS,就需将其文件后缀改成.cjs。若在package.json中未指定type或指定typecommonjs,则以.js为后缀的文件会被解析为CJS。

简而言之,mjs文件使用ESM解析,cjs文件使用CJS解析,js文件使用基于package.json指定的type解析 (type=commonjs使用CJStype=module使用ESM)

当然还可通过命令参数处理,不过我认为这样做操作过多,所以就不讨论具体方法了。

刚才说了 Node v13.2.0 在默认情况下,会启动对 ESM 的实验支持,无需在命令中加上 --experimental-modules 参数。那 Node是如何区分CJS与ESM?简而言之,Node会将以下情况视为ESM

  1. 文件后缀为 .mjs
  2. 文件后缀为 .js 且在 package.json中 指定 typemodule
  3. 命令中加上 --input-type=module
  4. 命令中加上 --eval cmd

esm中如何使用 __dirname__filename

javascript
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 相关信息。

js
import { NodeType } from "@yangzw/bruce-us/dist/node";

console.log(NodeType());

查看node对应的npm版本

使用Node Releases

低版本node兼容

若需兼容 更低版本Node,可在 package.json 中指定 babeltargets

javascript
{
    "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

json
{
    "nodemonConfig": {
        "env": {
            "NODE_ENV": "dev"
        },
        "execMap": {
            "js": "node --harmony"
        },
        "ext": "js json",
        "ignore": [
            "dist/"
        ],
        "watch": [
            "src/"
        ]
    }
}

前端部署

nginx 命令

java
./nginx #默认配置文件启动
./nginx -t #测试配置是否正确
./nginx -s reload #重启,加载默认配置文件
./nginx -c /usr/local/nginx/conf/nginx.conf #启动指定某个配置文件
./nginx -s stop #停止 #关闭进程

正反向代理

正向代理(服务器不知道被哪台客户端访问):客户端访问代理服务器并告诉它要访问的目标服务器,代理服务器帮客户端请求服务器

反向代理(客户端不知道访问哪台服务器):客户端访问代理服务器,代理服务器根据策略动态选择访问的服务器

查看电脑端口是否被占用

java
netstat -ano | findStr 80 //cmd命令,查找80端口进程

nginx 文件结构

nginx 安装目录通常位于

/usr/local/nginx

前端资源统一部署在 html 下

java
-nginx根目录
    -config 配置文件,主要是config下的nagix.conf
    -html   静态资源,存打包后的dist内的文件
    -log    日志文件

部署静态资源

先切换到 nginx 目录, 配置好 nginx 配置, 启动 nginx

再切换到 nginx 的 html 目录, rm rf *清空所有资源

rz 上传资源

unzip 解压 zip 文件

ls 查看

nginx
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服务