從 roadhog 轉移到 create-react-app 並升級 webpack4

從 roadhog 轉移到 create-react-app 並升級 webpack4

項目環境說明

公司項目,使用 antd 作爲開發的 UI 框架, 項目使用預編譯語言 less,這主要是爲了和 antd 官方保持一致,
項目中有一些 tsx 的組件和 一些 ts 的腳本,因此 新的配置必須可以適應 js 和 ts 混合編譯,支持
css-modules, 支持 less 以及 antd 的動態導入javascript

完整開發配置

eject 配置

$ yarn eject # npm run eject

開啓自定義配置css

webpack.config.dev.js

其中純 CRA 配置不存在自定義,不過把原有 CRA 的配置都升級到 webpack4 的版本上了。其中包含了 webpack
的配置特色,以及如何解決 css-modules 和 antd 的動態樣式導入的衝突html

// 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 前後順序,爲
瞭解決下面問題:html5

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, 搜索 樣式沒法導入便可。java

上面方案靈感來自於 roadhog loaders 配置

package.json

這部分主要包含最近發新版的包,以下:node

// 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 命名空間
react

{
  "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' 或者 developmentwebpack

(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 便可web

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

總結

使用 cra 之後, 雖然一開始仍是比較慢,可是已經比 roadhog 編譯快不少了,並且熱編譯也很不錯,最重要是你可以對項目從配置到具體業務都有充足瞭解和認識,同時也對 roadhog 學習一波. 用上 webpack4 之後又能夠對v4 版本的 Code Splitting, chunk graph and the splitChunks optimization 進行學習了typescript

版原本自: https://luoyangfu.com/detail/...

相關文章
相關標籤/搜索