tachybase_todo/packages/core/build/src/build.ts
jack zhang 5df3b0e75d
refactor!: plugins build and plugins load (#2253)
* refactor: plugin build and plugin template

* refactor: plugins' deps

* refactor: plugins bugs

* feat: add plugin static middleware

* fix: bugs

* refactor: frontend plugin add from remote

* refactor: delete useless app/client/plugins

* fix: requirejs move to local

* fix: tests case

* refactor: add src/client and src/server dir check

* fix: lodash tree shaking

* refactor: add BUILD_TIP

* refactor: add file size tip

* fix: bugs

* fix: bug

* fix: change china-division

* fix: change plugins response

* fix: recover dynamicImport

* fix: change server src entry

* fix: test error

* fix: plugins sourcemap => false

* fix: production file error

* refactor: change build tools to vite and tsup

* fix: yarn.lock

* fix: bugs

* fix: server build bugs

* fix: delete .fatherrc.ts

* fix: bug

* fix: bug

* fix: bugs

* fix: bugs

* fix: bugs

* refactor: add plugin d.ts

* refactor: delete fatherrc

* refactor: delete father scripts

* refactor: build bug

* fix: bug

* fix: deps adjust

* fix: add build tips

* fix: bug

* refactor: ignore plugins when build client

* docs: update doc

* refactor: docs and build

* fix: bug

* refactor: build deps

* fix: add USER_REMOTE_PLUGIN env

* feat: add plugin static cache

* feat: add build deps cache

* fix: bugs

* test: add test

* fix: add plugin depden on plugin tip

* fix: adjust shouldDevDependencies

* fix: deps

* fix: ajust deps

* fix: mobile style error

* fix: map error

* fix: test

* fix: bug

* feat: lodash and dayjs import from themself

* feat: @emotion/css 、ahooks and lodash to global

* fix: theme-editor plugin error

* fix: review

* feat: move all plugins' dependencies to devDependencies

* feat: change build

* feat: add devPlugins

* fix: bug

* fix: bugs

* fix: bugs

* fix: bugs

* feat: build bugs

* fix: bugs

* fix: bugs

* fix: review

* fix: bug

* fix: change deps build

* fix: bugs

* fix: bug

* fix: bug

* fix: bugs

* fix: bug

* fix: bug

* fix: multi language

* fix: dist

* fix: cronstrue

* fix: getPackageClientStaticUrl

* fix: antd dayjs locale

* fix: plugin' d.ts import from dist

* fix: multi language

* fix: build types error

* fix: requireModule

* fix: plugin lifecycle

* fix: client resource

* fix: improve code

* fix: locale

* feat: custom build

* fix: require locale

* fix: improve code

* fix: improve code

* fix: skip preset

* fix: collection undefined

* feat: yarn build

* fix: remove enabled

* fix: update dockerfile

* fix: formily version

* docs: update v12 changelog

* fix: devDependencies

* feat: @nocobase/app

* feat: generateAppDir

* fix: improve code

* fix: 0.11.1-alpha.5

* fix: missing @nocobase/client

* fix: error

* fix: add .npmignore

* feat: upgrade antd version

* fix: dependencies

* fix: peerDependencies

* fix: remove china-division dep

* fix: toposort deps

* fix: update dockerfile

* fix: plugin template

* fix: app client outputPath

* feat: update docs

* fix: nginx server root

* fix: storage/.app-dev

* fix: getChinaDivisionData

* feat: plugin info

* feat: update docs

* fix: docs menu

---------

Co-authored-by: chenos <chenlinxh@gmail.com>
2023-08-02 00:07:52 +08:00

288 lines
8.2 KiB
TypeScript
Executable File

import * as assert from 'assert';
import chalk from 'chalk';
import { existsSync, readFileSync } from 'fs';
import { merge } from 'lodash';
import { isAbsolute, join, sep } from 'path';
import rimraf from 'rimraf';
import signale from 'signale';
import babel from './babel';
import { buildPluginClient, buildPluginServer, deleteJsFiles } from './buildPlugin';
import getUserConfig, { CONFIG_FILES } from './getUserConfig';
import randomColor from './randomColor';
import registerBabel from './registerBabel';
import rollup from './rollup';
import { Dispose, IBundleOptions, IBundleTypeOutput, ICjs, IEsm, IOpts } from './types';
import { getExistFiles, getLernaPackages } from './utils';
export function getBundleOpts(opts: IOpts): IBundleOptions[] {
const { cwd, buildArgs = {}, rootConfig = {} } = opts;
const entry = getExistFiles({
cwd,
files: [
'src/index.tsx',
'src/index.ts',
'src/index.jsx',
'src/index.js',
'src/server/index.ts',
'src/server/index.js',
'src/client/index.js',
'src/client/index.ts',
'src/client/index.tsx',
],
onlyOne: false,
returnRelative: true,
});
const userConfig = getUserConfig({ cwd, customPath: buildArgs.config });
const userConfigs = Array.isArray(userConfig) ? userConfig : [userConfig];
return (userConfigs as any).map((userConfig) => {
const bundleOpts = merge(
{
entry,
},
rootConfig,
userConfig,
buildArgs,
);
// Support config esm: 'rollup' and cjs: 'rollup'
if (typeof bundleOpts.esm === 'string') {
bundleOpts.esm = { type: bundleOpts.esm };
}
if (typeof bundleOpts.cjs === 'string') {
bundleOpts.cjs = { type: bundleOpts.cjs };
}
return bundleOpts;
});
}
function validateBundleOpts(bundleOpts: IBundleOptions, { cwd, rootPath }) {
if (bundleOpts.runtimeHelpers) {
const pkgPath = join(cwd, 'package.json');
assert.ok(existsSync(pkgPath), `@babel/runtime dependency is required to use runtimeHelpers`);
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
assert.ok(
(pkg.dependencies || {})['@babel/runtime'],
`@babel/runtime dependency is required to use runtimeHelpers`,
);
}
if (bundleOpts.cjs && (bundleOpts.cjs as ICjs).lazy && (bundleOpts.cjs as ICjs).type === 'rollup') {
throw new Error(
`
cjs.lazy don't support rollup.
`.trim(),
);
}
if (!bundleOpts.esm && !bundleOpts.cjs && !bundleOpts.umd) {
throw new Error(
`
None format of ${chalk.cyan(
'cjs | esm | umd',
)} is configured, checkout https://github.com/umijs/father for usage details.
`.trim(),
);
}
if (bundleOpts.entry) {
const tsConfigPath = join(cwd, 'tsconfig.json');
const tsConfig = existsSync(tsConfigPath) || (rootPath && existsSync(join(rootPath, 'tsconfig.json')));
if (
!tsConfig &&
((Array.isArray(bundleOpts.entry) && bundleOpts.entry.some(isTypescriptFile)) ||
(!Array.isArray(bundleOpts.entry) && isTypescriptFile(bundleOpts.entry)))
) {
signale.info(`Project using ${chalk.cyan('typescript')} but tsconfig.json not exists. Use default config.`);
}
}
}
function isTypescriptFile(filePath) {
return filePath.endsWith('.ts') || filePath.endsWith('.tsx');
}
function isPluginPackage(name: string) {
const prefixes = (process.env.PLUGIN_PACKAGE_PREFIX || '').split(',');
for (const prefix of prefixes) {
if (prefix.includes('preset')) {
return false;
}
if (name.startsWith(prefix)) {
return true;
}
}
return false;
}
interface IExtraBuildOpts {
pkg?: string | { name?: string };
}
export async function build(opts: IOpts, extraOpts: IExtraBuildOpts = {}) {
const { cwd, rootPath, watch, buildArgs = {}, clean = true } = opts;
const { pkg } = extraOpts;
const dispose: Dispose[] = [];
const customConfigPath =
buildArgs.config && (isAbsolute(buildArgs.config) ? buildArgs.config : join(process.cwd(), buildArgs.config));
// register babel for config files
registerBabel({
cwd,
only: customConfigPath ? CONFIG_FILES.concat(customConfigPath) : CONFIG_FILES,
});
const pkgName = (typeof pkg === 'string' ? pkg : pkg?.name) || 'unknown';
function log(msg, ...args) {
console.log(`${pkg ? `${randomColor(`${pkgName}`)}: ` : ''}${msg}`, ...args);
}
// Get user config
const bundleOptsArray = getBundleOpts(opts);
const isPlugin = isPluginPackage(pkgName);
// Clean dist
if (clean && !isPlugin) {
log(chalk.gray(`Clean dist directory`));
rimraf.sync(join(cwd, 'dist'));
}
for (const bundleOpts of bundleOptsArray) {
validateBundleOpts(bundleOpts, { cwd, rootPath });
// Build umd
if (bundleOpts.umd) {
log(`Build umd`);
await rollup({
cwd,
rootPath,
log,
type: 'umd',
entry: bundleOpts.entry,
watch,
dispose,
bundleOpts,
});
}
// Build cjs
if (bundleOpts.cjs) {
const cjs = bundleOpts.cjs as IBundleTypeOutput;
log(`Build ${isPlugin ? 'd.ts' : 'cjs'} with ${cjs.type}`);
if (cjs.type === 'babel') {
await babel({ cwd, rootPath, watch, dispose, isPlugin, type: 'cjs', log, bundleOpts });
if (isPlugin) {
log(cwd);
deleteJsFiles(cwd, log);
await buildPluginServer(cwd, log);
await buildPluginClient(cwd, log);
const buildFile = join(cwd, 'build.js');
if (existsSync(buildFile)) {
log('build others');
try {
await require(buildFile).run(log);
} catch (error) {
console.error(error);
}
}
}
} else {
await rollup({
cwd,
rootPath,
log,
type: 'cjs',
entry: bundleOpts.entry,
watch,
dispose,
bundleOpts,
});
}
}
// Build esm
if (bundleOpts.esm) {
const esm = bundleOpts.esm as IEsm;
log(`Build esm with ${esm.type}`);
const importLibToEs = esm && esm.importLibToEs;
if (esm && esm.type === 'babel') {
await babel({ cwd, rootPath, watch, dispose, type: 'esm', importLibToEs, log, bundleOpts });
} else {
await rollup({
cwd,
rootPath,
log,
type: 'esm',
entry: bundleOpts.entry,
importLibToEs,
watch,
dispose,
bundleOpts,
});
}
}
}
return dispose;
}
function getPkgRelativePath(cwd, pkg) {
const basePath = cwd.split(sep).join('/') + '/packages/';
const dir = pkg.contents.split(sep).join('/');
return dir.substring(basePath.length);
}
export async function buildForLerna(opts: IOpts) {
const { cwd, rootConfig = {}, buildArgs = {}, packages = [] } = opts;
// register babel for config files
registerBabel({
cwd,
only: CONFIG_FILES,
});
const userConfig = merge(getUserConfig({ cwd }), rootConfig, buildArgs);
let pkgs = await getLernaPackages(cwd, userConfig.pkgFilter);
// support define pkgs in lerna
if (userConfig.pkgs) {
pkgs = pkgs.filter((pkg) => userConfig.pkgs.includes(getPkgRelativePath(cwd, pkg)));
}
const dispose: Dispose[] = [];
for (const pkg of pkgs) {
const pkgName = getPkgRelativePath(cwd, pkg);
if (userConfig.excludePkgs && userConfig.excludePkgs.includes(pkgName)) {
continue;
}
if (packages.length && !packages.includes(pkgName)) continue;
// build error when .DS_Store includes in packages root
const pkgPath = pkg.contents;
assert.ok(existsSync(join(pkgPath, 'package.json')), `package.json not found in packages/${pkg}`);
process.chdir(pkgPath);
dispose.push(
...(await build(
{
// eslint-disable-line
...opts,
buildArgs: opts.buildArgs,
rootConfig: userConfig,
cwd: pkgPath,
rootPath: cwd,
},
{
pkg,
},
)),
);
}
return dispose;
}
export default async function (opts: IOpts) {
const useLerna = existsSync(join(opts.cwd, 'lerna.json'));
const isLerna = useLerna && process.env.LERNA !== 'none';
const dispose = isLerna ? await buildForLerna(opts) : await build(opts);
return () => dispose.forEach((e) => e());
}