用Webpack4配置 React + Express + Less 开发环境
Web
字数10690 2018-12-09

被Webpack弄的头大

这几天在准备开发一个基于 React/Redux 的应用, NodeJS 和 React 一天之内就学完了, 但是学习 Webpack 的时候却花了两天的时间折腾, 主要原因有两个:

学习方法

好在经过两天Google, 看了一百多个国外的技术博客, 算是把Webpack入门了, 知道怎么用Webpack搭建 React + Express 开发环境了.

在分享搭建环境步骤之前, 我想分享一下我是怎么快速学习的方法, 特别是针对这种多维度、高复杂度和相互交叉的综合知识领域, 因为很多同学除了喜欢我的技术文章外, 更加关心我快速学习的方法.

1 要对自己有足够的信心, 即使一开始你还是一脸懵逼的状态, 不知道如何下手, 也要对自己有绝对的自信, 因为有时候我们没法精通一门技术, 不是因为我们笨, 而是一开始我们的信心不足, 被困难吓到了而不敢尝试. 所以在一开始看到 Webpack 一团乱麻的情况下, 我的信心来源于: “Emacs这个世界上最复杂、最难折腾的软件我都可以玩的很溜, 还有什么东西是我没法搞定的? “ 所以, 保持了这种强大的信心, 经过两天的缠斗最终还是彻底理解了 Webpack

2 自己想要解决的问题是什么, 只有非常清晰的知道自己想要解决的问题, 这样在搜索和学习的时候, 才不会被很多不关心的信息所干扰, 比如这两天, 我一直在Google上搜索 “React Express Webpack” 相关的文章, 这过程中, 看到了很多关于 Vue.js, Gulp 等等不相关的文章, 虽然也写的很好, 但是通通被我无情 pass 了, 我一直盯着我的目标在看材料. 所以, 清晰的目标能够帮助你避免过度无用信息的干扰.

3 通过看大量知识缩小思考范围, 在目标明确的前提下, 先看大量的技术博客和文章, 每个技术文章都会提供解决某一个小问题的知识, 比如, 有些文章对搭建环境的目录结构写的很好, 有些文章讲解 webpack 配置很详细, 有些文章讲热替换讲的很好. 你需要看足够多的文章, 通过对比, 才能知道哪篇文章是最新的, 而且是写的最清晰的, 哪篇文章写的东西是已经陈旧没用的. 我这两天看了100多篇技术文章, 最后通过对比和实验, 发现最后只有几篇文章中的内容才是最新的, 并符合我要求的素材. 大量看技术文章才能学到足够的知识去解决问题, 如果你看的文章和知识量不够, 即使最后你东平西凑碰巧成功了, 将来遇到问题后还是不清楚解决问题的根源, 最后会花费更多时间去交学费.

4 依葫芦画瓢才会融汇贯通, 一般复杂的知识都是没有现成解决方案的, 你只能通过多种知识组合才能解决问题, 而怎么组合的关键就是要先依葫芦画瓢把代码和配置步骤都 手工 敲一遍, 手工敲的目的是为了让手产生足够强度和手指记忆, 这样你在手工敲打的同时会在脑袋里反复的思考每一个步骤后面的意义, 这种看似笨的方法, 会最大强度的增强你对所学知识的印象, 这些知识会在潜意识里面沉淀着, 当你遇到两种不同的知识需要融合的时候, 这些潜意识的知识就会蹦出来帮你产生非常重要的联系和线索, 当所有知识点最后都连成一张清晰的逻辑网以后, 问题自然就解决了.

5 把解决方案写下来, 原来在深度的时候, 每个月都会给团队培训, 其实自己学的知识其实已经非常熟练和牢固了, 但当我把这些知识通过 PPT 或者技术文章的形式写出来的时候, 我会非常注意文章的简洁性和清晰度, 以方便别人能够最小负担的看懂. 最后, 我发现, 当你教别人的时候, 自己的知识体系也会变得更深更广, 思路也会更加清晰.

配置 React + Express 开发环境

带着目的性去学习

完整的工作流程

在折腾之前, 先把上面的这张逻辑流程想清楚, 这就是我们开发环境需要达到的最后目标, 记住这个开发流程, 你在阅读那些配置文件细节的时候, 就会理解的更深入:

其实 Webpack 本质上就是通过各种插件来完成这个工作流, 只不过随着你经验越来越丰富, 你会用更多的插件去粘合前后端框架和各种工具模块, 以完成更加智能的自动化流程.

没看懂? 没关系, 跟着我配置一遍吧, 配置完以后, 你就懂了.

准备工作

安装 homebrew

/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

安装 nodejs

brew install node

安装cnpm npm 每次去国外服务器下载东西都很慢, 浪费了很多时间, 所以我们需要先安装一下 cnpm 来加速 npm 包的下载:

npm install -g cnpm --registry=http://r.cnpmjs.org

新建项目

mkdir project && cd project && npm init -y

这个命令会在当前文件中创建一个 package.json 文件, package.json 的作用主要是记录开发过程中都安装了哪些包及包的版本, 方便用 git 管理和以后生产环境部署用.

安装依赖包 下面是 npm/cnpm 命令常用的三个安装命令的区别:

cnpm 命令 参数说明
cnpm install package -g 安装到系统中, 任何项目都可以使用
cnpm install package –save 安装到项目本地目录中, 并保存包名信息到依赖字段 dependencies 下, 下次部署服务器就可以通过 package.json 文件自动安装依赖包
cnpm install package –save-dev 和 –save 参数类似, 只不过保存包信息到依赖字段 devDependencies 下, 表示只在开发环境才会安装, 生产环境不会安装的包

执行下面的命令可以自动安装所有依赖包:

cnpm install --save react react-dom react-hot-loader

cnpm install --save express body-parser cookie-parser multer

cnpm install --save-dev webpack webpack-dev-server webpack-cli webpack-dev-middleware webpack-hot-middleware

cnpm install --save-dev @babel/core @babel/preset-env @babel/preset-react babel-loader

cnpm install --save-dev less less-loader css-loader style-loader

cnpm install --save-dev reload nodemon

下面是每条命令和安装包的作用说明: | 依赖包 | 作用说明| | ——— | ————| | react react-dom react-hot-loader | React 前端框架相关的库和热替换插件 | | express body-parser cookie-parser multer | 基于 NodeJS 的后端框架 Express 和它的依赖库 | | webpack webpack-dev-server webpack-cli webpack-dev-middleware webpack-hot-middleware | Webpack 和热替换中间件 | | @babel/core @babel/preset-env @babel/preset-react babel-loader | Babel 插件, 主要用于转换 React 的 ES6 语法到浏览器可以识别的正常 JS 语法 | | less less-loader css-loader style-loader | Less 语法编写的样式文件会自动编译成 CSS 格式的 | | reload nodemon | 页面主动刷新插件和服务器重启工具 |

目录结构

在折腾配置文件之前, 我们看看一下最终的目录结构:

project

├── app.js

├── client

│   ├── index.less

│   └── index.js

├── server

│   └── index.html

├── dist

│   └── bundle.js

├── .babelrc

├── nodemon.json

├── package.json

└── webpack.config.js

文件 文件说明
app.js 启动入口, 后端 express 代码, 控制路由和HTTP请求响应
client React 前端组件代码, 包括 JS 和 CSS 文件
server 后端视图文件, 主要是 html 文件
dist Webpack 编译打包文件存放的目录
.babelrc Babel 的配置文件
nodemon.json nodemon 的配置文件
package.json 项目配置文件, 主要存放包信息和服务端启动命令
webpack.config.js Webpack 配置文件

折腾配置文件

我们按照上面的目录结构去填充每个配置文件, 我会在文件下面讲解每行代码的意思:

app.js

var express = require('express'),
    app = express(),
    reload = require('reload'),
    rootPath = __dirname;

var webpack = require("webpack"),
    webpackConfig = require("./webpack.config"),
    webpackDevMiddleware = require("webpack-dev-middleware"),
    webpackHotMiddleware = require("webpack-hot-middleware"),
    compiler = webpack(webpackConfig);

app.use(
  webpackDevMiddleware(compiler, {
    publicPath: webpackConfig.output.publicPath,
    noInfo: true,
    stats: {
      colors: true
    }
  })
);
app.use(webpackHotMiddleware(compiler));
app.use(express.static(__dirname + '/dist'));

app.get('/', function (req, res) {
  res.sendFile(rootPath + '/server/index.html');
});

reload(app);

app.listen(3000, () => {
  console.log('* Server starting...');
});

client/index.js

import React from 'react';
import ReactDOM from 'react-dom';
import styles from "./index.less";

const title = 'React + Express rocks!';

ReactDOM.render(
    <div className={ styles.title } >{title}</div>,
    document.getElementById('app')
);

if (module.hot) {
  module.hot.accept();
}

client/index.js 这个文件主要是开发用的文件, 修改后会自动被 webpack 添加JS依赖后汇总编译成 dist/bundle.js 文件, 所以 server/index.html 的JS脚本不是 index.js , 而应该是 bundle.js

client/index.less

.title {
    color: red;
    margin: 20px;
}

最简单的LESS文件, 需要安装 less、less-loader、 style-loader 和 css-loader 这四个插件, 才能通过 webpack 保证 JS/JSX 文件中可以直接执行 import styles from "./index.less" 来使用 LESS 文件的样式内容.

server/index.html

<!DOCTYPE html>
<html>
    <head>
        <title>Cool</title>
    </head>
    <body>
        <h1>Hello World</h1>
        <div id="app"></div>
        <script src="./bundle.js"></script>
        <script src="/reload/reload.js"></script>
    </body>
</html>

非常简单的 HTML 文件

dist/bundle.js Webpack 为了加快编译速度, bundle.js 文件其实都存在于内存中, 所以 dist 目录下什么都没有, 只是方便 HTML/JS 文件之间能够通过 dist/bundle.js 进行关联, 我们这里需要 dist 这个目录占坑, 以使得 Webpack 可以粘连 HTML 和 JS 文件.

.babelrc

{
  "presets": [
    "@babel/preset-env",
    "@babel/preset-react"
  ]
}

Babel 相关的设置, 可以让你使用最新的 JavaScript 语法, Babel 会自动把最新的语法转换成所有浏览器可以执行的陈旧语法, 让你开发的时候用JavaScript最新语法, 同时又保证编译后的JS代码对浏览器的兼容性.

注意在 .babelrc 配置后, 就不需要在 package.json 文件中配置 babel 选项了.

nodemon.json

{
  "ignore": ["dist/", "client/*.js"],
  "ext": "json js html"
}

package.json

{
  "name": "project",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "nodemon ./app.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "body-parser": "^1.18.3",
    "cookie-parser": "^1.4.3",
    "express": "^4.16.4",
    "multer": "^1.4.1",
    "react": "^16.6.3",
    "react-dom": "^16.6.3",
    "react-hot-loader": "^4.3.12"
  },
  "devDependencies": {
    "@babel/core": "^7.2.0",
    "@babel/preset-env": "^7.2.0",
    "@babel/preset-react": "^7.0.0",
    "babel-loader": "^8.0.4",
    "css-loader": "^2.0.0",
    "less": "^3.9.0",
    "less-loader": "^4.1.0",
    "nodemon": "^1.18.7",
    "reload": "^2.4.0",
    "style-loader": "^0.23.1",
    "webpack": "^4.27.1",
    "webpack-cli": "^3.1.2",
    "webpack-dev-middleware": "^3.4.0",
    "webpack-dev-server": "^3.1.10",
    "webpack-hot-middleware": "^2.24.3"
  }
}

大部分代码都是被 npm/cnpm 命令自动生成的, 我们只用看 scripts 这一段:

webpack.config.js 如果你看到这里了, 你会发现上面的这些配置一点都不复杂, 其实最复杂的就是 webpack.config.js , 但是如果你理解了我上面说的工作流程, 你会发现理解 webpack.config.js 的内容也是非常自然的:

const webpack = require('webpack');

var hotMiddlewareScript = "webpack-hot-middleware/client?reload=true";

module.exports = {
  entry: {
    client: ["./client", hotMiddlewareScript]
  },
  mode: "development",
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        exclude: /node_modules/,
        use: ['babel-loader']
      },
      {
        test: /\.less$/,
        use: [
          {
            loader: "style-loader"
          },
          {
            loader: "css-loader",
            options: {
              sourceMap: true,
              modules: true,
              localIdentName: "[local]___[hash:base64:5]"
            }
          },
          {
            loader: "less-loader"
          }
        ]
      }
    ]
  },
  resolve: {
    extensions: ['*', '.js', '.jsx']
  },
  output: {
    path: __dirname + '/dist',
    publicPath: "/",
    filename: 'bundle.js'
  },
  plugins: [
    new webpack.HotModuleReplacementPlugin(),
    new webpack.NoEmitOnErrorsPlugin(),
  ],
  devServer: {
    hot: true
  }
};

启动服务

好了, 到目前为止, 所有配置文件都折腾完了, 执行下面命令就可以启动Web服务了:

npm start

然后打开 http://localhost:3000 就可以看到效果, 尝试修改一下 JS、LESS、HTML甚至服务器代码, 看看是不是全栈都自动刷新了呢?

Happy hacking!

相关技术博客参考链接

折腾过程中遇到的技术博客也非常有参考价值, 下面是我折腾过程中, 主要参考的技术博客和我觉得每个技术博客主要的参考价值

技术博客 参考价值
React + Webpack 4 + Babel 7 Setup Tutorial 主要介绍在 Webpack4 下怎么成功安装 Babel , 网上大部分的文章都是基于 Webpack3 安装的, 各种 Babel 相关的报错, 而且从项目整体配置上讲的也很清晰易懂
Express结合Webpack的全栈自动刷新 怎么做到 JS/CSS 热替换讲的非常好, 比如 reload=true 的技巧, 其他的技术博客各种折腾都不成功
CSS Modules 用法教程 怎么在 React 使用 CSS 组件的方案
React + CSS Modules + LESS + Webpack 4 LESS 相关配置写的最简单易懂的文章

最后

前端这个技术圈是我看到的最为混乱的技术圈, 各种框架和各种插件层出不穷, 写个Hello World之前就要折腾一大堆配置环境.

当然, 我也从前端技术圈中看到了像Emacs这样的折腾氛围, 技术圈和人一样, 折腾才有活力, 折腾才能学到更多知识, 因为当你折腾一遍以后, 不管折腾的多么复杂, 一旦你记住, 你就不会那么难了. 哈哈哈哈.