{"id":24664586,"url":"https://github.com/qinsong77/webpack5-react-template","last_synced_at":"2026-04-08T16:32:07.794Z","repository":{"id":193829273,"uuid":"519518151","full_name":"qinsong77/webpack5-react-template","owner":"qinsong77","description":"React boilerplate: Webpack5+typescript+react...","archived":false,"fork":false,"pushed_at":"2025-10-20T14:02:41.000Z","size":4540,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-10-20T15:35:51.502Z","etag":null,"topics":["eslint","husky","jest","mswjs","prettier","react-boilerplate","react-starter-project","react-testing-library","tdd","typescript","webpack5"],"latest_commit_sha":null,"homepage":"https://www.sysuke.com/webpack5-react-template/","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/qinsong77.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-07-30T13:03:00.000Z","updated_at":"2025-10-20T14:02:45.000Z","dependencies_parsed_at":null,"dependency_job_id":"7055e0ab-c115-494a-89b9-57f20ae96d2e","html_url":"https://github.com/qinsong77/webpack5-react-template","commit_stats":null,"previous_names":["qinsong77/webpack5-react-template"],"tags_count":0,"template":true,"template_full_name":null,"purl":"pkg:github/qinsong77/webpack5-react-template","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/qinsong77%2Fwebpack5-react-template","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/qinsong77%2Fwebpack5-react-template/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/qinsong77%2Fwebpack5-react-template/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/qinsong77%2Fwebpack5-react-template/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/qinsong77","download_url":"https://codeload.github.com/qinsong77/webpack5-react-template/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/qinsong77%2Fwebpack5-react-template/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31564898,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-08T14:31:17.711Z","status":"ssl_error","status_checked_at":"2026-04-08T14:31:17.202Z","response_time":54,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["eslint","husky","jest","mswjs","prettier","react-boilerplate","react-starter-project","react-testing-library","tdd","typescript","webpack5"],"created_at":"2025-01-26T06:13:34.056Z","updated_at":"2026-04-08T16:32:07.779Z","avatar_url":"https://github.com/qinsong77.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# 从零搭建 webpack5 + React + Typescript + Jest 基础模版\n\n\u003e WIP\n\n- [x] Webpack5\n  - [x] set-up/config with typescript\n  - [x] dev/build/analyzer\n  - [x] React hot refresh\n- [x] Typescript\n- [x] Test\n  - [x] Jest\n  - [x] RTL\n- [x] Code style/lint\n  - [x] husky\n  - [x] eslint\n  - [x] prettier\n  - [x] commitlint\n- [x] Babel\n- [x] Env\n- [x] postcss\n- [ ] mock serve\n  - [msw](https://mswjs.io/docs) for mock server and test, refer: [Stop mocking fetch](https://kentcdodds.com/blog/stop-mocking-fetch)\n  - not stable, jest failed sometime due to it.\n- [x] [Tailwindcss](https://tailwindcss.com/)\n- [x] [ui.shadcn](https://ui.shadcn.com/docs)\n- [ ] [Router](https://reactrouter.com/en/main) =\u003e [TanStack Router](https://tanstack.com/router/v1)\n- [ ] Zustand\n- [x] Generate Api with type: [orval](https://orval.dev/)\n- [ ] axios + useQuery?\n- [x] e2e test: playwright\n  - [midscenejs](https://midscenejs.com/zh/docs/getting-started/introduction.html) using LLM to help testing.\n  - [ ] reuse `data-testid`, make it consistent\n- [ ] Webpack =\u003e [rspack](https://rspack.dev/)\n\n\u003e Issues\n- [ ] `fork-ts-checker-webpack-plugin`会使用`tsconfig.json`的include的字段里去check文件，导致webpack dev时测试文件类型有问题也会报错，暂时是exclude排除了\n- [x] `pnpm run codegen:api`报错，和升级`prettier`有关系，回退到`2.8.4`没问题 =\u003e 使用[orval](https://github.com/orval-labs/orval) 替换了\n- [x] 跑测试axios目前还报错 Network Error, 等msw修复。。 [\"undici\": \"^5.0.0\",](https://github.com/mswjs/msw/discussions/1915)\n- [ ] `msw`结合 `jest` 中[hack的代码](https://mswjs.io/docs/faq/#requestresponsetextencoder-is-not-defined-jest)比较多，need to remove\n- [ ] `orval` 生成的`.msw`文件类型报错，显示是手动注释`@ts-nocheck`，但重新生成会覆盖\n- [x] failed to add ` \"type\": \"module\", ` for package.json, due to webpack crash. =\u003e Replace ts-node with [tsx](https://github.com/privatenumber/tsx) to solve it. but add [thread-loader](https://www.npmjs.com/package/thread-loader) failed.\n\n- [React 开发思想纲领](https://github.com/mithi/react-philosophies) [翻译](https://juejin.cn/post/7076244324614144014)\n- [react 项目架构指南：Bulletproof React](https://github.com/alan2207/bulletproof-react)\n\n\n## 初始化 package.json\n\n这里使用`pnpm`管理`package`，`pnpm`相比npm，yarn最大的优点就是节约磁盘空间并提升安装速度，在我用`pnpm-workspace+turborepo`搭建monorepo的项目中，感触颇深，得益于`pnpm`，在`monorepo`下即使有几十个app+package，安装速度也在接受范围内。\n所以后续的所有命令都使用`pnpm`完成。\n初始化：\n\n```shell\nmkdir webpack5-react-template\ncd webpack5-react-template \npnpm init\n```\n\n![](./image/shoot1.png)\n先稍微介绍下`package.json`中几个主要的字段如`dependencies`,`devDependencies`,`peerDependencies`,`scripts`的意思。\n\n- dependencies: 生产环境，项目运行的依赖（如：react,react-dom）\n- devDependencies: 开发环境，项目所需的依赖（如：webpack插件，打包插件，压缩插件，eslint等）\n- scripts: 指定了运行脚本命令的npm命令行缩写\n- private：如果设为true，无法通过`npm publish`发布代码。\n\n官网解释[文档](https://docs.npmjs.com/cli/v8/configuring-npm/package-json/)\n\n## typescript\n\n```shell\npnpm add typescript -D\n# tsc --init命令创建tsconfig.json\npnpm exec tsc --init \n```\n\n这个时候项目根目录下会生成一份`tsconfig.json`文件，删除了多余的注释，内容如下: \n```shell\n{\n    \"compilerOptions\": {\n        \"target\": \"es5\",\n        \"module\": \"commonjs\",\n        \"esModuleInterop\": true,\n        \"forceConsistentCasingInFileNames\": true,\n        \"strict\": true,\n        \"skipLibCheck\": true\n    }\n}\n```\n添加配置如下\n```json5\n{\n  /* Visit https://aka.ms/tsconfig to read more about this file */\n  \"$schema\": \"https://json.schemastore.org/tsconfig\",\n  \"compilerOptions\": {\n    \"target\": \"es5\",  /* 指定要编译到的目标ECMAScript版本：'ES3'、'ES5'（默认）、'ES2015'、'ES2016'、'ES2017'、'ES2018'、'ES2019'、'ES2020' 或 'ESNEXT'。 */\n    \"module\": \"esnext\", /* 指定要使用的模块系统 */\n    \"lib\": [\n      \"dom\",\n      \"dom.iterable\",\n      \"esnext\"\n    ],  /* 编译过程中需要引入的库文件的列表。 */\n    \"allowJs\": false, /* 不允许编译器编译JS，JSX文件 */\n    \"noEmit\": true, /* 不输出文件,即编译后不会生成任何js文件 */\n\n    \"strict\": true, /* 启用所有严格的类型检查选项。 */\n\n    \"moduleResolution\": \"node\", /** 模块解析策略，ts默认用node的解析策略，即相对的方式导入 */\n    \"allowSyntheticDefaultImports\": true, /* 允许从没有默认导出的模块中默认导入。 这不会影响代码发出，只是类型检查。 */\n    \"esModuleInterop\": true, /* 允许export=导出，由import from 导入 */\n\n    \"noFallthroughCasesInSwitch\": true,  /* 在switch语句中要求处理所有情况，避免出现漏写break导致的错误。 */\n\n    \"resolveJsonModule\": true, /* 允许导入JSON文件作为模块。 */\n    \"isolatedModules\": true, /* 将每个文件转换为一个单独的模块（类似于 'ts.transpileModule'）。 */\n    \"jsx\": \"react-jsx\",\n\n    \"skipLibCheck\": true, /* 跳过对导入的库文件进行类型检查。 */\n    \"forceConsistentCasingInFileNames\": true, /* 禁止对同一文件的大小写不一致地引用。 */\n  },\n  \"include\": [\n    \"src\"\n  ]\n}\n```\n\n## 引入React\n\n安装react\n\n```shell\npnpm i react react-dom\n# 安装类型校验\npnpm i @types/react @types/react-dom -D\n```\n\n新建`src`目录，和`index.tsx`和`app.tsx`文件\n\n```tsx\n// index\nimport * as React from 'react';\nimport * as ReactDOM from 'react-dom/client';\nimport App from './App';\n\nconst root = ReactDOM.createRoot(\n  document.getElementById('root') as HTMLElement\n);\nroot.render(\n  \u003cReact.StrictMode\u003e\n    \u003cApp /\u003e\n  \u003c/React.StrictMode\u003e\n);\n// app.tsx\nconst App = () =\u003e {\n  return (\n    \u003cdiv className=\"App\"\u003e\n      \u003cheader className=\"App-header\"\u003e\n        \u003cp\u003e\n          Edit \u003ccode\u003esrc/App.tsx\u003c/code\u003e and save to reload.\n        \u003c/p\u003e\n        \u003ca\n          className=\"App-link\"\n          href=\"https://reactjs.org\"\n          target=\"_blank\"\n          rel=\"noopener noreferrer\"\u003e\n          Learn React\n        \u003c/a\u003e\n      \u003c/header\u003e\n    \u003c/div\u003e\n  );\n};\n\nexport default App;\n```\n\n### `import React from ‘react’` 和 `import * as React from 'react'`区别\n\n示例代码\n```js\n// constant.js\nexport const a = 1\nconst b = 2\nexport default b \n\n// index.tsx\nimport constant from './constant'\nconsole.log(constant)\n```\n\n不管是 `ts` 还是 `babel`，在将 `esm `编译为 `cjs` 的时候，对于 `export default` 的处理，都会放在一个 `default `的属性上，即 `module.exports.default = xxx`，上面编译的结果大致为：\n\n```js\n\"use strict\";\nObject.defineProperty(exports, \"__esModule\", { value: true }); // 标示这是一个 esm 模块\nexports.a = 1;\nvar b = 2;\nexports.default = b;\n\n// index.tsx\nvar _constant = require(\"./constant\");\n\n// esm 和 cjs 的兼容处理\nvar constant_1 = _constant.__esModule ? _constant : {default: _constant}; \nconsole.log(constant_1.default);\n```\n\n在默认情况下ts会将`esm`模块编译成`commonjs`\n\n- 对于 `export default `的变量，ts会将其放在` module.exports` 的 `default` 属性上\n- 对于 `export` 的变量，ts会将其放在 `module.exports` 对应变量名的属性上\n- 额外给 `module.exports` 增加一个 `__esModule: true `的属性，用来告诉编译器，这本来是一个 esm 模块\n\n看一下`npm`包中`react`的导出\n\n![](./image/shoot2.png)\n\n可以看到通过npm方式引用react时默认是以`commonjs`方式导出的，结合上面ts默认编译的规则，`import React from 'react'` 会从 `exports.default` 上去拿代码，显然此时`default`属性不存在`commonjs`模块中，因此会导致打印`undefined`；而`import * as React from 'react'` 则会把React作为为一个对象，因此不会有问题。\n\n首先对于 [react v16.13.0](https://github.com/facebook/react/blob/v16.12.0/packages/react/src/React.js) 之前的版本都是通过 `export default` 导出的，所以使用 `import React from 'react'` 来导入 react，上面的 console.log(constant) 才不会是 undefined\n\n但是从  [react v16.13.0](https://github.com/facebook/react/blob/v16.13.0/packages/react/src/React.js) 开始，react 就改成了用 `export` 的方式导出了，如果在 ts 中使用 `import React from 'react'` 则会有错误提示：\n\n```\nTS1259: Module 'xxxx' has no default export.\n```\n\n由于没有了 `default` 属性，所以上面编译后的代码` console.log(constant)` 输出的是 `undefined ，ts 会提示有错误。\n\n### esModuleInterop 和 allowSyntheticDefaultImports\n\n上面的问题延伸一下，其实不仅仅是引入react，在`esm`中引入任何`commonjs`的模块在ts默认编译时都会有这样的问题，ts提供了`esModuleInterop` 和 `allowSyntheticDefaultImports` 这两个配置来影响ts默认的解析。\n\n`allowSyntheticDefaultImports`是一个类型检查的配置，它会把`import`没有`exports.default`的报错忽略，如果你的`target`是`es6`加上这个配置就够了，但如果你的目标代码是`es5`仅仅加上这个还不行，还需要使用`esModuleInterop`，因为它才会改变tsc的编译产物：\n\n```\n// tsconfig.json\n\n{\n    \"compilerOptions\": {\n      \"module\": \"commonjs\",\n      \"target\": \"es5\",\n      \"esModuleInterop\":true\n    }\n }\n \n// index.tsx\nimport React from 'react';\nconsole.log(React.useEffect)\n\n// tsc产物\n\"use strict\";\nvar __importDefault = (this \u0026\u0026 this.__importDefault) || function (mod) {\n    return (mod \u0026\u0026 mod.__esModule) ? mod : { \"default\": mod };\n};\nObject.defineProperty(exports, \"__esModule\", { value: true });\nvar react_1 = __importDefault(require(\"react\"));\nconsole.log(react_1.default.useEffect);\n```\n\n在加上`esModuleInterop` 之后编译产物多了一个`_importDefault` 辅助函数，而他的作用就是给`module.exports` 加上`default` 属性。\n根据 [ts官网](https://www.typescriptlang.org/tsconfig#allowSyntheticDefaultImports)的说明 开启`esModuleInterop`的同时也会默认开启`allowSyntheticDefaultImports`,因此更推荐直接加`esModuleInterop`。\n\n## 项目目录\n\n```markdown\nreact-ts-template\n├── package.json\n├── public # 存放html模板\n├── webpack # webpack配置\n│ ├── config # 配置文件\n│ ├── utils # \n│ ├── webpack.common.ts\n│ ├── webpack.development.ts\n│ ├── webpack.production.ts\n├── README.md\n├── src\n│ ├── assets # 存放会被 Webpack 处理的静态资源文件：一般是图片等静态资源\n│ │ ├── fonts # iconfont 目录\n│ │ ├── images # 图片资源目录\n│ ├── common # 存放项目通用文件\n│ ├── components # 项目中通用的组件目录\n│ ├── feature # 项目中通用的业务组件目录\n│ ├── config # 项目配置文件\n│ ├── pages # 项目页面目录\n│ │ ├── routes.tsx # 路由目录\n│ ├── typings # 项目中d.ts 声明文件目录\n│ ├── types # 项目中声明文件\n│ ├── uiLibrary # 组件库\n│ ├── services # 和后端相关的文件目录\n│ ├── store # redux 仓库\n│ ├── style  global style文件夹\n│ ├── utils # 全局通用工具函数目录\n│ ├── App.tsx # App全局\n│ ├── index.tsx # 项目入口文件\n└── tsconfig.json # TS 配置文件\n└── tsconfig.webpack.json # 给ts-node指定webpack的tsconfig-paths时使用\n```\n\n## webpack\n\n```shell\npnpm add webpack webpack-cli webpack-dev-server webpack-merge -D\n```\n\n这里`webpack的`配置文件也使用`typescript`，需要额外配置，参考官网[Configuration Languages](https://webpack.docschina.org/configuration/configuration-languages/)\n\n要使用 Typescript 来编写 webpack 配置，需要先安装必要的依赖，比如 Typescript 以及其相应的类型声明，类型声明可以从 `DefinitelyTyped` 项目中获取，依赖安装如下所示：\n\n```shell\npnpm add ts-node @types/node @types/webpack -D\n```\n\n值得注意的是你需要确保 `tsconfig.json` 的 `compilerOptions` 中 `module` 选项的值为 `commonjs`,否则 webpack 的运行会失败报错，因为 `ts-node` 不支持 `commonjs` 以外的其他模块规范。\n\n官网有三种设置方式，这里选择第三种\n\n先安装 `tsconfig-paths` 这个 npm 包，如下所示：\n\n```shell\npnpm add tsconfig-paths -D\n```\n然后添加`tsconfig.webpack.json`\n```json\n{\n  \"compilerOptions\": {\n    \"module\": \"commonjs\",\n    \"target\": \"es5\",\n    \"esModuleInterop\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"downlevelIteration\": true\n  },\n  \"include\": [\"webpack\"]\n}\n```\npackage.json\n```json\n{\n  \"scripts\": {\n    \"build\": \"cross-env TS_NODE_PROJECT=\\\"tsconfig.webpack.json\\\" webpack\"\n  }\n}\n```\n之所以要添加 `cross-env`，是因为我们在直接使用 `TS_NODE_PROJECT` 时遇到过 \"TS_NODE_PROJECT\" unrecognized command 报错的反馈，添加` cross-env` 之后该问题也似乎得到了解决，可以查看这个[issue](https://github.com/webpack/webpack.js.org/issues/2733)\n\n\u003e updated: Replace ts-node with [tsx](https://github.com/privatenumber/tsx)\n\n### 安装相关插件\n\n- html-webpack-plugin: 在webpack构建后生成html文件，同时把构建好入口js等文件引入到生成的html文件中。\n- mini-css-extract-plugin：抽取css为单独的css文件.\n- css-minimizer-webpack-plugin： 使用 [cssnano](https://cssnano.co/) 优化和压缩 CSS.\n- style-loader: 开发环境选择下使用`style-loader`, 它可以使用多个标签将 CSS 插入到 DOM 中，反应会更快\n- css-loader：css-loader 会对 `@import` 和 `url() `进行处理，就像 js 解析 `import/require()` 一样。\n- @pmmmwh/react-refresh-webpack-plugin \u0026\u0026 react-refresh: react热更新\n- dotenv：可以将环境变量中的变量从 `.env `文件加载到 `process.env` 中。\n- cross-env：运行跨平台设置和使用环境变量的脚本\n- @soda/friendly-errors-webpack-plugin: 用于美化控制台，良好的提示错误。\n- fork-ts-checker-webpack-plugin: runs TypeScript type checker on a separate process.\n- babel相关，后续单独罗列\n- postcss等，后续单独罗列\n\n```shell\npnpm add html-webpack-plugin @pmmmwh/react-refresh-webpack-plugin react-refresh dotenv cross-env mini-css-extract-plugin css-minimizer-webpack-plugin style-loader css-loader @soda/friendly-errors-webpack-plugin fork-ts-checker-webpack-plugin -D\n```\n\n### 添加public文件夹\n\n添加`index.html`\n\n```html\n\u003chtml lang=\"en\"\u003e\n  \u003chead\u003e\n    \u003cmeta charset=\"utf-8\" /\u003e\n    \u003ctitle\u003e\u003c%= htmlWebpackPlugin.options.title %\u003e\u003c/title\u003e\n    \u003clink rel=\"icon\" href=\"\u003c%= htmlWebpackPlugin.options.publicPath %\u003efavicon.ico\" /\u003e\n    \u003cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1\" /\u003e\n    \u003cmeta name=\"theme-color\" content=\"#000000\" /\u003e\n    \u003cmeta\n      name=\"description\"\n      content=\"\u003c%= htmlWebpackPlugin.options.description %\u003e\"\n    /\u003e\n    \u003clink rel=\"apple-touch-icon\" href=\"\u003c%= htmlWebpackPlugin.options.publicPath %\u003elogo192.png\" /\u003e\n  \u003c/head\u003e\n  \u003cbody\u003e\n    \u003cnoscript\u003eYou need to enable JavaScript to run this app.\u003c/noscript\u003e\n    \u003cdiv id=\"root\"\u003e\u003c/div\u003e\n  \u003c/body\u003e\n\u003c/html\u003e\n```\n\n### webpack中的指纹策略\n\n比如 `filename: '[name].[hash].[ext]'`\n\n- hash：以项目为单位，项目内容改变了，则会生成新的`hash`，内容不变则`hash`不变。 整个工程任何一个需要被打包的文件发生了改变，打包结果中的所有文件的hash值都会改变。\n- chunkhash：以`chunk`为单位，当一个文件内容改变，则整个`chunk`组的模块hash都会改变。\n\n比如： 假设打包出口有`a.123.js`和`c.123.js`，a文件中引入了b文件，修改了b文件的内容，重新的打包结果为`a.111.js`和`c.123.js` 的`hash`值会被影响，但是**c的hash值不受影响**。\n\n- contenthash：以自身内容为单位，依赖不算。\n\n### 静态资源\n\nwebpack5 之前，通常使用\n\n- raw-loader 将文件导入为字符串\n- url-loader 将文件作为data URL 内联到bundle中\n- file-loader 将文件发送到输出目录\n\n相比webpack5之前需要`url-loader`、`file-loader`等处理，在webpack5中直接内置了 [`asset`](https://webpack.docschina.org/guides/asset-modules/) 模块，\n\n- `asset/resource`发送一个单独的文件并导出 URL。之前通过使用`file-loader`实现\n- `asset/inline` 导出一个资源的 data URI。之前通过使用`url-loader`实现。\n- `asset/source`导出资源的源代码。之前通过使用`raw-loader`实现。\n- asset在导出一个 data URI 和发送一个单独的文件之间自动选择。之前通过使用url-loader，并且配置资源体积限制实现。\n\n关于配置`type:'asset'`后，webpack 将按照默认条件，自动地在 `resource` 和 `inline` 之间进行选择：小于 8kb 的文件，将会视为 `inline` 模块类型，否则会被视为 `resource` 模块类型。\n\n## ENV 相关\n\n- dotenv：可以将环境变量中的变量从 `.env `文件加载到 `process.env` 中。\n- cross-env：运行跨平台设置和使用环境变量的脚本\n- [DefinePlugin](https://webpack.js.org/plugins/define-plugin/): 允许在 `编译时` 将代码中的变量替换为其他值或表达式\n\n以`REACT_APP_`开头的env，DefinePlugin会自动处理替换。\n\n利用这里也可以实现，部署到不同环境下，都**只build一次**，配置不同的环境变量注入。\n\n## babel 设置\n\n关于TS转JS，有三种方案\n- tsc: 不好配合`webpack`使用，转换`es5`以后，一些语法特性不能转换。\n- [ts-loader](https://www.npmjs.com/package/ts-loader): 可以做类型检查，可搭配`tsconfig.json`使用。\n- `babel-loader` + `@babel/preset-typescript`, 插件丰富，提供缓存机制，后续兼容扩展更强，但做不了类型检查(可以使用[Fork TS Checker Webpack Plugin](https://www.npmjs.com/package/fork-ts-checker-webpack-plugin)。（推荐）\n\n`tsc` 生成的代码没有做 `polyfill` 的处理，需要全量引入 `core-js`，而 `babel` 则可以用 `@babel/preset-env` 根据 `targets` 的配置来按需引入 `core-js` 的部分模块，所以生成的代码体积更小。\n\nbabel 缺点就是有一些 ts 语法并不支持：\n\n比如不支持 `const enum`（会作为 enum 处理），不支持 `namespace` 的跨文件合并，导出非 const 的值，不支持过时的 export = import = 的模块语法。\n\n但关系不大。\n\n这里选择第三种，安装依赖：\n```shell\npnpm i babel-loader @babel/core @babel/preset-env @babel/preset-react @babel/preset-typescript core-js -D\n# 作为project不需要，library使用runtime\n# pnpm i @babel/plugin-transform-runtime -D\n# pnpm add @babel/runtime\n```\n\u003e 类库项目的构建如果需要注入 `polyfill` 的话，最好使用 `@babel/transform-runtime`，因为它提供了一种不污染全局作用域的方式。\n而业务项目中最好使用 `preset-env` 的 `useBuintIns` 配置来注入 polyfill，这种方式会污染全局作用域。\n\n- [前端基建」探索不同项目场景下Babel最佳实践方案](https://juejin.cn/post/7051355444341637128)\n\n- [@babel/preset-env 与@babel/plugin-transform-runtime 使用及场景区别](https://segmentfault.com/a/1190000021188054)\n- [babel-loader](https://webpack.docschina.org/loaders/babel-loader): 使用 Babel 和 webpack 转译 JavaScript 等文件，内部核心转译功能需要`@babel/core`这个核心库。\n- `@babel/core`: `@babel/core`是babel的核心库，所有的核心api都在这个库里，这些api可供`babel-loader`调用\n- [@babel/preset-env](https://babeljs.io/docs/babel-preset-env): 这是一个预设的插件集合，包含了一组相关的插件，Babel中是通过各种插件来指导如何进行代码转换。该插件包含所有es6转化为es5的翻译规则。可以做到按需加载我们需要的 `polyfill`\n\n\u003e @babel/prest-env是babel转译过程中的一些预设，它负责将一些基础的es 6+语法，比如const/let...转译成为浏览器可以识别的低级别兼容性语法。这里需要注意的是`@babel/prest-env`并不会对于一些es6+高版本语法的实现，比如`Promise`等polyfill，你可以将它理解为语法层面的转化不包含高级别模块(polyfill)的实现。\n\n- @babel/runtime:  is a library that contains Babel modular runtime helpers. preset-env的polyfill会污染全局环境，项目开发可以接受，但做library时最好避免，不应该污染全局，并且应该提供更好的打包体积和效率\n- @babel/plugin-transform-runtime: A plugin that enables the re-use of Babel's injected helper code to save on codesize.\n  - 当开发者使用异步或生成器的时候，自动引入@babel/runtime/regenerator，开发者不必在入口文件做额外引入；\n  - 提供沙盒环境，避免全局环境的污染\n  - 移除babel内联的helpers，统一使用@babel/runtime/helpers代替，减小打包体积\n- [@babel/preset-react](https://babeljs.io/docs/en/babel-preset-react): Babel preset for all React plugins.是一组预设，所谓预设就是内置了一系列babel plugin去转化jsx代码成为我们想要的js代码\n- @babel/preset-typescript:这是一个插件，使Babel能够将TypeScript代码转化为JavaScript。\n- @babel/polyfill：@babel/preset-env只是提供了语法转换的规则，但是它并不能弥补浏览器缺失的一些新的功能，如一些内置的方法和对象，如Promise，Array.from等，此时就需要polyfill来做js的垫片，弥补低版本浏览器缺失的这些新功能。注意：Babel 7.4.0该包将被废弃\n- core-js：它是JavaScript标准库的polyfill，而且它可以实现按需加载。使用@babel/preset-env的时候可以配置core-js的版本和core-js的引入方式。\n- regenerator-runtime：提供generator函数的转码\n\n`babel.config.js`\n```js\nconst IS_DEV = process.env.NODE_ENV === 'development'\n\n/** @type {import('@babel/core').ConfigFunction} */\nmodule.exports = {\n  presets: [\n    [\n      '@babel/preset-env',\n      {\n        useBuiltIns: 'usage',\n        // https://babeljs.io/docs/babel-preset-env#corejs\n        corejs: {\n          version: 3,\n          proposals: true, // 使用尚在提议阶段特性的 polyfill\n        },\n      },\n    ],\n    [\n      '@babel/preset-react',\n      {\n        runtime: 'automatic',\n        development: IS_DEV,\n      },\n    ],\n    '@babel/preset-typescript',\n  ],\n  plugins: [].concat(IS_DEV ? ['react-refresh/babel'] : []),\n}\n```\n\n### browserslist\n\nbrowserslist实际上就是声明了一段浏览器的合集，我们的工具可以根据这个合集描述，针对性的输出兼容性代码，browserslist应用于babel、postcss等工具当中。\n\n“\u003e 1%”表示兼容市面上使用量大于百分之一的浏览，“last 1 chrome version”表示兼容到谷歌的上一个版本，具体的可以使用命令npx browserslist \"\u003e 1%\"的方式查看都包含了哪些浏览器\n\nbrowserslist可以在`package.json`文件配置，也可以单出写一个`.browserslistrc`文件进行配置。\n工具会自动查找`.browserslistrc`中的配置，如果没有发现`.browserslistrc`文件，则会去package.json中查找\n\n```\n// 在.browserslistrc中的写法\n\u003e 1%\nlast 2 versions\n\n// 还可以配置不同环境下的规则（在.browserslistrc中）\n[production]\n\u003e 1%\nie 10\n\n[development]\nlast 1 chrome version\nlast 1 firefox version\n\n// 在package.json中的写法\n{\n  \"browserslist\": [\"\u003e 1%\", \"last 2 versions\"]\n}\n\n// 还可以配置不同环境下的规则（在package.json中）\n// production和development取决你webpack中mode字段的配置\n{\n  \"browserslist\": {\n    \"production\": [\n     \"\u003e0.2%\",\n     \"not dead\",\n     \"not op_mini all\"\n    ],\n    \"development\": [\n     \"last 1 chrome version\",\n     \"last 1 firefox version\",\n     \"last 1 safari version\"\n    ]\n }\n}\n```\n\n## postcss\n\npostcss其实就是类似css中的babel的作用，\n\n### [tailwindcss](https://tailwindcss.com/docs/installation/using-postcss)\n\n1. `pnpm add tailwindcss -D`\n2. `postcss.config.js`引入`tailwindcss`作为plugin\n3. 修改index.css, 引入`tailwindcss`组件(如官网`@tailwind base;` etc...)\n\n* 巨坑，和`css-loader`配置`modules`冲突,`modules`有了`tailwindcss`就不work了\n\n```\n{\n        loader: 'css-loader',\n        options: {\n          modules: {\n            localIdentName: '[local]_[hash:base64:5]',\n          },\n        },\n      },\n```\n```shell\npnpm add postcss postcss-loader postcss-preset-env postcss-flexbugs-fixes postcss-normalize -D\n```\n\n- [ ] Maybe [unocss](https://unocss.dev/guide/) is better?\n\n[transform-to-unocss](https://github.com/Simon-He95/transformToUnocss/blob/main/README_zh.md)\n\n## eslint, Prettier\n\n[ESLint](https://eslint.org/)是一个前端标准的静态代码检查工具，它可以根据配置的规则来检查代码是否符合规范。\n\n而 [Prettier](https://prettier.io/) 是一个代码格式化工具。 ESLint 是通过制定的的规范来检查代码的，这里的 **规范** 有两种：\n\n* 代码风格规范\n* 代码质量规范\n\nPrettier 主要负责的是**代码风格**。\n\n### `extends` vs `plugins`\n\nESLint 中 `extends` 和 `plugins` 这两个配置参数的区别总是会困扰。\n\n**`plugins` 只是开启了这个插件，而 `extends` 则会继承别人写好的一份 `.eslintrc` 的配置，这份配置不仅仅包括了 `rules`, 还有 `parser`，`plugins` 之类的东西。**\n\n**注意：要把 `Prettier` 的推荐配置 `plugin:prettier/recommended` 放在 `extends` 最后一项。**\n\n\n举个例子，假如我们要配置 ESLint + TypeScript，可以看到官网有这样的配置：\n\n```js\nmodule.exports = {\n  root: true,\n  parser: '@typescript-eslint/parser',\n  plugins: [\n    '@typescript-eslint',\n  ],\n  extends: [\n    'eslint:recommended',\n    'plugin:@typescript-eslint/recommended',\n  ],\n};\n```\n\n神奇的是，当你去掉 `plugins` 之后发现 `eslint` 依然可以正常工作。更神奇的是，只要你写了 `extends`，那么连 `parser` 也可以不用加，要知道没有指定 `parser` 选项，eslint 可看不懂你的 TypeScript 文件。\n\n所以说，到底是 `plugins` 加上了 TypeScript 的能力还是 `extends` 加上了 TypeScript 的规则呢？很让人困惑，翻找了一下网上的资料发现了[这个帖子](https://stackoverflow.com/questions/61528185/eslint-extends-vs-plugins-v2020)。\n\n先来说结论吧：**`plugins` 只是开启了这个插件，而 `extends` 则会继承别人写好的一份 `.eslintrc` 的配置，这份配置不仅仅包括了 `rules` 还有 `parser`，`plugins` 之类的东西。**\n\n所以回到问题，为什么在继承了 `plugin:@typescript-eslint/recommended` 之后就可以不写 `plugins` 和 `parser` 呢？因为别人已经把配置都放在 `recommended` 这份配置表里了，这样对使用的人来说，就可以少写很多配置项了。\n\n也就是说，下面两份配置是等价的：\n\n```js\nmodule.exports = {\n  parser: \"@typescript-eslint/parser\",\n  parserOptions: { sourceType: \"module\" },\n  plugins: [\"@typescript-eslint\"],\n  extends: [],\n  rules: {\n    \"@typescript-eslint/explicit-function-return-type\": [\n      \"error\",\n      {\n        allowExpressions: true\n      }\n    ]\n  }\n}\n```\n\n以及\n\n```js\nmodule.exports = {\n  plugins: [],\n  extends: [\"plugin:@typescript-eslint/recommended\"],\n  rules: {\n    \"@typescript-eslint/explicit-function-return-type\": [\n      \"error\",\n      {\n        allowExpressions: true\n      }\n    ]\n  }\n}\n```\n\n对于第一份配置：\n* 需要手动添加 `parser`, `parserOptions`, `plugins`\n* 只开启了 `@typescript-eslint/explicit-function-return-type` 一个规则\n\n对于第二份配置：\n* `plugin:@typescript-eslint/recommended` 自动添加了 `parser`, `parserOptions`, `plugins`\n* 自动加上一些推荐的 TypeScript 的 ESLint 规则\n* 自定义了 `@typescript-eslint/explicit-function-return-type` 规则\n\n```shell\npnpm add prettier -D\npnpm add eslint -D\npnpm add @typescript-eslint/parser  @typescript-eslint/eslint-plugin -D\npnpm add eslint-config-prettier eslint-plugin-prettier -D\npnpm add eslint-plugin-react eslint-plugin-react-hooks -D\npnpm add eslint-plugin-import eslint-import-resolver-typescript -D\n```\n- `eslint-plugin-import `: This plugin intends to support linting of ES2015+ (ES6+) import/export **syntax**(支持 ES2015+ (ES6+) 导入/导出语法的 linting), and prevent issues with misspelling of file paths and import names.\n- `eslint-import-resolver-typescript`: This plugin adds `TypeScript` support to `eslint-plugin-import`\n- [`eslint-plugin-simple-import-sort`](https://github.com/lydell/eslint-plugin-simple-import-sort) : Easy autofixable import sorting.\n### crate-react-app使用的配置\n- [eslint-config-react-app](https://www.npmjs.com/package/eslint-config-react-app)\n\n太麻烦了，还不如用[biomejs](https://biomejs.dev/guides/getting-started/)\n\n## lint-stage, husky, commitlint\n\n统一编辑器格式`.editorconfig`\n```editorconfig\n# Editor configuration, see http://editorconfig.org\nroot = true\n\n[*]\ncharset = utf-8\nend_of_line = lf\nindent_style = space\nindent_size = 2\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n\n[*.md]\nmax_line_length = off\ntrim_trailing_whitespace = false\n```\n\n\n`husky`用来绑定 `Git Hooks`,在指定时机（例如 `pre-commit`）执行我们想要的命令，比如可用于提交代码时进行 `eslint` 校验，如果有 `eslint` 报错可阻止代码提交。详细的安装使用方式可参考 [Husky 文档](https://typicode.github.io/husky/#/?id=automatic-recommended)\n\n`lint-staged` 能够让`lint`只检测`git缓存区`的文件，提升速度。\n\n```shell\npnpm add husky lint-staged -D\n```\n\npackage.json中添加命令\n\n```json\n{\n  \"scripts\":{\n    \"prepare\": \"husky install \u0026 npx only-allow pnpm\"\n  },\n  \"lint-staged\": {\n    \"*.{js,jsx,ts,tsx}\": [\"prettier --write\", \"eslint  --fix\"]\n  }\n}\n```\n或者\n```shell\npnpm i lint-staged husky -D\npnpm set-script prepare \"husky install\" # 在package.json中添加脚本\npnpm run prepare # 初始化husky,将 git hooks 钩子交由husky执行\n```\n接着设置你想要的git hooks\n\nHusky 初始化完成后，`pnpm dlx husky add .husky/commit-msg \"npx --no-install commitlint --edit $1\"`\n\n.husky下会出现文件`commit-msg`如下\n```shell\n#!/usr/bin/env sh\n. \"$(dirname -- \"$0\")/_/husky.sh\"\n\nnpx --no-install commitlint --edit \n```\n添加 lint-staged\n```shell\npnpm dlx husky add .husky/pre-commit \"npx --no-install lint-staged\" \n```\n\n### 规范代码提交\n\n`@commitlint/config-conventional` `@commitlint/cli` 制定了`git commit`提交规范，团队可以更清晰地查看每一次代码的提交记录\n\n`@commitlint/config-conventional` 这是一个规范配置，标识采用什么规范来执行消息校验, 这个默认是Angular的提交规范\n\n\n```shell\npnpm add -D @commitlint/config-conventional @commitlint/cli @commitlint/types \n```\n\n在项目根目录下创建`commitlint.config.ts`\n\n```ts\nimport type { UserConfig } from '@commitlint/types'\n\nconst Configuration: UserConfig = {\n  extends: ['@commitlint/config-conventional'],\n}\n\nexport default Configuration\n```\n\n### 使用commitizen规范commit提交格式\n\n`commitizen` 的作用主要是为了生成标准化的 `commit message`，符合 `Angular` 规范。\n\n一个标准化的 `commit message` 应该包含三个部分：Header、Body 和 Footer，其中的 Header 是必须的，Body 和 Footer 可以选填。\n\n```\n\u003ctype\u003e(\u003cscope\u003e): \u003csubject\u003e\n// 空一行\n\u003cbody\u003e\n// 空一行\n\u003cfooter\u003e\n```\n\nHeader 部分由三个字段组成：type（必需）、scope（可选）、subject（必需）\n\n- Type\n  `type` 必须是下面的其中之一：\n  - feat: 增加新功能\n  - fix: 修复 bug\n  - docs: 只改动了文档相关的内容\n  - style: 不影响代码含义的改动，例如去掉空格、改变缩进、增删分号\n  - refactor: 代码重构时使用，既不是新增功能也不是代码的bud修复\n  - perf: 提高性能的修改\n  - test: 添加或修改测试代码\n  - build: 构建工具或者外部依赖包的修改，比如更新依赖包的版本\n  - ci: 持续集成的配置文件或者脚本的修改\n  - chore: 杂项，其他不需要修改源代码或不需要修改测试代码的修改\n  - revert: 撤销某次提交\n\n- scope\n\n用于说明本次提交的影响范围。`scope` 依据项目而定，例如在业务项目中可以依据菜单或者功能模块划分，如果是组件库开发，则可以依据组件划分。\n\n- subject\n\n主题包含对更改的简洁描述：\n\n注意三点：\n\n1. 使用祈使语气，现在时，比如使用 \"change\" 而不是 \"changed\" 或者 ”changes“\n2. 第一个字母不要大写\n3. 末尾不要以.结尾\n\n- Body\n\n主要包含对主题的进一步描述，同样的，应该使用祈使语气，包含本次修改的动机并将其与之前的行为进行对比。\n\n- Footer\n\n包含此次提交有关重大更改的信息，引用此次提交关闭的issue地址，如果代码的提交是不兼容变更或关闭缺陷，则Footer必需，否则可以省略。\n\n使用方法：\n\n如果需要在项目中使用 `commitizen` 生成符合 `AngularJS` 规范的提交说明，还需要安装 `cz-conventional-changelog` 适配器。\n```shell\npnpm i commitizen cz-conventional-changelog -D\n```\n\n安装指令和命令行的展示信息\n```shell\npnpm set-script commit \"git-cz\" # package.json 中添加 commit 指令, 执行 `git-cz` 指令\n```\n初始化commit指令(可能出错)\n```shell\npnpm dlx commitizen init cz-conventional-changelog --save-dev --save-exact\n```\n或者直接在package.json添加\n```json\n{\n  \"config\": {\n    \"commitizen\": {\n      \"path\": \"cz-conventional-changelog\"\n    }\n  }\n}\n```\n接下来就可以使用 `$ pnpm commit` 来代替 `$ git commit` 进行代码提交了。\n\n也可以自定义提交规范，`cz-conventional-changelog`就可以移除了\n```shell\npnpm i commitlint-config-cz  cz-customizable -D\n```\n增加 `.cz-config.js`如下\n并修改配置：\n```json\n{\n  \"config\": {\n    \"commitizen\": {\n      \"path\": \"node_modules/cz-customizable\"\n    }\n  }\n}\n```\n官方[example](https://github.com/leoforfree/cz-customizable/blob/master/cz-config-EXAMPLE.js)\n```shell\n\"use strict\";\nmodule.exports = {\n  types: [\n    { value: \"✨新增\", name: \"新增:    新的内容\" },\n    { value: \"🐛修复\", name: \"修复:    修复一个Bug\" },\n    { value: \"📝文档\", name: \"文档:    变更的只有文档\" },\n    { value: \"💄格式\", name: \"格式:    空格, 分号等格式修复\" },\n    { value: \"♻️重构\", name: \"重构:    代码重构，注意和特性、修复区分开\" },\n    { value: \"⚡️性能\", name: \"性能:    提升性能\" },\n    { value: \"✅测试\", name: \"测试:    添加一个测试\" },\n    { value: \"🔧工具\", name: \"工具:    开发工具变动(构建、脚手架工具等)\" },\n    { value: \"⏪回滚\", name: \"回滚:    代码回退\" }\n  ],\n  scopes: [\n    { name: \"javascript\" },\n    { name: \"typescript\" },\n    { name: \"react\" },\n    { name: \"test\" }\n    { name: \"node\" }\n  ],\n  // it needs to match the value for field type. Eg.: 'fix'\n  /*  scopeOverrides: {\n    fix: [\n      {name: 'merge'},\n      {name: 'style'},\n      {name: 'e2eTest'},\n      {name: 'unitTest'}\n    ]\n  },  */\n  // override the messages, defaults are as follows\n  messages: {\n    type: \"选择一种你的提交类型:\",\n    scope: \"选择一个scope (可选):\",\n    // used if allowCustomScopes is true\n    customScope: \"Denote the SCOPE of this change:\",\n    subject: \"短说明:\\n\",\n    body: \"长说明，使用\\\"|\\\"换行(可选)：\\n\",\n    breaking: \"非兼容性说明 (可选):\\n\",\n    footer: \"关联关闭的issue，例如：#31, #34(可选):\\n\",\n    confirmCommit: \"确定提交说明?(yes/no)\"\n  },\n  allowCustomScopes: true,\n  allowBreakingChanges: [\"特性\", \"修复\"],\n  // limit subject length\n  subjectLimit: 100\n};\n```\n\n\n## analyze\n\n```shell\nwebpack --profile --json \u003e stats.json\npnpm i webpack-bundle-analyzer -g \nwebpack-bundle-analyzer stats.json \n```\n\n\n## [optimization.runtimeChunk](https://webpack.docschina.org/configuration/optimization/#optimizationruntimechunk)\n\n将 `optimization.runtimeChunk` 设置为 `true` 或 `multiple`，会为每个入口添加一个只含有 `runtime` 的额外 `chunk`。此配置的别名如下：\n\nwebpack.config.js\n\n```js\nmodule.exports = {\n  //...\n  optimization: {\n    runtimeChunk: {\n      name: (entrypoint) =\u003e `runtime~${entrypoint.name}`,\n    },\n  },\n};\n```\n运行时的chunk文件，形如`import('abc').then(res=\u003e{})`这种异步加载的代码，在webpack中即为运行时代码。比如这样异步引入一个组件：\n```tsx\nconst Button = React.lazy(\n  () =\u003e import(/* webpackChunkName: \"Button\" */ './components/Button')\n);\n```\n如果不设置runtimeChunk，默认是false，第一次打包：\n![](./image/noruntime-1.png)\n\n修改Button组件后打包:\n![](./image/noruntime-2.png)\n\n可以看到入口文件main的hash也变了。而我们明明只改了button组件。\n\n可设置runtimeChunk为true\n\u003e设置runtimeChunk是将包含chunks 映射关系的 list单独从 main.js里提取出来，因为每一个 chunk 的 id 基本都是基于内容 hash 出来的，所以每次改动都会影响它，如果不将它提取出来的话，等于 main.js每次都会改变。缓存就失效了。设置runtimeChunk之后，webpack就会生成一个个`runtime~xxx.js`的文件。\n然后每次更改所谓的运行时代码文件时，打包构建时 main.js的hash值是不会改变的。如果每次项目更新都会更改 main.js的hash值，那么用户端浏览器每次都需要重新加载变化的app.js，如果项目大切优化分包没做好的话会导致第一次加载很耗时，导致用户体验变差。现在设置了runtimeChunk，就解决了这样的问题。所以这样做的目的是避免文件的频繁变更导致浏览器缓存失效，所以其是更好的利用缓存。提升用户体验。\n\n\n但是这样又有一个问题，runtime.js size很小，如果chunk有变化，这个文件每次构建都会变，多个一个http请求。每次重新构建上线后，浏览器每次都需要重新请求它，它的 http 耗时远大于它的执行时间了，所以建议不要将它单独拆包，而是将它内联到我们的 index.html 之中.\n\n可使用插件[script-ext-html-webpack-plugin](https://www.npmjs.com/package/script-ext-html-webpack-plugin)解决。但这个插件虽然能用，但和webpack5不兼容了。可以使用插件[hwp-inline-runtime-chunk-plugin](https://www.npmjs.com/package/hwp-inline-runtime-chunk-plugin)代替。\n```js\nmodule.exports = {\n  //...\n  optimization: {\n    runtimeChunk: true,\n  },\n  plugins: [\n      // ...\n    new ScriptExtHtmlWebpackPlugin({\n      inline: /runtime~.+\\.js$/,\n    }),\n  ]\n};\n```\n\n## [tree-shaking](https://webpack.js.org/guides/tree-shaking/#root)\n\n在 Webpack 中，启动 Tree Shaking 功能必须同时满足三个条件：\n\n- 使用 ESM 规范编写模块代码(`import` and `export`)\n- 配置 `optimization.usedExports` 为 true（默认值），启动标记功能\n- 启动代码优化功能，可以通过如下方式实现： \n  - 配置 `mode = production `\n  - 配置 `optimization.minimize = true `（默认值）\n  - 提供 [`optimization.minimizer`](https://webpack.js.org/configuration/optimization/#optimizationminimizer) 数组, 注入 `Terser`（minimize为true时如果不覆盖选项，默认启用，覆盖了要单独引入使用）、`UglifyJS` 插件\n\nsideEffects\n\n`usedExports`是检查上下文有没有引用，如果没有引用，就会注入魔法注释，通过`terser`压缩进行去除未引入的代码\n\n而`sideEffects`是对没有副作用的代码进行去除\n\ncss tree shaking\n\nhttps://blog.csdn.net/pfourfire/article/details/126505335\n```js\n// webpack.config.js\nmodule.exports = {\n  entry: \"./src/index\",\n  mode: \"production\",\n  devtool: false,\n  optimization: {\n    usedExports: true,\n  },\n};\n```\n\n### webpack-bundle-analyzer\n\n每个打包以后的 bundle 文件里面，真正包含哪些内容，项目里的 module、js、component、html、css、img 最后都被放到哪个对应的 bunlde 文件里了。\n\n每个 bundle 文件里，列出了每一个的 module、component、js 具体 size，同时会列出 start size、parsed size、gzip size 这三种不同的形式下到底多大，方便优化。\n\n- start size：原始没有经过 minify 处理的文件大小\n\n- parse size：比如 webpack plugin 里用了 uglify，就是 minified 以后的文件大小\n\n- gzip size：被压缩以后的文件大小\n\n\n## 技术选型\n\n### css方案\n\n[stateofcss](https://2022.stateofcss.com/zh-Hans/)\n\n#### [vanilla](https://vanilla-extract.style/)\n\nZero-runtime Stylesheets in TypeScript. But not now for using vanilla\n\n### Generate API automatically\n使用[Swagger Petstore - OpenAPI 3.0](https://petstore3.swagger.io/) 测试\n- [pont](https://github.com/alibaba/pont), 不是特别好用，懒得配置\n\nPont 把 swagger、rap、dip 等多种接口文档平台，转换成 Pont 元数据。Pont 利用接口元数据，可以高度定制化生成前端接口层代码，接口 mock 平台和接口测试平台。\n\n- [OpenAPI Typescript](https://github.com/hey-api/openapi-ts) Generate TypeScript interfaces, REST clients, and JSON Schemas from OpenAPI specifications.\n- [orval](https://github.com/orval-labs/orval) orval is able to generate client with appropriate type-signatures (TypeScript) from any valid OpenAPI v3 or Swagger v2 specification, either in yaml or json formats.\n- [openapi-typescript](https://github.com/openapi-ts/openapi-typescript) Tools for consuming OpenAPI schemas in TypeScript.\n- [swagger-typescript-api](https://github.com/acacode/swagger-typescript-api) Generate the API Client for Fetch or Axios from an OpenAPI Specification\n- [ts-codegen](https://github.com/reeli/ts-codegen)  一个生成前端接口层代码和对应 TypeScript 定义的工具。\n\n对比了下，功能都差不多，但`orval`更自由些，支持自定义API client，没有强绑定，且：\n\n- Generate typescript models\n- Generate HTTP Calls\n- Generate Mocks with MSW\n\n\n## Reference\n- [Eslint + Prettier + Husky + Commitlint+ Lint-staged 规范前端工程代码规范](https://juejin.cn/post/7038143752036155428)\n- [搭建react项目脚手架的](https://juejin.cn/post/7235547967112806437)\n- [Module Resolution or Import Alias: The Final Guide](https://www.raulmelo.dev/blog/module-resolution-or-import-alias-the-final-guide)\n- [Webpack 原理系列九：Tree-Shaking 实现原理](https://segmentfault.com/a/1190000040814997)\n- [Configuring aliases in webpack + VS Code + Typescript + Jest](https://www.basefactor.com/configuring-aliases-in-webpack-vs-code-typescript-jest)\n- [webpack5从零搭建完整的react18+ts开发和打包环境](https://juejin.cn/post/7111922283681153038)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fqinsong77%2Fwebpack5-react-template","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fqinsong77%2Fwebpack5-react-template","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fqinsong77%2Fwebpack5-react-template/lists"}