feat: 重写源码编译

This commit is contained in:
winixt 2021-08-07 15:43:24 +08:00
parent 5d10a95941
commit 209b0c0525
40 changed files with 1417 additions and 2262 deletions

View File

@ -1,30 +0,0 @@
// utils must build before core
// runtime must build before renderer-react
const pkgs = [
"fes-runtime",
"fes-compiler",
"fes",
"fes-preset-built-in",
"fes-plugin-request",
"fes-plugin-access",
"fes-plugin-model",
"fes-plugin-layout",
"fes-plugin-icon",
"fes-plugin-locale",
"fes-plugin-enums",
"fes-plugin-jest",
"fes-plugin-vuex",
"create-fes-app",
"fes-plugin-qiankun",
"fes-plugin-sass",
"fes-plugin-monaco-editor"
];
export default {
target: "node",
cjs: { type: "babel", lazy: false },
disableTypeCheck: true,
pkgs,
};

6
babel.config.js Normal file
View File

@ -0,0 +1,6 @@
// for jest
module.exports = {
presets: [
['@babel/preset-env', { targets: { node: 'current' } }]
]
};

24
build.config.js Normal file
View File

@ -0,0 +1,24 @@
module.exports = {
// 需要编译的包
pkgs: [
'create-fes-app',
'fes',
'fes-compiler',
'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-preset-built-in',
'fes-runtime'
],
copy: []
};

17
jest.config.js Normal file
View File

@ -0,0 +1,17 @@
module.exports = {
moduleFileExtensions: [
'js',
'jsx',
'json'
],
transform: {
'\\.[jt]sx?$': 'babel-jest'
},
moduleDirectories: [
'node_modules'
],
transformIgnorePatterns: [
'node_modules/(?!lodash-es)'
]
};

View File

@ -10,11 +10,13 @@
"scripts": {
"clean": "lerna clean",
"bootstrap": "lerna bootstrap",
"build": "father-build --watch",
"dev": "node scripts/build.js --watch",
"build": "node scripts/build.js",
"ver": "lerna version --conventional-prerelease --no-changelog --no-commit-hooks --no-private",
"release": "father-build && lerna publish from-git",
"release": "node scripts/build.js && lerna publish from-git",
"docs:dev": "vuepress dev docs --clean-cache",
"docs:build": "vuepress build docs --clean-cache",
"test": "yarn jest",
"lint": "eslint -c ./.eslintrc.js --ext .js,.jsx,.vue,.ts"
},
"license": "MIT",
@ -29,20 +31,28 @@
"lerna": "^3.22.1"
},
"devDependencies": {
"@babel/core": "^7.15.0",
"@babel/preset-env": "^7.15.0",
"@commitlint/cli": "^11.0.0",
"@commitlint/config-conventional": "^11.0.0",
"@vuepress/plugin-docsearch": "^2.0.0-beta.22",
"@vuepress/plugin-pwa": "^2.0.0-beta.22",
"@vuepress/plugin-pwa-popup": "^2.0.0-beta.22",
"@webank/eslint-config-webank": "0.3.0",
"babel-jest": "^27.0.6",
"chalk": "^4.1.2",
"chokidar": "^3.5.2",
"commitizen": "^4.2.1",
"cz-conventional-changelog": "^3.3.0",
"father-build": "^1.19.1",
"deepmerge": "^4.2.2",
"fs-extra": "^10.0.0",
"husky": "^4.3.0",
"jest": "^27.0.6",
"lint-staged": "^10.4.0",
"postcss-loader": "^5.0.0",
"postcss": "^8.0.0",
"vuepress": "^2.0.0-beta.22"
"postcss-loader": "^5.0.0",
"vuepress": "^2.0.0-beta.22",
"yargs-parser": "^20.2.9"
},
"lint-staged": {
"*.{js,jsx,vue,ts}": [

View File

@ -15,7 +15,7 @@ const args = yParser(process.argv.slice(2), {
});
if (args._.length > 1) {
console.log(chalk.yellow('\n Info: You provided more than one argument. The first one will be used as the app\'s name, the rest are ignored.'));
console.log(chalk.yellow('\n Warning: You provided more than one argument. The first one will be used as the app\'s name, the rest are ignored.'));
}
if (args.version && !args._[0]) {

1
packages/deps/README.md Normal file
View File

@ -0,0 +1 @@
# Fes.js 公共依赖包管理

View File

@ -1,3 +0,0 @@
export default {
disableTypeCheck: false,
};

View File

@ -1,3 +0,0 @@
export default {
disableTypeCheck: false,
};

View File

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

View File

@ -1,3 +0,0 @@
export default {
disableTypeCheck: false,
};

View File

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

View File

@ -1,3 +0,0 @@
export default {
disableTypeCheck: false,
};

View File

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

View File

@ -1,3 +0,0 @@
export default {
disableTypeCheck: false,
};

View File

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

View File

@ -1,3 +0,0 @@
export default {
disableTypeCheck: false,
};

View File

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

View File

@ -1,3 +0,0 @@
export default {
disableTypeCheck: false,
};

View File

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

View File

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

View File

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

View File

@ -1,3 +0,0 @@
export default {
disableTypeCheck: false,
};

View File

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

View File

@ -66,15 +66,16 @@ function getRequestInstance() {
addResponseInterceptors(instance, responseInterceptors);
// 洋葱模型内部应该这是对数据的处理,避免有副作用调用
scheduler.use(paramsProcess)
scheduler
.use(setDataField) // 最外层进行数据格式化
.use(paramsProcess)
.use(genRequestKey)
.use(cacheControl)
.use(preventRepeatReq)
.use(throttle)
.use(axiosMiddleware)
.use(resDataAdaptor)
.use(resErrorProcess)
.use(setDataField);
.use(resErrorProcess);
return {
context: {
@ -180,7 +181,7 @@ export const request = (url, data, options = {}) => {
return currentRequestInstance.request(context).then(async () => {
if (!context.error) {
return context.config.useResonse ? context.response : context.filterData || context.response.data;
return context.config.useResonse ? context.response : context.response.data;
}
await handleRequestError(context);
return Promise.reject(context.error);

View File

@ -2,9 +2,9 @@ import { isObject } from './helpers';
// FEATURE: 后续支持 a.b.c
export default async (ctx, next) => {
const dataField = ctx.config.dataField ?? ctx.dataField;
if (ctx.response && isObject(ctx.response.data) && dataField) {
ctx.filterData = ctx.response.data[dataField];
}
await next();
const dataField = ctx.config.dataField ?? ctx.dataField;
if (!ctx.error && ctx.response && isObject(ctx.response.data) && dataField) {
ctx.response.data = ctx.response.data[dataField];
}
};

View File

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

View File

@ -1,3 +0,0 @@
export default {
disableTypeCheck: false,
};

View File

@ -1,6 +0,0 @@
export default {
target: 'browser',
cjs: { type: 'babel', lazy: false },
esm: { type: 'babel' },
disableTypeCheck: false,
};

View File

@ -0,0 +1,4 @@
module.exports = {
target: 'browser'
};

View File

@ -1,4 +1,4 @@
import sum from '@/utils/sum';
import sum from '../src/utils/sum';
test('adds 1 + 2 to equal 3', () => {
expect(sum(1, 2)).toBe(3);

View File

@ -1,5 +0,0 @@
export default {
cjs: { type: 'babel', lazy: false },
esm: { type: 'babel' },
disableTypeCheck: false
};

View File

@ -0,0 +1,4 @@
module.exports = {
copy: ['index.js']
};

View File

@ -8,7 +8,6 @@
},
"files": [
"lib",
"es",
"bin",
"types.d.ts"
],
@ -16,7 +15,7 @@
"fes": "./bin/fes.js"
},
"main": "lib/index.js",
"module": "es/index.js",
"module": "lib/index.js",
"author": "qlin",
"repository": {
"type": "git",

178
scripts/build.js Normal file
View File

@ -0,0 +1,178 @@
// 关闭 import 规则
/* eslint import/no-extraneous-dependencies: 0 */
const fs = require('fs');
const fse = require('fs-extra');
const path = require('path');
const merge = require('deepmerge');
const chokidar = require('chokidar');
const chalk = require('chalk');
const argv = require('yargs-parser')(process.argv.slice(2));
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',
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) {
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)) {
const userConfig = require(GLOBAL_CONFIG_PATH);
return merge(DEFAULT_CONFIG, userConfig);
}
return DEFAULT_CONFIG;
}
function getPkgConfig(config, pkgName) {
const pkgConfigPath = path.join(getPkgPath(pkgName), CONFIG_FILE_NAME);
if (fs.existsSync(pkgConfigPath)) {
return merge(config, require(pkgConfigPath));
}
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))) {
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);
} 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 baseName = path.basename(changeFile);
const shortChangeFile = genShortPath(changeFile);
const outputPath = changeFile.replace(dir, outputDir);
const stat = fs.lstatSync(changeFile);
log(`[${event}] ${shortChangeFile}`);
if (config.copy.includes(baseName)) {
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((pkgName) => {
const sourceCodeDir = getPkgSourcePath(pkgName);
if (fs.existsSync(sourceCodeDir)) {
const log = genLog(pkgName);
const config = 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();

48
scripts/compiler.js Normal file
View File

@ -0,0 +1,48 @@
// 关闭 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: '51' }
}]
]
});
}
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;

25
scripts/docs.md Normal file
View File

@ -0,0 +1,25 @@
# 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, "broswer" 输出目录 lib。默认编译目标 "node“
pkgs: [], // 需要编译的 packages 包,默认编译根目录下所有的 packages 包pkgs 参数只在根目录下的配置有效
copy: [] // 直接拷贝,不进行编译
}
```

35
scripts/randomColor.js Normal file
View File

@ -0,0 +1,35 @@
/* 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];
};

3183
yarn.lock

File diff suppressed because it is too large Load Diff