roadhog迁移到webpck4

  • Post author:
  • Post category:其他


现在做的项目还是基于早期的

ant-design-pro

那套东西,技术栈是 react + antd + dva + roadhog。随着项目的迭代,不知从何时起,发现项目打包很慢,每次

jenkins

上部署都要5、6分钟的样子。正好最近项目需求较少,正好有时间可以捣鼓一下这个打包慢的问题。

去roadhog的github下面搜索issues,发现有好多人和我遇到同样的问题,解决方法大概就是把roadhog换成原汁原味的webpack4,于是我就开始着手改造了。

修改package.json

  1. 删除roadhog相关依赖

    "roadhog": "^2.5.0-beta.1",
    "roadhog-api-doc": "^1.0.3",
    复制代码
  2. 在devDependencies添加webpack4需要用到相关依赖

    "webpack": "^4.8.1",
    "webpack-cli": "^3.1.1",
    "webpack-dev-server": "^3.1.4"
    复制代码

我完整的devDependencies是这样的

 		"@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

    里的启动、打包命令

    本地启动命令:

    "start": "cross-env ESLINT=none webpack-dev-server --config=webpack.config.development.js --mode development"
    复制代码

    打包命令:

    "build": "cross-env ESLINT=none webpack --config=webpack.config.production.js --mode production"
    复制代码

添加webpack配置文件

刚刚应该有注意到我上面的脚本里有用到

webpack.config.development.js



webpack.config.production.js

这两个文件。

这两个文件是需要我们手动新增到根目录下面的。


  • 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

多了

devServer



hmr

相关的配置,但是

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.
    复制代码

    发现我本地电脑用的

    node



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

有迁移想法的小伙伴欢迎在评论区交流。