源码学习(一)

108 阅读8分钟

.gitattributes 是干嘛的

gitattributes 是一个文本文件,文件中的一行定义一个路径的若干个属性,主要用于定义每种文件的属性,以方便 git 帮我们统一管理。

.editorconfig 是干嘛的

.editorConfig和Prettier一样,都是用来配置格式化代码.该文件用来定义项目的编码规范,编辑器的行为会与.editorconfig 文件中定义的一致,并且其优先级比编辑器自身的设置要高,这在多人合作开发项目时十分有用而且必要。

.editorConfig 文件中的设置用于在基本代码库中维持一致的编码风格和设置,例如缩进样式、选项卡宽度、行尾字符以及编码等,而无需考虑使用的编辑器或 IDE。

circle.yml 是如何配置的

在创建.circleci/config.yml文件并将其提交到GitHub储库后,CircleCI会立即检查您的代码并运行配置好的测试代码。

每次job,ci都会启动一个容器来运行该job

version: 你要使用的ci版本 job: 你要执行的 job 清单,集合中的键为 job 的名称,值是具体执行 job 的内容,如果你使用工作流(workflows),则 job 的名称在配置文件中必须唯一,如果你不使用 工作流(workflows),则必须包含名称为build的 job 来作为用户提交代码时的默认 job。 docker : 是用来指定 CircleCI 当前 job 使用 docker, 其值image是指 docker 所使用的镜像,必要时你可以同时指定多个镜像,比如你的项目需要依赖 mysql 或者 redis。 第一个列出的容器为主容器,steps 都会在主容器中进行。 steps: 当前 job 要运行的 命令 (command) 列表 working_directory: 属性是用来定义steps 在哪个目录运行。

构建、测试

1.检出代码

- checkout

2.安装依赖

- run:
    name: install dependences
    command: yarn

3.缓存依赖

- save_cache:
    key: dependency-cache-{{ checksum "yarn.lock" }}
    paths:
        -./node_modules

4.测试

- run:
    name: test
    command: yarn test:cov    

分析一下 package.json 里面的字段都是干嘛的

参考:https://www.jianshu.com/p/39eb5624bad1

package.json文件是一个JSON对象,从后缀名.json就可以看出,该对象的每一个成员就是当前项目的一项设置。

配置字段详细剖析

name

package.json文件中最重要的就是name和version字段,这两项是必填的。名称和版本一起构成一个标识符,该标识符被认为是完全唯一的。对包的更改应该与对版本的更改一起进行。

name必须是字符串,不能以.或_开头,不能有大写字母,因为名称最终成为URL的一部分因此不能包含任何非URL安全字符。 npm官方建议我们不要使用与核心节点模块相同的名称。不要在名称中加js或node。如果需要可以使用engines来指定运行环境。

该名称会作为参数传递给require,因此它应该是简短的,但也需要具有合理的描述性。

"name": "cac",

version

Version项目版本号, x.x.x的格式, 符合语义化版本规则(遵循“大版本.次要版本.小版本”的格式规定),在项目每次发布时需提交新的且唯一版本号

"version": "6.0.0",

exports

exports 字段声明了一个对应关系,用 import "package"import "package/sub/path" 会返回不同的模块。

这替换了默认返回 main 字段文件的行为。

根据模块的引用语法,来引用不同的文件:

"exports": {
    ".": {
      "import": "./dist/index.mjs",
      "require": "./index-compat.js"
    },
    "./package.json": "./package.json",
    "./": "./"
  },

description

项目描述信息,description是字符串,便于用户在npm上搜索到我们的项目

"description": "Simple yet powerful framework for building command-line apps.",

keywords

keywords是一个字符串组成的数组,便于用户在npm上搜索到我们的项目

"keywords":["node.js","vue", "element"],

homepage

homepage项目的主页地址。

"homepage": "https://github.com/owner"

license

项目许可证,让使用者知道如何使用此项目,有何权限来使用我们的模块,以及使用该模块有限制等

"license" : "BSD-3-Clause"

author,contributors

Author表示一个person对象

Contributors表示person的数组,相当于一群person

包含了作者及贡献者(我们的模块开发者以及众多贡献者)

一个对象中包含name、url和email

"author": { "name": "xiaoming", "email": "xiaoming@163.com", "url": " www.xiaoming.cn" } "contributors":[{"name":" xiaoming","email":" xiaoming2016@163.com ","url": " www.xiaoming.cn }] files

files是一个文件数组,描述了将软件包作为依赖项安装时要包括的条目。如果在数组里面声明了一个文件夹,那也会包含文件夹中的文件。某些特殊文件和目录也被包括或排除在外,无论它们是否存在于文件数组中。简而言之,下载依赖包所包含的文件

main

主文件,也是项目的入口文件,默认值是项目根目录下的index.js。

"main": "./index.js",

browser

如果要在客户端使用模块,则应使用browser字段来代替main字段。

bin

bin用来指定各个内部命令对应的可执行文件的位置。

"bin": {
  "mybuild": "./bin/mybuild.js"
}

上面代码指定,mybuild命令对应的可执行文件为 bin 子目录下的 mybuild.js。当本地安装myapp时,Npm会寻找这个文件,在./node_modules/.bin/目录下建立符号链接(快捷方式)。在上面的例子中,mybuild.js会建立符号链接./node_modules/.bin/mybuild。 ./node_modules/.bin/`目录会在运行时加入系统的PATH变量,因此在运行npm时,就可以不带路径,直接通过命令来调用这些脚本。

npx mybuild 所有node_modules/.bin/目录下的命令,都可以用npm run [命令]或npx [命令]的格式运行。bin中引用的文件需以#!/usr/bin/envnode开头

man

用来指定当前模块的man文档的位置

"man" :[ "./doc/doc.1" ] directories

用来标识模块结构的方法,类似于commonjs包规范的介绍,想查看npm中的package.json,就可以看到doc、lib、man目录及位置

repository

代码存放的地址

"repository": {
    "type" : "git",
    "url" : "https://github.com/cacjs/cac"
  }

scripts

scripts指定了运行脚本命令的npm命令行缩写

  
  "scripts": {
    "test": "jest", //测试
    "test:cov": "jest --coverage",
    "build:deno": "node -r sucrase/register scripts/build-deno.ts", // deno打包
    "build:node": "rollup -c", //打包
    "build": "yarn build:deno && yarn build:node",
    "toc": "markdown-toc -i README.md",
    "prepublishOnly": "npm run build && cp mod.js mod.mjs",
    "docs:api": "typedoc --out api-doc --readme none --exclude "**/__test__/**" --theme minimal"
  },

config

用于添加命令行的环境变量

"config": {
    "commitizen": {
      "path": "./node_modules/cz-conventional-changelog"
    }
  },

devDependencies 和 dependencies

dependencies,依赖,这些安装包都是程序所依赖的包,需要发布到生产环境的.

dev即develop开发环境下的依赖。

--save参数表示将该模块写入dependencies属性,--save-dev表示将该模块写入devDependencies属性

例如安装axios

安装到开发环境 npm axios --save-dev

安装到生产环境 npm axios --save

区别:

devDependencies中的插件只用于开发环境,不用于生产环境,而dependencies是要发布到生产环境

的。比如babel有关的转化es6到es5的依赖只是开发环境下转化用,生产过程中是用不到,所以只用写

在devDependencies中,而像vue或element-ui这种实际运行会调用,得写在dependencies中。

版本前可以加上各种限制,主要有以下几种: 指定版本:比如1.2.2,遵循“大版本.次要版本.小版本”的格式规定,安装时只安装指定版本。

波浪号(tilde)+指定版本:比如~1.2.2,表示安装1.2.x的最新版本(不低于1.2.2),但是不安装1.3.x,也就是说安装时不改变大版本号和次要版本号。

插入号(caret)+指定版本:比如ˆ1.2.2,表示安装1.x.x的最新版本(不低于1.2.2),但是不安装2.x.x,也就是说安装时不改变大版本号。需要注意的是,如果大版本号为0,则插入号的行为与波浪号相同,这是因为此时处于开发阶段,即使是次要版本号变动,也可能带来程序的不兼容。

latest:安装最新版本。

举例说明下:

1.0.2 大于当前版本

=1.0.2 大于等于当前版本

<1.0.2 小于当前版本

<=1.0.2小于等于当前版本

~1.0.2 不低于1.0.2,但不改变大版本号和次要版本号

^1.0.2 不低于1.0,2, 但不改变大版本号

1.2.x 表示1.2.3 ,1.2.4版本都支持

peerDependencies

当我们开发一个模块的时候,如果当前模块与所依赖的模块同时依赖一个第三方模块,并且依赖的是两个不兼容的版本时就会出现问题。

比如,你的项目依赖A模块和B模块的1.0版,而A模块本身又依赖B模块的2.0版。

大多数情况下,这不构成问题,B模块的两个版本可以并存,同时运行。但是,有一种情况,会出现问题,就是这种依赖关系将暴露给用户。

最典型的场景就是插件,比如A模块是B模块的插件。用户安装的B模块是1.0版本,但是A插件只能和2.0版本的B模块一起使用。这时,用户要是将1.0版本的B的实例传给A,就会出现问题。因此,需要一种机制,在模板安装的时候提醒用户,如果A和B一起安装,那么B必须是2.0模块。

peerDependencies字段,就是用来供插件指定其所需要的主工具的版本

从npm 3.0版开始,peerDependencies不再会默认安装了

bundledDependencies

bundledDependencies是一个数组,指定发布时将定义的模块一起打包

{
  "name": "vue-project",
  "version": "1.0.0",
  "bundledDependencies": [
    "elementui", "echarts"
  ]
}

optionaldependencies

如果出现包找不到或者安装失败时,但又不影响npm继续运行,可将该包放在optionalDependencies对象中。

 "optionalDependencies": {
    "echarts": "^4.9.0"
 }

表示的是定义的模块如果安装失败,不会在输入npm install时失败

engines

engines字段指明了该模块运行的平台,比如Node``的某个版本,或者npm的某个版本或者浏览器。

"engines": {"node" : ">=8.9.0 <12.x", "npm" : "~6.14.12" } os

指定你的项目将运行在什么操作系统上

"os" : [ "win32", "darwin", "linux" ], private

决定我们的项目是否会发布,如果设置为true,那么npm会拒绝发布

"private": true publishConfig

模块发布时生效,设置一些值的集合

通常publishConfig会配合private来使用,如果你只想让模块被发布到一个特定的npm仓库,如一个内部的仓库

"private": true,
  "publishConfig": {
    "tag": "1.0.0",
    "registry": "https://registry.npmjs.org/",
    "access": "public"
   }

preferGlobal

表示在不安装为全局时给予显示警告

写一个库的 README 需要哪几个部分?

  1. 项目介绍
  2. 代码实现了什么功能?
  3. 该如何使用? (系统环境参数,部署要素,操作说明等)
  4. 代码组织架构是什么样的?(目录结构说明等)
  5. 版本更新内容摘要(这个非常重要)

分析 rollup.config.js

 
  {
    input: 'src/index.ts', // 输入
    output: { // 输出的文件格式
      format: dts || esm ? 'esm' : 'cjs',
      file, // 输出的文件
      exports: 'named',
    },
    plugins: [  // 利用插件处理
      nodeResolvePlugin({
        mainFields: dts ? ['types', 'typings'] : ['module', 'main'],
        extensions: dts ? ['.d.ts', '.ts'] : ['.js', '.json', '.mjs'],
        customResolveOptions: {
          moduleDirectories: dts
            ? ['node_modules/@types', 'node_modules']
            : ['node_modules'],
        },
      }),
      !dts && require('@rollup/plugin-commonjs')(),
      !dts &&
        esbuildPlugin({
          target: 'es2017',
        }),
      dts && dtsPlugin(),
    ].filter(Boolean),
  }

分析一下 tsconfig 里面的配置项

{
  "compilerOptions": {
    "target": "es2015", // 目标语言的版本
    "declaration": true, // 生成声明文件,开启后会自动生成声明文件
    "declarationDir": "types", // 指定生成声明文件存放目录
    "esModuleInterop": true, // 允许export=导出,由import from 导入
    "pretty": true,
    "moduleResolution": "node", // 模块解析策略,ts默认用node的解析策略,即相对的方式导入
    "lib": ["es2015", "es2016.array.include"], // TS需要引用的库,即声明文件,es5 默认引用dom、es5、scripthost,如需要使用es的高级版本特性,通常都需要配置,如es8的数组新特性需要引入"ES2019.Array",
    "allowSyntheticDefaultImports": true,
    "stripInternal": true,
    "noImplicitAny": true,  // 不允许隐式的any类型
    "noImplicitReturns": true, //每个分支都会有返回值
    "noImplicitThis": true, // 不允许this有隐式的any类型
    "noUnusedLocals": true, // 检查只声明、未使用的局部变量(只提示不报错)
    "noUnusedParameters": true,  // 检查未使用的函数参数(只提示不报错)
    "noFallthroughCasesInSwitch": true, // 防止switch语句贯穿(即如果没有break语句后面不会执行)
    "strictNullChecks": true, // 不允许把null、undefined赋值给其他类型的变量
    "strictFunctionTypes": true, // 不允许函数参数双向协变
    "strictPropertyInitialization": true, // 类的实例属性必须初始化
    "alwaysStrict": true, // 在代码中注入'use strict'
    "module": "commonjs", // 生成代码的模板标准
    "outDir": "lib" // 指定输出目录
  },
  "include": ["src", "declarations.d.ts"],
  "exclude": ["src/deno.ts"]
}

ts-jest 是解决什么问题的

安装依赖

主要就是四个库: jesttypescript自不必说,ts-jest是要用到的预处理器,@types/jest用于测试框架的类型推断。

yarn add -D jest typescript ts-jest @types/jest

生成配置

yarn ts-jest config:init

这里是利用 ts-jest 自动生成 jest.config.js 文件。生成的代码如下

// jest.config.js
module.exports = {
  preset: "ts-jest",
  testEnvironment: "node",

运行

yarn jest

简单的单元测试用例:

describe("computed", () => {
    it("happy path", () => {
        const user = reactive({ age: 1 });
        const age = computed(() => {
            return user.age
        });
        expect(age.value).toBe(1);
    });
  });
})

分析一下 jest.config.js 这几个字段都有什么用?

官方文档:jestjs.io/zh-Hans/doc…

参考:www.bilibili.com/read/cv1048…

module.exports = {
  testEnvironment: 'node',
  transform: { // 设置哪些文件中的代码是需要被相应的转译器转换成 Jest
    '^.+\.tsx?$': 'ts-jest'
  },
  testRegex: '(/__test__/.*|(\.|/)(test|spec))\.tsx?$',
  testPathIgnorePatterns: ['/node_modules/', '/dist/', '/types/'], // 用正则来匹配不用测试的文件
  moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'] // 代表支持加载的文件名

CAC文档

安装CAC

yarn add cac

引入cac

src/cli.ts
import cac from 'cac'
// 从package.json中获取版本及名字
import { name, version } from '../package.json'
// 实例化cac
const cli = cac(name)
/**
 * 定义异常处理公共函数
 * @param err 
 */
const onError = (err: Error): void => {
    console.error(err.message)
    process.exit(1)
}
// 监听未捕获的异常事件
process.on('uncaughtException', onError)
// 监听Promise未捕获的异常事件
process.on('unhandledRejection', onError)
​
cli
    // 添加命令 ‘<>’ 中为必填项,'[]'中为选填项
    .command('<template> [project]', 'Create new project from a template')
    // 添加配置 --force 简写为 -f; 
    // 如果目标存在则覆盖
    .option('-f, --force', 'Overwrite if the target exists')
    // 添加配置 --offline 简写为 -o ;
    // 是否使用本地模板
    .option('-o, --offline', 'Try to use an offline template')
    // 示例内容配置
    .example('  # with an official template')
    .example(`  $ ${name} <template> [project]`)
    .example('  # with a custom github repo')
    .example(`  $ ${name} <owner>/<repo> [project]`)
    // 定义一个动作,传入一个回调函数
    .action((template: string, project: string = '.', options) => {
        console.log(template, project, options)
    })
​
// -h, --help出现标志时输出帮助信息。
cli.help()
​
// -v, --version 出现标志时输出版本号。
cli.version(version)
​
cli.parse()
​

测试命令

# 默认命令
<projectName>  template  project -f -o
# 输出结果
# 第一个参数是传入的模板名字 第二个是项目名字  第三个是传入的options 
template project { '--': [], f: true, o: true, force: true, offline: true }

# 帮助命令
<projectName>  -h 
<projectName>  --help
# 输出结果
<projectName> v1.0.0
Usage:
  $ <projectName> <template> [project]
Commands:
  <template> [project]  Create new project from a template
For more info, run any command with the `--help` flag:
  $ <projectName> --help
Options:
  -f, --force    Overwrite if the target exists 
  -o, --offline  Try to use an offline template 
  -h, --help     Display this message 
  -v, --version  Display version number 
Examples:
  # with an official template
  $ <projectName> <template> [project]
  # with a custom github repo
  $ <projectName> <owner>/<repo> [project]
​
# 查看版本命令
<projectName>  -v
<projectName>  --version
# 输出结果
# <projectName>/1.0.0 darwin-x64 node-v13.13.0

\