# react-demo **Repository Path**: tikeyc/react-demo ## Basic Information - **Project Name**: react-demo - **Description**: No description available - **Primary Language**: JavaScript - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2021-07-26 - **Last Updated**: 2021-07-26 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README ## 1.安装webpack ``` // webpack4中除了正常安装webpack之外,需要再单独安一个webpack-cli npm i webpack webpack-cli -D ``` ## 2.在项目下创建一个webpack.config.js(默认,可修改)文件来配置webpack ``` module.exports = { entry: '', // 入口文件 output: {}, // 出口文件 module: {}, // 处理对应模块 plugins: [], // 对应的插件 devServer: {}, // 开发服务器配置 mode: 'development' // 模式配置 } ``` ## 3.启动devServer需要安装一下webpack-dev-server ``` npm i webpack-dev-server -D ``` ## 4. webpack.config.js ``` const path = require('path'); module.exports = { entry: './src/index.js', // 入口文件 output: { filename: 'bundle.js', // 打包后的文件名称 path: path.resolve('dist') // 打包后的目录,必须是绝对路径 } } ``` ## 5.配置执行文件 package.json ``` "scripts": { "start": "webpack-dev-server", "build": "webpack", "test": "echo \"Error: no test specified\" && exit 1" }, npm run build就是我们打包后的文件,这是生产环境下,上线需要的文件 npm run start是我们开发环境下打包的文件,当然由于devServer帮我们把文件放到内存中了,所以并不会输出打包后的dist文件夹 ``` ## 6.配置Html模板 ``` npm i html-webpack-plugin -D let path = require('path'); // 插件都是一个类,所以我们命名的时候尽量用大写开头 let HtmlWebpackPlugin = require('html-webpack-plugin'); module.exports = { entry: './src/index.js', output: { // 添加hash可以防止文件缓存,每次都会生成4位的hash串 filename: 'bundle.js', path: path.resolve('dist') }, plugins: [ // 通过new一下这个类来使用插件 new HtmlWebpackPlugin({ // 用哪个html作为模板 // 在src目录下创建一个index.html页面当做模板来用 template: './src/index.html', hash: true, // 会在打包好的bundle.js后面加上hash串 }) ] } ``` ## 7.引用CSS文件 ``` 需要下载一些解析css样式的loader npm i style-loader css-loader -D // 引入less文件的话,也需要安装对应的loader npm i less less-loader -D npm i node-sass sass-loader -D 拆分CSS: npm i mini-css-extract-plugin -D const path = require('path'); // 插件都是一个类,所以我们命名的时候尽量用大写开头 const HtmlWebpackPlugin = require('html-webpack-plugin'); // 拆分css样式的插件 const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const { CleanWebpackPlugin } = require('clean-webpack-plugin'); const TerserJSPlugin = require('terser-webpack-plugin'); const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin'); ``` ``` function generateScopedName() { const name = process.env.NODE_ENV === 'production' ? '[hash:base64:5]' : '[path][name]__[local]__[hash:base64:5]'; return name; } const sourcePathName = 'assets'; // 处理对应模块 module.exports = { // 入口文件 entry: './src/index.js', // 出口文件 output: { // filename: 'bundle.js', // 打包后的文件名称 filename: `${sourcePathName}/scripts/[name].[chunkhash].js`, chunkFilename: `${sourcePathName}/scripts/[name].[chunkhash].js`, path: path.resolve('dist'), // 打包后的目录,必须是绝对路径 publicPath: '/', }, resolve: { // 别名 alias: { pages:path.join(__dirname,'src/pages'), component:path.join(__dirname,'src/component'), actions:path.join(__dirname,'src/redux/actions'), reducers:path.join(__dirname,'src/redux/reducers'), }, // 省略后缀 extensions: ['.js', '.jsx', '.json', '.css', '.scss', '.less'] }, // 提取公共代码 optimization: { // 提取 webpack 运行时包 runtimeChunk: { name: 'runtime', }, splitChunks: { cacheGroups: { // 提取第三方包(来自 node_modules 目录的包) vendor: { test: /[\\/]node_modules[\\/]/, chunks: 'all', name: 'vendor', priority: 10 }, common: { name: "common", test: /[\\/]app[\\/]/, minSize: 1024, chunks: "all", priority: 5 }, // 提取独立样式文件 styles: { test: /\.(c|le)ss$/, chunks: 'all', enforce: true, name: 'styles', }, }, }, minimize: true, minimizer: [ new TerserJSPlugin({ terserOptions: { format: { comments: false, }, }, extractComments: false, }), new OptimizeCSSAssetsPlugin({ cssProcessorOptions: { safe: true, map: { inline: false } }, }), ], }, // 处理对应模块 module: { rules: [ { test: /\.js$/, loader: 'babel-loader', include: /src/, // 只转化src目录下的js exclude: /node_modules/, // 排除掉node_modules,优化打包速度 options: { plugins: [ [ 'react-css-modules', { generateScopedName: generateScopedName(), filetypes: { '.less': { syntax: 'postcss-less', }, }, }, ], ], }, }, { test: /\.less$/, // 解析less use: [ MiniCssExtractPlugin.loader, // 'css-loader', { loader: 'css-loader', options: { modules: { localIdentName: generateScopedName(), }, }, }, 'postcss-loader', 'less-loader', ], // 从右向左解析 }, { test: /\.scss$/, // 解析scss use: [ MiniCssExtractPlugin.loader, // 'css-loader', { loader: 'css-loader', options: { modules: { localIdentName: generateScopedName(), }, }, }, 'postcss-loader', 'sass-loader', ], // 从右向左解析 }, { test: /\.css$/, // 解析css use: [ MiniCssExtractPlugin.loader, // 'css-loader', { loader: 'css-loader', options: { modules: { localIdentName: generateScopedName(), }, }, }, 'postcss-loader', ], }, { test: /\.(png|jpe?g|gif|svg|eot|ttf|woff|woff2)$/, use: [ { loader: 'url-loader', options: { limit: 8192, // 小于8k的图片自动转成base64格式,并且不会存在实体图片 outputPath: `${sourcePathName}/images/` // 图片打包后存放的目录 } } ] }, ], }, // 对应的插件 plugins: [ // 编译前清理 ./dist 文件夹 new CleanWebpackPlugin(), // 通过new一下这个类来使用插件 new HtmlWebpackPlugin({ title: 'tikeyc webpack', filename: 'index.html', // 用哪个html作为模板 // 在src目录下创建一个index.html页面当做模板来用,运行时是在根目录下 template: 'src/index.html', hash: true, // 会在打包好的bundle.js后面加上hash串 }), // 复制自定义静态资源文件 new CopyWebpackPlugin({ patterns: [ { // 运行时是在根目录下 from: 'static', to: 'static', }, ], }), // 拆分后会把css文件放到dist目录下的css/style.css new MiniCssExtractPlugin({ filename: `${sourcePathName}/css/[name].[contenthash].css` // 指定打包后的css }), // new MiniCssExtractPlugin({ // filename: 'css/[name].css' // 指定打包后的css // }), new webpack.HotModuleReplacementPlugin(), ], // 开发服务器配置 devServer: {}, // 模式配置 mode: 'development' } ``` ## 8.引用图片 (module.rules配置见第7点) ``` npm i file-loader url-loader -D 注意需在出口文件配置publicPath // 出口文件 output: { filename: 'bundle.js', // 打包后的文件名称 path: path.resolve('dist'), // 打包后的目录,必须是绝对路径 publicPath: '/', }, ``` ## 9.添加CSS3前缀 ``` npm i postcss-loader autoprefixer -D 安装后,我们还需要像webpack一样写一个config的配置文件,在项目根目录下创建一个postcss.config.js文件,配置如下: module.exports = { plugins: [ require('autoprefixer')({}) ] }; 再新建一个.browserslistrc文件,配置如下: > 1% last 2 versions not dead 然后在webpack里module.rules配置postcss-loader (module.rules配置见第7点) ``` ## 10.转义ES6和react ``` npm i @babel/core @babel/preset-env @babel/preset-react @babel/plugin-proposal-class-properties @babel/plugin-proposal-decorators @babel/plugin-transform-runtime -D ``` 通过一个.babelrc文件来配置一下,对这些版本的支持 ``` // .babelrc { "presets": [ "@babel/preset-env", "@babel/preset-react" ] } ``` 再在webpack里配置一下babel-loader既可以做到代码转成ES6了 (module.rules配置见第7点) ## 10.1 集成 babel-plugin-react-css-modules使用styleName className使得标签样式名(class)编码后唯一 ``` npm install babel-plugin-react-css-modules --save npm install postcss-scss postcss-less --save-dev ``` 须在.webpack.config文件配置module的rules,react-css-modules的plugins - 注意 css-loader 3.6.0版本以上module的rules的hash值与js中babel的plugins的hash值不一样,需要自定义getLocalIdent ``` const path = require('path'); const genericNames = require('generic-names'); const modeIsProduction = process.env.NODE_ENV === 'production'; exports.modeIsProduction = modeIsProduction; const GENERATE_PATH = '[path][name]__[local]--[hash:base64:5]'; function getLocalIdent(localName, filePath) { const relativePath = path.relative(process.cwd(), filePath); return genericNames(GENERATE_PATH)(localName, relativePath); } function generateScopedName() { const name = modeIsProduction ? '[hash:base64:5]' : GENERATE_PATH; return name; } { test: /\.less$/, // 解析less // include: path.resolve(__dirname, '../src'), exclude: [/node_modules/], use: [ { loader: MiniCssExtractPlugin.loader, options: { publicPath: process.env.NODE_ENV === 'production' ? '../../' : undefined, }, }, // 'css-loader', { loader: 'css-loader', options: { importLoaders: 4, sourceMap: true, // modules: true, // localIdentName: generateScopedName(), modules: { // localIdentName: generateScopedName(), // css-loader 3.6.0版本以上hash值与module的rules的hash值不一样,需要自定义getLocalIdent getLocalIdent: (context, _, localName) => ( getLocalIdent(localName, context.resourcePath)), }, }, }, 'postcss-loader', 'less-loader', ], // 从右向左解析 }, ``` ## 11.集成@babel/polyfill Babel默认只转换新的JavaScript句法(syntax),而不转换新的API,比如Iterator、Generator、Set、Maps、Proxy、Reflect、Symbol、Promise等全局对象,以及一些定义在全局对象上的方法(比如Object.assign)都不会转码。 举例来说,ES6在Array对象上新增了Array.from方法。Babel就不会转码这个方法。如果想让这个方法运行,必须使用babel-polyfill,为当前环境提供一个垫片。 ``` npm install --save-dev @babel/polyfill // 修改入口文件index.js import 'babel-polyfill'; ``` ## 12.集成clean-webpack-plugin ``` npm i clean-webpack-plugin -D const { CleanWebpackPlugin } = require('clean-webpack-plugin'); plugins: [ // 编译前清理 ./dist 文件夹 new CleanWebpackPlugin(), ] ``` ## 13.resolve解析 ``` npm i eslint-import-resolver-webpack -D 在.eslintrc.js文件添加确保在文件中能直接import resolve的别名 settings: { 'import/resolver': { webpack: { config: 'webpack.config.base.js' }, }, }, ``` ``` resolve: { // 别名 alias: { pages:path.join(__dirname,'src/pages'), component:path.join(__dirname,'src/component'), actions:path.join(__dirname,'src/redux/actions'), reducers:path.join(__dirname,'src/redux/reducers'), }, // 省略后缀 extensions: ['.js', '.jsx', '.json', '.css', '.scss', '.less'] } ``` ## 14.提取公共代码 ``` optimization: { // 提取 webpack 运行时包 runtimeChunk: { name: 'runtime', }, splitChunks: { cacheGroups: { // 提取第三方包(来自 node_modules 目录的包) vendor: { test: /[\\/]node_modules[\\/]/, chunks: 'all', name: 'vendor', priority: 10 }, common: { name: "common", test: /[\\/]app[\\/]/, minSize: 1024, chunks: "all", priority: 5 }, // 提取独立样式文件 styles: { test: /\.(c|le)ss$/, chunks: 'all', enforce: true, name: 'styles', }, }, }, minimizer: [ new TerserJSPlugin({ terserOptions: { format: { comments: false, }, }, extractComments: false, }), new OptimizeCSSAssetsPlugin({ cssProcessorOptions: { safe: true, map: { inline: false } }, }), ], }, ``` ## 15.为webpack打包生成的资源文件提供Web服务 ``` npm install webpack-dev-server --save-dev npm i webpack-merge -D // 开发服务器配置 devServer: { host: '0.0.0.0', port: 3000, // 端口 open: false, // 自动打开浏览器 hot: true, // 开启热更新 overlay: true, // 浏览器页面上显示错误 historyApiFallback: true }, ``` 现在我们发现一个问题,代码哪里写错了,浏览器报错只报在build.js第几行。这让我们排查错误无从下手,传送门。 在开发环境下配置webpack.dev.config.js // source map devtool: 'inline-source-map', ## 16.热更新 在配置devServer的时候,如果hot为true,就代表开启了热更新,但是这并没有那么简单,因为热更新还需要配置一个webpack自带的插件并且还要在主要js文件里检查是否有module.hot ``` // 热更新,热更新不是刷新 new webpack.HotModuleReplacementPlugin() // 在入口文件index.js // 还需要在主要的js文件里写入下面这段代码 if (module.hot) { // 实现热更新 module.hot.accept(); } ``` ## 17.eslint配置 ``` npm i eslint eslint-loader babel-eslint eslint-plugin-react eslint-config-airbnb eslint-plugin-jsx-a11y eslint-plugin-import eslint-plugin-react-hooks -D ``` .eslintignore ``` # unconventional js /vendor/ # compiled output /dist/ /tmp/ # dependencies /node_modules/ # misc !.* ``` .eslintrc ``` 因为使用了resolver别名(alias),如果webpack配置文件放在build目录下,需要相应修改 settings: { 'import/resolver': { webpack: { config: 'build/webpack.config.base.js' }, }, } ``` ``` const path = require('path'); module.exports = { env: { browser: true, es6: true, node: true, }, extends: [ 'plugin:react/recommended', 'airbnb', ], globals: { Atomics: 'readonly', SharedArrayBuffer: 'readonly', ways: true, }, settings: { 'import/resolver': { webpack: { config: 'webpack.config.base.js' }, }, }, parser: 'babel-eslint', parserOptions: { ecmaFeatures: { jsx: true, }, ecmaVersion: 2018, sourceType: 'module', }, plugins: [ 'react', ], rules: { 'func-names': 0, 'arrow-parens': [2, 'as-needed'], 'class-methods-use-this': 0, 'no-console': 0, 'react/jsx-filename-extension': [2, { extensions: ['.js', '.jsx'] }], 'react/jsx-one-expression-per-line': [1, { allow: 'single-child' }], 'react/jsx-props-no-spreading': 0, 'react/require-default-props': 0, 'jsx-a11y/anchor-is-valid': 0, 'jsx-a11y/img-redundant-alt': 0, 'jsx-a11y/label-has-associated-control': 0, 'jsx-a11y/click-events-have-key-events': 0, 'jsx-a11y/no-static-element-interactions': 0, 'import/no-extraneous-dependencies': 0, 'no-underscore-dangle': 0, 'prefer-destructuring': 0, 'global-require': 0, 'react/no-array-index-key': 0, 'react/state-in-constructor': 0, 'react/forbid-prop-types': 0, camelcase: 0, }, }; ```