从零开始使用 webpack 搭建 typescript + react 项目
为了熟悉 webpack 相关工具链,考虑跟随官方文档,从零开始去创建一个 typescript + react 的项目以作为实践,其中尽量为所有配置项的配置和意义都给予描述,专注所以然而非其然。这里想达到下面的目标:
- 最少的配置项,所有配置项都明确描述,尽量依赖默认配置项
- 支持 typescript,tsx,集成 react
- 提供和 create-react-app 一样的体验
啥是 webpack
简单来说,webpack 是一个静态的打包器,webpack 从特定的 js 文件(称为 entry)开始去构建对应的依赖树,其中包括该 js 文件依赖(import)的文本,css,其它 js 文件等,每个依赖树或 entry 都会打成一个包 bundle,也称为 chunk。
每个可以 import 的文件称为 module。
项目创建
首先创建一个新文件,执行npm init
,一路 Enter,创建一个默认的 package.json 文件,然后安装相应依赖。
1 |
|
编辑 package.json,移除 main 属性,添加 private 属性为 true。
然后初始化 tscconfig.json:
1 |
|
first step
先研究一下 webpack 的基础用法,考虑创建文件夹 src,创建两个 js 文件 index.js,Util.js:
1 |
|
这时的项目结构:
1 |
|
然后执行npx webpack
命令,能够发现它生成了dist/main.js
,其中内容为:
1 |
|
显然,webpack 将 index.js 和 Util.js 打包成了 main.js 文件,这就是 webpack 的默认行为——以src/index.js
为 entry,输出到dist/main.js
。因为 index.js 引入了 Util.js,因此 Util.js 被一并打包(并进行了优化)。这时候我们就可以在 dist 目录下添加 index.html 并引入 main.js 以查看效果。
但对其它文件呢?比如 jsx 文件?考虑编写一个简单的 jsx 文件并将其 import:
1 |
|
再执行npx webpack
,webpack 会试图找 js,json,wasm 后缀的 Hello 文件,找不到后抱怨Field 'browser' doesn't contain a valid alias configuration
,这是 webpack 的默认配置,而这里显然需要自定义配置。
创建配置文件
在项目根目录(package.json 同级目录)下创建文件 webpack.config.ts(也可以是 js,但这里利用上 ts 的类型机制;webpack 默认不支持 ts,需要安装 ts-loader,ts-node 依赖),这个文件将是 webpack 的配置文件。
1 |
|
配置 ts 支持
webpack 加载文件是通过 loader 去进行的,特定的 loader 加载特定类型的文件(毕竟要能够打包到 js 文件中,因此必须要将这些文件变成 js 代码格式),比如 raw-loader 加载文本文件,ts-loader 加载 typescript 文件……
在配置中的 module.rule 项便可以配置 loader 以及相应的后缀,这里配置 tsx 和 ts:
1 |
|
配置了这项后,便可以去直接导入 ts 和 tsx 文件了,index 文件也可以改成 ts。tsx 文件需要在 tsconfig.json 中添加一项配置(毕竟 typescript 需要知道要的究竟是哪个 tsx 实现):
1 |
|
配置 sourceMap
如果仅做了上面的配置,当代码中出现未捕获的异常中,控制台中无法看到异常出现的位置,这在开发中是无法容忍的,这将通过配置sourceMap来解决:
1 |
|
配置绝对路径导入
使用相对路径去 import 可读性不好,且重构的时候也不方便,实现一个绝对路径的导入是比较重要的。
绝对路径的配置需要配置两个部分——webpack 和 tsconfig,前者是为了让 webpack 去确定路径,后者为了让 ts 编译器确定路径。这里我们想让@
去代替 src 目录,因此我们可以使用形如@/util
的形式去导入。
对于 webpack,添加 resolve.alias 配置:
1 |
|
对于 tsconfig,添加下面的配置:
1 |
|
这里的 baseUrl 似乎是默认配置,但不给定这个配置的话 vscode 不会补全路径
既让 webpack 满意,也能让 tsc 满意,目的已经达到了。
需注意,tsc 编译时不会去转换 paths,因此使用 ts-node 去运行,或者使用 tsc 编译后使用 node 去运行时会报错,这在 webpack 环境下不是问题,但倘若将服务端的代码也放在这就会有问题了,解决方案是使用 tsconfig-paths,这里先不研究这个。
编写 react 示例
好玩的地方来了,在上面的基础上创建一个 react 示例。
首先需要在 dist 下去创建一个文件 index.html,编辑其 body,添加一个根元素并引入 main.js:
1 |
|
然后编写 index.tsx 和 App.tsx:
1 |
|
执行npx webpack
,在 dist 目录下执行http-server
,访问127.0.0.1:8080
,bingo!
快结束了,但这里仍有几个问题:
- 每次修改源代码都需要重新编译,费时费力
- 可能有缓存问题(即 main.js 更新,但浏览器仍旧用的旧的 main.js)
- css 等重要的资源未提供通过 js 的导入方式
- 未提供方便命令去进行编译,打包等操作
配置 dev server 和 http-plugin
解决问题 1,2 需要一个合适的热更新机制,webpack 提供了相应机制,即 webpack-dev-server,其会监测源代码的改变并进行热更新。
devServer 在 webpack 配置文件中配置,只需要添加下面的配置即可:
1 |
|
配置此之后,执行npx webpack serve
便可启动 devServer。
然后是解决缓存问题,这里配置输出 bundle 文件名为[name].[chunkhash].js
,配置一个插件 HtmlWebpackPlugin,该插件会自动生成 html 文件,其中会自动引入相应 script 标签:
1 |
|
这里因为要创建一个根 div,且保留原有的 header,因此使用一个模板 html,内容为之前的 html 移除 script 标签的内容。此时启动服务器,查看网页的 header,能看到 js 文件被自动引入了,且原有的 header,body 被保留了。
导入 css 文件
现在我们希望能够导入 css 文件,但又不想去手动编辑 html 文件,如何去操作呢?当然仍旧是使用 loader 去实现:
1 |
|
需要注意的是,cssloader 需要两个 loader,其中执行顺序为从后到前,即先执行 css-loader,再执行 style-loader。css-loader 的作用是处理 css 文件中的 import,style-loader 则是添加这样的 js 代码,即将 css 文件的内容在运行时添加到 header。
单这样问题就来了——css 文件全放到 js 里会大大增加 js 文件的大小,有什么方法可以规避的?可以使用 MiniCssExtractPlugin 这个插件使在编译时将文件复制到目标目录;配置 MiniCssExtractPlugin 需要配置 plugins 以及相应 loader:
1 |
|
导入图片资源
导入 css 很容易,但若是想导入如图片等资源呢?我们可能会在配置文件中加入相应 loader,然后这样使用图片资源:
1 |
|
但在 typescript 里,这样会有一个问题——tsc 不知道从这图片文件中究竟导出了什么玩意,这需要我们对 tsc 进行配置,方式是在源代码目录中添加一个 d.ts 文件,并进行相关定义:
1 |
|
然后在配置文件中添加下面的规则,这里使用了 webpack 5 对静态资源的处理方式 asset module,不需要额外的 loader,原始方法是使用 url-loader 或 file-loader:
1 |
|
配置完毕后,typescript 就能正确识别导出的类型了。
添加启动命令
每次执行都输入一堆东西很麻烦?考虑在 package.json 中添加相应命令:
1 |
|
这里只是介绍最简单的使用,各种优化,开发和生产配置分离,loader 的配置,plugin,webpack 的整个生命周期,测试……有需要再去学习。之后也考虑在这篇笔记的基础上搭建 MERN 环境。
上述代码见 此。
参考资料
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 协议 ,转载请注明出处!