dva-源码解析

dva-cli

dva 的命令行工具
原理就是当运行 dva new my-app 时,dva-cli 就将其项目中的boilerplates文件夹拷贝到process.cwd()目录下。并且运行npm install安装项目依赖。

查看 package.json 入口在bin/dva

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// Notify update when process exits
const updater = require('update-notifier');
const pkg = require('../package.json');
updater({ pkg: pkg }).notify({ defer: true });

// dva -v
if (process.argv.slice(2).join('') === '-v') {
// ...
}

program
.usage('<command> [options]')
.on('--help', printHelp)
.parse(process.argv);

const aliases = {
g: 'generate',
};
const args = process.argv.slice(3);

// new、generate
let subcmd = program.args[0];
if (aliases[subcmd]) subcmd = aliases[subcmd];

if (!subcmd) {
program.help();
} else {
const bin = executable(subcmd);
if (bin) {
// 执行文件
wrap(spawn(bin, args, {stdio: 'inherit', customFds: [0, 1, 2]}));
} else {
program.help();
}
}

function wrap(sp) {
sp.on('close', function(code) {
process.exit(code);
});
}

// 检查是不是有相应 subcmd 的执行文件
function executable(subcmd) {
var file = join(__dirname, 'dva-' + subcmd);
if (exists(file)) {
return file;
}
}

bin/dva 是dva-cli的入口文件,其中定义了-v命令以及执行相应的subcmd命令。如果你输入了dva new my-app,那么将会执行bin/dva-new,下面看下bin/dva-new文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 没有定义项目名称
if (!program.args[0]) {
program.help();
} else {
const dest = join(process.cwd(), program.args[0]);
// 如果已经存在该项目
if (existsSync(dest)) {
console.error(error('Existing directory here, please run new command for an empty folder!'));
process.exit(1);
}
// 创建目录
mkdirpSync(dest);
// 进入目录
process.chdir(dest);
require('../lib/init')(program);
}

bin/dva-new 检查了命令的有效性,并且创建、进入项目目录,执行dva-init初始化项目,下面去看一下bin/dva-init

1
2
3
4
5
6
program
.option('--demo', 'Generate dva project for demo')
.option('--no-install', 'Install dependencies after boilerplate, default: true')
.parse(process.argv);

require('../lib/init')(program);

定义了两个配置项,然后又调用了lib/init,由于lib文件夹是打包出来的,先去看一下src/init文件吧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
function init({ demo, install }) {
const type = demo ? 'demo' : 'app';
// __dirname 为执行文件所在的目录
const cwd = join(__dirname, '../boilerplates', type);
const dest = process.cwd();
const projectName = basename(dest);

if (!emptyDir(dest)) {
error('Existing files here, please run init command in an empty folder!');
process.exit(1);
}

console.log(`Creating a new Dva app in ${dest}.`);
console.log();

// vfs:文件流处理工具,将boilerplates中的文件拷贝到process.cwd目录下,
// 如果install为true的话,则执行src/install
vfs.src(['**/*', '!node_modules/**/*'], {cwd: cwd, cwdbase: true, dot: true})
.pipe(template(dest, cwd))
.pipe(vfs.dest(dest))
.on('end', function() {
info('rename', 'gitignore -> .gitignore');
renameSync(join(dest, 'gitignore'), join(dest, '.gitignore'));
if (install) {
info('run', 'npm install');
require('./install')(printSuccess);
} else {
printSuccess();
}
})
.resume();

function printSuccess() {
//...
}
}

src/install 会去根据系统安装的依赖管理工具进行install操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 好吧 竟然没有yarn
function findNpm() {
var npms = process.platform === 'win32' ? ['tnpm.cmd', 'cnpm.cmd', 'npm.cmd'] : ['tnpm', 'cnpm', 'npm'];
for (var i = 0; i < npms.length; i++) {
try {
which.sync(npms[i]);
console.log('use npm: ' + npms[i]);
return npms[i];
} catch (e) {
}
}
throw new Error('please install npm');
}
export default function (done) {
const npm = findNpm();
// 因为boilerplates/package.json 中没有dva,所以在运行install之后,再安装dva,why?
// 为什么不干脆将dva写在boilerplates/package.json里呢,
// 因为这样就可以保证每次安装的dva都是最新版本啊 骚年。
runCmd(which.sync(npm), ['install'], function () {
runCmd(which.sync(npm), ['install', 'dva', '--save'], function () {
console.log(npm + ' install end');
done();
});
});
};

在上面我们已经分析了dva new的相关命令,其实dva还支持generate操作(文档上是这么说的),不过。。。我们来看下generate文件

1
2
3
4
5
6
7
//...
console.log(chalk.red('😢 dva generate is disabled since we don\'t have enough time currently.'));
process.exit(0);

require('../lib/generate')(program, {
cwd: process.cwd(),
});

四个字,暂未开放… 这个文档更新有点落后啊。dva-generate中又依赖了dva-ast 其中主要使用了 facebook/jscodeshift这个库,作用是解析js,将js内容解析成 AST 语法树,然后提供一些便利的操作接口,方便我们对各个节点进行更改,比如更改所有的属性名之类的。感兴趣的同学可以自己去研究一下,就送到这里啦~ dva-cli就分析到这里了,接下来我会去分析dva。