构建 react应用程序 (二)(react-scripts实现原理)

来源:互联网 发布:ios10优化设置 编辑:程序博客网 时间:2024/06/06 04:22

在前面讲到了使用create-react-app来创建项目,这节我们来分析下原理。
react-scripts有以下支持,都帮你配置好了:

React, JSX, ES6, and Flow syntax support.Language extras beyond ES6 like the object spread operator.Import CSS and image files directly from JavaScript.Autoprefixed CSS, so you don’t need -webkit or other prefixes.A build script to bundle JS, CSS, and images for production, with sourcemaps.A dev server that lints for common errors.

翻译就不翻译了,大概就是说es6、css依赖啊 图片依赖之类的都已经通过react-scripts配置好了。
其实babel-core,webpack,等等这些 你都没下载,配置。
这些活,react-scripts 都帮你做了。

在命令窗口输入的是npm start,而start调用的是

  "start": "react-scripts start",

找到node_modules里面的react-scripts 插件。点开bin\react-scripts.js文件内容

'use strict';const spawn = require('react-dev-utils/crossSpawn');const script = process.argv[2];const args = process.argv.slice(3);switch (script) {  case 'build':  case 'eject':  case 'start':  case 'test': {    const result = spawn.sync(      'node',      [require.resolve('../scripts/' + script)].concat(args),      { stdio: 'inherit' }    );    if (result.signal) {      if (result.signal === 'SIGKILL') {        console.log(          'The build failed because the process exited too early. ' +            'This probably means the system ran out of memory or someone called ' +            '`kill -9` on the process.'        );      }
 [require.resolve('../scripts/' + script)].concat(args),

从上面这话可以看出调用start时需要调用scripts/start.js,点开该文件:

// @remove-on-eject-end'use strict';// Do this as the first thing so that any code reading it knows the right env.process.env.BABEL_ENV = 'development';process.env.NODE_ENV = 'development';// Makes the script crash on unhandled rejections instead of silently// ignoring them. In the future, promise rejections that are not handled will// terminate the Node.js process with a non-zero exit code.process.on('unhandledRejection', err => {  throw err;});// Ensure environment variables are read.require('../config/env');const fs = require('fs');const chalk = require('chalk');const webpack = require('webpack');//这里引入了webpackconst WebpackDevServer = require('webpack-dev-server');//这里引入了webpack-dev-serverconst clearConsole = require('react-dev-utils/clearConsole');const checkRequiredFiles = require('react-dev-utils/checkRequiredFiles');const {  choosePort,  createCompiler,  prepareProxy,  prepareUrls,} = require('react-dev-utils/WebpackDevServerUtils');const openBrowser = require('react-dev-utils/openBrowser');const paths = require('../config/paths');//文件处理的配置const config = require('../config/webpack.config.dev');//dev解析的相关配置const createDevServerConfig = require('../config/webpackDevServer.config');const useYarn = fs.existsSync(paths.yarnLockFile);const isInteractive = process.stdout.isTTY;// Warn and crash if required files are missingif (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) {  process.exit(1);}// Tools like Cloud9 rely on this.const DEFAULT_PORT = parseInt(process.env.PORT, 10) || 3000;//端口设置 这就是为什么 端口号 不是8080 而是 3000 的原因,在这里可以改成8080,重新npm run start 生效   const HOST = process.env.HOST || '0.0.0.0';// We attempt to use the default port but if it is busy, we offer the user to// run on a different port. `detect()` Promise resolves to the next free port.choosePort(HOST, DEFAULT_PORT)  .then(port => {    if (port == null) {      // We have not found a port.      return;    }    const protocol = process.env.HTTPS === 'true' ? 'https' : 'http';    const appName = require(paths.appPackageJson).name;    const urls = prepareUrls(protocol, HOST, port);    // Create a webpack compiler that is configured with custom messages.    const compiler = createCompiler(webpack, config, appName, urls, useYarn);    // Load proxy config    const proxySetting = require(paths.appPackageJson).proxy;    const proxyConfig = prepareProxy(proxySetting, paths.appPublic);    // Serve webpack assets generated by the compiler over a web sever.    const serverConfig = createDevServerConfig(      proxyConfig,      urls.lanUrlForConfig    );    const devServer = new WebpackDevServer(compiler, serverConfig);    // Launch WebpackDevServer.    devServer.listen(port, HOST, err => {      if (err) {        return console.log(err);      }      if (isInteractive) {        clearConsole();      }      console.log(chalk.cyan('Starting the development server...\n'));      openBrowser(urls.localUrlForBrowser);    });    ['SIGINT', 'SIGTERM'].forEach(function(sig) {      process.on(sig, function() {        devServer.close();        process.exit();      });    });  })  .catch(err => {    if (err && err.message) {      console.log(err.message);    }    process.exit(1);  });

点开path路径,其实里面好多默认的配置都是写在该文件,可以通过修改改文件来实现自己文件的存放配置

// @remove-on-eject-begin */// @remove-on-eject-end'use strict';const path = require('path');const fs = require('fs');const url = require('url');// Make sure any symlinks in the project folder are resolved:// https://github.com/facebookincubator/create-react-app/issues/637const appDirectory = fs.realpathSync(process.cwd());const resolveApp = relativePath => path.resolve(appDirectory, relativePath);const envPublicUrl = process.env.PUBLIC_URL;function ensureSlash(path, needsSlash) {  const hasSlash = path.endsWith('/');  if (hasSlash && !needsSlash) {    return path.substr(path, path.length - 1);  } else if (!hasSlash && needsSlash) {    return `${path}/`;  } else {    return path;  }}const getPublicUrl = appPackageJson =>  envPublicUrl || require(appPackageJson).homepage;function getServedPath(appPackageJson) {  const publicUrl = getPublicUrl(appPackageJson);  const servedUrl =    envPublicUrl || (publicUrl ? url.parse(publicUrl).pathname : '/');  return ensureSlash(servedUrl, true);}// config after eject: we're in ./config/module.exports = {  dotenv: resolveApp('.env'),  appBuild: resolveApp('build'),  appPublic: resolveApp('public'),  appHtml: resolveApp('public/index.html'),  appIndexJs: resolveApp('src/index.js'),  appPackageJson: resolveApp('package.json'),  appSrc: resolveApp('src'),  yarnLockFile: resolveApp('yarn.lock'),  testsSetup: resolveApp('src/setupTests.js'),  appNodeModules: resolveApp('node_modules'),  publicUrl: getPublicUrl(resolveApp('package.json')),  servedPath: getServedPath(resolveApp('package.json')),};// @remove-on-eject-beginconst resolveOwn = relativePath => path.resolve(__dirname, '..', relativePath);// config before eject: we're in ./node_modules/react-scripts/config/module.exports = {  dotenv: resolveApp('.env'),  appPath: resolveApp('.'),  appBuild: resolveApp('build'),  appPublic: resolveApp('public'),  appHtml: resolveApp('public/index.html'),  appIndexJs: resolveApp('src/index.js'),  appPackageJson: resolveApp('package.json'),  appSrc: resolveApp('src'),  yarnLockFile: resolveApp('yarn.lock'),  testsSetup: resolveApp('src/setupTests.js'),  appNodeModules: resolveApp('node_modules'),  publicUrl: getPublicUrl(resolveApp('package.json')),  servedPath: getServedPath(resolveApp('package.json')),  // These properties only exist before ejecting:  ownPath: resolveOwn('.'),  ownNodeModules: resolveOwn('node_modules'), // This is empty on npm 3};const ownPackageJson = require('../package.json');const reactScriptsPath = resolveApp(`node_modules/${ownPackageJson.name}`);const reactScriptsLinked =  fs.existsSync(reactScriptsPath) &&  fs.lstatSync(reactScriptsPath).isSymbolicLink();// config before publish: we're in ./packages/react-scripts/config/if (  !reactScriptsLinked &&  __dirname.indexOf(path.join('packages', 'react-scripts', 'config')) !== -1) {  module.exports = {    dotenv: resolveOwn('template/.env'),    appPath: resolveApp('.'),    appBuild: resolveOwn('../../build'),    appPublic: resolveOwn('template/public'),    appHtml: resolveOwn('template/public/index.html'),    appIndexJs: resolveOwn('template/src/index.js'),    appPackageJson: resolveOwn('package.json'),    appSrc: resolveOwn('template/src'),    yarnLockFile: resolveOwn('template/yarn.lock'),    testsSetup: resolveOwn('template/src/setupTests.js'),    appNodeModules: resolveOwn('node_modules'),    publicUrl: getPublicUrl(resolveOwn('package.json')),    servedPath: getServedPath(resolveOwn('package.json')),    // These properties only exist before ejecting:    ownPath: resolveOwn('.'),    ownNodeModules: resolveOwn('node_modules'),  };}// @remove-on-eject-end

点开webpack.config.dev.js,也会发现大量是曾相识的代码:

 {        test: /\.(js|jsx)$/,        enforce: 'pre',        use: [          {            options: {              formatter: eslintFormatter,              // @remove-on-eject-begin              baseConfig: {                extends: [require.resolve('eslint-config-react-app')],              },              ignore: false,              useEslintrc: false,              // @remove-on-eject-end            },            loader: require.resolve('eslint-loader'),          },        ],        include: paths.appSrc,      },      // ** ADDING/UPDATING LOADERS **      // The "file" loader handles all assets unless explicitly excluded.      // The `exclude` list *must* be updated with every change to loader extensions.      // When adding a new loader, you must add its `test`      // as a new entry in the `exclude` list for "file" loader.      // "file" loader makes sure those assets get served by WebpackDevServer.      // When you `import` an asset, you get its (virtual) filename.      // In production, they would get copied to the `build` folder.      {        exclude: [          /\.html$/,          /\.(js|jsx)$/,          /\.css$/,          /\.json$/,          /\.bmp$/,          /\.gif$/,          /\.jpe?g$/,          /\.png$/,        ],        loader: require.resolve('file-loader'),        options: {          name: 'static/media/[name].[hash:8].[ext]',        },      },      // "url" loader works like "file" loader except that it embeds assets      // smaller than specified limit in bytes as data URLs to avoid requests.      // A missing `test` is equivalent to a match.      {        test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],        loader: require.resolve('url-loader'),        options: {          limit: 10000,          name: 'static/media/[name].[hash:8].[ext]',        },      },      // Process JS with Babel.      {        test: /\.(js|jsx)$/,        include: paths.appSrc,        loader: require.resolve('babel-loader'),        options: {          // @remove-on-eject-begin          babelrc: false,          presets: [require.resolve('babel-preset-react-app')],          // @remove-on-eject-end          // This is a feature of `babel-loader` for webpack (not Babel itself).          // It enables caching results in ./node_modules/.cache/babel-loader/          // directory for faster rebuilds.          cacheDirectory: true,        },      },

具体这些内容细节就不讲解了,说白点的我们以前通过手配置的webpack.config.js的内容react-scripts都已经帮我们做了,当然是否还缺其他组件,目前不知道在后续使用过程中,如果发现的不同我们再来一一记录。