webpack4+react+ts项目搭建

创建项目

  1. 创建目录与远程仓库


  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对应的就是我们的文件名,我们只需要把index改成IReact即可
ii. 配置output

const path = require('path')
output: {
    path: path.resolve(__dirname, 'dist/lib'),
    library: 'IReact', //我们的库的名字,如果不写别人是用不了我们的库的
    libraryTarget: 'umd' //我们的库的输出格式,默认写umd
},

问题1:为什么要引入path
答:上面的__dirname就是你当前的项目目录比如我的是IReact那么它就是IReact,之所以引入一个path,是因为这里需要一个绝对路径,而对于mac和liux的用户需要写成__dirname + '/dist',在windows上你就需要写成__dirname+ '\\dist',所以你不管怎么写都是错的,而我们直接通过path.resolve(__dirname, 'dist/lib'),它会自动根据不同的操作系统给我们拼接成合适的绝对路径。
问题2:libraryTarget为什么要写umd
答:输出格式一般分为amd,commonjs和umd
1). 前端一开始是没有包管理系统的,所有的都是通过script标签一个个加载到页面的,这就存在全局变量之间互相影响,所以我们需要打包,也就是通过require.js,所有人把你需要的变量定义到一个define函数里,那么这个变量只在这个函数的作用域内,而这个require.js的标准就是amd,也就是异步模块定义。
2). nodejs中的模块定义是通过module.exports= {}导出,你在当前文件的module.exports外定义一个变量,那这个变量只对当前文件有效

  • 1.js
var a = 1 //a变量只在1.js中有效
module.exports = {
}

这个模块定义就叫做commonjs
3). 不管是commonjs还是requirejs(amd)都不通用,因为common.js只在nodejs中使用,而amd只在浏览器中使用。为了统一定义的方式,兼容其他的定义方式,umd(统一的模块定义)诞生了。umd的核心是:如果存在define函数就用define函数来定义(也就是amd),如果不存在define函数,它就会查找有没有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
如果你正在开发就配成development,如果你需要发布正式环境就配成production

创建ts配置

  1. 安装ts
yarn add typescript --dev 
  1. 创建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. 创建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/*

现在运行npx webpack已经可以打包了

安装webpack-dev-server自动打包

通过上面的配置我们可以把我们的index.tsx打包成index.js,但是我们每当有内容修改都得重新打包,所以我们需要webpack-dev-serve来自动打包

yarn add webpack-dev-server
npx webpack-dev-server

安装html-webpack-plugin插件,使我们可以访问页面

  1. 创建一个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"

创建一个简单的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,那是因为我们写的其实是React的语法糖就等价于

function Button() {
    return (
       React.createElement('div', null, 'hi')
    )
}

相关补充:
yarn.lock文件是做什么的?

  1. 指引;是yarn的lock文件
  2. 它lock(锁)的是所有依赖的版本号

减小我们打包后index.js的大小

正常情况下我们打包会把我们依赖的库react和react-dom都打包进去,所以就会让我们的js很大,如果我们不想把它们打包进去,只需要在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的区别

  1. 如果是development它的代码不会压缩,production就会压缩你所有的代码

区分我们的production和devlepment模式

在webpack.config的基础上新建一个webpack.config.dev和webpack.config.prod,其中
webpack.config里的代码是生产和开发都需要用到的共同的代码, 开发环境不需要代码压缩,所以不需要把react他们拆出来,生产环境也就是我们打包的时候是不需要生成一个html文件的,所以生产的不需要加HtmlWebpackPlugin,最后我们只需要在共有的代码基础上添加新的代码就行,也就是在一个新的对象上添加共有代码和自己的代码。

  • 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来保证我们的环境变量在各个平台上都可以成功的运行

  • 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对应的文件

在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. 创建.babelrc
{
    "presets": ["react-app"]
}
  1. 在package.json里配置测试命令
"test": "cross-env NODE_ENV=test jest --config=jest.config.js --runInBand",
  1. 创建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. 创建test/setupTests.js,我们可以先什么也不写
  2. 运行yarn test

看到上面图片中的代码就说明你成功了

  1. 开始写我们的单元测试
    testMatch: ['<rootDir>//tests//*.unit.(js|jsx|ts|tsx)'],
    我们的jest.config.js中的上面的testMatch就是测试的时候匹配的文件目录,所以我们需要在我们的lib目录下创建一个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)
    })
})

运行yarn test
会发现报错说tsconfig.test.json是没有发现的

  1. 创建tsconfig.test.json
{
    "extends": "./tsconfig.json",
  //   "compilerOptions": {
  //     "module": "commonjs"
  //   }
}

这样我们的单元测试就可以成功的运行了
针对button的单元测试

  • button.unti.tsx
import React from 'react'
import renderer from 'react-test-renderer'
import Button from '../button'

describe('button', () => {
    it('是个div', () => {
        //渲染一个Button,因为Button是一个对象所以我们可以把它转成json
        const json = renderer.create(<Button/>).toJSON()
        //期待它去匹配Snapshot
        expect(json).toMatchSnapshot()
    })
})

运行yarn test报错,说我们的renderer是undefined
原因:我们没有默认导出
解决方法:

  1. import后加* as
  2. 修改tsconfig里的allowSyntheticDefaultImportsesModuleInterop
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,214评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,307评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,543评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,221评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,224评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,007评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,313评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,956评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,441评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,925评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,018评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,685评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,234评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,240评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,464评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,467评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,762评论 2 345