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

273 阅读3分钟

buildpackage.js

const { spawn } = require('child_process');
const path = require('path');
// 运行脚本的路径
const runtimePath = process.cwd();
// 脚本所在的路径
const codePath = __dirname;
const commandPath = path.join(codePath, 'node_modules', '.bin', path.sep);
const { getEnv } = require('./util');

// buildpackage生成的目录名称
const generateDirName = 'lib';
// buildpackage原始名称
const srcDirName = 'src';

// 代码输出路径
const outputPath = path.join(runtimePath, generateDirName);

// 代码编译路径
let compilePath;

let index = 0;
// buildpackage的所有任务
const tasks = [
  // 清除生成目录
  clearTask,
  // babel转换,转换js
  babelTask,
  // 样式
  gulpTask,
];

/**
 * clearTask
 * 清除输出目录
 * @return {Promise}
 */
function clearTask() {
  return new Promise((resolve, reject) => {
    const command = process.platform === 'win32' ? `rimraf.cmd` : `rimraf`;
    const rimrafProcess = spawn(command, [outputPath], {
      cwd: codePath,
      encoding: 'utf-8',
      env: getEnv(commandPath),
    });

    rimrafProcess.stdout.on('data', (data) => {
      console.log(`stdout: ${data}`);
    });

    rimrafProcess.stderr.on('data', (data) => {
      console.log(`stderr: ${data}`);
    });

    rimrafProcess.on('close', (code) => {
      console.log(`rimrafClose:${code}`);
      resolve();
    });
  });
}

/**
 * babelTask
 * 转换src到lib
 * @return {Promise}
 */
function babelTask() {
  return new Promise((resolve, reject) => {
    const command = process.platform === 'win32' ? `babel.cmd` : `babel`;
    const babelProcess = spawn(
      command,
      [
        // 编译的目录
        compilePath,
        '-d',
        // 输出的目录
        outputPath,
        '--ignore',
        '__tests__',
      ],
      {
        cwd: codePath,
        encoding: 'utf-8',
        env: getEnv(commandPath),
      },
    );

    babelProcess.stdout.on('data', (data) => {
      console.log(`stdout: ${data}`);
    });

    babelProcess.stderr.on('data', (data) => {
      console.log(`stderr: ${data}`);
    });

    babelProcess.on('close', (code) => {
      console.log(`babelClose:${code}`);
      resolve();
    });
  });
}

/**
 * gulpTask
 * @return {Promise}
 */
function gulpTask() {
  return new Promise((resolve, reject) => {
    const command = process.platform === 'win32' ? `gulp.cmd` : `gulp`;
    const gulpProcess = spawn(
      command,
      [
        '--outputpath',
        // 输出路径
        path.join(outputPath, path.sep),
        '--compilepath',
        // 编译目录
        path.join(compilePath, path.sep),
      ],
      {
        cwd: codePath,
        encoding: 'utf-8',
        env: getEnv(commandPath),
      },
    );

    gulpProcess.stdout.on('data', (data) => {
      console.log(`stdout: ${data}`);
    });

    gulpProcess.stderr.on('data', (data) => {
      console.log(`stderr: ${data}`);
    });

    gulpProcess.on('close', (code) => {
      console.log(`gulpTaskClose:${code}`);
      resolve();
    });
  });
}

/**
 * loopTask
 * @return {Promise}
 */
function loopTask() {
  return new Promise((resolve, reject) => {
    if (index >= tasks.length) {
      resolve();
    } else {
      const task = tasks[index++];
      if (task) {
        task()
          .then(() => {
            loopTask().then(() => {
              resolve();
            });
          })
          .catch((error) => {
            reject(error);
          });
      } else {
        reject();
      }
    }
  });
}

module.exports = {
  /**
   * build
   * @param {String} - srcPath
   */
  build(srcPath) {
    if (srcPath) {
      // 指定了编译目录

      if (path.isAbsolute(srcPath)) {
        // 是绝对路径
        compilePath = srcPath;
      } else {
        // 是相对路径
        compilePath = path.join(runtimePath, srcPath);
      }
    } else {
      // 没有指定编译目录
      compilePath = path.join(runtimePath, srcDirName);
    }

    loopTask()
      .then(() => {
        console.log('finish');
        process.exit();
      })
      .catch((error) => {
        console.log(error);
      });
  },
};

gulpfile.js

const path = require('path');
const gulp = require('gulp');
const uglify = require('gulp-uglify');
const sourceMap = require('gulp-sourcemaps');

const copyexts = [
  'less',
  'css',
  'svg',
  'jpg',
  'jpeg',
  'gif',
  'png',
  'bmp',
  'json',
  'eot',
  'woff',
  'ttf',
];
const commandArgs = require('./commandArgs');

const argsMap = commandArgs.initCommandArgs();
const outputpath = argsMap.get('--outputpath')[0];
const compilePath = argsMap.get('--compilepath')[0];

/**
 * copy
 */
gulp.task('copy', () => {
  for (let i = 0; i < copyexts.length; i++) {
    gulp.src(path.join(compilePath, '**', `*.${copyexts[i]}`)).pipe(gulp.dest(outputpath));
  }
});

/**
 * 压缩
 */
gulp.task('minjs', () => {
  return gulp
    .src([
      path.join(outputpath, '**', '*.js'),
      path.join(outputpath, '**', '*.jsx'),
    ])
    .pipe(sourceMap.init())
    .pipe(uglify())
    .pipe(sourceMap.write('.'))
    .pipe(gulp.dest(outputpath));
});

gulp.task('default', ['copy', 'minjs']);

buildpackage.js是用来编译npm包(libary)的,现在市面上用来打包libary的工具很多,rollup、gulp和webpack等都可以去打包libary,现在比较流行的方式是使用rollup来打包libary,我这里使用了比较老的gulp来进行打包,可能是这个东西我用的时间长了,下面说一下libary的种类,我个人理解libary应该分为2种,第一种是nodejs的libary,另一种是browser的libary,nodejs的libary目前为止是不需要编译的,因为nodejs现在已经支持es6了,除非你用typescript编写了nodejs的包,如果不是应该不用编译,那其实我们需要编译的就是browser的libary,举个例子moment,lodash,antd这些都是browser的libary,browser包里面最主要的就是js、jsx、css、less和一些图片或者json等资源文件,我们只需要对js和jsx文件进行处理就可以,其他类型的文件不需要进行处理(之后会说为什么不需要处理),因为js和jsx文件可能使用了es7~es9的新语法,所以我们需要使用babel命令对这些文件进行编译(具体请参看上方代码),第二步就是使用gulp对其他文件进行一个原封不动的复制操作,应该说执行完这两个操作之后,会生成一个lib目录,lib里面的文件结构和src里面的文件结构是相同的,只是jsx文件变成了同名的js文件,js文件里面的内容是被babel进行转换后的结果,执行babel转换的时候会读取babel.config.js文件里的配置,其他的文件都是按照原样输出。这里需要说明一个问题,就是如果这个libery里面有样式(不管是css还是less)都不能在js或jsx里面进行import的引入,因为babel转换js和jsx的时候遇到import 'xxx.less'这种引入是解析不了的,同样也不能引入import 'xxx.png'或import 'xxx.json'这些资源文件,只能引入js或jsx文件。这些资源文件需要在宿主工程中进行引入,利用宿主工程中的webpack的配置进行解析,这就是为什么我们在使用antd的时候会在宿主工程中引入import 'antd/dist/css/antd.min.css'的原因了。

   github