从零开始搭建一个 React 项目 -- 配置篇(一)

从零开始搭建一个 React 项目 – 配置篇(一)

参考资源: 从零搭建完整的React项目模板(Webpack + React hooks + Mobx + Antd)

1. 项目初始化及常用以来安装配置

(1)创建名为 react-admin-demo 的目录,用于保存 react 工程;

(2)在项目目录下执行 npm init 创建并初始化 package.json 文件,根据实际项目需求设置 name, version, main 等属性:

name: 项目名称;

version: 项目版本;

main: 项目入口文件,通常默认为 index.js;

author: 项目作者

. . .

(3)安装打包依赖:webpack, webpack-cli, webpack-merge

webpack 相关教程:

!https://www.codercto.com/a/23518.html

webpack是一个前端构建工具。那么什么是构建 工具 呢?

前端构建工具就是把开发环境的代码转化成运行环境代码。一般来说,开发环境的代码是为了更好的阅读,而运行环境的代码则是为了能够更快地执行。因此开发环境和运行环境的代码形式也不相同。比如,开发环境的代码,要通过混淆压缩后才能放在线上运行,这样代码体积更小,而且对代码执行也不会有任何影响。一般需要构建工具处理的几种情况:

代码压缩:
将JS、CSS代码混淆压缩,让代码体积更小,加载更快

编译语法:
编写CSS时使用Less、Sass,编写JS时使用ES6、TypeScript等,这些标准目前都无法被浏览器兼容,因此需要构建工具编译,例如使用Babel编译ES6语法。

处理模块化:
CSS和JS的模块化语法,目前都无法被浏览器兼容。因此开发环境可以使用既定的模块化语法,但是需要构建工具将模块化语法编译为浏览器可识别形式。例如使用webpack、Rollup等处理JS模块化。

(4)创建 webpack build 配置文件:build/webpack.common.js,build/webpack.dev.js,build/webpack.prod.js

webpack.common.js:公共配置文件 – 抽离出公共的部分,通过 merge 在dev prod 文件内进行合并

webpack.dev.js:研发环境配置文件

webpack.prod.js:生产环境配置文件

(5)配置 html 模板:yarn add -D html-webpack-plugin

该插件的两个主要作用:

  • 为html文件中引入的外部资源如script、link,动态添加每次compile后的hash,防止引用缓存的外部文件问题

  • 可以生成创建html入口文件,比如单页面可以生成一个html文件入口,配置N个html-webpack-plugin可以生成N个页面入口

简而言之,插件的基本作用就是生成html文件

// ./build/webpack.common.js

const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");

const srcDir = path.join(__dirname, "../src");

module.exports = {
    // 配置 html 模板
    plugins: [
        new HtmlWebpackPlugin({
            template: `${srcDir}/index.html`
        }) 
    ]
}

(6)配置本地服务及热更新:yarn add -D webpack-dev-server clean-webpack-plugin

主要功能:webpack-dev-server是webpack官方提供的一个小型Express服务器。使用它可以为webpack打包生成的资源文件提供web服务。

webpack-dev-server 主要提供两个功能:

  • 为静态文件提供服务

  • 自动刷新和热替换(HMR): 不刷新网页的情况下, 改变代码后, 会自动编译并更新页面内容

//  ./build/webpack.dev.js

const { merge } = require("webpack-merge");
const { NamedModulesPlugin, HotModuleReplacementPlugin} = require("webpack");

const commonConfig = require("./webpack.common");

module.exports = merge(commonConfig, {

    // 开发模式
    mode: "development",

    // 配置本地服务及热更新
    devServer: {        // 本地服务配置
        port: 3000,       // 当前服务在本地运行在3000端口
        hot: true,          // 是否开启热更新:即本地代码修改后是否自动更新浏览器页面
        // open: 该属性用于DevServer启动且第一次构建完成时,自动使用系统默认浏览器去打开网页。
        open: false,
        // historyApiFallback 用于应对返回404页面时定向跳转到特定页面
        // 一般应用在 HTML5中History API 的单页应用,比如在访问路由的
        // 时候,若访问不到该路由会跳转到index.html页面。
        historyApiFallback: true,
        // compress: 该属性是一个布尔型的值,默认为false,当他为true的
        // 时候,它会对所有服务器资源采用gzip进行压缩。
        compress: true,
        // proxy 实现跨域: 我们使用webpack在本地启动服务器的时候,访问的
        // 域名通常是 http://localhost:3000 这样的,但是服务端的接口是其他的,
        // 那么就存在域名或端口号跨域的情况下, 幸运的是 devServer有一个叫proxy
        // 的配置项,可以通过该配置来解决跨域的问题,因为 dev-server 使用了 
        // http-proxy-middleware 包
        proxy: {    // 代理
                "/api": {
                    // target: 访问的目标域名或 ip:port
                    target: "https://www.easy-mock.com/mock/5dff0acd5b188e66c6e07329/react-template",
                    changeOrigin: true,     // 是否跨域
                    // secure: false,      // https 的时候 使用该参数
                    pathRewrite: { "^/api": "" }    // 重写路径
                }
        }
    },

    plugins: [
        // 在热加载时直接返回更新文件名,而不是文件的id
        new NamedModulesPlugin(),
        // 不刷新网页的情况下, 改变代码后, 会自动编译并更新页面内容
        new HotModuleReplacementPlugin()
    ]
});

(7)配置 Babel

yarn add -D babel-loader @babel/core @babel/plugin-transform-runtime @babel/preset-env @babel/preset-react babel-plugin-import @babel/plugin-proposal-class-properties @babel/plugin-proposal-decorators

为什么使用 Babel ?

webpack中,默认只能处理一部分ES6(2015)新语法,一部分更高级的ES6语法或者ES7语法,weback是处理不了的,这时候就要借助第三方的loader,来帮助webpack处理这些高级的语法,当第三方loader将这些语法处理完毕后,就会交给webpack进行打包到bundle.js文件里面。

怎样使用 Babel ?

使用 Babel 需要进行一下配置:

  • Step1. 安装依赖包:

yarn add -D babel-loader: babel-loader 与其他的 loader 一样,用来实现对特定文件的处理;

yarn add -D babel-core: babel-core 提供 babel-loader 处理的api, 也就是说, 在 babel-loader 对文件进行处理的时候,实际上是调用 babel-core 提供的 api 进行 语法树与代码 之间的转换;

yarn add -D babel-preset-env: babel-preset-env 用来告诉 babel 使用哪种规则对文件进行处理

  • Step2. 配置 babel 规则

以上步骤仅仅安装了 babel 对文件处理的依赖包, 还需要配置 babel 规则使 babel 起作用

配置 babel 规则有两种方式,一种是在 package.json 文件中设置 babel: {},另一种是在项目根目录下创建 .babelrc 文件写入配置.

a) 在 package.json 中配置:

// package.json

"babel": {
   "presets": [],
   "plugins": []
}

b) 新建 .babelrc 文件写入配置:

// .babelrc

{
    "presets": ["env"]
}

“presets” 属性字段设定转码规则,事实上,babel有几种规则都可以实现对ES6语法的转码,如babel-preset-es2015、babel-preset-latest、babel-preset-env,不过官方现已建议采用babel-preset-env.

babel/preset-env 选项说明, useBuiltIns: “usage” | “entry” | false

a). false: 默认值。不对polyfill处理,全部引入。

b). usage: 按需加载。

c). entry 在入口文件手工引入polyfill。根据配置的target浏览器,引入浏览器不兼容的polyfill,这里需要指定 core-js的版本

“plugins” 属性设置使用到的插件。在本项目中只需将 “babel” 属性 的 “presets” 设置为[ “env” ]即可

上面 presets 与 plugins 的设置告诉npm本项目将使用babel,并且使用 bable-preset-env 规则进行转码,即实现对ES2015+语法进行转码。

  • Step3. 创建并配置 webpack.config.js 文件

经过 Step1 和 Step2 的配置后, webpack仍然不知道何时使用该规则,这便需要使用webpack.config.js文件。这个文件的作用是对webpack打包的参数进行配置。

// ./build/webpack.config.js

module.exports = {
    module: {
        rules: {
            test: /\.js$/,
            exclude: /node_modules/,
            use: ["babel-loader"]
        }
    }
}

这就告诉webpack打包时,一旦匹配到 .js 文件就使用 babel-loader 进行处理,但是将 node_modules 目录文件排除. 如前文所述,babel-loader调用babel-core的api使用bable-preset-env的规则进行转码。

注: 本项目使用 React 框架进行搭建, 因此安装支持 React 的babel 包:

@babel/preset-react: 转换React jsx语法;
@babel/plugin-proposal-class-properties: 转换 Class语法;
@babel/plugin-proposal-decorators: 转换 Mobx 等更高级的语法;
babel-plugin-import: 配合实现React组件的按需加载;

// ./build/webpack.common.js

const path = require("path");

const srcDir = path.join(__dirname, "../src");

module.exports = {
    . . .

    module: {
        rules: [
            {
                test: /\.(js|jsx)$/,
                include: [srcDir],
                use: ["babel-loader?cacheDirectory=true"]   // // 开启缓存
            }
        ]
    }

    . . .

}

// .babelrc

{
    "presets": ["@babel/preset-env", "@babel/preset-react"],
    "plugins": [
           "@babel/transform-runtime",
           ["@babel/plugin-proposal-decorators", { "legacy": true }],
           ["@babel/plugin-proposal-class-properties", { "loose": true }],
           ["import", {
            "libraryName": "antd",
            "libraryDirectory": "es",
            // 配置 style: true 则在项目编译阶段,可以对引入的 antd 样式文件进行编译,
            // 从而可以压缩打包尺寸;而配置style: "css", 则直接引入经过打包后的 antd 样式文件
            "style": "css" // `style: true` 会加载 less 文件
          }]
    ]
}

plugins 配置项说明:

@babel/plugin-proposal-decorators:

legacy: Boolean 类型, 默认false;true为用stage1的语法(用废弃的语法)

@babel/plugin-proposal-class-properties:

loose: Boolean 默认 false; 定义类属性的方式

import:

"plugins": [
    ["import", { "libraryName": "antd", "libraryDirectory": "lib" }, "ant"],
    ["import", { "libraryName": "antd-mobile", "libraryDirectory": "lib"}, "antd-mobile"]
]

/**
 * "libraryDirectory": ""    表示从库的package.json的main入口;否则可设置为lib文件夹
 * /

(8) 处理Less样式和图片等资源

  • Step1. 安装依赖

yarn add -D less less-loader style-loader css-loader url-loader mini-css-extract-plugin postcss-loader autoprefixer

less-loader、style-loader、css-loader处理加载less、css文件;

postcss-loader、autoprefixer处理css样式浏览器前缀兼容;

url-loader处理图片、字体文件等资源;

mini-css-extract-plugin 分离css成单独的文件;

  • Step2. 配置webpack.common.js 的module: {rules: []}
// ./build/webpack.common.js

. . .

const MiniCssExtractPlugin = require("mini-css-extract-plugin")

// 从环境变量判断是否为生产环境
const devMode = process.env.NODE_ENV !== 'production';

module.exports = {
    module: {
        // rules: [
        //     {
        //         test: /\.(js|jsx)$/,
        //         include: [srcDir],
        //         use: ["babel-loader?cacheDirectory=true"]   // 开启缓存
        //     }
        // ]

        rules: [
            // css loader
            {
                test: /\.css$/,
                use: [
                    devMode ? "style-loader" : MiniCssExtractPlugin.loader, "css-loader", "postcss-loader", "less-loader"
                ]
            },
            // less loader
            {
                test: /\.less$/,
                use: [
                    devMode ? "style-loader" : MiniCssExtractPlugin.loader, "css-loader", "postcss-loader"
                ]
            },
            // 处理图片文件
            {
                test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
                use: ["url-loader"],
                include: [srcDir]
            },
            // 处理音频文件
            {
                test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
                use: ["url-loader"],
                include: [srcDir]
            },
            // 处理字体文件
            {
                test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
                use: ["url-loader"],
                include: [srcDir]
            }
        ]
    },

    
    plugins: [
        // 配置 html 模板
        new HtmlWebpackPlugin({
            template: `${srcDir}/index.html`
        }),
        // 配置 MiniCssExtractPlugin 插件
        new MiniCssExtractPlugin({
            filename: "[name].[contenthash:8].css",
            chunkFilename: "chunk/[id].[contenthash:8].css"
        })
    ],
}

注:

在node中,有全局变量 process 表示的是当前的node进程。

process.env 包含着关于系统环境的信息,但是 process.env 中并不存在 NODE_ENV 这个属性。

NODE_ENV是一个用户自定义的变量,在webpack中它的用途是判断生产环境或开发环境。

(9) 配置postcss .postcssrc.js文件

postcss 是一款使用插件去转换CSS的工具, 配置 postcss 表示指定使用的 css 预编译器,里面默认配置了 autoprefixer ,自动补全浏览器前缀;

配置方法:

在项目根目录下创建 .postcssrc.js 文件, 并在 .postcssrc.js 和 package.json 中分别写入以下配置项:

// .postcssrc.js

module.exports = {
  plugins: {
    autoprefixer: {}    // 默认配置了 autoprefixer ,自动补全浏览器前缀
  }
};

// package.json 中配置兼容浏览器
// 如下目标环境为:
// 超过市场份额1%的浏览器,且支持每个浏览器最后
// 两个版本和ie不小于等于10版本所需的polyfill代码转换
"browserslist": [
  "> 1%",
  "last 2 versions",
  "not ie <= 10"
]

Autoprefixer 可以自动在样式中添加浏览器厂商前缀,避免手动处理样式兼容问题(为其添加浏览器厂商前缀的PostCSS插件)。

如下, 一个不加浏览器兼容设置的 css 样式设置为:

.example {
    display: flex;
    transition: all .5s;
    user-select: none;
    background: linear-gradient(to bottom, white, black);
}

Autoprefixer 解析后:

.example {
    display: -webkit-box;
    display: -ms-flexbox;
    display: flex;
    -webkit-transition: all .5s;
    transition: all .5s;
    -webkit-user-select: none;
    -moz-user-select: none;
    -ms-user-select: none;
    user-select: none;
    background: -webkit-linear-gradient(top, white, black);
    background: linear-gradient(to bottom, white, black);
}

(10) 配置 Happypack

  • Happypack 是什么? 为什么使用 Happypack?

webpack 在 nodejs 中是以单线程模型运行的, 需要一个一个处理 js, css, 图片以及文字等多种文件, 当文件数量较多时, webpack 构建速度就会很慢. 因此HappyPack出现了,它能让webpack同时处理多个任务,它将任务分解给多个子进程去并发执行,子进程处理完成后再将结果发送给主进程中。

  • 如何配置使用 Happypack ?

a) 安装 Happypack 依赖: yarn add -D happypack

b) 将配置写入 webpack.common.js 文件

为了深入理解如何配置 happypack 的使用,我们先看一下不使用 happypack 时webpack 对js 或 jsx 文件的打包配置:

// webpack.common.js

const path = require("path");

module.exports = {

    . . .

    module: {
        rules: [
            {
                test: /\.(js|jsx)$/,
                use: ["babel-loader?cacheDirectory=true"],
                exclude: path.resolve(__dirname, "node_modules")
            }
        ]
    }
}

使用 happypack 后,变成如下配置:

// webpack.common.js

const path = require("path");
const os = require("os");
const HappyPack = require("happypack");

const happyPackThreadPool = HappyPack.ThreadPool({ size: os.cpus().length });

module.exports = {

    . . .

    module: {
        rules: [
            {
                test: /\.(js|jsx)$/,
                // 将对.js 或 .jsx 文件的处理转交给id为happybabel的HappyPack的实例
                use: ["happypack/loader?id=happybabel"],
                exclude: path.resolve(__dirname, "node_modules")
            }
        ]
    }

    // 同时配置 plugins
    plugins: {
        // 开启 happypack 线程池
        new HappyPack({
            // 用唯一的标识符id来代表当前的HappyPack 处理一类特定的文件
            id: "happybabel",
            // 如何处理.js文件,用法和Loader配置是一样的
            loaders: ["babel-loader?cacheDirectory=true"],
            threadPool: happyPackThreadPool,
            cache: true,
            //允许 HappyPack 输出日志
            verbose: true
        })
    }
}

(11) 生产环境 拆分模块

根据实际项目情况拆分模块,配合异步加载,防止单个文件过大。

  • 为什么进行模块拆分?
    模块拆分是通过将公共模块拆出来,最终合成的文件能够在最开始的时候加载一次,便存到缓存中供后续使用。这会带来速度上的提升,因为浏览器会迅速将公共的代码从缓存中取出来,而不是每次访问一个新页面时,再去加载一个更大的文件;

webpack4 之前, 用 CommonsChunkPlugin 插件进行模块拆分,是一个可选的用于建立一个独立文件(又称作 chunk)的功能,这个文件包括多个入口 chunk 的公共模块。

从webpack4 开始官方移除了 CommonsChunkPlugin 插件, 取而代之的是两个新的配置项 optimization.splitChunks 和 optimization.runtimeChunk, 这也应该是从V3升级到V4的代码修改过程中最为复杂的一部分.

  • 怎样进行模块拆分配置?
// ./build/webpack.prod.js

const merge = require("webpack-merge"); // 用于合并common config 与 当前的 prod config
const commonConfig = require("./webpack.common");

let prodConfig = merge(commonConfig, {
    mode: 'production', // 设置当前配置为生产环境的配置

    // 模块拆分配置
    optimization: {
        runtimeChunk: {
          name: 'manifest',
        },
        splitChunks: {
            // chunks: 共有3个值"initial","async"和"all"。配置后,代码分割优化仅选择初始块,按需块或所有块
            // 默认只作用于异步模块,为`all`时对所有模块生效,`initial`对同步模块有效
            chunks: 'all', 
            // minSize: 30000,         // 单位: 字节, 引入的文件大于30kb才进行分割
            // maxSize: 50000,         //50kb,尝试将大于50kb的文件拆分成n个50kb的文件
            // minChunks: 2, // 模块至少使用次数. 当值为2时,代表只引用了一次的模块不做分割打包处理
            cacheGroups: {    // 缓存组,定义分割的代码放到哪个文件中
                dll: {
                    test: /[\\/]node_modules[\\/](react|react-dom|react-dom-router|babel-polyfill|mobx|mobx-react|mobx-react-dom|antd|@ant-design)/,
                    minChunks: 1, //引入的库大于30kb,才做代码分割
                    priority: 2,          // 若一个模块符合多个组的 test 条件,就由priority决定放哪个组,值越大优先级越高
                    name: 'dll',         //  打包后的文件名
                },
                codeMirror: {
                    test: /[\\/]node_modules[\\/](react-codemirror|codemirror)/,
                    minChunks: 1,
                    priority: 2,
                    name: 'codemirror',
                },
                vendors: {
                    test: /[\\/]node_modules[\\/]/,   //引入的库是从node_modules引入,就分割库代码到当前的 vendors 组
                    minChunks: 1,
                    priority: 1,
                    name: 'vendors',
                },
            },
        }   // splitChunks
    }   // optimization
});

module.exports = prodConfig;

上述配置中 chunks 的配置提及异步代码与同步代码问题, 接下来看一下两种代码的例子:

// async code: set chunks to "async"

function getComponent() {
    return import(/* webpackChunkName: "lodash" */lodash).then(({default: _}) => {
        let element = document.createElement('div');
        element.innerHTML = _.join(['Dell', ' ', 'Lee', '-']);
        return element;
    })
}

getComponent().then(el => {
    document.body.appendChild(el);
})

// sync code: set chunks to "initial"

import _ from 'lodash';  //第三方库
import test from './test.js'; //业务代码
import jquery from 'jquery';  //第三方库
console.log(test.name);

var element = document.createElement('div');
element.innerHTML = _.join(['Dell', ' ', 'Lee', '-']);
document.body.appendChild(element);
console.log(jquery('div'));

(12) 引入 ESlint 规范代码开发

  • ESlint 是什么? 为什么使用ESlint?

在团队编程中,同样的功能可能会有多种写法,为了方便大家对于代码的维护,我们会使用ESLint来约束自己的代码,实际上,它是一种代码规范。

  • 如何在项目中使用eslint?

如果你要在项目中使用这种“规范”,我们首先需要进行安装:

yarn add -D eslint

光安装,还不能约束我们的代码规范,我们需要用一个配置文件来进行配置,这样的配置,我们可以简单的通过命令来生成,也可以通过引入外部的配置文件来配置,这里我们通过指令来进行配置。

npx eslint --init

提示的配置项,我们采用默认的即可.

执行完命令后,会在我们的根目录下生成一个’.eslintrc.js’的文件,不同版本和不同的配置方式生成的文件内容可能不一样,这里我生成的内容是

// .eslintrc.js

module.exports = {
    "env": {
        "browser": true,
        "es2021": true
    },
    "extends": [
        "eslint:recommended",
        "plugin:react/recommended"
    ],
    "parserOptions": {
        "ecmaFeatures": {
            "jsx": true
        },
        "ecmaVersion": "latest",
        "sourceType": "module"
    },
    "plugins": [
        "react"
    ],
    "rules": {
    }
}

关于简单的Eslint用法,我们只需要执行指令即可,比如我要检测src下的代码

npx eslint src

接下来配置 eslint 解析器:

先安装babel-eslintr: yarn add -D babel-eslint

然后在 .eslintrc.js 中指定 eslint 解析器

// .eslintrc.js
. . .
parser: "babel-eslint"
. . .

babel-eslint 解析器是一种使用频率很高的解析器,因为现在很多公司的很多项目目前都使用了es6,为了兼容性考虑基本都使用babel插件对代码进行编译。而用babel编译后的代码使用 babel-eslint 这款解析器可以避免不必要的麻烦。

补充一些 eslint 的配置项(在 .eslintrc.js 中):

root: 限定配置文件的使用范围. 在开发中有时根据需要,我们可能在同一个项目不同的目录使用不同的 .eslintrc.js 文件,这时我们就需要使用配置项 root: true 。

parser: 指定eslint的解析器

parserOptions: 设置解析器选项

extends: 指定eslint规范, 我们可以使用eslint官方推荐的,也可以使用一些大公司提供的的,如:aribnb, google, standard。使用第三方eslint 扩展需要安装依赖, 如:

yarn add -D eslint-config-airbnb // aribnb
yarn add -D eslint-config-standard // standard

plugins: 引用第三方的插件: 我们的项目中可能会有一些其他的文件也需要进行格式规范,如:html, vue, react等,对于这些文件的处理,我们需要引入第三方插件, 如:

yarn add -D eslint-plugin-html

这个插件将会提醒模块脚本之间模拟浏览器共享全局变量的行为,因为这不适用于模块脚本。

这个插件也可以扩展文件,如:.vue,.jsx

在配置文件里配置插件时, 可以使用 plugins 关键字来存放插件名字的列表。插件名称可以省略 eslint-plugin- 前缀。

env: 指定代码运行的宿主环境, 如 browser, node等

rules: 启用额外的规则或覆盖默认的规则

globals: 声明在代码中的自定义全局变量

  • 如何在 React 项目中使用eslint?

因为使用的是react项目,需要将 eslint 集成到webpack中。
我们需要安装 eslint-loader:

yarn add -D eslint-loader

并在 webpack 中写入如下配置:

// webpack.common.js

. . .

module.exports = {
    . . .

    module: {
        rules: [
            {
                enforce: "pre",
                test: /\.(js|jsx)$/,
                include: [srcDir],
                use: ["eslint-loader"]
            },
            {
                test: /\.(js|jsx)$/,
                exclude: /node_modules/,
                use: ["happypack?id=happy-babel-loader"]
            },
            . . .
        ]
    },

    . . .
}

注意, use 中的 loader 的执行顺序是从右往左, 因此

{
    test: /\.(js|jsx)$/,
    include: [srcDir],
    use: ["babel-loader", "eslint-loader"]
}

配置中压迫将 eslint-loader 配置写到 babel-loader 后面, 在编译前先执行eslint 检查对代码做规范

以上配置针对js文件的代码规范检查给出了运行npx eslint src检查一样的效果,但是这始终是在命令行里,为了方便使用 ,我们在devServer中加入配置项overlay: true这时候,运行项目,对于有错误的页面,会直接在页面上弹一个层,里面的内容就是我们ESlint的内容,这样即便是我们的编辑器中没有ESlint插件,也可以很快的定位错误。

如果不确定 loader 执行的顺序,建议将 webpack 的配置写成如下形式:

// webpack.common.js

. . .

module.exports = {
    . . .

    module: {
        rules: [
            {
                test: /\.(js|jsx)$/,
                include: [srcDir],
                loader: 'eslint-loader',
                enforce: 'pre',
                options: {
                  fix: true,
                },
            },
            // {
            //     enforce: "pre",
            //     test: /\.(js|jsx)$/,
            //     include: [srcDir],
            //     use: ["eslint-loader"]
            // },
            {
                test: /\.(js|jsx)$/,
                exclude: /node_modules/,
                use: ["happypack?id=happy-babel-loader"]
            },
            . . .
        ]
    },

    . . .
}
  • ESlint 配置项解析

下面我们针对几个代表性的配置项,做一下简单的介绍. 们可以进入到eslint-loader的git仓库https://github.com/webpack-contrib/eslint-loader, 可以看到一些配置项:

fix: 当配置为true时,对于代码中一些浅显的问题,webpack会帮我们自动改掉

cache: 因为eslint会有一个对代码分析的过程,势必会造成一些性能损耗,开启cache将对这种性能损耗,做出一些优化。

  • 配置忽略 eslint 检查的文件 .eslintignore

在项目根目录创建 .eslintignore 文件,写入如下配置:

// .eslintignore

.DS_Store
package.json
dist/
yarn.lock
yarn-error.log
*.sh
.gitignore
.prettierignore
.editorconfig
.eslintignore

(13) npm scripts 配置

在 package.json 中配置npm 脚本命令:

//  package.json
. . .

"scripts": {
    "start": "webpack-dev-server --color --progress --config build/webpack.dev.js",
    "build": "NODE_ENV=production webpack --progress --config ./build/webpack.prod.js",
    "build:report": "NODE_ENV=production webpack --progress --config ./build/webpack.prod.js",
    "build:watch": "NODE_ENV=production webpack --progress --config ./build/webpack.prod.js"
},

. . .

命令解释:

// 运行开发环境;
yarn start 

// 生产环境打包压缩;
yarn build

// 图形化分析打包文件大小;
yarn build:report

// 方便排查生产环境打包后文件的错误信息(文件source map);
yarn build:watch

(14) 编写 react 测试代码并启动服务

  • 安装 react 依赖

yarn add react react-dom react-router-dom mobx mobx-react mobx-react-router axios antd moment

  • 在 src 目录下创建 index.js index.html 和 App.js 并写入如下代码:
// src/App.js

function App() {
  return (
    
{/* logo */}

Edit src/App.js and save to reload.

Learn React
); } export default App;
// src/index.js

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

import App from './App';
// import reportWebVitals from './reportWebVitals';

ReactDOM.render(
  
    
  ,
  document.getElementById('root')
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
// reportWebVitals();

// src/index.html



  
    
    
    
    
    管理平台
  

  
    
    

运行 yarn start:

报错1:

Module build failed (from ./node_modules/eslint-loader/dist/cjs.js):

TypeError: Cannot read properties of undefined (reading 'getFormatter')

解决办法:

package.json 中修改 eslint 和eslint-loader 的版本:

“eslint”: “^7.32.0”,

“eslint-loader”: “^4.0.2”

然后重新安装依赖: yarn install

报错2:

[webpack-cli] TypeError: webpack.NamedModulesPlugin is not a constructor

原因:

webpack5.0 的时候,NamedModulesPlugin已经移除了

解决办法:

webpack.dev.js: 中修改new webpack.NamedModulesPlugin() →

webpack.prod.js:

optimization: {
    moduleIds: 'named'
}

“eslint”: “^7.32.0”,

“eslint-loader”: “^4.0.2”

报错3:

yarn start 启动服务器后, 浏览器无法显示内容, 控制台报错:

Uncaught ReferenceError: React is not defined

原因:

babel 需要建 ReactJs 语法转换为 CommonJs, 举个例子:

转换前:

import React, { Component } from 'react';

class Process extends Component {
    render() {
        return (
哈哈哈
) } }

转换后:

import React, { Component } from 'react';

class Process extends Component {
    render() {
        // 用到了React.createElement方法
        return React.createElement(
            'div',
            null,
            '\u54C8\u54C8\u54C8'
        );
    }
}

为什么代码没有使用react相关方法,也要在文件顶部import react?

原因: 通过babel会将React jsx / React js 编译成普通js代码(如下)会用到React.createElement,所以需要React. 即:


import React from "react";
const App = () => (
    
Hello World!!!
); // 等同于: export default App; var App = function App() { return React.createElement( "div", {className:'test'} "Hello World!!!" ); };

解决办法:

App.js 中导入 React:

import React from 'react';

若不想每个模块都引入 React, 可在webpack中全局配置引入:

修改 webpack.common.js:

const webpack = require('webpack');

. . .

module.exports = {
    . . .
    plugins: [
        . . .
        new webpack.ProvidePlugin({
            React: 'react'
        })
        . . .
    ]
}

你可能感兴趣的:(webpack,react,前端)