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

685 阅读4分钟

一、目的

  1. 可以打包React的宿主工程
  2. 可以打包package
  3. 对实现细节进行封装
  4. 用户在宿主工程中以配置文件的方式可对已有配置进行添加删除和修改
  5. 外部暴露不同功能的命令
  6. 用户安装后可以实现开箱即用的效果
  7. 支持javascript和typescript进行编写宿主工程和package

二、命令设计

  • startapp - 本地运行宿主工程

  • buildapp - 打包宿主工程

  • buildpackage - 编译package

  • buildumd - 编译package为umd模式

  • buildpackagets - 编译ts编写的package

  1. startapp命令

    -c, --config <path> (-c或--config用来指定配置文件)-d, --define (用来指定传递给命令的参数使用","分割)
    
  2. buildapp命令

    -c, --config <path> (-c或--config用来指定配置文件)
    -d, --define (用来指定传递给命令的参数使用","分割)
    
  3. buildumd命令

    -c, --config <path> (-c或--config用来指定配置文件)
    -p, --packagename (打包文件的包名字)
    -d, --define (用来指定传递给命令的参数使用","分割)
    
  4. buildpackage命令

    -p, --srcpath <path> (-p--srcpath用来指定源代码的路径)
    
  5. buildpackagets命令

    -p, --srcpath <path> (-p--srcpath用来指定源代码的路径)
    

三、命令实现细节

整个工程重要的文件如下

babel.config.js // babel的配置文件
build.js // 工程的入口文件
buildapp.js // buildapp命令实现
buildpackage.js // buildpackage命令实现 
buildpackagets.js // buildpackagets命令实现
buildumd.js // buildumd命令实现
startapp.js // startapp命令实现
babel.config.js // 对babel的解析进行配置
gulpfile.js // 打package的gulp配置
package.json // package.json文件

下面列举一下package.json中的依赖,为了让大家看清楚所有的依赖,下面准备把依赖项进行分组

webpack相关

"webpack": "^4.20.2",
"webpack-cli": "^3.1.2",
"webpack-dev-server": "^3.1.9",
"webpack-merge": "^4.1.4",
"webpackbar": "^4.0.0",

loader相关

"babel-loader": "^8.0.4",
"cache-loader": "^4.1.0",
"css-loader": "3.2.0",
"csv-loader": "^3.0.2",
"ejs-loader": "^0.3.1",
"file-loader": "^2.0.0",
"less-loader": "^4.1.0",
"ts-loader": "^6.2.1",
"url-loader": "^2.1.0",
"xml-loader": "^1.2.1",
"yaml-loader": "^0.5.0",
"json-loader": "^0.5.7",

babel相关

"@babel/cli": "^7.1.5",
"@babel/core": "^7.1.6",
"@babel/plugin-proposal-class-properties": "^7.2.3",
"@babel/plugin-proposal-decorators": "^7.10.3",
"@babel/plugin-proposal-function-bind": "^7.0.0",
"@babel/plugin-proposal-optional-chaining": "^7.10.1",
"@babel/plugin-syntax-dynamic-import": "^7.0.0",
"@babel/plugin-transform-runtime": "^7.1.0",
"@babel/preset-env": "^7.1.6",
"@babel/preset-react": "^7.0.0",
"@babel/preset-typescript": "^7.8.3",
"@babel/runtime": "^7.1.5",

plugins相关

"clean-webpack-plugin": "^3.0.0",
"copy-webpack-plugin": "^4.5.3",
"fork-ts-checker-webpack-plugin": "^4.0.5",
"html-webpack-include-assets-plugin": "^1.0.6",
"html-webpack-plugin": "^4.0.0-beta.2",
"mini-css-extract-plugin": "^0.8.0",

样式相关

"less": "3.9.0",

其他

"commander": "^3.0.1",
"cross-env": "^5.2.0",
"gulp": "^3.9.1",
"rimraf": "^2.6.2",
"typescript": "^3.8.2"

因为我们提供的是一系列的命令,所以要在package.json中定义bin字段

"bin": { 
  "ctbuild": "./build.js"
},

在bin中定义的key是命令的名称,安装这个包的时候会在node_modules的.bin目录中创建ctbuild.sh,ctbuild.cmd这2个文件,值是一个js脚本路径,不管执行哪个文件都会执行相应的脚本文件,下面详细介绍一下关键的js文件

build.js

命令的入口文件,入口文件主要做的就是解析命令行参数,根据不同的参数来执行不同的操作,在这里我们使用的是commander(www.npmjs.com/package/com…)库来解析命令行参数

#!/usr/bin/env node

const program = require('commander');
const packageJson = require('./package');
// 命令的配置
const commandConfig = require('./commandConfig');

// 设置版本号
program.version(packageJson.version);

// 遍历所有的命令配置
Object.keys(commandConfig).forEach((command) => {  
    // 获取一个命令的配置信息
    const { alias, description, options = [], action } = commandConfig[command];  

    // 设置命令
    const commandHandler = program
        .command(command)
        .alias(alias)    
        .description(description)    
        .action(action);  

    // 设置命令的参数
    options.forEach(({ command: optionCommand, description: optionDescription }) => {   
      commandHandler.option(optionCommand, optionDescription);  
    });
});

// 设置命令行参数
program.parse(process.argv);

if (!program.args.length) {  
    program.help();
}

第一行是Shebang用于指明这个脚本文件的解释程序。了解了Shebang之后就可以理解,增加这一行是为了指定用node执行脚本文件

// commandConfig.js 命令的配置

// startapp
const startapp = require('./startapp');
const buildapp = require('./buildapp');
const buildumd = require('./buildumd');
const buildpackage = require('./buildpackage');
const buildpackagets = require('./buildpackagets');

/**
 * 将用","分割的define参数转换成key/value的map
 * @param {String} - define
 * @return Array
 */
function getDefineMap(define = '') {
  return define.split(',');
}

module.exports = {
  // dev环境
  startapp: {
    alias: 'start',
    description: 'run dev',
    options: [
      {
        command: '-c, --config <path>',
        description: 'ctbuild.config.js Configuration file path',
      },
      {
        command: '-d, --define <path>',
        description: 'custom params split ","',
      },
    ],
    action: (entry) => {
      console.log('startapp');
      const { config, define = '' } = entry;
      startapp.build({
        config,
        define: getDefineMap(define),
      });
    },
  },
  // 打包生产环境
  buildapp: {
    alias: 'build',
    description: 'build app',
    options: [
      {
        command: '-c, --config <path>',
        description: 'ctbuild.config.js Configuration file path',
      },
      {
        command: '-d, --define <path>',
        description: 'custom params split ","',
      },
    ],
    action: (entry) => {
      console.log('buildapp');
      // buildapp.build(entry.config, entry.define);
      const { config, define = '' } = entry;
      buildapp.build({
        config,
        define: getDefineMap(define),
      });
    },
  },
  // 打包生成环境的umd
  buildumd: {
    alias: 'umd',
    description: 'build app by umd',
    options: [
      {
        command: '-c, --config <path>',
        description: 'ctbuild.config.js Configuration file path',
      },
      {
        command: '-p, --packagename <name>',
        description: 'package name',
      },
      {
        command: '-d, --define <path>',
        description: 'custom params split ","',
      },
    ],
    action: (entry) => {
      console.log('buildumd');
      const { config, packagename, define = '' } = entry;
      buildumd.build({
        config,
        packagename,
        define: getDefineMap(define),
      });
    },
  },
  // 编译package
  buildpackage: {
    alias: 'package',
    description: 'build package',
    options: [
      {
        command: '-p, --srcpath <path>',
        description: 'build path',
      },
    ],
    action: (entry) => {
      console.log('buildpackage');
      buildpackage.build(entry.srcpath);
    },
  },
  // 编译package的ts版本
  buildpackagets: {
    alias: 'packagets',
    description: 'build packagets',
    options: [
      {
        command: '-p, --srcpath <path>',
        description: 'build path',
      },
    ],
    action: (entry) => {
      console.log('buildpackagets');
      buildpackagets.build(entry.srcpath);
    },
  },
};

以上就是命令的配置,key就是命令名称,alias命令的别名,description命令的简单描述,options是命令的参数,参数可以有多个,command: '-p, --srcpath <path>'这里的-p是命令的简写,--srcpath是参数的名称,是--srcpath的值,action是命令的实现,在entry中可以获取命令的参数,调用相应脚本的build方法实现功能。

startapp.js,buildapp.js,buildumd.js都是对webpack操作的封装,所以接下来需要对webpack的配置文件进行一个说明。

   github