使Prettier一键格式化WXSS(上集)

本文将会结合 ESLint、Prettier、husky、lint-stage 等工具使得项目一键化操作,减少在格式化、代码检查等操作上浪费时间,因为大前端真的太多东西学了,不学会“偷懒”的话,我们就要落后更多了。

本系列文章的示例 Demo 在这里 GitHub: wechat_applet_demo

分为三篇文章介绍:

扩展篇

最近在做公司部门前端项目由 SVN 迁移 Git 的事情,由于历史代码在此之前并没有引入类似 ESLintPrettier 的代码检查或者格式约束等工具。

目前部门仅剩我一人维护这十几个小程序、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 的代码格式化。

但其实是不能识别 wxssacss 等小程序特有的层叠样式,尽管它们规则与 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,然后通过 streampipe() 方法把流导入到你想要的地方。比如 Gulp 插件中,经过插件处理后的流又可以导入到其他插件汇总,当然也可以把流写入文件中,所以 Gulp 是以 stream 为媒介的,它不需要频繁的生成临时文件,这也是 Gulp 的速度比 Grunt 快的一个原因。

我刚开始时的想法是:首先将 wxssacss)转换并导出为 css,接着删除 wxssacss)文件,再者使用 Prettier 对 css 文件进行格式化,转回 wxssacss)之后,再删除掉 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 的 API 与方法说明,主要参考自官方文档与无双的一篇文章

五、开始配置

首先,安装 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

上面已经实现了对 wxssacss 扩展名的文件进行一键格式化了。

还可以“更懒”一些,利用 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"
      }
    ]
  }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268