国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

create-react-app 源碼學習(上)

MkkHou / 378人閱讀

摘要:這里通過調(diào)用方法方法主要是通過來通過命令執(zhí)行下的方法。

原文地址Nealyang/personalBlog
前言

對于前端工程構(gòu)建,很多公司、BU 都有自己的一套構(gòu)建體系,比如我們正在使用的 def,或者 vue-cli 或者 create-react-app,由于筆者最近一直想搭建一個個人網(wǎng)站,秉持著呼吸不停,折騰不止的原則,編碼的過程中,還是不想太過于枯燥。在 coding 之前,搭建自己的項目架構(gòu)的時候,突然想,為什么之前搭建過很多的項目架構(gòu)不能直接拿來用,卻還是要從 0 到 1 的去寫 webpack 去下載相關(guān)配置呢?遂!學習下 create-react-app 源碼,然后自己搞一套吧~

create-react-app 源碼

代碼的入口在 packages/create-react-app/index.js下,核心代碼在createReactApp.js中,雖然有大概 900+行代碼,但是刪除注釋和一些友好提示啥的大概核心代碼也就六百多行吧,我們直接來看

index.js

index.js 的代碼非常的簡單,其實就是對 node 的版本做了一下校驗,如果版本號低于 8,就退出應(yīng)用程序,否則直接進入到核心文件中,createReactApp.js

createReactApp.js

createReactApp 的功能也非常簡單其實,大概流程:

命令初始化,比如自定義create-react-app --info 的輸出等

判斷是否輸入項目名稱,如果有,則根據(jù)參數(shù)去跑安裝,如果沒有,給提示,然后退出程序

修改 package.json

拷貝 react-script 下的模板文件

準備工作:配置 vscode 的 debug 文件
        {
            "type": "node",
            "request": "launch",
            "name": "CreateReactApp",
            "program": "${workspaceFolder}/packages/create-react-app/index.js",
            "args": [
                "study-create-react-app-source"
            ]
        },
        {
            "type": "node",
            "request": "launch",
            "name": "CreateReactAppNoArgs",
            "program": "${workspaceFolder}/packages/create-react-app/index.js"
        },
        {
            "type": "node",
            "request": "launch",
            "name": "CreateReactAppTs",
            "program": "${workspaceFolder}/packages/create-react-app/index.js",
            "args": [
                "study-create-react-app-source-ts --typescript"
            ]
        }

這里我們添加三種環(huán)境,其實就是 create-react-app 的不同種使用方式

create-react-app study-create-react-app-source

create-react-app

create-react-app study-create-react-app-source-ts --typescript

commander 命令行處理程序

commander 文檔傳送門

let projectName;

const program = new commander.Command(packageJson.name)
  .version(packageJson.version)//create-react-app -v 時候輸出的值 packageJson 來自上面 const packageJson = require("./package.json");
  .arguments("") //定義 project-directory ,必填項
  .usage(`${chalk.green("")} [options]`)
  .action(name => {
    projectName = name;//獲取用戶的輸入,存為 projectName
  })
  .option("--verbose", "print additional logs")
  .option("--info", "print environment debug info")
  .option(
    "--scripts-version ",
    "use a non-standard version of react-scripts"
  )
  .option("--use-npm")
  .option("--use-pnp")
  .option("--typescript")
  .allowUnknownOption()
  .on("--help", () => {// on("option", cb) 語法,輸入 create-react-app --help 自動執(zhí)行后面的操作輸出幫助
    console.log(`    Only ${chalk.green("")} is required.`);
    console.log();
    console.log(
      `    A custom ${chalk.cyan("--scripts-version")} can be one of:`
    );
    console.log(`      - a specific npm version: ${chalk.green("0.8.2")}`);
    console.log(`      - a specific npm tag: ${chalk.green("@next")}`);
    console.log(
      `      - a custom fork published on npm: ${chalk.green(
        "my-react-scripts"
      )}`
    );
    console.log(
      `      - a local path relative to the current working directory: ${chalk.green(
        "file:../my-react-scripts"
      )}`
    );
    console.log(
      `      - a .tgz archive: ${chalk.green(
        "https://mysite.com/my-react-scripts-0.8.2.tgz"
      )}`
    );
    console.log(
      `      - a .tar.gz archive: ${chalk.green(
        "https://mysite.com/my-react-scripts-0.8.2.tar.gz"
      )}`
    );
    console.log(
      `    It is not needed unless you specifically want to use a fork.`
    );
    console.log();
    console.log(
      `    If you have any problems, do not hesitate to file an issue:`
    );
    console.log(
      `      ${chalk.cyan(
        "https://github.com/facebook/create-react-app/issues/new"
      )}`
    );
    console.log();
  })
  .parse(process.argv);

關(guān)于 commander 的使用,這里就不介紹了,對于 create-react-app 的流程我們需要知道的是,它,初始化了一些 create-react-app 的命令行環(huán)境,這一波操作后,我們可以看到 program 張這個樣紙:

接著往下走

當我們 debug 啟動 noArgs 環(huán)境的時候,走到這里就結(jié)束了,判斷 projectName 是否為 undefined,然后輸出相關(guān)提示信息,退出~

createApp

在查看 createApp function 之前,我們再回頭看下命令行的一些參數(shù)定義,方便我們理解 createApp 的一些參數(shù)

我們使用

        {
            "type": "node",
            "request": "launch",
            "name": "CreateReactAppTs",
            "program": "${workspaceFolder}/packages/create-react-app/index.js",
            "args": [
                "study-create-react-app-source-ts",
                "--typescript",
                "--use-npm"
            ]
        }

debugger 我們項目的時候,就可以看到,program.typescripttrueuseNpmtrue,當然,這些也都是我們在commander中定義的 options,所以源碼里面 createApp 中,我們傳入的參數(shù)分別為:

projectName : 項目名稱

program.verbose 是否輸出額外信息

program.scriptsVersion 傳入的腳本版本

program.useNpm 是否使用 npm

program.usePnp 是否使用 Pnp

program.typescript 是否使用 ts

hiddenProgram.internalTestingTemplate 給開發(fā)者用的調(diào)試模板路徑

function createApp(
  name,
  verbose,
  version,
  useNpm,
  usePnp,
  useTypescript,
  template
) {
  const root = path.resolve(name);//path 拼接路徑
  const appName = path.basename(root);//獲取文件名

  checkAppName(appName);//檢查傳入的文件名合法性
  fs.ensureDirSync(name);//確保目錄存在,如果不存在則創(chuàng)建一個
  if (!isSafeToCreateProjectIn(root, name)) { //判斷新建這個文件夾是否安全,否則直接退出
    process.exit(1);
  }

  console.log(`Creating a new React app in ${chalk.green(root)}.`);
  console.log();

  const packageJson = {
    name: appName,
    version: "0.1.0",
    private: true,
  };
  fs.writeFileSync(
    path.join(root, "package.json"),
    JSON.stringify(packageJson, null, 2) + os.EOL
  );//寫入 package.json 文件

  const useYarn = useNpm ? false : shouldUseYarn();//判斷是使用 yarn 呢還是 npm
  const originalDirectory = process.cwd();
  process.chdir(root);
  if (!useYarn && !checkThatNpmCanReadCwd()) {//如果是使用npm,檢測npm是否在正確目錄下執(zhí)行
    process.exit(1);
  }

  if (!semver.satisfies(process.version, ">=8.10.0")) {//判斷node環(huán)境,輸出一些提示信息, 并采用舊版本的 react-scripts
    console.log(
      chalk.yellow(
        `You are using Node ${
          process.version
        } so the project will be bootstrapped with an old unsupported version of tools.

` +
          `Please update to Node 8.10 or higher for a better, fully supported experience.
`
      )
    );
    // Fall back to latest supported react-scripts on Node 4
    version = "react-scripts@0.9.x";
  }

  if (!useYarn) {//關(guān)于 npm、pnp、yarn 的使用判斷,版本校驗等
    const npmInfo = checkNpmVersion();
    if (!npmInfo.hasMinNpm) {
      if (npmInfo.npmVersion) {
        console.log(
          chalk.yellow(
            `You are using npm ${
              npmInfo.npmVersion
            } so the project will be bootstrapped with an old unsupported version of tools.

` +
              `Please update to npm 5 or higher for a better, fully supported experience.
`
          )
        );
      }
      // Fall back to latest supported react-scripts for npm 3
      version = "react-scripts@0.9.x";
    }
  } else if (usePnp) {
    const yarnInfo = checkYarnVersion();
    if (!yarnInfo.hasMinYarnPnp) {
      if (yarnInfo.yarnVersion) {
        console.log(
          chalk.yellow(
            `You are using Yarn ${
              yarnInfo.yarnVersion
            } together with the --use-pnp flag, but Plug"n"Play is only supported starting from the 1.12 release.

` +
              `Please update to Yarn 1.12 or higher for a better, fully supported experience.
`
          )
        );
      }
      // 1.11 had an issue with webpack-dev-middleware, so better not use PnP with it (never reached stable, but still)
      usePnp = false;
    }
  }

  if (useYarn) {
    let yarnUsesDefaultRegistry = true;
    try {
      yarnUsesDefaultRegistry =
        execSync("yarnpkg config get registry")
          .toString()
          .trim() === "https://registry.yarnpkg.com";
    } catch (e) {
      // ignore
    }
    if (yarnUsesDefaultRegistry) {
      fs.copySync(
        require.resolve("./yarn.lock.cached"),
        path.join(root, "yarn.lock")
      );
    }
  }

  run(
    root,
    appName,
    version,
    verbose,
    originalDirectory,
    template,
    useYarn,
    usePnp,
    useTypescript
  );
} 

代碼非常簡單,部分注釋已經(jīng)加載代碼中,簡單的說就是對一個本地環(huán)境的一些校驗,版本檢查啊、目錄創(chuàng)建啊啥的,如果創(chuàng)建失敗,則退出,如果版本較低,則使用對應(yīng)低版本的create-react-app,最后調(diào)用 run 方法

checkAppName

這些工具方法,其實在寫我們自己的構(gòu)建工具的時候,也可以直接 copy 的哈,所以這里我們也是簡單看下里面的實現(xiàn),

checkAPPName 方法主要的核心代碼是validate-npm-package-name package,從名字即可看出,檢查是否為合法的 npm 包名

var done = function (warnings, errors) {
  var result = {
    validForNewPackages: errors.length === 0 && warnings.length === 0,
    validForOldPackages: errors.length === 0,
    warnings: warnings,
    errors: errors
  }
  if (!result.warnings.length) delete result.warnings
  if (!result.errors.length) delete result.errors
  return result
}
...
...
var validate = module.exports = function (name) {
  var warnings = []
  var errors = []

  if (name === null) {
    errors.push("name 不能使 null")
    return done(warnings, errors)
  }

  if (name === undefined) {
    errors.push("name 不能是 undefined")
    return done(warnings, errors)
  }

  if (typeof name !== "string") {
    errors.push("name 必須是 string 類型")
    return done(warnings, errors)
  }

  if (!name.length) {
    errors.push("name 的長度必須大于 0")
  }

  if (name.match(/^./)) {
    errors.push("name 不能以點開頭")
  }

  if (name.match(/^_/)) {
    errors.push("name 不能以下劃線開頭")
  }

  if (name.trim() !== name) {
    errors.push("name 不能包含前空格和尾空格")
  }

  // No funny business
  // var blacklist = [
  //   "node_modules",
  //   "favicon.ico"
  // ]
  blacklist.forEach(function (blacklistedName) {
    if (name.toLowerCase() === blacklistedName) { //不能是“黑名單”內(nèi)的
      errors.push(blacklistedName + " is a blacklisted name")
    }
  })

  // Generate warnings for stuff that used to be allowed
  // 為以前允許的內(nèi)容生成警告

 // 后面的就不再贅述了

  return done(warnings, errors)
}

最終,checkAPPName返回的東西如截圖所示,后面寫代碼可以直接拿來借鑒!借鑒~

isSafeToCreateProjectIn

所謂安全性校驗,其實就是檢查當前目錄下是否存在已有文件。

checkNpmVersion

后面的代碼也都比較簡單,這里就不展開說了,版本比較實用的是一個semver package.

run

代碼跑到這里,該檢查的都檢查了,雞也不叫了、狗也不咬了,該干點正事了~

run 主要做的事情就是安裝依賴、拷貝模板。

getInstallPackage做的事情非常簡單,根據(jù)傳入的 version 和原始路徑 originalDirectory 去獲取要安裝的 package 列表,默認情況下version 為 undefined,獲取到的 packageToInstall 為react-scripts,也就是我們?nèi)缟蠄D的 resolve 回調(diào)。

最終,我們拿到需要安裝的 info 為

{
  isOnline:true,
  packageName:"react-scripts"
}

當我們梳理好需要安裝的 package 后,就交給 npm 或者 yarn 去安裝我們的依賴即可

spawn執(zhí)行完命令后會有一個回調(diào),判斷code是否為 0,然后 resolve Promise,

 .then(async packageName => {
         // 安裝完 react, react-dom, react-scripts 之后檢查當前環(huán)境運行的node版本是否符合要求
        checkNodeVersion(packageName);
        // 檢查 package.json 中的版本號
        setCaretRangeForRuntimeDeps(packageName);

        const pnpPath = path.resolve(process.cwd(), ".pnp.js");

        const nodeArgs = fs.existsSync(pnpPath) ? ["--require", pnpPath] : [];

        await executeNodeScript(
          {
            cwd: process.cwd(),
            args: nodeArgs,
          },
          [root, appName, verbose, originalDirectory, template],
          `
        var init = require("${packageName}/scripts/init.js");
        init.apply(null, JSON.parse(process.argv[1]));
      `
        );

create-react-app之前的版本中,這里是通過調(diào)用react-script下的 init方法來執(zhí)行后續(xù)動作的。這里通過調(diào)用executeNodeScript 方法

function executeNodeScript({ cwd, args }, data, source) {
  // cwd:"/Users/nealyang/Desktop/create-react-app/study-create-react-app-source"

  // data:
  // 0:"/Users/nealyang/Desktop/create-react-app/study-create-react-app-source"
  // 1:"study-create-react-app-source"
  // 2:undefined
  // 3:"/Users/nealyang/Desktop/create-react-app"
  // 4:undefined

  // source
  // "  var init = require("react-scripts/scripts/init.js");
  //   init.apply(null, JSON.parse(process.argv[1]));
  // "
  
  return new Promise((resolve, reject) => {
    const child = spawn(
      process.execPath,
      [...args, "-e", source, "--", JSON.stringify(data)],
      { cwd, stdio: "inherit" }
    );

    child.on("close", code => {
      if (code !== 0) {
        reject({
          command: `node ${args.join(" ")}`,
        });
        return;
      }
      resolve();
    });
  });
}

executeNodeScript 方法主要是通過 spawn 來通過 node命令執(zhí)行react-script下的 init 方法。所以截止當前,create-react-app完成了他的工作: npm i ,

react-script/init.js

修改 vscode 的 debugger 配置,然后我們來 debugger react-script 下的 init 方法

function init(appPath, appName, verbose, originalDirectory, template) {
  // 獲取當前包中包含 package.json 所在的文件夾路徑
  const ownPath = path.dirname(
    //"/Users/nealyang/Desktop/create-react-app/packages/react-scripts"
    require.resolve(path.join(__dirname, "..", "package.json"))
  );
  const appPackage = require(path.join(appPath, "package.json")); //項目目錄下的 package.json
  const useYarn = fs.existsSync(path.join(appPath, "yarn.lock")); //通過判斷目錄下是否有 yarn.lock 來判斷是否使用 yarn

  // Copy over some of the devDependencies
  appPackage.dependencies = appPackage.dependencies || {};

  //   react:"16.8.6"
  // react-dom:"16.8.6"
  // react-scripts:"3.0.1"
  const useTypeScript = appPackage.dependencies["typescript"] != null;

  // Setup the script rules 設(shè)置 script 命令
  appPackage.scripts = {
    start: "react-scripts start",
    build: "react-scripts build",
    test: "react-scripts test",
    eject: "react-scripts eject",
  };

  // Setup the eslint config 這是 eslint 的配置
  appPackage.eslintConfig = {
    extends: "react-app",
  };

  // Setup the browsers list 組件autoprefixer、bable-preset-env、eslint-plugin-compat、postcss-normalize共享使用的配置項 (感謝網(wǎng)友指正)
  appPackage.browserslist = defaultBrowsers;

  // 寫入我們需要創(chuàng)建的目錄下的 package.json 中
  fs.writeFileSync(
    path.join(appPath, "package.json"),
    JSON.stringify(appPackage, null, 2) + os.EOL
  );

  const readmeExists = fs.existsSync(path.join(appPath, "README.md"));
  if (readmeExists) {
    fs.renameSync(
      path.join(appPath, "README.md"),
      path.join(appPath, "README.old.md")
    );
  }

  // Copy the files for the user  獲取模板的路徑
  const templatePath = template //"/Users/nealyang/Desktop/create-react-app/packages/react-scripts/template"
    ? path.resolve(originalDirectory, template)
    : path.join(ownPath, useTypeScript ? "template-typescript" : "template");
  if (fs.existsSync(templatePath)) {
    // 這一步就過分了, 直接 copy!  appPath:"/Users/nealyang/Desktop/create-react-app/study-create-react-app-source"
    fs.copySync(templatePath, appPath);
  } else {
    console.error(
      `Could not locate supplied template: ${chalk.green(templatePath)}`
    );
    return;
  }

  // Rename gitignore after the fact to prevent npm from renaming it to .npmignore 重命名gitignore以防止npm將其重命名為.npmignore
  // See: https://github.com/npm/npm/issues/1862
  try {
    fs.moveSync(
      path.join(appPath, "gitignore"),
      path.join(appPath, ".gitignore"),
      []
    );
  } catch (err) {
    // Append if there"s already a `.gitignore` file there
    if (err.code === "EEXIST") {
      const data = fs.readFileSync(path.join(appPath, "gitignore"));
      fs.appendFileSync(path.join(appPath, ".gitignore"), data);
      fs.unlinkSync(path.join(appPath, "gitignore"));
    } else {
      throw err;
    }
  }

  let command;
  let args;

  if (useYarn) {
    command = "yarnpkg";
    args = ["add"];
  } else {
    command = "npm";
    args = ["install", "--save", verbose && "--verbose"].filter(e => e);
  }
  args.push("react", "react-dom");
  // args Array
  // 0:"install"
  // 1:"--save"
  // 2:"react"
  // 3:"react-dom"

  // 安裝其他模板依賴項(如果存在)
  const templateDependenciesPath = path.join(//"/Users/nealyang/Desktop/create-react-app/study-create-react-app-source/.template.dependencies.json"
    appPath,
    ".template.dependencies.json"
  );
  if (fs.existsSync(templateDependenciesPath)) {
    const templateDependencies = require(templateDependenciesPath).dependencies;
    args = args.concat(
      Object.keys(templateDependencies).map(key => {
        return `${key}@${templateDependencies[key]}`;
      })
    );
    fs.unlinkSync(templateDependenciesPath);
  }

  // 安裝react和react-dom以便與舊CRA cli向后兼容
  // 沒有安裝react和react-dom以及react-scripts
  // 或模板是presetend(通過--internal-testing-template)
  if (!isReactInstalled(appPackage) || template) {
    console.log(`Installing react and react-dom using ${command}...`);
    console.log();

    const proc = spawn.sync(command, args, { stdio: "inherit" });
    if (proc.status !== 0) {
      console.error(``${command} ${args.join(" ")}` failed`);
      return;
    }
  }

  if (useTypeScript) {
    verifyTypeScriptSetup();
  }

  if (tryGitInit(appPath)) {
    console.log();
    console.log("Initialized a git repository.");
  }

  // 顯示最優(yōu)雅的cd方式。
  // 這需要處理未定義的originalDirectory
  // 向后兼容舊的global-cli。
  let cdpath;
  if (originalDirectory && path.join(originalDirectory, appName) === appPath) {
    cdpath = appName;
  } else {
    cdpath = appPath;
  }

  // Change displayed command to yarn instead of yarnpkg
  const displayedCommand = useYarn ? "yarn" : "npm";

  console.log("xxxx....xxxxx");
}

初始化方法主要做的事情就是修改目標路徑下的 package.json,添加一些配置命令,然后 copy!react-script 下的模板到目標路徑下。

走到這一步,我們的項目基本已經(jīng)初始化完成了。

所以我們 copy 了這么多 scripts

    start: "react-scripts start",
    build: "react-scripts build",
    test: "react-scripts test",
    eject: "react-scripts eject",

究竟是如何工作的呢,其實也不難,就是一些開發(fā)、測試、生產(chǎn)的環(huán)境配置。鑒于篇幅,咱就下一篇來分享下大佬們的前端構(gòu)建的代碼寫法吧~~

總結(jié)

本來想用一張流程圖解釋下,但是。。。create-react-app 著實沒有做啥!咱還是等下一篇分析完,自己寫構(gòu)建腳本的時候再畫一下整體流程圖(架構(gòu)圖)吧~

ok~ 簡單概述下:

判斷 node 版本,如果大版本小于 8 ,則直接退出(截止目前是 8)

createReactApp.js 初始化一些命令參數(shù),然后再去判斷是否傳入了 packageName,否則直接退出

各種版本的判斷,然后通過cross-spawn來用命令行執(zhí)行所有的安裝

當所有的依賴安裝完后,依舊通過命令行,初始化 node 環(huán)境,來執(zhí)行 react-script 下的初始化方法:修改 package.json 中的一些配置、以及 copy 模板文件

處理完成,給出用戶友好提示

通篇看完 package 的職能后,發(fā)現(xiàn),哇,這有點簡答啊~~其實,我們學習源碼的其實就是為了學習大佬們的一些邊界情況處理,在后面自己開發(fā)的時候再去 copy~ 借鑒一些判斷方法的編寫。后面會再簡單分析下react-scripts,然后寫一個自己的一些項目架構(gòu)腳本~

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/104825.html

相關(guān)文章

  • 深度解析`create-react-app`源碼

    摘要:這個選項看意思就知道了,默認使用來安裝,運行,如果你沒有使用,你可能就需要這個配置了,指定使用。 2018-06-13 更新。昨天突然好奇在Google上搜了一波關(guān)于create-react-app 源碼的關(guān)鍵詞,發(fā)現(xiàn)掘金出現(xiàn)好幾篇仿文,就連我開頭前沿瞎幾把啰嗦的話都抄,我還能說什么是吧?以后博客還是首發(fā)在Github上,地址戳這里戳這里!!轉(zhuǎn)載求你們注明出處、改編求你們貼一下參考鏈...

    waruqi 評論0 收藏0
  • 學習 create-react-app

    摘要:所以再看文件這里不貼圖占地方有興趣自己看。常見的用法就是和中的搭配發(fā)布自己的包通讀下來就是一個幫助搭建項目文件夾,寫一些語句的作用。 版本說明:2.0.4 首先看文檔中怎樣使用:showImg(https://segmentfault.com/img/bVbiI27?w=1422&h=616); 這里的兩條命令是等價的,以npm init ...為例來看命令是怎么用的 showImg(...

    qujian 評論0 收藏0
  • react開發(fā)教程(二)安裝

    摘要:使用快速構(gòu)建開發(fā)環(huán)境第一步安裝全局包是來自于,通過該命令我們無需配置就能快速構(gòu)建開發(fā)環(huán)境。執(zhí)行以下命令創(chuàng)建項目項目目錄在瀏覽器中打開,即可顯示上一篇開發(fā)教程初識下一篇開發(fā)教程三組件的構(gòu)建 react安裝 React可以直接下載使用,下載包中也提供了很多學習的實例。本教程使用了 React 的版本為 15.4.2,你可以在官網(wǎng) http://facebook.github.io/reac...

    Jonathan Shieber 評論0 收藏0
  • TypeScript 、React、 Redux和Ant-Design的最佳實踐

    摘要:使用官方的的另外一種版本和一起使用自動配置了一個項目支持。需要的依賴都在文件中。帶靜態(tài)類型檢驗,現(xiàn)在的第三方包基本上源碼都是,方便查看調(diào)試。大型項目首選和結(jié)合,代碼調(diào)試維護起來極其方便。 showImg(https://segmentfault.com/img/bVbrTKz?w=1400&h=930); 阿特伍德定律,指的是any application that can be wr...

    wangbinke 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<