今天想要分享的是“ 黑盒 ”。
最近,在搭建某个业务系统的过程中,我选用了 react 作为主要的技术栈。和同事的业务交流中,我提到了“黑盒”这个词汇。
在我司同事的强烈要求(火锅承诺)下,就有了今天这篇《CRA 为什么要做成“黑盒”》。
# 什么是“黑盒”
首先从前端 er 们熟悉的 vue-cli 说起,以下是vue 脚手架的基本目录:

再来看下 create-react-app 的项目目录:

在这里,我就不对每个文件进行介绍了。
相比 create-react-app,vue-cli 将配置文件完全暴露出来。但是,create-react-app 的基本目录中却隐藏了 config 相关的配置文件,当然 @vue/cli 也做了相关功能的实现。
对于这种方式我把它称之为“ 黑盒 ”,也就是我们今天的主题。
# 脚手架为什么要使用黑盒
一般的项目流程大致如下所示:
- 实现项目的基本结构,如基础设置搭建,测试;
- 过程中的文件配置,如 webpack eslint 等;
- 完成项目业务的一些功能需求。
在实际业务操作中,前面两项流程可以为后期的项目服务,进而减少很多重复性的工作。
所以,为了开发的方便性,我们完全可以用一个 黑盒子 把项目的基本架构和文件配置装起来,给后续开发者们留下一些“痕迹”,这样新手们就不需要担心项目的前期准备,达现开箱即用的体验。

开箱即用是给开发者提供的一种便利。
# 矛盾的黑盒自由
开发界有这样一条箴言:“新手渴望规则,老手渴望自由”。
随着开发者能力的提升,黑盒可能无法满足你的需求,娴熟的老手们越来越想解除黑盒的约束,都想去实现黑盒中文件的自由支配,但 @vue/cli 和 create-react-app 黑盒给老手们的自由度是完全不一样的。
- @vue/cli 给老手们留了实现黑盒自由的接口 vue-config.js
- create-react-app 的宗旨是,要么不给自由,要么把自由权全给你 npm run eject
技术大佬们对于 create-react-app 的黑盒是爱恨交加的,既想让它给予自由,又不想打破它的黑盒约束。
黑盒自由一旦被打破,整个操作是不可逆的,项目依赖项后期的版本更新就无人可做了。
所以有技术大佬投身于 CRA 的研究,最终实现了两方面都能兼得的效果,既能保证开发者受制于 CRA 黑盒,又能实现黑盒中文件配置的自由。

实现 react 中的 CRA 的依赖项是 react-app-rewired。 地址:https://www.npmjs.com/package/react-app-rewired

之前我们聊到,之所以在 Vue 和 React 中实现黑盒功能,其目的很明确,就是方便版本升级后,我们所开发的项目的依赖会自动跟随升级。
但是,一旦打破这种约束,表面上看起来是“我行我素”了,事实上项目变得糟糕透了,项目后期的维护会相当困难。
今天我们就来聊聊如何使得两者兼具,即满足项目依赖自动升级,又能死死地拿捏住黑盒中配置文件的主动权。
# 以 React 中的 CRA 为例
当我们使用 create-react-app 搭建项目的基础结构时,它基本的目录是这样的:

在这个目录下,没有找到任何跟 config 有关的文件。
在没有配置文件的情况下,我们来谈一个简单的需求:
- 项目里要采用 css 预处理器 less || sass
乍一看这个需求很简单,但是却发现整个项目中找不到半点和 webpack.config 相关的文件,有种 “ 狗咬刺猬,无处下嘴 ”的感觉。

别担心,本文的主角 react-app-rewired 登场了!它就像是一把手术刀,可以一下子解决病根。
在 https://www.npmjs.com 官网上搜索 react-app-rewired 的简介:
- Tweak the create-react-app webpack config(s) without using 'eject' and without creating a fork of the react-scripts. 在不使用 'eject' 且未创建 react-scripts 分支的情况下,调整 create-react-app 中 webpack 配置。
- All the benefits of create-react-app without the limitations of "no config". You can add plugins, loaders whatever you need. create-react-app 的所有好处没有“ no config”的限制。您可以根据需要添加插件,加载程序。
虽然翻译得非常直白,但依然令人兴奋不已。它真的可以争取 CRA 中 webpack 配置的主动权。
# 动手操作
首先,安装 react-app-rewired 项目目录下 npm install react-app-rewired -S。
安装完之后看一下 react-app-rewired 的版本:

*载下**完后的版本是 react-app-rewired@2.1.6。
接下来,在当前目录创建一个 config-overrides.js 文件,文件内容如下:
module.exports = function override(config, env) {
//do stuff with the webpack config...
return config;
}
最后修改 package.json 中的 scripts 脚本指令,具体流程参考https://github.com/timarney/react-app-rewired 。
修改前

修改后

npm start 我们发现项目依然可以正常启动。

# 接受 webpack 的配置项
对于修改黑盒内的 webpack 配置,react-app-rewired 2.0+ 版本需要另一个依赖模块叫 customize-cra。
npm install customize-cra -S 安装这个依赖。同时安装 less-loader,安装方式 npm install less-loader -D 。
这是它对外 API 介绍地址:https://github.com/arackaf/customize-cra/blob/master/api.md
const { override,addLessLoader } = require("customize-cra");
module.exports = override(addLessLoader());
//丰富的API可以满足你项目的任何需求
下面演示向配置项中添加 less-loader,然后将 App.css 改成 App.less。同时将 less 文件引入到 App.js 中。

npm start 重新启动服务后,页面效果如下:

以上就是实现 CRA 黑盒中 webpack.config 文件的关于 less 的自由配置的整个过程。
我们终于把 CRA 的黑盒主动权争取来了!
# CRA 黑盒的实现
不管是 @vue/cli 还是 create-react-app,它们内部都暗藏着黑盒的机制。在这里大家万不可把 @vue/cli 和 create-react-app 都认为是黑盒。它们仅仅是官方提供的两个脚手架,用于快速搭建项目基础结构。

# 黑盒如何工作
在项目开发中,我们把它分成三份:一份是是项目结构,一份是黑盒,一份是最终的产出。

简单来说,写好后的项目,经过黑盒处理之后,就成了一个可以上线部署的产品。这个处理过程就需要前端开发者们去努力实现了。
黑盒其实很简单,就是需要我们打破平时的开发思维,给开发者提供更简单的解决方案,一句话概括就是怎么简单怎么来。
对于黑盒原理,我个人认为它其实就是 包裹的 webpack 或者其他工具 。
本篇文章我们就以 webpack 为例,写一个自己的黑盒。
正常使用 webpack 的开发中,我们需要手动创建一个 webpack 的配置文件 webpack.config.js 。
黑盒现在要做的是:
- 删掉这个配置文件,在内部完成基本配置。
- 如果你想要自己配置,不好意思,你得按照我的约定来,更换新的配置文件 demo.config.js(demo是自己起的名字)
# 实际操作
有一个已经配置好的项目,如下图:

配置文件的内容很简单,如下:
const path = require("path")
module.exports = {
mode:"none",
entry:"./src/index.js",
output:{
path:path.resolve(process.cwd(),"dist"),
filename:"bundle.js"
}
}
现在我们要移除 webpack.config.js 文件,同时在项目同级目录下建个 cwj-page 的文件夹。

这个 cwj-page 就是我们要实现的黑盒。
它里面的文件结构如下:

bin 文件夹是我们使用该文件夹名称当作指令要执行的文件,在 bin 文件夹下创建一个跟黑盒同名的 js 文件: cwj-page.js 内容如下:
#!/usr/bin/env node
const config = require("..")
const webpack = require("webpack")
const compiler = webpack(config,(err,state)=>{
if(err)throw err;
console.log("编译成功")
})
lib 是我们要存储的一个js文件,这个文件其实就是我们所有关于 webpack.config.js 的基本配置。
const path = require("path")
const cwd = process.cwd()
const fs = require("fs")
let config = {
mode:"none",
entry:"./src/index.js",
output:{
path:path.resolve(process.cwd(),"dist"),
filename:"build.js"
}
}
// const loadconfig = require(`${cwd}/page.config.js`)
let cf = fs.existsSync(`${cwd}/page.config.js`)//判断当前目录下时候有约定的配置文件
if(cf){ //有约定的配置文件
try {
const loadconfig = require(`${cwd}/page.config.js`)
config = Object.assign({},config,loadconfig)
}catch(e){
console.log(e)
}
}else{ //没有约定的配置文件
onfig = Object.assign({},config)
}
module.exports = config
注意 :在这个 cwj-page 下的 webpack 必须用 npm install webpack -S *载下**,因为它是黑盒所依赖的模块,目的就是要对 webpack 进行包装。
完成之后我们现在要把 cwj-page 发布 npm 或者直接本地软连接,因为我们后面的操作要完全使用黑盒指令对项目打包。所以在 cwj-page 下的终端进行 npm link,这样在全局安装目录下就会有一个 cwj-page 的文件。

再回到 demo 项目下,在 src 下的 index.js 随便写写 js 代码 ,然后使用黑盒指令 cwj-page。

到此,一个简单的黑盒就算包装成功了。后期我会录制一个实现黑盒的视频,将同步发布 bilibili 和公号上,到时候各位小伙伴们别忘了一键三连!