father-build支持sourceMap

仅个人记录

简易版father-build支持sourceMap

rollup方式打包

js 复制代码
import { ModuleFormat, rollup, watch } from "rollup";
import signale from "signale";
import chalk from "chalk";
import getRollupConfig from "./getRollupConfig";
import { Dispose, IBundleOptions } from "./types";
import normalizeBundleOpts from "./normalizeBundleOpts";

interface IRollupOpts {
  cwd: string;
  rootPath?: string;
  entry: string | string[];
  type: ModuleFormat;
  log: (string) => void;
  bundleOpts: IBundleOptions;
  watch?: boolean;
  dispose?: Dispose[];
  importLibToEs?: boolean;
}

async function build(entry: string, opts: IRollupOpts) {
  const { cwd, rootPath, type, log, bundleOpts, importLibToEs, dispose } = opts;
  const rollupConfigs = getRollupConfig({
    cwd,
    rootPath: rootPath || cwd,
    type,
    entry,
    importLibToEs,
    bundleOpts: normalizeBundleOpts(entry, bundleOpts),
  });

  for (const rollupConfig of rollupConfigs) {
    if (opts.watch) {
      const watcher = watch([
        {
          ...rollupConfig,
          watch: {},
        },
      ]);
      await new Promise<void>((resolve) => {
        watcher.on("event", (event) => {
          // 每次构建完成都会触发 BUNDLE_END 事件
          // 当第一次构建完成或出错就 resolve
          if (event.code === "ERROR") {
            signale.error(event.error);
            resolve();
          } else if (event.code === "BUNDLE_END") {
            log(
              `${chalk.green(`Build ${type} success`)} ${chalk.gray(
                `entry: ${entry}`
              )}`
            );
            resolve();
          }
        });
      });
      process.once("SIGINT", () => {
        watcher.close();
      });
      dispose?.push(() => watcher.close());
    } else {
      const { output, ...input } = rollupConfig;
      const bundle = await rollup(input); // eslint-disable-line
      await bundle.write({
        ...output,
        sourcemap: true,
      }); // eslint-disable-line
      log(
        `${chalk.green(`Build ${type} success`)} ${chalk.gray(
          `entry: ${entry}`
        )}`
      );
    }
  }
}

export default async function(opts: IRollupOpts) {
  if (Array.isArray(opts.entry)) {
    const { entry: entries } = opts;
    for (const entry of entries) {
      await build(entry, opts);
    }
  } else {
    await build(opts.entry, opts);
  }
  if (opts.watch) {
    opts.log(chalk.magentaBright(`Rebuild ${opts.type} since file changed 👀`));
  }
}

babel打包方式

js 复制代码
import { join, extname, relative } from "path";
import { existsSync, readFileSync, statSync } from "fs";
import vfs from "vinyl-fs";
import signale from "signale";
import lodash from "lodash";
import rimraf from "rimraf";
import through from "through2";
import slash from "slash2";
import * as chokidar from "chokidar";
import * as babel from "@babel/core";
import gulpTs from "gulp-typescript";
import gulpLess from "gulp-less";
import gulpPlumber from "gulp-plumber";
import gulpIf from "gulp-if";
import chalk from "chalk";
import getBabelConfig from "./getBabelConfig";
import { Dispose, IBundleOptions } from "./types";
import * as ts from "typescript";
import * as sourcemaps from "gulp-sourcemaps";

interface IBabelOpts {
  cwd: string;
  rootPath?: string;
  type: "esm" | "cjs";
  target?: "browser" | "node";
  log?: (string) => void;
  watch?: boolean;
  dispose?: Dispose[];
  importLibToEs?: boolean;
  bundleOpts: IBundleOptions;
}

interface ITransformOpts {
  file: {
    contents: string;
    path: string;
  };
  type: "esm" | "cjs";
}

export default async function(opts: IBabelOpts) {
  const {
    cwd,
    rootPath,
    type,
    watch,
    dispose,
    importLibToEs,
    log,
    bundleOpts: {
      target = "browser",
      runtimeHelpers,
      extraBabelPresets = [],
      extraBabelPlugins = [],
      browserFiles = [],
      nodeFiles = [],
      nodeVersion,
      disableTypeCheck,
      cjs,
      lessInBabelMode,
    },
  } = opts;
  const srcPath = join(cwd, "src");
  const targetDir = type === "esm" ? "es" : "lib";
  const targetPath = join(cwd, targetDir);

  log(chalk.gray(`Clean ${targetDir} directory`));
  rimraf.sync(targetPath);

  function transform(opts: ITransformOpts, source) {
    const { file, type } = opts;
    const { opts: babelOpts, isBrowser } = getBabelConfig({
      target,
      type,
      typescript: true,
      runtimeHelpers,
      filePath: slash(relative(cwd, file.path)),
      browserFiles,
      nodeFiles,
      nodeVersion,
      lazy: cjs && cjs.lazy,
      lessInBabelMode,
      cwd,
    });
    if (importLibToEs && type === "esm") {
      babelOpts.plugins.push(require.resolve("../lib/importLibToEs"));
    }
    babelOpts.presets.push(...extraBabelPresets);
    babelOpts.plugins.push(...extraBabelPlugins);

    const relFile = slash(file.path).replace(`${cwd}/`, "");
    log(
      `Transform to ${type} for ${chalk[isBrowser ? "yellow" : "blue"](
        relFile
      )}`
    );

    const content = file.contents;
    const path = file.path;
    if (source) {
      return JSON.stringify(
        babel.transform(content, {
          ...babelOpts,
          sourceMaps: true,
          filename: file.path,
        }).map
      );
    }

    let filename = path.split("/").pop();
    filename = filename.replace(".ts", ".js");
    let code = babel.transform(content, {
      ...babelOpts,
      filename: path,
    }).code;

    code = code + "\n" + `//# sourceMappingURL=${filename}.map`;

    console.log(path);
    return code;

    return babel.transform(file.contents, {
      ...babelOpts,
      filename: file.path,
      // 不读取外部的babel.config.js配置文件,全采用babelOpts中的babel配置来构建
      configFile: false,
    }).code;
  }

  /**
   * tsconfig.json is not valid json file
   * https://github.com/Microsoft/TypeScript/issues/20384
   */
  function parseTsconfig(path: string) {
    const readFile = (path: string) => readFileSync(path, "utf-8");
    const result = ts.readConfigFile(path, readFile);
    if (result.error) {
      return;
    }
    const pkgTsConfig = result.config;
    if (pkgTsConfig.extends) {
      const rootTsConfigPath = slash(relative(cwd, pkgTsConfig.extends));
      const rootTsConfig = parseTsconfig(rootTsConfigPath);
      if (rootTsConfig) {
        const mergedConfig = {
          ...rootTsConfig,
          ...pkgTsConfig,
          compilerOptions: {
            ...rootTsConfig.compilerOptions,
            ...pkgTsConfig.compilerOptions,
          },
        };
        return mergedConfig;
      }
    }
    return pkgTsConfig;
  }

  function getTsconfigCompilerOptions(path: string) {
    const config = parseTsconfig(path);
    return config ? config.compilerOptions : undefined;
  }

  function getTSConfig() {
    const tsconfigPath = join(cwd, "tsconfig.json");
    const templateTsconfigPath = join(__dirname, "../template/tsconfig.json");
    console.log("cwd===", cwd, "rootPath===", rootPath);

    if (existsSync(tsconfigPath)) {
      return getTsconfigCompilerOptions(tsconfigPath) || {};
    }

    if (rootPath && existsSync(join(rootPath, "tsconfig.json"))) {
      return getTsconfigCompilerOptions(join(rootPath, "tsconfig.json")) || {};
    }
    return getTsconfigCompilerOptions(templateTsconfigPath) || {};
  }

  function createStream(src, sourcemap) {
    const tsConfig = getTSConfig();
    const babelTransformRegexp = disableTypeCheck ? /\.(t|j)sx?$/ : /\.jsx?$/;

    function isTsFile(path) {
      return /\.tsx?$/.test(path) && !path.endsWith(".d.ts");
    }

    function isTransform(path) {
      return babelTransformRegexp.test(path) && !path.endsWith(".d.ts");
    }

    return vfs
      .src(src, {
        allowEmpty: true,
        base: srcPath,
      })
      .pipe(watch ? gulpPlumber() : through.obj())
      .pipe(
        gulpIf((f) => !disableTypeCheck && isTsFile(f.path), gulpTs(tsConfig))
      )
      .pipe(
        gulpIf(
          (f) => lessInBabelMode && /\.less$/.test(f.path),
          gulpLess(lessInBabelMode || {})
        )
      )
      .pipe(
        gulpIf(
          (f) => isTransform(f.path),
          through.obj((file, env, cb) => {
            try {
              file.contents = Buffer.from(
                transform(
                  {
                    file,
                    type,
                  },
                  sourcemap
                )
              );
              // .jsx -> .js
              if (sourcemap) {
                file.path = file.path.replace(extname(file.path), ".js.map");
              } else {
                file.path = file.path.replace(extname(file.path), ".js");
              }
              cb(null, file);
            } catch (e) {
              signale.error(`Compiled faild: ${file.path}`);
              console.log(e);
              cb(null);
            }
          })
        )
      )
      .pipe(vfs.dest(targetPath));
  }

  return new Promise((resolve) => {
    const patterns = [
      join(srcPath, "**/*"),
      `!${join(srcPath, "**/fixtures{,/**}")}`,
      `!${join(srcPath, "**/demos{,/**}")}`,
      `!${join(srcPath, "**/__test__{,/**}")}`,
      `!${join(srcPath, "**/__tests__{,/**}")}`,
      `!${join(srcPath, "**/*.mdx")}`,
      `!${join(srcPath, "**/*.md")}`,
      `!${join(srcPath, "**/*.+(test|e2e|spec).+(js|jsx|ts|tsx)")}`,
      `!${join(srcPath, "**/tsconfig{,.*}.json")}`,
      `!${join(srcPath, ".umi{,-production,-test}{,/**}")}`,
    ];
    createStream(patterns, false).on("end", () => {
      if (watch) {
        log(
          chalk.magenta(
            `Start watching ${slash(srcPath).replace(
              `${cwd}/`,
              ""
            )} directory...`
          )
        );
        const watcher = chokidar.watch(patterns, {
          ignoreInitial: true,
        });

        const files = [];
        function compileFiles() {
          while (files.length) {
            createStream(files.pop());
          }
        }

        const debouncedCompileFiles = lodash.debounce(compileFiles, 1000);
        watcher.on("all", (event, fullPath) => {
          const relPath = fullPath.replace(srcPath, "");
          log(
            `[${event}] ${slash(join(srcPath, relPath)).replace(`${cwd}/`, "")}`
          );
          if (!existsSync(fullPath)) return;
          if (statSync(fullPath).isFile()) {
            if (!files.includes(fullPath)) files.push(fullPath);
            debouncedCompileFiles();
          }
        });
        process.once("SIGINT", () => {
          watcher.close();
        });
        dispose?.push(() => watcher.close());
      }
      resolve();
    });
    createStream(patterns, true);
  });
}
相关推荐
by————组态6 分钟前
低代码 Web 组态
前端·人工智能·物联网·低代码·数学建模·组态
拉不动的猪10 分钟前
UniApp金融理财产品项目简单介绍
前端·javascript·面试
菜冬眠。14 分钟前
uni-app/微信小程序接入腾讯位置服务地图选点插件
前端·微信小程序·uni-app
jayson.h14 分钟前
pdf解密程序
java·前端·pdf
萌萌哒草头将军16 分钟前
😡😡😡早知道有这两个 VueRouter 增强插件,我还加什么班!🚀🚀🚀
前端·vue.js·vue-router
苏卫苏卫苏卫40 分钟前
【Vue】案例——To do list:
开发语言·前端·javascript·vue.js·笔记·list
0509151 小时前
测试基础笔记第四天(html)
前端·笔记·html
聪明的墨菲特i2 小时前
React与Vue:哪个框架更适合入门?
开发语言·前端·javascript·vue.js·react.js
时光少年2 小时前
Android 副屏录制方案
android·前端
拉不动的猪2 小时前
v2升级v3需要兼顾的几个方面
前端·javascript·面试