创建 Yarn/NPM 脚手架快速生成项目

背景

yarn create <starter-kit-package> [<args>] 是为了统一前端项目脚手架引入的,这之前,各项目会有自己的方式和姿势来生成新项目。

这里,约定 <starter-kit-package>create- 开头的 npm 包,通过 package.json bin 字段对外导出可执行命令用于创建新项目时执行。

当执行 yarn create <starter-kit-package> [<args>] 时,相当于执行 yarn global add <starter-kit-package>,只不过前者会自动调用 <starter-kit-package> 里的 bin 命令并透传 args 参数立即开始新项目的生成。

以 create-react-app 为例,生成项目可以这样:

$ yarn create react-app <app-name>

也可以这样:

$ yarn global add create-react-app
$ create-react-app <app-name>

npm

npm 本身有一个 init 命令,用来快速创建 package.json 文件(yarn 对应的命令为 yarn init)。后期也加入了对 create- 脚手架的支持。

用法示例:

npm init foo -> npx create-foo
npm init @usr/foo -> npx @usr/create-foo
npm init @usr -> npx @usr/create

创建 create- 开头的脚手架

由上面可知,create- 开头的脚手架 npm 包实际上是一个对外导出了可执行文件的 npm 包,所以 package.json 中需要实现 bin 字段,这与平时使用的命令行工具比如 Webpack 别无二致。

所以 bin 字段中提供的命令就是脚手架与外界沟通的桥梁,外界通过调用该命令,传递参数,调起脚手架。脚手架执行命令,解析参数,执行项目的生成逻辑。

添加示例代码

$ mkdir create-hello && cd $_
$ yarn init -y
$ touch index.js

index.js 中输入:

#!/usr/bin/env node

console.log('hello')

作为 bin 命令入口的文件需要以 #!/usr/bin/env node 开头,作用是告诉系统使用什么环境来执行该脚本。

编辑 package.json 加入 bin 字段的配置,指向刚刚添加的 index.js

{
  "name": "create-hello",
  "version": "1.0.0",
  "main": "index.js",
+  "bin": {
+     "create-hello": "./index.js"
+ },
  "license": "MIT"
}

创建项目后,需要 link 到全局,这样才能本地测试,就像使用正式发布的 npm 包一样调用。

直接在当前目录下执行:

$ yarn link 
yarn link v1.22.4
success Registered "create-hello".
info You can now run `yarn link "create-hello"` in the projects where you want to use this package and it will be used instead.
✨  Done in 0.03s.

完成 link 后,yarn 会创建可执行命令到对应路径下,具体的路径可通过 yarn global bin 来查看,

$ yarn global bin
/usr/local/bin

然后查看该目录,可看到刚刚 link 成功的命令:

$ ll /usr/local/bin | grep create-hello                                                                                                                                                                                             19:56:26
lrwxr-xr-x  1 wayouliu  admin    63B May 13 18:02 create-hello -> ../../../Users/wayouliu/.config/yarn/link/create-hello/index.js

测试执行

$ yarn create hello                                                                                                                                                                                                                 19:56:31
yarn create v1.22.4
info Using linked package for "create-hello".
/bin/sh: /usr/local/bin/create-hello: Permission denied
error Command failed.
Exit code: 126
Command: /usr/local/bin/create-hello
Arguments:
Directory: /Users/wayouliu/Documents/dev/tencent/test-tcnf
Output:

info Visit https://yarnpkg.com/en/docs/cli/create for documentation about this command.

可以看到,并没有正常执行,提示权限问题。

真实原因是被 link 的入口文件其属性为非可执行,请看:

$ ll /usr/local/bin | grep create-hello                                                                                                                                                                                             19:56:26
lrwxr-xr-x  1 wayouliu  admin    63B May 13 18:02 create-hello -> ../../../Users/wayouliu/.config/yarn/link/create-hello/index.js

$ ll /Users/wayouliu/.config/yarn/link/create-hello/index.js
total 16
-rw-r--r--  1 wayouliu  staff    43B May 13 17:57 index.js
-rw-r--r--  1 wayouliu  staff   145B May 13 18:01 package.json

这里生成到 /usr/local/bin 下的软链是 x 可执行的,但软链对应的实际文件,也就是项目中作为 bin 命令入口的 index.js,其属性不是 x

解决办法是将其转一下:

$ sudo chmod 0777  /Users/wayouliu/.config/yarn/link/create-hello/index.js

再次查看其文件属性已经变化 :

$ ll /Users/wayouliu/.config/yarn/link/create-hello/index.js                                        21:22:56
-rwxrwxrwx  1 wayouliu  staff    43B May 13 17:57 /Users/wayouliu/.config/yarn/link/create-hello/index.js

现在便可正常执行了:

$ yarn create hello 
yarn create v1.22.4
info Using linked package for "create-hello".
hello
✨  Done in 1.24s.

另,还有种情况,也需要进行如上 chmod 操作来解决,即虽然 link 成功后在 /usr/local/bin 下生成了命令,但因为文件不是可执行属性,有可能会报找不到命令的错误:

fish: Unknown command: create-hello

包含 scope 的情况

对于包含 scope 的 npm 包,执行 yarn create @foo/bar 时,实际执行的是 yarn global add @foo/create-bar

更新 package.json:

{
-  "name": "create-hello",
+  "name": "@tencent/create-hello",
  "version": "1.0.0",
  "main": "index.js",
  "bin": {
      "create-hello": "./index.js"
   },
  "license": "MIT"
}

执行 yarn link 重新 link。调试过程中难免多次进行 link 操作,所以如果之前已经 link 过同名包的话这里会失败,错误信息会类似下面这样:

$ yarn link
yarn link v1.22.4
warning There's already a package called "@tencent/create-hello" registered. This command has had no effect. If this command was run in another folder with the same name, the other folder is still linked. Please run yarn unlink in the other folder if you want to register this folder.
✨  Done in 0.03s.

yarn link 生成的软链可以在 ~/.config/yarn/link 目录找到,里面可看到所有 link 的包,通过 yarn unlink 可解决上面的告警。

参数的处理

yarn create <starter-kit-package> [<args>][<args>] 部分会透传到 bin 命令,所以在 bin 命令的入口文件中接收处理即可。

更新 index.js:

#!/usr/bin/env node

- console.log('hello')
+ console.log('hello',process.argv)

执行后的输出:

$ yarn create hello world                                                                                                                                                                                                     22:06:08
yarn create v1.22.4
info Using linked package for "create-hello".
hello [
  '/Users/wayouliu/.nvm/versions/node/v14.0.0/bin/node',
  '/usr/local/bin/create-hello',
  'world'
]
✨  Done in 0.59s.

然后脚手架中就可以根据参数要求生成相应产出。

当然,参数处理这块一般使用成熟的三方库比如 commander

相关资源