webpack4+react+ts項目搭建

創(chuàng)建項目

  1. 創(chuàng)建目錄與遠(yuǎn)程倉庫


  2. 克隆你的項目到本地
    git clone 項目地址

  3. npm初始化

npm init -y

webpack初始化

  1. 初始化webpack-cli
yarn add webpack webpack-cli --dev
  1. 新建lib/index.tsx
  2. 新建 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配置

  1. 安裝ts
yarn add typescript --dev 
  1. 創(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"
    ]
}
  1. 創(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"
    ]
  }
}
  1. 在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插件蛹找,使我們可以訪問頁面

  1. 創(chuàng)建一個index.html
  2. 安裝html-webpack-plugin
  3. 在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組件

  1. 在lib目錄下新建button.tsx
function Button() {
    return (
        <div>
            按鈕
        </div>
    )
}
export default Button
  1. 在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文件是做什么的庸疾?

  1. 指引;是yarn的lock文件
  2. 它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ū)別

  1. 如果是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單元測試

  1. 安裝
yarn add --dev jest babel-jest @babel/preset-env @babel/preset-react react-test-renderer
  1. 創(chuàng)建.babelrc
{
    "presets": ["react-app"]
}
  1. 在package.json里配置測試命令
"test": "cross-env NODE_ENV=test jest --config=jest.config.js --runInBand",
  1. 創(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"]
  }
  1. 安裝ts-jest
yarn add ts-jest --dev
  1. 創(chuàng)建test/setupTests.js,我們可以先什么也不寫
  2. 運(yùn)行yarn test

看到上面圖片中的代碼就說明你成功了

  1. 開始寫我們的單元測試
    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)的

  1. 創(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)出
解決方法:

  1. import后加* as
  2. 修改tsconfig里的allowSyntheticDefaultImportsesModuleInterop
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末罪裹,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子运挫,更是在濱河造成了極大的恐慌状共,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,402評論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件谁帕,死亡現(xiàn)場離奇詭異峡继,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)匈挖,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評論 3 392
  • 文/潘曉璐 我一進(jìn)店門碾牌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人儡循,你說我怎么就攤上這事舶吗。” “怎么了择膝?”我有些...
    開封第一講書人閱讀 162,483評論 0 353
  • 文/不壞的土叔 我叫張陵誓琼,是天一觀的道長。 經(jīng)常有香客問我肴捉,道長腹侣,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,165評論 1 292
  • 正文 為了忘掉前任齿穗,我火速辦了婚禮傲隶,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘窃页。我一直安慰自己跺株,他們只是感情好复濒,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著帖鸦,像睡著了一般芝薇。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上作儿,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天,我揣著相機(jī)與錄音馋劈,去河邊找鬼攻锰。 笑死,一個胖子當(dāng)著我的面吹牛妓雾,可吹牛的內(nèi)容都是我干的娶吞。 我是一名探鬼主播,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼械姻,長吁一口氣:“原來是場噩夢啊……” “哼妒蛇!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起楷拳,我...
    開封第一講書人閱讀 38,896評論 0 274
  • 序言:老撾萬榮一對情侶失蹤绣夺,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后欢揖,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體陶耍,經(jīng)...
    沈念sama閱讀 45,311評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,536評論 2 332
  • 正文 我和宋清朗相戀三年她混,在試婚紗的時候發(fā)現(xiàn)自己被綠了烈钞。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,696評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡坤按,死狀恐怖毯欣,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情臭脓,我是刑警寧澤酗钞,帶...
    沈念sama閱讀 35,413評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站谢鹊,受9級特大地震影響算吩,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜佃扼,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,008評論 3 325
  • 文/蒙蒙 一偎巢、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧兼耀,春花似錦压昼、人聲如沸求冷。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽匠题。三九已至,卻和暖如春但金,著一層夾襖步出監(jiān)牢的瞬間韭山,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評論 1 269
  • 我被黑心中介騙來泰國打工冷溃, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留钱磅,地道東北人。 一個月前我還...
    沈念sama閱讀 47,698評論 2 368
  • 正文 我出身青樓似枕,卻偏偏與公主長得像盖淡,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子凿歼,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,592評論 2 353