- 前端项目交付流程中需要考量的点能让我们更有大局观
 - npm script视频链接: https://pan.baidu.com/s/1gfeZ619 密码: xx8j
 
前端工作流
- 代码风格检查
 - 单元测试
- 测试运行
 - 覆盖率收集
 - 覆盖率查
 
 
软件工程师做的事情基本都是在实现自动化,比如:
- 各种业务系统是为了业务运转的自动化
 - 部署系统是为了运维的自动化
 - 对于开发者本身,自动化也是提升效率的关键环节,实际开发过程中也有不少事情是可以自动化的
 - npm script 依赖 package.json
- Google Trends https://trends.google.com/trends/explore
 
 - npm init 初始化 package.json
- 包名称、版本号、作者信息、入口文件、仓库地址、许可协议等,多数问题已经提供了默认值
 
 - npm init -y 跳过参数,直接创建 package.json
 
mkdir npm-demo & cd npm-demonpm initnpm init -f # --force的意思npm init -y # --yes
将默认配置和 -f 参数结合使用,能让你用最短的时间创建 package.json
npm init
package name: (npm-script)version: (0.1.0)description: hello npm scriptentry point: (index.js)test command:git repository:keywords: npm, scriptlicense: (MIT)
package.json
{"name": "npm-script","version": "0.1.0","description": "npm script","main": "index.js","scripts": {"test": "echo \"Error: no test specified\" && exit 1"},"keywords": ["npm","script"],"author": "","license": "MIT"}
npm config set init 修改 package.json的配置
npm config set init.author.email "lulongwen@live.com"npm config set init.author.name "lulongwen"npm config set init.author.url "http://github.com/lulongwen"npm config set init.license "MIT"npm config set init.version "0.1.0"
npm run test
运行项目测试
npm run test // 简写为 npm test,或 npm tnpm testnpm t
npm cache 清除缓存
npm cache cleannpm cache clean --force // 强制清除
npm内置命令
npm startnpm test
npm 是如何管理和执行各种 scripts?
- npm run 是 npm run-script的简写
 - 当我们运行 npm run xxx 时,步骤如下
- 从 package.json 文件中读取 scripts 对象里面的全部配置
 - 以传给 npm run 的第一个参数作为键,本例中为 xxx,在 scripts 对象里面获取对应的值作为接下来要执行的命令,如果没找到直接报错
 - 在系统默认的 shell 中执行上述命令,系统默认 shell 通常是 bash
 
 - npm run 原理:npm 在执行指定 script 之前会把 node_modules/.bin 加到环境变量 $PATH 的前面
- 任何内含可执行文件的 npm 依赖都可以在 npm script 中直接调用
 - 你不需要在 npm script 中加上可执行文件的完整路径,比如 
./node_modules/.bin/eslint **.js 
 
自定义 npm script
package.json如下:
{"name": "npm-script","devDependencies": {"eslint": "latest"},"scripts": {"eslint": "eslint **.js"}}
不带任何参数执行 npm run,会列出可执行的所有命令,比如下面这样
Available scripts in the myproject package:eslinteslint **.js
运行 npm run eslint,npm 会在 shell 中运行 eslint **.js
eslint
- vue的 eslint:eslint-plugin-vue https://github.com/vuejs/eslint-plugin-vue
 - react eslint
- eslint-plugin-react https://github.com/yannickcr/eslint-plugin-react
 - eslint-plugin-react-native https://github.com/Intellicode/eslint-plugin-react-native
 - eslint-config-airbnb 内置了 eslint-plugin-react https://www.npmjs.com/package/eslint-config-airbnb
 
 - 存放规则集的文件就是配置文件,
./node_modules/.bin/eslint --init- 把 eslint 安装为项目依赖而非全局命令,项目可移植性更高
 - eslint 内置了代码风格自动修复模式 
--fix 
 .eslintrc*
module.exports = {env: {es6: true,node: true,},extends: 'eslint:recommended',rules: {indent: ['error', 2],'linebreak-style': ['error', 'unix'],quotes: ['error', 'single'],semi: ['error', 'always'],},}
peerDependencies 安装失败问题可参照 npmjs 主页上的如下方法解决
(export PKG=eslint-config-airbnb;npm info "$PKG@latest" peerDependencies --json | command sed 's/[\{\},]//g ; s/: /@/g' | xargs npm install --save-dev "$PKG@latest")
给 npm script 传递参数以减少重复的 npm script
—fix,—是分隔符;要给 npm run lint:js 实际指向的命令传递额外的参数
{"lint:js": "eslint *.js","lint:js:fix": "eslint *.js --fix",}
npm run lint:js -- --fix
不想单独声明 lint:js:fix 命令,在需要的时候直接运行: npm run lint:js -- --fix 来实现同样的效果
eslint代码检查
- eslint 定制的 js 代码检查 https://eslint.org/
 - stylelint 样式文件检查,支持 css、less、scss https://stylelint.io/
 - jsonlint json文件语法检查 https://github.com/zaach/jsonlint
 - markdownlint-cli https://github.com/igorshubovych/markdownlint-cli
 
{"name": "npm-script","version": "0.1.0","main": "index.js","scripts": {"lint:js": "eslint *.js","lint:css": "stylelint *.less","lint:json": "jsonlint --quiet *.json","lint:markdown": "markdownlint --config .markdownlint.json *.md","test": "mocha tests/"},"devDependencies": {"chai": "^4.1.2","eslint": "^4.11.0","jsonlint": "^1.6.2","markdownlint-cli": "^0.5.0","mocha": "^4.0.1","stylelint": "^8.2.0","stylelint-config-standard": "^17.0.0"}}
运行多个 npm命令
- npm-run-all
 - 用 
&&符号把多条 npm script 按先后顺序串起来,&& 串行- 把子命令的运行从串行改成并行,把连接多条命令的 
&&符号替换成&即可,& 并行 
 - 把子命令的运行从串行改成并行,把连接多条命令的 
 - 串行执行的时候如果前序命令失败(通常进程退出码非0),后续全部命令都会终止执行
- & wait: 加上 wait 的额外好处是:子命令中启动了长时间运行的进程,可以
ctrl + c结束进程 - npm run all 代替 & await
 
 - & wait: 加上 wait 的额外好处是:子命令中启动了长时间运行的进程,可以
 
"scripts": {// 串行"test": "npm run lint:js && npm run lint:css && npm run lint:json && npm run lint:markdown && mocha tests/",// 并行"test": "npm run lint:js & npm run lint:css & npm run lint:json & npm run lint:markdown & mocha tests/"}// 执行顺序eslint ==> stylelint ==> jsonlint ==> markdownlint ==> mocha
npm run all
https://github.com/mysticatea/npm-run-all/blob/HEAD/docs/npm-run-all.md
npm i npm-run-all -D
package.json
{"test": "npm-run-all lint:js lint:css lint:json lint:markdown mocha",// npm-run-all 还支持通配符匹配分组的 npm script,以上可以修改为 *"test": "npm-run-all lint:* mocha",// 让多个 npm script 并行执行"test": "npm-run-all --parallel lint:* mocha"}
让多个 npm script 并行执行, —parallel
并行执行的时候,并不需要在后面增加 & wait,因为 npm-run-all 已经帮我们做
{"test": "npm-run-all lint:* mocha","test": "npm-run-all --parallel lint:* mocha"}
在根目录的,最顶部引入 ployfill
import '@babel/ployfill'
npm命令
# 查看npm全局安装过的包npm list -g --depth 0npm fund # 捐赠
npm script 传递参数, —
npm run lint:js -- --fix
不想单独声明 lint:js:fix 命令,在需要的时候直接运行: npm run lint:js -- --fix 来实现同样的效果
npm script 添加注释
- json 天然是不支持添加注释的,trick写法
 - 增加 
//为键的值,注释就可以写在对应的值里面,npm 会忽略这种键,缺点:- npm run 列出来的命令列表不能把注释和实际命令对应上,在命令前面加上注释
 
 
{"//": "运行所有代码检查和单元测试","test": "npm-run-all --parallel lint:* mocha"}
- CLI的本质是 shell 命令
- 注意:注释后面的换行符 
\n和多余的空格,- 换行符是用于将注释和命令分隔开,这样命令就相当于微型的 shell 脚本
 - 多余的空格是为了控制缩进,也可以用制表符 
\t替代 
 - 能让 npm run 列出来的命令更美观,但是 scripts 声明阅读起来不那么整齐
 
 - 注意:注释后面的换行符 
 
{"test": "# 运行所有代码检查和单元测试 \n npm-run-all --parallel lint:* mocha"}
- 更好的做法: 把复杂的命令抽离到单独的文件中管理
 
npm script日志输出
运行 npm script 出现问题时,要要有能力去调试
- 默认的日志输出:不加任何日志控制参数得到的输出
 - 显示尽可能少的有用信息
- 结合其他工具调用 npm script 的时候比较有用,—loglevel silent,或 —silent 控制
 
 - 显示尽可能多的运行时状态
- 排查脚本问题的时候比较有用,需要使用 
--loglevel verbose,或者--verbose 
 - 排查脚本问题的时候比较有用,需要使用 
 
npm script钩子
npm命令的执行增加了类似生命周期的机制
- pre
 - post
 - 在某些操作前需要做检查、某些操作后需要做清理的情况下非常有用
 - 举例来说,运行 npm run test 的时候,分 3 个阶段:
- 检查 scripts 对象中是否存在 pretest 命令,如果有,先执行该命令;
 - 检查是否有 test 命令,有的话运行 test 命令,没有的话报错;
 - 检查是否存在 posttest 命令,如果有,执行 posttest 命令
 
 
覆盖率收集
改造 test
- 基于钩子机制对现有的 scripts 做以下 3 点重构,把代码检查和测试运行串起来
- 增加简单的 lint 命令,并行运行所有的 lint 子命令;
 - 增加 pretest 钩子,在其中运行 lint 命令;
 - 把 test 替换为更简单的 
mocha tests/ 
 
"lint": "npm-run-all --parallel lint:*","lint:js": "eslint *.js","lint:js:fix": "npm run lint:js -- --fix","lint:css": "stylelint *.less","lint:json": "jsonlint --quiet *.json","lint:markdown": "markdownlint --config .markdownlint.json *.md",- "mocha": "mocha tests/",- "test": "# 运行所有代码检查和单元测试 \n npm-run-all --parallel lint:* mocha"+ "pretest": "npm run lint",+ "test": "mocha tests/",
运行 npm test 的时候,会先自动执行 pretest 里面的 lint
增加覆盖率收集
- 把运行测试和覆盖率收集串起来
- 做法:增加覆盖率收集的命令,并且覆盖率收集完毕之后自动打开 html 版本的覆盖率报告
 
 - 覆盖率收集工具 nyc,是覆盖率收集工具 istanbul 的命令行版本,istanbul 支持生成各种格式的覆盖率报告
- 衡量测试效果的重要指标是:测试覆盖率
 
 - 打开 html 文件的工具 opn-cli,是能够打开任意程序的工具 opn 的命令行版本
 
npm i nyc opn-cli -D
- 然后在 package.json 增加 nyc 的配置,告诉 nyc 该忽略哪些文件。最后是在 scripts 中新增 3 条命令:
- precover,收集覆盖率之前把之前的覆盖率报告目录清理掉;
 - cover,直接调用 nyc,让其生成 html 格式的覆盖率报告;
 - postcover,清理掉临时文件,并且在浏览器中预览覆盖率报告
 
 - 运行 npm run cover
 
{" scripts":{"precover":"rm -rf coverage","cover":"nyc --reporter=html npm test","postcover":"rm -rf .nyc_output && opn coverage/index.html"},"devDependencies":{"npm-run-all":"^4.1.2","nyc":"^11.3.0","opn-cli":"^3.1.0","stylelint":"^8.2.0","stylelint-config-standard":"^17.0.0"},"nyc":{"exclude":["**/*.spec.js",".*.js"]}}
环境变量
$PATH
正在执行的命令、包的名称和版本号、日志输出的级别
预定义变量
自定义变量
变量的使用方法遵循 shell 里面的语法,直接在 npm script 给想要引用的变量前面加上 $ 符号即可
{"dummy": "echo $npm_package_name"}
postcover 做了 3 件事情:
npm run cover:archive,归档本次覆盖率报告;npm run cover:cleanup,清理本次覆盖率报告;opn coverage_archive/$npm_package_version/index.html,直接预览覆盖率报告
npm 跨平台的兼容性
- linux, max兼容,window不兼容;不是所有的 shell 命令都是跨平台兼容的
 - window建议使用 git bash 来运行 npm script,自带的 cmd 可能会遇到比较多的问题**
 - npm script 的跨平台兼容注意点
- 所有使用引号的地方,建议使用双引号,并且加上转义
 - 没做特殊处理的命令比如 eslint、stylelint、mocha、opn 等工具本身都是跨平台兼容的
 - 使用 Linux 做开发
 
 - NODE_ENV=test,NODE_ENV是 node的环境变量
 
cross-env 设置环境变量
cross-env 来实现 npm script 的跨平台兼容
npm install cross-env --save-dev
package.json
"scripts": {- "test": "NODE_ENV=test mocha tests/",+ "test": "cross-env NODE_ENV=test mocha tests/",},
文件系统操作的跨平台兼容
- 跨平台兼容的包,npmjs.com上搜索 cross platform
 - npm script涉及 目录的创建、删除、移动、复制等操作
- rimraf 或 del-cli,用来删除文件和目录,实现类似于 
rm -rf的功能; - cpr,用于拷贝、复制文件和目录,实现类似于 
cp -r的功能; - make-dir-cli,用于创建目录,实现类似于 
mkdir -p的功能 
 - rimraf 或 del-cli,用来删除文件和目录,实现类似于 
 
pm install rimraf cpr make-dir-cli --save-dev
修改 package.json兼容
"scripts": {- "cover:cleanup": "rm -rf coverage && rm -rf .nyc_output",- "cover:archive": "cross-var \"mkdir -p coverage_archive/$npm_package_version && cp -r coverage/* coverage_archive/$npm_package_version\"",+ "cover:cleanup": "rimraf coverage && rimraf .nyc_output",+ "cover:archive": "cross-var \"make-dir coverage_archive/$npm_package_version && cpr coverage/* coverage_archive/$npm_package_version -o\"","cover:serve": "cross-var http-server coverage_archive/$npm_package_version -p $npm_package_config_port","cover:open": "cross-var opn http://localhost:$npm_package_config_port",- "postcover": "npm-run-all cover:archive cover:cleanup --parallel cover:serve cover:open"+ "precover": "npm run cover:cleanup",+ "postcover": "npm-run-all cover:archive --parallel cover:serve cover:open"},
rm -rf直接替换成rimraf;mkdir -p直接替换成make-dir;cp -r的替换需特别说明下,cpr默认是不覆盖的,需要显示传入-o配置项,并且参数必须严格是cpr <source> <destination> [options]的格式,即配置项放在最后面;- 把 
cover:cleanup从postcover挪到precover里面去执行,规避cpr没归档完毕覆盖率报告就被清空的问题 - 任何改动之后记得重新运行 npm run cover,确保所有的 npm script 还是按预期工作的
 
cross-var 引用变量
- 使用内置和预定义变量减少代码重复代码
 - 用 cross-var 实现跨平台的变量引用
- Linux 用 
$npm_package_name - Windows 用 
%npm_package_name% 
 - Linux 用 
 - 安装 cross-var
 
npm install cross-var --save-dev
修改 package.json
"scripts": {"cover:cleanup": "rm -rf coverage && rm -rf .nyc_output",- "cover:archive": "mkdir -p coverage_archive/$npm_package_version && cp -r coverage/* coverage_archive/$npm_package_version",- "cover:serve": "http-server coverage_archive/$npm_package_version -p $npm_package_config_port",- "cover:open": "opn http://localhost:$npm_package_config_port",+ "cover:archive": "cross-var \"mkdir -p coverage_archive/$npm_package_version && cp -r coverage/* coverage_archive/$npm_package_version\"",+ "cover:serve": "cross-var http-server coverage_archive/$npm_package_version -p $npm_package_config_port",+ "cover:open": "cross-var opn http://localhost:$npm_package_config_port","postcover": "npm-run-all cover:archive cover:cleanup --parallel cover:serve cover:open"},
直接在原始命令前增加 cross-var 命令即可
cover:archive 内含了两条子命令,我们需要用引号把整个命令包起来(注意这里是用的双引号,且必须转义),然后在前面加上 cross-var
引入 cross-var 之后,竟然还安装了 babel,如果想保持依赖更轻量的话,用 cross-var-no-babel。
npm script实战
监听文件变化并自动运行 npm script
- 代码检查自动化,要借助 onchange 工具包来实现,
 - 因为: stylelint、eslint、jsonlint 不全支持 —watch 模式
 - onchange 可以方便的让我们在文件被修改、添加、删除时运行需要的命令
 - onchange在运行指定命令之前,会输出哪个文件发生了哪些变化
 
安装 onchange
npm install onchange --save-devyarn add onchange -D
package.json添加 watch:lint 和 watch 两个子命令
+ "watch": "npm-run-all --parallel watch:*",+ "watch:lint": "onchange -i \"**/*.js\" \"**/*.less\" -- npm run lint","watch:test": "npm t -- --watch",
watch:lint里面的文件匹配模式可以使用通配符,但是模式两边使用了转义的双引号,做跨平台兼容的;watch:lint里面的-i参数是让 onchange 在启动时就运行一次--之后的命令,即代码没变化的时候,变化前后的对比大多数时候还是有价值的- watch 命令实际上是使用了 npm-run-all 来运行所有的 watch 子命令
- watch使用了跨平台的文件系统监听包 chokidar
 - 你能基于 chokidar 做点什么有意思的事情呢?
 
 
单元测试自动化
- 运行 npm run watch:test
 - 进程并没有退出,接下来尝试去修改测试代码,测试是不是自动重跑了!
 
"test": "cross-env NODE_ENV=test mocha tests/",+ "watch:test": "npm t -- --watch","cover": "node scripts/cover.js",
live-reload自动刷新
- 前端开发,实际上最浪费时间的操作是什么?就是刷新页面
 - 要让变更生效,需要重新加载,刷新页面的操作就变成了重复低效的操作
 - create-react-app刷新用的是
 - LiveReload的缺点:刷新页面意味着客户端状态的全部丢失,HMR、HR 都是基于 liveReload的优化
 - Hot Module Replacement HMR,vue-cli用的就是 HMR提高效率
 
npm install livereload http-server --save-devyarn add livereload http-server -D
添加 npm script,client 命令能同时启动 livereload 服务、静态文件服务
运行 npm run client
- "cover:open": "scripty"+ "cover:open": "scripty",+ "client": "npm-run-all --parallel client:*",+ "client:reload-server": "livereload client/",+ "client:static-server": "http-server client/"
为什么启动2个服务?
- http-server 启动的是静态文件服务器,启动后可以通过 http 的方式访问文件系统上的文件
 - livereload 启动了自动刷新服务,负责监听文件系统变化,并在文件系统变化时通知所有连接的客户端
index.html中嵌入的那段 js 实际上是和 livereload-server 连接的一个 livereload-client
 - index.html
 
<body><h2>LiveReload Demo</h2><script>document.write('<script src="http://' + (location.host || 'localhost').split(':')[0] +':3579/livereload.js?snipver=1"></' + 'script>')</script></body>
嵌入的 script,通过 判断 location.hostname 去检查当前页面运行环境,如果是线上环境就不嵌入了
或者使用打包工具处理 html 文件,上线前直接去掉即可
node配置自动检测文件改变不重启
npm install -g nodemonnodemon ./bin/www或者在npm start命令里把node改为nodemon
git hooks中运行 npm script
pre、post的钩子机制,叫做 Git Hooks- 钩子机制能让我们在代码 commit、push 之前(后)做自己想做的事情
 - 通过 npm script 为本地仓库配置了 pre-commit、pre-push 钩子检查
- 本地检查:为了尽早给提交代码的同学反馈,哪些地方不符合规范,哪些地方需要注意
- pre-commit、pre-push
 
 - 远程检查 Remotes:为了确保远程仓库收到的代码是符合团队约定的规范的
- pre-receive
 --no-verify(简写为-n) 参数可以跳过本地检查
 
 - 本地检查:为了尽早给提交代码的同学反馈,哪些地方不符合规范,哪些地方需要注意
 - npm script 和 git-hooks 的方案
- husky 支持更多的 Git Hooks 种类,再结合 lint-staged 试用就更溜
 - pre-commit
 
 
husky
npm install husky lint-staged --save-devyarn add husky lint-staged -D
package.json
"scripts": {"precommit": "npm run lint","prepush": "npm run test","lint": "npm-run-all --parallel lint:*","lint:js": "eslint *.js","test": "jest","format": "prettier --single-quote --no-semi --write **/*.js","install": "node ./bin/install.js","uninstall": "node ./bin/uninstall.js"},
install 就是你在项目中安装 husky 时执行的脚本(所有的魔法都藏在在这里了
检查仓库的 .git/hooks 目录
ls .git/hookscat .git/hooks/pre-commit
lint-staged 改进 pre-commit
- lint 每次提交代码会检查所有的代码,比较慢就不说了,初期 lint 工具可能会报告几百上千个错误,让人崩溃
 - lint-staged 来缓解这个问题,每个团队成员提交的时候,只检查当次改动的文件
 
"scripts": {- "precommit": "npm run lint",+ "precommit": "lint-staged","prepush": "npm run test","lint": "npm-run-all --parallel lint:*",},+ "lint-staged": {+ "*.js": "eslint",+ "*.less": "stylelint",+ "*.css": "stylelint",+ "*.json": "jsonlint --quiet",+ "*.md": "markdownlint --config .markdownlint.json"+ },
尝试提交这个文件:git commit -m 'add eslint error' index.js
如果 husky 的 pre-commit 钩子执行失败,提交也就没有成功
husky 和 lint-staged 构建超溜的代码检查工作流 https://juejin.im/post/6844903479283040269
npm script构建流水线
- 部署前最关键的环节就是构建,构建环节要完成的事情:
- 源代码预编译:比如 less、sass、typescript;
 - 图片优化、雪碧图生成;
 - JS、CSS 合并、压缩;
 - 静态资源加版本号和引用替换;
 - 静态资源传 CDN 等
 
 - 大多数项目构建都是脚手架配置好的,但要知道构建过程的原理
 - 构建过程必须遵循下面的步骤
- 压缩图片;
 - 编译 less、压缩 css;
 - 编译、压缩 js;
 - 给图片加版本号并替换 js、css 中的引用;
 - 给 js、css 加版本号并替换 html 中的引用
 
 
构建过程
- 构建产生的结果代码,放在 dist 目录
 - 每次构建前,清空之前的构建目录
- 构建过程分为:images、styles、scripts、hash 四个步骤
 
 - 利用 npm 的钩子机制添加 prebuild 命令
 
{"client:static-server": "http-server client/","prebuild": "rm -rf dist && mkdir -p dist/{images,styles,scripts}"}
图片构建的经典工具是 imagemin,提供了命令行版本 imagemin-cli
npm install imagemin-cli --save-devyarn add imagemin-cli -D
scripts/build/images.sh 中添加 imagemin client/images/* --out-dir=dist/images
package.json 中添加 build:images 命令
运行 npm run prebuild && npm run build:images,然后观察 dist 目录的变化
"build:images": "scripty"
样式构建
cssmin 来完成代码预压缩
npm install cssmin --save-dev
运行 npm run prebuild && npm run build:styles
js构建
uglify-es 来进行 es6代码压缩, uglify-es配置 https://github.com/mishoo/UglifyJS/tree/harmony#command-line-options
uglify-js 来压缩 js代码
npm install uglify-es --save-dev
静态资源版本号
- 静态资源加版本号的原因:是线上环境的静态资源通常都放在 CDN 上,或者设置了很长时间的缓存
 - 如果资源更新了但没有更新版本号,浏览器端是拿不到最新内容的
 - 手动加版本号的过程很繁琐并且容易出错
 - 通常的做法是利用文件内容做哈希,比如 md5
 - hashmark,自动添加版本号;
 - replaceinfiles,自动完成引用替换,它需要将版本号过程的输出作为输入
 
npm install hashmark replaceinfiles --save-dev
