从 roadhog 转移到 create-react-app 并升级 webpack4

项目环境说明

公司项目,使用 antd 做为开发的 UI 框架, 项目使用预编译语言 less,这主要是为了和 antd 官方保持一致,
项目中有一些 tsx 的组件和 一些 ts 的脚本,所以 新的配置必须能够适应 js 和 ts 混合编译,支持
css-modules, 支持 less 以及 antd 的动态导入

完整开发配置

eject 配置

$ yarn eject # npm run eject

开启自定义配置

webpack.config.dev.js

其中纯 CRA 配置不存在自定义,不过把原有 CRA 的配置都升级到 webpack4 的版本上了。其中包含了 webpack
的配置特点,以及如何解决 css-modules 和 antd 的动态样式导入的冲突

// webpack.config.dev.js
'use strict'

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 tsImportPluginFactory = require('ts-import-plugin')
const getClientEnvironment = require('./env')
const paths = require('./paths')

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', '.ts', '.tsx'],
    alias: {
      'react-native': 'react-native-web',
    },
    plugins: [new ModuleScopePlugin(paths.appSrc, [paths.appPackageJson])],
  },
  module: {
    strictExportPresence: true,
    rules: [
      {
        oneOf: [
          {
            test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
            loader: require.resolve('url-loader'),
            options: {
              limit: 10000,
              name: 'static/media/[name].[hash:8].[ext]',
            },
          },
          {
            test: /\.(ts|tsx)$/,
            use: [
              {
                loader: require.resolve('awesome-typescript-loader'),
                options: {
                  transpileOnly: true,
                  useCache: true,
                  useBabel: true,
                  babelOptions: {
                    babelrc: true,
                  },
                  babelCore: '@babel/core',
                },
              },
            ],
            exclude: /node_modules/,
          },
          {
            test: /\.(js|jsx|mjs)$/,
            include: paths.appSrc,
            // loader: 'happypack/loader',
            loader: require.resolve('babel-loader'),
            query: {
              cacheDirectory: true,
              // plugins: [['import', { libraryName: 'antd', style: true }]],
            },
          },
          {
            test: /\.css$/,
            use: [
              require.resolve('style-loader'),
              {
                loader: require.resolve('css-loader'),
                options: {
                  importLoaders: 1,
                  modules: true,
                  localIdentName: '[name]__[local]___[hash:base64:5]',
                },
              },
              {
                loader: require.resolve('postcss-loader'),
                options: {
                  ident: 'postcss',
                  plugins: () => [
                    require('postcss-flexbugs-fixes'),
                    autoprefixer({
                      browsers: ['>1%', 'last 4 versions', 'Firefox ESR', 'not ie < 9'],
                      flexbox: 'no-2009',
                    }),
                  ],
                },
              },
            ],
          },
          {
            test: /\.less$/,
            exclude: path.resolve(__dirname, '../node_modules'),
            use: [
              require.resolve('style-loader'),
              {
                loader: require.resolve('css-loader'),
                options: {
                  importLoaders: 1,
                  modules: true,
                  localIdentName: '[name]__[local]___[hash:base64:5]',
                },
              },
              {
                loader: require.resolve('postcss-loader'),
                options: {
                  ident: 'postcss',
                  plugins: () => [
                    require('postcss-flexbugs-fixes'),
                    autoprefixer({
                      browsers: ['>1%', 'last 4 versions', 'Firefox ESR', 'not ie < 9'],
                      flexbox: 'no-2009',
                    }),
                  ],
                },
              },
              {
                loader: require.resolve('less-loader'),
                options: {
                  modifyVars: { '@primary-color': '#1DA57A' },
                  javascriptEnabled: true,
                },
              },
            ],
          },
          {
            test: /\.less$/,
            include: [path.resolve(__dirname, '../node_modules/')],
            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'],
                      flexbox: 'no-2009',
                    }),
                  ],
                },
              },
              {
                loader: require.resolve('less-loader'),
                options: {
                  modifyVars: { '@primary-color': '#1DA57A' },
                  javascriptEnabled: true,
                },
              },
            ],
          },
          {
            exclude: [/\.(js|jsx|mjs)$/, /\.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',
  },
}

在上面配置中,有变动的地方和 HtmlWebpackPlugin 和 react-dev-utils/InterpolateHtmlPlugin 先后顺序,为
了解决下面问题:

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'.

其次关于 test: /\.less/ 有两次,观察两个 loader 的不同,一个 配置中 css-modules 是关闭,一个是开启
的,同时一个 exclude 是去除 node_modules 一个是 include node_modules, 为了解决 css-modules 的开
启会导致 antd 的样式无法导入问题, 具体可以关注 ant-design 的仓库 issue, 搜索 样式无法导入即可。

上面方案灵感来自于 roadhog loaders 配置

package.json

这部分主要包含最近发新版的包,如下:

// package.json
{
  "name": "dva项目",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "@babel/polyfill": "^7.0.0",
    "antd": "^3.9.2",
    "bizcharts": "^3.2.2",
    "classnames": "^2.2.6",
    "currency.js": "^1.1.4",
    "dva": "^2.4.0",
    "enquire-js": "^0.2.1",
    "fastclick": "^1.0.6",
    "immutable": "^3.8.2",
    "lodash": "^4.17.11",
    "lodash-decorators": "^6.0.0",
    "qrcode": "^1.2.2",
    "rc-drawer-menu": "^1.1.0",
    "rc-trigger": "^2.5.4",
    "react": "^16.5.1",
    "react-container-query": "^0.11.0",
    "react-countup": "^4.0.0-alpha.6",
    "react-dnd": "^5.0.0",
    "react-dnd-html5-backend": "^5.0.1",
    "react-document-title": "^2.0.3",
    "react-dom": "^16.5.1",
    "react-virtualized": "^9.20.1",
    "rollbar": "^2.4.6",
    "ua-parser-js": "^0.7.18",
    "xlsx": "^0.14.0"
  },
  "devDependencies": {
    "@babel/core": "^7.0.1",
    "@babel/plugin-proposal-class-properties": "^7.0.0",
    "@babel/plugin-proposal-decorators": "^7.0.0",
    "@babel/plugin-proposal-optional-chaining": "^7.0.0",
    "@babel/plugin-syntax-dynamic-import": "^7.0.0",
    "@babel/preset-env": "^7.0.0",
    "@babel/preset-react": "^7.0.0",
    "@babel/runtime": "^7.0.0",
    "@types/lodash": "^4.14.116",
    "@types/react": "^16.4.14",
    "@types/react-dom": "^16.0.7",
    "autoprefixer": "7.1.6",
    "awesome-typescript-loader": "^5.2.1",
    "babel-eslint": "7.2.3",
    "babel-jest": "20.0.3",
    "babel-loader": "^8.0.0-beta.6",
    "babel-plugin-import": "^1.8.0",
    "case-sensitive-paths-webpack-plugin": "2.1.1",
    "chalk": "1.1.3",
    "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": "1.9.0",
    "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",
    "extract-text-webpack-plugin": "3.0.2",
    "file-loader": "^2.0.0",
    "fs-extra": "3.0.1",
    "happypack": "^5.0.0",
    "hard-source-webpack-plugin": "^0.12.0",
    "html-webpack-plugin": "^3.2.0",
    "jest": "20.0.4",
    "less": "^3.8.1",
    "less-loader": "^4.1.0",
    "object-assign": "4.1.1",
    "postcss-flexbugs-fixes": "3.2.0",
    "postcss-loader": "2.0.8",
    "promise": "8.0.1",
    "raf": "3.4.0",
    "react-dev-utils": "^6.0.0-next.a671462c",
    "resolve": "1.6.0",
    "style-loader": "0.19.0",
    "sw-precache-webpack-plugin": "^0.11.5",
    "ts-import-plugin": "^1.5.5",
    "typescript": "^3.0.3",
    "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": {
    "dev": "NODE_ENV=development webpack --config config/webpack.config.dev.js --profile",
    "start": "node scripts/start.js",
    "build": "node scripts/build.js",
    "test": "node scripts/test.js --env=jsdom"
  }
}

.babelrc

不仅将 webpack 从 v3 到 v4 而且,基本把 babel 都升级到最新版,最新版的 babel 都是在 @babel 命名空间

{
  "presets": ["@babel/preset-env", "@babel/preset-react"],
  "plugins": [
    ["import", { "libraryName": "antd", "libraryDirectory": "es", "style": true }],
    "@babel/plugin-syntax-dynamic-import",
    ["@babel/plugin-proposal-decorators", { "legacy": true }],
    "@babel/plugin-proposal-class-properties",
    "@babel/plugin-proposal-optional-chaining"
  ]
}

常见问题

WARNING in configuration
The 'mode' option has not been set, webpack will fallback to 'production' for this value. Set 'mode' option to 'development' or 'production' to enable defaults for each environment.
You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/concepts/mode/

增加 webpack 文件中 mode: 'production' 或者 development

(node:6005) DeprecationWarning: Tapable.plugin is deprecated. Use new API on `.hooks` instead
    at ModuleScopePlugin.apply (/Users/yes/.../node_modules/react-dev-utils/ModuleScopePlugin.js:21:14)

升级 react-dev-utils 即可

$ yarn add -D react-dev-utils@next

总结

使用 cra 以后, 虽然一开始还是比较慢,但是已经比 roadhog 编译快很多了,而且热编译也很不错,最重要是你能够对项目从配置到具体业务都有充足了解和认识,同时也对 roadhog 学习一波. 用上 webpack4 以后又可以对v4 版本的 Code Splitting, chunk graph and the splitChunks optimization 进行学习了

本文来自: https://luoyangfu.com/detail/5b9cb82c4a41fc3eb4b797ea

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值