之前做
React
项目 都是 直接使用官方提供的脚手架create-react-app
, 今天自己使用webpack
来搭建自己的脚手架, 可以在项目中使用React + TypeScript
来完成项目, 并写出自己的UI
组件库,React Hooks + TypeScript
风格
开始准备
- 下面是我电脑上的环境
node -v // v12.13.0
npm -v // 6.12.0
yarn -v // 1.19.1
- 编辑器
VS Code
- 命令行工具
cmder
- 远程仓库
github
- 浏览器
Chrome
如何搭建
-
我们先在
github
创建一个远程仓库, 取个名字叫ts-demo
clone
到本地, 做代码管理git clone 你的ssh地址
-
然后初始化我们的项目, 生成
package.json
npm init (-y) or yarn init (-y)
-
安装
webpack
-
webpack
是一个现代JavaScript
应用程序的静态模块打包器 - webapck安装
// 使用 webpack 4+ 版本 yarn add webpack webpack-cli --dev // dev 安装到开发者依赖
// package.json { "name": "ts-demo", "version": "1.0.0", "main": "index.js", "license": "MIT", "devDependencies": { "webpack": "^4.43.0", "webpack-cli": "^3.3.11" } }
-
-
安装好了
webpack
, 然后我们创建lib/index.tsx
, 测试我们下载的webpack
// index.tsx console.log('hi') // 测试能否编译 tsx 文件
-
在这之前, 需要创建
webpack.config.js
配置文件, 配置我们需要的东西module.exports = { // 应用程序的起点入口 entry: './lib/index.tsx', }
-
但上面的配置并不能编译
tsx
, 下面的配置如何编译tsx
使用到
webpack loader : loader
用于对模块的源代码进行转换
module.exports = { entry: './lib/index.tsx', // 模块 module: { // 规则 rules: [ { test: /\.tsx?$/, loader: 'awesome-typescript-loader' } ] } }
// 使用到 ts 的 loader 加载器, 需要安装 // 也可以使用 ts-loader, 二选一 yarn add awesome-typescript-loader --dev or yarn add ts-loader --dev
-
配置输出路径, 打包后, 管理我们的输出
1. 前端没有 包管理 工具 2. 有了 require.js, define(fn) 3. AMD 规范 ==>在浏览器使用 4. Node.js 社区, CMD 规范 module.exports = {} 5. UMD(统一的模块定义) ==> if AMD else CMD const path = require('path') module.exports = { entry: './lib/index.tsx', // 输出 output: { path: path.resolve(__dirname, 'dist'), library: 'yui', libraryTarget: 'umd' }, module: { rules: [ { test: /\.tsx?$/, loader: 'awesome-typescript-loader' } ] } }
-
-
因为我们使用到
.tsx
是ts
文件, 安装typescript
yarn add typescript --dev
-
设置
tsconfig.json
, tsconfig.json 配置含义-
tsconfig.json
文件中指定了用来编译这个项目的根文件和编译选项- 不带任何输入文件的情况下调用
tsc
,编译器会从当前目录开始去查找tsconfig.json
文件,逐级向上搜索父目录。 - 不带任何输入文件的情况下调用
tsc
,且使用命令行参数--project
(或-p
)指定一个包含tsconfig.json
文件的目录。
- 不带任何输入文件的情况下调用
// tsconfig.json 配置 { "compilerOptions": { "outDir": "dist", // 指定输出目录 "declaration": true, // 生成声明文件,开启后会自动生成声明文件 "baseUrl": "", // 解析非相对模块的基地址,默认是当前目录 "module": "esnext", // 生成代码的模板标准 "target": "es5", // 目标语言的版本 "lib": ["es6", "dom"], // TS需要引用的库,即声明文件,es5 默认引用dom、es5、scripthost,如需要使用es的高级版本特性,通常都需要配置 "sourceMap": true, // 生成目标文件的sourceMap文件 "jsx": "react", "moduleResolution": "node", // 模块解析策略,ts默认用node的解析策略,即相对的方式导入 "rootDir": ".", "noImplicitReturns": true, //每个分支都会有返回值 "noImplicitThis": true, // 不允许this有隐式的any类型 "noImplicitAny": true, // 不允许隐式的any类型 "importHelpers": true, // 通过tslib引入helper函数,文件必须是模块 "strictNullChecks": true, // 不允许把null、undefined赋值给其他类型的变量 "esModuleInterop": true, // 允许export=导出,由import from 导入 "noUnusedLocals": true // 检查只声明、未使用的局部变量(只提示不报错) }, "includes": ["lib/**/*"], "exclude": ["node_modules", "build", "dist"] }
-
-
到现在为止, 其实现在我们并没有加多少东西, 先看看我们的配置:
安装 webpack webpack-cl typescript awesome-typescript-loader
配置 webpack.config.js tsconfig.json lib/index.tsx
// 自行解决 各种报错 npx webpack // 编译 可以打包 dist了
- 会看到生成一个
dist
目录,dist/index.js
, 被webpack编译
-
我们先简单解释一下安装时
--save-dev --save
的区别npm install --save-dev // 只给程序员用 + dev npm install --save // 用户也用, 默认 --save 简写 -S -dev 简写 -D yarn add yarn add --dev
-
安装
webpack-dev-server
-
webpack-dev-server
主要是启动了一个使用express
的Http
服务器
npm install --save-dev webpack-dev-server or yarn add webpack-dev-server --dev ==> // 执行 npx webpack-dev-server
- 我们打开
localhost:8080
, 因为没有html
文件, 我们可以打开dist/index.js
在页面上, 可以看到我们的console.log('hi')
-
-
上一步已经可以开启一个
localhost:8080
的服务器了, 不过我们还需要创建html
<!-- index.html --> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <!-- webpack 配置 title --> <title><%= htmlWebpackPlugin.options.title %></title> </head> <body></body> </html> // 我们做到的不是自己插入 js 路径, 而是 webpack 自动添加
-
引入插件
HtmlWebpackPlugin
-
HtmlWebpackPlugin
简化了HTML文件的创建,以便为你的webpack包提供服务。这对于在文件名中包含每次会随着编译而发生变化哈希的 webpack bundle 尤其有用。 你可以让插件为你生成一个HTML文件
// 补充一下 webpack 配置 const path = require('path') const HtmlWebpackPlugin = require('html-webpack-plugin'); module.exports = { entry: './lib/index.tsx', output: { path: path.resolve(__dirname, 'dist'), library: 'yui', libraryTarget: 'umd' }, module: { rules: [ { test: /\.tsx?$/, loader: 'awesome-typescript-loader' } ] }, // 插件 plugins: [ new HtmlWebpackPlugin({ title: 'Hello Webpack' template: 'index.html' }) ] }
// lib/index.tsx const div = document.createElement("div"); div.innerText = "Hello World"; document.body.appendChild(div);
-
-
好了, 虽然写了很多, 但我们没有做多少东西, 我们可以打包, 可以开启服务了, 可以在
浏览器看到 Hello World
npx webpack npx webpack-dev-server // 在 package.json 里 "scripts": { "start": "webpack-dev-server", "build": "webpack" },
-
上面我们可以开启服务了, 但还有许多细节去完善, 现在我们在页面上写
react
, 需要安装一些yarn add react react-dom // 声明文件 用 typescript 使用 yarn add @types/react @types/react-dom
-
我们创建个页面, 测试一下当前页面
// lib/button.tsx import React from 'react' const Button = () => { return <div>按钮</div> } // lib/index.tsx import React from 'react' import ReactDOM from 'react-dom'; import Button from "./button" ReactDOM.render(<Button></Button>, document.body)
- 在上面运行时, 打包会找不到
.tsx
结尾的文件
// webpack.config.tsx module.exports = { // 加载项配置 + resolve: { + extensions: ['.js', '.jsx', '.ts', '.tsx'] + }, }
- 在上面运行时, 打包会找不到
-
现在我们可以再页面没上看到
按钮
, 现在把我们webpack.config.js
的代码全部看一下const path = require("path") const HtmlWebpackPlugin = require("html-webpack-plugin") module.exports = { mode: "production", entry: { index: "./lib/index.tsx", }, output: { path: path.resolve(__dirname, "dist"), library: "yui", libraryTarget: "umd", }, resolve: { extensions: [".js", ".jsx", ".ts", ".tsx"], }, module: { rules: [ { test: /\.tsx?$/, loader: "awesome-typescript-loader", }, ], }, plugins: [ new HtmlWebpackPlugin({ title: "Webpack", template: "index.html", }), ], }
- 当
mode: 'production' 为生产环境
, 执行npm start
, 这时会有一个warning
- 当
- `Webpack` 可以配置` externals` 来将依赖的库指向全局变量,从而不再打包这个库
- `externals` 配置选项提供了「从输出的 bundle 中排除依赖」的方法
- [externals](https://webpack.docschina.org/configuration/externals/)
const path = require("path")
const HtmlWebpackPlugin = require("html-webpack-plugin")
module.exports = {
mode: "production",
entry: {
index: "./lib/index.tsx",
},
output: {
path: path.resolve(__dirname, "dist"),
library: "yui",
libraryTarget: "umd",
},
resolve: {
extensions: [".js", ".jsx", ".ts", ".tsx"],
},
module: {
rules: [
{
test: /\.tsx?$/,
loader: "awesome-typescript-loader",
},
],
},
plugins: [
new HtmlWebpackPlugin({
title: "Webpack",
template: "index.html",
}),
],
// 不属于内部的库, 外部的
externals: {
react: {
commonjs: "react",
commonjs2: "react",
amd: "react",
root: "React",
},
"react-dom": {
commonjs: "react",
commonjs2: "react",
amd: "react",
root: "React",
},
},
}
-
现在我们的代码比较全了, 但
mode
有开发环境和生产环境, 我们在不同的环境配置不同的webpack
, 把之前的一个文件拆成三个文件// webpack.config.js const path = require("path"); module.exports = { // 1. 影响提示, 2. 文件大小 development/production // mode: "production", // 入口是 tsx, 但程序不认识 jsx, 配置 rules entry: { index: "./lib/index.tsx" }, // 输出目录 output: { // 因为不同的操作系统, 路径不一样, 所以使用 __dirname, 当前路径 path: path.resolve(__dirname, "dist/lib"), // 库的name library: "YUI", // 库最后导出的格式 libraryTarget: "umd" }, // 配置 import 引入 resolve: { extensions: [".ts", ".tsx", ".js", ".jsx"] }, module: { rules: [ // 配置 ts tsx { test: /\.tsx?$/, loader: "awesome-typescript-loader" }, ] } };
// webpack.config.dev.js const base = require('./webpack.config'); const HtmlWebpackPlugin = require('html-webpack-plugin'); module.exports = Object.assign({}, base, { mode: 'development', plugins: [ new HtmlWebpackPlugin({ title: 'YUI', template: 'index.html', }), ], });
// webpack.config.prod.js const base = require('./webpack.config') module.exports = Object.assign({}, base, { mode: "production", // 不属于内部的库, 外部的 externals: { react: { commonjs: 'react', commonjs2: 'react', amd: 'react', root: 'React' }, 'react-dom': { commonjs: 'react', commonjs2: 'react', amd: 'react', root: 'React' } } });
// 修改我们 package.json 里面 script 的配置 "scripts": { "start": "webpack-dev-server --config webpack.config.dev.js", "build": "webpack --config webpack.config.prod.js", },
配置测试
Jest
, 因为我们不使用create react app
, 这个比较麻烦, 自己搜着尝试啊
- [Jest文档](https://jestjs.io/docs/zh-Hans/tutorial-react
- 自己网上搜, 尝试
yarn add --dev jest babel-jest @babel/preset-env @babel/preset-react react-test-renderer
// 创建 babel.config.js ==> .babelrc
module.exports = {
presets: ['@babel/preset-env', '@babel/preset-react'],
};
// 1. 添加命令 test
"test": "jest --config=jest.config.js",
// 2. 添加 jest.config.js
// https://jestjs.io/docs/en/configuration.html
module.exports = {
verbose: true,
clearMocks: false,
collectCoverage: true,
// 测试那些, 不测试哪些
collectCoverageFrom: ["lib/**/*.{js,jsx,ts,tsx}", "!**/node_modules/**"],
// 生成的报告放在那里
coverageDirectory: "coverage",
moduleFileExtensions: ["js", "jsx", "ts", "tsx"],
moduleDirectories: ["node_modules"],
moduleNameMapper: {
"\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$":
"<rootDir>/test/__mocks__/file-mock.js",
"\\.(css|less|sass|scss)$": "<rootDir>/test/__mocks__/object-mock.js"
},
// 测试文件在哪里
testMatch: ["<rootDir>/**/__tests__/**/*.unit.(js|jsx|ts|tsx)"],
transform: {
"^.+unit\\.(js|jsx)$": "babel-jest",
"^.+\\.(ts|tsx)$": "ts-jest"
},
setupFilesAfterEnv: ["<rootDir>test/setupTests.js"]
};
// 执行 yarn test, 根据报错,
1. yarn add --dev ts-jest
2. test/setupTests.js
3. 创建对应的 **/__tests__/**/*.unit.(js|jsx|ts|tsx)
4. yarn add @types/jest
// 根据
testMatch: ["<rootDir>/**/__tests__/**/*.unit.(js|jsx|ts|tsx)"],
// 我们创建测试文件, 先随便测试看看能不能成功
// lib/__tests__/hello.unit.tsx
describe('Test', () => {
it('Hello', () => {
expect(1).toEqual(2) // 报错
});
});
// 因为我们使用的是 .tsx, 提示安装 @types/jest yarn add @types/jest --dev
// 上面的示例比较简单, 我们来一个正确的
// lib/__tests__/hello.unit.tsx
function sum(a: number, b: number): number {
return a + b
}
describe('Test', () => {
it('sum', () => {
expect(sum(1, 2)).toEqual(3)
});
});
好的, 我们的测试可以成功了, 先这样, 后面我们写组件时, 再具体的问题具体完善
- 最后, 我们完善一个
script
命令问题
- 使用
cross-env
- 运行
跨平台
设置和使用环境变量的脚本
yarn add --dev cross-env
- 运行
总结
上面的东西基本上够我们使用 ts + react
开发我们的组件了, 后面需要什么我们在对应的配置, 虽然写了很多, 但其实配置的并不多, 主要我们还是多搜索, 多尝试, 现在我们看看我们有哪些目录了
基本配置现在这些可以使用
React + TypeSctipt
开发了, 赶紧测试一下吧
// 安装的依赖
"devDependencies": {
"@babel/preset-env": "^7.7.7",
"@babel/preset-react": "^7.7.4",
"awesome-typescript-loader": "^5.2.1",
"babel-jest": "^24.9.0",
"html-webpack-plugin": "^3.2.0",
"jest": "^24.9.0",
"react-test-renderer": "^16.12.0",
"ts-jest": "^24.2.0",
"typescript": "^3.7.4",
"webpack": "^4.41.4",
"webpack-cli": "^3.3.10",
"webpack-dev-server": "^3.10.1"
},
"dependencies": {
"@types/jest": "^24.0.24",
"@types/react": "^16.9.17",
"@types/react-dom": "^16.9.4",
"react": "^16.12.0",
"react-dom": "^16.12.0"
}
下期文章
-
react hook
全解