# 前端 DevOps 实施
# 流程和规范
## 规范自动化
- editorconfig,帮助开发人员定义和维护跨编辑器(或IDE)的统一的代码风格
- prettier,一个强势武断的代码格式化工具。
- husky,一个用于 Node.js 项目的快速安装 git hooks 的工具。
- lint-staged,在 git staged 阶段,执行各种 Linter 的工具。
- converntional-changelog,根据 Git 提交历史生成 CHANGELOG 的工具。
- commitLint, 对提交信息进行 Lint 的工具。
- styleLint,对 CSS 进行 Lint 的工具。
- remark-lint,使用 Remark 对 Markdown 进行 Lint
### 基于 Husky + LintStaged
流程:
1. 待提交的代码 `git add` 添加到暂存区;
2. 执行 `git commit`;
3. 注册到 git 钩子函数的 husky `pre-commit` 脚本被调用,执行 `lint-staged`;
4. 修改的文件依次执行 lint-staged 定义的任务;
5. lint 失败,则需要等待修复;
6. lint 成功,而执行 commit
7. 同理,对于 `pre-push` 也是如此。
## 代码规范
- Google JavaScript 规范:[Google JavaScript Style Guide](https://google.github.io/styleguide/jsguide.html) , 中文翻译:[Google JavaScript 代码风格指南](https://juejin.im/post/5bd01d4a518825781e647e90)
- Airbnb JavaScript 规范:[Airbnb JavaScript Style Guide() {](https://github.com/airbnb/javascript)
- JavaScript Standard 规范:[JavaScript 代码规范,自带 linter & 代码自动修正](https://github.com/standard/standard/blob/master/docs/README-zhcn.md)
### 自定义 Lint
#### StyleLint 相关
相关的库:
- 标准:[https://github.com/stylelint/stylelint-config-standard](https://github.com/stylelint/stylelint-config-standard) ,包含可能报错的 rule,code format 的 css 标准
- 推荐:[https://github.com/stylelint/stylelint-config-recommended](https://github.com/stylelint/stylelint-config-recommended) , 继承于 recommend,包含了一些常见的css书写标准,启用其他规则以强制执行一些 CSS 样式指南中的通用样式约定,包括:The Idiomatic CSS Principles,Google 的 CSS 样式指南,Airbnb 的样式指南和 @mdo 的代码指南。
#### ESLint 示例
相关的库:
- https://eslint.org/
- https://github.com/ElemeFE/eslint-config-elemefe
- https://github.com/AlloyTeam/eslint-config-alloy
- https://github.com/vuejs/eslint-plugin-vue
- https://github.com/yannickcr/eslint-plugin-react
- https://github.com/typescript-eslint/typescript-eslint
### 整合 Sonar
- https://github.com/SonarSource/eslint-plugin-sonarjs
- https://github.com/racodond/sonar-css-plugin
### React 示例
```javascript
module.exports = {
parser: 'babel-eslint',
extends: ['airbnb', 'prettier', 'plugin:compat/recommended'],
env: {
browser: true,
node: true,
es6: true,
mocha: true,
jest: true,
jasmine: true,
},
globals: {
ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION: true, // preview.pro.ant.design only do not use in your production ; preview.pro.ant.design 专用环境变量,请不要在你的项目中使用它。
page: true,
},
rules: {
'react/jsx-filename-extension': [1, { extensions: ['.js'] }],
'react/jsx-wrap-multilines': 0,
'react/prop-types': 0,
'react/forbid-prop-types': 0,
'react/jsx-one-expression-per-line': 0,
'import/no-unresolved': [2, { ignore: ['^@/', '^umi/'] }],
'import/no-extraneous-dependencies': [
2,
{
optionalDependencies: true,
devDependencies: ['**/tests/**.js', '/mock/**/**.js', '**/**.test.js'],
},
],
'import/no-cycle': 0,
'jsx-a11y/no-noninteractive-element-interactions': 0,
'jsx-a11y/click-events-have-key-events': 0,
'jsx-a11y/no-static-element-interactions': 0,
'jsx-a11y/anchor-is-valid': 0,
'linebreak-style': 0,
'jsx-a11y/media-has-caption': 0,
'react/no-array-index-key': 0,
},
settings: {
polyfills: ['fetch', 'Promise', 'Number.isNaN', 'Object.assign', 'Object.entries', 'URL'],
},
}
```
### Vue 示例
```javascript
module.exports = {
root: true,
env: {
browser: true,
node: true,
},
extends: [
'eslint:recommended',
'plugin:vue/recommended',
'plugin:import/recommended',
process.env.CI ? '' : 'plugin:prettier/recommended',
'prettier',
'prettier/vue',
],
rules: {
eqeqeq: [
'error',
'always',
{
null: 'ignore',
},
],
'consistent-return': "error",
'import/no-unresolved': 'off',
'import/first': 'error',
'import/order': [
'error',
{
'newlines-between': 'always-and-inside-groups',
},
],
'import/newline-after-import': 'error',
'import/no-duplicates': 'error',
},
parserOptions: {
parser: 'babel-eslint',
ecmaVersion: 2018,
sourceType: 'module',
},
overrides: [
{
files: ['**/__tests__/*.{j,t}s?(x)', '**/tests/unit/**/*.{j,t}s?(x)'],
env: {
jest: true,
},
},
],
globals: {
QC: false,
},
noInlineConfig: true,
}
```
### TypeScript 示例
```javascript
module.exports = {
root: true,
env: {
browser: true,
node: true,
},
extends: [
'eslint:recommended',
'plugin:vue/recommended',
// 'plugin:import/recommended',
'@vue/typescript/recommended',
// '@vue/prettier',
// '@vue/prettier/@typescript-eslint',
],
rules: {
'eqeqeq': [
'error',
'always',
{
null: 'ignore',
},
],
// Best Practices
// 'array-callback-return': 'error',
// 'class-methods-use-this': 'error',
'complexity': ['error', 50],
'curly': 'error',
'consistent-return': 'error',
'eqeqeq': ['error', 'always', { null: 'ignore' }],
'no-else-return': 'error',
'no-extend-native': 'error',
'no-implicit-coercion': 'error',
'no-sequences': 'error',
'no-throw-literal': 'error',
'no-useless-return': 'error',
'no-void': 'error',
'prefer-promise-reject-errors': 'error',
// 'radix': 'error',
'yoda': 'error',
// End of 'Best Practices'
// Stylistic
'array-bracket-newline': ['error', 'consistent'],
'array-bracket-spacing': 'error',
'comma-dangle': ["error", {
"arrays": "always-multiline",
"objects": "always-multiline",
"imports": "always-multiline",
"exports": "always-multiline",
"functions": "never"
}],
'eol-last': 'error',
// End of 'Stylistic'
// 'import/no-unresolved': 'off',
// 'import/first': 'error',
// 'import/order': [
// 'error',
// {
// 'newlines-between': 'always-and-inside-groups',
// },
// ],
// 'import/newline-after-import': 'error',
// 'import/no-duplicates': 'error',
'vue/no-v-html': 'off',
'@typescript-eslint/camelcase': 'off',
'@typescript-eslint/no-use-before-define': 'off',
'@typescript-eslint/no-empty-function': 'off',
'@typescript-eslint/no-this-alias': 'off',
'@typescript-eslint/no-explicit-any': 'warn',
'@typescript-eslint/no-non-null-assertion': 'off',
'indent': 'off',
'@typescript-eslint/indent': ['error', 2],
'semi': 'off',
'@typescript-eslint/semi': ['error', 'never'],
'quotes': 'off',
'@typescript-eslint/quotes': ['error', 'single'],
},
parserOptions: {
parser: '@typescript-eslint/parser',
},
overrides: [
{
files: ['**/__tests__/*.{j,t}s?(x)', '**/tests/unit/**/*.{j,t}s?(x)'],
env: {
jest: true,
},
},
{
files: ['vue.config.js'],
rules: {
'@typescript-eslint/no-var-requires': 'off',
},
},
],
globals: {
QC: false,
},
noInlineConfig: true,
}
```
# 前端测试
## 通用 BDD
| x | Cucumber | Gauge | Robot |
| ------------ | --------------------------------- | ---------------------------------- | ------------------------------ |
| 编程语言支持 | Java,Ruby,JavaScript 等 13 种语言 | Java, JavaScript, Ruby 等 6 种语言 | Python, Java, C |
| 支持的系统 | 所有主流系统 | 所有主流系统 | 所有主流系统 |
| 多语言支持 | UTF-8 | UTF-8 | 用户关键字及用例层面支持 UTF-8 |
| 中文社区支持 | 完善 | 待完善 | 完善 |
| Report | JS 不支持 HTML | 粗粒度 | 细粒度 |
| 失败时截图 | 不支持 | 支持 | 支持 |
## Angular
自带
- Unit: Jasmine
- E2E: Protractor
## Vue
```bash
vue add @vue/unit-jest
```
### 单元测试(UT)
> [Vue CLI](https://cli.vuejs.org/zh/) 拥有开箱即用的通过 [Jest](https://github.com/facebook/jest) 或 [Mocha](https://mochajs.org/) 进行单元测试的内置选项。我们还有官方的 [Vue Test Utils](https://vue-test-utils.vuejs.org/zh/) 提供更多详细的指引和自定义设置。
## 快照测试
### DOM Snapshots
Jest: [https://jestjs.io/docs/zh-Hans/next/snapshot-testing](https://jestjs.io/docs/zh-Hans/next/snapshot-testing)
```javascript
describe('TodoItem snapshot test', () => {
it('first render', () => {
const wrapper = shallowMount(TodoItem, {
propsData: {
item: {
finished: true,
content: 'test TodoItem',
},
},
});
expect(wrapper.html()).toMatchSnapshot();
});
});
```
示例结果:
```javascript
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders correctly 1`] = `
Facebook
`;
```
### Visual Snapshots
- [Wraith](https://github.com/BBC-News/wraith) is a screenshot comparison tool, created by developers at BBC News.
- [Hermione](https://github.com/gemini-testing/hermione) is a utility for integration testing of web pages using WebdriverIO v4 and Mocha.
- [Differencify](https://github.com/NimaSoroush/differencify) is a library for visual regression testing via comparing your local changes with reference screenshots of your website.
更多工具:[关于前端测试](https://juejin.im/post/5daac11fe51d45252a3df4db#heading-39)
## E2E 测试
- [TestCafe](https://devexpress.github.io/testcafe/)
- [Cucumber.js](https://github.com/cucumber/cucumber-js)
- [Nightwatch](https://nightwatchjs.org)
- [Puppeteer](https://github.com/puppeteer/puppeteer)
- [Cypress](https://www.cypress.io/)
### TestCafe
```bash
npm install -g testcafe
```
```javascript
import { Selector } from 'testcafe';
fixture`Getting Started`.page`http://devexpress.github.io/testcafe/example`;
test('My first test', async (t) => {
await t.typeText('#developer-name', 'John Smith').click('#submit-button');
});
```
### Nightwatch
Install -> [Installation](https://nightwatchjs.org/gettingstarted/installation/)
```javascript
module.exports = {
'Demo test ecosia.org': function (browser) {
browser
.url('https://www.ecosia.org/')
.waitForElementVisible('body')
.assert.titleContains('Ecosia')
.assert.visible('input[type=search]')
.setValue('input[type=search]', 'nightwatch')
.assert.visible('button[type=submit]')
.click('button[type=submit]')
.assert.containsText('.mainline-results', 'Nightwatch.js')
.end();
},
};
```
### Puppeteer
```bash
npm i puppeteer
```
```javascript
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://example.com');
await page.screenshot({ path: 'example.png' });
await browser.close();
})();
```
### Cucumber.js
```bash
yarn add -D chai@latest cucumber@latest
npm i -D chai@latest cucumber@latest
```
Steps
```gherkin
# features/simple_math.feature
Feature: Simple maths
In order to do maths
As a developer
I want to increment variables
Scenario: easy maths
Given a variable set to 1
When I increment the variable by 1
Then the variable should contain 2
Scenario Outline: much more complex stuff
Given a variable set to
When I increment the variable by
Then the variable should contain
Examples:
| var | increment | result |
| 100 | 5 | 105 |
| 99 | 1234 | 1333 |
| 12 | 5 | 17 |
```
```javascript
// features/support/steps.js
const { Given, When, Then } = require('cucumber');
const { expect } = require('chai');
Given('a variable set to {int}', function (number) {
this.setTo(number);
});
When('I increment the variable by {int}', function (number) {
this.incrementBy(number);
});
Then('the variable should contain {int}', function (number) {
expect(this.variable).to.eql(number);
});
```
### Cypress
```bash
npm install cypress
```
示例:
```javascript
describe('My First Test', () => {
it('clicking "type" navigates to a new url', () => {
cy.visit('https://example.cypress.io');
cy.contains('type').click();
// Should be on a new URL which includes '/commands/actions'
cy.url().should('include', '/commands/actions');
});
});
```
# 前端架构拆分
方式:
- 微应用化
- 微前端:
## 微前端
> 微前端架构是一种类似于微服务的架构,它将微服务的理念应用于浏览器端,即将 Web 应用由单一的单体应用转变为多个小型前端应用聚合为一的应用。
> 由此带来的变化是,这些前端应用可以独立运行、独立开发、独立部署。以及,它们应该可以在共享组件的同时进行并行开发——这些组件可以通过 NPM 或者 Git Tag、Git Submodule 来管理。
详细:[微前端如何落地](https://www.infoq.cn/article/xm_AaiOTXmLpPgWvX9y9)
## 微应用化
> 微应用化与微前端架构相当的类似,它们在开发时都是独立应用,在构建时又可以按照需求单独加载。如果以微前端的单独开发、单独部署、运行时聚合的基本思想来看,微应用化就是微前端的一种实践。只是使用微应用化意味着:我们只能使用唯一的一种前端框架。如果从框架不限的角度来定义,怕是离微前端有些远,不过大团队怕是不会想同时支持多个前端框架。
详细见:[微前端:微应用化](https://www.phodal.com/blog/architecutre-in-word-design-micro-application-frontend-architecture/)
# 前端监控
参考来源:《[前端如何搞监控总结篇](https://www.yuque.com/giscafer/felearn/wymb2u#d6Kig)》