1、安装
cnpm i webpack webpack-cli webpack-dev-server webpack-merge autoprefixer node-sass postcss-safe-parser react-dev-utils eslint -Dcnpm i postcss-loader sass-loader eslint-loader url-loader file-loader babel-loader style-loader css-loader @babel/core @babel/preset-env -Dcnpm 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、目录

3、package.json
开发环境执行npm run start 启动devServer
生产环境执行npm run build 打包设置环境变量为生产环境
{"name": "7.config","version": "1.0.0","description": "","main": "env.js","scripts": {"build": "cross-env key=value webpack --env=production","start": "webpack-dev-server --env=development --config webpack.dev.config.js"},"keywords": [],"author": "","license": "ISC","homepage": "/static/","devDependencies": {"@babel/core": "^7.11.4","@babel/preset-env": "^7.11.0","autoprefixer": "^9.8.6","babel-loader": "^8.1.0","case-sensitive-paths-webpack-plugin": "^2.3.0","css-loader": "^4.2.2","eslint": "^7.7.0","eslint-loader": "^4.0.2","file-loader": "^6.0.0","html-webpack-plugin": "^4.3.0","mini-css-extract-plugin": "^0.11.0","node-sass": "^4.14.1","optimize-css-assets-webpack-plugin": "^5.0.3","pnp-webpack-plugin": "^1.6.4","postcss-loader": "^3.0.0","postcss-safe-parser": "^4.0.2","react-dev-utils": "^10.2.1","resolve-url-loader": "^3.1.1","sass-loader": "^10.0.1","style-loader": "^1.2.1","terser-webpack-plugin": "^4.1.0","url-loader": "^4.1.0","webpack": "^4.44.1","webpack-cli": "^3.3.12","webpack-dev-server": "^3.11.0","webpack-manifest-plugin": "^2.2.0","webpack-merge": "^5.1.2"}}
4、静态文件
public/index.html
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>cra</title></head><body><div id="root">root</div><p>其它内容</p></body></html>
5、webpack设置
webpack.config.js
'use strict';const webpack = require('webpack');const paths = require("./paths");const CaseSensitivePathsPlugin = require("case-sensitive-paths-webpack-plugin");const TerserPlugin = require("terser-webpack-plugin");const PnpWebpackPlugin = require("pnp-webpack-plugin");const HtmlWebpackPlugin = require("html-webpack-plugin");const ManifestPlugin = require("webpack-manifest-plugin");const MiniCssExtractPlugin = require("mini-css-extract-plugin");const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");const InlineChunkHtmlPlugin = require("react-dev-utils/InlineChunkHtmlPlugin");const WatchMissingNodeModulesPlugin = require("react-dev-utils/WatchMissingNodeModulesPlugin");const ModuleScopePlugin = require("react-dev-utils/ModuleScopePlugin");const InterpolateHtmlPlugin = require("react-dev-utils/InterpolateHtmlPlugin");const ModuleNotFoundPlugin = require("react-dev-utils/ModuleNotFoundPlugin");const getClientEnvironment = require("./env");const cssRegex = /\.css$/;const sassRegex = /\.(scss|sass)$/;module.exports = function (webpackEnv) {console.log("webpackEnv", webpackEnv); //webpackEnv productionprocess.env;//开发环境const isEnvDevelopment = webpackEnv === "development";//false//生产环境const isEnvProduction = webpackEnv === "production";//true//set GENERATE_SOURCEMAP=false 是否在生产环境下生成sourcemap文件const shouldUseSourceMap = process.env.GENERATE_SOURCEMAP !== "false";//是否把运行时的runtime内置到html中const shouldInlineRuntimeChunk = process.env.INLINE_RUNTIME_CHUNK !== "false";console.log("shouldInlineRuntimeChunk", shouldInlineRuntimeChunk);// %PUBLIC_URL% in `index.html` and `process.env.PUBLIC_URL` in JavaScript.//忽略结束的/ 把环境变量中的变量注入到当前应用中来const env = getClientEnvironment(paths.publicUrlOrPath.slice(0, -1));console.log("env", env);const getStyleLoaders = (cssOptions, preProcessor) => {const loaders = [isEnvDevelopment && require.resolve("style-loader"),isEnvProduction && {loader: MiniCssExtractPlugin.loader,},{loader: require.resolve("css-loader"),options: cssOptions,},{loader: require.resolve("postcss-loader"),},].filter(Boolean);if (preProcessor) {loaders.push({loader: require.resolve("resolve-url-loader"),},{loader: require.resolve(preProcessor),options: {sourceMap: true,},});}return loaders;};return {mode: isEnvProduction ? "production" : "development",devtool: isEnvProduction? shouldUseSourceMap? "source-map": false: isEnvDevelopment && "cheap-module-source-map",entry: [isEnvDevelopment &&require.resolve("react-dev-utils/webpackHotDevClient"),paths.appIndexJs,].filter(Boolean),output: {path: isEnvProduction ? paths.appBuild : undefined, //输出的目标路径//一个main bundle一个文件,每个异步代码块也对应一个文件 ,在生产环境中,并不产出真正的文件filename: isEnvProduction? "static/js/[name].[contenthash:8].js": "static/js/bundle.js",//如果使用了代码分割的话,这里有额外的JS代码块文件chunkFilename: isEnvProduction? "static/js/[name].[contenthash:8].chunk.js": "static/js/[name].chunk.js",//打包后的文件的访问路径publicPath: paths.publicUrlOrPath,},optimization: {minimize: isEnvProduction,//生产环境要压缩 开发环境不压缩minimizer: [//压缩JSnew TerserPlugin({}),//压缩CSSnew OptimizeCSSAssetsPlugin({}),],//自动分割第三方模块和公共模块splitChunks: {chunks: "all",name: false,},//为了长期缓存保持运行时代码块是单独的文件runtimeChunk: {name: (entrypoint) => `runtime-${entrypoint.name}`,},},resolve: {//设置modules的目录modules: ["node_modules", paths.appNodeModules],//指定扩展名extensions: paths.moduleFileExtensions.map((ext) => `.${ext}`),alias: {//设置别名"react-native": "react-native-web",},plugins: [//PnpWebpackPlugin,//防止用户引用在src或者node_modules之外的文件new ModuleScopePlugin(paths.appSrc, [paths.appPackageJson]),],},resolveLoader: {//plugins: [PnpWebpackPlugin.moduleLoader(module)],},module: {rules: [//在babel处理之前执行linter{test: /\.(js|mjs|jsx|ts|tsx)$/,enforce: "pre",use: [{loader: require.resolve("eslint-loader"),},],include: paths.appSrc,},{//OneOf会遍历接下来的loader直到找一个匹配要求的,如果没有匹配的会走file-loaderoneOf: [{test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],loader: require.resolve("url-loader"),},{test: /\.(js|mjs|jsx|ts|tsx)$/,include: paths.appSrc,loader: require.resolve("babel-loader"),},{test: cssRegex,//用于配置css-loader,作用于@import资源之前有多少个loader//0=>无(默认) 1=>postcss-loader 2 postcss-loader sass-loaderuse: getStyleLoaders({ importLoaders: 1 }),},{test: sassRegex,//postcss-loader sass-loaderuse: getStyleLoaders({ importLoaders: 3 }, "sass-loader"),},{loader: require.resolve("file-loader"),exclude: [/\.(js|mjs|jsx|ts|tsx)$/, /\.html$/, /\.json$/],options: {name: "static/media/[name].[hash:8].[ext]",},},],},],},plugins: [//使用插入的script标签生成一个index.html插件new HtmlWebpackPlugin({inject: true,template: paths.appHtml,}),//把运行时插入到html里,这样可以节约一个请求isEnvProduction &&shouldInlineRuntimeChunk &&new InlineChunkHtmlPlugin(HtmlWebpackPlugin, [/runtime-.+[.]js/]),//保证在index.html中获取到环境变量public URL可以通过%PUBLIC_URL%获取new InterpolateHtmlPlugin(HtmlWebpackPlugin, env.raw),//模块找不到的时候提供一些必要上下文信息new ModuleNotFoundPlugin(paths.appPath),//保证在JS中获取到环境变量if (process.env.NODE_ENV === 'production') { ... }new webpack.DefinePlugin(env.stringified),//模块热更新插件isEnvDevelopment && new webpack.HotModuleReplacementPlugin(),//当你大小写拼错的时候进行提示isEnvDevelopment && new CaseSensitivePathsPlugin(),//重新安装模块后不用重新启动开发服务器isEnvDevelopment &&new WatchMissingNodeModulesPlugin(paths.appNodeModules),//提取CSSisEnvProduction &&new MiniCssExtractPlugin({filename: "static/css/[name].[contenthash:8].css",chunkFilename: "static/css/[name].[contenthash:8].chunk.css",}),//生成一个manifest文件new ManifestPlugin({fileName: "asset-manifest.json",publicPath: paths.publicUrlOrPath,generate: (seed, files, entrypoints) => {const manifestFiles = files.reduce((manifest, file) => {manifest[file.name] = file.path;return manifest;}, seed);const entrypointFiles = entrypoints.main.filter((fileName) => !fileName.endsWith(".map"));return {files: manifestFiles,entrypoints: entrypointFiles,};},}),].filter(Boolean),};};
webpack.dev.config.js
const { merge } = require("webpack-merge");let config = require('./webpack.config');let devServerConfig = require('./webpackDevServer.config');module.exports = merge(config('development'), {devServer: devServerConfig()});
webpackDevServer.config.js
'use strict';const fs = require('fs');const errorOverlayMiddleware = require('react-dev-utils/errorOverlayMiddleware');const evalSourceMapMiddleware = require('react-dev-utils/evalSourceMapMiddleware');const ignoredFiles = require('react-dev-utils/ignoredFiles');const redirectServedPath = require('react-dev-utils/redirectServedPathMiddleware');const paths = require('./paths');const host = process.env.HOST || '0.0.0.0';module.exports = function(proxy, allowedHost) {return {//禁用主机检查disableHostCheck: true,//启动gzip压缩compress: true,//禁用WebpackDevServer自己的日志,警告和错误还是可以显示的clientLogLevel: "none",//静态文件根目录contentBase: paths.appPublic,contentBasePublicPath: paths.publicUrlOrPath,//默认情况下contentBase里的文件变更不会触发页面刷新watchContentBase: true,//启用热更新hot: true,//使用ws而非socketjs-node模块transportMode: "ws",//不需要注入WS客户端injectClient: false,//访问路径publicPath: paths.publicUrlOrPath.slice(0, -1),//更少的WebpackDevServer日志quiet: true,watchOptions: {ignored: ignoredFiles(paths.appSrc), //不要监控src目录},host,historyApiFallback: {//禁用dotRuledisableDotRule: true,index: paths.publicUrlOrPath,},public: allowedHost,//proxy会在before和after之间执行proxy,before(app, server) {//在出错的时候获取源码内容app.use(evalSourceMapMiddleware(server));//让我们从运行时错误打开文件app.use(errorOverlayMiddleware());//由于代理注册的中间件if (fs.existsSync(paths.proxySetup)) {require(paths.proxySetup)(app);}},after(app) {//如果URL不匹配重定向到`PUBLIC_URL` or `homepage` from `package.json`app.use(redirectServedPath(paths.publicUrlOrPath));},};};
6、环境变量和路径处理
env.js
'use strict';const fs = require('fs');const path = require('path');const paths = require('./paths');//一般可能是production或者development set NODE_ENV=developmentconst NODE_ENV = process.env.NODE_ENV;//环境变量的文件路径const dotenvFiles = [`${paths.dotenv}.${NODE_ENV}.local`, // .env.development.local`${paths.dotenv}.${NODE_ENV}`, // .env.development//在测试环境下不要包括.env.localNODE_ENV !== 'test' && `${paths.dotenv}.local`, // .env.localpaths.dotenv,//.env].filter(Boolean);//从.env*文件中加载环境变量process.env.username;dotenvFiles.forEach(dotenvFile => {if (fs.existsSync(dotenvFile)) {require('dotenv-expand')(require('dotenv').config({path: dotenvFile,}));}});//process.env.PUBLIC_URL=//static2//支持通过NODE_PATH加载解析模块 set NODE_PATH=modules;extraModulesconst appDirectory = fs.realpathSync(process.cwd());//配置node_modulesprocess.env.NODE_PATH = (process.env.NODE_PATH || '').split(path.delimiter).filter(folder => folder && !path.isAbsolute(folder)).map(folder => path.resolve(appDirectory, folder)).join(path.delimiter);//把相对变绝对//获取NODE_ENV and REACT_APP_*环境变量,并且准备通过DefinePlugin插入应用//set REACT_APP_NAME=zhufengconst REACT_APP = /^REACT_APP_/i;function getClientEnvironment(publicUrl) {const raw = Object.keys(process.env).filter((key) => REACT_APP.test(key)).reduce((env, key) => {env[key] = process.env[key];return env;},{//决定当前是否处于开发模式NODE_ENV: process.env.NODE_ENV || "development",//用来解析处于public下面的正确资源路径PUBLIC_URL: publicUrl,});//把所有的值转成字符串以便在DefinePlugin中使用const stringified = {"process.env": Object.keys(raw).reduce((env, key) => {env[key] = JSON.stringify(raw[key]);return env;}, {}),};return { raw, stringified };}module.exports = getClientEnvironment;
path.js
//当前的工作目录let path = require("path");const appDirectory = process.cwd();;console.log(appDirectory);//从相对路径中解析绝对路径const resolveApp = (relativePath) =>{return path.resolve(appDirectory, relativePath);}let r = resolveApp('./index.js');console.log(r);
paths.js
'use strict';const path = require('path');const fs = require('fs');//当前的工作目录const appDirectory = fs.realpathSync(process.cwd());//从相对路径中解析绝对路径const resolveApp = relativePath => path.resolve(appDirectory, relativePath);//获取PublicUrlOrPath /static/const publicUrlOrPath = require(resolveApp("package.json")).homepage || process.env.PUBLIC_URL || "";//默认的模块扩展名const moduleFileExtensions = ['js','ts','tsx','json','jsx',];//解析模块路径 './title.js' resolveFn? 从相对路径得到绝对路径const resolveModule = (resolveFn, filePath) => {//jsconst extension = moduleFileExtensions.find(extension =>fs.existsSync(resolveFn(`${filePath}.${extension}`)));if (extension) {return resolveFn(`${filePath}.${extension}`);//./title.js}return resolveFn(`${filePath}.js`);//如果没有默认是.js};module.exports = {dotenv: resolveApp('.env'),//客户端环境变量的文件名路径appPath: resolveApp('.'),//当前工作路径appBuild: resolveApp('build'),//输出的build目标路径appPublic: resolveApp('public'),//public目录appHtml: resolveApp('public/index.html'),//html文件绝对路径appIndexJs: resolveModule(resolveApp, 'src/index'),//入口文件appPackageJson: resolveApp('package.json'),//package.json文件路径appSrc: resolveApp('src'),//src路径appTsConfig: resolveApp('tsconfig.json'),appJsConfig: resolveApp('jsconfig.json'),appNodeModules: resolveApp('node_modules'),publicUrlOrPath,};module.exports.moduleFileExtensions = moduleFileExtensions;
7、其他配置eslint、postcss.config.js
.eslint.json
{"env": {"browser": true,"es2020": true},"parserOptions": {"ecmaVersion": 2020,"sourceType": "module"},"rules": {}}
postcss.config.js
module.exports = {plugins: [require("autoprefixer")({ overrideBrowserslist: ["> 0.15% in CN"] }), // 自动添加css前缀],};
.gitignore
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.# dependencies/node_modules/.pnp.pnp.js# testing/coverage# production/build# misc.DS_Store.env.local.env.development.local.env.test.local.env.production.localnpm-debug.log*yarn-debug.log*yarn-error.log*
8、测试文件
src/index.js
import './index.scss'import '../title'console.log(process.env.PUBLIC_URL);
src/index.scss
@import "./bg.scss";@import "./front.scss";$color:pink;#root{color:$color;}
src/bg.scss
$color:green;body {background-color: $color;}
src/font.scss
$color:red;body {color: $color;}
9、.env
.env
PUBLIC_URL=/static2username=zhufengpassword=123456
.env.development
PUBLIC_URL=/static2username=zhufengpassword=123456
.env.production
PUBLIC_URL=/static2username=zhufengpassword=123456
