美文网首页
React Native自定义脚手架

React Native自定义脚手架

作者: leach_chen | 来源:发表于2023-05-28 20:14 被阅读0次

    一、react-native-cli 官方脚手架分析
    1:nodejs加载文件并执行:
    首先看个node加载其它文件代码并执行例子:

    文件index.js:
    var path = require('path');  //引入node路径库
    var CLI_MODULE_PATH = function() {  //构建路径对象  当前命令行所在路径+"/run.js"
      return path.resolve(
        process.cwd(), 
        'run.js'
      );
    };
    var cliPath = CLI_MODULE_PATH(); //得到路径对象
    var cli = require(cliPath);  //执行加载并返回
    cli.default.run() //加载运行run.js文件,运行run.js文件里的run方法
    ----------------------------------------------------------------------------------------
    文件run.js:
    console.log("code load")
    async function run()
    {
        console.log("exec run function")
    }
    var _default = {
      run
    };
    exports.default = _default;
    

    运行index.js:


    image.png

    2:react-native-cli源码解析(2.0.1版本):
    React Native发布为两个npm包,分别是:react-native-cli、react-native。

    1:react-native-cli需要被全局安装,作为脚手架在命令行工具中使用。react-native-cli比较轻量级,进入其目录主要能看到一个index.js代码文件,它的主要工作是将所有命令交给本地的react-native执行及创建项目;

    2:react-native包含源码及模板项目等大部分功能;

    image.png
    目录结构:

    image

    那当我们执行react-native init xxx命名创建项目时,代码的执行过程是怎样的呢?


    image.png

    react-native-cli中创建项目的主要函数执行过程(RN项目启动服务过程原理类似):


    image.png

    react-native-cli源码:

    `#!/usr/bin/env node`
    
    `'use strict'``;`
    
    `//require导入nodejs相关功能模块`
    
    `var fs = require(``'fs'``); ``//文件操作`
    
    `var path = require(``'path'``); ``//路径操作`
    
    `var exec = require(``'child_process'``).exec; ``//子进程执行命令`
    
    `var execSync = require(``'child_process'``).execSync; ``//子进程执行命令,异步`
    
    `var chalk = require(``'chalk'``); ``//作用是修改控制台中字符串的样式,包括:字体样式(加粗、隐藏等)、字体颜色、背景颜色`
    
    `var prompt = require(``'prompt'``); ``//命令行交互,如可以控制命令行输入确认`
    
    `var semver = require(``'semver'``); ``//semver可以作为一个node模块,同时也可以作为一个命令行工具。功能包括:比较两个版本号的大小、验证某个版本号是否合法、提取版本号,例如从“=v1.2.1”体取出"1.2.1"、分析版本号是否属于某个范围或符合一系列条件`
    
    `var options = require(``'minimist'``)(process.argv.slice(``2``)); ``//命令行参数解析工具,如react-native -v,可以解析到v参数`
    
    `//得到路径操作对象,为当前目录/node_modules/react-native/cli.js`
    
    `var CLI_MODULE_PATH = function() {`
    
    `return` `path.resolve(`
    
    `process.cwd(),`
    
    `'node_modules'``,`
    
    `'react-native'``,`
    
    `'cli.js'`
    
    `);`
    
    `};`
    
    `//得到路径操作对象,为当前目录/node_modules/react-native/package.json`
    
    `var REACT_NATIVE_PACKAGE_JSON_PATH = function() {`
    
    `return` `path.resolve(`
    
    `process.cwd(),`
    
    `'node_modules'``,`
    
    `'react-native'``,`
    
    `'package.json'`
    
    `);`
    
    `};`
    
    `//获取命令后面带“-”的参数,如执行react-native -v、react-native -version 会进入到if里面`
    
    `if` `(options._.length === ``0` `&& (options.v || options.version)) {`
    
    `printVersionsAndExit(REACT_NATIVE_PACKAGE_JSON_PATH());`
    
    `}`
    
    `//获取yarn版本号,如果有安装的话,创建项目时调用的方法`
    
    `function getYarnVersionIfAvailable() {`
    
    `var yarnVersion;`
    
    `try` `{`
    
    `// execSync returns a Buffer -> convert to string`
    
    `if` `(process.platform.startsWith(``'win'``)) {  ``//执行yarn --version获取到yarn版本号`
    
    `yarnVersion = (execSync(``'yarn --version'``).toString() || ``''``).trim();`
    
    `} ``else` `{`
    
    `yarnVersion = (execSync(``'yarn --version 2>/dev/null'``).toString() || ``''``).trim();`
    
    `}`
    
    `} ``catch` `(error) {`
    
    `return` `null``;`
    
    `}`
    
    `// yarn < 0.16 has a 'missing manifest' bug`
    
    `try` `{`
    
    `if` `(semver.gte(yarnVersion, ``'0.16.0'``)) { ``//如果当前yarn版本号比0.16.0大,则返回当前版本号`
    
    `return` `yarnVersion;`
    
    `} ``else` `{`
    
    `return` `null``;`
    
    `}`
    
    `} ``catch` `(error) {`
    
    `console.error(``'Cannot parse yarn version: '` `+ yarnVersion);`
    
    `return` `null``;`
    
    `}`
    
    `}`
    
    `var cli;`
    
    `var cliPath = CLI_MODULE_PATH();  ``//得到路径操作对象,为当前目录/node_modules/react-native/cli.js`
    
    `if` `(fs.existsSync(cliPath)) {`
    
    `cli = require(cliPath); ``//如果该文件存在,则得到执行cli.js的对象`
    
    `}`
    
    `var commands = options._; ``//获取参数,如react-native init,则可以通过commands对象获取到init,如果参数带“-”,如react-native -init,则commands长度为0`
    
    `if` `(cli) {`
    
    `cli.run(); ``//调用该文件中的run方法,具体机制可以参考上面的"nodejs加载文件并执行"`
    
    `} ``else` `{`
    
    `if` `(options._.length === ``0` `&& (options.h || options.help)) { ``//当执行该文件时如果参数带有 -h、-help时会进入到if里面,打印出相关帮助`
    
    `console.log([`
    
    `''``,`
    
    `'  Usage: react-native [command] [options]'``,`
    
    `''``,`
    
    `''``,`
    
    `'  Commands:'``,`
    
    `''``,`
    
    `'    init <ProjectName> [options]  generates a new project and installs its dependencies'``,`
    
    `''``,`
    
    `'  Options:'``,`
    
    `''``,`
    
    `'    -h, --help    output usage information'``,`
    
    `'    -v, --version output the version number'``,`
    
    `''``,`
    
    `].join(``'\n'``));`
    
    `process.exit(``0``);`
    
    `}`
    
    `if` `(commands.length === ``0``) { ``//执行的命令没有带参数`
    
    `console.error(`
    
    `'You did not pass any commands, run `react-native --help` to see a list of all available commands.'`
    
    `);`
    
    `process.exit(``1``);`
    
    `}`
    
    `switch` `(commands[``0``]) {`
    
    `case` `'init'``:  ``//执行react-native init时`
    
    `if` `(!commands[``1``]) {`
    
    `console.error(`
    
    `'Usage: react-native init <ProjectName> [--verbose]'`
    
    `);`
    
    `process.exit(``1``);`
    
    `} ``else` `{`
    
    `init(commands[``1``], options); ``//react-native项目创建`
    
    `}`
    
    `break``;`
    
    `default``: ``//执行非react-native init时`
    
    `console.error(`
    
    `'Command `%s` unrecognized. '` `+`
    
    `'Make sure that you have run `npm install` and that you are inside a react-native project.'``,`
    
    `commands[``0``]`
    
    `);`
    
    `process.exit(``1``);`
    
    `break``;`
    
    `}`
    
    `}`
    
    `//校验项目名称是否合法`
    
    `function validateProjectName(name) {`
    
    `if` `(!name.match(/^[$A-Z_][``0``-9A-Z_$]*$/i)) {`
    
    `console.error(`
    
    `'"%s" is not a valid name for a project. Please use a valid identifier '` `+`
    
    `'name (alphanumeric).'``,`
    
    `name`
    
    `);`
    
    `process.exit(``1``);`
    
    `}`
    
    `if` `(name === ``'React'``) {`
    
    `console.error(`
    
    `'"%s" is not a valid name for a project. Please do not use the '` `+`
    
    `'reserved word "React".'``,`
    
    `name`
    
    `);`
    
    `process.exit(``1``);`
    
    `}`
    
    `}`
    
    `//创建项目,决定用哪个方法创建`
    
    `function init(name, options) {`
    
    `validateProjectName(name);`
    
    `if` `(fs.existsSync(name)) {`
    
    `createAfterConfirmation(name, options);`
    
    `} ``else` `{`
    
    `createProject(name, options);`
    
    `}`
    
    `}`
    
    `//创建项目时如果项目已经存在,则先进行提示`
    
    `function createAfterConfirmation(name, options) {`
    
    `prompt.start();`
    
    `var property = {`
    
    `name: ``'yesno'``,`
    
    `message: ``'Directory '` `+ name + ``' already exists. Continue?'``,`
    
    `validator: /y[es]*|n[o]?/,`
    
    `warning: ``'Must respond yes or no'``,`
    
    `default``: ``'no'`
    
    `};`
    
    `prompt.get(property, function (err, result) {`
    
    `if` `(result.yesno[``0``] === ``'y'``) {`
    
    `createProject(name, options);`
    
    `} ``else` `{`
    
    `console.log(``'Project initialization canceled'``);`
    
    `process.exit();`
    
    `}`
    
    `});`
    
    `}`
    
    `//进行项目创建`
    
    `function createProject(name, options) {`
    
    `var root = path.resolve(name);`
    
    `var projectName = path.basename(root);`
    
    `console.log(`
    
    `'This will walk you through creating a new React Native project in'``,`
    
    `root`
    
    `);`
    
    `if` `(!fs.existsSync(root)) {`
    
    `fs.mkdirSync(root);`
    
    `}`
    
    `var packageJson = {`
    
    `name: projectName,`
    
    `version: ``'0.0.1'``,`
    
    `private``: ``true``,`
    
    `scripts: {`
    
    `start: ``'node node_modules/react-native/local-cli/cli.js start'`
    
    `}`
    
    `};`
    
    `fs.writeFileSync(path.join(root, ``'package.json'``), JSON.stringify(packageJson));`
    
    `process.chdir(root);`
    
    `run(root, projectName, options);`
    
    `}`
    
    `//获取需要安装的包,如果有传版本号则会得到安装指定版本号`
    
    `function getInstallPackage(rnPackage) {`
    
    `var packageToInstall = ``'react-native'``;`
    
    `var isValidSemver = semver.valid(rnPackage); ``//如果执行的这个命令react-native init TestPrj --version 0.60.1,则isValidSemver 得到的是0.60.1`
    
    `if` `(isValidSemver) {`
    
    `packageToInstall += ``'@'` `+ isValidSemver;`
    
    `} ``else` `if` `(rnPackage) {`
    
    `// for tar.gz or alternative paths`
    
    `packageToInstall = rnPackage;`
    
    `}`
    
    `return` `packageToInstall;`
    
    `}`
    
    `//创建项目时运行的函数`
    
    `function run(root, projectName, options) {`
    
    `// E.g. '0.38' or '/path/to/archive.tgz'`
    
    `const` `rnPackage = options.version;`
    
    `const` `forceNpmClient = options.npm;`
    
    `const` `yarnVersion = (!forceNpmClient) && getYarnVersionIfAvailable();`
    
    `var installCommand;`
    
    `if` `(options.installCommand) {`
    
    `// In CI environments it can be useful to provide a custom command,`
    
    `// to set up and use an offline mirror for installing dependencies, for example.`
    
    `installCommand = options.installCommand;`
    
    `} ``else` `{`
    
    `if` `(yarnVersion) {`
    
    `console.log(``'Using yarn v'` `+ yarnVersion);`
    
    `console.log(``'Installing '` `+ getInstallPackage(rnPackage) + ``'...'``);`
    
    `installCommand = ``'yarn add '` `+ getInstallPackage(rnPackage) + ``' --exact'``;`
    
    `if` `(options.verbose) {`
    
    `installCommand += ``' --verbose'``;`
    
    `}`
    
    `} ``else` `{`
    
    `console.log(``'Installing '` `+ getInstallPackage(rnPackage) + ``'...'``);`
    
    `if` `(!forceNpmClient) {`
    
    `console.log(``'Consider installing yarn to make this faster: [https://yarnpkg.com](https://yarnpkg.com/)'``);`
    
    `}`
    
    `installCommand = ``'npm install --save --save-exact '` `+ getInstallPackage(rnPackage);`
    
    `if` `(options.verbose) {`
    
    `installCommand += ``' --verbose'``;`
    
    `}`
    
    `}`
    
    `}`
    
    `try` `{`
    
    `execSync(installCommand, {stdio: ``'inherit'``}); ``//会执行yarn add react-native --exact`
    
    `} ``catch` `(err) {`
    
    `console.error(err);`
    
    `console.error(``'Command `'` `+ installCommand + ``'` failed.'``);`
    
    `process.exit(``1``);`
    
    `}`
    
    `checkNodeVersion();`
    
    `cli = require(CLI_MODULE_PATH());`
    
    `cli.init(root, projectName);`
    
    `}`
    
    `//检查node版本`
    
    `function checkNodeVersion() {`
    
    `var packageJson = require(REACT_NATIVE_PACKAGE_JSON_PATH());`
    
    `if` `(!packageJson.engines || !packageJson.engines.node) {`
    
    `return``;`
    
    `}`
    
    `if` `(!semver.satisfies(process.version, packageJson.engines.node)) {`
    
    `console.error(chalk.red(`
    
    `'You are currently running Node %s but React Native requires %s. '` `+`
    
    `'Please use a supported version of Node.\n'` `+`
    
    `'See [https://facebook.github.io/react-native/docs/getting-started.html'](https://facebook.github.io/react-native/docs/getting-started.html')`
    
    `),`
    
    `process.version,`
    
    `packageJson.engines.node);`
    
    `}`
    
    `}`
    
    `//打印版本并退出`
    
    `function printVersionsAndExit(reactNativePackageJsonPath) {`
    
    `console.log(``'react-native-cli: '` `+ require(``'./package.json'``).version);`
    
    `try` `{`
    
    `console.log(``'react-native: '` `+ require(reactNativePackageJsonPath).version);`
    
    `} ``catch` `(e) {`
    
    `console.log(``'react-native: n/a - not inside a React Native project directory'``);`
    
    `}`
    
    `process.exit();`
    
    `}`
    
    

    二、自定义脚手架
    上面提到,React-native发布有两个包。一个脚手架,一个react-native包里包含源码模板等。同理,我们需要自定义脚手架的话同样需要两个包。我这里命名为rn-cli(脚手架)、rn-template(包含项目模板)两个包

    rn-cli(脚手架)
    1:创建目录rn-cli;

    2:进入目录执行npm init;

    3:在package.json dependencies中添加nodejs相关依赖

    {
      "name": "rn-cli",
      "version": "1.0.0",
      "description": "",
      "main": "index.js",
      "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1"
      },
      "dependencies": {
        "chalk": "^1.1.1",
        "minimist": "^1.2.0",
        "prompt": "^0.2.14",
        "semver": "^5.0.3"
      },
      "bin": {
        "rn-cli": "index.js"
      },
      "author": "",
      "license": "ISC"
    }
    

    4:将react-native-cli中index.js拷贝到rn-cli中,并作相应修改

    修改CLI_MODULE_PATH
    修改run方法,xxx


    image.png

    rn-template
    1:创建目录rn-template;

    2:进入目录执行npm init;

    3:在package.json dependencies中添加nodejs相关依赖

    {
      "name": "rn-telmpate",
      "version": "1.0.0",
      "description": "",
      "main": "cli.js",
      "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1"
      },
      "dependencies": {
        "chalk": "^1.1.1",
        "minimist": "^1.2.0",
        "prompt": "^0.2.14",
        "semver": "^5.0.3"
      },
      "author": "",
      "license": "ISC"
    }
    

    4:再一个已创建的RN项目中node_module/react-native中将创建项目的文件拷贝移植过来,并做简单修改。


    image.png

    5:将该包发布到npm仓库

    大致流程:


    image.png

    注:开发调试rn-template过程中,可以先在rn-cli目录下通过命令node index.js执行创建一个项目,然后将rn-template代码放到创建的项目目录node_module目录下,即可进行调试开发。

    相关文章

      网友评论

          本文标题:React Native自定义脚手架

          本文链接:https://www.haomeiwen.com/subject/qnlchrtx.html