之前做
React
項目 都是 直接使用官方提供的腳手架create-react-app
, 今天自己使用webpack
來搭建自己的腳手架, 可以在項目中使用React + TypeScript
來完成項目, 并寫出自己的UI
組件庫,React Hooks + TypeScript
風格
開始準備
- 下面是我電腦上的環(huán)境
node -v // v12.13.0
npm -v // 6.12.0
yarn -v // 1.19.1
- 編輯器
VS Code
- 命令行工具
cmder
- 遠程倉庫
github
- 瀏覽器
Chrome
如何搭建
-
我們先在
github
創(chuàng)建一個遠程倉庫, 取個名字叫ts-demo
clone
到本地, 做代碼管理git clone 你的ssh地址
-
然后初始化我們的項目, 生成
package.json
npm init (-y) or yarn init (-y)
-
安裝
webpack
-
webpack
是一個現(xiàn)代JavaScript
應用程序的靜態(tài)模塊打包器 - webapck安裝
// 使用 webpack 4+ 版本 yarn add webpack webpack-cli --dev // dev 安裝到開發(fā)者依賴
// 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
, 然后我們創(chuàng)建lib/index.tsx
, 測試我們下載的webpack
// index.tsx console.log('hi') // 測試能否編譯 tsx 文件
-
在這之前, 需要創(chuàng)建
webpack.config.js
配置文件, 配置我們需要的東西module.exports = { // 應用程序的起點入口 entry: './lib/index.tsx', }
-
但上面的配置并不能編譯
tsx
, 下面的配置如何編譯tsx
使用到
webpack loader : loader
用于對模塊的源代碼進行轉換
module.exports = { entry: './lib/index.tsx', // 模塊 module: { // 規(guī)則 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 規(guī)范 ==>在瀏覽器使用 4. Node.js 社區(qū), CMD 規(guī)范 module.exports = {} 5. UMD(統(tǒng)一的模塊定義) ==> 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
,且使用命令行參數(shù)--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函數(shù)慌核,文件必須是模塊 "strictNullChecks": true, // 不允許把null距境、undefined賦值給其他類型的變量 "esModuleInterop": true, // 允許export=導出,由import from 導入 "noUnusedLocals": true // 檢查只聲明垮卓、未使用的局部變量(只提示不報錯) }, "includes": ["lib/**/*"], "exclude": ["node_modules", "build", "dist"] }
-
-
到現(xiàn)在為止, 其實現(xiàn)在我們并沒有加多少東西, 先看看我們的配置:
安裝 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
的區(qū)別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 ==> // 執(zhí)行 npx webpack-dev-server
- 我們打開
localhost:8080
, 因為沒有html
文件, 我們可以打開dist/index.js
在頁面上, 可以看到我們的console.log('hi')
-
-
上一步已經可以開啟一個
localhost:8080
的服務器了, 不過我們還需要創(chuàng)建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文件的創(chuàng)建垫桂,以便為你的webpack包提供服務。這對于在文件名中包含每次會隨著編譯而發(fā)生變化哈希的 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" },
-
上面我們可以開啟服務了, 但還有許多細節(jié)去完善, 現(xiàn)在我們在頁面上寫
react
, 需要安裝一些yarn add react react-dom // 聲明文件 用 typescript 使用 yarn add @types/react @types/react-dom
-
我們創(chuàng)建個頁面, 測試一下當前頁面
// 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'] + }, }
- 在上面運行時, 打包會找不到
-
現(xiàn)在我們可以再頁面沒上看到
按鈕
, 現(xiàn)在把我們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' 為生產環(huán)境
, 執(zhí)行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",
},
},
}
-
現(xiàn)在我們的代碼比較全了, 但
mode
有開發(fā)環(huán)境和生產環(huán)境, 我們在不同的環(huán)境配置不同的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: { // 因為不同的操作系統(tǒng), 路徑不一樣, 所以使用 __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
- 自己網(wǎng)上搜, 嘗試
yarn add --dev jest babel-jest @babel/preset-env @babel/preset-react react-test-renderer
// 創(chuàng)建 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"]
};
// 執(zhí)行 yarn test, 根據(jù)報錯,
1. yarn add --dev ts-jest
2. test/setupTests.js
3. 創(chuàng)建對應的 **/__tests__/**/*.unit.(js|jsx|ts|tsx)
4. yarn add @types/jest
// 根據(jù)
testMatch: ["<rootDir>/**/__tests__/**/*.unit.(js|jsx|ts|tsx)"],
// 我們創(chuàng)建測試文件, 先隨便測試看看能不能成功
// 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
- 運行
跨平臺
設置和使用環(huán)境變量的腳本
yarn add --dev cross-env
- 運行
總結
上面的東西基本上夠我們使用 ts + react
開發(fā)我們的組件了, 后面需要什么我們在對應的配置, 雖然寫了很多, 但其實配置的并不多, 主要我們還是多搜索, 多嘗試, 現(xiàn)在我們看看我們有哪些目錄了
基本配置現(xiàn)在這些可以使用
React + TypeSctipt
開發(fā)了, 趕緊測試一下吧
// 安裝的依賴
"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
全解