From fa2708e8a441c91877b71fb1bf9109e912ef323b Mon Sep 17 00:00:00 2001 From: Frankie <1426203851@qq.com> Date: Sun, 17 Mar 2024 16:48:52 +0800 Subject: [PATCH] docs: update issue 123 --- archives/2023/123.md | 530 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 530 insertions(+) create mode 100644 archives/2023/123.md diff --git a/archives/2023/123.md b/archives/2023/123.md new file mode 100644 index 0000000..dc5f2eb --- /dev/null +++ b/archives/2023/123.md @@ -0,0 +1,530 @@ +--- +title: 使 Prettier 一键格式化 WXSS(上集) +number: '#123' +link: 'https://github.com/toFrankie/blog/issues/123' +created_at: '2023-02-25 21:14:56' +updated_at: '2024-03-17 16:48:49' +labels: + - 编码规范 + - 小程序 + - 部分废弃 + - '2020' +--- +## 写在前面 + +最近,在处理部门前端项目由 SVN 迁移 Git 的事情。由于历史代码在此之前并没有引入类似 [ESLint](http://eslint.cn/)、[Prettier](https://prettier.io/) 的代码检查或者格式约束等工具。 + +目前部门仅剩我一人维护这十几个小程序、H5 前端项目。现在只要接触以前没有经手的项目,就头疼不想改,很无奈,谁让我是一个打工人呢! + +本文将会结合 ESLint、Prettier、husky、lint-stage 展开介绍,旨在在代码格式化、代码检查上减少时间浪费。 + +完整示例:[wechat_applet_demo](https://github.com/toFrankie/wechat_applet_demo.git) + +共三篇: + +- [使 Prettier 一键格式化 WXSS(上集)](https://github.com/toFrankie/blog/issues/123) +- [使 Prettier 一键格式化 WXSS(下集)](https://github.com/toFrankie/blog/issues/124) +- [使 Prettier 一键格式化 WXSS(结局篇)](https://github.com/toFrankie/blog/issues/125) + +扩展篇: + +- [Git Commit 规范](https://github.com/toFrankie/blog/issues/101) + + +## 编辑器插件 + +使用到 VS Code 插件: + +- [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) +- [Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) + +相关配置以保存时自动格式化: + +```json5 +{ + "files.associations": { + "*.wxss": "css", + "*.wxs": "javascript", + "*.acss": "css", + "*.axml": "html", + "*.wxml": "html", + "*.swan": "html" + }, + "files.trimTrailingWhitespace": true, + "eslint.workingDirectories": [{ "mode": "auto" }], + "eslint.enable": true, // 是否开启 vscode 的 eslint + "eslint.options": { + // 指定 vscode 的 eslint 所处理的文件的后缀 + "extensions": [".js", ".ts", ".tsx"] + }, + "eslint.validate": ["javascript"], + "editor.codeActionsOnSave": { + "source.fixAll.eslint": true + }, + "git.ignoreLimitWarning": true +} +``` + + +## 开始 + +### 安装 ESLint、Prettier 相关依赖 + +避免重复造轮子,社区上已有最佳实践,选择它们即可,比如 airbnb、standard、prettier 等。如果团队有特殊要求,自定义一些规则即可。 + +这里选择的是国内腾讯 AlloyTeam 团队出品的 [eslint-config-alloy](https://alloyteam.github.io/eslint-config-alloy/?language=zh-CN)。 + + +其实他们团队最开始使用 Airbnb 规则,但是由于它过于严格,部分规则还是需要个性化,导致后来越改越多,最后决定重新维护一套。经过两年多的打磨,现在 eslint-config-alloy 已经非常成熟了。 + +我选择它的几点原因: + +* 适用于 React/Vue/Typescript 项目 +* 样式相关规则由 Prettier 管理 +* 有[中文文档](https://github.com/AlloyTeam/eslint-config-alloy/blob/master/README.zh-CN.md)和网站[示例](https://alloyteam.github.io/eslint-config-alloy/?language=zh-CN&rule=base) +* 更新快,且拥有官方维护的 vue、typescript、react+typescript 规则 + +```shell +$ yarn add --dev babel-eslint@10.0.3 +$ yarn add --dev eslint@6.7.1 +$ yarn add --dev eslint-config-alloy@3.7.1 +$ yarn add --dev eslint-config-prettier@6.10.0 +$ yarn add --dev eslint-plugin-prettier@3.1.4 +$ yarn add --dev prettier@2.0.5 +$ yarn add --dev prettier-eslint-cli@5.0.0 +``` + +### ESLint、Prettier 配置文件 + +> 按需配置,仅供参考。 + +`.eslintrc.js` 👇 + +```js +module.exports = { + root: true, + parser: 'babel-eslint', + env: { + browser: true, + es6: true, + node: true, + commonjs: true + }, + extends: ['alloy'], + plugins: ['prettier'], + globals: { + Atomics: 'readonly', + SharedArrayBuffer: 'readonly', + __DEV__: true, + __WECHAT__: true, + __ALIPAY__: true, + App: true, + Page: true, + Component: true, + Behavior: true, + wx: true, + my: true, + swan: true, + getApp: true, + getCurrentPages: true + }, + parserOptions: { + ecmaVersion: 2018, + sourceType: 'module' + }, + rules: { + 'no-debugger': 2, + 'no-unused-vars': 1, + 'no-var': 0, + 'no-param-reassign': 0, + 'no-irregular-whitespace': 0, + 'no-useless-catch': 1, + 'max-params': ['error', 3], + 'array-callback-return': 1, + eqeqeq: 0, + indent: ['error', 2, { SwitchCase: 1 }] + } +} +``` + +`.prettierrc.js` 👇 + +```js +module.exports = { + printWidth: 120, + tabWidth: 2, + useTabs: false, + semi: false, + singleQuote: true, + + // 对象的 key 仅在必要时用引号 + quoteProps: 'as-needed', + + // jsx 不使用单引号,而使用双引号 + jsxSingleQuote: false, + + // 末尾不需要逗号 + trailingComma: 'none', + + // 大括号内的首尾需要空格 + bracketSpacing: true, + + // jsx 标签的反尖括号需要换行 + jsxBracketSameLine: false, + + // 箭头函数,只有一个参数的时候,无需括号 + arrowParens: 'avoid', + + // 每个文件格式化的范围是文件的全部内容 + rangeStart: 0, + + rangeEnd: Infinity, + + // 不需要写文件开头的 @prettier + requirePragma: false, + + // 不需要自动在文件开头插入 @prettier + insertPragma: false, + + // 使用默认的折行标准 + proseWrap: 'preserve', + + // 根据显示样式决定 html 要不要折行 + htmlWhitespaceSensitivity: 'css', + + // 换行符使用 lf + endOfLine: 'lf' +} +``` + +### 配置 ESLint、Prettier 忽略文件 + +> 按需配置,仅供参考。 + +`.eslintignore` 👇 + +```.gitignore +# .eslintignore + +*.min.js +typings +node_modules +``` + +`.prettierignore` 👇 + +```.gitignore +*.min.js +/node_modules +/dist +# OS +.DS_Store +.idea +.editorconfig +.npmrc +package-lock.json +# Ignored suffix +*.log +*.md +*.svg +*.png +*ignore +## Built-files +.cache +dist +``` +### EditorConfig 配置文件 + +它是用来抹平不同编辑器之间的差异的。同样放置在项目根目录下。 + +> 按需配置,仅供参考。 + +`.editorconfig` 👇 + +```ini +# .editorconfig +# http://editorconfig.org +# https://github.com/editorconfig/editorconfig/wiki/EditorConfig-Properties + + +# 根目录的配置文件,编辑器会由当前目录向上查找,如果找到 `roor = true` 的文件,则不再查找 +root = true + +# 匹配所有的文件 +[*] +# 缩进风格:space +indent_style = space +# 缩进大小 2 +indent_size = 2 +# 换行符 lf +end_of_line = lf +# 字符集 utf-8 +charset = utf-8 +# 不保留行末的空格 +trim_trailing_whitespace = true +# 文件末尾添加一个空行 +insert_final_newline = true +# 运算符两遍都有空格 +spaces_around_operators = true + +# 对所有的 js 文件生效 +[*.js] +# 字符串使用单引号 +quote_type = single + +[*.md] +trim_trailing_whitespace = false +``` + +### 添加 NPM Scripts + +添加三条脚本指令: + +```json +{ + "scripts": { + "eslint": "eslint ./ --ext .js", + "eslint:fix": "eslint --fix ./ --ext .js", + "prettier:fix": "prettier --config .prettierrc.js --write './**/*.{js,css,less,scss,json}'" + } +} +``` + + +这样就可以一键格式化和修复了,注意 ESLint 使用 `--fix` 只能修复一部分问题,部分需手动解决。 + +```json +{ + "name": "wechat_applet_demo", + "version": "1.0.0", + "description": "微信小程序 Demo", + "main": "app.js", + "repository": "git@github.com:toFrankie/wechat_applet_demo.git", + "author": "Frankie <1426203851@qq.com>", + "license": "MIT", + "private": true, + "scripts": { + "eslint": "eslint ./ --ext .js", + "eslint:fix": "eslint --fix ./ --ext .js", + "prettier:fix": "prettier --config .prettierrc.js --write './**/*.{js,css,less,scss,json}'" + }, + "devDependencies": { + "babel-eslint": "10.0.3", + "eslint": "6.7.1", + "eslint-config-alloy": "3.7.1", + "eslint-config-prettier": "6.10.0", + "eslint-plugin-prettier": "3.1.4", + "prettier": "2.0.5", + "prettier-eslint-cli": "5.0.0" + } +} +``` + +## 还没完 + +接下来涉及 Gulp.js 内容,是为了让 Prettier 处理 Gulp.js 转换出来的 `css`,以达到最终 Prettier 格式化处理 `wxss` 的目的。 + + +> 上述方式走了一些弯路,其实通过 [Overrides](https://prettier.io/docs/en/configuration.html) 配置是可以指定 `.wxss` 文件使用指定的解析器的。换句话说,我们可以在处理 `.wxss` 文件时使用 CSS 解析器去处理它就好了,具体看[结局篇](https://github.com/toFrankie/blog/issues/125)。 + + +本文最想分享的是下面的内容,前面很简单。 + +Prettier 支持的 JavaScript、JSX、Angular、Vue、Flow、TypeScript、CSS、Less、Scss、HTML、JSON、GraphQL、Markdown(GFM、MDX)、YAML 的代码格式化。 + +但其实是不能识别 `wxss`、`acss` 等小程序特有的层叠样式,尽管它们规则与 CSS 无异,但是 Prettier 并没有解析器去解析它们。 + +我们试图去调整脚本命令为(添加 `*.wxss` 扩展名的文件): +```json +{ + "scripts": { + "prettier:fix": "prettier --config .prettierrc.js --write './**/*.wxss'", + } +} +``` +然后去执行的时候就会报错,如下: +> [error] No parser could be inferred for file: app.wxss + +既然这样走不通的话,总不能利用 VS Code 的 Prettier 插件一个一个地去格式化 `*.wxss` 的文件吧,那样工作量太大了,不符合我们“偷懒”的做法。下面使用 [Gulp](https://www.gulpjs.com.cn/) 来处理。 + +## Gulp + +简单说下 Gulp 的工作方式,它使用的是 Node.js 中的 `stream`(流),首先获取到需要的 `stream`,然后通过 `stream` 的 `pipe()` 方法把流导入到你想要的地方。比如 Gulp 插件中,经过插件处理后的流又可以导入到其他插件汇总,当然也可以把流写入文件中,所以 Gulp 是以 `stream` 为媒介的,它不需要频繁的生成临时文件,这也是 Gulp 的速度比 [Grunt](https://www.gruntjs.net/) 快的一个原因。 + +>我刚开始时的想法是:首先将 `wxss`(`acss`)转换并导出为 `css`,接着删除 `wxss`(`acss`)文件,再者使用 Prettier 对 `css` 文件进行格式化,转回 `wxss`(`acss`)之后,再删除掉 `css` 文件。这个过程会频繁的生成临时文件,思路是有点像 Grunt。 +> +> 但是了解了 Gulp 的思想后,其实它帮我们省掉了频繁增删文件的环节,全部放在内存中操作,也会更快一些,所以此前的方案被我否掉了。 + +下面我们只用到 Gulp 的其中两个 API, [`gulp.src()`](https://www.gulpjs.com.cn/docs/api/src/) 和 [`gulp.dest()`](https://www.gulpjs.com.cn/docs/api/dest/)。 + +### gulp.src() + +这个方法是用来获取流的,但要注意这个流里面的内容不是原始的文件流,而是一个虚拟文件对象流([Vinyl files](https://www.gulpjs.com.cn/docs/api/vinyl/)),这个虚拟文件对象中存储着原始文件的路径、文件名、内容等信息。(这里不深入,点到为止,有兴趣自行了解) + +> 语法:gulp.src(globs[, options]) + +* **globs**:是文件匹配模式,用来匹配文件路径(包括文件名) +* **options**:为可选参数,通常情况我们不需要用到 + +*关于参数详细说明,请看[文档](https://www.gulpjs.com.cn/docs/api/src/)。 + +### gulp.dest() + +该方法是用来写文件的 + +> gulp.dest(path[, options]) + +* **path**:是写入文件的路径 +* **options**:为可选参数,通常情况我们不需要用到 + +要想使用好 `gulp.dest()` 这个方法,就要理解给它传入的路径参数与最终生成的文件的关系。 + +Gulp 的使用流程一般是:首先通过 `gulp.src()` 方法获取到我们想要处理的文件流,然后把文件流通过 `pipe()` 方法导入到 Gulp 的插件中,最后把经过插件处理后的流再通过 `pipe()` 方法导入到 `gulp.dest()` 中,`gulp.dest()` 方法则把流中的内容写入到文件中。 + +这里需要弄清楚的一点是,我们给 `gulp.dest()` 传入的路径参数,只能用来指定要生成的文件的目录,而不能指定生成文件的文件名,它生成文件的文件名使用的是导入到它的文件流自身的文件名,所以生成的文件名是由导入到它的文件流决定的,即使我们给它传入一个带有文件名的路径参数,然后它也会把这个文件名当做是目录名,例如: +```js +const gulp = require('gulp') +gulp.src('script/jquery.js').pipe(gulp.dest('dist/foo.js')) + +// 最终生成的文件路径为 dist/foo.js/jquery.js,而不是 dist/foo.js +``` + +若需要修改文件名,需要使用插件 [gulp-rename](https://www.npmjs.com/package/gulp-rename)。 +* 关于上述 Gulp 的 API 与方法说明,主要参考自[官方文档](https://www.gulpjs.com.cn/)与无双的一篇[文章](https://www.cnblogs.com/2050/p/4198792.html)。 + + +## Gulp 配置 + +首先,安装 Gulp 相关依赖包。 + +```shell +$ yarn add --dev gulp@4.0.2 +$ yarn add --dev gulp-clean@0.4.0 +$ yarn add --dev gulp-debug@4.0.0 +$ yarn add --dev gulp-prettier@3.0.0 +$ yarn add --dev gulp-rename@2.0.0 +``` + +接着,我们在项目根目录下创建一个 `gulpfile.js` 文件。思路如下: + +> 使用 `gulp.src()` 获取流,然后使用 Gulp 插件对流分别作重命名(gulp-rename)、格式化(`gulp-prettier`)、再重命名回来(`gulp-rename`)、最后导出(`gulp.dest()`)。过程中有利用 `gulp-debug` 插件来查看一些信息。 + +这里我对微信小程序、支付宝小程序的层叠样式都处理了。 + +```js +// gulpfile.js +const { series, parallel, src, dest } = require('gulp') +const rename = require('gulp-rename') +const debug = require('gulp-debug') +const clean = require('gulp-clean') +const prettier = require('gulp-prettier') +const config = require('./.prettierrc') + +// wxss 一键格式化 +const wxssPrettier = () => { + return src('./**/*.wxss') + .pipe( + // 可以利用插件,查看一些 debug 信息 + debug() + ) + .pipe( + // 重写扩展名为 css,才能被 Prettier 识别解析 + rename({ + extname: '.css' + }) + ) + .pipe( + // Prettier 格式化 + prettier(config) + ) + .pipe( + // 重新将扩展名改为 wxss + rename({ + extname: '.wxss' + }) + ) + .pipe( + // 导出文件 + dest(__dirname) + ) +} + +// acss 一键格式化 +const acssPrettier = () => { + return src('./**/*.acss') + .pipe(debug()) + .pipe( + rename({ + extname: '.css' + }) + ) + .pipe(prettier(config)) + .pipe( + rename({ + extname: '.acss' + }) + ) + .pipe(dest(__dirname)) +} + +// 这里导出多个 task,通过 gulp xxx 就能来调用了,如 gulp all +// 关于 series、parallel API 分别是按顺序执行(同步)、同时执行(并行) +module.exports = { + all: parallel(wxssPrettier, acssPrettier), + wxss: wxssPrettier, + acss: acssPrettier +} +``` + +在配置下 NPM Script: + +```json5 +{ + "scripts": { + "prettier:wxss": "gulp wxss", + "prettier:accs": "gulp acss", + "prettier:wxss:acss": "gulp all" + } +} +``` + +结果如下,说明配置成功了。 + +![](https://upload-images.jianshu.io/upload_images/5128488-7412e44f142f43d8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + +## Git-Hooks + +上面已经实现了对 `wxss`、`acss` 扩展名的文件进行一键格式化了。 + +还可以“更懒”一些,利用 [git-hooks](https://git-scm.com/book/zh/v2/%E8%87%AA%E5%AE%9A%E4%B9%89-Git-Git-%E9%92%A9%E5%AD%90) 我们可实现在 `commit` 之前,对项目进行 ESLint、Prettier 检测和格式化,一旦出现错误,将停止 `commit` 操作。 + +本文篇幅过长,放到[下篇](https://github.com/toFrankie/blog/issues/124)接着... + + +## 插个题外话 + +由于本项目的 npm 包仅用于代码检查与格式化,并未参与页面代码逻辑中。所以我在小程序本地项目配置文件中添加上[打包配置选项](https://developers.weixin.qq.com/miniprogram/dev/devtools/projectconfig.html)。 + +> packOptions 用以配置项目在打包过程中的选项。打包是预览、上传时对项目进行的必须步骤。 +> +>目前可以指定 `packOptions.ignore` 字段,用以配置打包时对符合指定规则的文件或文件夹进行忽略,以跳过打包的过程,这些文件或文件夹将不会出现在预览或上传的结果内。 + +需要注意的是支付宝小程序,在编写本文时还未支持类似 `ignore` 选项。 + +```json5 +// project.config.json +{ + "packOptions": { + "ignore": [ + { + "type": "regexp", + "test": "\\.md$" + }, + { + "type": "folder", + "test": "node_modules" + } + ] + } +} +```