从0到1使用Webpack5 + React + TS构建标准化应用

发表于 2年以前  | 总阅读数:318 次

前言

本篇文章主要讲解如何从一个空目录开始,建立起一个基于webpack + react + typescript的标准化前端应用。

  • 技术栈: webpack5 + React18 + TS
  • 工程化: eslint + prettier + husky + git hooks
  • 支持图片、less、sass、fonts、数据资源(JSON、csv、tsv等)、Antd按需加载以及主题
  • 支持热更新、资源压缩、代码分离(动态导入、懒加载等)、缓存、devServer

背景

在项目开发中,我们可以使用create-react-app或者飞冰等脚手架工具,那么,为什么我们要自己来搭建一个标准化项目?

原因

  • 当我们使用优秀的脚手架工具开发项目时,当然会提升很多便利,他们的功能更全面、性能更强大,但是在这些值得学习的榜样面前,我们需要从零开始,动手去实现每一个细节和功能,看的再多都不如自己动手实现一个demo更有效果。并且动手实践也可以帮助我们理解项目打包和编译的原理,进而提升自己的技术熟练度,扩展我们的知识面。Webpack 实现工程化方方面面的功能,自然不是 all in one code实现的。从 Webpack 的设计理念和实现原理中,我们能接触到工程化方面的知识:架构扩展、插件化、缓存机制。学习Webpack也代表着学习前端的发展趋势:例如在webpack的竟对Vite上,我们可以学到bundleless的理念,跳过了传统的打包这个概念,并且其他先进理念都是我们需要去学习的地方。

  • 开发中,我们发现使用def、aone等生成一个成熟的前端项目模版,不难会发现,项目中的babel、weback、prettier、loader等配置文件缺失,而且难以修改现成的脚手架配置,可扩展能力较弱。导致在性能优化方面能做的工作有限,使得开发受到限制。

项目结构

目录


├── dist                                // 默认的 build 输出目录
├── .husky                              // pre-commit hook
├── webpack.config.js                   // 全局配置文件及webpack配置文件
├── test                                // 测试目录
└── src                                 // 源码目录
    ├── assets                          // 公共的文件(如image、css、font等)
    ├── components                      // 项目组件
    ├── constants                       // 常量/接口地址等
    ├── routes                          // 路由
    ├── utils                           // 工具库
    ├── pages                           // 页面模块
        ├── Home                        // Home模块,建议组件统一大写开头
        ├── ...
    ├── App.tsx                         // react顶层文件
    ├── typing                          // ts类型文件
├── .editorconfig                       // IDE格式规范
├── .eslintignore                       // eslint忽略
├── .eslintrc                           // eslint配置文件
├── .gitignore                          // git忽略
├── .prettierrc                         // prettierc配置文件
├── .babelrc                         // babel配置文件
├── LICENSE.md                          // LICENSE
├── package.json                        // package
├── README.md                           // README
├── tsconfig.json                       // typescript配置文件

依赖

 "dependencies": {
    "antd": "^4.22.4", // 懂得都懂
    "react": "^18.2.0", // 懂得都懂
    "react-dom": "^18.2.0" // 懂得都懂
  },
  "devDependencies": {
    // babel全家桶
    "@babel/core": "^7.18.10",
    "@babel/plugin-proposal-class-properties": "^7.18.6", // React class支持
    "@babel/plugin-transform-runtime": "^7.18.10", // 抽离提取 Babel的注入代码,防止重复加载,减小体积
    "@babel/preset-env": "^7.18.10", // 提供的预设,允许我们使用最新的JavaScript
    "@babel/preset-react": "^7.18.6", // react支持

    // ts类型检查
    "@types/node": "^18.6.4",
    "@types/react": "^18.0.15",
    "@types/react-dom": "^18.0.6",
    // @types 开头的是对应包的 TypeScript 类型声明
    "@typescript-eslint/eslint-plugin": "^5.33.0",
    "@typescript-eslint/parser": "^5.33.0",

    // webpack loader:解析对应文件
    "csv-loader": "^3.0.5",
    "sass-loader": "^13.0.2",
    "xml-loader": "^1.2.1",
    "ts-loader": "^9.3.1",
    "less-loader": "^11.0.0",

    // eslint全家桶
    "eslint": "^8.21.0",
    "eslint-config-ali": "^14.0.1", // ali前端规约
    "eslint-config-prettier": "^8.5.0", // 关闭所有不必要或可能与[Prettier]冲突的规则
    "eslint-import-resolver-typescript": "^3.4.0", // 添加 ts 语法支持  eslint-plugin-import
    "eslint-plugin-import": "^2.26.0", // ES6+  import/export 语法支持
    "eslint-plugin-prettier": "^4.2.1", // prettier语法支持
    "eslint-plugin-react": "^7.30.1", // react语法支持
    "eslint-plugin-react-hooks": "^4.6.0", // hooks语法支持
    "eslint-webpack-plugin": "^3.2.0", 

    // webpack plugin
    "fork-ts-checker-webpack-plugin": "^7.2.13", // 避免webpack中检测ts类型
    "html-webpack-plugin": "^5.5.0", // 简化HTML文件的创建 ,配合webpack包含hash的bundle使用
    "mini-css-extract-plugin": "^2.6.1", // css拆分
    "optimize-css-assets-webpack-plugin": "^6.0.1", // css压缩
    "terser-webpack-plugin": "^5.3.3", // 使用 terser 压缩 js (terser 是一个管理和压缩 ES6+ 的工具)
    "webpack-bundle-analyzer": "^4.5.0", // webpack打包体积可视化分析
    "webpack-cli": "^4.10.0", // 提供脚手架命令
    "webpack": "^5.74.0", // webpack引擎
    "webpack-dev-server": "^4.9.3", // 开发环境的live server

    // 工具
    "husky": "^8.0.1", // 自动配置 Git hooks 钩子
    "less": "^4.1.3", // css类型
    "sass": "^1.54.3", // css类型
    "typescript": "^4.7.4", // ts
    "lint-staged": "^13.0.3", // 对暂存的git文件运行linter

    // prettier 格式化
    "prettier": "^2.7.1",
    "pretty-quick": "^3.1.3", // 在更改的文件上运行 prettier
  }

实现过程

项目初始化

首先从一个空目录开始,对项目初始化:

mkdir demo
cd demo
git init
npm init

React和Babel引入

对于一个React项目,我们首先要安装React,写一个Hello World!

安装我们主要的项目依赖:

tnpm i -S react react-dom

由于我们的浏览器不支持最新的ECMAScript语法,所以我们需要Babel来转义为ES5或者ES6。

安装我们的Babel来提高兼容性:

tnpm i -D @babel/core babel-preset-env babel-preset-react @babel/plugin-proposal-class-properties
  • @babel/core: babel转码的核心引擎
  • babel-preset-env: 添加对ES5、ES6的支持
  • babel-preset-react: 添加对JSX的支持
  • @babel/plugin-proposal-class-properties: 对React中class的支持

Webpack引入

tnpm i -D webpack webpack-cli webpack-dev-server html-webpack-plugin
  • webpack: weback插件的核心依赖
  • webpack-cli: 为插件提供命令行工具
  • webpack-dev-server: 帮助启动live server
  • html-webpack-plugin: 帮助创建HTML模版

Babel配置

.babelrc中添加基本配置:


{
  "presets": ["@babel/react", "@babel/env"],
  "plugins": ["@babel/plugin-proposal-class-properties"]
}

Babel Plugin

Babel是代码转换器,借助Babel,我们可以使用最流行的js写法,而plugin就是实现Babel功能的核心。

这里的配置是为了支持react中class的写法。

Babel Preset

Babel的Plugin一般拆成尽可能小的粒度,开发者可以按需引进,例如ES6到ES5的功能,官方提供了20+插件,这样可以提高性能和扩展性,但是很多时候逐个引入就很让人头大,而Babel Preset就是为此而生,可以视为Presets是相关Plugins的集合。

  • @babel/react: 支持了React所有的转码需求
  • @babel/env: 不夸张滴讲,仅需要它自己内部的配置项,就可以完成现代JS工程几乎所有的转码需求

Webpack基本配置

新建一个webpack.config.js文件。

//webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
   entry: './src/index.js',
   output: {
      path: path.join(__dirname, '/dist'),
      filename: 'bundle.js'
   },
   devServer: {
      port: 8080
   },
   module: {
      rules: [
         {
            test: /\.jsx?$/,
            exclude: /node_modules/,
            loader: 'babel-loader',
         },
         {
            test: /\.css$/,
            use: [ 'style-loader', 'css-loader' ]
        }
      ]
   },
   plugins:[
       new HtmlWebpackPlugin({
            template: path.join(__dirname,'/src/index.html')
       }) 
   ]
}
  • entry: 入口,开始打包的起点
  • output: 打包文件的地址
  • devServer: live server配置
  • test: 使用loader的文件类型
  • loader: 将要使用的loader

Package.json基本配置


"start": "webpack serve --mode development --open --hot",
"build": "webpack --mode production"
  • mode: process.env.NODE_ENV --> development, 为modules和chunks启用有意义的名称
  • open: 告诉server在服务启动后打开默认浏览器
  • hot: 开启热更新

写一个React Demo

目前的项目结构如下图所示: js和html文件如下图所示:


/_index.js_

import React from "react";
import ReactDOM from "react-dom";

const App = () => {
  return (
    <div>
      <h1>Hello!!</h1>
      <h2>Welcome to your First React App..!</h2>
    </div>
  );
};

ReactDOM.render(<App />, document.getElementById("root"));

//_index.html_

<!DOCTYPE html>
<html lang = "en">
  <head>
    <meta charset = "UTF-8">
    <title>React Web</title>
  </head>
  <body>
    <div id = "root"></div>
    <script src = 'bundle.js'></script>
  </body>
</html>

最后,只要start一下,项目就会启动在8080端口。

TypeScript配置

tnpm install -D typescript ts-loader @types/node @types/react @types/react-dom
  • typescript: TypeScript的主要引擎
  • ts-loader: 转义.ts --> .js 并打包
  • @types/node @types/react @types/react-dom: 对node、react、react dom类型的定义

同时在根目录加入tsconfig.json来对ts编译进行配置:


//_tsconfig.json_

{
  "compilerOptions": {
    "outDir": "./dist/",
    "noImplicitAny": true,
    "module": "es6",
    "target": "es5",
    "jsx": "react",
    "allowJs": true,
    "allowSyntheticDefaultImports": true,
    "moduleResolution": "Node"
  }
}

最后在webpack中添加对ts的支持。

添加ts-loader:


//_webpack.config.js_
...
{
  test: /\.tsx?$/,
  exclude: /node_modules/,
  loader: 'ts-loader'
}
...

设置resolve属性,来指定文件如何被解析:


//_webpack.config.js_
...
resolve: 
{
   extensions: [ '.tsx', '.ts', '.js' ],
}
...

rename入口:


//_webpack.config.js_
...
entry: "./src/index.tsx",
...

最后启动一下server来看一下ts配置是否正确。

上述我们的配置其实相当于执行了一次:

npx create-react-app my-app --template typescript

在这种流程下很是麻烦,将 *.ts 提供给 TypeScript,然后将运行的结果提供给 Babel,而且还要借助很多loader。

那么我们能不能简化一下这样的流程,因为Babel7中提供的babel-loader就可以完美进行编译ts,答案是可以的,这种方式直接简化了过程。


module: {
    rules: [
      {
        test: /\.tsx?$/,
        exclude: /node_modules/,
        loader: ['babel-loader']
      }
    ]
  },

并且在.babelrc中也只多了一行@babel/preset-typescript,这种配置更简单,而且打包速度更快一点,逻辑更加清晰。

那么为什么还要在项目中使用ts-loader呢?

  • ts-loader 在内部是调用了 TypeScript 的官方编译器 -- tsc。所以,ts-loader 和 tsc 是共享 tsconfig.json,所以会提供完整的报错信息,ts-loader也与 vscode 提供的语法校验表现一致
  • 而@babel/preset-typescript有的时候会无法提供完整的报错信息和类型提示

管理资源

webpack 只能理解 JavaScript 和 JSON 文件,这是 webpack 开箱可用的自带能力。loader 让 webpack 能够去处理其他类型的文件,并将它们转换为有效的模块中。

loader中,test属性可以识别出哪些文件会被转换;use属性可以定义出转换时,应该是用哪个loader。

CSS、Less、Sass

安装loader:

tnpm i -D less less-loader style-loader css-loader sass sass-loader

webpack配置:


//_webpack.config.js_
...
rules: [
      {
        test: /\.jsx?$/,
        exclude: /node_modules/,
        loader: 'babel-loader',
      },
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader'],
      },
      {
        test: /\.tsx?$/,
        exclude: /node_modules/,
        loader: 'ts-loader',
      },
      {
        test: /\.(less|css)$/,
        exclude: /\.module\.less$/,
        use: [
          {
            loader: 'css-loader',
            options: {
              importLoaders: 2,
              sourceMap: !!DEV,
            },
          },
          {
            loader: 'less-loader',
            options: {
              sourceMap: !!DEV,
            },
          },
        ],
      },
      {
        test: /\.(sass|scss)$/,
        use: [
          {
            loader: 'css-loader',
            options: {
              importLoaders: 2,
              sourceMap: !!DEV,
            },
          },
          {
            loader: 'sass-loader',
            options: {
              sourceMap: !!DEV,
            },
          },
        ],
      },
 ...

图片、JSON资源

对于图片和字体,我们可以使用内置的Assets Modules来轻松地把这些内容加到我们的系统中,对于类型,我们可以选择:

  • asset/resource 发送一个单独的文件并导出 URL。
  • asset/inline 导出一个资源的 data URI。
  • asset/source 导出资源的源代码。
  • asset 在导出一个 data URI 和发送一个单独的文件之间自动选择。

//_webpack.config.js_
...
module: {
  rules: [{
    test: /\.png/,
    type: 'asset/resource'
  }]
},
...

对于其他类型资源,我们需要安装csv-loader、xml-loader等:


//_webpack.config.js_
...
{
  test: /\.(csv|tsv)$/i,
  use: ['csv-loader'],
},
{
  test: /\.xml$/i,
  use: ['xml-loader'],
},
...

搭建开发环境

目前,我们的应用已经可以正常运行tsx文件,并且在本地进行调试和开发,那么我们来看看如何设置一个开发环境,来使开发变得更加轻松。


//_webpack.config.js_
...
const { DEV, DEBUG } = process.env;
process.env.BABEL_ENV = DEV ? 'development' : 'production';
process.env.NODE_ENV = DEV ? 'development' : 'production';
...
mode: DEV ? 'development' : 'production',
devtool: DEV && 'source-map',
...

我们可以从process.env中获取环境变量来区分开发环境和生产环境。当webpack在本地打包代码时,我们可以使用inline-source-map,可以将编译后的代码映射回原始源代码,这样在报错的时候,错误就会被定为到确切的文件和行数。当然,在生产环境中,为了保护隐私,最好把这个设置动态关掉。

在开发环境中,webpack-dev-server会为你提供一个基本的web server,并且具有实时重新加载功能。

完善打包配置与缓存

我们希望每次打包都把上次的打包文件删除,可以使用CleanWebpackPlugin:

//_webpack.config.js_
...
const { CleanWebpackPlugin } = require('clean-webpack-plugin');

module.exports = {
  plugins: [
    new CleanWebpackPlugin(),
  ]
}
...

并且,在我们生产环境,我们希望改动后的新版本可以丢弃缓存,并且没有改动的版本可以保留缓存;但是在开发环境,我们不希望有缓存,而是每次都是拿到最新的资源。所以,需要对webpack config做一次拆分:分成

  • webpack.prod.js 生产环境打包配置
  • webpack.dev.js 开发环境打包配置

里面的区别主要在于打包后的文件名称、sourceMap等。生产环境contenthash:只有模块的内容改变,才会改变hash值:


output: {
    filename: 'js/[name].[contenthash:8].js', // contenthash:只有模块的内容改变,才会改变hash值
 },

开发环境


output: {
  filename: 'js/[name].[hash:8].js',
}

性能优化

打包分析工具

可以使用webpack-bundle-analyzer来分析我们打包资源的大小:


const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
plugins: [
  DEBUG && new BundleAnalyzerPlugin(),
]

同时设置package.json的启动项

资源压缩

OptimizeCSSAssetsPlugin主要用来优化css文件的输出,包括摈弃重复的样式定义、砍掉样式规则中多余的参数、移除不需要的浏览器前缀等。

TerserPlugin主要用来优化js体积,包括重命名变量,甚至是删除整个的访问不到的代码块。

//_webpack.config.js_
...
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin');
...
optimization: {
  minimizer: [
    new TerserPlugin({
      parallel: false,
      terserOptions: {
        output: {
          comments: false,
        },
      },
    }),
    new OptimizeCSSAssetsPlugin({}),
  ],
    minimize: !DEV,
      splitChunks: {
        minSize: 500000,
          cacheGroups: {
            vendors: false,
          },
      },
},
...

代码分离

资源分离
1)多入口

webpack内置的特性能够把代码分离到不同的bundle 中,然后可以按需加载或并行加载这些文件。代码分离可以用于获取更小的bundle,以及控制资源加载优先级,如果使用合理,会极大影响加载时间。


//_webpack.config.js_
...
entry: {
  index: './src/index.js',
  another: './src/another-module.js',
},
output: {
  filename: '[name].bundle.js'
...
2)Tree Shaking

Webpack5在生产环境已经集成了Tree Shaking功能,不用的代码会被shaking掉:


// _webpack.config.js_
module.exports = {
  // ...
  mode: 'production',
};

但是在开发环境中需要手动配置(Not Recommend):


// _webpack.config.js_
module.exports = {
  // ...
  mode: 'development',
  optimization: {
    usedExports: true,
  }
};

处于好奇,webpack是如何完美的避开没有使用的代码的呢?

很简单:就是 Webpack 没看到你使用的代码。Webpack 跟踪整个应用程序的import/export 语句,因此,如果它看到导入的东西最终没有被使用,它会认为那是未引用代码(或叫做“死代码”—— dead-code ),并会对其进行 tree-shaking 。死代码并不总是那么明确的。下面是一些例子:


// _test.js_
// 这会被看作“活”代码,不会做 tree-shaking
import { add } from './math'
console.log(add(5, 6))
// 导入但没有赋值给 JavaScript 对象,也没有在代码里用到
// 这会被当做“死”代码,会被 tree-shaking
import { add, minus } from './math'
console.log('hello webpack')
// 导入整个库,但是没有赋值给 JavaScript 对象,也没有在代码里用到
// 非常奇怪,这竟然被当做“活”代码,因为 Webpack 对库的导入和本地代码导入的处理方式不同。
import { add, minus } from './math' // 死的
import 'lodash' // 活的
console.log('hello webpack')

所以对于这种三方库我们可以使用下面的Shimming方法。

注意 Webpack 不能百分百安全地进行 tree-shaking。有些模块导入,只要被引入,就会对应用程序产生重要影响。一个很好的例子就是全局样式表,或者设置全局配置的JavaScript 文件。Webpack 认为这样的文件有“副作用”。具有副作用的文件不应该做 tree-shaking,因为这将破坏整个应用。比较好的告诉Webpack你的代码有副作用的方法就是在package.json里面设置sideEffects。

{
  "name": "your-project",
  "sideEffects": ["./src/some-side-effectful-file.js", "*.css"]
}
3)Shimming预置依赖

对于上面的lodash库无法被shaking,我们可以使用细粒度shimming预置的方法来优化,首先引入ProvidePlugin插件,把应用程序中的模块依赖,改为一个全局变量依赖,让我们先移除 lodash 的 import语句,改为通过插件提供它,并且提取出join方法来全局使用它:


// _src/index.tsx
console.log(join(['hello', 'webpack'], ' '))
// _webpack.config.js_
plugins: [
  new webpack.ProvidePlugin({
    //_: 'lodash'
    // 如果没注释的话,需要这样引用console.log(_.join(['hello', 'webpack'], ' '))
    join: ['lodash', 'join'],
  })
]

细粒度Shimming

一些遗留的模块依赖的this指向的window对象,我们可以使用import-loaders,它对依赖 window 对象下的全局变量(比如 $ 或 this )的第三方模块非常有用。

CommonJS 上下文中,这将会变成一个问题,也就是说此时的 this指向的是 module.exports。在这种情况下,你可以通过使用 imports-loader覆盖 this 指向:


// _webpack.config.js_
module: {
  rules: [
    {
      test: require.resolve('./src/index.js'),
      use: 'imports-loader?wrapper=window',
    },
  ]
},
4)公共部分提取

防止重复可以使用splitChunk,提取出代码中的公共部分:


//_webpack.config.js_
...
minimize: !DEV,
  splitChunks: {
    minSize: 500000,
      cacheGroups: {
        vendors: false,
      },
  },
...
  • minSize:形成一个新代码块最小的体积
  • cacheGroups:这里开始设置缓存的 chunks
5)按需分离

在React项目中,代码按需分离可以使用如下方法,webpack 把 import() 作为一个分离点(split-point),并把引入的模块作为一个单独的 chunk。import() 将模块名字作为参数并返回一个 Promoise 对象,即 import(name) -> Promise。


//_index.tsx_
...
const WdAndDxEntry = lazy(() => import(/* webpackChunkName: "wd-and-dx" */ '../../old-code/component/wd-and-dx/entry'));
const WdAndDxFallback = () => ()
const SSRCompatibleSuspense = (props: Parameters<typeof Suspense>['0']) => {
  const isMounted = useMounted();

  if (isMounted) {
    return <Suspense {...props} />;
  }
  return <>{props.fallback}</>;
} 
...
return (
  <SSRCompatibleSuspense fallback={<WdAndDxFallback />}>
    <WdAndDxEntry
      className=""
      data={data}
      style={{
        height: 150,
      }}
      />
  </SSRCompatibleSuspense>
); 
6)分离三方库

配置 dependOn option 选项,这样可以在多个 chunk 之间共享模块:


//_webpack.config.js_
...
module.exports = {
  entry: {
    index: {
      import: './src/index.js',
      dependOn: 'shared',
  },
  another: {
    import: './src/another-module.js',
    dependOn: 'shared',
  },
  shared: 'lodash',
  }
}
...
CSS分离

该插件MiniCssExtractPlugin将CSS提取到单独的文件中。它为每个包含CSS的JS文件创建一个CSS文件。

//_webpack.config.js_
...
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
...
{
  test: /\.(sass|scss)$/,
  use: [
    {
      loader: MiniCssExtractPlugin.loader,
    },
    {
      loader: 'css-loader',
      options: {
        importLoaders: 2,
        sourceMap: !!DEV,
      },
    },
    {
      loader: 'sass-loader',
      options: {
        sourceMap: !!DEV,
      },
    },
  ],
},
...
DEBUG && new BundleAnalyzerPlugin(),
  new MiniCssExtractPlugin({
  filename: '[name].css',
  chunkFilename: '[name].css',
}),
...

提高构建速度

当项目体积增大时,编译时间也随之增加。其中时间大头就是ts的类型检测耗时。ts-loader 提供了一个 transpileOnly 选项,它默认为 false,我们可以把它设置为 true,这样项目编译时就不会进行类型检查,也不会输出声明文件。


//_webpack.config.js_
...
module: {
  rules: [
    {
      test: /\.tsx?$/,
      use: [
        {
          loader: 'ts-loader',
          options: {
            transpileOnly: true
          }
        }
      ]
    }
  ]
}
...

可以看一下开关这个选项后的前后对比:

开启检查前


$ webpack --mode=production --config ./build/webpack.config.js
Hash: 36308e3786425ccd2e9d
Version: webpack 4.41.0
Time: 2482ms
Built at: 12/20/2019 4:52:43 PM
     Asset       Size  Chunks             Chunk Names
    app.js  932 bytes       0  [emitted]  main
index.html  338 bytes          [emitted]
Entrypoint main = app.js
[0] ./src/index.ts 14 bytes {0} [built]
Child html-webpack-plugin for "index.html":
     1 asset
    Entrypoint undefined = index.html
    [0] ./node_modules/html-webpack-plugin/lib/loader.js!./index.html 489 bytes {0} [built]
    [2] (webpack)/buildin/global.js 472 bytes {0} [built]
    [3] (webpack)/buildin/module.js 497 bytes {0} [built]
        + 1 hidden module
✨  Done in 4.88s.

关闭检查后

$ webpack --mode=production --config ./build/webpack.config.js
Hash: e5a133a9510259e1f027
Version: webpack 4.41.0
Time: 726ms
Built at: 12/20/2019 4:54:20 PM
Asset       Size  Chunks             Chunk Names
app.js  932 bytes       0  [emitted]  main
index.html  338 bytes          [emitted]
Entrypoint main = app.js
[0] ./src/index.ts 14 bytes {0} [built]
Child html-webpack-plugin for "index.html":
1 asset
Entrypoint undefined = index.html
[0] ./node_modules/html-webpack-plugin/lib/loader.js!./index.html 489 bytes {0} [built]
[2] (webpack)/buildin/global.js 472 bytes {0} [built]
[3] (webpack)/buildin/module.js 497 bytes {0} [built]
+ 1 hidden module
✨  Done in 2.40s.

From 4.88s --> 2.4s,但是缺少了类型检查。

这里官方推荐了一个解决方案,使用fork-ts-checker-webpack-plugin,它在一个单独的进程上运行类型检查器,此插件使用 TypeScript 而不是 webpack 的模块解析,有了 TypeScript 的模块解析,我们不必等待webpack 编译。可以极大加快编译速度。


//_webpack.config.js_
...
module: {
    rules: [
      {
        test: /\.tsx?$/,
        use: [
          {
            loader: 'ts-loader',
            options: {
              transpileOnly: true
            }
          }
        ]
      }
    ]
  },
  plugins: [
    new ForkTsCheckerWebpackPlugin()
  ]
...

用editorconfig统一编辑器规范

在根目录新建.editorconfig即可,注意不要与已有的lint规则冲突:


// __.editorconfig__
# http://editorconfig.org
root = true

[*]
 indent_style = space
 indent_size = 2
 end_of_line = lf
 charset = utf-8
 trim_trailing_whitespace = true
 insert_final_newline = true

 [*.md]
trim_trailing_whitespace = false

[makefile]
indent_style = tab
indent_size = 4

Antd配置

babel中配置按需加载:


{
  "presets": ["@babel/react", "@babel/env"],
  "plugins": [
    "@babel/plugin-proposal-class-properties",
    [
      "import",
      {
        "libraryName": "antd",
        "libraryDirectory": "es",
        "style": true // or 'css'
      },
      "antd"
    ]
  ]
}

webpack中定制主题:


module: {
    rules: [
      // 处理 .css
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader'],
      },
      // 处理 .less
      {
        test: /\.less$/,
        use: [
          'style-loader',
          'css-loader',
          // less-loader
          {
            loader: 'less-loader',
            options: {
              lessOptions: {
                // 替换antd的变量,去掉 @ 符号即可
                // https://ant.design/docs/react/customize-theme-cn
                modifyVars: {
                  'primary-color': '#1DA57A',
                },
                javascriptEnabled: true, // 支持js
              },
            },
          },
        ],
      },
    ]
  }

注意样式必须加载 less 格式,一个常见的问题就是引入了多份样式,less 的样式被 css 的样式覆盖了。

ESlint配置

ESlint主要功能包含代码格式和代码质量的校验,并且可以配置pre-commit来规范代码的提交。

tnpm install -D eslint eslint-webpack-plugin @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint-plugin-react
  • eslint: eslint主要引擎
  • eslint-webpack-plugin: webpack loader
  • @typescript-eslint/parser: 帮助ESlint lint ts代码
  • @typescript-eslint/eslint-plugin: 包含TS扩展规则的插件
  • eslint-plugin-react: 包含React扩展规则的插件

ESlint配置文件


// _eslintrc_
module.exports =  {
  parser:  '@typescript-eslint/parser',  // ESlint Parser
  extends:  [
    'plugin:react/recommended',  // 从@eslint-plugin-react中选择推荐的规则
    'plugin:@typescript-eslint/recommended',  // 从@typescript-eslint/eslint-plugin选择推荐的规则
  ],
  parserOptions:  {
    ecmaVersion:  2018,  // 帮助转化最先进的ECMAScript功能
    sourceType:  'module',  // 允许imports的用法
    ecmaFeatures:  {
      jsx:  true,  // JSX兼容
    },
  },
  rules:  {
  },
  settings:  {
    react:  {
      version:  'detect',  // 告诉eslint-plugin-react自动检测最新版本的react
    },
  },
};

Prettier配置

虽然 ESLint 也可以校验代码格式,但 Prettier 更擅长,所以项目中一般会搭配一起使用。为了避免二者的冲突,一般的解决思路是禁掉 ESLint 中与 Prettier 冲突的规则,然后使用 Prettier 做格式化, ESLint 做代码校验。prettier配置文件


{
  "arrowParens": "avoid",
  "bracketSpacing": true,
  "embeddedLanguageFormatting": "auto",
  "htmlWhitespaceSensitivity": "css",
  "insertPragma": false,
  "jsxBracketSameLine": true,
  "jsxSingleQuote": false,
  "printWidth": 100,
  "proseWrap": "preserve",
  "quoteProps": "as-needed",
  "requirePragma": false,
  "semi": true,
  "singleQuote": true,
  "tabWidth": 2,
  "trailingComma": "es5",
  "useTabs": true,
  "vueIndentScriptAndStyle": false
}

代码提交规范

prettier 只是保证了在通过编辑器(vs code)进行格式化代码的时候,格式化成需要的格式(当然可以通过配置 onSave 在代码保存时自动格式化),但是无法保证所有人都会主动进行。

因此进行自动格式化显得非常重要,而自动格式化的是时机选择 pre-commit 最恰当,通过 git hook ,能够在 commit 之前格式化好代码(如果已经 commit,会将暂存转为提交,生成提交记录,需要回滚才会撤销)。

tnpm i -D pretty-quick prettier husky 
  • pretty-quick: 配合git-hooks进行代码检测,并且fix
  • husky: 可以通过配置的方式来使用git-hooks,避免手动修改

package.json设置


"pretty": "./node_modules/.bin/pretty-quick --staged"
...
"husky": {
    "hooks": {
      "pre-commit": "tnpm run pretty"
    }
  },

Webpack完整配置

最后贴一下完整的配置,因为Aone发布自动更新版本号,所以不用拆分config文件来根据环境设置缓存,并且配置已经尽可能简化,拆分反而会增加维护成本。


//_webpack.config.js_
//webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin');
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
const ESLintPlugin = require('eslint-webpack-plugin');

const { DEV, DEBUG } = process.env;

process.env.BABEL_ENV = DEV ? 'development' : 'production';
process.env.NODE_ENV = DEV ? 'development' : 'production';

module.exports = {
  entry: './src/index.tsx',
  output: {
    path: path.join(__dirname, '/dist'),
    filename: 'bundle.js',
    clean: true,
  },
  devServer: {
    port: 8080,
  },
  mode: DEV ? 'development' : 'production',
  devtool: DEV && 'source-map',
  module: {
    rules: [
      {
        test: /\.jsx?$/,
        exclude: /node_modules/,
        loader: 'babel-loader',
      },
      {
        test: /\.tsx?$/,
        exclude: /node_modules/,
        loader: 'ts-loader',
      },
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader'],
      },
      // 处理 .less
      {
        test: /\.less$/,
        use: [
          'style-loader',
          'css-loader',
          // less-loader
          {
            loader: 'less-loader',
            options: {
              lessOptions: {
                // 替换antd的变量,去掉 @ 符号即可
                // https://ant.design/docs/react/customize-theme-cn
                modifyVars: {
                  'primary-color': '#1DA57A',
                  'border-color-base': '#d9d9d9', // 边框色
                  'text-color': '#d9d9d9'
                },
                javascriptEnabled: true, // 支持js
              },
            },
          },
        ],
      },
      {
        test: /\.(sass|scss)$/,
        use: [
          {
            loader: MiniCssExtractPlugin.loader,
          },
          {
            loader: 'css-loader',
            options: {
              importLoaders: 2,
              sourceMap: !!DEV,
            },
          },
          {
            loader: 'sass-loader',
            options: {
              sourceMap: !!DEV,
            },
          },
        ],
      },
      {
        test: /\.png/,
        type: 'asset/resource',
      },
      {
        test: /\.(woff|woff2|eot|ttf|otf)$/i,
        type: 'asset/resource',
      },
      {
        test: /\.(csv|tsv)$/i,
        use: ['csv-loader'],
      },
      {
        test: /\.xml$/i,
        use: ['xml-loader'],
      },
    ],
  },
  optimization: {
    minimizer: [
      new TerserPlugin({
        parallel: false,
        terserOptions: {
          output: {
            comments: false,
          },
        },
      }),
      new OptimizeCSSAssetsPlugin({}),
    ],
    minimize: !DEV,
    splitChunks: {
      minSize: 500000,
      cacheGroups: {
        vendors: false,
      },
    },
  },
  resolve: {
    modules: ['node_modules'],
    extensions: ['.json', '.js', '.jsx', '.ts', '.tsx', '.less', 'scss'],
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: path.join(__dirname, '/src/index.html'),
      filename: 'app.html',
      inject: 'body',
    }),
    DEBUG && new BundleAnalyzerPlugin(),
    new MiniCssExtractPlugin({
      filename: '[name].css',
      chunkFilename: '[name].css',
    }),
    new ESLintPlugin(),
    new ForkTsCheckerWebpackPlugin(),
  ].filter(Boolean),
};

总结

这篇文章主要记录了开发过程中从项目初始化开始,再到一个标准化前端项目的搭建路程。涉及相关代码规范、开发环境搭建、生产环境优化等,旨在打造出一个可快速使用的现代Webpack5.x+React18.x+Typescript+Antd4.x模板,以供在以后的实际业务场景需求中零成本使用。


本文由哈喽比特于2年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/4XxG4NB1dGnWiZdKxGsS5g

 相关推荐

刘强东夫妇:“移民美国”传言被驳斥

京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。

发布于:1年以前  |  808次阅读  |  详细内容 »

博主曝三大运营商,将集体采购百万台华为Mate60系列

日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。

发布于:1年以前  |  770次阅读  |  详细内容 »

ASML CEO警告:出口管制不是可行做法,不要“逼迫中国大陆创新”

据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。

发布于:1年以前  |  756次阅读  |  详细内容 »

抖音中长视频App青桃更名抖音精选,字节再发力对抗B站

今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。

发布于:1年以前  |  648次阅读  |  详细内容 »

威马CDO:中国每百户家庭仅17户有车

日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。

发布于:1年以前  |  589次阅读  |  详细内容 »

研究发现维生素 C 等抗氧化剂会刺激癌症生长和转移

近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。

发布于:1年以前  |  449次阅读  |  详细内容 »

苹果据称正引入3D打印技术,用以生产智能手表的钢质底盘

据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。

发布于:1年以前  |  446次阅读  |  详细内容 »

千万级抖音网红秀才账号被封禁

9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...

发布于:1年以前  |  445次阅读  |  详细内容 »

亚马逊股东起诉公司和贝索斯,称其在购买卫星发射服务时忽视了 SpaceX

9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。

发布于:1年以前  |  444次阅读  |  详细内容 »

苹果上线AppsbyApple网站,以推广自家应用程序

据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。

发布于:1年以前  |  442次阅读  |  详细内容 »

特斯拉美国降价引发投资者不满:“这是短期麻醉剂”

特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。

发布于:1年以前  |  441次阅读  |  详细内容 »

光刻机巨头阿斯麦:拿到许可,继续对华出口

据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。

发布于:1年以前  |  437次阅读  |  详细内容 »

马斯克与库克首次隔空合作:为苹果提供卫星服务

近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。

发布于:1年以前  |  430次阅读  |  详细内容 »

𝕏(推特)调整隐私政策,可拿用户发布的信息训练 AI 模型

据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。

发布于:1年以前  |  428次阅读  |  详细内容 »

荣耀CEO谈华为手机回归:替老同事们高兴,对行业也是好事

9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI操控无人机能力超越人类冠军

《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI生成的蘑菇科普书存在可致命错误

近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。

发布于:1年以前  |  420次阅读  |  详细内容 »

社交媒体平台𝕏计划收集用户生物识别数据与工作教育经历

社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”

发布于:1年以前  |  411次阅读  |  详细内容 »

国产扫地机器人热销欧洲,国产割草机器人抢占欧洲草坪

2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。

发布于:1年以前  |  406次阅读  |  详细内容 »

罗永浩吐槽iPhone15和14不会有区别,除了序列号变了

罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。

发布于:1年以前  |  398次阅读  |  详细内容 »
 相关文章
Android插件化方案 5年以前  |  237236次阅读
vscode超好用的代码书签插件Bookmarks 2年以前  |  8071次阅读
 目录