roadhog遷移到webpck4

如今作的項目仍是基於早期的ant-design-pro那套東西,技術棧是 react + antd + dva + roadhog。隨着項目的迭代,不知從什麼時候起,發現項目打包很慢,每次jenkins上部署都要五、6分鐘的樣子。正好最近項目需求較少,正好有時間能夠搗鼓一下這個打包慢的問題。javascript

去roadhog的github下面搜索issues,發現有好多人和我遇到一樣的問題,解決方法大概就是把roadhog換成原汁原味的webpack4,因而我就開始着手改造了。css

修改package.json

  1. 刪除roadhog相關依賴html

    "roadhog": "^2.5.0-beta.1",
    "roadhog-api-doc": "^1.0.3",
    複製代碼
  2. 在devDependencies添加webpack4須要用到相關依賴java

    "webpack": "^4.8.1",
    "webpack-cli": "^3.1.1",
    "webpack-dev-server": "^3.1.4"
    複製代碼

我完整的devDependencies是這樣的node

"@hot-loader/react-dom": "^16.8.6",
    "@webassemblyjs/ast": "^1.3.1",
    "@webassemblyjs/wasm-edit": "^1.3.1",
    "address": "^1.0.3",
    "babel-core": "^6.26.3",
    "babel-eslint": "^8.2.3",
    "babel-loader": "^7.1.4",
    "babel-plugin-import": "^1.7.0",
    "babel-plugin-transform-class-properties": "^6.24.1",
    "babel-plugin-transform-decorators-legacy": "^1.3.4",
    "babel-plugin-transform-remove-console": "^6.9.2",
    "babel-plugin-transform-runtime": "^6.23.0",
    "babel-polyfill": "^6.26.0",
    "babel-preset-env": "^1.6.1",
    "babel-preset-react": "^6.24.1",
    "babel-preset-stage-0": "^6.24.1",
    "babel-runtime": "^6.26.0",
    "body-parser": "^1.18.3",
    "clean-webpack-plugin": "^0.1.19",
    "copy-webpack-plugin": "^4.5.1",
    "cross-env": "^5.1.1",
    "cross-port-killer": "^1.0.1",
    "css-hot-loader": "^1.4.4",
    "css-loader": "^0.28.11",
    "cssnano": "^3.10.0",
    "eslint": "^4.19.1",
    "eslint-config-airbnb": "^16.1.0",
    "eslint-plugin-compat": "^2.2.0",
    "eslint-plugin-jsx-a11y": "^6.0.3",
    "eslint-plugin-react": "^7.7.0",
    "estraverse": "^4.2.0",
    "file-loader": "^1.1.11",
    "happypack": "^5.0.0-beta.4",
    "hard-source-webpack-plugin": "^0.8.0",
    "html-webpack-plugin": "^4.0.0-beta.5",
    "husky": "^1.0.0-rc.4",
    "less": "^3.0.4",
    "less-loader": "^4.1.0",
    "mini-css-extract-plugin": "^0.4.1",
    "mockjs": "^1.0.1-beta3",
    "optimize-css-assets-webpack-plugin": "^4.0.1",
    "pro-download": "^1.0.1",
    "react-hot-loader": "^4.8.4",
    "redbox-react": "^1.5.0",
    "redux-devtools": "^3.4.1",
    "redux-devtools-dock-monitor": "^1.1.3",
    "redux-devtools-log-monitor": "^1.4.0",
    "regenerator-runtime": "^0.11.1",
    "sass-loader": "^7.0.1",
    "serve-index": "^1.9.1",
    "style-loader": "^0.21.0",
    "stylelint": "^9.2.0",
    "stylelint-config-standard": "^18.2.0",
    "type-is": "^1.6.15",
    "uglifyjs-webpack-plugin": "^1.2.5",
    "url-loader": "^1.0.1",
    "webpack": "^4.8.1",
    "webpack-bundle-analyzer": "^2.11.2",
    "webpack-cli": "^3.1.1",
    "webpack-dev-server": "^3.1.4"

複製代碼
  1. 修改scripts裏的啓動、打包命令react

    本地啓動命令:webpack

    "start": "cross-env ESLINT=none webpack-dev-server --config=webpack.config.development.js --mode development"
    複製代碼

    打包命令:git

    "build": "cross-env ESLINT=none webpack --config=webpack.config.production.js --mode production"
    複製代碼

添加webpack配置文件

剛剛應該有注意到我上面的腳本里有用到webpack.config.development.jswebpack.config.production.js這兩個文件。github

這兩個文件是須要咱們手動新增到根目錄下面的。web

  • webpack.config.development.js是用來給本地啓動用的,下面是這個文件裏的完整內容
const webpack = require('webpack');
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const theme = require('./src/theme');

module.exports = {
  entry: path.resolve(__dirname, 'src', 'index.js'),
  devServer: {
    contentBase: path.resolve(__dirname, 'dist'),
    host: 'localhost', // 主機地址
    port: 8000, // 端口號
    open: true,
    inline: true,
    openPage: 'ioc/#/user/login',
    hot: true,
    publicPath: '/ioc/',
    historyApiFallback: true,
    overlay: {
      errors: true,
    },
  },
  output: {
    filename: '[name].js',
    path: path.resolve(__dirname, 'dist'),
    publicPath: './',
    chunkFilename: '[name].async.js',
  },
  resolve: {
    alias: {
      src: path.resolve(__dirname, 'src/'),
    },
  },
  stats: {
    children: false,
    warningsFilter: warn => warn.indexOf('Conflicting order between:') > -1,
  },

  module: {
    rules: [
      {
        test: /\.js$/,
        include: [path.resolve(__dirname, 'src')],
        loader: 'babel-loader?cacheDirectory',
      },
      {
        test: /\.css$/,
        use: [
          'css-hot-loader',
          MiniCssExtractPlugin.loader,
          {
            loader: 'css-loader',
            options: {
              importLoaders: 1,
            },
          },
        ],
      },
      {
        test: /\.less$/,
        use: [
          'css-hot-loader',
          MiniCssExtractPlugin.loader,
          {
            loader: 'css-loader',
            options: {
              importLoaders: 1,
              modules: true,
              localIdentName: '[name]_[local]-[hash:base64:5]',
            },
          },
          {
            loader: 'less-loader',
            options: {
              javascriptEnabled: true,
              modifyVars: theme,
            },
          },
        ],
        exclude: /node_modules/,
      },
      {
        test: /\.less$/,
        use: [
          MiniCssExtractPlugin.loader,
          {
            loader: 'css-loader',
            options: {
              importLoaders: 1,
            },
          },
          {
            loader: 'less-loader',
            options: {
              javascriptEnabled: true,
              modifyVars: theme,
            },
          },
        ],
        exclude: /src/,
      },
      {
        test: /\.(png|svg|jpg|gif|ttf)$/,
        use: [
          {
            loader: 'url-loader',
            options: {
              limit: 8192,
              outputPath: './assets/',
            },
          },
        ],
      },
    ],
  },
  node: {
    fs: 'empty',
    module: 'empty',
  },
  devtool: false,
  optimization: {
    splitChunks: {
      cacheGroups: {
        styles: {
          name: 'styles',
          test: /\.(css|less)/,
          chunks: 'all',
          enforce: true,
        },
      },
    },
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: '[name].css',
    }),
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, 'src', 'index.ejs'), // 模板
      filename: 'index.html',
      hash: true, // 防止緩存
    }),
    new CleanWebpackPlugin(['dist']),
    new CopyWebpackPlugin([
      {
        from: path.resolve(__dirname, 'public'),
      },
    ]),
    new webpack.HotModuleReplacementPlugin(),
  ],
};

複製代碼
  • webpack.config.production.js是用來給打包用的,下面是這個文件裏的完整內容
const webpack = require('webpack');
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const HappyPack = require('happypack');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const os = require('os');
const happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length });

const theme = require('./src/theme');

module.exports = {
  entry: path.resolve(__dirname, 'src', 'index.js'),
  output: {
    filename: '[name].[chunkhash:8].js',
    path: path.resolve(__dirname, 'dist'),
    publicPath: './',
    chunkFilename: '[name].[chunkhash:8].async.js',
  },

  resolve: {
    alias: {
      src: path.resolve(__dirname, 'src/'),
    },
  },

  module: {
    rules: [
      {
        test: /\.js$/,
        include: [path.resolve(__dirname, 'src')],
        use: ['happypack/loader?id=babel'],
      },
      {
        test: /\.css$/,
        use: [
          MiniCssExtractPlugin.loader,
          {
            loader: 'css-loader',
            options: {
              importLoaders: 1,
            },
          },
        ],
      },
      {
        test: /\.less$/,
        use: [
          MiniCssExtractPlugin.loader,
          {
            loader: 'css-loader',
            options: {
              importLoaders: 1,
              modules: true,
              localIdentName: '[name]_[local]-[hash:base64:5]',
            },
          },
          {
            loader: 'less-loader',
            options: {
              javascriptEnabled: true,
              modifyVars: theme,
            },
          },
        ],
        exclude: /node_modules/,
      },
      {
        test: /\.less$/,
        use: [
          MiniCssExtractPlugin.loader,
          {
            loader: 'css-loader',
            options: {
              importLoaders: 1,
            },
          },
          {
            loader: 'less-loader',
            options: {
              javascriptEnabled: true,
              modifyVars: theme,
            },
          },
        ],
        exclude: /src/,
      },
      {
        test: /\.(png|svg|jpg|gif|ttf)$/,
        use: [
          {
            loader: 'url-loader',
            options: {
              limit: 8192,
              outputPath: './assets/',
            },
          },
        ],
      },
    ],
  },
  stats: {
    children: false,
    warningsFilter: warn => warn.indexOf('Conflicting order between:') > -1,
  },
  node: {
    fs: 'empty',
    module: 'empty',
  },
  optimization: {
    splitChunks: {
      cacheGroups: {
        styles: {
          name: 'styles',
          test: /\.(css|less)/,
          chunks: 'all',
          enforce: true,
        },
        commons: {
          name: 'commons',
          chunks: 'initial',
          minChunks: 2,
        },
        vendors: {
          name: 'vendors',
          test: /[\\/]node_modules[\\/]/,
          priority: -10,
        },
      },
    },
    runtimeChunk: true,
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: '[name].css',
    }),
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, 'src', 'index.ejs'), // 模板
      filename: 'index.html',
      hash: true, // 防止緩存
    }),
    new CleanWebpackPlugin(['dist']),
    new CopyWebpackPlugin([
      {
        from: path.resolve(__dirname, 'public'),
      },
    ]),
    new OptimizeCssAssetsPlugin({
      assetNameRegExp: /\.css$/g,
      cssProcessor: require('cssnano'),
      cssProcessorOptions: { discardComments: { removeAll: true } },
      canPrint: true,
    }),
    new HappyPack({
      id: 'babel',
      loaders: ['babel-loader?cacheDirectory'],
      threadPool: happyThreadPool,
    }),
    new webpack.HashedModuleIdsPlugin(),
  ],
};

複製代碼

別小看上面這段配置,這但是我百度了一些webpack的配置模板,而後再去研究webpack4的接口文檔,再結合咱們的這個實際項目,不斷調試報錯,花了大半天時間搞出來的。

能夠看到development的配置文件比production多了devServerhmr相關的配置,可是production的比development多了代碼壓縮、以及HappyPack相關配置。因此我以爲分紅兩個配置文件仍是頗有必要的,這樣就能夠根據本地調試和線上打包具體需求的差別修改不一樣的配置文件。

修改.babelrc

找到根目錄下的.babelrc文件,稍做修改

{
  "presets": ["env", "react", "stage-0"],
  "plugins": [
    "dva-hmr",
    "transform-decorators-legacy",
    ["import", { "libraryName": "antd", "libraryDirectory": "es", "style": true }],
    "transform-class-properties",
    "transform-runtime"
  ],
  "env": {
    "production": {
      "plugins": ["transform-remove-console"]
    }
  }
}

複製代碼

主要是添加了dva-hmr這個熱更新插件。

啓動

  1. 刪除node_modules。因爲 package.json中依賴改的比較多,因此建議先把原來項目中的node_modules文件夾刪掉,以避免形成沒必要要的衝突。

  2. 安裝依賴

    npm i
    複製代碼

    或者

    cnpm i 
    複製代碼
  3. 本地啓動

    npm start
    複製代碼

    不出意外的話,本地應該能夠啓動成功,而且會自動打開瀏覽器頁面

  4. 打包

    npm run build
    複製代碼

    本地啓動成功,再試着打一個線上環境的包,根目錄下會多出一個dist文件夾,裏面就是打包好的文件。

變化

打包時間明顯縮短了,這一點無論是本地打包或者jenkins打包,都明顯提高。

  • 遷移以前時間

  • 遷移以後時間

注意事項

  1. node版本最好升級到v8.11.1以上。一開始我本地能夠打包成功,可是jenkens上打包失敗了,看了一下log

    npm ERR! node v6.16.0
    npm ERR! npm  v3.10.10
    npm ERR! code ELIFECYCLE
    npm ERR! green-town-ioc@0.3.0 build: `cross-env ESLINT=none webpack --config=webpack.config.development.js --mode development`
    npm ERR! Exit status 1
    npm ERR! 
    npm ERR! Failed at the green-town-ioc@0.3.0 build script 'cross-env ESLINT=none webpack --config=webpack.config.development.js --mode development'.
    npm ERR! Make sure you have the latest version of node.js and npm installed.
    複製代碼

    發現我本地電腦用的nodev8.11.1,可是jenkins服務器的版本是v6.16.0的,而後我讓咱們的運維童鞋把jenkins上的node版本升級了一下,就打包成功了。

  2. 關於HMR,用的是dva提供的babel插件dva-hmr,這樣本地修改代碼,頁面就會自動刷新了。一開始我dev配置把sourceMap功能打開了,而後每次修改完代碼,就會隔好久頁面才能刷新,後來直接去掉了,熱更新就快了不少

後續優化

  1. 第三方不常常變更的庫,如react、antd等,打包成獨立的文件引用進來,這樣就不用每次都打包它們。這可能要用到DllReferencePlugin插件。
  2. hmr換成react-hot-loader,這樣本地開發更新了代碼後就能夠保存react組件狀態。當時也試過用它,可是始終沒有成功,因此被迫無奈用了dva-hmr

有遷移想法的小夥伴歡迎在評論區交流。

相關文章
相關標籤/搜索