打造自己的 Create-React-App

2,864 阅读5分钟

作为前端开发者,现在已经有足够多的脚手架可供选择。官方的有 create-react-app, vue-cli, @angular/cli 等,老牌脚手架工具 Yeoman,各开发者定制脚手架更是不胜枚举。但每个人、每个团队使用的技术栈并不完全相同,因此市面上的脚手架大抵分两类:要么只包含基本功能,要么就大而全。想找到完全适合的脚手架并不容易。

前几天看到一篇文章说,要成为中高级前端就要学会定制自己的脚手架工具。我十分同意,特别是作为 Leader,为团队定制合理的模板可以有效提高团队的工作效率。

如果你使用过 create-react-app,你可能会注意到 react-scripts 这个工具,它负责处理编译打包相关工作。你不必再关注如何编写 webpack.config.js 或者 .babelrc。但 create-react-app 甚至连路由库都没预置,因此项目创建后你仍要配置一大堆依赖。

对开发者而言,技术栈的变动通常不太频繁。我希望制定模板的同时,可以像 react-scripts 一样忽略打包细节。现有的脚手架方案都不太满足我的要求,于是我写了 Tacer

按自己的技术栈,我写了 4 个模板:

  • react: React + TypeScript + Jest + styled-components
  • koa: Koa + TypeScript + Jest
  • lib: A library template for published to npmjs
  • bin: A binary template

只需要运行 npx tacer <template-name> [project-path] 就可以快速搭建项目。

创建模板

这些模板仅针对我个人喜好定制,我依然相信量身定制才是好的方案(就像定制耳机总是比公模耳机要舒服一样)。

为方便自定义模板,Tacer 提供了一个模板的模板,Seed(种子)。下文假设我们要定制一个使用 Electron 的模板。

创建脚手架

npx tacer seed tacer-template-electron
# 你也可以全局安装 tacer
npm install -g tacer
tacer seed tacer-template-electron

tacer 支持两种模板来源:

  • 公共来源:以 tacer-template- 为前缀的 npm 包,可被所有人访问
  • 私有来源:Github, GitLab 及其它自建私有 Git 仓库,其可见性由仓库类型决定

因此,使用 react 模板创建项目,可以使用两种方式:

npx tacer react project-path
npx tacer https://github.com/IdanLoo/tacer-template-react project-path

无论如何,命令执行完之后,我们已经可以看到 tacer-tepmlate-electron 的目录结构了。

- bin
    - script.js # tacer-script 所在路径,一般无需更改
- scripts # tacer-script 运行的命令
    - build.js # tacer-script build
    - start.js # tacer-script start
    - ... # 可以在此处添加其它命令
- template # 模板所在路径,需自己创建

命令

rollup 为例,讲解一下如何编写 scripts。

// build.js
const rollup = require('rollup')
const autoExternal = require('rollup-plugin-auto-external') // 处理外部依赖
const typescript = require('rollup-plugin-typescript2') // 处理 TypeScript
const { terser } = require('rollup-plugin-terser') // 代码压缩
const { resolve } = require("path")

const workdir = process.cwd() // 工作目录,即运行 tacer-script 的目录,一般为目标项目的根目录
const entrypoint = resolve(workdir, 'src', 'index.js') // 项目入口,<workdir>/src/index.js
const output = resolve(workdir, 'dist', 'index.js') // 打包路径, <workdir>/dist/index.js
// 缓存路径,<workdir>/node_modules/tacer-template-electron/.cache
const cacheRoot = resolve(__dirname, '..', '.cache')

// 没有参数
function build() {
    // 返回 Promise 对象以供其它命令调用
    return rollup({
        input: entrypoint,
        plugins: [
            autoExternal(),
            typescript({ cacheRoot }),
            terser()
        ]
    }).then(bundle => bundle.write({ file: output, format: 'cjs' }))
}

// 按 CommonJS 规范导出函数,以供 tacer-script 调用
module.exports = build

注意tacer-script 在调用时没有参数传递,我们建议为不同的命令编写不同的文件,即使是 test:unittest:e2e 这样同属于测试的命令,也应当编写 test:unit.jstest:e2e.js 两个文件。

注意:为了保持精简,我们并未内置 Babel。因此在 ES Module 足够流行之前,仍应使用 CommonJS 规范导出函数。

注意:你可以需要阅读打包工具的 JS API 以了解它们如何被 JavaScript 调用。这需要一点耐心。如果你使用的工具未提供 JS API(如 Jest)或者你更喜欢命令行调用形式,请使用 Spawn

编写模板

你可以在脚手架目录中新建一个 template 文件夹,从头开始配置你的模板,也可以复制任何现有的项目到脚手架目录中并重命为 template。按照你的喜好,配置一个最小可运行的模板。

连接模板与脚手架

在模板目录中执行

npm install --save-dev file:..
# or
yarn add -D file:..

脚手架会作为 devDependency 安装到 template/package.json 中。修改 package.jsonscripts 属性

{
    // ...
    "scripts": {
        "build": "tacer-script build",
        "start": "tacer-script start",
        // ... 其它你在脚手架中定义的命令
    },
    // ...
    "devDependencies": {
        "tacer-template-electron": "file:.." // 这个依赖在 tacer 运行时会自动处理成版本号或 Git 仓库地址
    }
}

尝试一下 npm start 是不是能正常运行吧!

注意file:.. 应始终作为 devDependency 安装。如果安装至 dependencies,tacer 将不能正确处理这个依赖。

注意tacer 运行时,会要求用户输入以下字段,并自动替换 package.json 中的值

  • name
  • version
  • description
  • repository: 如果值不为空,会自动初始化 Git,并设置 origin remote
  • keywords
  • author
  • license

打包

模板需要被打包为 template.zip 文件才能被 tacer 正确解析。tacer-template-seed 已经提供了 build 命令,只需运行 npm run build 就可以将 template 目录打包。

在模板目录我们常常会有一些不希望被打包的文件或目录(如 node_modules/, dist/),为此可使用 .tacerignore 文件忽略某些文件。

注意:打包工具只会查看 template 目录中的内容,因此 .tacerignore 文件需置于 template/.tacerignore

注意.tacerignore 的匹配规则与 .gitignore 基本一致,但有一点不同。为了实现更简单,.tacerignore 匹配目录时需写成 node_modules/** 而不是像 .gitignore 一样写成 node_modules/

发布

现在你可以把你的脚手架发布到 npmjs 或 Git 仓库了。尽情享受定制脚手架带来的便捷吧。