創(chuàng)建項目
-
創(chuàng)建目錄與遠(yuǎn)程倉庫
克隆你的項目到本地
git clone 項目地址npm初始化
npm init -y
webpack初始化
- 初始化webpack-cli
yarn add webpack webpack-cli --dev
- 新建lib/index.tsx
- 新建 webpack.config.js
i. 配置entery
entry: {
index: './lib/index.tsx'
},
上面的index對應(yīng)的就是我們的文件名,我們只需要把index
改成IReact
即可
ii. 配置output
const path = require('path')
output: {
path: path.resolve(__dirname, 'dist/lib'),
library: 'IReact', //我們的庫的名字,如果不寫別人是用不了我們的庫的
libraryTarget: 'umd' //我們的庫的輸出格式,默認(rèn)寫umd
},
問題1:為什么要引入path
答:上面的__dirname
就是你當(dāng)前的項目目錄比如我的是IReact那么它就是IReact,之所以引入一個path
附迷,是因為這里需要一個絕對路徑,而對于mac和liux的用戶需要寫成__dirname + '/dist'
,在windows上你就需要寫成__dirname+ '\\dist'
竭钝,所以你不管怎么寫都是錯的忙菠,而我們直接通過path.resolve(__dirname, 'dist/lib')
,它會自動根據(jù)不同的操作系統(tǒng)給我們拼接成合適的絕對路徑肆良。
問題2:libraryTarget為什么要寫umd
答:輸出格式一般分為amd,commonjs和umd
1). 前端一開始是沒有包管理系統(tǒng)的筛璧,所有的都是通過script標(biāo)簽一個個加載到頁面的,這就存在全局變量之間互相影響惹恃,所以我們需要打包夭谤,也就是通過require.js,所有人把你需要的變量定義到一個define函數(shù)里巫糙,那么這個變量只在這個函數(shù)的作用域內(nèi)朗儒,而這個require.js的標(biāo)準(zhǔn)就是amd,也就是異步模塊定義参淹。
2). nodejs中的模塊定義是通過module.exports= {}
導(dǎo)出醉锄,你在當(dāng)前文件的module.exports外定義一個變量,那這個變量只對當(dāng)前文件有效
- 1.js
var a = 1 //a變量只在1.js中有效
module.exports = {
}
這個模塊定義就叫做commonjs
3). 不管是commonjs還是requirejs(amd)都不通用浙值,因為common.js只在nodejs中使用恳不,而amd只在瀏覽器中使用。為了統(tǒng)一定義的方式开呐,兼容其他的定義方式烟勋,umd(統(tǒng)一的模塊定義)誕生了。umd的核心是:如果存在define函數(shù)就用define函數(shù)來定義(也就是amd)负蚊,如果不存在define函數(shù)神妹,它就會查找有沒有module,如果有就說明是commonjs的方式家妆,如果還是沒有就會用script方式
iii. 配置 module.rules
a. jsx
b. tsx
{
test: /\.tsx?$/,
loader: 'awesome-typescript-loader'
}
- cli
yarn add awesome-typescript-loader --dev
c. scss
iv. 配置plugins
iiv. 配置mode
如果你正在開發(fā)就配成development
鸵荠,如果你需要發(fā)布正式環(huán)境就配成production
創(chuàng)建ts配置
- 安裝ts
yarn add typescript --dev
- 創(chuàng)建tsconfig.json
{
"compilerOptions": {
"declaration": true,
"baseUrl": ".",
"module": "esnext",
"target": "es5",
"lib": ["es6", "dom"],
"sourceMap": true,
"jsx": "react",
"moduleResolution": "node",
"rootDir": ".",
"forceConsistentCasingInFileNames": false,
"noImplicitReturns": true,
"noImplicitThis": true,
"noImplicitAny": true,
"importHelpers": true,
"strictNullChecks": true,
"allowSyntheticDefaultImports": true,
"noUnusedLocals": true
},
"include": [
],
"exclude": [
"node_modules",
"build",
"dist",
"scripts",
"acceptance-tests",
"webpack",
"jest",
"src/setupTests.ts",
"*.js"
]
}
- 創(chuàng)建tslint.json
{
"extends": ["tslint:recommended", "tslint-react"],
"rules": {
"no-console": [false, "log", "error"],
"jsx-no-multiline-js": false,
"whitespace": false,
"no-empty-interface": false,
"space-before-function-paren": false,
"no-namespace": false,
"label-position": false,
"quotemark": [true, "single", "jsx-double"],
"member-access": false,
"semicolon": [true, "always", "ignore-bound-class-methods"],
"no-unused-expression": [true, "allow-fast-null-checks"],
"member-ordering": false,
"trailing-comma": false,
"arrow-parens": false,
"jsx-self-close": false,
"max-line-length": false,
"interface-name": false,
"no-empty": false,
"comment-format": false,
"ordered-imports": false,
"object-literal-sort-keys": false,
"eofline": false,
"jsx-no-lambda": false,
"no-trailing-whitespace": false,
"jsx-alignment": false,
"jsx-wrap-multilines": false,
"no-shadowed-variable": [
false,
{
"class": true,
"enum": true,
"function": false,
"interface": false,
"namespace": true,
"typeAlias": false,
"typeParameter": false
}
]
},
"linterOptions": {
"exclude": [
"config/**/*.js",
"node_modules/**/*.ts",
"coverage/lcov-report/*.js"
]
}
}
- 在gitigonre里添加dist目錄
/dist/*
現(xiàn)在運(yùn)行npx webpack
已經(jīng)可以打包了
安裝webpack-dev-server
自動打包
通過上面的配置我們可以把我們的index.tsx
打包成index.js
,但是我們每當(dāng)有內(nèi)容修改都得重新打包伤极,所以我們需要webpack-dev-serve
來自動打包
yarn add webpack-dev-server
npx webpack-dev-server
安裝html-webpack-plugin插件蛹找,使我們可以訪問頁面
- 創(chuàng)建一個index.html
- 安裝html-webpack-plugin
- 在webpack.config.js中配置
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
plugins: [
new HtmlWebpackPlugin({
template: 'index.html'
})
]
}
添加package.json配置
"start": "webpack-dev-server",
"build": "webpack"
創(chuàng)建一個簡單的button組件
- 在lib目錄下新建button.tsx
function Button() {
return (
<div>
按鈕
</div>
)
}
export default Button
- 在index.tsx中引入我們的組件
1). 安裝react和react-dom
yarn add react react-dom
import React from 'react';
import ReactDom from 'react-dom';
2). 安裝react和react-dom的聲明文件
yarn add @types/react @types/react-dom --dev
3). 引入Button
import Button from './button'
ReactDom.render(<Button />, document.querySelector('#root'));
這時候我們會看到這么兩個報錯
i. 它找不到我們的./button文件;
解決方法:在webapck配置文件中添加下面的代碼
resolve: {
extensions: ['.ts', '.tsx', '.js', '.jsx']
},
ii. 我們在button里定義了React但是沒有引入
- button.tsx
import React from 'react'
function Button() {
return (
<div>
按鈕
</div>
)
}
export default Button
上面的代碼我們看上去并沒使用React哨坪,那是因為我們寫的其實(shí)是React的語法糖就等價于
function Button() {
return (
React.createElement('div', null, 'hi')
)
}
相關(guān)補(bǔ)充:
yarn.lock文件是做什么的庸疾?
- 指引;是yarn的lock文件
- 它lock(鎖)的是所有依賴的版本號
減小我們打包后index.js的大小
正常情況下我們打包會把我們依賴的庫react和react-dom都打包進(jìn)去当编,所以就會讓我們的js很大届慈,如果我們不想把它們打包進(jìn)去,只需要在webpack里配置一個externals
(外部的包)
externals: {
react: {
commonjs: 'react',
commonjs2: 'react',
amd: 'react',
root: 'React',
},
'react-dom': {
commonjs: 'react-dom',
commonjs2: 'react-dom',
amd: 'react-dom',
root: 'ReactDOM'
}
}
mode中devlopment和production的區(qū)別
- 如果是development它的代碼不會壓縮,production就會壓縮你所有的代碼
區(qū)分我們的production和devlepment模式
在webpack.config的基礎(chǔ)上新建一個webpack.config.dev和webpack.config.prod金顿,其中
webpack.config里的代碼是生產(chǎn)和開發(fā)都需要用到的共同的代碼, 開發(fā)環(huán)境不需要代碼壓縮臊泌,所以不需要把react他們拆出來,生產(chǎn)環(huán)境也就是我們打包的時候是不需要生成一個html文件的揍拆,所以生產(chǎn)的不需要加HtmlWebpackPlugin渠概,最后我們只需要在共有的代碼基礎(chǔ)上添加新的代碼就行,也就是在一個新的對象上添加共有代碼和自己的代碼嫂拴。
- webpack.config.js
const path = require('path')
module.exports = {
entry: {
index: './lib/index.tsx'
},
resolve: {
extensions: ['.ts', '.tsx', '.js', '.jsx']
},
output: {
path: path.resolve(__dirname, 'dist/lib'),
library: 'IReact',
libraryTarget: 'umd'
},
module: {
rules: [
{
test: /\.tsx?$/,
loader: 'awesome-typescript-loader'
}
]
}
}
- webpack.config.dev.js
const path = require('path')
const base = require('./webpack.config')
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = Object.assign({}, base, {
mode: 'development',
plugins: [
new HtmlWebpackPlugin({
template: 'index.html'
})
]
})
- webpack.config.prod.js
const path = require('path')
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-dom',
commonjs2: 'react-dom',
amd: 'react-dom',
root: 'ReactDOM'
}
}
})
安裝cross-env
來保證我們的環(huán)境變量在各個平臺上都可以成功的運(yùn)行
- package.json
"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"
配置我們的類型聲明文件
將我們的.d.ts的類型聲明文件生成的出口改成dist
- tsconfig.json
"compilerOptions": {
"outDir": "dist"
}
這樣我們打包后就會在dist目錄下生成.d.ts對應(yīng)的文件
在package.json里添加我們類型聲明文件使用的文件播揪,以及修改我們的main文件為dist目錄下的
"main": "dist/lib/index.js",
"types": "dist/lib/index",
配置Jest單元測試
- 安裝
yarn add --dev jest babel-jest @babel/preset-env @babel/preset-react react-test-renderer
- 創(chuàng)建.babelrc
{
"presets": ["react-app"]
}
- 在package.json里配置測試命令
"test": "cross-env NODE_ENV=test jest --config=jest.config.js --runInBand",
- 創(chuàng)建jest.config.js
// https://jestjs.io/docs/en/configuration.html
module.exports = {
verbose: true,
clearMocks: false,
collectCoverage: false,
reporters: ["default"],
moduleFileExtensions: ['js', 'jsx', 'ts', 'tsx'],
moduleDirectories: ['node_modules'],
globals: {
'ts-jest': {
tsConfig: 'tsconfig.test.json',
},
},
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",
},
testMatch: ['<rootDir>/**/__tests__/**/*.unit.(js|jsx|ts|tsx)'],
transform: {
"^.+unit\\.(js|jsx)$": "babel-jest",
'^.+\\.(ts|tsx)$': 'ts-jest',
},
setupFilesAfterEnv: ["<rootDir>test/setupTests.js"]
}
- 安裝ts-jest
yarn add ts-jest --dev
- 創(chuàng)建test/setupTests.js,我們可以先什么也不寫
- 運(yùn)行yarn test
看到上面圖片中的代碼就說明你成功了
- 開始寫我們的單元測試
testMatch: ['<rootDir>//tests//*.unit.(js|jsx|ts|tsx)'],
我們的jest.config.js中的上面的testMatch就是測試的時候匹配的文件目錄筒狠,所以我們需要在我們的lib目錄下創(chuàng)建一個tests目錄猪狈,下面的文件以.unit.(js|jsx|ts|tsx)來命名
- tests/hello.unit.tsx
function add(a: number, b:number): number {
return a + b
}
describe('add(1,2)', () => {
it('等于3', () => {
expect(add(1,2)).toEqual(3)
})
})
運(yùn)行yarn test
會發(fā)現(xiàn)報錯說tsconfig.test.json是沒有發(fā)現(xiàn)的
- 創(chuàng)建tsconfig.test.json
{
"extends": "./tsconfig.json",
// "compilerOptions": {
// "module": "commonjs"
// }
}
這樣我們的單元測試就可以成功的運(yùn)行了
針對button的單元測試
- button.unti.tsx
import React from 'react'
import renderer from 'react-test-renderer'
import Button from '../button'
describe('button', () => {
it('是個div', () => {
//渲染一個Button,因為Button是一個對象所以我們可以把它轉(zhuǎn)成json
const json = renderer.create(<Button/>).toJSON()
//期待它去匹配Snapshot
expect(json).toMatchSnapshot()
})
})
運(yùn)行yarn test報錯窟蓝,說我們的renderer是undefined
原因:我們沒有默認(rèn)導(dǎo)出
解決方法:
- import后加* as
- 修改
tsconfig
里的allowSyntheticDefaultImports
為esModuleInterop