如何从零开始搭建一个react脚手架

前言

搭建一个 React 工程的方式有很多,官方也有自己的脚手架,如果你和我一样,喜欢刨根究底,从零开始自己一行一行代码创建一个 React 脚手架项目,那你就来对地方了。本教程是针对 React 新手,以及对 webpack 还不熟悉的用户,或者是想了解当前前端工程化方案的用户。我会在整个系列通过 webpack4 的配置,从生产和开发环境分别入手,包含代码压缩,大文件 gz 压缩,webpack4 的 code split,postcss 等插件如何引入,css 编译,如何让环境支持各种 css 预处理器,cssModule 配置化,bundle 结果分析,本地代理配置,React 相关技术栈等等都有一个讲解。

前提条件

在开始之前,请确保安装了 Node.js 的最新版本。建议使用 Node.js 最新的长期支持版本(LTS - Long Term Support)。如果你使用旧版本,你可能遇到各种问题,因为它们可能缺少 本教程用到的相关的 package 包。

执行以下命令可以查看你本机安装的 node 版本:

node - v && npm - v;

如果你本机的 node 版本不是最新的,建议升级到新的 node 版本。node 版本建议通过 nvm 进行管理,了解可以查看 nvm 这里不再阐述。

1.从空文件夹开始

现在创建一个空的文件夹,用你的开发工具打开它。 打开终端,确保你已经进入刚刚创建的目录,接下去执行:

npm init

终端会让你输入一些信息,比如名字,版本,描述,入口文件等等。 完成后,npm 会自动在你的根目录生成 package.json 文件。package.json 文件不仅仅包含项目的配置信息(比如名称、版本、许可证等元数据),还会记录项目所需要的各种模块,以及项目运行的脚本等等。

2.安装 webpack4

接下来就是现在前端项目中必不可少的 webpack,这里我们用最新版本的 webpack,也就是 webpack4,执行以下命令,安装 webpack

npm install --save-dev webpack

npm 安装的包会存放在一个叫 node_modules 文件夹。如果你用的 npm 版本是 npm 5 版本,你还会发现多了 package-lock.json 文件。package-lock.json 文件是当 node_modules 或 package.json 发生变化时自动生成的文件,它的主要功能是确定当前安装的包的依赖,以便后续重新安装的时候生成相同的依赖,而忽略项目开发过程中有些依赖已经发生的更新(翻译自官方说明)。

打开 package.json,你会发现多了一个名为 devDependencies 的属性,通过–save-dev 安装的包会记录在此属性下。后续我们还会使用–save 去安装一个模块包,区别是通过–save 安装的模块会记录在 dependencies 属性内,而不是 devDependencies 属性。通过字面意思,你应该也可以知道,一个是 dev 开发时候的依赖,一个是运行时的依赖。

新建 webpack 配置文件。webpack 的配置文件,是导出一个对象的 JavaScript 文件。在根目录创建 build 文件夹,添加一个 js 文件,命名为 webpack.base.conf.js。 手动敲入以下代码:

const path = require("path");
const DIST_PATH = path.resolve(__dirname, "../dist");

module.exports = {
  entry: {
    app: "./app/index.js"
  },
  output: {
    filename: "js/bundle.js",
    path: DIST_PATH
  }
};

webpack 配置是标准的 Node.js CommonJS 模块,它通过 require 来引入其他模块,通过 module.exports 导出模块,由 webpack 根据对象定义的属性进行解析。

这里定义了 2 个属性。当然,webpack 的配置属性远远不止这 2 个,之后我会深入讲解更多属性的配置。回到我们定义的 2 个属性,entry 定义了入口文件,output 定义了编译后的输出。按照对这段代码的理解,它应该是告诉 webpack,我这个项目入口文件是 app 目录下的 index.js 文件,你编译后,在根目录下创建 dist 文件夹(如果不存在),最后把编译后的文件输出到 dist 文件下,命名为 bundle.js。

webpack4.0 还增加了 mode 属性,用来表示不同的环境。 我们使用 merge 的方式来组织 webpack 基础配置和不同环境的配置。 先安装 webpack-merge:


npm install --save-dev webpack-merge

在build文件夹中再添加一个js文件,命名为webpack.prod.conf.js,输入以下代码。


const merge = require('webpack-merge');
const baseWebpackConfig = require('./webpack.base.conf');
module.exports = merge(baseWebpackConfig, {
    mode: 'production'
});

在根目录下创建app目录,然后创建index.js文件。


var element =document.getElementById('root');
element.innerHTML = 'hello, world!';

在根目录创建一个public文件夹,然后新建一个index.html文件。

<!DOCTYPE html>
<html lang="en">
  <head>
      <meta charset="UTF-8">
      <title>从零开始搭建react工程</title>
  </head>
  <body>
  <div id="root"></div>
  <script src="../dist/js/bundle.js"></script>
  </body>
</html>

你现在的文件路径应该看起来是这样:(显然我们引用的bundle.js并不存在)


  |- /app
    |- index.js
  |- /node_modules
  |- /public
    |- index.html
  |- /build
    |- webpack.base.conf.js
    |- webpack.prod.conf.js
  |- package.json
  |- package-lock.json

接下去我们要通过执行webpack命令,来编译我们的代码,生成bundle.js。4.0版本之后的webpack,已经将webpack命令工具迁移到webpack-cli模块了。你需要安装webpack-cli。

npm install --save-dev webpack-cli

安装完成后,执行下面脚本进行编译:

webpack --config build/webpack.prod.conf.js

编译完成后,刷新根目录,可以看到已经生成dist文件夹和bundle.js文件。 用浏览器打开html文件,你会看到hello webpack。我们成功通过webpack编译了js文件,并且没有出现问题。 webpack –config build/webpack.prod.conf.js命令,我们可以通过npm scripts管理起来。 在package.json文件,我们为scripts属性配置一个build命令,其值为:webpack –config build/webpack.prod.conf.js,以下是scripts的相关代码:

"scripts": {
    "build": "webpack --config build/webpack.prod.conf.js",
    "test": "echo \"Error: no test specified\" && exit 1"
},

然后在命令行输入:

npm run build

我们可以看到,webpack重新进行了编译,这和执行 webpack –config build/webpack.prod.conf.js 是一样的效果。

3.安装react


npm install --save react react-dom

安装成功后,我们在项目使用react,我们直接修改app目录下的index.js的代码,我们用react来插入这句hello world!

import React from "react";
import ReactDom from "react-dom";

ReactDom.render(
    <h1>hello, world!</h1>,
    document.getElementById("root")
);

好了,我们再编译试试看。


npm run build

失败了?对不对!首先,我告诉你这段代码没有任何问题,你需要思考下,它为什么会编译失败。下一小节,我们一起来了解下原因。

4.整合babel

为什么会失败?因为webpack只识别JavaScript文件,而且只能编译es5版本的JavaScript语法。实际上,我们使用ES2015,以及jsx的语法糖,webpack它根本不认识啊。怎么办?webpack 可以使用 loader 来预处理文件。它不仅仅可以处理JavaScript本身,还允许你打包任何的静态资源。 其中,babel-loader,就是这样一个预处理插件,它加载 ES2015+ 代码,然后使用 Babel 转译为 ES5。我们来了解下如何在webpack配置babel-loader。 首先安装babel相关的模块:


npm install --save-dev babel-loader babel-core babel-preset-env babel-preset-react

除了babel-loader,我们还安装了好多的包,其中babel-core是babel的核心模块,babel-preset-env是转译ES2015+的语法,babel-preset-react是转译react的JSX以及FLOW。了解详情可以移步 babel官方。 第二步,你需要在根目录建立一个.babelrc的文件,配置相关的presets:

{
  "presets": [
    [
      "env",
      {
        "targets": {
          "browsers": [
            "> 1%",
            "last 5 versions",
            "ie >= 8"
          ]
        }
      }
    ],
    "react"
  ]
}

更多关于babel的配置可以看下官方的说明,这里不再阐述。

第三步,修改webpack.base.conf.js文件。

const path = require('path');
const APP_PATH = path.resolve(__dirname, '../app');
const DIST_PATH = path.resolve(__dirname, '../dist');
module.exports = {
    entry: {
        app: './app/index.js'
    },    
    output: {
        filename: 'js/bundle.js',
        path: DIST_PATH
    },
    module: {
        rules: [
            {
                test: /\.js?$/,
                use: "babel-loader",
                include: APP_PATH
            }
        ]
    }
};

在 webpack 配置中定义 loader 时,要定义在 module.rules 中。其中,test和use属性是必须的。include属性,定义了rules执行的范围。这告诉 webpack 编译器如下信息: 嘿,webpack 编译器,你在编译文件过程中,如果这个是在app目录下的js文件,在你对它打包之前,先使用 babel-loader 转换一下。 重新执行本地编译

npm run build

这次不再报错,编译成功。 好了,再次打开public目录的index.html,页面成功显示了hello world。 可能,你还有一些疑惑,html文件如何也同步到dist目录?bundle.js文件修改了,万一被浏览器缓存了怎么办?如何通过script标签把打包生成的js自动添加到html?react和业务代码如何分开打包?如何实现开发环境的热更新?

下一章,我将针对这些问题,会一一进行讲解,并且持续优化我们的工程。

赞 赏