编写一个基于webpack的React打包工具(2)

323 阅读4分钟

webpack的配置

webpack.common.js
webpack.dev.js
webpack.prod.js
webpack.umdcommon.js
webpack.umd.js

startapp.js,buildapp.js都是对webpack的封装,而2个命令都有一些公共的配置,公共的配置写在webpack.common.js文件中

webpack.common.js

const path = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');

const HappyPack = require('happypack');
const WebpackBar = require('webpackbar');
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');

const runtimePath = process.argv[8];

const APP_PATH = path.resolve(runtimePath, 'src'); // 项目src目录

const { getPostCssConfigPath } = require('../util');

module.exports = {
  plugins: {
    HtmlWebpackPlugin,
    MiniCssExtractPlugin,
    CopyWebpackPlugin,
  },
  config: {
    /**
     * 入口
     */
    entry: {
      index: path.join(runtimePath, 'src', 'index.js'),
    },
    /**
     * 出口
     */
    output: {
      filename:
        process.env.NODE_ENV === 'production'
          ? '[name].[chunkhash].bundle.js'
          : '[name].[hash].bundle.js',
      chunkFilename:
        process.env.NODE_ENV === 'production'
          ? '[name].[chunkhash].bundle.js'
          : '[name].[hash].bundle.js',
      path: path.resolve(runtimePath, 'dist'),
      publicPath: '/',
    },
    plugins: [
      // html模板
      new HtmlWebpackPlugin({
        title: '', // 模板html的title
        filename: 'index.html', // 模板名称
        template: path.join(runtimePath, 'src', 'index.html'),// 模板的地址
        hash: true, // 防止缓存
        minify: {
          removeAttributeQuotes: true, // 压缩 去掉引号
        },
        chunks: ['index'],
      }),
      // 该插件会根据模块的相对路径生成一个四位数的hash作为模块id, 建议用于生产环境
      new webpack.HashedModuleIdsPlugin(),
      // 将css放入单独的文件
      new MiniCssExtractPlugin({
        filename: '[name].css',
        chunkFilename: '[id].css',
        ignoreOrder: false, // Enable to remove warnings about conflicting order
      }),
      // 在单独的进程上运行TypeScript类型检查器的Webpack插件
      new ForkTsCheckerWebpackPlugin({
        tsconfig: path.join(runtimePath, 'tsconfig.json'),
        checkSyntacticErrors: true,
      }),
      // jsx和js的babel解析
      new HappyPack({
        id: 'babel',
        loaders: [
          'cache-loader',
          {
            loader: 'babel-loader',
            query: {
              presets: [
                [
                  '@babel/preset-env',
                  {
                    useBuiltIns: 'usage',
                    // 使用corejs3来进行polyfill
                    corejs: { version: 3, proposals: true },
                  },
                ],
                '@babel/preset-react',
              ],
              plugins: [
                '@babel/plugin-transform-runtime',
                '@babel/plugin-syntax-dynamic-import',
                '@babel/plugin-proposal-function-bind',
                '@babel/plugin-proposal-class-properties',
              ],
            },
          },
        ],
      }),
      // ts和tsx的解析
      new HappyPack({
        id: 'ts',
        loaders: [
          {
            loader: 'ts-loader',
            options: {
              transpileOnly: true,
              happyPackMode: true,
              configFile: path.join(runtimePath, 'tsconfig.json'),
            },
          },
        ],
      }),
      // css的解析
      new HappyPack({
        id: 'css',
        loaders: [
          'cache-loader',
          {
            loader: 'css-loader',
            options: {
              importLoaders: 1,
            },
          },
          {
            loader: 'postcss-loader',
            options: {
              config: {
                path: getPostCssConfigPath(runtimePath),
              },
            },
          },
        ],
      }),
      // less的解析
      new HappyPack({
        id: 'less',
        loaders: [
          'cache-loader',
          {
            loader: 'css-loader',
            options: {
              importLoaders: 1,
            },
          },
          {
            loader: 'postcss-loader',
            options: {
              config: {
                path: getPostCssConfigPath(runtimePath),
              },
            },
          },
          {
            loader: 'less-loader',
            query: {
              javascriptEnabled: true,
            },
          },
        ],
      }),
      new WebpackBar({ reporters: ['profile'], profile: true }),
    ],
    optimization: {
      runtimeChunk: 'single',
      splitChunks: {
        cacheGroups: {
          vendor: {
            test: /[\\/]node_modules[\\/]/,
            name: 'vendors',
            chunks: 'all',
          },
        },
      },
    },
    module: {
      rules: [
        {
          test: /\.m?jsx?$/,
          exclude: /(node_modules|bower_components)/,
          include: [APP_PATH],
          use: ['happypack/loader?id=babel'],
        },
        {
          test: /\.m?tsx?$/,
          exclude: /(node_modules|bower_components)/,
          include: [APP_PATH],
          use: ['happypack/loader?id=ts'],
        },
        {
          test: /\.css$/,
          include: [
            APP_PATH,
            /highlight.js/,
            /photoswipe.css/,
            /default-skin.css/,
            /swiper.min.css/,
            /antd/,
            /antd-mobile/,
            /normalize.css/,
          ],
          use: [
            process.env.NODE_ENV === 'development' ? 'style-loader' : MiniCssExtractPlugin.loader,
            'happypack/loader?id=css',
          ],
        },
        {
          test: /\.less$/,
          include: [APP_PATH, /normalize.less/],
          use: [
            process.env.NODE_ENV === 'development' ? 'style-loader' : MiniCssExtractPlugin.loader,
            'happypack/loader?id=less',
          ],
        },
        {
          test: /\.(png|svg|jpg|gif|ico)$/,
          use: [
            {
              loader: 'url-loader',
              options: {
                limit: 1024,
              },
            },
          ],
        },
        {
          test: /\.(woff|woff2|eot|ttf|otf)$/,
          use: [
            {
              loader: 'url-loader',
              options: {
                limit: 1024,
              },
            },
          ],
        },
        {
          test: /\.(csv|tsv)$/,
          use: ['csv-loader'],
        },
        {
          test: /\.xml$/,
          use: ['xml-loader'],
        },
        {
          test: /\.ejs/,
          loader: ['ejs-loader?variable=data'],
        },
        {
          test: /\.ya?ml$/,
          use: ['json-loader', 'yaml-loader'],
        },
      ],
    },
    resolve: {
      extensions: ['.js', '.jsx', '.ts', '.tsx', '.less', '.css', '.json'], // 后缀名自动补全
    },
  },
};

// 配置postcss的时候寻找postcss.config.js配置文件

getPostCssConfigPath(runtimePath) {
    if (fs.existsSync(path.join(runtimePath, 'postcss.config.js'))) {
      return path.join(runtimePath, 'postcss.config.js');
    }
    return path.join(__dirname, 'postcss.config.js');
}

在公共的配置中使用了很多loader和插件,也对loader部分进行了优化,优化这块主要使用了happypack,同时也加入了对less的解析,也使用了postcss-loader进行了autoprefixer的操作,其他的配置都是一些基础的配置,这里就不多做介绍了。需要说明的是文件导出2个key,分别是plugins和config

这个公共文件写好是之后,接下来就是实现dev和prod这两个配置文件了

webpack.dev.js

const webpack = require('webpack');
const merge = require('webpack-merge');
const common = require('./webpack.common.js');
const commandArgs = require('../commandArgs');
const argsMap = commandArgs.initCommandArgs();

const customConfigPath = argsMap.get('--customconfig')[0];
let customModule;

const curModule = merge(common.config, {
  mode: 'development',
  devtool: 'cheap-module-eval-source-map',
  devServer: {
    publicPath: '/',
    host: 'localhost',
    compress: true,
    port: 8000,
    clientLogLevel: 'none', //不再输出繁琐的信息
    historyApiFallback: true,
    overlay: true, //浏览器全屏显示错误信息
    hot: true, // 启动模块热更新 HMR
    open: true, // 开启自动打开浏览器页面
  },
  plugins: [
    new webpack.DefinePlugin({
      process: {
        env: {
          NODE_ENV: JSON.stringify('development'),
          REAP_PATH: JSON.stringify(process.env.REAP_PATH),
        },
      },
    }),
    new webpack.NamedModulesPlugin(),
    new webpack.HotModuleReplacementPlugin(),
  ],
});

const define = argsMap.get('--define')[0] || '';

if (customConfigPath) {
  customModule = require(customConfigPath);
  if (customModule && customModule.getConfig) {
    customModule.getConfig({
      webpack,
      curModule,
      plugins: common.plugins,
      define: commandArgs.toCommandArgs(define),
    });
  }
}

module.exports = curModule;

这里会对dev环境进行单独的参数设置,主要就是webpack-dev-server的设置

webpack.prod.js

const webpack = require('webpack');
const merge = require('webpack-merge');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const common = require('./webpack.common.js');
const commandArgs = require('../commandArgs');

const argsMap = commandArgs.initCommandArgs();

const customConfigPath = argsMap.get('--customconfig')[0];
let customModule;

const curModule = merge(common.config, {
  mode: 'production',
  plugins: [
    new CleanWebpackPlugin(),
    new webpack.DefinePlugin({
      process: {
        env: {
          NODE_ENV: JSON.stringify('production'),
          REAP_PATH: JSON.stringify(process.env.REAP_PATH),
        },
      },
    }),
  ],
});

const define = argsMap.get('--define')[0] || '';

if (customConfigPath) {
  customModule = require(customConfigPath);
  if (customModule && customModule.getConfig) {
    customModule.getConfig({
      webpack,
      curModule,
      plugins: common.plugins,
      define: commandArgs.toCommandArgs(define),
    });
  }
}

module.exports = curModule;

这里会对prod环境进行单独的参数设置。

不管是dev还是prod最后都会寻找参数中customCinfigPath这个路径下是否有用户的配置文件,如果用户没有传这个参数,那么就会寻找ctbuild.config.js这个配置文件,这里会把webpack,和当前配置,所有插件和其他命令行参数传递给getConfig这个方法,用户在配置文件中在对传递过去的配置进行一个修改,达到自定义配置的效果。

这里说一下怎么使用webpack解析typescript,首先大家要明白一点,webpack.dev.js和webpack.prod.js都是去编译宿主工程而不是packge工程,如果宿主工程使用的是typescript进行编写,那么我们使用ts-loader和fork-ts-checker-webpack-plugin插件来进行解析就可以,详细请看上方代码部分。

接下来在介绍一下babel.config.js文件,这个文件是用来配置babel解析的,这个配置文件放在包工程的根目录下,这个文件在执行startapp、buildapp和buildpackage的时候都会用到。

babel.config.js

const presets = [
  [
    '@babel/preset-env',
    {
      // 模式是使用了才进行polyfill
      useBuiltIns: 'usage',
      // 使用corejs3来进行polyfill
      corejs: { version: 3, proposals: true },
    },
  ],
  '@babel/preset-react',
];

const plugins = [
  '@babel/plugin-transform-runtime',
  '@babel/plugin-syntax-dynamic-import',
  '@babel/plugin-proposal-function-bind',
  '@babel/plugin-proposal-optional-chaining',
  // 可以使用装饰器模式
  ['@babel/plugin-proposal-decorators', { legacy: true }],
  ['@babel/plugin-proposal-class-properties', { loose: true }],
];

module.exports = { presets, plugins };

接下来会对startapp.js、buildapp.js、buildpackage.js和buildpackagets.js进行详细的讲解

   github