前言
本文将会结合 ESLint、Prettier、husky、lint-stage 等工具使得项目一键化操作,减少在格式化、代码检查等操作上浪费时间,因为大前端真的太多东西学了,不学会“偷懒”的话,我们就要落后更多了。
本系列文章的示例 Demo 在这里 GitHub: wechat_applet_demo。
分为三篇文章介绍:
- 使Prettier一键格式化WXSS(上集)
- 使Prettier一键格式化WXSS(下集)
- 使Prettier一键格式化WXSS(结局篇)
扩展篇
- Git Commit 规范
最近在做公司部门前端项目由 SVN 迁移 Git 的事情,由于历史代码在此之前并没有引入类似 ESLint、Prettier 的代码检查或者格式约束等工具。
目前部门仅剩我一人维护这十几个小程序、H5 前端项目。现在只要接触以前那些没有经手的项目,就头疼不想改。虽然思想是这样,但很无奈,谁让我只是一个“打工仔”呢!
吐槽完,入正题。
正文
一、必备
1. 新建一个微信小程序项目
此处过于简单省略一万字...
# 或者克隆 wechat_applet_demo 项目下来 $ cd your_folder $ git clone git@github.com:toFrankie/wechat_applet_demo.git
2. 使用 yarn 作为包管理工具
yarn
相关的安装不在本系列教程,相信你们都懂。也不再赘述,自行搜索。
3. 使用 Visual Studio Code 作为编辑器
虽然从业有一段时间了,不好意思,前端开发我只用 VS Code,将来好长一段时间应该还是它。至于什么 WebStorm、Atom、Sublime Text 等,用过但现在已经不会了。
Anyway,什么开发工具不重要,自己用着舒服就好。
下面介绍几个与本项目相关的 VS Code 插件
ESLint:自动检测 ESLint Rule,不符合规则时,在编辑页面会有警告 ️
Prettier - Code formatter:可用于格式化
按照以上两个插件之后,需要对编辑器做添加一些配置。
考虑到多人开发的场景,而每个人的开发工具配置不尽相同,所以我把以下配置放到项目根目录下中,并将其加入 Git 版本控制中,这样每个人拿到项目都有此配置了。
路径是:your_project/.vscode/settings.json
{ "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 }
二、要开始了
1. yarn
初始化生成 package.json
:
image.png
2. 安装 ESLint、Prettier 相关依赖
若要使用 ESLint,往往需要配置很多繁杂的 rules 规则,如果每个人都要这种做的话,显然会耗费很多精力。于是就有人站了出来,并在 GitHub 上开源了他们的代码规范库,比较流行的有 airbnb、standard、prettier 等。
在这里我选择的是国内腾讯 AlloyTeam 团队出品的 eslint-config-alloy 开源规范库。
其实他们团队最开始使用 Airbnb 规则,但是由于它过于严格,部分规则还是需要个性化,导致后来越改越多,最后决定重新维护一套。经过两年多的打磨,现在 eslint-config-alloy 已经非常成熟了。
我选择它的几点原因:
- 适用于 React/Vue/Typescript 项目
- 样式相关规则由 Prettier 管理
- 有中文文档和网站示例(就我那蹩脚的英语水平,这点极吸引我,哈哈)
- 更新快,且拥有官方维护的 vue、typescript、react+typescript 规则
$ 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
3. 安装完依赖,那么就要加上 ESLint、Prettier 的配置文件
他们的配置文件可以有多种,这里使用 JavaScript 格式分别是 .eslintrc.js
、.prettierrc.js
,都放置在项目根目录下。
对于配置我就不展开说了,若有疑惑的,可以自行搜索查找答案或者评论留言给我。
// .eslintrc.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 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' }
4. 配置 ESLint、Prettier 忽略规则
对应的文件是 .eslintignore
、.prettierignore
,同样的都放在项目根目录下。
这些就根据自己项目实际情况做调整了,以下仅供参考:
# .eslintignore *.min.js typings node_modules
# .prettierignore *.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
5. 添加 .editorconfig
配置文件
它是用来抹平不同编辑器之间的差异的。同样放置在项目根目录下。
# .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
6. 添加 npm scripts
添加三条脚本指令:
"eslint": "eslint ./ --ext .js"
"eslint:fix": "eslint --fix ./ --ext .js"
"prettier:fix": "prettier --config .prettierrc.js --write './**/*.{js,css,less,scss,json}'"
通过 yarn run <command>
即可执行一键格式化和修复了,当然了 ESLint 使用 --fix
只能修复一部分,剩余的只能手动解决了。
{ "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": { "test": "echo \"Error: no test specified\" && exit 1", "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
的目的。但是上述方式我确实走了一些弯路,其实通过 Prettier Configuration Overrides 配置是可以指定
.wxss
等扩展名文件使用指定的解析器的。换句话说就是,我们可以在处理.wxss
文件时使用 CSS 解析器去处理它就好了。但我的建议是将前面两篇看完,再看结局篇。
不不不,本文我最想分享的是下面这个,前面的内容都比较简单,很多人都懂了。
Prettier 支持的 JavaScript、JSX、Angular、Vue、Flow、TypeScript、CSS、Less、Scss、HTML、JSON、GraphQL、Markdown(GFM、MDX)、YAML 的代码格式化。
但其实是不能识别 wxss
、acss
等小程序特有的层叠样式,尽管它们规则与 CSS 无异,但是 Prettier 并没有解析器去解析它们。
我们试图去调整脚本命令为(添加 *.wxss
扩展名的文件):
{ "scripts": { "prettier:fix": "prettier --config .prettierrc.js --write './**/*.wxss'", } }
然后去执行的时候就会报错,如下:
[error] No parser could be inferred for file: app.wxss
既然这样走不通的话,总不能利用 VS Code 的 Prettier 插件一个一个地去格式化 *.wxss
的文件吧,那样工作量太大了,不符合我们“偷懒”的做法。
那么如何解决呢?
我使用的是 Gulp.js 来处理。如果对 Gulp 不太熟悉了,点击这里了解一下。
四、Gulp.js
简单说下 Gulp.js 的工作方式,它使用的是 Node.js 中的 stream
(流),首先获取到需要的 stream
,然后通过 stream
的 pipe()
方法把流导入到你想要的地方。比如 Gulp 插件中,经过插件处理后的流又可以导入到其他插件汇总,当然也可以把流写入文件中,所以 Gulp 是以 stream
为媒介的,它不需要频繁的生成临时文件,这也是 Gulp 的速度比 Grunt 快的一个原因。
我刚开始时的想法是:首先将
wxss
(acss
)转换并导出为css
,接着删除wxss
(acss
)文件,再者使用 Prettier 对css
文件进行格式化,转回wxss
(acss
)之后,再删除掉css
文件。这个过程会频繁的生成临时文件,思路是有点像 Grunt。但是了解了 Gulp 的思想后,其实它帮我们省掉了频繁增删文件的环节,全部放在内存中操作,也会更快一些,所以此前的方案被我否掉了。
下面我们只用到 Gulp 的其中两个 API, gulp.src()
和 gulp.dest()
。
1. gulp.src()
这个方法是用来获取流的,但要注意这个流里面的内容不是原始的文件流,而是一个虚拟文件对象流(Vinyl files),这个虚拟文件对象中存储着原始文件的路径、文件名、内容等信息。(这里不深入,点到为止,有兴趣自行了解)
语法:gulp.src(globs[, options])
- globs:是文件匹配模式,用来匹配文件路径(包括文件名)
- options:为可选参数,通常情况我们不需要用到
*关于参数详细说明,请看文档。
2. gulp.dest()
该方法是用来写文件的
gulp.dest(path[, options])
- path:是写入文件的路径
- options:为可选参数,通常情况我们不需要用到
要想使用好 gulp.dest()
这个方法,就要理解给它传入的路径参数与最终生成的文件的关系。
Gulp 的使用流程一般是:首先通过 gulp.src()
方法获取到我们想要处理的文件流,然后把文件流通过 pipe()
方法导入到 Gulp 的插件中,最后把经过插件处理后的流再通过 pipe()
方法导入到 gulp.dest()
中,gulp.dest()
方法则把流中的内容写入到文件中。
这里需要弄清楚的一点是,我们给 gulp.dest()
传入的路径参数,只能用来指定要生成的文件的目录,而不能指定生成文件的文件名,它生成文件的文件名使用的是导入到它的文件流自身的文件名,所以生成的文件名是由导入到它的文件流决定的,即使我们给它传入一个带有文件名的路径参数,然后它也会把这个文件名当做是目录名,例如:
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。
五、开始配置
首先,安装 Gulp 相关依赖包。
$ 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.js 官网快速入门的教程,很简单,这里不在赘述。
思路:使用
gulp.src()
获取流,然后使用 Gulp 插件对流分别作重命名(gulp-rename)、格式化(gulp-prettier
)、再重命名回来(gulp-rename
)、最后导出(gulp.dest()
)。过程中有利用gulp-debug
插件来查看一些信息。
这里我对微信小程序、支付宝小程序的层叠样式都处理了。
// 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 }
通过以下方式调用就好了
// package.json { "scripts": { "prettier:wxss": "gulp wxss", "prettier:accs": "gulp acss", "prettier:wxss:acss": "gulp all" } }
执行命令,我们看到如下结果,说明配置成功了。
六、Git-Hooks
上面已经实现了对 wxss
、acss
扩展名的文件进行一键格式化了。
还可以“更懒”一些,利用 git-hooks 我们可实现在 commit
之前,对项目进行 ESLint、Prettier 检测和格式化,一旦出现错误,将停止 commit
操作。
七、插个题外话
由于本项目的 npm 包仅用于代码检查与格式化,并未参与页面代码逻辑中。所以我在小程序本地项目配置文件中添加上打包配置选项。
packOptions 用以配置项目在打包过程中的选项。打包是预览、上传时对项目进行的必须步骤。
目前可以指定
packOptions.ignore
字段,用以配置打包时对符合指定规则的文件或文件夹进行忽略,以跳过打包的过程,这些文件或文件夹将不会出现在预览或上传的结果内。
*需要注意的是支付宝小程序,在编写本文时还未支持类似 ignore
选项。
// project.config.js { "packOptions": { "ignore": [ { "type": "regexp", "test": "\\.md$" }, { "type": "folder", "test": "node_modules" } ] } }