1. 通过什么来了解源码的功能?
我通常是通过源码中的测试来了解功能点,测试代码完全可以看做是一个活文档(后续准备些一篇关于测试的好处的文章),比如:
it('basic', () => {
const imports: Import[] = [{ from: 'test-id', name: 'fooBar', as: 'fooBar' }]
expect(toImports(imports))
.toMatchInlineSnapshot('"import { fooBar } from \'test-id\';"')
expect(toImports(imports, true))
.toMatchInlineSnapshot('"const { fooBar } = require(\'test-id\');"')
})
根据这个测试代码,我们大致知道toImports需要实现的功能:
toImports接收两个参数- 类型是
Import的一个数组 isCJS是不是commonjs的标志位
- 类型是
- 输出内容:
- 根据是否是
commonjs分别将输入为Import类型的数组转换为相应的引入语句
- 根据是否是
2. 实现功能的过程
根据测试文件可以知道,我们实现的功能需要跑通测试文件下的所有case,那么我们也就完成了相应功能,所以我们就可以通过增加一个一个的测试用例,来完整的实现这个过程,这个过程也叫做TDD.
-
实现基础功能
-
上测试代码
it('basic', () => { const imports: Import[] = [{ from: 'test-id', name: 'fooBar', as: 'fooBar' }] expect(toImports(imports)) .toMatchInlineSnapshot('"import { fooBar } from \'test-id\';"') expect(toImports(imports, true)) .toMatchInlineSnapshot('"const { fooBar } = require(\'test-id\');"') }) -
实现功能
// type Import { from: string name: string as: string } export function toImports (imports: Import[], isCJS = false) { return imports.map((importObj) => { return isCJS ? `const { ${importObj.name} } = require('${importObj.from}');` : `import { ${importObj.name} } from '${importObj.from}';` }).join('/n') }
-
-
增加别名功能
-
加一段测试代码
it('alias', () => { const imports: Import[] = [{ from: 'test-id', name: 'foo', as: 'bar' }] expect(toImports(imports)) .toMatchInlineSnapshot('"import { foo as bar } from \'test-id\';"') expect(toImports(imports, true)) .toMatchInlineSnapshot('"const { foo: bar } = require(\'test-id\');"') }) -
实现功能
分析:
as作为别名,name作为模块导出的名称,所以只有当as和name才不需要生成语句的as后面的名称// type Import { from: string name: string as: string } // 增加一个函数 // 导出的名称分为 as 和 name 相等以及不相等的情况 function stringifyImportAlias (item: Import, isCJS = false) { return item.name === item.as ? item.name : isCJS ? `${item.name}: ${item.as}` : `${item.name} as ${item.as}` } export function toImports (imports: Import[], isCJS = false) { return imports.map((importObj) => { return isCJS ? `const { ${stringifyImportAlias(importObj)} } = require('${importObj.from}');` : `import { ${stringifyImportAlias(importObj)} } from '${importObj.from}';` }).join('/n') }
-
-
如果引入中有多个相同的
from-
增加测试代码
it('multiple', () => { const imports: Import[] = [ { from: 'test1', name: 'foo', as: 'foo' }, { from: 'test1', name: 'bar', as: 'bar' }, { from: 'test2', name: 'foobar', as: 'foobar' } ] expect(toImports(imports)) .toMatchInlineSnapshot(` "import { foo, bar } from 'test1'; import { foobar } from 'test2';" `) expect(toImports(imports, true)) .toMatchInlineSnapshot(` "const { foo, bar } = require('test1'); const { foobar } = require('test2');" `) }) -
实现功能
分析:
from可以有多个,最后可以转换为import {foo1, foo2} from 'bar',所以我们想通过对象的结构:key是from的值,value是from相同的值的一个set集合。function stringifyImportAlias (item: Import, isCJS = false) { return item.name === item.as ? item.name : isCJS ? `${item.name}: ${item.as}` : `${item.name} as ${item.as}` } export function toImportModuleMap (imports: Import[]) { const map: Record<Import['from'], Set<Import>> = {} for (const _import of imports) { if (!map[_import.from]) { map[_import.from] = new Set() } map[_import.from].add(_import) } return map } export function toImports (imports: Import[], isCJS = false) { // 转换为分析的对象形式 const map = toImportModuleMap(imports) // 遍历返回 return Object.entries(map).map(([name, importSet]) => { return isCJS ? `const { ${Array.from(importSet).map(importObj => stringifyImportAlias(importObj)).join(', ')} } = require('${name}');` : `import { ${Array.from(importSet).map(importObj => stringifyImportAlias(importObj)).join(', ')} } from '${name}';` }).join('\n') }
-
-
增加
defult-
增加测试的代码
it('default', () => { const imports: Import[] = [ { from: 'test1', name: 'default', as: 'foo' } ] expect(toImports(imports)) .toMatchInlineSnapshot('"import foo from \'test1\';"') expect(toImports(imports, true)) .toMatchInlineSnapshot('"const { default: foo } = require(\'test1\');"') }) -
功能实现
分析:将
name为default的进行单独处理function stringifyImportAlias (item: Import, isCJS = false) { return item.name === item.as ? item.name : isCJS ? `${item.name}: ${item.as}` : `${item.name} as ${item.as}` } export function toImportModuleMap (imports: Import[]) { const map: Record<Import['from'], Set<Import>> = {} for (const _import of imports) { if (!map[_import.from]) { map[_import.from] = new Set() } map[_import.from].add(_import) } return map } export function toImports (imports: Import[], isCJS = false) { const map = toImportModuleMap1(imports) return Object.entries(map).map(([name, importSet]) => { const entries:string [] = [] const _imports = Array.from(importSet).filter((i) => { if (i.name === 'default') { entries.push( isCJS ? `const { default: ${i.as} } = require('${name}');` : `import ${i.as} from '${name}';` ) return false } return true }) if (_imports.length) { const importsAs = _imports.map(importObj => stringifyImportAlias(importObj)) entries.push( isCJS ? `const { ${importsAs.join(', ')} } = require('${name}');` : `import { ${importsAs.join(', ')} } from '${name}';` ) } return entries }).join('\n') }
-
-
增加
*-
增加测试用例
it('import all as', () => { const imports: Import[] = [ { from: 'test1', name: '*', as: 'foo' } ] expect(toImports(imports)) .toMatchInlineSnapshot('"import * as foo from \'test1\';"') expect(toImports(imports, true)) .toMatchInlineSnapshot('"const foo = require(\'test1\');"') }) -
功能实现
分析:将
*单独处理export function toImports (imports: Import[], isCJS = false) { const map = toImportModuleMap(imports) return Object.entries(map).map(([name, importSet]) => { const entries:string [] = [] const _imports = Array.from(importSet).filter((i) => { if (i.name === 'default') { entries.push( isCJS ? `const { default: ${i.as} } = require('${name}');` : `import ${i.as} from '${name}';` ) return false } else if (i.name === '*') { // 增加 * 的单独处理 entries.push( isCJS ? `const ${i.as} = require('${name}');` : `import * as ${i.as} from '${name}';` ) return false } return true }) if (_imports.length) { const importsAs = _imports.map(importObj => stringifyImportAlias(importObj)) entries.push( isCJS ? `const { ${importsAs.join(', ')} } = require('${name}');` : `import { ${importsAs.join(', ')} } from '${name}';` ) } return entries }).join('\n') }
-
-
其他边界处理
-
增加测试用例
it('mixed', () => { const imports: Import[] = [ { from: 'test1', name: '*', as: 'foo' }, { from: 'test1', name: '*', as: 'bar' }, { from: 'test1', name: 'foo', as: 'foo' }, { from: 'test1', name: 'bar', as: 'bar' }, { from: 'test2', name: 'foobar', as: 'foobar' }, { from: 'test2', name: 'default', as: 'defaultAlias' }, { from: 'sideeffects', name: '', as: '' } ] expect(toImports(imports)) .toMatchInlineSnapshot(` "import * as foo from 'test1'; import * as bar from 'test1'; import { foo, bar } from 'test1'; import defaultAlias from 'test2'; import { foobar } from 'test2'; import 'sideeffects';" `) expect(toImports(imports, true)) .toMatchInlineSnapshot(` "const foo = require('test1'); const bar = require('test1'); const { foo, bar } = require('test1'); const { default: defaultAlias } = require('test2'); const { foobar } = require('test2'); require('sideeffects');" `) }) -
功能实现
分析:增加
name或者as为空的条件export function toImports (imports: Import[], isCJS = false) { const map = toImportModuleMap(imports) return Object.entries(map).flatMap(([name, importSet]) => { const entries:string [] = [] const _imports = Array.from(importSet).filter((i) => { if (i.name === '' || i.as === '') { entries.push( isCJS ? `require('${name}');` : `import '${name}';` ) return false } else if (i.name === 'default') { entries.push( isCJS ? `const { default: ${i.as} } = require('${name}');` : `import ${i.as} from '${name}';` ) return false } else if (i.name === '*') { entries.push( isCJS ? `const ${i.as} = require('${name}');` : `import * as ${i.as} from '${name}';` ) return false } return true }) if (_imports.length) { const importsAs = _imports.map(importObj => stringifyImportAlias(importObj)) entries.push( isCJS ? `const { ${importsAs.join(', ')} } = require('${name}');` : `import { ${importsAs.join(', ')} } from '${name}';` ) } return entries }).join('\n') }
-
3.总结
以上我们便是通过TDD的思想实现toImports函数,我们可以通过测试文件知道函数的意图,然后通过一个一个的case的通过来实现功能。只要将所有的case通过那么我们的功能函数就是我们期待的