乐趣区

技本功丨create-react-app升级webpack4填坑

都说 create-react-app 是业界最优秀的 React 应用开发工具之一。But,webpack4 都更新到 v4.20.2 它居然~ 还没升级,简直~ 不能忍

看到 webpack 这更新速度,本人慌得一批,刚好抽空搭建 react-andt-mobx 脚手架准备升级~
So,在此分享一下升级攻略,收好不谢!

01 安装
npm install -g create-react-app
02 创建应用
//create-react-app 是全局命令来创建 react 项目 create-react-app react-demo
03 自定义 webpack 配置
npm run eject  // 自定义模式,暴露出 webpack 配置,不可逆
04 着手自定义 webpack 配置
1、目标结构

当然 webpack 升级准备,调整 create-react-app 的目录结构已符合我们项目开发的规范是必不可少的。这里重点需关注的为 build 目录下的一下文件:

paths 文件更改打包路经更改:

在项目开发的过程中 host 配置以及 proxy 代理是常见的配置,在 create-react-app 中配置在 package.json 配置下,灵活性相对不太好,提取 webpack 中 server.js 配置:

别忘了修改 webpackDevServer.config.js 下引用 host 及 proxy 下的引用哦。
此时,目录改造全部完毕
渐入佳境,赶紧进入正题

2、webpack3 升级 webpack4
webpack4 新出了一个 mode 模式,有三种选择,none,development,production. 最直观的感受就是你可以少些很多配置,因为一旦你开启了 mode 模式,webpack4 就会给你设置很多基本的东西。
development 模式下,将侧重于功能调试和优化开发体验,包含如下内容:
浏览器调试工具
开发阶段的详细错误日志和提示
快速和优化的增量构建机制
production 模式下,将侧重于模块体积优化和线上部署,包含如下内容:
开启所有的优化代码
更小的 bundle 大小
去除掉只在开发阶段运行的代码
Scope hoisting 和 Tree-shaking
自动启用 uglifyjs 对代码进行压缩
话不多说,下安装:yarn add webpack webpack-cli webpack-dev-server
这 3 个包是 webpack4 的基础功能
webpack 在 webpack 4 里将命令行相关的都迁移至 webpack-cli 包
webpack-dev-server 为实时监控文件变化包
安装完成之后,请保持淡定,
得先运行一下,万一直接能打包呢
或许它偷偷做了兼容处理呢,
梦想还是要有呢,虽然 …

Plugin could not be registered at ‘html-webpack-plugin-before-html-processing’. Hook was not found. BREAKING CHANGE: There need to exist a hook at ‘this.hooks’. To create a compatiblity layer for this hook, hook into ‘this._pluginCompat’. 果然还是熟悉的味道
上面这个问题是 HtmlWebpackPlugin 和 react-dev-utils/InterpolateHtmlPlugin 先后顺序问题,调整下他们的顺序
 new HtmlWebpackPlugin({inject: true,      template: paths.appHtml,      chunksSortMode: ‘none’,}),    new InterpolateHtmlPlugin(env.raw),
嗯,不出意外的话,搞定
再跑一下代码, 这个时候可能就出现了一些百度不到问题,你需要升级各种 loader 了,
less-loader,sass-loader style-loader url-loader
具体命令:
yarn add less-loader@next
和上面的命令相同,依次升级,运行代码,查看报错,缺啥补啥,成功的选择,值得拥有 ….
需要升级的有:
html-webpack-plugin
react-dev-utils
修改代码完整篇 webpack.config.dev.js:const autoprefixer = require(‘autoprefixer’);const path = require(‘path’); const webpack = require(‘webpack’); const HtmlWebpackPlugin = require(‘html-webpack-plugin’); const CaseSensitivePathsPlugin = require(‘case-sensitive-paths-webpack-plugin’); const InterpolateHtmlPlugin = require(‘react-dev-utils/InterpolateHtmlPlugin’); const WatchMissingNodeModulesPlugin = require(‘react-dev-utils/WatchMissingNodeModulesPlugin’); const eslintFormatter = require(‘react-dev-utils/eslintFormatter’); const ModuleScopePlugin = require(‘react-dev-utils/ModuleScopePlugin’); const getClientEnvironment = require(‘./env’); const paths = require(‘./paths’); function resolve (dir) {return path.join(__dirname, ‘..’, dir) } const publicPath = ‘/’; const publicUrl = ”; const env = getClientEnvironment(publicUrl); module.exports = {mode: ‘development’,  devtool: ‘cheap-module-source-map’,  entry: [    require.resolve(‘./polyfills’),    require.resolve(‘react-dev-utils/webpackHotDevClient’),    paths.appIndexJs,  ],  output: {pathinfo: true,    filename: ‘static/js/bundle.js’,    chunkFilename: ‘static/js/[name].chunk.js’,    publicPath: publicPath,    devtoolModuleFilenameTemplate: info =>      path.resolve(info.absoluteResourcePath).replace(/\/g, ‘/’),  },  resolve: {modules: [‘node_modules’, paths.appNodeModules].concat(process.env.NODE_PATH.split(path.delimiter).filter(Boolean)    ),    extensions: [‘.web.js’, ‘.mjs’, ‘.js’, ‘.json’, ‘.web.jsx’, ‘.jsx’,’.less’,’.scss’],    alias: {‘@’: resolve(‘src’),      ‘public’: resolve(‘src/public’),      ‘components’: resolve(‘src/components’),      ‘pages’: resolve(‘src/pages’),      ‘api’: resolve(‘src/api’),      ‘mock’: resolve(‘src/public/mock’),    },    plugins: [new ModuleScopePlugin(paths.appSrc, [paths.appPackageJson]),    ],  },  module: {strictExportPresence: true,    rules: [      {        test: /.(js|jsx|mjs)$/,        enforce: ‘pre’,        use: [{            options: {              formatter: eslintFormatter,              eslintPath: require.resolve(‘eslint’),                          },            loader: require.resolve(‘eslint-loader’),          },       ],        include: paths.appSrc,      },      {oneOf: [          {            test: [/.bmp$/, /\.gif$/, /.jpe?g$/, /\.png$/],            loader: require.resolve(‘url-loader’),            options: {limit: 10000,              name: ‘static/media/[name].[hash:8].[ext]’,            },          },          {test: /.(js|jsx|mjs)$/,            include: paths.appSrc,            loader: require.resolve(‘babel-loader’),            options: {cacheDirectory: true,}          },          {test: /.(css|less)$/,            use: [require.resolve(‘style-loader’),              {loader: require.resolve(‘css-loader’),                options: {importLoaders: 1,},              },              {loader: require.resolve(‘postcss-loader’),                options: {ident: ‘postcss’,                  plugins: () => [require(‘postcss-flexbugs-fixes’),                    autoprefixer({browsers: [                        ‘>1%’,                        ‘last 4 versions’,                        ‘Firefox ESR’,                        ‘not ie < 9’, // React doesn’t support IE8 anyway],                      flexbox: ‘no-2009’,                    }),                  ],                },              },              {loader: require.resolve(‘less-loader’) // compiles Less to CSS              },            ],          },          {test: /.(css|scss)$/,            use: [require.resolve(‘style-loader’),              {loader: require.resolve(‘css-loader’),                options: {importLoaders: 1,},              },              {loader: require.resolve(‘postcss-loader’),                options: {ident: ‘postcss’,                  plugins: () => [require(‘postcss-flexbugs-fixes’),                    autoprefixer({browsers: [                        ‘>1%’,                        ‘last 4 versions’,                        ‘Firefox ESR’,                        ‘not ie < 9’, // React doesn’t support IE8 anyway],                      flexbox: ‘no-2009’,                    }),                  ],                },             },              {loader: require.resolve(‘sass-loader’) // compiles Less to CSS              },            ],          },          {exclude: [/.(js|jsx|mjs)$/,/\.(css|less)$/, /.html$/, /\.json$/],            loader: require.resolve(‘file-loader’),            options: {name: ‘static/media/[name].[hash:8].[ext]’,            },          },        ],      },    ],  },  plugins: [new HtmlWebpackPlugin({      inject: true,      template: paths.appHtml,      chunksSortMode: ‘none’,}),    new InterpolateHtmlPlugin(env.raw),    new webpack.DefinePlugin(env.stringified),    new webpack.HotModuleReplacementPlugin(),    new CaseSensitivePathsPlugin(),    new WatchMissingNodeModulesPlugin(paths.appNodeModules),    new webpack.IgnorePlugin(/^./locale$/, /moment$/),  ],  node: {dgram: ’empty’,    fs: ’empty’,    net: ’empty’,    tls: ’empty’,    child_process: ’empty’,},  performance: {hints: false,},  optimization: {namedModules: true,    nodeEnv: ‘development’,}, }; 还需注意的是 webpack4 对 ExtractTextWebpackPlugin 做了调整,建议选用新的 CSS 文件提取插件 mini-css-extract-plugin。生产环境下我们需要做一下配置调整:
webpack.config.prod.js
const autoprefixer = require(‘autoprefixer’);const path = require(‘path’);const webpack = require(‘webpack’);const HtmlWebpackPlugin = require(‘html-webpack-plugin’);const ManifestPlugin = require(‘webpack-manifest-plugin’);const InterpolateHtmlPlugin = require(‘react-dev-utils/InterpolateHtmlPlugin’);const SWPrecacheWebpackPlugin = require(‘sw-precache-webpack-plugin’);const eslintFormatter = require(‘react-dev-utils/eslintFormatter’);const ModuleScopePlugin = require(‘react-dev-utils/ModuleScopePlugin’);const MiniCssExtractPlugin = require(‘mini-css-extract-plugin’);const UglifyJsPlugin = require(“uglifyjs-webpack-plugin”);const OptimizeCSSAssetsPlugin = require(“optimize-css-assets-webpack-plugin”);const paths = require(‘./paths’);const getClientEnvironment = require(‘./env’);const theme = require(‘../antd-theme.js’);function resolve (dir) {return path.join(__dirname, ‘..’, dir)}const publicPath = paths.servedPath;const shouldUseSourceMap = process.env.GENERATE_SOURCEMAP !== ‘false’;const publicUrl = publicPath.slice(0, -1);const env = getClientEnvironment(publicUrl);
if (env.stringified[‘process.env’].NODE_ENV !== ‘”production”‘) {throw new Error(‘Production builds must have NODE_ENV=production.’);}
module.exports = {mode: “production”,  bail: true,  devtool: shouldUseSourceMap ? ‘source-map’ : false,  entry: [require.resolve(‘./polyfills’), paths.appIndexJs],  output: {path: paths.appBuild,    filename: ‘static/js/[name].[chunkhash:8].js’,    chunkFilename: ‘static/js/[name].[chunkhash:8].chunk.js’,    publicPath: publicPath,    devtoolModuleFilenameTemplate: info =>      path        .relative(paths.appSrc, info.absoluteResourcePath)        .replace(/\/g, ‘/’),  },  resolve: {modules: [‘node_modules’, paths.appNodeModules].concat(// It is guaranteed to exist because we tweak it in env.js      process.env.NODE_PATH.split(path.delimiter).filter(Boolean)    ),    extensions: [‘.web.js’, ‘.mjs’, ‘.js’, ‘.json’, ‘.web.jsx’, ‘.jsx’,’.less’],    alias: {‘@’: resolve(‘src’),      ‘public’: resolve(‘src/public’),      ‘components’: resolve(‘src/components’),      ‘pages’: resolve(‘src/pages’),      ‘mock’: resolve(‘src/public/mock’),      ‘api’: resolve(‘src/api’),      ‘react-native’: ‘react-native-web’,    },    plugins: [new ModuleScopePlugin(paths.appSrc, [paths.appPackageJson]),    ],  },  module: {strictExportPresence: true,    rules: [      {        test: /.(js|jsx|mjs)$/,        enforce: ‘pre’,        use: [{            options: {              formatter: eslintFormatter,              eslintPath: require.resolve(‘eslint’),                          },            loader: require.resolve(‘eslint-loader’),          },        ],        include: paths.appSrc,      },      {oneOf: [          {            test: [/.bmp$/, /\.gif$/, /.jpe?g$/, /\.png$/],            loader: require.resolve(‘url-loader’),            options: {limit: 10000,              name: ‘static/media/[name].[hash:8].[ext]’,            },          },          // Process JS with Babel.          {test: /.(js|jsx|mjs)$/,            include: paths.appSrc,            loader: require.resolve(‘babel-loader’),            options: {plugins: [                  [‘import’, [{ libraryName: ‘antd’, style: true}]],  // import less              ],              compact: true,            },          },          {test: /.(less|css)$/,            use: [MiniCssExtractPlugin.loader,              “css-loader”,              “less-loader?{modifyVars:” + JSON.stringify(theme) + “}”            ],          },          {test: /.(scss|sass)$/,            use: [MiniCssExtractPlugin.loader,              “css-loader”,              “sass-loader”]          },          {loader: require.resolve(‘file-loader’),            exclude: [/.(js|jsx|mjs)$/,/\.(css|less)$/, /.html$/, /\.json$/],            options: {name: ‘static/media/[name].[hash:8].[ext]’,            },          },        ],      },    ],  },  optimization: {runtimeChunk: {      name: ‘manifest’},    minimize: true,    noEmitOnErrors: true,    minimizer: [new UglifyJsPlugin({        cache: true,        parallel: true,        sourceMap: false}),      new OptimizeCSSAssetsPlugin({})    ],    splitChunks: {minSize: 30000,      maxSize: 3000000,      minChunks: 1,      maxAsyncRequests: 5,      maxInitialRequests: 3,      name: true,      cacheGroups: {        vendor: {          chunks: ‘initial’,          name: ‘vendor’,          test: ‘vendor’},        echarts: {chunks: ‘all’,          name: ‘echarts’,          test: /[\/]echarts[\/]/,        }      }    }  },  plugins: [new HtmlWebpackPlugin({      inject: true,      template: paths.appHtml,      minify: {        removeComments: true,        collapseWhitespace: true,        removeRedundantAttributes: true,        useShortDoctype: true,        removeEmptyAttributes: true,        removeStyleLinkTypeAttributes: true,        keepClosingSlash: true,        minifyJS: true,        minifyCSS: true,        minifyURLs: true,},    }),    new InterpolateHtmlPlugin(env.raw),    new webpack.DefinePlugin(env.stringified),    new webpack.NamedModulesPlugin(),    new webpack.optimize.OccurrenceOrderPlugin(true),    new MiniCssExtractPlugin({filename: “css/[name].[hash].css”,      chunkFilename: “css/[name].[hash].css”    }),    new ManifestPlugin({fileName: ‘asset-manifest.json’,}),    new SWPrecacheWebpackPlugin({dontCacheBustUrlsMatching: /.w{8}./,      filename: ‘service-worker.js’,      logger(message) {if (message.indexOf(‘Total precache size is’) === 0) {return;}        if (message.indexOf(‘Skipping static resource’) === 0) {return;}        console.log(message);      },      minify: true,      navigateFallback: publicUrl + ‘/index.html’,      navigateFallbackWhitelist: [/^(?!/__).*/],      staticFileGlobsIgnorePatterns: [/.map$/, /asset-manifest\.json$/],    }),    new webpack.IgnorePlugin(/^./locale$/, /moment$/),  ],  node: {dgram: ’empty’,    fs: ’empty’,    net: ’empty’,    tls: ’empty’,    child_process: ’empty’,},};
此时基本完成了 webpack4 升级的改造
但是运行下,还有个隐形的坑待处理……
因为引入了 andt 组件库,
高版本对 less 编译会出错。。
噗~ 升级了一路,碰到个需要降级处理的,
好,降低 less 版本 “less”: “2.7.3”,
运行一波,亲测完美~

最后附 package.json
{“dependencies”: {    “antd”: “^3.9.0-beta.6”,    “autoprefixer”: “7.1.6”,    “axios”: “^0.18.0”,    “babel-core”: “6.26.0”,    “babel-eslint”: “7.2.3”,    “babel-jest”: “20.0.3”,    “babel-loader”: “7.1.2”,    “babel-plugin-import”: “^1.8.0”,    “babel-polyfill”: “^6.26.0”,    “babel-preset-react-app”: “^3.1.1”,    “babel-runtime”: “^6.26.0”,    “case-sensitive-paths-webpack-plugin”: “2.1.1”,    “chalk”: “1.1.3”,    “classnames”: “^2.2.6”,    “core-decorators”: “^0.20.0”,    “create-keyframe-animation”: “^0.1.0”,    “css-loader”: “0.28.7”,    “dotenv”: “4.0.0”,    “dotenv-expand”: “4.2.0”,    “eslint”: “4.10.0”,    “eslint-config-react-app”: “^2.1.0”,    “eslint-loader”: “^2.1.1”,    “eslint-plugin-flowtype”: “2.39.1”,    “eslint-plugin-import”: “2.8.0”,    “eslint-plugin-jsx-a11y”: “5.1.1”,    “eslint-plugin-react”: “7.4.0”,    “express”: “^4.16.3”,    “fastclick”: “^1.0.6”,    “file-loader”: “2.0.0”,    “fs-extra”: “3.0.1”,    “good-storage”: “^1.0.1”,    “history”: “^4.7.2”,    “html-webpack-plugin”: “^3.2.0”,    “immutable”: “^3.8.2”,    “jest”: “20.0.4”,    “js-base64”: “^2.4.3”,    “jsonp”: “^0.2.1”,    “less”: “2.7.3”,    “less-loader”: “^4.0.1”,    “lyric-parser”: “^1.0.1”,    “mini-css-extract-plugin”: “^0.4.3”,    “mobx”: “^4.1.1”,    “mobx-react”: “^5.0.0”,    “mobx-react-devtools”: “^5.0.1”,    “node-sass”: “^4.9.3”,    “object-assign”: “4.1.1”,    “optimize-css-assets-webpack-plugin”: “^5.0.1”,    “postcss-flexbugs-fixes”: “3.2.0”,    “postcss-loader”: “2.0.8”,    “promise”: “8.0.1”,    “prop-types”: “^15.6.1”,    “raf”: “3.4.0”,    “react”: “^16.3.0”,    “react-addons-css-transition-group”: “^15.6.2”,    “react-dev-utils”: “^6.0.0-next.a671462c”,    “react-dom”: “^16.3.0”,    “react-hot-loader”: “^4.3.4”,    “react-lazyload”: “^2.3.0”,    “react-loadable”: “^5.5.0”,    “react-router-dom”: “^4.2.2”,    “react-transition-group”: “^2.3.1”,    “sass-loader”: “^7.1.0”,    “style-loader”: “0.19.0”,    “sw-precache-webpack-plugin”: “^0.11.5”,    “uglifyjs-webpack-plugin”: “^2.0.1”,    “url-loader”: “0.6.2”,    “webpack”: “^4.19.0”,    “webpack-cli”: “^3.1.0”,    “webpack-dev-server”: “^3.1.8”,    “webpack-manifest-plugin”: “^2.0.4”,    “whatwg-fetch”: “2.0.3”},  “scripts”: {“start”: “node scripts/start.js”,    “build”: “node scripts/build.js”},  “babel”: {“presets”: [      “react-app”],    “plugins”: [“transform-decorators-legacy”]  },  “eslintConfig”: {“extends”: “react-app”},  “devDependencies”: {“babel-plugin-transform-decorators-legacy”: “^1.3.4”,    “better-scroll”: “^1.9.1”},}
结 语
前端的框架更新速度,悄无声息又超乎想象,需要不断保持着对前端的热情和主动,研究一波前沿的技术架构和设计理念 ….
比如参加 D2 前端技术沙龙~
恍惚中,感觉这一波操作
好像还能再优化~ 优化~

退出移动版