webpack5从零搭建react16项目万字踩坑记📝

398 阅读25分钟

前言

因为自己平常会尝试写各种各样的东西,经常用的框架就是react。但是奈何使用create-react-app搭建的项目全是顶配,而通常工作中都是低配。所以极其的不方便,在对比react老版本和新版本时也不好比较。所以萌生了自己搭建一个老版react项目的想法。这样无论是自己写东西还是将工作中的问题移到自己的项目中都比较方便。

版本

搭建一个项目最重要的就是版本,框架的版本与其周围的生态系统版本以及对应typescript编译的包版本一定要匹配,这样可以提前帮你解决很多未知的bug。

就好比你用react18的 @types/react去给16做类型解析,那肯定不匹配。

下面是我的主要版本:

"dependencies": {
    "react": "^16.14.0",
    "react-dom": "^16.14.0",
    "react-router": "^5.2.0",
    "react-router-config": "^5.1.1",
    "react-router-dom": "^5.2.0"
  },
  "devDependencies": {
    "@types/react": "^16.14.45",
    "@types/react-dom": "^16.9.19",
    "@types/react-router": "^5.1.2",
    "@types/react-router-config": "^5.0.6",
    "@types/react-router-dom": "^5.3.3",

搭建踩坑

这里我用的搭建工具是最新的webpack,不要🙅🙅🙅🙅问我为什么不用vite,没必要比较,喜欢哪个用哪个。

无法打开html页面

在使用HtmlWebpackPlugin配置打包后的html时将filename配置为了app.html。导致后面再启动webpack-dev-server后,会打开一个文件列表页面,这个文件列表就是我打包后的dist目录。

image.png

我每次都需要手动点击那个app.html才可以打开我的页面,这样很傻。

而众所周知的是,如果文件夹下有index.html,他就会自动打开这个html。所以这里将app.html改为index.html即可。

找不到入口index.js

webpack编译报错:找不到入口index.js。

原因是引入ts后,没有在webpack.config.js中将index.js改为index.tsx

访问不到文件 xxxxx.js

与上面同理,当文件省略后缀文件类型.js时,会自动将其补全。但是接入ts后,将文件名改为.ts或者是.tsx后当然就找不到了。所以要在配置中告诉webpack要补全的后缀类型。

 resolve: {
    extensions: [".ts", ".tsx", ".js", ".json"], // 补全优先级变为ts、tsx
 }

node_modules/@types/react-dom/index"' has no default export.

当你配置好后新建react的根文件引入reactreact-dom时,在编译时会报这个错:

// node_modules/@types/react/index"' can only be default-imported using the 'esModuleInterop' flag
// node_modules/@types/react-dom/index"' has no default export.

解决的方式也说的很明显,那就是只能在使用了esModuleInterop的情况下才能被默认导出。那我们再到tsconfig.json中将他配置上即可。

{
"include": [
        "./src/**/*"
    ],
    "exclude": [
        "node_modules",
    ],
    "esModuleInterop": true,
    "compilerOptions": {
        ...
    }
}

配置完发现仍然报错,那就对了。因为他是一个编译选项,要配置在compilerOptions中。事实上tsconfig.json的配置大部分都是配置到这里,毕竟ts是一个预编译语言,并不参与实际运行。具体可以参考typescript官网中对于tsconfig.json的配置部分。

App”表示值,但在此处用作类型。是否指“类型 App?

image.png

都准备好了,将react16的经典render方法引进来发现又报错了。其实这也是react开发中说常见也常见,说不常见也基本见不到的bug。

那就是错误的定义了文件类型。

显而易见的是这里的<App />是一个JSX元素,但你可以发现上面引入的react并没有亮,也就是没有被使用。要知道在react16中JSX并不是会在运行时被自动编译为函数,而是需要使用React来进行转换。但是这里React并没有被使用,说明转换并没有被用到。

换句话说他并不认为<App />是一个JSX元素,因此可以轻易想到是在引入ts后将根文件定义为了index.ts而不是index.tsx

TS2307: Cannot find module '@pages/Home' or its corresponding type declarations.

在项目中为了引入方便配置别名已经见怪不怪了,但是配置好后发现报这个错。翻译成中文就是:找不到模块“@pages/about”或其相应的类型声明。ts(2307)

这种错误的场景也比较多见,有的情况是路由都可以正常使用了,但是会报类型错误。有的时候是编译都不通过,页面都出不来。

通过分析很好理解这个错误就是这个文件找不到了。顾名思义就是你没配或者是你配的不对。而配置他的位置就在webpack.config.js中。比较经常犯的错误就是将这里配置的绝对路径写为相对路径

resolve: {
    extensions: [".ts", ".tsx", ".js", ".json"],
    alias: {
        // 注意这里的路径是绝对路径, 没有./, 不是 "./src/pages"
      "@pages": path.resolve(__dirname, "src/pages"), 
      
    },
  },

那文件路径写对了为什么还报类型错误?这就是第一种情况,路由页面可以用但是报类型错误。因为webpack帮你解析了,但是ts表示我不认识这玩意。所以你得在tsconfig.json中带他认识认识什么TMD叫@pages!!!

但是注意,这里是相对路径。

 "compilerOptions": {
 "baseUrl": "." ,
    "paths": {
        "@pages/*": ["./src/pages/*"], // 这TMD叫@pages!!!
    },
 }

为什么该认识的都认识了,怎么还是报这个错!!!What the fuck!!!

我知道你很急,但是你先别急。

问题就出在你以为ts知道了什么TMD叫惊喜,事实上他可能还是不知道。就像我们去医院拍片子,是拍一个固定的部位而不是全身扫描。同样一整个项目ts也不会全部扫描,而是会扫描你所指定的部位。这个部位由tsconfig.json中的includesexclude来控制。

而网上大部分的include配置都是"include": ["./src/**/*"],那么结果就很明了了,如果你把什么router.tsx或者是App.tsx放在了src文件夹外面,那ts认识才见鬼了!

HashRouter' cannot be used as a JSX component'Router'

伴随这种错误常见的还有在 这些类型中,"render()" 返回的类型不兼容。

What the fuck!!!

我就是从网上抄的,引入路由、render路由、定义routes的方式都没错,为什么别人的不报错!!!

稍安勿躁,其实这个问题和上面一样,都是因为你将App.tsx或者是router.tsx放到了ts所能识别的"include": ["./src/**/*"]外面。导致他不认识这些东西,不知道他们导出了什么,所以开始瞎指挥、乱弹琴。

找不到名称“to”。ts(2304)

image.png 就是复制粘贴过来的,为什么别人不报这个错!!!

其实还是很简单的问题,因为他没有将to识别为一个属性,也就是没有将Redirect识别为一个JSX。所以还是上面的解决方案,将router.ts改为router.tsx

Cannot get/About

心心念念的路由终于不报错了,调整好了,访问测试一下吧😄😄😄😄😄😄

浏览器:Cannot get/About

对不起,我真的会谢!!!What the fuck!!!

这里直接说解决方案吧,就是hashRouterhistoryRouter的区别导致的。前者依赖于hashChange事件,因此可以随意切换不会遇到无法访问的情况。而后者依赖于服务器上有没有托管这个路由的静态资源,答案是当然没有!所以需要将服务器托管的index.html返回给客户端,客户端再根据路由匹配到对应的路由页展示出来。

因此,对于historyRouter或者在react-route中被称之为BrowserRouter的路由需要在服务器上单独配置一下。而我们用的是webpack-dev-server,所以需要在他上面加一个针对历史路由的配置来使他拥有这个功能。

historyApiFallback官网链接

devServer: {
    static: "./dist",
    port: "8080",
    open: true,
    hot: true,
    compress: true,
    historyApiFallback: true,  // 当当当当,就是他!
  },

Module build failed Undefined variable.

在写样式的时候会经常用到一些诸如:居中flex、两端flex、单行文本省略、图片背景或者是页面的主容器wrapper等的样式。这些样式都是使用频率超级高,但是每个页面又都需要用到的,因此将他们封装成一个公共方法或者是公共样式是极好的。

除此之外,还有一些公共的变量,例如背景色、下划线颜色、placeholder颜色、文字基础色、按钮等皮肤色系,在不同的页面重复使用的频率也是很高的。

综上所述,就想创建一个公共文件来存放这些公共变量、方法以及样式,然后在每个scss中使用它们。但问题在于每次都要引入这个文件,很是麻烦。

所以想到了将tool.scss文件引入到index.scss中,再将index.scss引入到App.tsx中。

// tool.scss
$lg: lightgreen;

// index.scss
@import "./tool.scss";

// App.tsx
import "@style/index.scss";

// home.scss
body {
    background: $lg;
}

本意是将这些公共变量通过引入最终传递到App.tsx中,使背景色变量$lg生效,为body添加颜色。但是在编译过程中就报错了,显示该变量是undefined

事实上通过这种方式可以引入一些基础的样式,但是无法引入公共变量、方法、样式。正确的方法是可以使用sass-loader的功能additionalData

他可以为每一个scss文件在文件的开头加一些代码,这样我们就可以利用它的特性在打包时将我们的tool.scss自动加到每个scss文件的顶部就解决了这个问题。

{
    loader: "sass-loader",
    options: {
      additionalData: `@import "@style/tool.scss";`,
    },
},

Refused to apply style from "xx" because its MIME type ('text/html') is not a supported stylesheet MIME type, and strict MIME checking is enabled.

style-loader会将样式通过<style>标签注入到head中,当我们的样式文件比较多时head中的style标签就比较多。因此可以使用mini-css-extract-plugin插件将其统一合并为一个样式文件,通过link标签引入。

const MiniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports = {
    module: {
        rules: [
          {
            test: /\.s[ac]ss$/i,
            use: [
              MiniCssExtractPlugin.loader,
              "css-loader",
              "sass-loader"
            ],
          }
        ]
    },
    plugins: [
        new MiniCssExtractPlugin({
          filename: "styles/[hash].css",  // 这里使用的是打包为hash
        })
    ]
 }

打包好之后,确实可以将style都合并到link里,但是又遇到了一个新的问题。那就是在修改scss文件后,并不会热更新,而是会报这个错。再次刷新一次后,就又正常了。

控制台source查看打包好的文件,发现打包出的结果并没有这个style文件。

截屏2023-08-31 22.58.04.png 但是当刷新一次后,这个style文件又出现了。

截屏2023-08-31 23.00.53.png

这就是这个插件的弊端,在修改样式后需要手动刷新才可以更新样式。原因就在于我们在配置生成的css文件时配置成为了hash模式。如果我们将plugin中配置的filename改为name的形式,就不会存在这个问题。

plugins: [
    new MiniCssExtractPlugin({
      filename: "styles/[name].css",  // 这里使用的是打包为name
    })
]

修改后就可以得到更新了,但是是通过recompiling的方式,重新加载了css。貌似css只能通过这种方式进行热更新,style-loader也是这种方式。无法做到局部更新。

截屏2023-08-31 23.07.14.png 因此,为了发布可以在生产环境下改为hash模式,在开发环境改为方便的name模式。

TS2307: Cannot find module '@assets/images/logo.svg' or its corresponding type declarations.

image.png 在配置好图片进行测试的时候,明明路径是正确的,但是却提示找不到这个模块或声明。原因在于webpack.config.js的打包配置是可以通过配置rules来对非js资源局进行打包的。但是对于typescript来说他不认识除了ts文件之外的东西,所以会报出这个错误。

因此为这种除ts文件之外的文件类型做专门的声明,告诉ts他是什么文件就可以了。

在根目录或者src文件下创建一个声明文件xxxx.d.ts;

// 根目录 global.d.ts

declare module "*.svg";
declare module "*.png";
declare module "*.jpg";
declare module "*.jpeg";
declare module "*.gif";
declare module "*.bmp";
declare module "*.tiff";

定义完之后如果还是报这个错,那你就要检查你是否告诉了ts你添加了配置。受到ts类型检查的文件只有tsconfig.jsoninclude所包含的文件。如果你添加了新的配置不在include所包含的文件范围内,则属于无效添加和没写一样。

因此这里需要将定义在根目录下的global.d.ts文件加入到include中。

"include": {
  "./src/**/*",
  "global.d.ts"
}

Failed to load resource: the server responded with a status of 404 () Cannot GET localhost:8080/favicon.ico

在测试项目的过程中,有时候会出现这个错误。原因在于项目会自动去查找根目录下的favicon.ico图标,然后将他显示到浏览器中。这个图标就是我们打开一个网页,网页标题栏左侧的那个小图标。

image.png

解决办法也很简单,既然报错找不到那我们就搞一个就可以了。也不必担心自己不会做这个图标,现在网上有很多免费的,只要百度搜索favicon.ico就有很多可以免费制作的网站。

做好以后接下来就是配置了。配置也非常简单只需要使用link链接到你的根html文件中即可。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>react16</title>
    // 链接到这里
    <link rel="shortcut icon" href="/favicon.ico"/>
</head>
<body>
    <div id="root"></div>
</body>
</html>

不过这里要注意这个图标的路径,这里的路径是你的根目录,所以要将图标放到根目录下。当然你也可以将他放到其他文件夹中,例如assets | public。但是需要注意的是,放到其他文件夹中需要将这个图标拷贝到打包后的文件夹中。

除了上述手动配置的方法外,你还可以直接通过在html-webpack-plugin插件中进行配置来直接实现这个需求。

plugins: [
    new HtmlWebpackPlugin({
      template: "./index.html",
      filename: "index.html",
      // 配置图片路径
      favicon: path.resolve(__dirname, "src/assets/favicon.ico"),
      inject: "body",
    }),
 ]

配置后会自动为你在根html文件中插入一个link标签。

image.png

配置了favicon.ico却不显示

使用上述方法配置好之后却不显示,检查各种路径都正确,刷新页面半天也不出来。

是因为这个图标会被浏览器缓存,因此解决办法也很简单,配置好之后使用浏览器的清空缓存并硬性重加载刷新即可。

修改代码后刷新favicon.ico丢失

本来采用的是在html-webpack-plugin中配置的方式,本来想着这样比较方便讲配置都放到webpack.config.js中,不用单独放到html文件里。即使展示不出来,清缓存刷新一下就好了。然而在实际操作中,又遇到了新的问题,那就是当我刷新一下页面时图标就又消失了!!!

一直没有想明白这个问题,但是在配置本地mock文件对copy-webpack-plugin做配置时才找到了一丝解释。理论上我们在浏览器访问到的文件都是在webpack编译打包过后的文件,按照我的配置

new CopyWebpackPlugin({
        patterns: [
                { from: "src/assets", to: "assets" },
                { from: "mock", to: "mock" }
        ]
}),

当我要访问这个assets下的ico图标时,我应该使用的是./assets/xxx.ico又或者是assets/xxx.ico。但实际上使用这种路径会使打包不通过,报错找不到这个文件。

Html Webpack Plugin:
  Error: HtmlWebpackPlugin: could not load file /Volumes/D/frontEnd/react16/assets/favicon.ico
  
  - index.js:543 
    [react16]/[html-webpack-plugin]/index.js:543:35

但是如果将配置路径改为favicon: path.resolve(__dirname, "src/assets/favicon.ico"),就可以访问到这个图标。由此可以猜测,它是在打包的时候顺着这个路径找到了这个图标并把他拷贝到了打包后文件的内存里。如果打包时你用打包后的路径去取就会找不到,必须要用打包前的当前路径。而当打包过后如果不手动刷新页面,只是修改了本地文件和代码触发webpack热更新就会去读取这个缓存的图标,并不会出现消失的问题。

但是当你手动刷新时很有可能就将这个图标刷没了,因此你就再也访问不到他了。所以解决的办法就是切换成第一种方式,将引入图标直接写到html文件中,路径就是其打包之后的路径。这样无论你怎么刷新他都会去从拷贝过来的文件中读取图标。

 <link rel="shortcut icon " type="images/x-icon" href="./assets/favicon.ico">

eslint报错合集

配置eslint配的我想骂人,配了一万年,全TM是坑。家人们,谁懂啊!!!

我现在是真的理解了为什么能不升级就不升级,一升级各种包之间的不兼容真是搞死人哇。本来大部分项目都是用webpack4配置的,我这次作死用了5,自己要被雷炸死了。还好在最后关头还是挺过来了。

按照官网配置了eslint,但是不生效

进入eslint官网,按照指示运行指令:npm init @eslint/config,一路配置好后发现并没有什么反应。

原因在于webpack解析资源是通过loader来解析的,如果想要对ts文件添加eslint检验,那么当然要先用对应的loader进行解析。于是安装了eslint-loader并配置。

{
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: ["babel-loader", "eslint-loader"],
          options: {
            presets: ["@babel/preset-env"],
            plugins: [["@babel/plugin-transform-runtime"]],
          },
        },
      },

结果又报错了,提示:configuration.module.rules[1].use.loader should be a non-empty string.。看起来意思是loader不应该是一个空的字符串,说明loader的配置方式有问题。查询过官网发现,是我将两个loader共用了babel-loaderoptions。应该将二者分开写,并设置优先级。

{
        test: /\.(ts|tsx|js|jsx)$/,
        enforce: "pre", // // 设置loader优先级
        use: ["eslint-loader"],
        exclude: [/node_modules/],
      },
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: "babel-loader",
          options: {
            presets: ["@babel/preset-env"],
            plugins: [["@babel/plugin-transform-runtime"]],
          },
        },
      },

Cannot read property 'getFormatter' of undefined

本来以为按照官网的配置就弄好了,结果又报错了。查了一下原因是eslint-loader被废弃了,而我用的恰好是webpack5,可能会存在一堆兼容问题。在5中改为了使用插件eslint-webpack-plugin的形式。

const ESLintWebpackPlugin = require("eslint-webpack-plugin");
plugins: [
    ...
    new ESLintWebpackPlugin(),
  ],

配置好了但是不生效

按照上述配置好后仍然不生效,查看官网后发现是没有添加options指定eslint对那些文件进行检查。因此补充更新了如下配置:

new ESLintWebpackPlugin({
  context: path.resolve(__dirname, "src"),
  extensions: ["ts", "tsx", "js", "jsx"],
}),

报错合集

  1. Failed to load plugin '@typescript-eslint' declared in '.eslintrc.json': Unexpected token '||='Referenced from: /Volumes/D/frontEnd/react16/.eslintrc.json
  2. Failed to load plugin '@typescript-eslint' declared in '.eslintrc.json': Class extends value undefined is not a constructor or null

上述的这一类问题通常都是版本不匹配所导致的,具体是eslint@typescript-eslint/xxxx之间版本的不兼容所造成。

我最开始是采取降低eslint版本到7.32.0来做的,但实际上我使用的webpack5 + eslint-webpack-plugin都是较高版本。所以向下兼容的话实际上走了许多的弯路。报了很多奇奇怪怪的错。

后面安装的所有包都是@latest顶配版,错误就少了许多。但是安装高版本的包需要安装相应的支持,不然会报下面的错:

This syntax requires an imported helper but module 'tslib' cannot be found.

解决办法就是将tslib安上即可。

Failed to load config "react-app" to extend from.

安装 eslint-config-react-app

将所有包装好后,又开始报这个错。随后我按照网上搜到的答案开始装eslint-config-react-app。这里一开始也走了弯路,也是向下走了好几个版本发现不行,最后又装回了高版本。

配置react-app

但是装好之后还是不行。将这个报错翻译过来就是:

未能加载要从中扩展的配置“react app”。

也就是没有从扩展中加载到react-app,通过多方的查阅我最后才明白其中道理。

事实上eslint-config-react-app这个插件类似于create-react-app的一套针对react的报错规则。而eslint要做的就是将针对react项目的报错规则拓展进来(eslint中文翻译为集成,因为一个项目可以有多套规则)。使用eslint官网默认集成的规则是:

// .eslintrc.js
extends: [
    "eslint:recommended",
    "plugin:@typescript-eslint/recommended",
    "plugin:react/recommended",
  ],

但是我在查找资料中看到Stack Overflow有人说这个react/recommended的规则已经很久没更新了,他换了一个airbnb那一套规则就解决了问题。所以我猜测他报这个错的原因是想让我们采用create-react-app的这套规则,或者说是安装的包中有包用到了这套规则。

因此我们在这里将他需要的配置集成(extend)进来即可。

// .eslintrc.js
extends: [
    "react-app",
    "eslint:recommended",
    "plugin:@typescript-eslint/recommended",
    "plugin:react/recommended",
  ],

更新node版本

本来以为到这里就可以解决了,但是还是不行!!!WTF!!!

后来仔细排查,发现在安装create-react-app时出现了很多警告:

image.png 好家伙,node不兼容,我刚好是14.17.1。所以装了等于没装。于是搜方法升级了node,最后终于大功告成可以使用了🎉🎉🎉!

vscode不提示

白高兴一场,终端提示了,但是代码里没提示,WTF!!!

众所周知,代码里的提示需要用到vscode中的插件 eslint ,所以第一时间想到的就是没有配置正确。

  1. 于是打开settings.json,关闭vscode并重启还是不行;
  2. 后来又想着难道是要配置到工作区域里?然后创建了.vscode,在其中的文件夹中配置了settings.json,仍然不行;
  3. 又想着会不会是配置中对javascripttypescriptjavascriptreacttypescriptreact的解析采用的是esbenp.prettier-vscode。于是都改为了dbaeumer.vscode-eslint,但是仍然不可以。

vscode插件日志

后面在网上搜了很多方法,最后发现vscode插件在被使用时,也是有他输出日志的终端。位于vscode终端中的第二个栏位,在右侧可以选择你使用的插件名称,就可以显示出对应插件运行的日志。

image.png

然后日志中就输出了一个error:lib/node_modules/eslint/lib/api.js doesn't export a CLIEngine. You need at least eslint@1.0.0。网上有人给出了答案。

image.png 看的我血压都上来了,我一路升级到最高版本,最后告诉我eslict插件还没有完全适配8版本,WTF!!!

降版本遇到的问题

向上不行,只能再次尝试向下兼容,但是版本又不能太低。因为eslint-webpack-plugin只支持eslint7以上版本。所以接下来就是选择版本。

如何选择版本

以前总听别人说选版本困难,自己开始做才发现是真的难。就拿eslint这几件套来说他们都存在着互相依赖或者是覆盖依赖的问题,即Overwrite Dependency 也就是说你两个插件中都同时依赖于一个插件,但是那个插件的版本不同。所以你就需要对她进行适配。当你适配完成后在安装另一个插件时,他又识别到你当前安装的依赖版本过低,又需要升级。类似于下面的报错:

如何查看所需版本

image.png

这里有一个要学习的地方就是。 紫色字体peer表示当前项目中的版本,而from后面的版本为他需要的版本。

如何找到合适的依赖版本

再选择版本时面对这种循环依赖问题,有一个好的办法就是找到对应插件的github地址。然后打开package.json通过调整tag调整包的版本来找到对应版本所依赖的其他包版本,从而降低出错率。

image.png

或者从 npm的对应包首页的 code中的package.json中也可以找到。

image.png

如何找到合适的包版本

在npm中找到对应的包,然后根据依赖选择下载人数最多的即可,因为下载人数多的通常是可以使用的稳定版本。

image.png

Parsing error: Invalid value for lib provided: esnan

最终我终于找到了不存在相互依赖的版本,将上面所有包装好。满心欢喜以为要成功的时候,新的错误出现了。这个错误是eslint已经开始工作了,但不知道什么原因失败了。实在是没有找到是什么原因,自己也实在太心累了。

image.png

而这一切的根源在于自己在最初打包项目时选择了最新版本的webpack@5.88.2,于是为了正常使用eslint,我决定降低webpack版本,使用更加稳定成熟的eslint-loader

降版本引发的问题,和安装eslint相同。导致前面安装的各种babel、loader等都要跟着降版本,不然就无法使用。于是巨大的工作量让我再度放弃,重新改变自己的思路寻找解决问题的答案。

既然需要升级eslint插件,那现在的eslint插件的版本是多少?支持eslint8了吗?

升级eslint插件

后来在vscode的拓展商店发现eslint已经更新到了2.4.2,可以支持8.0版本以上。但是当我右键进行拓展更新想将我1.7.8的版本升上去时告诉我不行,vscode版本过低。

于是又尝试升级vscode,用了很多办法都无法升级。最后卸载了,重新安装即可。Mac用户要将下载解压的vscode拖入到应用程序中,才可以获得更新权限。不然就会更新失败。

升级vscode后就可以获取到拓展包的最新版,下好了eslint@2.4.2

其他错误

事实上中间遇到的错误,远比记录的要多,很多都来不及记录。也有很多错误本来是正确的但是改错了方向而造成的新错误。

比如在报错 Failed to load config "react-app" to extend from.时即使我安好了eslint-config-react-app他又报错:我在eslint-config-react-app中的eslint配置文件与@typescript/eslint-pulgin中的相冲突。

除此之外等一堆错误。我原本计划着先将eslint恢复到可以在终端看到编译错误的提示,再去升级eslint插件。但是各种奇怪的报错让我晕头转向,最后决定换个思路先去升级vscode的eslint插件。

升级过程中为了防止之前改bug对.eslintrc文件的影响,以及其他安装的东西影响。所以将我安装的eslint相关全部删除。将.eslintrc拷贝到项目外面准备重新开始生成配置文件,然后将外面文件中的rules拷贝到新的中。结果就是这个举动让我遇到了新的bug。

eslint查询配置文件.eslintrc可能是从外向里找的,找到就不继续查找了。所以他找到了项目外的这个我考出来的配置文件。然后报错,相关的@typescript-eslint/eslint-plugin@typescript-eslint/parser找不到。

都不在项目里面,能找到才怪了!!!

最为要命的是刚开始我想到了这一点,但是.eslintrc在Mac是个隐藏文件我到他读取的路径下面去找根本找不到,所以以为自己思路错了。结果睡觉前突然想到有一次在文件夹里明明有这个配置但是没看到才想起来他可能是隐藏文件。

接下来为了将这个隐藏文件删除也是想了很多办法,最后灵机一动才感觉自己真是个蠢逼,那不vscode就能看到吗?直接打开那个文件夹不就行了。

最后删除之后,重新运行就没问题了。重新使用eslint官网的命令一步步往下走,什么都不用改,什么都不用装,也没有什么报错,然后就好了????

WTF????????

postcss配置失败

在根据webpack官网上面的要求设置完毕postcss之后,想要测试一下 针对不同浏览器的样式兼容效果,所以写了一个tansform属性,发现没有生效。

后来发现自己忽略了一点,既然是针对不同浏览器的效果,那首先要确定你要兼容的浏览器范围。也就是需要设置browerslistrc文件确定要兼容的浏览器范围。

添加之后再次进行测试就解决了问题。

环境变量报错合集

使用cross-env包做了如下配置

"scripts": {
    "start": "cross-env NODE_ENV=dev webpack serve --watch",
    "uat": "cross-env NODE_ENV=uat webpack serve --watch",
},

Uncaught ReferenceError: process is not defined

按照上面配置好后,在项目里打印了下环境变量process.env.NODE_ENV报出这个错误。

其实很好理解打包过程是在node中完成的,环境变量也是在node中配置的。但是我们却是在浏览器环境中访问的这个变量,当然访问不到了。所以我们需要在打包过程中定义一个全局变量,将此时的node环境保存下来。然后在浏览器中在访问这个全局变量就可以解决这个问题,这里要借助webpack自带的DefinPlugin

TypeError: webpack.DefinePlugin is not a constructor

在复制了官网的代码增加了以下配置后,又报了这样一个错。

// webpack.config.js
const NODE_ENV = process.env.NODE_ENV;
console.log("当前打包环境的NODE_ENV是:", NODE_ENV);
......
new webpack.DefinePlugin({
   "process.env.NODE_ENV": JSON.stringify(NODE_ENV) 
})

原因也很简单,就是在引入webpack时自己进行了解构,或者是编译器自动帮你采用结构的方式引入了webpack,所以导致了错误。

// 错误写法
const { webpack } = require("webpack");

// 正确写法1
const webpack = require("webpack");
new webpack.DefinePlugin();

// 正确写法2
const { DefinePlugin } = require("webpack");
new DefinePlugin();

WARNING DefinePlugin Conflicting values for 'process.env.NODE_ENV'

报错这个的原因是webpack内部会根据你当前设置的mode模式将mode的值自动设置给process.env.NODE_ENV。然后你又重新设置一遍,他们的值还不一样,所以报错冲突了。 因此解决方法也很简单,要么就是你不给他重新设置值就使用默认的mode值。

要么就是可以加入以下的自定义配置来取消webpack对这个变量的赋值

optimization: {
    nodeEnv: false
}

mock文件的复制

使用复制插件:copy-webpack-plugin

// 注意:这里的mock文件夹与src文件夹和webpack.config.js同级
new CopyWebpackPlugin({
    patterns: [
        { from: "src/assets", to: "assets" },
        { from: "mock", to: "mock" }
    ]
}),
  1. 复制后的文件并不出现到浏览器控制台的source中,所以哪里没有不代表他没有被复制。
  2. 访问复制后的文件时并不是以webpack.config.js为根路径进行访问,而是以在打包后的文件夹内部根路径来访问。
例如:
访问打包后的mock文件应该采用:http://localhost:8080/mock
而不是:http://localhost:8080/dist/mock
  1. 访问对应文件夹无法判断是否复制成功,而是要访问对应的文件才可以得到。
例如:
像上面那样访问:http://localhost:8080/mock你只会得到:Cannot GET /mock
你应该访问mock下的对应文件,例如:http://localhost:8080/mock/xxx.json
或者是某张静态资源图片:http://localhost:8080/assets/10b22f71ba237249ac0d.jpg

总结

以上就是在项目搭建中遇到的绝大多数问题,本项目集成了 eslint + prettier + husky + lint-staged + typescript + react16。其他方面按照官网配置及网上的需要都可以正常配置好,只有在eslint这里踩了比较大的坑。

虽然在开始之前就预料到了版本问题,但是实际操作过程中版本问题还是给我造成了很大的困扰。所以在配置一个项目时,遇到问题首先要考虑从编译器 => 解析器 => 打包工具 => 包 => node => vscode插件等一系列的版本问题。