Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

干掉roadhog,自己动手搭建webpack #1

Open
404cat opened this issue Aug 12, 2019 · 3 comments
Open

干掉roadhog,自己动手搭建webpack #1

404cat opened this issue Aug 12, 2019 · 3 comments

Comments

@404cat
Copy link
Owner

404cat commented Aug 12, 2019

1、为什么要自己动手搭建webpack?


公司老项目的技术配置为react+dva+roadhog,到我接手时已经迭代开发了好几年,项目文件和依赖都十分巨大,据交接给我的开发人员说可能由于第三方升级的原因,导致编译速度十分缓慢;开发时的热更新速度一般都是10-20s+,线上打包速度更是六七分钟以上,线上打包也不是经常操作,所以时间长点忍忍也就过去了, 但是开发时的热更新速度这么慢简直不能忍受;
image

2、如何干掉roadhog&配置webpack?


2.1、首先把项目中的关于roadhog的配置全部删除:

package.json:"roadhog": "^1.1.2"
根目录:.roadhogrc.js文件(暂时先不删删除,因为需要兼容项目,所以这里面的一些配置要copy到webpack当中);

2.2、webpack配置:

首先安装webpack相关:

"webpack": "^4.8.1",
"webpack-cli": "^3.3.5",
"webpack-merge": "^4.2.1",

接着在项目根目录创建

webpack.common.js, // 用来放置一些公用的配置
webpack.dev.js // 用来放置开发配置
webpack.prod.js // 放置线上打包配置

具体可以查看webpack并结合自身项目来进行配置,这里贴一下我的配置:

common.js

const path = require('path')
const webpack = require('webpack')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const CopyWebpackPlugin = require('copy-webpack-plugin')
// const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
// let bundleConfig = require('./bundle-config.json')
const theme = require('../theme.config.js')
const config = require('../src/utils/config')

const nodeEnv = process.env.NODE_ENV || 'development'
const isDev = nodeEnv !== 'production'

console.log('webpack NODE_ENV CONFIG_ENV config.publicPath', process.env.NODE_ENV, process.env.CONFIG_ENV, config.publicPath, config.baseURL)

module.exports = {
  cache: isDev,
  entry: {
    index: ['babel-polyfill', 'es6-symbol/polyfill', path.resolve(__dirname, '..', 'src', 'index.js')],
  },
  output: {
    filename: isDev ? '[name].js' : '[name].[chunkhash:8].js',
    path: path.join(__dirname, '..', 'dist'),
    publicPath: config.publicPath,
    chunkFilename: isDev ? '[name].chunk.js' : '[name].[chunkhash:8].chunk.js',
  },
  resolve: {
    alias: {
      src: path.resolve(__dirname, '..', 'src/'),
      '@config': path.resolve(__dirname, '..', './src/utils/config'),
      '@assets': path.resolve(__dirname, '..', './src/assets'),
      '@srcRoot': path.resolve(__dirname, '..', './src'),
      '@components': path.resolve(__dirname, '..', './src/components'),
      '@utils': path.resolve(__dirname, '..', './src/utils'),
      '@themes': path.resolve(__dirname, '..', './src/themes'),
      '@services': path.resolve(__dirname, '..', './src/services'),
      '@models': path.resolve(__dirname, '..', './src/models'),
      '@routes': path.resolve(__dirname, '..', './src/routes'),
      '@shared': path.resolve(__dirname, '..', './src/shared'),
    },
    extensions: ['.js', '.jsx'], // 导入没有带后缀的文件
  },
  devtool: isDev ? 'inline-source-map' : 'hidden-source-map',
  module: {
    rules: [
      {
        test: /\.(js|mjs|jsx|ts|tsx)$/,
        include: [path.resolve(__dirname, '..', 'src')],
        exclude: /node_modules/,
        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: {
    net: 'empty',
    fs: 'empty',
    module: 'empty',
    child_process: 'empty',
    tls: 'empty',
    dgram: 'empty',
  },
  optimization: {
    splitChunks: {
      chunks: 'async',
      minSize: 30000,
      minChunks: 1,
      maxAsyncRequests: 5,
      maxInitialRequests: 3,
      automaticNameDelimiter: '~',
      name: true,
      cacheGroups: {
        styles: {
          name: 'styles',
          test: /\.(css|less)/,
          chunks: 'async',
          enforce: true,
        },
        commons: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          chunks: 'async',
        },
      },
    },
  }, // 提取公共代码
  plugins: [
    new webpack.DefinePlugin({
      'process.env': {},
      'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
      'process.env.CONFIG_ENV': JSON.stringify(process.env.CONFIG_ENV),
      'process.env.THEME': JSON.stringify(theme),
      'process.env.PUBLICPATH': JSON.stringify(config.publicPath),
    }),
    new MiniCssExtractPlugin({
      filename: '[name].css',
    }),
    // new webpack.DllReferencePlugin({
    //   context: __dirname,'..',
    //   // eslint-disable-next-line global-require
    //   manifest: require('./dll/manifest.json'),
    // }),
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, '..', 'src', 'index.ejs'), // 模板
      filename: 'index.html',
      //   vendorJsName: bundleConfig.vendor.js, // 加载dll文件
      hash: true, // 防止缓存
      minify: false,
    }),
    new CleanWebpackPlugin({
      verbose: true,
    }),
    new CopyWebpackPlugin([
      {
        from: path.resolve(__dirname, '..', 'public'),
      },
    ]),
    // new BundleAnalyzerPlugin(),

  ],
}

dev.js

const path = require('path')
const webpack = require('webpack')
const merge = require('webpack-merge')
const common = require('./webpack.common')
const { baseURL } = require('../src/utils/config')
// const { publicPath, NODE_ENV } = require('../src/utils/config')
// const theme = require('../theme.config.js')
// const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin

module.exports = merge(common, {
  devServer: {
    contentBase: path.resolve(__dirname, 'dist'), // 提供静态文件内容
    host: 'localhost',
    port: 8008,
    open: true,
    inline: true,
    hot: true,
    publicPath: '/',
    historyApiFallback: true,
    clientLogLevel: 'none',
    overlay: {
      errors: true,
    },
    proxy: {
      [baseURL]: {
        target: 'http://192.168.1.87:9003/',
        changeOrigin: true,
        secure: false,
        pathRewrite: {
          [baseURL]: '/aek-mspp',
        },
      },
    },
  },
  devtool: 'eval-source-map',
  stats: {
    children: false,
    warningsFilter: warn => warn.indexOf('Conflicting order between:') > -1,
  },
  performance: {
    hints: 'warning',
  },
  plugins: [
    new webpack.HotModuleReplacementPlugin(),
    // new BundleAnalyzerPlugin(),
  ],
})

prod.js

// const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
const merge = require('webpack-merge')
const ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin')
const common = require('./webpack.common')

module.exports = merge(common, {
  devtool: false,
  optimization: {
    minimizer: [
      new ParallelUglifyPlugin({ // 多进程压缩
        cacheDir: '.cache/',
        uglifyJS: {
          output: {
            comments: false,
            beautify: false,
          },
          compress: {
            // warnings: false,
            drop_console: true,
            collapse_vars: true,
            reduce_vars: true,
          },
        },
      }),
      // new UglifyJsPlugin({ // 这个不用是因为上面的ParallelUglifyPlugin也可以起到压缩代码的作用
      //   exclude: /\.min\.js$/,
      //   cache: true,
      //   parallel: true, // 开启并行压缩,充分利用cpu
      //   sourceMap: false,
      //   extractComments: false, // 移除注释
      //   uglifyOptions: {
      //     compress: {
      //       unused: true,
      //       warnings: false,
      //       drop_debugger: true,
      //     },
      //     output: {
      //       comments: false,
      //     },
      //   },
      // }), // 压缩代码
    ],
  },
})

babel配置 在根目录配置 .babelrc 文件

{
    "presets": ["env", "react", "stage-0"],
    "plugins": [
        "dva-hmr",
        "transform-decorators-legacy",
        ["import", { "libraryName": "antd", "style": true }],
        "transform-class-properties",
        "transform-runtime",
        "add-module-exports",
        "recharts",
        "lodash"
    ],
    "env": {
        "development": {
        "plugins": ["dynamic-import-node"]
        },
        "production": {
        "plugins": ["transform-remove-console"]
        }
    }
}

package.json

{
  "private": true,
  "dependencies": {
    "antd": "^2.13.6",
    "axios": "^0.19.0",
    "babel-polyfill": "^6.23.0",
    "braft-editor": "^2.3.7",
    "braft-extensions": "0.0.18",
    "braft-polyfill": "0.0.2",
    "classnames": "^2.2.5",
    "countup.js": "^1.9.2",
    "decimal.js-light": "^2.3.0",
    "dva": "^2.0.1",
    "dva-loading": "^1.0.2",
    "dva-model-extend": "^0.1.1",
    "es6-symbol": "^3.1.1",
    "form-data": "^2.3.1",
    "lodash": "^4.17.4",
    "md5": "^2.2.1",
    "nprogress": "^0.2.0",
    "parse-domain": "^1.1.0",
    "path-to-regexp": "^3.0.0",
    "prop-types": "^15.5.10",
    "qrcode.react": "^0.8.0",
    "qs": "^6.2.0",
    "rc-tween-one": "^1.0.0",
    "react": "^15.5.4",
    "react-barcode": "^1.2.0",
    "react-color": "^2.17.3",
    "react-copy-to-clipboard": "^5.0.0",
    "react-dom": "^15.5.4",
    "react-helmet": "^5.0.0",
    "react-loadable": "^5.5.0",
    "react-modal": "^3.0.0",
    "react-pdf": "^2.2.0",
    "react-scroll": "^1.5.5",
    "recharts": "^1.0.0-alpha.1",
    "sockjs-client": "1.0.0",
    "stompjs": "^2.3.3"
  },
  "devDependencies": {
    "@hot-loader/react-dom": "^16.8.6",
    "@webassemblyjs/ast": "^1.3.1",
    "@webassemblyjs/wasm-edit": "^1.3.1",
    "address": "^1.0.3",
    "assets-webpack-plugin": "^3.9.10",
    "babel-core": "^6.26.3",
    "babel-eslint": "^8.2.3",
    "babel-loader": "^7.1.4",
    "babel-plugin-add-module-exports": "^1.0.2",
    "babel-plugin-dev-expression": "^0.2.1",
    "babel-plugin-dva-hmr": "^0.3.2",
    "babel-plugin-dynamic-import-node": "^2.3.0",
    "babel-plugin-import": "^1.7.0",
    "babel-plugin-lodash": "^3.3.2",
    "babel-plugin-module-resolver": "^2.7.1",
    "babel-plugin-recharts": "^1.1.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",
    "better-npm-run": "^0.1.1",
    "body-parser": "^1.18.3",
    "clean-webpack-plugin": "^3.0.0",
    "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-import-resolver-babel-module": "^4.0.0-beta.3",
    "eslint-plugin-compat": "^2.2.0",
    "eslint-plugin-import": "^2.7.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": "^3.0.0",
    "less": "^3.0.4",
    "less-loader": "^4.1.0",
    "less-vars-to-js": "^1.1.2",
    "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",
    "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.3.5",
    "webpack-dev-server": "^3.7.2",
    "webpack-merge": "^4.2.1",
    "webpack-parallel-uglify-plugin": "^1.1.0",
    "window-size": "^1.1.1"
  },
  "pre-commit": [
    "lint"
  ],
  "scripts": {
    "start": "cross-env CONFIG_ENV=development better-npm-run start-dev",
    "startOnline": "cross-env CONFIG_ENV=production CONFIG_PROXY_ONLINE_ENV=development better-npm-run start-dev",
    "build:production": "cross-env CONFIG_ENV=production better-npm-run build",
    "build:test": "cross-env CONFIG_ENV=test better-npm-run build",
    "build:preTest": "cross-env CONFIG_ENV=preTest better-npm-run build",
    "dll": "cross-env webpack --config webpack.dll.config.js --colors --display-modules"
  },
  "betterScripts": {
    "start-dev": {
      "command": "webpack-dev-server --profile --inline --display-modules --progress --color --config=./script/webpack.dev.js",
      "env": {
        "NODE_ENV": "development"
      }
    },
    "build": {
      "command": "webpack --profile --inline --display-modules --progress --color --config=./script/webpack.prod.js",
      "env": {
        "NODE_ENV": "production"
      }
    }
  },
  "theme": "./src/theme.config.js"
}

2.3、采坑点和需要注意的点

webpack基础

  • entry:入口文件,webpack会从该文件遍历寻找出所依赖到的文件并打包成bundle,入口文件可以配置一个或者多个;

  • output:出口,打包好的模块的输出配置;output的常见配置:

    filename:  // 每个输出bundle的文件名,如果有多个bundle的话应该使用`id , name , hash, chunkhash`等来对文件名进行唯一标识
    path: path.join(__dirname, '..', 'dist'), // 输出文件的目标文件夹,应该是
    publicPath: config.publicPath, // 需要引用外部资源时配置这个选项,比如说线上都是把js 和 css文件放在云服务器上,这时候就要配置这个publicPath,在打包的时候会被解析插入到相对应的HTML文件中,比如说我们项目线上的源代码中能看到:<script src="**//js.xxx.com/**index.70b799cc.js?bb88a66d5cfeb59190a8"></script>  其中加粗的地方就是配置的publicPath,后面跟着的是服务器上的js文件,这个地址如果没有配置好的话,那么在加载这些js 或者其他资源的时候会报404错误;
    chunkFilename:  // 需要异步加载的文件名称,可以在这里统一配置,也可以在使用代码切割时指定切割文件的chunkname;

关于filename和chunkfilename 的配置可选:一般会采用hash值来对前端资源实现增量更新,参考Webpack中hash与chunkhash的区别,以及js与css的hash指纹解耦方案

  • resolve 设置打包的模块如何被解析,具体配置可参考webpack官网~~

路由按需加载

路由按需加载是用的dva封装的dynamic,webpack在打包的时候会根据路由表的配置进行代码切割 或者使用 react 提供的loadable来进行代码切割;
但是有个坑就是在开发环境下进行代码切割在编译Module and chunk tree optimization阶段会巨慢,
然后搜索到参考1 这个issues,答案提及的babel-plugin-dynamic-import-node这个插件,可以把import转换为require,从而在编译的时候不进行切割;
但是当时忘了只能在Dev环境下使用这个插件,然后prod打包时代码不会进行切割了,然后各种排查。。。
代码切割参考webpack import

打包成功但是dist文件夹内没有生产出文件

开发和线上配置区分没有做好。把new webpack.HotModuleReplacementPlugin(),plugin放在了common中,这样在打包prod时也启用了热更新,热更新插件会把输出到dist的文件移除。

before & after

配置完之后首次打开项目12s,热更新编译的速度1-2s,跟之前的速度项目简直爽的不要不要的;


结语

本来觉得webpack配置很复杂,一不小心就是各种报错,所以也不太想了解;但是花了时间把这套配置搭建出来后,对开发效率有了质的提升,在解决各种报错和玄学问题的过程中也收货颇大,现在对webpack的了解虽然还只是冰山一角,但是现在看到它不会再去害怕它!

@404cat 404cat changed the title 干掉roadhog,自己动手搭建webpack打包 干掉roadhog,自己动手搭建webpack Aug 12, 2019
@chenhonghui
Copy link

package.json 里面的scripts分享一下呢

@404cat
Copy link
Owner Author

404cat commented Aug 14, 2019

package.json 里面的scripts分享一下呢

已更新

@chenhonghui
Copy link

然后搜索到参考1 这个issues,答案提及的babel-plugin-dynamic-import-node这个插件,可以把import转换为require,从而在编译的时候不进行切割;
什么地方的import转换为require???

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants