创建项目
-
创建目录与远程仓库
克隆你的项目到本地
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对应的就是我们的文件名,我们只需要把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配置
- 安装ts
yarn add typescript --dev
- 创建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"
]
}
- 创建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/*
现在运行npx webpack
已经可以打包了
安装webpack-dev-server
自动打包
通过上面的配置我们可以把我们的index.tsx
打包成index.js
,但是我们每当有内容修改都得重新打包,所以我们需要webpack-dev-serve
来自动打包
yarn add webpack-dev-server
npx webpack-dev-server
安装html-webpack-plugin插件,使我们可以访问页面
- 创建一个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"
创建一个简单的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,那是因为我们写的其实是React的语法糖就等价于
function Button() {
return (
React.createElement('div', null, 'hi')
)
}
相关补充:
yarn.lock文件是做什么的?
- 指引;是yarn的lock文件
- 它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的区别
- 如果是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单元测试
- 安装
yarn add --dev jest babel-jest @babel/preset-env @babel/preset-react react-test-renderer
- 创建.babelrc
{
"presets": ["react-app"]
}
- 在package.json里配置测试命令
"test": "cross-env NODE_ENV=test jest --config=jest.config.js --runInBand",
- 创建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
- 创建test/setupTests.js,我们可以先什么也不写
- 运行yarn test
看到上面图片中的代码就说明你成功了
- 开始写我们的单元测试
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是没有发现的
- 创建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
原因:我们没有默认导出
解决方法:
- import后加* as
- 修改
tsconfig
里的allowSyntheticDefaultImports
为esModuleInterop