发起一个github/npm工程协做项目,门槛过高了!!javascript
最基础的问题,你都要花好久去研究:css
这四样工具的配置,是每一个github项目都会用上的。另外,gitignore配置、editconfig、readme、lisence。。。也是必不可缺的。前端
你可能须要花数天时间去研究文档、数天时间去作基础配置。vue
这样的时间成本,能够直接劝退大多数人。java
但,假如几秒钟,就能够按需求配置好这一切呢?node
你能够先来体验一下“轮子工厂”,在命令行输入:webpack
npx lunz myapp
一路回车,而后试一试yarn lint
,yarn test
,yarn build
命令git
第一部分: 2019年github + npm工程化协做开发栈最佳实践es6
第二部分: 使用脚手架,10秒钟构建可自由配置的开发栈。github
咱们将花半小时实战撸一个包含package.json, babel, jest, eslint, prettify, gitignore, readme, lisence的标准的用于github工程协做的npm包开发栈。
若是能实际操做,请实际操做。若是不能实际操做,请在bash下输入
npx lunz npmdev
得到一样的效果。
mkdir npmdev && cd npmdev
npm init
package name: 回车 version: 回车 description: 本身瞎写一个,不填也行 entry point: 输入`dist/index.js` test command: 输入`jest` git repository: 输入你的英文名加上包名,例如`wanthering/npmdev` keywords: 本身瞎写一个,不填也行 author: 你的英文名,例如`wanthering` license: 输入`MIT`
在package.json中添加files字段,使npm发包时只发布dist
... "files": ["dist"], ...
以前不是建立了.editorconfig
、LICENSE
、circle.yml
、.gitignore
、README.md
吗,这四个复制过来。
npx eslint --init
How would you like to use ESLint? 选第三个 What type of modules does your project use? 选第一个 Which framework does your project use? 选第三个None Where does your code run? 选第二个 Node How would you like to define a style for your project? 选第一个popular Which style guide do you want to follow? 选第一个standard What format do you want your config file to be in? 选第一个 javascript
在package.json中添加一条srcipts命令:
... "scripts": { "test": "jest", "lint": "eslint src/**/*.js test/**/*.js --fix" }, ...
为了兼容eslint,须要安装三个包
yarn add prettier eslint-plugin-prettier eslint-config-prettier -D
在package.json中添加prettier字段
... "prettier": { "singleQuote": true, "semi": false }, ...
在.eslintrc.js中,修改extends字段:
... 'extends': ['standard',"prettier","plugin:prettier/recommended"], ...
mkdir src && touch src/index.js
src/index.js中,咱们用最简单的add函数作示意
const add = (a,b)=>{ return a+b} export default add
这时命令行输入
yarn lint
这会看到index.js自动排齐成了
const add = (a, b) => { return a + b } export default add
全部的npm包,均采用测试驱动开发。
如今流行的框架,无非jest和ava,其它的mocha之类的框架已经死在沙滩上了。
咱们安装jest
npm i jest -D
而后根目录下新建一个test文件夹,放置进jest/index.spec.js文件
mkdir test && touch test/index.spec.js
在index.spec.js内写入:
import add from "../src/index.js"; test('add',()=>{ expect(add(1,2)).toBe(3)})
配置一下eslint+jest:
yarn add eslint-plugin-jest -D
在.eslintrc.js中,更新env字段,添加plugins字段:
'env': { 'es6': true, 'node': true, 'jest/globals': true }, 'plugins': ['jest'], ...
由于须要jest中使用es6语句,须要添加babel支持
yarn add babel-jest @babel/core @babel/preset-env -D
建立一下.babelrc配置,注意test字段,是专门为了转化测试文件的:
{ "presets": [ [ "@babel/preset-env", { "targets": { "node": 6 } } ] ], "env": { "test": { "presets": [ [ "@babel/preset-env", { "targets": { "node": "current" } } ] ] } } }
好,跑一下yarn lint,以及yarn test
yarn lint yarn test
比起使用babel转码(安装@babel/cli
,再调用npx babel src --out-dir dist
),我更倾向于使用bili
进行打包。
yarn add bili -D
而后在package.json的script中添加
"scripts": { "test": "jest", "lint": "eslint src/**/*.js test/**/*.js --fix", "build": "bili" },
建立 .gitignore,复制如下内容到文件里
node_modules .DS_Store .idea *.log dist output examples/*/yarn.lock
建立.editorconfig,复制如下内容到文件里
root = true [*] indent_style = space indent_size = 2 end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true [*.md] trim_trailing_whitespace = false
建立circle.yml,复制如下内容到文件内
version: 2 jobs: build: working_directory: ~/project docker: - image: circleci/node:latest branches: ignore: - gh-pages # list of branches to ignore - /release\/.*/ # or ignore regexes steps: - checkout - restore_cache: key: dependency-cache-{{ checksum "yarn.lock" }} - run: name: install dependences command: yarn install - save_cache: key: dependency-cache-{{ checksum "yarn.lock" }} paths: - ./node_modules - run: name: test command: yarn test
建立README.md,复制如下内容到文件内
# npm-dev > my laudable project
好了,如今咱们的用于github工程协做的npm包开发栈已经完成了,相信我,你不会想再配置一次。
这个项目告一段落。
事实上,这个npm包用npm publish
发布出去,人们在安装它以后,能够做为add
函数在项目里使用。
一样,这一章节若是没时间实际操做,请输入
git clone https://github.com/wanthering/lunz.git
当你开启新项目,复制粘贴之前的配置和目录结构,浪费时间且容易出错。
package.json、webpack、jest、git、eslint、circleci、prettify、babel、gitigonre、editconfig、readme的强势劝退组合,让你无路可走。
因此有了vue-cli,很是强大的脚手架工具,但你想自定义本身的脚手架,你必须学透了vue-cli。
以及yeoman,配置贼麻烦,最智障的前端工具,谁用谁sb。
还有人求助于docker,
有幸,一位来自成都的宝藏少年egoist开发了前端工具SAO.js。
SAO背景不错,是nuxt.js的官方脚手架。
做为vue的亲弟弟nuxt,不用vue-cli反而用sao.js,你懂意思吧?
由于爽!!!!!!!!
由于,一旦你学会批量构建npm包,将来将能够把精力集中在“造轮子”上。
全局安装
npm i sao -g
快速建立sao模板
sao generator sao-npm-dev
一路回车到底
ok,当前目录下出现了一个sao-npm-dev
打开看一下:
├── .editorconfig ├── .git ├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── circle.yml ├── package.json ├── saofile.js ├── template │ ├── .editorconfig │ ├── .gitattributes │ ├── LICENSE │ ├── README.md │ └── gitignore ├── test │ └── test.js └── yarn.lock
别管其它文件,都是用于github工程协做的文件。
有用的只有两个:template
文件夹, 和saofile.js
把template
文件夹删空,咱们要放本身的文件。
好,把npmdev整个文件夹内的内容,除了node_modules/、package-lock.json和dist/,所有拷贝到清空的sao-npm-dev/template/文件夹下
如今的sao-npm-dev/template文件夹结构以下:
├── template │ ├── .babelrc │ ├── .editorconfig │ ├── .eslintrc.js │ ├── .gitignore │ ├── LICENSE │ ├── README.md │ ├── circle.yml │ ├── package.json │ ├── src │ │ └── index.js │ ├── test │ │ └── index.spec.js │ └── yarn.lock
模板文件中.eslint.js .babelrc .gitignore package.json,很容易形成配置冲突,咱们先更名使它们失效:
mv .eslintrc.js _.eslintrc.js mv .babelrc _.babelrc mv .gitignore _gitignore mv package.json _package.json
如今所见的saofile,由三部分组成: prompts, actions, completed。
分别表示: 询问弹窗、自动执行任务、执行任务后操做。
你们能够回忆一下vue-cli的建立流程,基本上也是这三个步骤。
弹窗询问的,便是咱们用于github工程协做的npm包开发栈每次开发时的变量,有哪些呢?
我来列一张表:
字段 | 输入方式 | 可选值 | 意义 |
---|---|---|---|
name | input | 默认为文件夹名 | 项目名称 |
description | input | 默认为my xxx project | 项目简介 |
author | input | 默认为gituser | 做者名 |
features | checkbox | eslint和prettier | 安装插件 |
test | confirm | yes 和no | 是否测试 |
build | choose | babel 和 bili | 选择打包方式 |
pm | choose | npm 和yarn | 包管理器 |
根据这张表,咱们修改一下saofile.js中的prompts,而且新增一个templateData(){},用于向template中引入其它变量
prompts() { return [ { name: 'name', message: 'What is the name of the new project', default: this.outFolder }, { name: 'description', message: 'How would you descripe the new project', default: `my ${superb()} project` }, { name: 'author', message: 'What is your GitHub username', default: this.gitUser.username || this.gitUser.name, store: true }, { name: 'features', message: 'Choose features to install', type: 'checkbox', choices: [ { name: 'Linter / Formatter', value: 'linter' }, { name: 'Prettier', value: 'prettier' } ], default: ['linter', 'prettier'] }, { name: 'test', message: 'Use jest as test framework?', type: 'confirm', default: true }, { name: 'build', message: "How to bundle your Files?", choices: ['bili', 'babel'], type: 'list', default: 'bili' }, { name: 'pm', message: 'Choose a package manager', choices: ['npm', 'yarn'], type: 'list', default: 'yarn' } ] }, templateData() { const linter = this.answers.features.includes('linter') const prettier = this.answers.features.includes('prettier') return { linter, prettier } },
先把saofile放下,咱们去修改一下template文件,使template中的文件能够应用这些变量
template下的文件,引入变量的方式是ejs方式,不熟悉的能够看一看ejs官方页面,很是简单的一个模板引擎
如今咱们一个一个审视文件,看哪些文件须要根据变量变更。
无需变更
若是test为false,则文件无需加载。test为true,则加载文件。
无需改动
无需改动
若是build采用的babel,或test为true,则导入文件。
而且,若是test为true,应当开启env,以下设置文件
_.babelrc
{ "presets": [ [ "@babel/preset-env", { "targets": { "node": 6 } } ] ]<% if( test ) { %>, "env": { "test": { "presets": [ [ "@babel/preset-env", { "targets": { "node": "current" } } ] ] } }<% } %> }
在打开test的状况下,加载env下的jest/globals
及设置plugins
下的jest
。
在开启prettier的状况下,加载extends
下的prettier
和plugin:prettier/recommend
因此文件应当这样改写
_.eslintrc.js
module.exports = { 'env': { 'es6': true, 'node': true<% if(test) { %>, 'jest/globals': true<% } %> }<% if(test) { %>, 'plugins': ['jest']<% } %>, 'extends': ['standard'<% if(prettier) { %>,'prettier','plugin:prettier/recommended'<% } %>], 'globals': { 'Atomics': 'readonly', 'SharedArrayBuffer': 'readonly' }, 'parserOptions': { 'ecmaVersion': 2018, 'sourceType': 'module' } }
name字段,加载name变量
description字段,加载description变量
author字段,加载author变量
bugs,homepage,url跟据author和name设置
prettier为true时,设置prettier字段,以及devDependence加载eslint-plugin-prettier、eslint-config-prettier以及prettier
eslint为true时,加载eslint下的其它依赖。
jest为true时,加载eslint-plugin-jest、babel-jest、@babel/core和@babel/preset-env,且设置scripts下的lint语句
build为bili时,设置scripts下的build字段为bili
build为babel时,设置scripts下的build字段为babel src --out-dir dist
最后实际的文件为:(注意里面的ejs判断语句)
{ "name": "<%= name %>", "version": "1.0.0", "description": "<%= description %>", "main": "dist/index.js", "scripts": { "build": "<% if(build === 'bili') { %>bili<% }else{ %>babel src --out-dir dist<% } %>"<% if(test){ %>, "test": "jest"<% } %><% if(linter){ %>, "lint": "eslint src/**/*.js<% } if(linter && test){ %> test/**/*.js<% } if(linter){ %> --fix"<% } %> }, "repository": { "type": "git", "url": "git+https://github.com/<%= author %>/<%= name %>.git" }, "author": "<%= author %>", "license": "MIT", "bugs": { "url": "https://github.com/<%= author %>/<%= name %>/issues" }<% if(prettier){ %>, "prettier": { "singleQuote": true, "semi": false }<% } %>, "homepage": "https://github.com/<%= author %>/<%= name %>#readme", "devDependencies": { <% if(build === 'bili'){ %> "bili": "^4.7.4"<% } %><% if(build === 'babel'){ %> "@babel/cli": "^7.4.4"<% } %><% if(build === 'babel' || test){ %>, "@babel/core": "^7.4.4", "@babel/preset-env": "^7.4.4"<% } %><% if(test){ %>, "babel-jest": "^24.8.0", "jest": "^24.8.0"<% } %><% if(linter){ %>, "eslint": "^5.16.0", "eslint-config-standard": "^12.0.0", "eslint-plugin-import": "^2.17.2", "eslint-plugin-node": "^9.0.1", "eslint-plugin-promise": "^4.1.1", "eslint-plugin-standard": "^4.0.0"<% } %><% if(linter && test){ %>, "eslint-plugin-jest": "^22.5.1"<% } %><% if (prettier){ %>, "prettier": "^1.17.0", "eslint-plugin-prettier": "^3.1.0", "eslint-config-prettier": "^4.2.0"<% } %> } }
判断使用的lockFile文件是yarn.lock仍是package-lock.json
<% const lockFile = pm === 'yarn' ? 'yarn.lock' : 'package-lock.json' -%> version: 2 jobs: build: working_directory: ~/project docker: - image: circleci/node:latest branches: ignore: - gh-pages # list of branches to ignore - /release\/.*/ # or ignore regexes steps: - checkout - restore_cache: key: dependency-cache-{{ checksum "<%= lockFile %>" }} - run: name: install dependences command: <%= pm %> install - save_cache: key: dependency-cache-{{ checksum "<%= lockFile %>" }} paths: - ./node_modules - run: name: test command: <%= pm %> test
# <%= name %> > <%= description %>
填入name和desc变量。
并跟据linter、test、build变量来选择提示命令。
具体文件略。
好,文件的变量导入完成,如今回到saofile.js:
当咱们经过弹窗询问到了变量。
当咱们在构建好模板文件,只等变量导入了。
如今就须要经过saofile.js中的actions进行导入。
把actions进行以下改写:
actions() { return [{ type: 'add', files: '**', filters: { '_.babelrc': this.answers.test || this.answers.build === 'babel', '_.eslintrc.js': this.answers.features.includes('linter'), 'test/**': this.answers.test } }, { type: 'move', patterns: { '_package.json': 'package.json', '_gitignore': '.gitignore', '_.eslintrc.js': '.eslintrc.js', '_.babelrc': '.babelrc' } }] },
其实很好理解! type:'add'
表示将模板文件添加到目标文件夹下,files表示是全部的, filters表示如下这三个文件存在的条件。
type:'move'
就是更名或移动的意思,将以前加了下划线的四个文件,改回原来的名字。
当文件操做处理完以后,咱们还须要作以下操做:
async completed() { this.gitInit() await this.npmInstall({ npmClient: this.answers.pm }) this.showProjectTips() }
SAO已经帮你写好了测试文件,在test文件夹下。
由于咱们要测试不少个选项,原来的sao.mock和snapshot要写不少次。因此咱们把它提炼成一个新的函数verifyPkg()
咱们进行一下改写,同时将package.json、.eslintrc.js打印在snapshot文件中。
import path from 'path' import test from 'ava' import sao from 'sao' const generator = path.join(__dirname, '..') const verifyPkg = async (t, answers) => { const stream = await sao.mock({ generator }, answers) const pkg = await stream.readFile('package.json') t.snapshot(stream.fileList, 'Generated files') t.snapshot(getPkgFields(pkg), 'package.json') if(answers && answers.features.includes('linter')){ const lintFile = await stream.readFile('.eslintrc.js') t.snapshot(lintFile, '.eslintrc.js') } } const getPkgFields = (pkg) => { pkg = JSON.parse(pkg) delete pkg.description return pkg } test('defaults', async t => { await verifyPkg(t) }) test('only bili', async t => { await verifyPkg(t,{ features: [], test: false, build: 'bili' }) }) test('only babel', async t => { await verifyPkg(t,{ features: [], test: false, build: 'babel' }) }) test('launch test', async t => { await verifyPkg(t,{ features: [], test: true }) }) test('launch linter', async t => { await verifyPkg(t,{ features: ['linter'] }) }) test('launch prettier', async t => { await verifyPkg(t,{ features: ['prettier'] }) })
ok,这时候跑一下测试就跑通了
测试文件打印在snapshots/test.js.md
中,你须要一项一项检查,输入不一样变量时候,获得的文件结构和package.json 以及.eslintrc.js的内容。
这个时候,整个项目也就完成了。
咱们先在npmjs.com下注册一个账号,登陆一下npm login
登陆一下。
而后,直接npm publish成功以后,就可使用
sao npm-dev myapp
初始化一个github工程化协做开发栈了。
大部分人,不会专门去安装sao以后再调用脚手架,而更喜欢使用
npx lunz myapp
那就新添加一个cli.js
文件
#!/usr/bin/env node const path = require('path') const sao = require('sao') const generator = path.resolve(__dirname, './') const outDir = path.resolve(process.argv[2] || '.') console.log(`> Generating lunz in ${outDir}`) sao({ generator, outDir, logLevel: 2 }) .run() .catch((err) => { console.trace(err) process.exit(1) })
经过sao函数,能够轻松调用于来sao脚手架。
而后,将package.json
中的name
更名成你想发布npm全局工具名称,好比我建立的是lunz
而且,加入bin字段,且修改files字段
... "bin": "cli.js", "files": [ "cli.js", "saofile.js", "template" ], ...
这时,应用一下npm link
命令,就能够本地模拟出
lunz myapp
的效果了。
若是效果ok的话,就可使用npm publish
发包。
注意要先登陆,登陆不上的话多是由于你处在淘宝源下,请切换到npm正版源。
如今,你有什么想法,只须要随时随刻 npx lunz myapp
一下,就能够获得当前最新、最标准、最现代化的github+npm工程化实践。
把时间集中花在轮子的构建逻辑上,而不是基础配置上。
与前端之“神”并肩,经过你的经验,让前端的生态更繁荣。
若是实在想研究基础配置,不如帮助我完善这个“轮子工厂”
欢迎你们提交pull request,交最新的实践整合到项目中
github地址: https://github.com/wanthering...
git checkout -b my-new-feature
git commit -am 'Add some feature'
git push origin my-new-feature
pull request
😘