Webpack

遵循MVP原则, 即最简化可实行产品原则, 示例:

既有项目引入新的组件/库

假设现有项目用到了 react, react-router , antd 等库, 并且 controller, router, model, view 已基本成型.

此时如果要引入 mobx, 最佳实践步骤为:

  1. 新建一个空项目, 将既有库 react, antd 等安装, 配置一个最简单的 hello world 路由
  2. 安装 mobx, 引入并测试通过
  3. 再在原有项目上进行功能扩充

既有项目打包优化

假设现有项目用到了 react, react-router , antd, mobx 等库, 并且 controller, router, model, view 已基本成型. webpack打包过大, 应用性能较差.

最佳实践步骤:

  1. 新建一个空项目, 新建一个空的webpack配置
  2. 安装 react (或 antd, 或 mobx等) 写一个简单示例引入项目
  3. 针对单一库进行 webpack 打包优化, 一般情况下, 除了 loader rules / vendor 需要每个库单独优化, 其他配置都能保证通用
  4. 一项优化完成后重复2,3步骤, 直到所有库优化完成
  5. 对原有项目的 webpack 配置进行替换, 不动项目源码
  6. 进一步优化, 比如 react-router-loader 之类的引入, 开始针对项目源码进行优化

示例: React/Antd 项目初始化

1. 配置 eslint

  • 创建: .eslintrc.eslintignore
  • 安装: yarn add --dev eslint eslint-config-dwing eslint-config-airbnb eslint-plugin-react eslint-plugin-jsx-a11y babel-eslint
// .eslintrc.js
module.exports = {
  extends: [
    'eslint-config-dwing',
    'eslint-config-airbnb-base/rules/strict',
    'eslint-config-airbnb/rules/react'
  ].map(require.resolve),
  plugins: [
    'react'
  ],
  parser: 'babel-eslint',
  env: {
    browser: true
  }
};

2. 配置 babel

  • 创建: .babelrc
  • 安装: yarn add --dev babel-preset-env babel-preset-react babel-plugin-transform-runtime babel-plugin-transform-decorators-legacy babel-plugin-import babel-plugin-transform-class-properties babel-plugin-transform-object-rest-spread babel-runtime babel-polyfill babel-core
// .babelrc
{
  "presets": [
    ["env", {
      "targets": {
        "browsers": ["last 2 versions"]
      }
    }],
    "react"
  ],
  "plugins": [
    ["transform-runtime", {
      "helpers": false,
      "polyfill": false,
      "regenerator": true,
      "moduleName": "babel-runtime"
    }],
    "transform-decorators-legacy",
    ["import", { "libraryName": "antd", "style": true }],
    "transform-class-properties",
    "transform-object-rest-spread"
  ]
}

3. 安装 react/antd 等

  • 安装: yarn add react react-dom react-router antd mobx
  • webpack相关: yarn add --dev babel-loader less less-loader css-loader postcss-loader autoprefixer

4. 配置 webpack

  • 创建: webpack.config.js (用作产品) webpack.config.dev.js (用作开发)
  • 安装: yarn add --dev webpack html-webpack-plugin extract-text-webpack-plugin

该示例项目源码: https://github.com/AirDwing/webpack-lab/tree/antd

注意事项

1.最好不要用各类脚手架生成的 webpack 配置

原因有如下几点:

  1. 臃肿,夹杂了一大堆没用的第三方npm包,结构混乱, 难维护!!
  2. webpack更新速度较快, 现在已经到了 3.x 版本了, 很多脚手架还停留在 1.x 或 2.x的阶段
  3. 知其然知其所以然, 不能仅做代码搬运的机器, 这样的话就失去了人的价值了. 只有在频繁地接触和使用过程中才能挖掘更优的配置

2.webpack配置最佳实践

个人推荐以一个配置为base(基准),其他进行微调.

如, 产品环境配置为:

// webpack.config.js
/* eslint-disable import/no-extraneous-dependencies */
const webpack = require('webpack');
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const theme = require('./antd.config');

module.exports = {
  entry: {
    app: path.resolve(__dirname, '../src/main.jsx'),
    vendor: ['react', 'react-dom', 'react-router', 'mobx']
  },
  output: {
    path: path.resolve(__dirname, '../dist'),
    filename: '[name].js',
    publicPath: '/'
  },
  module: {
    rules: [
      {
        test: /\.jsx?$/,
        loader: 'babel-loader',
        exclude: /node_modules/
      },
      {
        test: /\.less$/,
        loader: ExtractTextPlugin.extract(
          `${require.resolve('css-loader')}?sourceMap&-autoprefixer!` +
          `${require.resolve('less-loader')}?{"sourceMap":true,"modifyVars":${JSON.stringify(theme)}}`
        )
      }
    ]
  },
  resolve: {
    modules: [
      'node_modules',
      path.resolve(__dirname, '../src')
    ],
    extensions: ['.js', '.json', '.jsx', '.css']
  },
  plugins: [
    new webpack.DefinePlugin({
      'process.env.NODE_ENV': '"production"'
    }),
    // 或灵活配置
    // new webpack.DefinePlugin({
    //   'process.env': {
    //     NODE_ENV: JSON.stringify(process.env.NODE_ENV)
    //   }
    // }),
    new webpack.optimize.AggressiveMergingPlugin(),
    new webpack.optimize.CommonsChunkPlugin({
      name: 'common',
      filename: 'common.js'
    }),
    new webpack.optimize.ModuleConcatenationPlugin(),
    new webpack.optimize.UglifyJsPlugin({
      minimize: true,
      output: {
        comments: false
      },
      compress: {
        warnings: false,
        drop_console: false
      }
    }),
    new ExtractTextPlugin('[name].css', {
      disable: false,
      allChunks: true
    }),
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, '../src/index.html')
    })
  ]
};

开发环境配置可以是这样去写:

// webpack.config.dev.js
const webpack = require('webpack');
// 引入 base
const config = require('./webpack.config');
// 对 base 进行扩展
module.exports = Object.assign({}, config, {
  entry: [
    // 重新完整定义一个 entry, 当然一般情况下是用不着这么做的
  ],
  output: Object.assign({}, config.output, {
    // 仅修改 output 的 publicPath
    publicPath: 'http://localhost/'
  }),
  // 比如, 在开发环境中需要多加一个 plugin
  plugins: [
    ...config.plugins,
    // 该插件仅用于示例
    new webpack.optimize.ModuleConcatenationPlugin()
  ],
  // 加一项新的配置
  devtools: ''
});

当然你可能觉得这么写性能很低, 但对于只在启动时执行一次的代码来说没有什么, 而且你如果仔细研究一下,比如webpack-merge或者其他的, 它们的底层实现也是这样, 还另外套了许多的封装.