参考文章:我是这样搭建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 能够跨平台设置和应用环境变量,解决macwindow配置不同的问题
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 = 9527const 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.jsconst { resolve } = require('path')module.exports = {  entry: resolve(__dirname,"../src/index.js"),  output: {    filename: 'js/bundle.[hash:8].js',    path: resolve(__dirname, '../dist'),  },}
//webpack.dev.jsconst { merge } = require('webpack-merge')const common = require('./webpack.common.js')module.exports = merge(common, {  mode: 'development',})
//webpack.prod.jsconst { 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 publictouch 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.jsmodule.exports = merge(common, {  mode: 'development',+ devtool: 'eval-source-map',})
//webpack.prod.jsmodule.exports = merge(common, {  mode: 'production',+ devtool: 'none',})

款式解决

style-loadercss-loader是必备的了,接下来如果是解决less文件,须要装置lessless-loader。解决sass须要装置node-sasssass-loader,这里我用的是less,所以装置:

npm install css-loader style-loader less less-loader -D

失常状况下咱们配置两条rule,针对css文件和less文件就好了:

// webpack.common.jsmodule.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 款式兼容

postcssbabel相似,咱们也要装置一些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')]}        }}

这里能够发现cssless的编译配置差不多,所以这里封装成一个通用办法来配置,
build文件夹下新建utils.js文件,用来寄存封装的通用办法:

//utils.jsconst {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.jsmodule.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的起因,这里配置的baseUrlpaths别名还是会报错,解决这个问题还须要装置依赖包:

npm install eslint-import-resolver-typescript -D

批改 eslintrc.js 文件的 setting 字段:

settings: {  'import/resolver': {    node: {      extensions: ['.tsx', '.ts', '.js', '.json'],    },    typescript: {},  },},

这里编译typescript用到的是@babel/preset-typescriptfork-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.tsxApp.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 缩小打包体积

咱们其实并不想把reactreact-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.lazyReact.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,                           // 端口号设置    }),  ],})