如何设计一款支持webpack5和vite构建工具?

941 阅读3分钟

初衷

当你看到这个标题的时候,可能会觉得很奇怪,为什么需要支持webpack和vite两种构建工具?

确实,对于很多业务场景,webpack和vite都能解决问题,但是相对于一些比较老的项目,vite可能就显得相对乏力了,在进行webpack迁移至vite项目的过程中,经常会遇到很多奇奇怪怪的报错,并且无从下手,这很令人头疼。vite极速的构建效率,可以解决大型项目webpack构建缓慢带来的困扰。

webpack发展历史时长,有丰富的生态环境,随着不断的优化,其稳定性及构建性能都是相当优秀;vite在开发环境利用浏览器Es module特性和esbuild为开发提供了极速的响应,在生产环境使用rollup构建,在treeShakeing还是有很大优势。

结合两者的特性和具体的业务场景:

  1. 对于能使用vite构建项目,推荐用vite进行构建;
  2. 历史原因,迁移至vite困难,或者vite不能解决的问题,使用webpack5进行构建。

一、如何设计?

1. 设计目标

  • 支持webpack和vite两种构建方式
  • 支持React和Vue3两种框架应用开发

2. 基础准备

  • 掌握webpack和vite开发环境和生产环境的基本配置。
  • 熟悉React和Vue3常用的babel插件、vite插件,根据不同构建使用对应框架配置。

3. 设计原理

image.png

  1. 设计cli工具提供dev和build构建命令。
  2. 从项目的一份配置文件读取基本配置。
  3. 根据配置文件或cli命令,进行webpack或vite构建。

二、设计配置文件读取功能

  1. 判断项目根目录是否存在配置文件。
  2. 读取项目根目录的hull.conf.ts或hull.conf.js。
  3. 支持配置文件导出一个对象或者一个函数模块。

1. 判断是否存在某个文件

import { existsSync } from 'fs';
import { join } from 'path';

interface IGetExistFile{
  appDirectory: string;
  files?: string[];
  returnRelative?: boolean;
}

interface IGetExistFileRe{
  isOk: boolean;
  absFilePath: string;
}

export function getExistFile({ appDirectory, files = [] }: IGetExistFile): IGetExistFileRe {
  for (const file of files) {
    const absFilePath = join(appDirectory, file);
    if (existsSync(absFilePath)) {
      return {
        isOk: true,
        absFilePath,
      };
    }
  }
  return {
    isOk: false,
    absFilePath: '',
  };
}

2.读取项目根目录的hull.conf.ts或hull.conf.js。

由于在node环境需要支持ts、es+语法,所以需要@babel/register实现。

import { join } from 'path';

import babel, { IGetBabelOptions } from '@hulljs/babel-preset-hull-app';

const slash = (input: string) => {
  const isExtendedLengthPath = /^\\\\\?\\/.test(input);

  if (isExtendedLengthPath) {
    return input;
  }
  return input.replace(/\\/g, '/');
};

export const isDefault = <T>(obj: any): T => obj.default || obj;

export const registerNodeCiBabel = function(appDirectory: string, only: string[]): void {
  const babelOptions: IGetBabelOptions = {
    isTypeScript: true,
    isProduction: true,
  };
  require('@babel/register')({
    presets: [[babel, babelOptions]],
    extensions: ['.es6', '.es', '.jsx', '.js', '.mjs', '.ts', '.tsx'],
    only: only.map((file) => slash(join(appDirectory, file))),
    babelrc: false,
    cache: false,
  });
};

export const getFileExport = async<T>(appDirectory: string, files: string[]): Promise<T> => {
  try {
    registerNodeCiBabel(appDirectory, files);
    const { isOk, absFilePath } = getExistFile({ appDirectory, files: files, returnRelative: false });
    if(isOk){
      return isDefault<T>(await import(absFilePath));
    }else{
      throw Error(`${appDirectory} isn't has any ${files.join('、')}`);
    }
  } catch (error: any) {
    throw Error(error);
  }
};

3. 读取配置文件

export const CONFIG_FILES = [
  'hull.conf.js',
  'hull.conf.ts',
];
const configFnOrObj = await getFileExport<IngetUserConfigRe>(fs.realpathSync(process.cwd()), CONFIG_FILES);

let config;
if(typeof configFnOrObj === 'function'){
  const resConf = configFnOrObj(env);
  config = resConf;
} else if(typeof configFnOrObj === 'object'){
  config = configFnOrObj;
}else{
  log.error('hull.conf export is a funtion or object!');
  return;
}

三、根据不同构建选择不同的配置。

  1. 读取项目配置配置文件
  2. 校验配置文件、生成默认值
  3. webpack构建则生成webpack配置
  4. vite构建生成vite配置 image.png 具体实现:github.com/luoguoxiong…

四、CLI设计

后续更新

最后

项目开源地址:github.com/luoguoxiong…

详细文档:hulljs.netlify.app

欢迎顺手点个star~ 十分感谢!