共计 14011 个字符,预计需要花费 36 分钟才能阅读完成。
参考文章:我是这样搭建 Typescript+React 我的项目环境的
根本配置
基于 webpack 4+ 搭建
装置 webpack
:
npm install webpack@4 webpack-cli@3 -D
新建文件夹build
,用于保留配置:
mkdir build
接着在 build
文件夹下新建这几个文件:
config.js
环境变量proxy.js
代理配置webpack.common.js
通用配置webpack.dev.js
开发环境配置webpack.prod.js
生产环境配置
$ cd build
$ touch config.js proxy.js webpack.common.js webpack.dev.js webpack.prod.js
接下来装置两个依赖包:
webpack-merge
能够将通用配置 webpack.common.js 和 开发环境 dev 或 生产环境 prod 的配置合并起来cross-env
能够跨平台设置和应用环境变量,解决mac
和window
配置不同的问题
npm install webpack-merge cross-env -D
批改 package.json
文件:
"scripts": {
"start": "cross-env NODE_ENV=development webpack-dev-server --config ./build/webpack.dev.js",
"build": "cross-env NODE_ENV=production webpack --config ./build/webpack.prod.js"
}
筹备好构建须要的环境变量,批改config.js
:
const SERVER_PORT = 9527
const SERVER_HOST = '127.0.0.1'
const PROJECT_NAME = "cli"
const isDev = process.env.NODE_ENV !== 'production'
module.exports = {
isDev,
PROJECT_NAME,
SERVER_PORT,
SERVER_HOST
}
接下来筹备好 webpack
配置文件:
// webpack.common.js
const {resolve} = require('path')
module.exports = {entry: resolve(__dirname,"../src/index.js"),
output: {filename: 'js/bundle.[hash:8].js',
path: resolve(__dirname, '../dist'),
},
}
//webpack.dev.js
const {merge} = require('webpack-merge')
const common = require('./webpack.common.js')
module.exports = merge(common, {mode: 'development',})
//webpack.prod.js
const {merge} = require('webpack-merge')
const common = require('./webpack.common.js')
module.exports = merge(common, {mode: 'production',})
新建工程入口文件:
src/
- index.js
启动我的项目
要启动我的项目,有几个配置依赖包是必备的:
html-webpack-plugin
模板文件,将咱们打包后的资源引入到 html 中webpack-dev-server
开启一个本地 http 服务,能够配置热更新、代理等。clean-webpack-plugin
清理文件夹,每次打包后先主动清理旧的文件copy-webpack-plugin
将 资源文件 复制到打包目录下
npm install html-webpack-plugin webpack-dev-server clean-webpack-plugin copy-webpack-plugin -D
模板文件配置
新建 public
文件夹,外面放咱们的 html 模板文件:
mkdir public
touch index.html
//index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title><%=htmlWebpackPlugin.options.title %></title>
</head>
<body>
<div id="root"></div>
</body>
</html>
通过 htmlWebpackPlugin
能够拿到配置的变量信息,接着批改webpack.common.js
:
const {resolve} = require('path')
const config = require("./config")
const CopyPlugin = require('copy-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const {CleanWebpackPlugin} = require('clean-webpack-plugin')
module.exports = {
entry: "../src/index.tsx",
output: {filename: 'js/bundle.[hash:8].js',
path: resolve(__dirname, '../dist'),
},
plugins:[
new HtmlWebpackPlugin({template: resolve(__dirname, '../public/index.html'),
filename: 'index.html',
title: config.PROJECT_NAME,
cache: false,
}),
new CopyPlugin({
patterns: [{ from: resolve(__dirname, "../public"), to: resolve(__dirname, "../dist") }
],
}),
new CleanWebpackPlugin()]
}
devServer 配置
代理
批改proxy.js
,配置代理:
const proxySetting = {
'/api/': {
target: 'http://localhost:3001',
changeOrigin: true,
},
// 接口代理 2
'/api-2/': {
target: 'http://localhost:3002',
changeOrigin: true,
pathRewrite: {'^/api-2': '',},
},
}
module.exports = proxySetting
devServer
批改 webpack.dev.js
:
const {merge} = require('webpack-merge');
const webpack = require('webpack');
const {resolve} = require("path");
const common = require('./webpack.common.js');
const proxySetting = require('./proxy');
const config = require('./config');
module.exports = merge(common, {
mode: 'development',
devServer: {
host: config.SERVER_HOST,
port: config.SERVER_PORT,
stats: 'errors-only',
clientLogLevel: 'silent',
compress: true,
open: false,
hot: true, // 热更新
proxy: {...proxySetting}, // 代理配置
contentBase: resolve(__dirname, '../public')
},
plugins: [new webpack.HotModuleReplacementPlugin()],
});
devtool
devtool
能够将编译后的代码映射回原始源代码,不便咱们调试错误代码,对我来说 eval-source-map
是可能承受的调试模式,生产环境下间接禁用,批改文件如下:
//webpack.dev.js
module.exports = merge(common, {
mode: 'development',
+ devtool: 'eval-source-map',
})
//webpack.prod.js
module.exports = merge(common, {
mode: 'production',
+ devtool: 'none',
})
款式解决
style-loader
和 css-loader
是必备的了,接下来如果是解决 less
文件,须要装置 less
和less-loader
。解决 sass
须要装置 node-sass
和sass-loader
, 这里我用的是less
,所以装置:
npm install css-loader style-loader less less-loader -D
失常状况下咱们配置两条 rule
,针对css
文件和 less
文件就好了:
// webpack.common.js
module.exports = {
// other...
module: {
rules: [{test: /\.css$/,use: ['style-loader','css-loader']},
{test: /\.less$/,use: [
'style-loader',
{
loader:'css-loader',
options:{importLoaders:1}
},
'less-loader'
]
},
]
},
}
不过咱们还是要解决款式兼容性问题和不同环境下的sourceMap
postcss 款式兼容
postcss
和 babel 相似,咱们也要装置一些 preset 能力失效:
- postcss-flexbugs-fixes:用于修复一些和 flex 布局相干的 bug。
- postcss-preset-env:将最新的 CSS 语法转换为指标环境的浏览器可能了解的 CSS 语法,目标是使开发者不必思考浏览器兼容问题。咱们应用 autoprefixer 来主动增加浏览器头。
- postcss-normalize:从 browserslist 中主动导入所须要的 normalize.css 内容。
npm install postcss-loader postcss-flexbugs-fixes postcss-preset-env autoprefixer postcss-normalize -D
postcss
的配置如下:
{
loader: 'postcss-loader',
options: {
sourceMap: config.isDev, // 是否生成 sourceMap
postcssOptions: {
plugins: [
// 修复一些和 flex 布局相干的 bug
require('postcss-flexbugs-fixes'),
require('postcss-preset-env')({autoprefixer: {grid: true,flexbox: 'no-2009'},
stage: 3,
}),
require('postcss-normalize')]}
}
}
这里能够发现 css
和less
的编译配置差不多,所以这里封装成一个通用办法来配置,
build文件夹下新建 utils.js
文件,用来寄存封装的通用办法:
//utils.js
const {isDev} = require('./config')
exports.getCssLoaders = (importLoaders) => [
'style-loader',
{
loader: 'css-loader',
options: {
modules: false,
sourceMap: isDev,
importLoaders,
},
},
{
loader: 'postcss-loader',
options: {
sourceMap: isDev,
postcssOptions: {
plugins: [
// 修复一些和 flex 布局相干的 bug
require('postcss-flexbugs-fixes'),
require('postcss-preset-env')({
autoprefixer: {
grid: true,
flexbox: 'no-2009',
},
stage: 3,
}),
require('postcss-normalize'),
],
},
},
},
]
接着批改 webpack.common.js
文件:
const {getCssLoaders} = require("./utils");
...
module:{
rules:[{ test: /.(css)$/, use: getCssLoaders(1) },
{
test: /\.less$/,
use: [...getCssLoaders(2),
{
loader: 'less-loader',
options: {sourceMap: config.isDev},
}
]
}
]
}
...
最初,还须要在 package.json
中增加 browserslist
:
{
"browserslist": [
">0.2%",
"not dead",
"ie >= 9",
"not op_mini all"
],
}
图片和字体文件解决
图片和其它资源文件解决比较简单,图片能够应用 url-loader
解决,如果是小图或图标能够转成 base64,如果其它资源文件,通过 file-loader
转成流的形式输入,先装置依赖包:
npm install file-loader url-loader -D
// webpack.common.js
module.exports = {
// other...
module: {
rules: [
// other...
{test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
use: [
{
loader: 'url-loader',
options: {
limit: 10 * 1024,
name: '[name].[contenthash:8].[ext]',
outputPath: 'assets/images',
},
},
],
},
{test: /\.(ttf|woff|woff2|eot|otf|svg)$/,
use: [
{
loader: 'file-loader',
options: {name: '[name].[contenthash:8].[ext]',
outputPath: 'assets/fonts',
},
},
],
},
]
},
plugins: [//...],
}
typescript
环境下还要先申明类型,这里再 src/typings
下新建 file.d.ts
文件,输出上面内容:
declare module '*.svg' {
const path: string
export default path
}
declare module '*.bmp' {
const path: string
export default path
}
declare module '*.gif' {
const path: string
export default path
}
declare module '*.jpg' {
const path: string
export default path
}
declare module '*.jpeg' {
const path: string
export default path
}
declare module '*.png' {
const path: string
export default path
}
react 和 typescript
先装置react
:
npm install react react-dom -S
装置 babel
相干依赖:
npm install babel-loader @babel/core @babel/preset-env @babel/plugin-transform-runtime @babel/preset-react -D
npm install @babel/runtime-corejs3 -S
留神:@babel/runtime-corejs3 的装置为生产依赖。
- babel-loader 应用
babel
解析文件 - @babel/core
babel
外围模块 - @babel/preset-env 转换成最新的 javascript 规定
- @babel/preset-react 转译 jsx 语法
- @babel/plugin-transform-runtime 开发库 / 工具、移除冗余工具函数(helper function)
- @babel/runtime-corejs3 辅助函数
新建.babelrc
,输出以下代码:
{
"presets": [
[
"@babel/preset-env",
{
// 避免 babel 将任何模块类型都转译成 CommonJS 类型,导致 tree-shaking 生效问题
"modules": false
}
],
"@babel/preset-react"
],
"plugins": [
[
"@babel/plugin-transform-runtime",
{
"corejs": {
"version": 3,
"proposals": true
},
"useESModules": true
}
]
]
}
批改 webpack.common.js
文件,减少以下代码:
module.exports = {
// other...
module: {
rules: [
{test: /\.(tsx?|js)$/,
loader: 'babel-loader',
options: {cacheDirectory: true},
exclude: /node_modules/,
},
// other...
]
},
plugins: [//...],
}
babel-loader 在执行的时候,可能会产生一些运行期间反复的公共文件,造成代码体积大冗余,同时也会减慢编译效率,所以咱们开启 cacheDirectory 将这些公共文件缓存起来,下次编译就会放慢很多。
resolve.extensions 和 resolve.alias
extensions
扩展名辨认alias
别名
给 webpack.common.js
新增 resolve
:
resolve: {alias:{"@":resolve(__dirname, '../src')},
extensions: ['.tsx', '.ts', '.js', '.json'],
},
反对 typescript
批改 src/index.js
文件为src/index.tsx
:
entry: {app: resolve(__dirname, '../src/index.tsx'),
},
每个 Typescript 都会有一个 tsconfig.json
文件,其作用简略来说就是:
- 编译指定的文件
- 定义了编译选项
在控制台输出上面代码能够生成 tsconfig.json
文件:
npx tsc --init
默认的 tsconfig.json
的配置有点乱不好治理,这里举荐深刻了解 TypeScript-tsconfig-json 查看更多,关上tsconfig.json
,输出新的配置:
{
"compilerOptions": {
// 根本配置
"target": "ES5", // 编译成哪个版本的 es
"module": "ESNext", // 指定生成哪个模块零碎代码
"lib": ["dom", "dom.iterable", "esnext"], // 编译过程中须要引入的库文件的列表
"allowJs": true, // 容许编译 js 文件
"jsx": "react", // 在 .tsx 文件里反对 JSX
"isolatedModules": true,
"strict": true, // 启用所有严格类型查看选项
"noImplicitAny": false, // 容许 any 类型
// 模块解析选项
"moduleResolution": "node", // 指定模块解析策略
"esModuleInterop": true, // 反对 CommonJS 和 ES 模块之间的互操作性
"resolveJsonModule": true, // 反对导入 json 模块
"baseUrl": "./", // 根门路
"paths": { // 门路映射,与 baseUrl 关联
"@/*": ["src/*"],
},
// 实验性选项
"experimentalDecorators": true, // 启用实验性的 ES 装璜器
"emitDecoratorMetadata": true, // 给源码里的装璜器申明加上设计类型元数据
// 其余选项
"forceConsistentCasingInFileNames": true, // 禁止对同一个文件的不统一的援用
"skipLibCheck": true, // 疏忽所有的申明文件(*.d.ts)的类型查看
"allowSyntheticDefaultImports": true, // 容许从没有设置默认导出的模块中默认导入
"noEmit": true // 只想应用 tsc 的类型查看作为函数时(当其余工具(例如 Babel 理论编译)时)应用它
},
"exclude": ["node_modules"]
}
因为 eslint
的起因,这里配置的 baseUrl
和paths
别名还是会报错,解决这个问题还须要装置依赖包:
npm install eslint-import-resolver-typescript -D
批改 eslintrc.js 文件的 setting 字段:
settings: {
'import/resolver': {
node: {extensions: ['.tsx', '.ts', '.js', '.json'],
},
typescript: {},},
},
这里编译 typescript
用到的是 @babel/preset-typescript
和fork-ts-checker-webpack-plugin
@babel/preset-typescript
编译 ts 代码很粗犷,间接去掉 ts 的类型申明,再用其余 babel 插件进行编译fork-ts-checker-webpack-plugin
尽管用preset-typescript
编译简略粗犷速度快,然而启动和编译过程中控制台还是会短少类型查看的谬误揭示
装置插件:
npm install @babel/preset-typescript fork-ts-checker-webpack-plugin -D
给 webpack.common.js
减少上面代码:
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin')
module.exports = {
plugins: [
// 其它 plugin...
new ForkTsCheckerWebpackPlugin({
typescript: {configFile: resolve(__dirname, '../tsconfig.json'),
},
}),
]
}
给.babelrc
增加 preset-typescript
:
"presets": [
[
//...
"@babel/preset-typescript"
]
最初装上 React
类型申明文件:
npm install @types/react @types/react-dom -D
测试
src
文件夹下新建 index.tsx
和App.tsx
文件,输出上面内容测试:
- index.tsx
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
ReactDOM.render(<App age={12} name="test" />, document.querySelector("#root"));
- App.tsx
import React from "react";
interface IProps {
name: string;
age: number;
}
function App(props: IProps) {const { name, age} = props;
return (
<div className="app">
<span>{`Hello! I'm ${name}, ${age} years old.`}</span>
</div>
);
}
export default App;
优化
显示编译进度
webpackbar
能够在启动或编译的时候显示打包进度
npm install webpackbar -D
在 webpack.common.js 减少以下代码:
const WebpackBar = require("webpackbar");
class Reporter {done(context) {if (config.isDev) {console.clear();
console.log(` 启动胜利:${config.SERVER_HOST}:${config.SERVER_PORT}`);
}
}
}
module.exports = {
plugins: [
// 其它 plugin...
new WebpackBar({
name: config.isDev ? "正在启动" : "正在打包",
color: "#fa8c16",
reporter: new Reporter()})
]
}
放慢二次编译速度
hard-source-webpack-plugin
为程序中的模块(如 lodash)提供了一个两头缓存,放到本我的项目 node_modules/.cache/hard-source 下,首次编译时会消耗略微比原来多一点的工夫,因为它要进行一个缓存工作,然而再之后的每一次构建都会变得快很多
npm install hard-source-webpack-plugin -D
在 webpack.common.js
中减少以下代码:
const HardSourceWebpackPlugin = require('hard-source-webpack-plugin')
module.exports = {
plugins: [
// 其它 plugin...
new HardSourceWebpackPlugin(),]
}
external 缩小打包体积
咱们其实并不想把
react
、react-dom
打包进最终生成的代码中,这种第三方包个别会剥离进来或者采纳 CDN 链接模式
批改 webpack.common.js,减少以下代码:
module.exports = {
externals: {
react: 'React',
'react-dom': 'ReactDOM',
},
}
能够通过两种形式引入
- CDN 形式引入:
<!DOCTYPE html>
<html lang="en">
<body>
<div id="root"></div>
+ <script crossorigin src="https://unpkg.com/react@16.13.1/umd/react.production.min.js"></script>
+ <script crossorigin src="https://unpkg.com/react-dom@16.13.1/umd/react-dom.production.min.js"></script>
</body>
</html>
- 本地引入:
在 public
文件夹下新建 lib
文件夹,寄存咱们的公共文件:
public/
index.html
lib/
react.production.min.js
react-dom.production.min.js
DllPlugin
另外一种通过 dll 动态链接库的形式也能够达到缩小打包体积的作用,这里不做示例了,举荐一步到位的 autodll-webpack-plugin;
splitChunks
React 组件能够借助 React.lazy
和 React.Suspense
进行懒加载,具体能够看上面的示例:
import React, {Suspense, useState} from 'react'
const ComputedOne = React.lazy(() => import('Components/ComputedOne'))
const ComputedTwo = React.lazy(() => import('Components/ComputedTwo'))
function App() {const [showTwo, setShowTwo] = useState<boolean>(false)
return (
<div className='app'>
<Suspense fallback={<div>Loading...</div>}>
<ComputedOne a={1} b={2} />
{showTwo && <ComputedTwo a={3} b={4} />}
<button type='button' onClick={() => setShowTwo(true)}>
显示 Two
</button>
</Suspense>
</div>
)
}
export default App
通过懒加载的加载的组件会打出独立的 chunk
文件,为了让第三方依赖也打进去独立 chunk,须要在 webpack.common.js 中减少以下代码:
module.exports = {
// other...
externals: {//...},
optimization: {
splitChunks: {
chunks: 'all',
name: true,
},
},
}
热更新
后面 devServer 其实曾经做了热更新的配置,然而批改 js 代码还是不能达到部分刷新的目标,这里还要在入口文件 index.jsx
增加判断:
if (module && module.hot) {module.hot.accept()
}
因为 ts 的缘故,会导致未声明的文件报错,这里还要装置@types/webpack-env
:
npm install @types/webpack-env -D
生产环境优化
款式解决
抽离款式
装置mini-css-extract-plugin
:
npm install mini-css-extract-plugin -D
build/utils.js
新增上面代码:
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const getCssLoaders = (importLoaders) => [
isDev ? 'style-loader' : MiniCssExtractPlugin.loader,
// ....
]
webpack.prop.js
新增上面代码:
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
module.exports = {
plugins: [
// 其它 plugin...
new MiniCssExtractPlugin({filename: 'css/[name].[contenthash:8].css',
chunkFilename: 'css/[name].[contenthash:8].css',
ignoreOrder: false,
}),
]
}
去除无用款式
npm install purgecss-webpack-plugin glob -D
webpack.prop.js
新增上面代码:
const glob = require("glob");
const PurgeCSSPlugin = require('purgecss-webpack-plugin')
const {resolve} = require("path");
module.exports = merge(common, {
plugins: [
// ...
new PurgeCSSPlugin({paths: glob.sync(`${resolve(__dirname, "../src")}/**/*.{tsx,scss,less,css}`, {nodir: true}),
whitelist: ["html", "body"]
})
],
})
代码压缩
npm install optimize-css-assets-webpack-plugin terser-webpack-plugin@4 -D
webpack.prop.js
新增上面代码:
const TerserPlugin = require("terser-webpack-plugin");
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin')
module.exports = merge(common, {
//...
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
extractComments: false,
terserOptions: {compress: { pure_funcs: ["console.log"] }
}
}),
new OptimizeCssAssetsPlugin()]
},
plugins:[...]
})
增加包正文
webpack.prop.js
新增上面代码:
const webpack = require('webpack')
module.exports = merge(common, {
plugins: [
// ...
new webpack.BannerPlugin({
raw: true,
banner: '/** @preserve Powered by chenwl */',
}),
]
})
打包剖析
npm install webpack-bundle-analyzer -D
webpack.prop.js
新增上面代码:
const {BundleAnalyzerPlugin} = require('webpack-bundle-analyzer')
module.exports = merge(common, {
plugins: [
// ...
new BundleAnalyzerPlugin({
analyzerMode: 'server', // 开一个本地服务查看报告
analyzerHost: '127.0.0.1', // host 设置
analyzerPort: 8081, // 端口号设置
}),
],
})