使用 webpack4.0 从零搭建 React + TypeScript 应用

2,738 阅读9分钟

代码地址 github.com/Maricaya/ad… , 有帮助请 star ~

背景

刚接触 react 的时候,大家都是通过 create-react-app 等脚手架来快速创建应用。当基本配置满足不了我们的特殊需求时(比如使用 sass),我们会修改 eject 出来的 webpack 配置文件。

面对各种配置项一脸懵逼,不知道怎么快速修改。其实,学习项目搭建与配置没有什么技巧,都是经验(套路)。

这篇文章会介绍使用 webpack 手动搭建一个 react 项目的套路,来看看我的踩坑经历吧。

工具

  1. Yarn
  2. webpack 4
  3. 环境:webpack-dev-server 3
  4. 语言:TypeScript 3
  5. 编辑器:WebStorm / VScode 等

步骤

npm 初始化

npm init -y 

这一步创建 package.json

安装 webpack、TypeScript、React

打开 webpack 官网,找到安装命令。

yarn add webpack webpack-cli typescript --dev
yarn add react react-dom 
yarn add @types/react @types/react-dom --dev

新建 lib/index.tsx

在这个文件中写入几行 TypeScript 代码。

console.log(123);

index.tsx 在浏览器中是不能运行的,需要 webpack 帮我们打包成浏览器可执行的 index.js。

webpack.config.js

在根目录新建 webpack.config.js。

格式

以下配置都写在 module.exports 对象内部。

module.exports = {}

mode

mode 是 webpack 4 中新增加的选项, 有两个可选值:production(生产环境)和 development(开发环境)。 mode 不可省略。 我们先配置成开发环境,后面有需要再修改。

mode: 'development'

entry

打包的入口文件。

entry: { 
  <key>: <value>
}

在这里只介绍对象形式,是因为这个是最完整的entry配置,其他形式只是它的简化而已。

对象中的每一对属性对,都代表着一个入口文件。因此多页面配置时,肯定是这种形式的 entry。

key 可以是字符串变量,对应输出文件的名称;也可以是路径,比如 'a/b/c',对应着文件的输出路径。 详细解释可以看这篇文章

entry: { 
  index: './lib/index.tsx' // ./ 当前目录下
}

resolve

webpack在启动后会从配置的入口模块触发找出所有依赖的模块, resolve 配置 webpack 如何寻找模块对应的文件。

在导入语句没带文件后缀时,webpack 会自动带上后缀去尝试访问文件是否存在。 resolve.extensions 用于配置在尝试过程中用到的后缀列表。

resolve: {
  extensions: ['.ts', '.tsx', '.js', '.jsx']
}

module.rules

tsx

接下来配置解释 tsx 的 loader。那么问题来了,怎么知道哪些是最合适的 loader?

答案是没有标准 loader,只能是一个一个试。

把网上能找到的配置方式全部试一遍之后,我找到一个我认为最好的方式 awesome-typescript-loader

安装

yarn add awesome-typescript-loader --dev
module: {
   rules: [
      {
        test: /\.tsx?$/, 
        loader: 'awesome-typescript-loader'
      }
  ]
}

scss

再配置翻译 scss 的模块,需要使用三个 loader,style-loader, css-loader,sass-loader

yarn add --dev style-loader css-loader sass-loader

可以看出 webpack 中 loader 的原则,一个loader只做一件事情

  • sass-loader 将 icon.scss 翻译为 icon.css
  • css-loader icon.css -> 对象
  • style-loader 对象 -> <style>css</style>
{
  test: /\.s([ac])ss$/,
  use: [
    'style-loader',
    'css-loader',
    'sass-loader'
  ]
}

配置完了输入、中间 loader,接下来我们来配置输出 output。

output

  • path 输出地址 我本来以为这里可以直接写绝对路径 __dirname + './dist',后来发现在 windows 上会报错。 因为在不同的操作系统上,路径表示方法不一样。

    • windows: __dirname + '\\dist'
    • mac & linux: __dirname + './dist'

所以,我们直接使用 node.js 提供的方法 path.resolve(__dirname, 'dist'), 这个 API 会根据操作系统的类型将两个目录连接起来。

  • library: 'adorable-react' 开发 npm 包的时候会用到这个命令,library 是指定用户使用require时的模块名。 这个模块在使用时,命令是 require("adorable-react")

  • libraryTarget 表示库的输出格式是什么,输出模块规范。有这么几种选择 umd、amd、commonjs2。

一般都选 'umd',一种在所有模块定义下都可运行的方案。

下面问题来了,umd 到底是什么呢?

要知道 umd 是什么,我们需要先了解下前端打包的历史。

前端最开始是没有包管理系统的,所有的js代码都通过 script 标签一个一个加载。 这个阶段 js 包内重名的全局变量会互相影响。这时就需要打包工具,把变量作用域限制在局部。

前端程序员提出了 AMD 打包方案,Node.js 社区也提出了另一套打包规则 commonJS。

  1. AMD 比较著名的方案 require.js,我们的变量都定义到 define 函数中。
define (function () { 
	// 这里是我们的变量
	var a = 1 ... 
}) 

这个标准叫做 amd 异步模块定义。

  1. commonJS 差不多是同一时间,Node.js 社区也提出了另一套打包规则 commonJS。
var a = 1 // 只在当前文件有效
module.exports = { ... }

我们正在写的 webpack.config.js 也遵循这个规范。

commonJS 只在 Node.js 中使用,AMD 只在浏览器中使用。 导致你在使用时,必须区分代码是运行在客户端还是服务端。

这时,有人发明了一套统一的规则 UMD 统一模块定义。 UMD 会判断当前运行环境是什么,根据运行环境选择使用 commonJS 或者 AMD。

所以在这里,我们选择最完整的 UMD。

最后,完整的 output 代码如下:

const path = require('path')
// mo
output: {
  path: path.resolve(__dirname, 'dist/lib'),
-  library: 'adorable-react',
  libraryTarget: 'umd'
}

配置 tsconfig.json

在根目录下创建 tsconfig.json,它是 TypeScript 的配置文件。

主要包含两块内容:

  1. 指定待编译的文件
  2. 定义编译选项

我们这篇文章主要讲 webpack 4.0 的配置,ts 的配置文件先略过,可以直接拷贝我的仓库里 tsconfig.json 文件。

想知道详细解释的小伙伴可以看官方文档,和这篇博客。

尝试第一次打包

配置完这些基本规则之后,我们可以尝试用 npx webpack 打包啦。

打包后的文件是这样的:

根目录/dist/lib/index.js — 这是 index.tsx 翻译之后的文件

根目录/lib/index.d.ts — 包含了声明的所有类型

index.d.ts 放置的目录不对,生成的 *.d.ts 类型文件都应该放入dist目录中。

一顿搜索后,我在 tsconfig.json "compilerOptions" 中配置 "outDir": "dist"

再次打包,所有文件都放进了 dist 目录,第一次用 webpack 打包就完成啦。

目前 webpack 就做了一件事,把 ts、tsx 翻译成 js。

webpack-dev-server

接下来我们安装 webpack-dev-server,让代码能够自动编译,实现热更新。

yarn add webpack-dev-server --dev
npx webpack-dev-server 

此时,访问 http://localhost:8080/index.js 我们可以看到编译后的文件。

随意改动 lib/index.tsx 中的文件,http://localhost:8080/index.js 会自动更新。

那么问题来了,webpack-dev-server 为什么编译这么快?什么是热更新?

速度快

整个的过程我们可以简化一下,webpack-dev-server 会启动一个小型服务器(称为 Bundle Server)。

Bundle Server就是一个服务器,会执行这些编译后的文件,让浏览器可以访问到。

webpack 打包好的文件 index.js 会传输给 Bundle Server,放在内存中,

等用户访问 index.tsx 时,服务器直接从内存中抛出index.js,所以编译很快。

热更新

在应用程序的开发环境,方便开发人员在不刷新页面的情况下,就能修改代码, 并且直观地在页面上看到变化的机制。

html-webpack-plugin

到目前为止,我还没有 html 文件,在根目录新建 html 入口文件 index.html。

下面问题来了,引入 js 的文件路径是什么?

只要 entry 下的文件名称修改,我们就需要手动修改 js 的路径。那为什么不自动生成路径呢?

安装插件 html-webpack-plugin,根目录创建 index.html 文件,把 webpack 打包后的静态文件自动插入到 html 中。

yarn add --dev html-webpack-plugin

webpack.config.js 配置

const HtmlWebpackPlugin = require('html-webpack-plugin')

plugins: [
  new HtmlWebpackPlugin({
    title: "adorable-react", // 页面 title
    template: "index.html"
  })
],

把 index.tsx 的 ReactDom 挂载到 index.html 上。 index.tsx 中:

const div = document.createElement('div');

div.innerText = 'div';
document.body.appendChild(div);

externals

如果我们想引用一个库,但是又不想让webpack打包, 可以通过配置 externals 实现。

这个功能主要用在,创建一个 npm 库的时候。

比如:我们开发了一个 React 的组件库,里面引用了 react 和 react-dom。 但是我们没必要把 react 和 react-dom 打包起来,因为用户会在使用时会自己下载。

那么我们就可以 externals 的方式,将 react/react-dom 从我们的源代码中排出去。

    externals: {
// 配置如何引入
      react: {
// 运行在 Node.js 环境中,import * as React from 'react' 等价于 const react = require('react')
        commonjs: 'react', 
        commonjs2: 'react',
// 使用require.js等加载,等价于 define(["react"], factory);
        amd: 'react',
//在浏览器中使用,需要提供一个全局的变量'React',等价于 var React = (window.React) or (React);
        root: 'React'
      },
      'react-dom': {
        commonjs: 'react-dom',
        commonjs2: 'react-dom',
        amd: 'react-dom',
        root: 'ReactDom'
      }
    }

webpack 配置区分开发、生产模式

看到这里,你已经完成了 webpack 所有基本配置!给自己鼓鼓掌吧!

不过目前 develop 模式和 production 模式都写在一起,难道每次打包都需要手动修改吗?

别急,webpack 为我们提供了自动切换方式。

将 webpack 文件分为 develop 模式和 production 模式,公共部分写入 webpack.config.js

production 写入 webpack.config.prod.js,develop 写入 webpack.config.dev.js

完整写法可以点击链接查看,这里这给出核心代码: webpack.config.prod.js

const base = require('./webpack.config')
// 使用 Object.assign
module.exports = Object.assign({}, base,{
  mode: 'production'
})

配置 package.json scripts

我们在 package.json scripts 中配置好命令,直接使用 yarn xxx 就可以运行项目。

直接看最后的命令:

{
    "script": {
	"start": "cross-env NODE_ENV=development webpack-dev-server --config webpack.config.dev.js",
	"build": "cross-env NODE_ENV=production webpack --config webpack.config.prod.js"
    }
}

开发模式命令 webpack-dev-server,webpack 的配置是 webpack.config.dev.js

生产模式命令 webpack,webpack 的配置是 webpack.config.prod.js

cross-env NODE_ENV=xxx

NODE_ENV=development 是我们写入的环境变量。

你可能也有这个疑问,webpack.dev.config.js 中不是有 mode 吗? 为什么要手动写入环境变量?

因为 webpack 处理的这个入口脚本文件及其引用的脚本文件都无法访问 webpack.dev.config.js 的属性, 所以手动写入环境变量,让所有文件可以访问到 process.env.NODE_ENV。

cross-env 插件的作用是:保证环境变量在各个平台都能添加成功。(在 unix、windows 上环境变量写法不一样)

安装方式

yarn add --dev cross-env 

引入测试(选看)

引入 react 社区最流行的测试框架 jest,官网

测试配置不是重点,大家可以直接看仓库

这里简单说下配置:

yarn add --dev jest babel-jest @babel/preset-env @babel/preset-react react-test-renderer
yarn add --dev ts-jest

创建 .babelrc

{ "presets": ["react-app"] }

配置 scripts test

"test": "cross-env NODE_ENV=test jest --config=jest.config.js --runInBand"

创建 jest.config.js

配置 Enzyme Enzyme 是用于 React 的 JavaScript 测试实用程序,可以更轻松地测试 React 组件。

yarn add --dev enzyme enzyme-adapter-react-16

创建 test 文件夹,写入配置,这里不赘述了,有兴趣直接看源码吧~

总结每个目录作用

终于完成了项目搭建,最后来看看每个目录的作用吧!

现在每个文件的功能我们都非常清楚,出了问题,我们也知道应该在哪里修改。

.
├── .babelrc                      // babel 配置
├── README.md
├── index.html                    // 首页
├── jest.config.js                // jest 配置
├── dist                          // 最终代码
├── lib                           // 源代码         
│   ├── __tests__
│   │   └── index.unit.tsx
│   ├── index.scss
│   └── index.tsx
├── package.json
├── test                          // test 配置
│   ├── __mocks__
│   │   ├── file-mock.js
│   │   └── object-mock.js
│   └── setupTests.js
├── tsconfig.json                 // ts 配置
├── tsconfig.test.json            // ts 测试配置
├── tslint.json                   // 代码检测配置
├── webpack.config.dev.js         // webpack 配置
├── webpack.config.js
├── webpack.config.prod.js
├── webpack.config.wheel.js
└── yarn.lock

恭喜你完成了项目搭建,接下来我们可以愉快地 coding 了!