webpack5 手动搭建前端项目(react+antd + ts)

2,399 阅读8分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

第一步: 初始化项目

创建文件夹webpack_project cd 文件夹 webpack_project 执行命令

npm init

初始化项目得到package.json 文件

第二步:React和TypeScript的结合

1. 先编译一个简单的js 文件

安装webpack的依赖等 webpack
webpack-cli
webpack-dev-server:开发环境启动
webpack-merge:多个配置文件合并,之后对不同环境进行配置的时候会用得到

npm install --save-dev webpack webpack-cli webpack-dev-server webpack-merge 

1.1 src文件夹下创建 index.js文件 (ps: 随便写点就行了意思意思🤫)

新建文件夹 src(存放主文件) config (存放配置文件)

路径:src/index.js

const a = 'hello haha'

console.log(a)

1.2 在config文件夹下创建配置文件 webpack.config.js

路径: config/webpack.config.js

// webpack.config.js
const path = require('path')

module.exports = {
  target: 'web', // 默认打包成web平台的
  mode: 'production', // 环境 development 和 production 环境 链接: https://www.webpackjs.com/concepts/mode/#mode-development
  entry: path.resolve(__dirname, '../src/index.js'), // 文件的入口
  output: {
    filename: 'js/[name].[chunkhash:8].js', // 文件名
    path: path.resolve(__dirname, '../dist') // 文件输出地址
  }
}

1.3 修改package.json 文件中的 scripts 新增一条运行的命令 用来执行编译index.js 文件

"dev": "webpack --config ./config/webpack.config.js"

// package.json
{
  "name": "webpack_project",
  "version": "1.0.0",
  "description": "antd+ts",
  "main": "index.js",
  "scripts": {
    "dev": "webpack --config ./config/webpack.config.js"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "webpack": "^5.72.1",
    "webpack-cli": "^4.9.2",
    "webpack-dev-server": "^4.9.0",
    "webpack-merge": "^5.8.0"
  }
}

1.4 执行代码

npm run dev

得到index.js 编译之后的文件

image.png

2. 增加 react 和资源文件的转换等

2.1 安装 react 和 react 路由依赖。修改文件

npm install react react-dom --save-dev
npm install react-router-dom

在src 下新增文件app.jsx

// src/app.jsx
import React from 'react'

export default () => {
  return <div>
    hello world
  </div>
}

更改src/index.js 文件为src/index.jsx 且修改内容为下⬇️

// src/index.jsx
import React from 'react';
import ReactDom from 'react-dom';
import App from './app'

ReactDom.render(<App/>, document.querySelector('#root'))

修改config/webpack.config.js文件中入口改为index.jsx

// config/webpack.config.js
const path = require('path')

module.exports = {
  target: 'web',
  mode: 'production',
  // entry: path.resolve(__dirname, '../src/index.js'),
  entry: path.resolve(__dirname, '../src/index.jsx'),
  output: {
    filename: 'js/[name].[chunkhash:8].js',
    path: path.resolve(__dirname, '../dist')
  }
}

2.2 增加babel对react 进行转译为js使其能够执行在浏览器中运行

参考链接:
www.cnblogs.com/zhansu/p/13… juejin.cn/post/684516…
批量增加 proposal 语法支持目前没搞懂作用: juejin.cn/post/684490…

依赖说明参考链接
babel-loaderwebpack的loader插件-
@babel/core核心-
core-jscorejs版本3-
@babel/preset-env语法转换-
@babel/preset-react转换react-
@babel/polyfill补齐api的(未使用)-
babel-plugin-dynamic-import-node路由动态加载的-
@babel/runtime集成所有所有语法转换会用到的辅助函数-减少代码打包之后的体积-
@babel/plugin-transform-runtime@babel/runtime中辅助函数的自动替换-
@babel/plugin-transform-arrow-functions箭头函数的转换-
@babel/plugin-syntax-dynamic-import用以解析识别import()动态导入语法-
babel-plugin-import按需加载需要的-
@babel/plugin-proposal-decorators装饰器的使用image.png
@babel/plugin-proposal-class-properties支持类属性链接: blog.csdn.net/youlinhuany…
@babel/plugin-proposal-private-methods私有方法语法的编译链接:www.cnblogs.com/ZheOneAndOn…
@babel/plugin-proposal-object-rest-spread支持剩余扩展操作符-
@babel/plugin-syntax-import-meta暂时不知道proposal-
@babel/plugin-proposal-function-bind暂时不知道proposal-
@babel/plugin-proposal-json-strings暂时不知道proposal-
@babel/plugin-proposal-do-expressions暂时不知道-
@babel/plugin-proposal-nullish-coalescing-operator暂时不知道-
@babel/plugin-proposal-optional-chaining暂时不知道-

然后呢 我们先安装部分的转译的,其他的之后需要在安装

npm install babel-loader @babel/core @babel/preset-env @babel/preset-react @babel/runtime babel-plugin-dynamic-import-node @babel/plugin-transform-runtime @babel/plugin-transform-arrow-functions @babel/plugin-syntax-dynamic-import @babel/plugin-proposal-decorators @babel/plugin-proposal-class-properties @babel/plugin-proposal-private-methods @babel/plugin-proposal-object-rest-spread core-js --save-dev

安装完成之后在项目文件夹中新增.babelrc文件
(PS: 编译的时候默认会全局的查找babel的文件,名称可以为babelrc.js等。也可以不创建文件直接在loader中进行配置,或者是package.json 中添加配置)

// .babelrc
{
  "comments": false,
  "presets": [
    ["@babel/env", {
      "targets": { "browsers": ["> 1%", "last 2 versions", "not ie <= 8"] },
      "useBuiltIns": "usage",
      "corejs": 3,
      "loose": true
    }],
    "@babel/react"
  ],
  "plugins": [
    "dynamic-import-node",
    "@babel/plugin-transform-runtime",
    "@babel/plugin-transform-arrow-functions",
    "@babel/plugin-syntax-dynamic-import",
    "@babel/plugin-proposal-object-rest-spread",
    ["@babel/plugin-proposal-decorators", { "legacy": true }],
    ["@babel/plugin-proposal-class-properties", { "loose": true }],
    ["@babel/plugin-proposal-private-methods", { "loose": true }]
  ]
}

添加在文件中config/webpack.config.js 添加转译的loader 和 增加引用文件时可以忽略的后缀引入

// config/webpack.config.js
module.exports = {
    //...
    module: {
        rules: [
          {
            test: /\.(js|jsx)$/,
            exclude: /(node_modules|bower_components)/,
            use: [
              {
                loader: 'babel-loader',
                options: {
                  cacheDirectory: true
                }
              }
            ]
          }
        ]
    },
    resolve: {
       extensions: ['.jsx', '.js', '.css']
    }
}

image.png

2.3 将打包的好的react 文件放入index.html 中使其在浏览器中运行

在config下新增ejs的html模版 index.ejs(PS: 也可以使用index.html不一定用ejs的,看你自己)

// config/index.ejs
<!DOCTYPE html >
<html lang="zh">
  <head>
    <meta charset="UTF-8">
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta http-equiv="pragma" content="no-cache">
    <meta http-equiv="Cache-Control" content="no-store, must-revalidate">
    <meta http-equiv="expires" content="Wed, 26 Feb 1997 08:21:57 GMT">
    <meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no"/>
    <title><%= htmlWebpackPlugin.options.title %></title>
  </head>
  <body>
    <div id="root"></div>
  </body>
</html>

安装 webpack 插件 html-webpack-plugin:将打包编译好的文件注入到html 中,可以使用自定的模版页可以不使用模版
clean-webpack-plugin:清理 /dist 文件夹

npm install html-webpack-plugin clean-webpack-plugin --save-dev 

修改 config/webpack.config.js 配置 新增一下配置

// config/webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin')
const CleanWebpackPlugin = require('clean-webpack-plugin').CleanWebpackPlugin
// ......
plugins: [
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      title: '项目',
      filename: 'index.html',
      template: path.resolve(__dirname, './index.ejs'),
      hash: true,
      cache: false,
      inject: true,
      minify: {
        removeComments: true,
        removeAttributeQuotes: true,
        collapseWhitespace: true,
        minifyJS: true, // 在脚本元素和事件属性中缩小JavaScript(使用UglifyJS)
        minifyCSS: true // 缩小CSS样式元素和样式属性
      },
      nodeModules: path.resolve(__dirname, '../node_modules')
    }),
  ]

image.png

在package.json 中的 scripts 新增一条build命令

// package.json
"scripts": {
   "dev": "webpack --config ./config/webpack.config.js",
   "build": "webpack --config ./config/webpack.config.js"
},

执行命令:

npm run build

在dist 文件夹中可以看到编译出了 index.html 文件和js 文件夹(存放js的) image.png 在浏览器中打开index.html 可以看到代码中的hello world 已经出来了

image.png 在这呢 ⬇️

image.png

2.4 添加样式的等资源文件的加载

插件说明
style-loader行内样式转换
css-loadercss样式识别转换
file-loader加载图片和字体资源
postcss | postcss-loader不同的浏览器的前缀转换: css浏览器的兼容
autoprefixer自动编译转译的配合postcss
less | less-loaderless的转换:(ps)为之后添加antd的做准备,因为antd使用的less的所以直接就用less了

样式: sass stylus 这几种写法目前我没用,加载方式都差不多,看个人喜好

要导入 CSV、TSV 和 XML,你可以使用 csv-loader 和 xml-loader

依赖安装一波

npm install --save-dev style-loader css-loader file-loader postcss postcss-loader less less-loader autoprefixer

在项目中新增postcss的配置文件: postcss.config.js
在编译时会寻找全局的文件自动加上浏览器的前缀(PS: 我习惯用文件进行配置,也可以直接在loader中进行配置postcss-loader) 设置支持哪些浏览器,必须设置支持的浏览器才会自动添加浏览器兼容

// postcss.config.js
module.exports = {
  ident: 'postcss',
  plugins: [
    require('autoprefixer')
  ]
}
// package.json 中增加支持的浏览器
//...
"browserslist": [
    "> 1%",
    "last 2 versions",
    "not ie <= 8"
],
//...

image.png

在config/webpack.config.js文件中的module 下 rules新增一下转译

// config/webpack.config.js
{
    test: /\.(png|svg|jpg|gif)$/, // 图片
    use: [
      {
        loader: 'file-loader',
        options: {
          name: 'assets/images/[name].[ext]' // 存放的位置: dist/assets/images/文件
        }
      }
    ]
  },
  {
    test: /\.(woff|woff2|eot|ttf|otf)$/, // 字体
    use: [
      {
        loader: 'file-loader',
        options: {
          name: 'assets/fonts/[name].[ext]'// 存放的位置: dist/assets/fonts/文件
        }
      }
    ]
  },
  {
    test: /\.css$/, // css 样式
    use: [
      'style-loader',
      'css-loader',
      'postcss-loader'
    ]
  },
  {
    test: /\.less$/i, // less 样式
    use: [
      'style-loader',
      'css-loader',
      'postcss-loader',
      'less-loader'
    ]
  },

在src 下新增app.css 同时 app.jsx修改 引入样式文件

// app.css
.div-container {
  margin: 0;
  padding: 0;
}
.div-container .div-btn {
  width: 100px;
  height: 30px;
  border-radius: 4px;
  text-align: center;
  line-height: 30px;
  font-size: 14px;
  text-align: center;
  border: 1px solid #108EE9;
  transition: all; // 测试自动加浏览器前缀使用
}

// app.jsx
import React from 'react'
import './app.css'

export default () => {
  return <div className='div-container'>
    <p>hello world</p>
    <div className='div-btn'>跳转</div>
  </div>
}

执行命令: npm run build在浏览器中查看index.html 文件,可以看到样式生效且引入成功

image.png

但是仔细查看会发现样式被打包进入科main.js 文件中

image.png 所以接下来把样式文件拆分一下吧

2.4.1 样式分离

样式分离的话用的这个插件: mini-css-extract-plugin 安装一波

npm install --save-dev mini-css-extract-plugin

然后修改config/webpack.config.js配置

// config/webpack.config.js
// ....
const MiniCssExtractPlugin = require('mini-css-extract-plugin')

module.exports = {
    // ....
    plugins: [
        //...
        new MiniCssExtractPlugin({ filename: 'css/[name].[contenthash:8].css' }), // 设置文件存放的位置和名称
        //...
    ],
    module: {
        rules: [
          //....
          {
            test: /\.css$/,
            use: [
              MiniCssExtractPlugin.loader, // 'style-loader', 因为我们直接用的 mode是'production' 所以就直接替换了
              'css-loader',
              'postcss-loader'
            ]
          },
          {
            test: /\.less$/i,
            use: [
              MiniCssExtractPlugin.loader, // 'style-loader',
              'css-loader',
              'postcss-loader',
              'less-loader'
            ]
          },
          // ....
        ]
    },
    //...
}

执行一波命令之后,可以看到样式被分离了 image.png

2.5 创建开发环境:方便开发调试等

将config/webpack.config.js 文件复制一份,重命名为webpack.dev.js。并且进行调整

// webpack.dev.js
// ...
module.exports = {
  target: 'web',
  mode: 'development', // 修改为 development
  entry: path.resolve(__dirname, '../src/index.jsx'),
  output: {
    filename: '[name].[hash:8].js', // 修改名称命名采用hash
    path: path.resolve(__dirname, '../dist')
  },
  plugins: [
    // new CleanWebpackPlugin(), 可以删除
    new MiniCssExtractPlugin({ filename: 'css/[name].css' }), // 修改名称命名
    //...
  ],
  module: {
    rules: [
      //...
      ,
      {
        test: /\.css$/,
        use: [
          'style-loader',
          'css-loader',
          'postcss-loader'
        ]
      },
      {
        test: /\.less$/i,
        use: [
         'style-loader',
          'css-loader',
          'postcss-loader',
          'less-loader'
        ]
      },
      //...
    ]
  },
  //...
  devServer: { // 新增webpack-dev-server 的配置
    headers: { 'Access-Control-Allow-Origin': '*' },
    hot: true, // 热更新
    host: '127.0.0.1', // 地址
    port: '8081', // 端口
    open: true, // 是否自动打开
    setupExitSignals: true,
    compress: true
  }
}

复制更改之后的前后对比 image.png

修改package.json中scripts的dev命令为

 // "dev": "webpack --config ./config/webpack.config.js" 删除了不要了
 "dev": "webpack-dev-server --config ./config/webpack.dev.js",

执行命令

npm run dev

自动打开了浏览器 地址:http://127.0.0.1:8081 image.png 开发环境搭建完成了

3. 增加TypeScript

ps: 先增加TypeScript, 接下来全用js写的话然后又把文件转换成ts的写法,太麻烦了

3.1 安装 TS 所需依赖, 同时将配置也替换成ts编译

依赖包说明是否用啦
typescript主要的包使用
ts-node用来编译ts文件(ps:使用ts的配置文件的时候需要用)使用
@babel/preset-typescriptbabel来对ts进行转译使用
ts-loaderts的转译暂未使用
awesome-typescript-loaderts的转译暂未使用

ps: 因为我使用的是babel来进行转译的js和一些es6的语法的支持等,所以就直接使用@babel/preset-typescript 啦,ts-loader 和 awesome-typescript-loader的话,你们自己看情况用吧 参考的链接

安装一波依赖

npm install --save-dev typescript ts-node @babel/preset-typescript

3.1.1 新增tsconfig.json文件配置

在项目的目录下新建tsconfig.json文件

在tsconfig.json文件中我增加了ts-node的配置项,他的配置和正常的ts配置一样,只是我们在执行使用ts-node执行命令的时候会优先匹配这个配置来解析配置文件, 开发配置文件的配置和项目的配置是不相同的

tsconfig.json的配置参考

// tsconfig.json
{
  "ts-node": { // 这个是属于ts-node去编译配置文件的时候会执行的配置和开发的配置有所不同
    "compilerOptions": {
      "module": "commonjs",
      "target": "es5",
      "sourceMap": true,
      "removeComments": true,
      "strict": true,
      "noImplicitAny": true,
      "strictNullChecks": true,
      "moduleResolution": "node",
      "baseUrl": "./",
      "typeRoots": ["node_modules/@types"],
      "esModuleInterop": true
    },
    "exclude": ["node_modules"],
    "include": [
      "config/*"
    ]
  },
  "compilerOptions": {
    "target": "es6", 
    "module": "esnext",
    "jsx": "react",
    "sourceMap": true,
    "removeComments": true,
    "strict": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "moduleResolution": "node",
    "baseUrl": "./",
    "typeRoots": ["node_modules/@types"],
    "allowSyntheticDefaultImports": true,
    "esModuleInterop": true,
    "experimentalDecorators": true
  },
  "exclude": ["node_modules", "config/*"],
  "include": ["src/*"]
}

3.1.2 修改入口文件

app.jsx => app.tsx
index.jsx => index.tsx
修改index.jsx为index.tsx, 修改文件后缀之后引入的依赖会存在红线报错,这个时候提示我们没有找到ts的声明文件,现在就需要安装react 对应的声明文件的依赖

image.png

 npm i --save-dev @types/react  @types/react-dom

3.1.3 修改配置文件

webpack.dev.js => webpack.dev.ts
webpack.config.js => webpack.config.ts

webpack.dev.ts 内容进行如下修改 ⬇️

注意:之前文件中的module.exports 的写法被我替换成了const config: Configuration。写成了TypeScript的写法。因为使用ts来进行配置文件的编写,所有webpack 是无法直接识别ts的语法的,所以使用的是ts-node 来进行ts的转译之后进行node的方式运行的webpack。

//  webpack.dev.ts
import path from 'path'
import webpack, { Configuration } from 'webpack'
import WebpackDevServer from 'webpack-dev-server'
import HtmlWebpackPlugin from 'html-webpack-plugin'
import { CleanWebpackPlugin } from 'clean-webpack-plugin'
import MiniCssExtractPlugin from 'mini-css-extract-plugin'

const config: Configuration = {
    //...
    entry: path.resolve(__dirname, '../src/index.tsx'), // 替换成tsx
    //...
    module: {
        rules: [
            //...,
            {
                test: /\.(ts|tsx)$/,// 替换成ts|tsx
                exclude: /(node_modules|bower_components)/,
                use: [
                  {
                    loader: 'babel-loader',
                    options: {
                      cacheDirectory: true
                    }
                  }
                ]
             }
            //...
        ]
    },
    resolve: {
      // 将.js|.jsx 删除 替换成.ts|.tsx新增加 .less
      extensions: ['.tsx', '.js', '.ts', '.less', '.css']
    }
}
// 之前是直接命令行之行的代码,因为使用ts-node 所以使用ts运行webpack-dev-server
const devserver = new WebpackDevServer({
  headers: { 'Access-Control-Allow-Origin': '*' },
  hot: true, // 热更新
  host: '127.0.0.1', // 地址
  port: '8081', // 端口
  open: true, // 是否自动打开
  setupExitSignals: true,
  compress: true
}, webpack(config))
// 启动
devserver.start()

webpack.config.ts 内容进行如下修改 ⬇️

// webpack.config.ts
import path from 'path'
import webpack, { Configuration } from 'webpack'
import HtmlWebpackPlugin from 'html-webpack-plugin'
import { CleanWebpackPlugin } from 'clean-webpack-plugin'
import MiniCssExtractPlugin from 'mini-css-extract-plugin'

const config:Configuration = {
  target: 'web',
  mode: 'production',
  entry: path.resolve(__dirname, '../src/index.tsx'),
  //...
  module: {
    rules: [
      //...,
      {
        test: /\.(ts|tsx)$/,
        exclude: /(node_modules|bower_components)/,
        use: [
          {
            loader: 'babel-loader',
            options: {
              cacheDirectory: true
            }
          }
        ]
      }
    ]
  },
  resolve: {
    extensions: ['.tsx', '.js', '.ts', '.less', '.css']
  }
}
// 运行 webpack 和输出日志
webpack(config, (err:any, state:any) => {
  if (err) {
    console.log(err.stack || err)
  } else if (state.hasErrors()) {
    let err = ''
    state.toString({
      chunks: false,
      colors: true
    }).split(/\r?\n/).forEach((line:any) => {
      err += `    ${line}\n`
    })
    console.warn(err)
  } else {
    console.log(state.toString({
      chunks: false,
      colors: true
    }))
  }
})

webpack.dev.ts 修改前后对比, webpack.config.ts我就不贴代码了

image.png

修改完成之后我们就可以直接在设置值的时候,能够知道需要输入的类型啦

image.png

3.1.4 在.babelrc 中新增对应的插件

// .babelrc
{
  "comments": false,
  "presets": [
    ["@babel/env", {
      "targets": { "browsers": ["> 1%", "last 2 versions", "not ie <= 8"] },
      "useBuiltIns": "usage",
      "corejs": 3,
      "loose": true
    }],
    "@babel/react",
    "@babel/typescript" // 增加转译
  ],
  //...
}

3.1.5 修改packgae.json 中的命令

修改命令为以下 ⬇️,然后执行命令npm run dev

// packgae.json
"scripts": {
    // "dev": "webpack-dev-server --config ./config/webpack.dev.js",
    // "build": "webpack --config ./config/webpack.config.js"
    "dev": "ts-node ./config/webpack.dev.ts",
    "build": "ts-node ./config/webpack.config.ts"
  },

运行成功 撒花 🌹💐💐💐💐💐💐 image.png

3.2 配置文件优化

写到这之后发现了文件存在很多代码都是可以复用的, 那就优化一波吧,之前安装webpack的时候时候安装了一个 webpack-merge 的依赖,这个时候就能用到啦

首先创建一个公共的配置文件 webpack.web.ts 存放在config/web路径下

⚠️注意入口文件路径和其他的文件路径

// webpack.web.ts
import path from 'path'
import { Configuration } from 'webpack'
import HtmlWebpackPlugin from 'html-webpack-plugin'
import MiniCssExtractPlugin from 'mini-css-extract-plugin'

export const ConfigInit = (mode: "development" | "production"):Configuration => {
  const isPro:boolean = mode === 'production'
  // 样式的数组的
  const cssLoaderAry:string[] = [
    isPro ? MiniCssExtractPlugin.loader : 'style-loader',
    'css-loader',
    'postcss-loader'
  ]
  return {
    target: 'web',
    mode,
    entry: path.resolve(__dirname, '../../src/index.tsx'),
    output: {
      filename: isPro ? 'js/[name].[chunkhash:8].js' : '[name].[hash:8].js',
      path: path.resolve(__dirname, '../../dist')
    },
    plugins: [
      new MiniCssExtractPlugin({ filename: isPro ? 'css/[name].[contenthash:8].css' : 'css/[name].css' }),
      new HtmlWebpackPlugin({
        title: '项目',
        filename: 'index.html',
        template: path.resolve(__dirname, './index.ejs'),
        hash: true,
        cache: false,
        inject: true,
        minify: {
          removeComments: true,
          removeAttributeQuotes: true,
          collapseWhitespace: true,
          minifyJS: true, // 在脚本元素和事件属性中缩小JavaScript(使用UglifyJS)
          minifyCSS: true // 缩小CSS样式元素和样式属性
        },
        nodeModules: path.resolve(__dirname, '../../node_modules')
      }),
    ],
    module: {
      rules: [
        {
          test: /\.(png|svg|jpg|gif)$/,
          use: [
            {
              loader: 'file-loader',
              options: {
                name: 'assets/images/[name].[ext]'
              }
            }
          ]
        },
        {
          test: /\.(woff|woff2|eot|ttf|otf)$/,
          use: [
            {
              loader: 'file-loader',
              options: {
                name: 'assets/fonts/[name].[ext]'
              }
            }
          ]
        },
        {
          test: /\.css$/,
          use: cssLoaderAry
        },
        {
          test: /\.less$/i,
          use: [
            ...cssLoaderAry,
            'less-loader'
          ]
        },
        {
          test: /\.(ts|tsx)$/,
          exclude: /(node_modules|bower_components)/,
          use: [
            {
              loader: 'babel-loader',
              options: {
                cacheDirectory: true
              }
            }
          ]
        }
      ]
    },
    resolve: {
      extensions: ['.tsx', '.js', '.ts', '.less', '.css']
    }
  }
}

修改webpack.dev.ts

// webpack.dev.ts
import webpack, { Configuration } from 'webpack'
import WebpackDevServer from 'webpack-dev-server'
import { ConfigInit } from './web/webpack.web'

// 开发环境的配置文件
const config:Configuration = ConfigInit('development')

const devserver = new WebpackDevServer({
  headers: { 'Access-Control-Allow-Origin': '*' },
  hot: true, // 热更新
  host: '127.0.0.1', // 地址
  port: '8081', // 端口
  open: true, // 是否自动打开
  setupExitSignals: true,
  compress: true
}, webpack(config))

devserver.start()

修改webpack.config.ts并重命名为webpack.pro.ts
ps: 注意修改命令中的文件地址

import webpack, { Configuration } from 'webpack'
import { CleanWebpackPlugin } from 'clean-webpack-plugin'
import { merge } from 'webpack-merge' // 文件合并
import { ConfigInit } from './web/webpack.web'

const config:Configuration = merge(ConfigInit('production'), {
  plugins: [
    new CleanWebpackPlugin()
  ]
})

webpack(config, (err:any, state:any) => {
  if (err) {
    console.log(err.stack || err)
  } else if (state.hasErrors()) {
    let err = ''
    state.toString({
      chunks: false,
      colors: true
    }).split(/\r?\n/).forEach((line:any) => {
      err += `    ${line}\n`
    })
    console.warn(err)
  } else {
    console.log(state.toString({
      chunks: false,
      colors: true
    }))
  }
})

修改完成之后如下 ⬇️ 注意名称修改了,运行命令的也要修改 image.png

4. 项目优化

4.1 ts图片样式的引入

创建资源文件夹src/assets并添加了一个图片

image.png

在app.tsx 中引用图片会提示没有相应的声明类型

image.png 那就解决吧

在项目目录下创建一个文件夹typings存放一个全局的声明,创建global.d.ts文件 顺便把less 的也声明了

// global.d.ts
declare module '*.less' {
  const content: any
  export = content
}
declare module '*.png'
declare module '*.jpg'
declare module '*.jpeg'
declare module '*.gif'
declare module '*.bmp'
declare module '*.tiff'
declare module '*.pdf'

declare module '*.svg' {
  const content: any
  export default content
}

修改tsconfig.json 配置文件, 在typeRoots 和 include 中新增typings/*.d.ts

{
  //...,
  "compilerOptions": {
    //...
    "typeRoots": ["typings/*.d.ts", "node_modules/@types"],
    //...
  },
  //...
  "include": ["src/*", "typings/*.d.ts"]
}

好啦,这下就可以啦。不报错了

运行命令,图片引入成功

image.png

将css 修改称less 引入

image.png

同样引入成功

4.2 ts文件路径的优化

在目前的项目中我们在引用路径的时候,需要加上(./)或者(../../)多层的这个去找到对应的文件

为了方便和美观,可以在配置中加上这个配置

在webpack.web.ts 中加上以下配置和在tsconfig.json 中加上配置

// webpack.web.ts
//...
resolve: {
  alias: {
    '@assets': path.resolve(__dirname, '../../src/assets'),
  },
  extensions: ['.tsx', '.js', '.ts', '.less', '.css']
}
//...

// tsconfig.json
//...
"paths": {
  "@assets/*": ["src/assets/*"]
},
//...

image.png

image.png

然后替换项目中的路径,重启项目,然后OK 🎉撒花

image.png

4.3 环境变量的设置

在项目的开发中会判断不同的环境,开发环境 测试环境 正式环境,这就需要一个不同的值来进行判断了

我这边是用到了cross-env这个来区分不同的环境

npm install cross-env --save-dev

在命令中加入tag来区分不同的环境 dev:开发 tes:测试 pro:正式

"scripts": {
    "dev": "cross-env tag=dev ts-node ./config/webpack.dev.ts",
    "build:tes": "cross-env tag=tes ts-node ./config/webpack.pro.ts",
    "build:pro": "cross-env tag=pro ts-node ./config/webpack.pro.ts"
},

image.png

修改配置文件webpack.web.ts 和 typings/global.d.ts新增配置

// webpack.web.ts
//...
plugins: [
  //...
  new DefinePlugin({
    'process.env': {
      tag: JSON.stringify(process.env.tag),
      version: JSON.stringify(packages.version)
    }
  }),
],
//...

// typings/global.d.ts
//...
declare module 'process' {
  global {
    namespace NodeJS {
      export interface ProcessEnv {
        tag: 'dev' | 'tes' | 'pro'
        version: string
      }
    }
  }
}

image.png

image.png 测试输出 可以啦 image.png image.png

4.4 代码压缩优化等

依赖包 ⬇️

依赖说明
terser-webpack-plugin用于处理 js 的压缩和混淆
css-minimizer-webpack-plugin压缩css文件
compression-webpack-plugin预先准备的资源压缩版本,使用 Content-Encoding 提供访问服务
copy-webpack-plugin复制静态的文件: 存在文件才能复制
npm install terser-webpack-plugin css-minimizer-webpack-plugin compression-webpack-plugin copy-webpack-plugin --save-dev

修改webpack.pro.ts 文件 简单的配置一波压缩优化

import webpack, { Configuration, BannerPlugin, LoaderOptionsPlugin } from 'webpack'
import { CleanWebpackPlugin } from 'clean-webpack-plugin'
// import CopyWebpackPlugin from 'copy-webpack-plugin' // 复制静态资源使用
import CompressionWebpackPlugin from 'compression-webpack-plugin'
import TerserPlugin from 'terser-webpack-plugin'
import CssMinimizerPlugin from 'css-minimizer-webpack-plugin'
import { merge } from 'webpack-merge'
import { ConfigInit } from './web/webpack.web'

const config:Configuration = merge(ConfigInit('production'), {
  plugins: [
    new CleanWebpackPlugin(),
    new CompressionWebpackPlugin(),
    // new CopyWebpackPlugin({
    //   patterns: [{ from: 'statics', to: 'statics' }]
    // }),
    new LoaderOptionsPlugin({
      minimize: true
    }),
    new BannerPlugin('版权所有,翻版必究')
  ],
  optimization: {
    splitChunks: {
      chunks: 'all'
    },
    runtimeChunk: {
      name: 'mainifels'
    },
    minimize: true,
    minimizer: [
      new TerserPlugin(),
      new CssMinimizerPlugin()
    ]
  },
  performance: {
    hints: false,
    maxAssetSize: 4000000, // 整数类型(以字节为单位)
    maxEntrypointSize: 5000000 // 整数类型(以字节为单位)
  }
})
//....省略

image.png

4.5 开发环境浏览器打开在同一个tab页打开

运行命令的时候重启一次打开一个tab 页很烦,所以呢优化一下

参考:create-react-app 的启动方式

复制出这两个文件也可看当前项目的源码
openBrowser.js
openChrome.applescript

修改配置文件webpack.dev.ts如下 ⬇️

import webpack, { Configuration } from 'webpack'
import WebpackDevServer from 'webpack-dev-server'
import { ConfigInit } from './web/webpack.web'

const openBrowser = require('./util/openBrowser')

// 开发环境的配置文件
const config:Configuration = ConfigInit('development')

const host:string = '127.0.0.1'
const port:string = '8081'

const devserver = new WebpackDevServer({
  headers: { 'Access-Control-Allow-Origin': '*' },
  hot: true, // 热更新
  host: host, // 地址
  port: port, // 端口
  // open: true, // 关闭
  setupExitSignals: true,
  compress: true
}, webpack(config))

devserver.start().then(() => {
  // 启动界面
  openBrowser(`http://${host}:${port}`)
})

记得关闭webpack-dev-server的配置中的自动打开 open: false 或者注释
在ts中引用js 采用的require的形式 image.png

运行命令:npm run dev

完结撒花 🌹🌹

4.6 构建优化

在项目越做越大的时候,项目文件太多,在打包编译时,运行会越来越慢

  1. 提前打包需要编译的包, 分离第三方的包(DllPlugin 和 DllReferencePlugin)
  2. 编译使用多线程进行编译(webpack5之前的可以使用happypack进行优化,但是webpack5就不行了,官方不在维护happypack,推荐使用: thread-loader

4.6.1 提前打包需要编译的包

安装依赖

依赖说明
add-asset-html-webpack-plugin向html中插入指定的script
npm install add-asset-html-webpack-plugin --save-dev 

新增配置文件webpack.dll.ts对第三方的包进行抽离

DllPlugin 在当前配置文件中使用,打包好的文件输出在config/dll目录下

import path from 'path'
import webpack, { Configuration, DllPlugin } from 'webpack'
import { CleanWebpackPlugin } from 'clean-webpack-plugin'

export const WebpackDllConfig:Configuration = {
  target: 'web',
  mode: 'production',
  entry: {
    reactrouterdom: 'react-router-dom',
    react: 'react',
    reactdom: 'react-dom'
  },
  output: {
    path: path.resolve(__dirname, 'dll'),
    publicPath: './',
    filename: '[name].js',
    library: '[name]_library_wcr'
  },
  plugins: [
    new CleanWebpackPlugin(),
    new DllPlugin({
      path: path.join(__dirname, 'dll', '[name].manifest.json'),
      name: '[name]_library_wcr'
    })
  ]
} 

webpack(WebpackDllConfig, (err:any, state:any) => {
  if (err) {
    console.log(err.stack || err)
  } else if (state.hasErrors()) {
    let err = ''
    state.toString({
      chunks: false,
      colors: true
    }).split(/\r?\n/).forEach((line:any) => {
      err += `    ${line}\n`
    })
    console.warn(err)
  } else {
    console.log(state.toString({
      chunks: false,
      colors: true
    }))
  }
})

增加命令:build:dll

 "scripts": {
    //...
    "build:dll": "ts-node ./config/webpack.dll.ts",
    //...
  },

修改webpack.pro.ts配置

import path from 'path'
import webpack, { Configuration, BannerPlugin, LoaderOptionsPlugin, DllReferencePlugin } from 'webpack'
//...

const WebpackDllConfig = require('./webpack.dll').WebpackDllConfig

// 提前打包好的信息 进行引用,循环遍历设置
const dllKey:string[] = Object.keys(WebpackDllConfig.entry)
const dllPluginsAry:any[] = [] // DllReferencePlugin数组
const AddAssetHtmlPluginAry:any[] = [] // AddAssetHtmlPlugin 插入的数组
for (let index = 0; index < dllKey.length; index++) {
  const key = dllKey[index]
  AddAssetHtmlPluginAry.push({
    filepath: path.resolve(__dirname, `./dll/${key}.js`),
    publicPath: './dll',
    outputPath: 'dll'
  })
  dllPluginsAry.push(new DllReferencePlugin({
    manifest: require(path.join(__dirname, './dll/', `${key}.manifest.json`))
  }))
}

const config:Configuration = merge(ConfigInit('production'), {
  plugins: [
    //...
    ...dllPluginsAry,  // DllReferencePlugin的使用
    // 插入指定的js
    new AddAssetHtmlPlugin(AddAssetHtmlPluginAry),
    //...
  ],
  //...
})
//...

配置完成之后如图, 注意配置的路径和AddAssetHtmlPlugin 输出的路径

image.png

注意先执行分离打包在进行编译

npm run build:dll
npm run build:tes

结果如下 ⬇️

image.png

4.6.2 多进程编译

安装依赖

依赖说明
thread-loader多进程进行编译, 放在耗时的 loader之前
npm install --save-dev thread-loader

image.png

第三步 增加antd

antd 参考链接

1. 安装依赖

依赖说明
babel-plugin-import按需加载使用的
antd主要的
npm install antd
npm install babel-plugin-import --save-dev

2. 修改config/web/webpack.web.ts下的配置

//...
{
 test: /\.less$/i,
 use: [
   ...cssLoaderAry,
   {
     loader: 'less-loader',
     options: {
       lessOptions: {
         exclude: /node_modules/,
         // modifyVars: theme, // 自定义主题的
         javascriptEnabled: true
       }
     }
   }
 ]
},
//...

3. 修改.babelrc文件

{
  "comments": false,
  "presets": [
    ["@babel/env", {
      "targets": { "browsers": ["> 1%", "last 2 versions", "not ie <= 8"] },
      "useBuiltIns": "usage",
      "corejs": 3,
      "loose": true
    }],
    "@babel/react"
  ],
  "plugins": [
    ["import", { "libraryName": "antd", "style": "css" }, "antd"], // 新增这个
    "dynamic-import-node",
    "@babel/plugin-transform-runtime",
    "@babel/plugin-transform-arrow-functions",
    "@babel/plugin-syntax-dynamic-import",
    "@babel/plugin-proposal-object-rest-spread",
    ["@babel/plugin-proposal-decorators", { "legacy": true }],
    ["@babel/plugin-proposal-class-properties", { "loose": true }],
    ["@babel/plugin-proposal-private-methods", { "loose": true }]
  ]
}

4. 在app.tsx 中引入 完成

image.png

image.png

第四步 代码检查和规范

为了规范代码的格式 eslint 和 tslint 这个有空在更新吧🥱

结束

第一次写,写得有遗漏的请指出。不明白的或者有问题欢迎指出👏

项目地址: Github 地址