1、安装

  1. cnpm i webpack webpack-cli webpack-dev-server webpack-merge autoprefixer node-sass postcss-safe-parser react-dev-utils eslint -D
  2. cnpm i postcss-loader sass-loader eslint-loader url-loader file-loader babel-loader style-loader css-loader @babel/core @babel/preset-env -D
  3. cnpm i pnp-webpack-plugin terser-webpack-plugin html-webpack-plugin optimize-css-assets-webpack-plugin case-sensitive-paths-webpack-plugin webpack-manifest-plugin mini-css-extract-plugin -D

2、目录

image.png

3、package.json

开发环境执行npm run start 启动devServer
生产环境执行npm run build 打包设置环境变量为生产环境

  1. {
  2. "name": "7.config",
  3. "version": "1.0.0",
  4. "description": "",
  5. "main": "env.js",
  6. "scripts": {
  7. "build": "cross-env key=value webpack --env=production",
  8. "start": "webpack-dev-server --env=development --config webpack.dev.config.js"
  9. },
  10. "keywords": [],
  11. "author": "",
  12. "license": "ISC",
  13. "homepage": "/static/",
  14. "devDependencies": {
  15. "@babel/core": "^7.11.4",
  16. "@babel/preset-env": "^7.11.0",
  17. "autoprefixer": "^9.8.6",
  18. "babel-loader": "^8.1.0",
  19. "case-sensitive-paths-webpack-plugin": "^2.3.0",
  20. "css-loader": "^4.2.2",
  21. "eslint": "^7.7.0",
  22. "eslint-loader": "^4.0.2",
  23. "file-loader": "^6.0.0",
  24. "html-webpack-plugin": "^4.3.0",
  25. "mini-css-extract-plugin": "^0.11.0",
  26. "node-sass": "^4.14.1",
  27. "optimize-css-assets-webpack-plugin": "^5.0.3",
  28. "pnp-webpack-plugin": "^1.6.4",
  29. "postcss-loader": "^3.0.0",
  30. "postcss-safe-parser": "^4.0.2",
  31. "react-dev-utils": "^10.2.1",
  32. "resolve-url-loader": "^3.1.1",
  33. "sass-loader": "^10.0.1",
  34. "style-loader": "^1.2.1",
  35. "terser-webpack-plugin": "^4.1.0",
  36. "url-loader": "^4.1.0",
  37. "webpack": "^4.44.1",
  38. "webpack-cli": "^3.3.12",
  39. "webpack-dev-server": "^3.11.0",
  40. "webpack-manifest-plugin": "^2.2.0",
  41. "webpack-merge": "^5.1.2"
  42. }
  43. }

4、静态文件

public/index.html

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <title>cra</title>
  7. </head>
  8. <body>
  9. <div id="root">root</div>
  10. <p>其它内容</p>
  11. </body>
  12. </html>

5、webpack设置

webpack.config.js

  1. 'use strict';
  2. const webpack = require('webpack');
  3. const paths = require("./paths");
  4. const CaseSensitivePathsPlugin = require("case-sensitive-paths-webpack-plugin");
  5. const TerserPlugin = require("terser-webpack-plugin");
  6. const PnpWebpackPlugin = require("pnp-webpack-plugin");
  7. const HtmlWebpackPlugin = require("html-webpack-plugin");
  8. const ManifestPlugin = require("webpack-manifest-plugin");
  9. const MiniCssExtractPlugin = require("mini-css-extract-plugin");
  10. const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
  11. const InlineChunkHtmlPlugin = require("react-dev-utils/InlineChunkHtmlPlugin");
  12. const WatchMissingNodeModulesPlugin = require("react-dev-utils/WatchMissingNodeModulesPlugin");
  13. const ModuleScopePlugin = require("react-dev-utils/ModuleScopePlugin");
  14. const InterpolateHtmlPlugin = require("react-dev-utils/InterpolateHtmlPlugin");
  15. const ModuleNotFoundPlugin = require("react-dev-utils/ModuleNotFoundPlugin");
  16. const getClientEnvironment = require("./env");
  17. const cssRegex = /\.css$/;
  18. const sassRegex = /\.(scss|sass)$/;
  19. module.exports = function (webpackEnv) {
  20. console.log("webpackEnv", webpackEnv); //webpackEnv production
  21. process.env;
  22. //开发环境
  23. const isEnvDevelopment = webpackEnv === "development";//false
  24. //生产环境
  25. const isEnvProduction = webpackEnv === "production";//true
  26. //set GENERATE_SOURCEMAP=false 是否在生产环境下生成sourcemap文件
  27. const shouldUseSourceMap = process.env.GENERATE_SOURCEMAP !== "false";
  28. //是否把运行时的runtime内置到html中
  29. const shouldInlineRuntimeChunk = process.env.INLINE_RUNTIME_CHUNK !== "false";
  30. console.log("shouldInlineRuntimeChunk", shouldInlineRuntimeChunk);
  31. // %PUBLIC_URL% in `index.html` and `process.env.PUBLIC_URL` in JavaScript.
  32. //忽略结束的/ 把环境变量中的变量注入到当前应用中来
  33. const env = getClientEnvironment(paths.publicUrlOrPath.slice(0, -1));
  34. console.log("env", env);
  35. const getStyleLoaders = (cssOptions, preProcessor) => {
  36. const loaders = [
  37. isEnvDevelopment && require.resolve("style-loader"),
  38. isEnvProduction && {
  39. loader: MiniCssExtractPlugin.loader,
  40. },
  41. {
  42. loader: require.resolve("css-loader"),
  43. options: cssOptions,
  44. },
  45. {
  46. loader: require.resolve("postcss-loader"),
  47. },
  48. ].filter(Boolean);
  49. if (preProcessor) {
  50. loaders.push(
  51. {
  52. loader: require.resolve("resolve-url-loader"),
  53. },
  54. {
  55. loader: require.resolve(preProcessor),
  56. options: {
  57. sourceMap: true,
  58. },
  59. }
  60. );
  61. }
  62. return loaders;
  63. };
  64. return {
  65. mode: isEnvProduction ? "production" : "development",
  66. devtool: isEnvProduction
  67. ? shouldUseSourceMap
  68. ? "source-map"
  69. : false
  70. : isEnvDevelopment && "cheap-module-source-map",
  71. entry: [
  72. isEnvDevelopment &&
  73. require.resolve("react-dev-utils/webpackHotDevClient"),
  74. paths.appIndexJs,
  75. ].filter(Boolean),
  76. output: {
  77. path: isEnvProduction ? paths.appBuild : undefined, //输出的目标路径
  78. //一个main bundle一个文件,每个异步代码块也对应一个文件 ,在生产环境中,并不产出真正的文件
  79. filename: isEnvProduction
  80. ? "static/js/[name].[contenthash:8].js"
  81. : "static/js/bundle.js",
  82. //如果使用了代码分割的话,这里有额外的JS代码块文件
  83. chunkFilename: isEnvProduction
  84. ? "static/js/[name].[contenthash:8].chunk.js"
  85. : "static/js/[name].chunk.js",
  86. //打包后的文件的访问路径
  87. publicPath: paths.publicUrlOrPath,
  88. },
  89. optimization: {
  90. minimize: isEnvProduction,//生产环境要压缩 开发环境不压缩
  91. minimizer: [
  92. //压缩JS
  93. new TerserPlugin({}),
  94. //压缩CSS
  95. new OptimizeCSSAssetsPlugin({}),
  96. ],
  97. //自动分割第三方模块和公共模块
  98. splitChunks: {
  99. chunks: "all",
  100. name: false,
  101. },
  102. //为了长期缓存保持运行时代码块是单独的文件
  103. runtimeChunk: {
  104. name: (entrypoint) => `runtime-${entrypoint.name}`,
  105. },
  106. },
  107. resolve: {
  108. //设置modules的目录
  109. modules: ["node_modules", paths.appNodeModules],
  110. //指定扩展名
  111. extensions: paths.moduleFileExtensions.map((ext) => `.${ext}`),
  112. alias: {
  113. //设置别名
  114. "react-native": "react-native-web",
  115. },
  116. plugins: [
  117. //PnpWebpackPlugin,
  118. //防止用户引用在src或者node_modules之外的文件
  119. new ModuleScopePlugin(paths.appSrc, [paths.appPackageJson]),
  120. ],
  121. },
  122. resolveLoader: {
  123. //plugins: [PnpWebpackPlugin.moduleLoader(module)],
  124. },
  125. module: {
  126. rules: [
  127. //在babel处理之前执行linter
  128. {
  129. test: /\.(js|mjs|jsx|ts|tsx)$/,
  130. enforce: "pre",
  131. use: [
  132. {
  133. loader: require.resolve("eslint-loader"),
  134. },
  135. ],
  136. include: paths.appSrc,
  137. },
  138. {
  139. //OneOf会遍历接下来的loader直到找一个匹配要求的,如果没有匹配的会走file-loader
  140. oneOf: [
  141. {
  142. test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
  143. loader: require.resolve("url-loader"),
  144. },
  145. {
  146. test: /\.(js|mjs|jsx|ts|tsx)$/,
  147. include: paths.appSrc,
  148. loader: require.resolve("babel-loader"),
  149. },
  150. {
  151. test: cssRegex,
  152. //用于配置css-loader,作用于@import资源之前有多少个loader
  153. //0=>无(默认) 1=>postcss-loader 2 postcss-loader sass-loader
  154. use: getStyleLoaders({ importLoaders: 1 }),
  155. },
  156. {
  157. test: sassRegex,
  158. //postcss-loader sass-loader
  159. use: getStyleLoaders({ importLoaders: 3 }, "sass-loader"),
  160. },
  161. {
  162. loader: require.resolve("file-loader"),
  163. exclude: [/\.(js|mjs|jsx|ts|tsx)$/, /\.html$/, /\.json$/],
  164. options: {
  165. name: "static/media/[name].[hash:8].[ext]",
  166. },
  167. },
  168. ],
  169. },
  170. ],
  171. },
  172. plugins: [
  173. //使用插入的script标签生成一个index.html插件
  174. new HtmlWebpackPlugin({
  175. inject: true,
  176. template: paths.appHtml,
  177. }),
  178. //把运行时插入到html里,这样可以节约一个请求
  179. isEnvProduction &&
  180. shouldInlineRuntimeChunk &&
  181. new InlineChunkHtmlPlugin(HtmlWebpackPlugin, [/runtime-.+[.]js/]),
  182. //保证在index.html中获取到环境变量public URL可以通过%PUBLIC_URL%获取
  183. new InterpolateHtmlPlugin(HtmlWebpackPlugin, env.raw),
  184. //模块找不到的时候提供一些必要上下文信息
  185. new ModuleNotFoundPlugin(paths.appPath),
  186. //保证在JS中获取到环境变量if (process.env.NODE_ENV === 'production') { ... }
  187. new webpack.DefinePlugin(env.stringified),
  188. //模块热更新插件
  189. isEnvDevelopment && new webpack.HotModuleReplacementPlugin(),
  190. //当你大小写拼错的时候进行提示
  191. isEnvDevelopment && new CaseSensitivePathsPlugin(),
  192. //重新安装模块后不用重新启动开发服务器
  193. isEnvDevelopment &&
  194. new WatchMissingNodeModulesPlugin(paths.appNodeModules),
  195. //提取CSS
  196. isEnvProduction &&
  197. new MiniCssExtractPlugin({
  198. filename: "static/css/[name].[contenthash:8].css",
  199. chunkFilename: "static/css/[name].[contenthash:8].chunk.css",
  200. }),
  201. //生成一个manifest文件
  202. new ManifestPlugin({
  203. fileName: "asset-manifest.json",
  204. publicPath: paths.publicUrlOrPath,
  205. generate: (seed, files, entrypoints) => {
  206. const manifestFiles = files.reduce((manifest, file) => {
  207. manifest[file.name] = file.path;
  208. return manifest;
  209. }, seed);
  210. const entrypointFiles = entrypoints.main.filter(
  211. (fileName) => !fileName.endsWith(".map")
  212. );
  213. return {
  214. files: manifestFiles,
  215. entrypoints: entrypointFiles,
  216. };
  217. },
  218. }),
  219. ].filter(Boolean),
  220. };
  221. };

webpack.dev.config.js

  1. const { merge } = require("webpack-merge");
  2. let config = require('./webpack.config');
  3. let devServerConfig = require('./webpackDevServer.config');
  4. module.exports = merge(config('development'), {
  5. devServer: devServerConfig()
  6. });

webpackDevServer.config.js

  1. 'use strict';
  2. const fs = require('fs');
  3. const errorOverlayMiddleware = require('react-dev-utils/errorOverlayMiddleware');
  4. const evalSourceMapMiddleware = require('react-dev-utils/evalSourceMapMiddleware');
  5. const ignoredFiles = require('react-dev-utils/ignoredFiles');
  6. const redirectServedPath = require('react-dev-utils/redirectServedPathMiddleware');
  7. const paths = require('./paths');
  8. const host = process.env.HOST || '0.0.0.0';
  9. module.exports = function(proxy, allowedHost) {
  10. return {
  11. //禁用主机检查
  12. disableHostCheck: true,
  13. //启动gzip压缩
  14. compress: true,
  15. //禁用WebpackDevServer自己的日志,警告和错误还是可以显示的
  16. clientLogLevel: "none",
  17. //静态文件根目录
  18. contentBase: paths.appPublic,
  19. contentBasePublicPath: paths.publicUrlOrPath,
  20. //默认情况下contentBase里的文件变更不会触发页面刷新
  21. watchContentBase: true,
  22. //启用热更新
  23. hot: true,
  24. //使用ws而非socketjs-node模块
  25. transportMode: "ws",
  26. //不需要注入WS客户端
  27. injectClient: false,
  28. //访问路径
  29. publicPath: paths.publicUrlOrPath.slice(0, -1),
  30. //更少的WebpackDevServer日志
  31. quiet: true,
  32. watchOptions: {
  33. ignored: ignoredFiles(paths.appSrc), //不要监控src目录
  34. },
  35. host,
  36. historyApiFallback: {
  37. //禁用dotRule
  38. disableDotRule: true,
  39. index: paths.publicUrlOrPath,
  40. },
  41. public: allowedHost,
  42. //proxy会在before和after之间执行
  43. proxy,
  44. before(app, server) {
  45. //在出错的时候获取源码内容
  46. app.use(evalSourceMapMiddleware(server));
  47. //让我们从运行时错误打开文件
  48. app.use(errorOverlayMiddleware());
  49. //由于代理注册的中间件
  50. if (fs.existsSync(paths.proxySetup)) {
  51. require(paths.proxySetup)(app);
  52. }
  53. },
  54. after(app) {
  55. //如果URL不匹配重定向到`PUBLIC_URL` or `homepage` from `package.json`
  56. app.use(redirectServedPath(paths.publicUrlOrPath));
  57. },
  58. };
  59. };

6、环境变量和路径处理

env.js

  1. 'use strict';
  2. const fs = require('fs');
  3. const path = require('path');
  4. const paths = require('./paths');
  5. //一般可能是production或者development set NODE_ENV=development
  6. const NODE_ENV = process.env.NODE_ENV;
  7. //环境变量的文件路径
  8. const dotenvFiles = [
  9. `${paths.dotenv}.${NODE_ENV}.local`, // .env.development.local
  10. `${paths.dotenv}.${NODE_ENV}`, // .env.development
  11. //在测试环境下不要包括.env.local
  12. NODE_ENV !== 'test' && `${paths.dotenv}.local`, // .env.local
  13. paths.dotenv,//.env
  14. ].filter(Boolean);
  15. //从.env*文件中加载环境变量
  16. process.env.username;
  17. dotenvFiles.forEach(dotenvFile => {
  18. if (fs.existsSync(dotenvFile)) {
  19. require('dotenv-expand')(
  20. require('dotenv').config({
  21. path: dotenvFile,
  22. })
  23. );
  24. }
  25. });
  26. //process.env.PUBLIC_URL=//static2
  27. //支持通过NODE_PATH加载解析模块 set NODE_PATH=modules;extraModules
  28. const appDirectory = fs.realpathSync(process.cwd());
  29. //配置node_modules
  30. process.env.NODE_PATH = (process.env.NODE_PATH || '')
  31. .split(path.delimiter)
  32. .filter(folder => folder && !path.isAbsolute(folder))
  33. .map(folder => path.resolve(appDirectory, folder))
  34. .join(path.delimiter);
  35. //把相对变绝对
  36. //获取NODE_ENV and REACT_APP_*环境变量,并且准备通过DefinePlugin插入应用
  37. //set REACT_APP_NAME=zhufeng
  38. const REACT_APP = /^REACT_APP_/i;
  39. function getClientEnvironment(publicUrl) {
  40. const raw = Object.keys(process.env)
  41. .filter((key) => REACT_APP.test(key))
  42. .reduce(
  43. (env, key) => {
  44. env[key] = process.env[key];
  45. return env;
  46. },
  47. {
  48. //决定当前是否处于开发模式
  49. NODE_ENV: process.env.NODE_ENV || "development",
  50. //用来解析处于public下面的正确资源路径
  51. PUBLIC_URL: publicUrl,
  52. }
  53. );
  54. //把所有的值转成字符串以便在DefinePlugin中使用
  55. const stringified = {
  56. "process.env": Object.keys(raw).reduce((env, key) => {
  57. env[key] = JSON.stringify(raw[key]);
  58. return env;
  59. }, {}),
  60. };
  61. return { raw, stringified };
  62. }
  63. module.exports = getClientEnvironment;

path.js

  1. //当前的工作目录
  2. let path = require("path");
  3. const appDirectory = process.cwd();;
  4. console.log(appDirectory);
  5. //从相对路径中解析绝对路径
  6. const resolveApp = (relativePath) =>{
  7. return path.resolve(appDirectory, relativePath);
  8. }
  9. let r = resolveApp('./index.js');
  10. console.log(r);

paths.js

  1. 'use strict';
  2. const path = require('path');
  3. const fs = require('fs');
  4. //当前的工作目录
  5. const appDirectory = fs.realpathSync(process.cwd());
  6. //从相对路径中解析绝对路径
  7. const resolveApp = relativePath => path.resolve(appDirectory, relativePath);
  8. //获取PublicUrlOrPath /static/
  9. const publicUrlOrPath = require(resolveApp("package.json")).homepage || process.env.PUBLIC_URL || "";
  10. //默认的模块扩展名
  11. const moduleFileExtensions = [
  12. 'js',
  13. 'ts',
  14. 'tsx',
  15. 'json',
  16. 'jsx',
  17. ];
  18. //解析模块路径 './title.js' resolveFn? 从相对路径得到绝对路径
  19. const resolveModule = (resolveFn, filePath) => {
  20. //js
  21. const extension = moduleFileExtensions.find(extension =>
  22. fs.existsSync(resolveFn(`${filePath}.${extension}`))
  23. );
  24. if (extension) {
  25. return resolveFn(`${filePath}.${extension}`);//./title.js
  26. }
  27. return resolveFn(`${filePath}.js`);//如果没有默认是.js
  28. };
  29. module.exports = {
  30. dotenv: resolveApp('.env'),//客户端环境变量的文件名路径
  31. appPath: resolveApp('.'),//当前工作路径
  32. appBuild: resolveApp('build'),//输出的build目标路径
  33. appPublic: resolveApp('public'),//public目录
  34. appHtml: resolveApp('public/index.html'),//html文件绝对路径
  35. appIndexJs: resolveModule(resolveApp, 'src/index'),//入口文件
  36. appPackageJson: resolveApp('package.json'),//package.json文件路径
  37. appSrc: resolveApp('src'),//src路径
  38. appTsConfig: resolveApp('tsconfig.json'),
  39. appJsConfig: resolveApp('jsconfig.json'),
  40. appNodeModules: resolveApp('node_modules'),
  41. publicUrlOrPath,
  42. };
  43. module.exports.moduleFileExtensions = moduleFileExtensions;

7、其他配置eslint、postcss.config.js

.eslint.json

  1. {
  2. "env": {
  3. "browser": true,
  4. "es2020": true
  5. },
  6. "parserOptions": {
  7. "ecmaVersion": 2020,
  8. "sourceType": "module"
  9. },
  10. "rules": {
  11. }
  12. }

postcss.config.js

  1. module.exports = {
  2. plugins: [
  3. require("autoprefixer")({ overrideBrowserslist: ["> 0.15% in CN"] }), // 自动添加css前缀
  4. ],
  5. };

.gitignore

  1. # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
  2. # dependencies
  3. /node_modules
  4. /.pnp
  5. .pnp.js
  6. # testing
  7. /coverage
  8. # production
  9. /build
  10. # misc
  11. .DS_Store
  12. .env.local
  13. .env.development.local
  14. .env.test.local
  15. .env.production.local
  16. npm-debug.log*
  17. yarn-debug.log*
  18. yarn-error.log*

8、测试文件

src/index.js

  1. import './index.scss'
  2. import '../title'
  3. console.log(process.env.PUBLIC_URL);

src/index.scss

  1. @import "./bg.scss";
  2. @import "./front.scss";
  3. $color:pink;
  4. #root{
  5. color:$color;
  6. }

src/bg.scss

  1. $color:green;
  2. body {
  3. background-color: $color;
  4. }

src/font.scss

  1. $color:red;
  2. body {
  3. color: $color;
  4. }

9、.env

.env

  1. PUBLIC_URL=/static2
  2. username=zhufeng
  3. password=123456

.env.development

  1. PUBLIC_URL=/static2
  2. username=zhufeng
  3. password=123456

.env.production

  1. PUBLIC_URL=/static2
  2. username=zhufeng
  3. password=123456