chore: 整理 create-app

This commit is contained in:
winixt 2025-09-08 18:09:16 +08:00
parent f29492ebe0
commit fd811c5269
26 changed files with 119 additions and 737 deletions

View File

@ -1,30 +0,0 @@
export default {
pkgs: [
'create-fes-app',
'fes',
'fes-compiler',
'fes-preset-built-in',
'fes-builder-vite',
'fes-builder-webpack',
'fes-runtime',
'fes-utils',
'fes-plugin-access',
'fes-plugin-enums',
'fes-plugin-icon',
'fes-plugin-jest',
'fes-plugin-layout',
'fes-plugin-locale',
'fes-plugin-model',
'fes-plugin-monaco-editor',
'fes-plugin-qiankun',
'fes-plugin-request',
'fes-plugin-sass',
'fes-plugin-vuex',
'fes-plugin-pinia',
'fes-plugin-windicss',
'fes-plugin-watermark',
'fes-plugin-login',
'fes-plugin-swc',
],
copy: [],
};

View File

@ -17,23 +17,20 @@
"fes" "fes"
], ],
"sideEffects": false, "sideEffects": false,
"main": "lib/index.js", "main": "dist/index.js",
"bin": { "bin": {
"create-fes-app": "bin/create-fes-app.js" "create-fes-app": "bin/create-fes-app.js"
}, },
"files": [ "files": [
"bin", "bin",
"lib", "dist",
"templates/**/*" "templates/**/*"
], ],
"publishConfig": { "publishConfig": {
"access": "public" "access": "public"
}, },
"dependencies": { "dependencies": {
"@fesjs/utils": "^3.0.3", "fs-extra": "^11.3.1",
"fs-extra": "^11.1.1",
"inquirer": "^7.3.3",
"readline": "^1.3.0",
"validate-npm-package-name": "^3.0.0" "validate-npm-package-name": "^3.0.0"
} }
} }

View File

@ -9,8 +9,6 @@ import AppGenerator from './generator/App';
import PluginGenerator from './generator/Plugin'; import PluginGenerator from './generator/Plugin';
import { clearConsole } from './utils'; import { clearConsole } from './utils';
v;
export default async ({ cwd, args }) => { export default async ({ cwd, args }) => {
if (args.proxy) { if (args.proxy) {
process.env.HTTP_PROXY = args.proxy; process.env.HTTP_PROXY = args.proxy;

View File

@ -1,16 +0,0 @@
# http://editorconfig.org
root = true
lib
[*]
charset = utf-8
indent_style = space
indent_size = 4
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
insert_final_newline = false
trim_trailing_whitespace = false

View File

@ -1,21 +0,0 @@
module.exports = {
extends: ['@webank/eslint-config-webank/vue.js'],
globals: {
// 这里填入你的项目需要的全局变量
// 这里值为 false 表示这个全局变量不允许被重新赋值,比如:
//
// Vue: false
__DEV__: false,
},
rules: {
'vue/comment-directive': 'off',
'global-require': 'off',
'import/no-unresolved': 'off',
'no-restricted-syntax': 'off',
'no-undefined': 'off',
'vue/valid-template-root': 'off',
},
env: {
jest: true,
},
};

View File

@ -1,2 +1,8 @@
.DS_Store
node_modules node_modules
lib lib
dist
npm-debug.log

View File

@ -1,4 +0,0 @@
{
"singleQuote": true,
"trailingComma": "none"
}

View File

@ -0,0 +1 @@
# Fes.js 插件

View File

@ -1,3 +0,0 @@
module.exports = {
copy: ['runtime'],
};

View File

@ -0,0 +1,36 @@
// eslint.config.js
import antfu from '@antfu/eslint-config';
export default await antfu({
stylistic: {
indent: 4,
quotes: 'single',
semi: 'always',
ignores: ['*.yaml'],
},
typescript: true,
vue: true,
rules: {
'curly': ['error', 'multi-line'],
'vue/block-order': [
'error',
{
order: ['template', 'script', 'style'],
},
],
'style/member-delimiter-style': [
'error',
{
multiline: {
delimiter: 'semi',
requireLast: true,
},
singleline: {
delimiter: 'semi',
requireLast: false,
},
multilineDetection: 'brackets',
},
],
},
});

View File

@ -1,17 +1,17 @@
{ {
"name": "fes-plugin-{{{name}}}", "name": "fes-plugin-{{{name}}}",
"version": "3.0.0", "version": "1.0.0",
"description": "一个fes.js插件", "description": "一个fes.js插件",
"main": "lib/index.js", "main": "dist/index.mjs",
"module": "dist/index.mjs",
"files": [ "files": [
"lib", "dist",
"README.md", "README.md",
"types.d.ts" "types.d.ts"
], ],
"scripts": { "scripts": {
"dev": "node scripts/build.js --watch", "dev": "tsup --watch --sourcemap",
"build": "node scripts/build.js", "build": "tsup"
"lint": "eslint -c ./.eslintrc.js --ext .js,.jsx,.vue,.ts"
}, },
"license": "MIT", "license": "MIT",
"keywords": [ "keywords": [
@ -19,31 +19,14 @@
"dependencies": { "dependencies": {
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.23.2", "@antfu/eslint-config": "^5.2.2",
"@babel/preset-env": "^7.23.2", "tsup": "^8.5.0",
"@webank/eslint-config-webank": "1.2.7", "fs-extra": "^11.3.1",
"chalk": "^4.1.2", "eslint": "^9.34.0",
"chokidar": "^3.5.2", "typescript": "^5.9.2"
"deepmerge": "^4.2.2",
"fs-extra": "^11.1.1",
"husky": "^4.3.0",
"lint-staged": "^10.4.0",
"yargs-parser": "^20.2.9"
}, },
"peerDependencies": { "peerDependencies": {
"@fesjs/fes": "^3.0.0", "@fesjs/fes": "^4.0.0",
"vue": "^3.2.47" "vue": "^3.5.20",
},
"lint-staged": {
"*.{js,jsx,vue,ts}": [
"eslint --format=codeframe"
]
},
"husky": {
"hooks": {
"pre-commit": "lint-staged",
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
} }
},
"typings": "./types.d.ts"
} }

View File

@ -1,149 +0,0 @@
// 关闭 import 规则
const fs = require('node:fs');
const path = require('node:path');
const chalk = require('chalk');
const chokidar = require('chokidar');
const merge = require('deepmerge');
const fse = require('fs-extra');
const argv = require('yargs-parser')(process.argv.slice(2));
const pkg = require('../package.json');
const compiler = require('./compiler');
const randomColor = require('./randomColor');
const ESM_OUTPUT_DIR = 'es';
const NODE_CJS_OUTPUT_DIR = 'lib';
const SOURCE_DIR = 'src';
const CONFIG_FILE_NAME = 'build.config.js';
const GLOBAL_CONFIG_PATH = path.join(process.cwd(), CONFIG_FILE_NAME);
const DEFAULT_CONFIG = {
target: 'node',
};
function genLog(pkgName) {
return (msg) => {
console.log(`${randomColor(pkgName)}: ${msg}`);
};
}
function genShortPath(filePath) {
const codePath = filePath.split(`/${SOURCE_DIR}/`)[1];
return `${SOURCE_DIR}/${codePath}`;
}
function getPkgSourcePath() {
return path.join(process.cwd(), SOURCE_DIR);
}
function getOutputPath(config) {
if (config.target === 'browser') {
return path.join(process.cwd(), ESM_OUTPUT_DIR);
}
return path.join(process.cwd(), NODE_CJS_OUTPUT_DIR);
}
function getGlobalConfig() {
if (fs.existsSync(GLOBAL_CONFIG_PATH)) {
const userConfig = require(GLOBAL_CONFIG_PATH);
return merge(DEFAULT_CONFIG, userConfig);
}
return DEFAULT_CONFIG;
}
function cleanBeforeCompilerResult(log) {
const esmOutputDir = path.join(process.cwd(), ESM_OUTPUT_DIR);
const cjsOutputDir = path.join(process.cwd(), NODE_CJS_OUTPUT_DIR);
if (fs.existsSync(esmOutputDir)) {
log(chalk.gray(`Clean ${ESM_OUTPUT_DIR} directory`));
fse.removeSync(esmOutputDir);
}
if (fs.existsSync(cjsOutputDir)) {
log(chalk.gray(`Clean ${NODE_CJS_OUTPUT_DIR} directory`));
fse.removeSync(cjsOutputDir);
}
}
function transformFile(filePath, outputPath, config, log) {
if (/\.[jt]sx?$/.test(path.extname(filePath))) {
try {
const code = fs.readFileSync(filePath, 'utf-8');
const shortFilePath = genShortPath(filePath);
const transformedCode = compiler(code, config);
const type = config.target === 'browser' ? ESM_OUTPUT_DIR : NODE_CJS_OUTPUT_DIR;
log(`Transform to ${type} for ${config.target === 'browser' ? chalk.yellow(shortFilePath) : chalk.blue(shortFilePath)}`);
fse.outputFileSync(outputPath, transformedCode);
}
catch (error) {
console.error(error);
}
}
else {
fse.copySync(filePath, outputPath);
}
}
function compilerPkg(codeDir, outputDir, config, log) {
const files = fs.readdirSync(codeDir);
files.forEach((file) => {
const filePath = path.join(codeDir, file);
const outputFilePath = path.join(outputDir, file);
const fileStats = fs.lstatSync(filePath);
if (config.copy.includes(file)) {
fse.copySync(filePath, outputFilePath);
}
else if (fileStats.isDirectory(filePath) && !/__tests__/.test(file)) {
fse.ensureDirSync(outputFilePath);
compilerPkg(filePath, outputFilePath, config, log);
}
else if (fileStats.isFile(filePath)) {
transformFile(filePath, outputFilePath, config, log);
}
});
}
function watchFile(dir, outputDir, config, log) {
chokidar
.watch(dir, {
ignoreInitial: true,
})
.on('all', (event, changeFile) => {
// 修改的可能是一个目录,一个文件,一个需要 copy 的文件 or 目录
const shortChangeFile = genShortPath(changeFile);
const outputPath = changeFile.replace(dir, outputDir);
const stat = fs.lstatSync(changeFile);
log(`[${event}] ${shortChangeFile}`);
if (config.resolveCopy.some(item => changeFile.startsWith(item))) {
fse.copySync(changeFile, outputPath);
}
else if (stat.isFile()) {
transformFile(changeFile, outputPath, config, log);
}
else if (stat.isDirectory()) {
compilerPkg(changeFile, outputPath, config);
}
});
}
function main() {
const sourceCodeDir = getPkgSourcePath();
const pkgName = pkg.name;
if (fs.existsSync(sourceCodeDir)) {
const log = genLog(pkgName);
const config = getGlobalConfig();
const outputDir = getOutputPath(config);
cleanBeforeCompilerResult(log);
const type = config.target === 'browser' ? ESM_OUTPUT_DIR : NODE_CJS_OUTPUT_DIR;
log(chalk.white(`Build ${type} with babel`));
compilerPkg(sourceCodeDir, outputDir, config, log);
if (argv.watch) {
log(chalk.magenta(`Start watch ${SOURCE_DIR} directory...`));
watchFile(sourceCodeDir, outputDir, config, log);
}
}
}
main();

View File

@ -1,52 +0,0 @@
// 关闭 import 规则
/* eslint import/no-extraneous-dependencies: 0 */
const babel = require('@babel/core');
function transform(code, options) {
const result = babel.transformSync(code, options);
return result.code;
}
function transformNodeCode(code) {
return transform(code, {
presets: [
[
'@babel/preset-env',
{
modules: 'cjs',
targets: { node: '12' },
},
],
],
});
}
function transformBrowserCode(code) {
// 因为 fes.js 在生产打包的时候,会处理所有的 node_modules 下的文件,确保不会丢失必要 polyfill
// 因此这里不对 polyfill 进行处理,避免全局污染
return transform(code, {
presets: [
[
'@babel/preset-env',
{
modules: false,
useBuiltIns: false,
targets: { chrome: '64' },
},
],
],
});
}
function compiler(code, config) {
if (!config.target || config.target === 'node') {
return transformNodeCode(code);
}
if (config.target === 'browser') {
return transformBrowserCode(code);
}
throw new Error(`config target error: ${config.target}, only can use 'node' and 'browser'`);
}
module.exports = compiler;

View File

@ -1,35 +0,0 @@
/* eslint import/no-extraneous-dependencies: 0 */
const chalk = require('chalk');
const colors = [
'red',
'green',
'yellow',
'blue',
'magenta',
'cyan',
'gray',
'redBright',
'greenBright',
'yellowBright',
'blueBright',
'magentaBright',
'cyanBright',
];
let index = 0;
const cache = {};
module.exports = function (pkg) {
if (!cache[pkg]) {
const color = colors[index];
const str = chalk[color].bold(pkg);
cache[pkg] = str;
if (index === colors.length - 1) {
index = 0;
} else {
index += 1;
}
}
return cache[pkg];
};

View File

@ -1,5 +1,5 @@
import { join } from 'path'; import { join } from 'node:path';
import { readFileSync } from 'fs'; import { readFileSync } from 'node:fs';
import { name } from '../package.json'; import { name } from '../package.json';
const namespace = 'plugin-{{{name}}}'; const namespace = 'plugin-{{{name}}}';

View File

@ -0,0 +1,15 @@
{
"compilerOptions": {
"target": "ES2022",
"rootDir": "./src",
"module": "ESNext",
"moduleResolution": "Bundler",
"strict": true,
"outDir": "./dist",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"skipLibCheck": true
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}

View File

@ -0,0 +1,15 @@
import { copySync } from 'fs-extra/esm';
import { defineConfig } from 'tsup';
export default defineConfig({
entry: ['src/index.ts'],
splitting: false,
sourcemap: false,
clean: true,
dts: false,
shims: true,
format: ['esm'],
onSuccess() {
copySync('src/runtime', 'dist/runtime');
},
});

View File

@ -1,10 +0,0 @@
import {} from '@fesjs/fes';
declare module "@fesjs/fes" {
interface PluginBuildConfig {
}
interface PluginRuntimeConfig {
}
}

View File

@ -18,7 +18,6 @@ import yParser from 'yargs-parser';
import changePort from './changePort'; import changePort from './changePort';
import compatESModuleRequire from './compatESModuleRequire'; import compatESModuleRequire from './compatESModuleRequire';
import delay from './delay'; import delay from './delay';
import Generator from './Generator';
import getAppPath from './getAppPath'; import getAppPath from './getAppPath';
import getHostName from './getHostName'; import getHostName from './getHostName';
import getPort from './getPort'; import getPort from './getPort';
@ -53,7 +52,6 @@ export {
changePort, changePort,
compatESModuleRequire, compatESModuleRequire,
delay, delay,
Generator,
getAppPath, getAppPath,
getHostName, getHostName,
getPort, getPort,

49
pnpm-lock.yaml generated
View File

@ -325,18 +325,9 @@ importers:
packages/create-fes-app: packages/create-fes-app:
dependencies: dependencies:
'@fesjs/utils':
specifier: ^3.0.3
version: 3.0.3
fs-extra: fs-extra:
specifier: ^11.1.1 specifier: ^11.3.1
version: 11.3.1 version: 11.3.1
inquirer:
specifier: ^7.3.3
version: 7.3.3
readline:
specifier: ^1.3.0
version: 1.3.0
validate-npm-package-name: validate-npm-package-name:
specifier: ^3.0.0 specifier: ^3.0.0
version: 3.0.0 version: 3.0.0
@ -5009,10 +5000,6 @@ packages:
resolution: {integrity: sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==} resolution: {integrity: sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==}
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
inquirer@7.3.3:
resolution: {integrity: sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==}
engines: {node: '>=8.0.0'}
inquirer@8.2.5: inquirer@8.2.5:
resolution: {integrity: sha512-QAgPDQMEgrDssk1XiwwHoOGYF9BAbUcc1+j+FhEvaOt8/cKRqyLn0U5qA6F74fGhTMGxf92pOvPBeh29jQJDTQ==} resolution: {integrity: sha512-QAgPDQMEgrDssk1XiwwHoOGYF9BAbUcc1+j+FhEvaOt8/cKRqyLn0U5qA6F74fGhTMGxf92pOvPBeh29jQJDTQ==}
engines: {node: '>=12.0.0'} engines: {node: '>=12.0.0'}
@ -6844,9 +6831,6 @@ packages:
resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==}
engines: {node: '>= 14.18.0'} engines: {node: '>= 14.18.0'}
readline@1.3.0:
resolution: {integrity: sha512-k2d6ACCkiNYz222Fs/iNze30rRJ1iIicW7JuX/7/cozvih6YCkFZH+J6mAFDVgv0dRBaAyr4jDqC95R2y4IADg==}
redent@3.0.0: redent@3.0.0:
resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==}
engines: {node: '>=8'} engines: {node: '>=8'}
@ -7003,10 +6987,6 @@ packages:
run-parallel@1.2.0: run-parallel@1.2.0:
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
rxjs@6.6.7:
resolution: {integrity: sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==}
engines: {npm: '>=2.0.0'}
rxjs@7.8.2: rxjs@7.8.2:
resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==}
@ -7566,9 +7546,6 @@ packages:
ts-interface-checker@0.1.13: ts-interface-checker@0.1.13:
resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==}
tslib@1.14.1:
resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==}
tslib@2.8.1: tslib@2.8.1:
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
@ -13009,22 +12986,6 @@ snapshots:
ini@4.1.1: ini@4.1.1:
optional: true optional: true
inquirer@7.3.3:
dependencies:
ansi-escapes: 4.3.2
chalk: 4.1.2
cli-cursor: 3.1.0
cli-width: 3.0.0
external-editor: 3.1.0
figures: 3.2.0
lodash: 4.17.21
mute-stream: 0.0.8
run-async: 2.4.1
rxjs: 6.6.7
string-width: 4.2.3
strip-ansi: 6.0.1
through: 2.3.8
inquirer@8.2.5: inquirer@8.2.5:
dependencies: dependencies:
ansi-escapes: 4.3.2 ansi-escapes: 4.3.2
@ -14950,8 +14911,6 @@ snapshots:
readdirp@4.1.2: {} readdirp@4.1.2: {}
readline@1.3.0: {}
redent@3.0.0: redent@3.0.0:
dependencies: dependencies:
indent-string: 4.0.0 indent-string: 4.0.0
@ -15124,10 +15083,6 @@ snapshots:
dependencies: dependencies:
queue-microtask: 1.2.3 queue-microtask: 1.2.3
rxjs@6.6.7:
dependencies:
tslib: 1.14.1
rxjs@7.8.2: rxjs@7.8.2:
dependencies: dependencies:
tslib: 2.8.1 tslib: 2.8.1
@ -15717,8 +15672,6 @@ snapshots:
ts-interface-checker@0.1.13: {} ts-interface-checker@0.1.13: {}
tslib@1.14.1: {}
tslib@2.8.1: {} tslib@2.8.1: {}
tsup@8.5.0(@swc/core@1.13.5)(jiti@2.5.1)(postcss@8.5.6)(typescript@5.9.2)(yaml@2.8.1): tsup@8.5.0(@swc/core@1.13.5)(jiti@2.5.1)(postcss@8.5.6)(typescript@5.9.2)(yaml@2.8.1):

View File

@ -1,198 +0,0 @@
import fs from 'node:fs';
import path from 'node:path';
import process from 'node:process';
import chalk from 'chalk';
import chokidar from 'chokidar';
import merge from 'deepmerge';
import fse from 'fs-extra';
import yargsParser from 'yargs-parser';
import buildConfig from '../build.config.js';
import compiler from './compiler.mjs';
import randomColor from './randomColor.mjs';
const argv = yargsParser(process.argv.slice(2));
const ESM_OUTPUT_DIR = 'es';
const NODE_CJS_OUTPUT_DIR = 'lib';
const SOURCE_DIR = 'src';
const CONFIG_FILE_NAME = 'build.config.js';
const GLOBAL_CONFIG_PATH = path.join(process.cwd(), CONFIG_FILE_NAME);
const DEFAULT_CONFIG = {
target: 'node',
pkgs: [],
copy: [],
};
const PACKAGE_PATH = path.join(process.cwd(), './packages');
function genLog(pkgName) {
return (msg) => {
console.log(`${randomColor(pkgName)}: ${msg}`);
};
}
function getPkgPath(pkgName) {
return path.join(PACKAGE_PATH, pkgName);
}
function genShortPath(filePath) {
// 处理 windows 的路径
filePath = filePath.replace(/\\/g, '/');
const codePath = filePath.split(`/${SOURCE_DIR}/`)[1];
return `${SOURCE_DIR}/${codePath}`;
}
function getPkgSourcePath(pkgName) {
return path.join(getPkgPath(pkgName), SOURCE_DIR);
}
function getOutputPath(config, pkgName) {
if (config.target === 'browser') {
return path.join(getPkgPath(pkgName), ESM_OUTPUT_DIR);
}
return path.join(getPkgPath(pkgName), NODE_CJS_OUTPUT_DIR);
}
function getGlobalConfig() {
if (fs.existsSync(GLOBAL_CONFIG_PATH)) {
return merge(DEFAULT_CONFIG, buildConfig);
}
return DEFAULT_CONFIG;
}
async function getPkgConfig(config, pkgName) {
const pkgConfigPath = path.join(getPkgPath(pkgName), CONFIG_FILE_NAME);
if (argv.watch) {
config.sourceMap = true;
}
if (fs.existsSync(pkgConfigPath)) {
const content = await import(process.platform === 'win32' ? `file://${pkgConfigPath}` : pkgConfigPath);
const result = merge(config, content.default);
result.resolveCopy = result.copy.map(item => path.join(getPkgPath(pkgName), 'src', item));
return result;
}
return config;
}
function getNeedCompilerPkg(config) {
// 用户通过 cli 指定的包,优先级最高
if (argv.pkg) {
return Array.isArray(argv.pkg) ? argv.pkg : argv.pkg;
}
// 默认编译所有 packages
if (!config.pkgs?.length) {
const pkgs = fs.readdirSync(PACKAGE_PATH);
return pkgs;
}
return config.pkgs;
}
function cleanBeforeCompilerResult(pkgName, log) {
const esmOutputDir = path.join(getPkgPath(pkgName), ESM_OUTPUT_DIR);
const cjsOutputDir = path.join(getPkgPath(pkgName), NODE_CJS_OUTPUT_DIR);
if (fs.existsSync(esmOutputDir)) {
log(chalk.gray(`Clean ${ESM_OUTPUT_DIR} directory`));
fse.removeSync(esmOutputDir);
}
if (fs.existsSync(cjsOutputDir)) {
log(chalk.gray(`Clean ${NODE_CJS_OUTPUT_DIR} directory`));
fse.removeSync(cjsOutputDir);
}
}
function transformFile(filePath, outputPath, config, log) {
if (/\.[jt]sx?$/.test(path.extname(filePath))) {
try {
const code = fs.readFileSync(filePath, 'utf-8');
const shortFilePath = genShortPath(filePath);
if (config.sourceMap) {
config.sourceFileName = filePath;
}
const transformedCode = compiler(code, config);
const type = config.target === 'browser' ? ESM_OUTPUT_DIR : NODE_CJS_OUTPUT_DIR;
log(`Transform to ${type} for ${config.target === 'browser' ? chalk.yellow(shortFilePath) : chalk.blue(shortFilePath)}`);
fse.outputFileSync(outputPath, transformedCode);
}
catch (error) {
console.error(error);
}
}
else {
fse.copySync(filePath, outputPath);
}
}
function compilerPkg(codeDir, outputDir, config, log) {
const files = fs.readdirSync(codeDir);
files.forEach((file) => {
const filePath = path.join(codeDir, file);
const outputFilePath = path.join(outputDir, file);
const fileStats = fs.lstatSync(filePath);
if (config.copy.includes(file)) {
fse.copySync(filePath, outputFilePath);
}
else if (fileStats.isDirectory(filePath) && !/__tests__/.test(file)) {
fse.ensureDirSync(outputFilePath);
compilerPkg(filePath, outputFilePath, config, log);
}
else if (fileStats.isFile(filePath)) {
transformFile(filePath, outputFilePath, config, log);
}
});
}
function watchFile(dir, outputDir, config, log) {
chokidar
.watch(dir, {
ignoreInitial: true,
})
.on('all', (event, changeFile) => {
// 修改的可能是一个目录,一个文件,一个需要 copy 的文件 or 目录
const shortChangeFile = genShortPath(changeFile);
const outputPath = changeFile.replace(dir, outputDir);
const stat = fs.lstatSync(changeFile);
log(`[${event}] ${shortChangeFile}`);
if (config.resolveCopy?.some(item => changeFile.startsWith(item))) { fse.copySync(changeFile, outputPath); }
else if (stat.isFile()) { transformFile(changeFile, outputPath, config, log); }
else if (stat.isDirectory()) { compilerPkg(changeFile, outputPath, config); }
});
}
function compilerPkgs(pkgs, globalConfig) {
pkgs.forEach(async (pkgName) => {
const sourceCodeDir = getPkgSourcePath(pkgName);
if (fs.existsSync(sourceCodeDir)) {
const log = genLog(pkgName);
const config = await getPkgConfig(globalConfig, pkgName);
const outputDir = getOutputPath(config, pkgName);
cleanBeforeCompilerResult(pkgName, log);
const type = config.target === 'browser' ? ESM_OUTPUT_DIR : NODE_CJS_OUTPUT_DIR;
log(chalk.white(`Build ${type} with babel`));
compilerPkg(sourceCodeDir, outputDir, config, log);
if (argv.watch) {
log(chalk.magenta(`Start watch ${SOURCE_DIR} directory...`));
watchFile(sourceCodeDir, outputDir, config, log);
}
}
});
}
function main() {
const globalConfig = getGlobalConfig();
const pkgs = getNeedCompilerPkg(globalConfig);
compilerPkgs(pkgs, globalConfig);
}
main();

View File

@ -1,51 +0,0 @@
import { transformSync } from '@babel/core';
function transform(code, options) {
const result = transformSync(code, options);
return result.code;
}
function transformNodeCode(code, config) {
return transform(code, {
presets: [
[
'@babel/preset-env',
{
modules: false,
targets: { node: '20' },
},
],
],
sourceFileName: config.sourceMap ? config.sourceFileName : undefined,
sourceMaps: config.sourceMap ? 'inline' : false,
});
}
function transformBrowserCode(code) {
// 因为 fes.js 在生产打包的时候,会处理所有的 node_modules 下的文件,确保不会丢失必要 polyfill
// 因此这里不对 polyfill 进行处理,避免全局污染
return transform(code, {
presets: [
[
'@babel/preset-env',
{
modules: false,
useBuiltIns: false,
targets: { chrome: '51' },
},
],
],
});
}
export default function compiler(code, config) {
if (!config.target || config.target === 'node') {
return transformNodeCode(code, config);
}
if (config.target === 'browser') {
return transformBrowserCode(code);
}
throw new Error(`config target error: ${config.target}, only can use 'node' and 'browser'`);
}

View File

@ -1,24 +0,0 @@
# fes.js 源码编译
优雅的编译和日志输出,约定源码放在 `src` 目录。支持 node 端 cjsbrowseresm 编译。
不支持 browser 端的 cjs 编译,有两个理由:
1. 我们的内部包,目前来看只会在我们内部使用,没必再编译一份 cjs。
2. 即使后来有其他包使用,也不大可能不支持 esm即便不支持到时候再加也没问题。
## 使用方式
- 在项目根目录下添加 `build.config.js` 指定需要编译的 `packages`
- 可以通过 `--watch` cli 参数开启 `watch` 模式
- 如果需要只编译某个包,通过 `--pkg pkgName` 参数指定
## 配置
```
const config = {
target: "node", // 编译目标 "node" | "browser", "node" 输出目录 lib, "browser" 输出目录 lib。默认编译目标 "node“
pkgs: [], // 需要编译的 packages 包,默认编译根目录下所有的 packages 包pkgs 参数只在根目录下的配置有效
copy: [] // 直接拷贝,不进行编译
}
```

View File

@ -1,35 +0,0 @@
/* eslint import/no-extraneous-dependencies: 0 */
import chalk from 'chalk';
const colors = [
'red',
'green',
'yellow',
'blue',
'magenta',
'cyan',
'gray',
'redBright',
'greenBright',
'yellowBright',
'blueBright',
'magentaBright',
'cyanBright',
];
let index = 0;
const cache = {};
export default function (pkg) {
if (!cache[pkg]) {
const color = colors[index];
const str = chalk[color].bold(pkg);
cache[pkg] = str;
if (index === colors.length - 1)
index = 0;
else
index += 1;
}
return cache[pkg];
}

View File

@ -1,12 +1,12 @@
import fs from 'node:fs'; import fs from 'node:fs';
import path from 'node:path';
import process from 'node:process'; import process from 'node:process';
import * as url from 'node:url'; import * as url from 'node:url';
import path from 'node:path';
import minimist from 'minimist';
import chalk from 'chalk'; import chalk from 'chalk';
import semver from 'semver';
import enquirer from 'enquirer'; import enquirer from 'enquirer';
import { execa } from 'execa'; import { execa } from 'execa';
import minimist from 'minimist';
import semver from 'semver';
import buildConfig from '../build.config.js'; import buildConfig from '../build.config.js';
@ -20,14 +20,16 @@ const versionIncrements = ['patch', 'minor', 'major', 'prepatch', 'preminor', 'p
function incVersion(version, i) { function incVersion(version, i) {
let _preId = preId || semver.prerelease(version)?.[0]; let _preId = preId || semver.prerelease(version)?.[0];
if (!_preId && /pre/.test(i)) if (!_preId && /pre/.test(i)) {
_preId = 'beta'; _preId = 'beta';
}
return semver.inc(version, i, _preId); return semver.inc(version, i, _preId);
} }
function autoIncVersion(version) { function autoIncVersion(version) {
if (version.includes('-')) if (version.includes('-')) {
return semver.inc(version, 'prerelease'); return semver.inc(version, 'prerelease');
}
return semver.inc(version, 'patch'); return semver.inc(version, 'patch');
} }
@ -48,8 +50,9 @@ async function publishPackage(pkg, runIfNotDry) {
step(`Publishing ${pkg.name}...`); step(`Publishing ${pkg.name}...`);
try { try {
let _releaseTag; let _releaseTag;
if (pkg.newVersion.includes('-')) if (pkg.newVersion.includes('-')) {
_releaseTag = 'next'; _releaseTag = 'next';
}
await runIfNotDry( await runIfNotDry(
// note: use of pnpm is intentional here as we rely on its publishing // note: use of pnpm is intentional here as we rely on its publishing
@ -64,11 +67,11 @@ async function publishPackage(pkg, runIfNotDry) {
console.log('Successfully published :', chalk.green(`${pkg.name}@${pkg.newVersion}`)); console.log('Successfully published :', chalk.green(`${pkg.name}@${pkg.newVersion}`));
} }
catch (e) { catch (e) {
if (e.stderr.match(/previously published/)) if (e.stderr.match(/previously published/)) {
console.log(chalk.red(`Skipping already published: ${pkg.name}`)); console.log(chalk.red(`Skipping already published: ${pkg.name}`));
}
else else { throw e; }
throw e;
} }
} }
@ -95,13 +98,15 @@ function updatePackage(pkgName, version, pkgs) {
pkgJson.version = version; pkgJson.version = version;
pkgJson.dependencies pkgJson.dependencies
&& Object.keys(pkgJson.dependencies).forEach((npmName) => { && Object.keys(pkgJson.dependencies).forEach((npmName) => {
if (pkgs[npmName]) if (pkgs[npmName]) {
pkgJson.dependencies[npmName] = `^${pkgs[npmName].newVersion}`; pkgJson.dependencies[npmName] = `^${pkgs[npmName].newVersion}`;
}
}); });
pkgJson.peerDependencies pkgJson.peerDependencies
&& Object.keys(pkgJson.peerDependencies).forEach((npmName) => { && Object.keys(pkgJson.peerDependencies).forEach((npmName) => {
if (pkgs[npmName]) if (pkgs[npmName]) {
pkgJson.peerDependencies[npmName] = `^${pkgs[npmName].newVersion}`; pkgJson.peerDependencies[npmName] = `^${pkgs[npmName].newVersion}`;
}
}); });
writePackageJson(pkgName, pkgJson); writePackageJson(pkgName, pkgJson);
} }
@ -238,8 +243,9 @@ async function main() {
.join('\n')}\nConfirm?`, .join('\n')}\nConfirm?`,
}); });
if (!yes) if (!yes) {
return; return;
}
const newRootVersion = await genRootPackageVersion(); const newRootVersion = await genRootPackageVersion();
@ -252,11 +258,11 @@ async function main() {
await run('pnpm', ['i']); await run('pnpm', ['i']);
// // build all packages with types // // build all packages with types
step('\nBuilding all packages...'); step('\nBuilding all packages...');
if (!isDryRun) if (!isDryRun) {
await run('pnpm', ['build']); await run('pnpm', ['build']);
}
else else { console.log(`(skipped build)`); }
console.log(`(skipped build)`);
// generate changelog // generate changelog
step('\nGenerating changelog...'); step('\nGenerating changelog...');
@ -274,8 +280,9 @@ async function main() {
// publish packages // publish packages
step('\nPublishing packages...'); step('\nPublishing packages...');
for (const pkg of packagesVersion) for (const pkg of packagesVersion) {
await publishPackage(pkg, runIfNotDry); await publishPackage(pkg, runIfNotDry);
}
// push to GitHub // push to GitHub
step('\nPushing to GitHub...'); step('\nPushing to GitHub...');
@ -283,8 +290,9 @@ async function main() {
await runIfNotDry('git', ['push', 'origin', `refs/tags/v${newRootVersion}`]); await runIfNotDry('git', ['push', 'origin', `refs/tags/v${newRootVersion}`]);
await runIfNotDry('git', ['push']); await runIfNotDry('git', ['push']);
if (isDryRun) if (isDryRun) {
console.log(`\nDry run finished - run git diff to see package changes.`); console.log(`\nDry run finished - run git diff to see package changes.`);
}
console.log(); console.log();
} }