前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版

Typescript+React模板搭建(三)从零配到可上线

首页2018-12-31 23:50:14Front-End
TypeScriptReactWebpack前端工程化

前两篇把 TypeScript 的语法过完了,但语法会写不等于项目能跑。真正卡人的是工程那一层:.scss 引进来编译器说找不到模块,路径写成七八层 ../,装饰器一加就报错,打包出来一个几兆的 app.js 塞满第三方库。这些问题跟类型系统没半点关系,却能让人一整天挪不动。

这篇就是把这些坑一个个填掉的记录,从 npm init 开始,一直配到构建缓存、按需加载、代码压缩和提交前的 lint 卡口。跟完之后你手上会有一份能直接拿去改的模板,而且每一行配置你都知道它在解决什么。

整理于网络

在本篇文章中,我们将从浅入深,和大家一起学习以下知识:

  • 从零建目录、装依赖、写第一份只有 entry 和 output 的 webpack 配置
  • tsconfig.json 里 jsx、target、module、moduleResolution 这几项分别在管什么
  • sass、css module、公共变量、装饰器、路径别名、构建缓存这六项开发体验优化
  • 把臃肿的 webpack.config.js 按 plugins / rules / utils 拆成模块
  • 集成 antd 并改主题色,顺带把按需加载一起解决掉
  • 用 mobx 做状态管理,以及怎么给 store 补上全局的 TypeScript 校验
  • 打包阶段的 css 分离、代码分割、第三方库抽离、js 和 css 压缩、externals
  • tslint、stylelint、prettier、pre-commit 这一套团队规范怎么落地

这是连载的第三篇,前两篇分别是 Typescript基础及结合React实践(一) 和 Typescript总结篇(二)。语法部分在那两篇里讲透了,这篇只管工程。

先把时间背景交代清楚。这套模板搭于 2018 年底,webpack 是 4.x,TypeScript 是 3.x,React 是 16.x。这些年工具链换了好几轮,文中不少包已经停止维护。原始配置我一行不动地保留,涉及到现在有替代方案的地方另起一段说明,具体版本行为以各自官方文档为准。如果你只是想快速起一个 TS + React 项目,现在用 npm create vite@latest 选 react-ts 模板三十秒就好,这篇的价值在于让你看清每一个配置项当初是为了解决什么问题才被加进来的。

# 一、项目初始化

这一章的目标很朴素,让 npm run dev 跑起来,浏览器里能看到一个 <div>1234</div>。中间会踩到 JSX 版本没配、扩展名没配这两个几乎人人都会踩的坑。

# 1.1 创建项目

确保安装了 npm install -g typescript

# -S 是--save简写
# -D 是--save-dev简写

# 创建目录
mkdir ts-react && cd ts-react

# 生成package.json、tsconfig.json
npm init -y && tsc --init

# 安装开发工具
npm install -D webpack webpack-cli webpack-dev-server

# 安装react相关
npm install -S react react-dom

# 安装react相关的ts验证包
npm install -D @types/react @types/react-dom

# 安装ts-loader(或者awesome-typescript-loader) 这两款loader用于将ts代码编译成js代码
npm install -D awesome-typescript-loader
@前端进阶之旅: 代码已经复制到剪贴板

这几行装的东西可以分成三类。webpack 三件套负责打包和起开发服务,react 和 react-dom 是运行时依赖所以用 -S,@types/react 和 @types/react-dom 是 React 的类型说明书,只在编译期用得上所以进 -D。最后那个 loader 才是 TypeScript 接进 webpack 的关键,没有它 webpack 根本不认识 .tsx。

原文这里有两处笔误我顺手改了:npm install-D 缺了空格,装不上;注释写的是 ts-loader 或 awesome-typescript-loader,命令里却装成了 babel-loader,而后文用的一直是 awesome-typescript-loader,所以我按后文统一了。

补一句现状。awesome-typescript-loader 早就不再维护了,现在这个位置的常见选择是 ts-loader,或者干脆用 babel-loader 加 @babel/preset-typescript 只做转译、把类型检查交给单独的 tsc --noEmit 进程。Vite 走的是同一条思路,转译交给 esbuild,检查另开一个进程。这个拆分是这些年构建提速最大的一笔。

# 1.2 webpack配置

先建目录,把 webpack 配置单独放一个 build 文件夹,好处是根目录干净,后面配置项拆多了也有地方放。

  1. 在项目根目录新建一个build文件夹
mkdir build && cd build && touch webpack.config.js
@前端进阶之旅: 代码已经复制到剪贴板
  1. 根目录下新建src文件夹,然后在src里新建index.tsx文件作为项目入口
mkdir src && cd src && touch index.tsx
@前端进阶之旅: 代码已经复制到剪贴板
  1. 编写简单的webpack配置,只包含entry和output

webpack 配置最小可用的形态就两项,从哪进(entry)、往哪出(output)。因为配置文件放在 build/ 里,所有路径都要用 path.join(__dirname, '../', ...) 先跳回根目录再往下找,这一点后面拆配置的时候会被抽成一个工具函数。

const path = require('path')

module.exports = {
    entry: {
        app: path.join(__dirname, '../', 'src/index.tsx')
    },
    output: {
        path: path.join(__dirname, '../', 'dist'),
        filename: '[name].js'
    }
}
@前端进阶之旅: 代码已经复制到剪贴板

原文这段代码里有四处会直接让 Node 报语法错的问题,我都改对了:module.export 少了 s,三个字符串少了收尾的引号,output 里的 path.join(...) 没写 path: 这个键名。照原样复制是跑不起来的。

filename: '[name].js' 里的 [name] 会被替换成 entry 的键,也就是 app,所以产物是 app.js。后面做代码分割的时候,这个占位符会派上真正的用场。

  1. 编写awesome-typescript-loader配置项: 在webpack中的module是专门用来决定如何处理各种模块的配置项,例如本例中的typescript,这里主要用的配置项就是module.rules,而当前只需要简单配置解析.tsx文件类型即可

module.rules 的写法是「用正则匹配文件名,命中了就交给指定的 loader 处理」。这里只需要一条规则,test: /\.tsx?$/ 匹配 .ts 和 .tsx,loader 指向上面装的那个。规则的顺序和多个 loader 的执行方向下一节讲 sass 的时候会细说,那里更直观。

  1. 在src/index.tsx中写入口文件
import * as React from 'react'
import * as ReactDOM from 'react-dom'

import Test from '@components/Test'

const render = () => {
    ReactDOM.render(
        <div>1234</div>,
        document.querySelector('#app')
    )
}
render()
@前端进阶之旅: 代码已经复制到剪贴板

写完这个文件,编辑器里那句 ReactDOM.render 上会立刻飘红。下面这张就是当时的报错,注意红线画在 JSX 那一行上。

但是这时候你会发现有一个错误没有处理

tsconfig 未配置 jsx 选项时 index.tsx 中 JSX 语法报错

看到这个报错说明 TypeScript 已经在工作了,它只是不知道该拿 JSX 怎么办。.tsx 后缀只是告诉编译器「这个文件里有 JSX」,具体编译成什么还得靠 jsx 这个选项指定。

这是因为在tsconfig里面没有指定JSX的版本,这时候在tsconfig的compilerOptions中添加"jsx": "react"配置项即可消除错误

"jsx": "react" 的意思是把 JSX 编译成 React.createElement(...),所以每个 .tsx 文件顶部都必须有 React 的引入,否则运行时会报 React is not defined。这个值后来多了个 react-jsx,对应 React 17 引入的新 JSX 转换,产物走 react/jsx-runtime,好处正是不用再手动引 React。新项目建议直接用 react-jsx,具体版本要求以 React 官方文档为准。

  • 此外还需要注意一点,以后需要import xxx from 'xxx'这样的文件的话需要在webpack中的resolve项中配置extensions,这样以后引入文件就不需要带扩展名
module.exports = {
    resolve: {
        extensions: ['.ts', '.tsx', '.js', '.jsx']
    }
}
@前端进阶之旅: 代码已经复制到剪贴板

这个数组的顺序是有意义的,webpack 按从左到右的顺序挨个试。把 .ts 和 .tsx 放在 .js 前面,意味着同名文件优先用 TypeScript 版本,从 JS 渐进迁移的项目会很依赖这个行为。忘了配的后果是所有不带扩展名的 import 全部报模块找不到,大概率你也遇到过。

顺带说一句,上面入口文件里 import Test from '@components/Test' 引进来却没用上,那个 @components 别名要到 2.5 节才配好,这一步先跑通的话可以把这行注释掉。

  1. 添加页面模板

在public文件夹下新建文件夹tpl,然后在tpl中新建一个index.html,如下

mkdir public && cd public && touch index.html
@前端进阶之旅: 代码已经复制到剪贴板
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <div id="app"></div>
</body>
</html>
@前端进阶之旅: 代码已经复制到剪贴板

这时候有了页面模板还是不够的,还需要将页面模板和打包出来的js文件关联起来,因为考虑到以后打包出来的js的文件不会是一个固定的名称,所以这里需要使用一个webpack的插件html-webpack-plugin

fe
  • 一、项目初始化
    • 1.1 创建项目
    • 1.2 webpack配置
  • 二、提升开发体验
    • 2.1 支持sass
    • 2.2 支持css module
    • 2.3 配置公共sass属性
    • 2.4 支持装饰器
    • 2.5 优化路径
    • 2.6 构建缓存
  • 三、整理杂项
    • 3.1 整理项目结构
    • 3.2 集成antd
    • 3.3 整合常用函数
    • 3.4 集成mobx
    • 3.5 使用react-hot-loader进行热加载
    • 3.6 集成svg-component
  • 四、项目打包
    • 4.1 添加打包命令
    • 4.2 分离css文件
    • 4.3 修改html-webpack-plugin配置项
    • 4.4 代码分离和按需加载
    • 4.5 添加optimization
    • 4.6 代码压缩
    • 4.7 关于externals
  • 五、团队规范
    • 5.1 使用tslint进行代码检测
    • 5.2 使用stylelint做代码检测
    • 5.3 添加npm script进行检测
    • 5.4 使用prettier进行代码格式化
    • 5.5 使用pre-commit
  • 六、代码
  • 总结
  • 参考

← 浏览器缓存原理总结 强缓存与协商缓存的判定顺序Typescript总结篇(二) 语法全景与工程配置 →