refactor: build tools (#2374)

* refactor: core/build and cli/build.js

* fix: build

* fix: bug

* fix: replaceAll to replace

* fix: node version check

* fix: add require check

* fix: esbuild other ext

* fix: process json

* fix: exlude jsx-runtime

* feat: split chunk

* fix: minify plugin client bundle

* fix: compatibility

* fix: support import()

* feat: update docs

* fix: server deps

* feat: demo

* fix: remove cjs treeshake

* fix: local error

* fix: bug

* fix: lazy load

* fix: rewrites

* fix: remove dynamic import  function

* feat: doc demo

* fix: codesanbox vite template

* fix: codesanbox demo

* fix: hide stackblitz

* fix: revert rspack

* fix: test bug

* fix: delete console

* fix: import dayjs locale

---------

Co-authored-by: chenos <chenlinxh@gmail.com>
This commit is contained in:
jack zhang 2023-09-03 10:59:33 +08:00 committed by GitHub
parent 24179c4469
commit 08c5383bee
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
471 changed files with 2336 additions and 8141 deletions

View File

@ -1,11 +0,0 @@
export default {
target: 'node',
cjs: { type: 'babel', lazy: true },
excludePkgs: [
'core/build',
'core/cli',
'core/create-nocobase-app',
'core/devtools',
'app/client',
],
};

View File

@ -0,0 +1,221 @@
import DumiPreviewerActions from 'dumi/theme-default/slots/PreviewerActions';
import React, { useRef, useEffect, useState } from 'react';
import { Spin } from 'antd'
import { IPreviewerProps } from 'dumi';
const indexHtml = `<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
`
const mainTsx = `
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>,
)
`
const packageJson = `
{
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview"
},
"dependencies": {
},
"devDependencies": {
"flat": "^5.0.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"@types/react": "^18.2.15",
"@types/react-dom": "^18.2.7",
"@vitejs/plugin-react": "^4.0.3",
"less": "^4.2.0",
"typescript": "^5.0.2",
"vite": "^4.4.5"
}
}
`
const tsConfigJson = `
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": [
"ES2020",
"DOM",
"DOM.Iterable"
],
"module": "ESNext",
"skipLibCheck": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
"composite": true,
"strict": false,
"noUnusedLocals": true,
"noUnusedParameters": true,
"allowSyntheticDefaultImports": true,
"noFallthroughCasesInSwitch": true
},
"include": [
"src",
"vite.config.ts"
]
}
`
const viteConfigTs = `
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
})
`
const sandboxTask = `
{
"setupTasks": [
{
"name": "Install Dependencies",
"command": "yarn install"
}
],
"tasks": {
"dev": {
"name": "dev",
"command": "yarn dev",
"runAtStart": true,
"preview": {
"port": 5173
}
},
"build": {
"name": "build",
"command": "yarn build",
"runAtStart": false
},
"preview": {
"name": "preview",
"command": "yarn preview",
"runAtStart": false
}
}
}
`
function getCSBData(opts: IPreviewerProps, ext: string) {
const files: Record<
string,
{
content: string;
isBinary: boolean;
}
> = {};
const deps: Record<string, string> = {};
const entryFileName = `index${ext}`;
Object.entries(opts.asset.dependencies).forEach(([name, { type, value }]) => {
if (type === 'NPM') {
// generate dependencies
deps[name] = value;
} else {
// append other imported local files
files[name === entryFileName ? `src/App${ext}` : name] = {
content: value,
isBinary: false,
};
}
});
// append package.json
let pkg = JSON.parse(packageJson)
try {
for (let key in deps) {
if (!pkg['devDependencies'][key]) {
pkg.dependencies[key] = deps[key]
}
}
} catch (e) {
console.log(e)
}
files['package.json'] = {
content: JSON.stringify(
{
name: opts.title,
...pkg,
},
null,
2,
),
isBinary: false,
};
files['index.html'] = { content: indexHtml, isBinary: false };
files['src/main.tsx'] = { content: mainTsx, isBinary: false };
files['package.json'] = { content: JSON.stringify(pkg, null, 2), isBinary: false };
files['.codesandbox/task.json'] = { content: sandboxTask, isBinary: false };
files['tsconfig.json'] = { content: tsConfigJson, isBinary: false };
files['vite.config.ts'] = { content: viteConfigTs, isBinary: false };
return { files };
}
export function openCodeSandbox(opts: IPreviewerProps) {
const isTSX = Boolean(opts.asset.dependencies?.['index.tsx']);
const ext = isTSX ? '.tsx' : '.jsx';
return fetch("https://codesandbox.io/api/v1/sandboxes/define?json=1", {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json"
},
body: JSON.stringify(getCSBData(opts, ext))
})
.then(x => x.json())
.then(data => {
window.open(`https://codesandbox.io/p/sandbox/${data.sandbox_id}?file=/src/App${ext}`);
});
}
const PreviewerActions: typeof DumiPreviewerActions = (props) => {
const div = useRef<HTMLDivElement>(null);
const [loading, setLoading] = useState(false);
useEffect(() => {
if (div.current) {
const elements = div.current.querySelectorAll('.dumi-default-previewer-actions');
elements.forEach((element) => {
element.addEventListener('click', (e) => {
e.stopImmediatePropagation();
setLoading(true);
openCodeSandbox(props).finally(() => {
setLoading(false);
});
})
})
}
}, [div])
return <Spin spinning={loading}><div ref={div}><DumiPreviewerActions {...props} disabledActions={['STACKBLITZ']} /></div></Spin>
};
export default PreviewerActions;

View File

@ -5,6 +5,9 @@
"packages/*/*" "packages/*/*"
], ],
"license": "Apache-2.0", "license": "Apache-2.0",
"engines": {
"node": ">=16.0.0"
},
"licenses": [ "licenses": [
{ {
"type": "Apache-2.0", "type": "Apache-2.0",

View File

@ -1 +0,0 @@
export { default } from '@nocobase/plugin-api-doc/client';

View File

@ -1,4 +1,3 @@
import { ACL } from './acl';
import minimatch from 'minimatch'; import minimatch from 'minimatch';
export type SnippetOptions = { export type SnippetOptions = {
@ -7,7 +6,10 @@ export type SnippetOptions = {
}; };
class Snippet { class Snippet {
constructor(public name: string, public actions: Array<string>) {} constructor(
public name: string,
public actions: Array<string>,
) {}
} }
export type SnippetGroup = { export type SnippetGroup = {

View File

@ -8,7 +8,9 @@ process.env.MFSU_AD = 'none';
process.env.DID_YOU_KNOW = 'none'; process.env.DID_YOU_KNOW = 'none';
const pluginPrefix = (process.env.PLUGIN_PACKAGE_PREFIX || '').split(',').filter((item) => !item.includes('preset')); // 因为现在 preset 是直接引入的,所以不能忽略,如果以后 preset 也是动态插件的形式引入,那么这里可以去掉 const pluginPrefix = (process.env.PLUGIN_PACKAGE_PREFIX || '').split(',').filter((item) => !item.includes('preset')); // 因为现在 preset 是直接引入的,所以不能忽略,如果以后 preset 也是动态插件的形式引入,那么这里可以去掉
const pluginDirs = 'packages/plugins/,packages/samples/'.split(',').map((item) => path.join(process.cwd(), item));
const pluginDirs = (process.env.PLUGIN_PATH || 'packages/plugins/,packages/samples/')
.split(',').map(item => path.join(__dirname, '..', '..', '..', '..', item));
const outputPluginPath = path.join(__dirname, 'src', '.plugins'); const outputPluginPath = path.join(__dirname, 'src', '.plugins');
const indexGenerator = new IndexGenerator(outputPluginPath, pluginDirs); const indexGenerator = new IndexGenerator(outputPluginPath, pluginDirs);
@ -51,6 +53,9 @@ export default defineConfig({
edge: 79, edge: 79,
safari: 12, safari: 12,
}, },
codeSplitting: {
jsStrategy: 'depPerChunk'
},
chainWebpack(config, { env }) { chainWebpack(config, { env }) {
if (env === 'production') { if (env === 'production') {
config.plugin('ignore nocobase plugins').use(require('webpack').IgnorePlugin, [ config.plugin('ignore nocobase plugins').use(require('webpack').IgnorePlugin, [

View File

@ -1 +0,0 @@
Used in bin/father.js to determine if it is in the local debug state.

View File

@ -4,7 +4,7 @@ const { existsSync } = require('fs');
const { join } = require('path'); const { join } = require('path');
const yParser = require('yargs-parser'); const yParser = require('yargs-parser');
const chalk = require('chalk'); const chalk = require('chalk');
const signale = require('signale'); const { build } = require('../lib');
// print version and @local // print version and @local
const args = yParser(process.argv.slice(2), { const args = yParser(process.argv.slice(2), {
@ -26,46 +26,4 @@ const updater = require('update-notifier');
const pkg = require('../package.json'); const pkg = require('../package.json');
updater({ pkg }).notify({ defer: true }); updater({ pkg }).notify({ defer: true });
function stripEmptyKeys(obj) { build(args._);
Object.keys(obj).forEach((key) => {
if (!obj[key] || (Array.isArray(obj[key]) && !obj[key].length)) {
delete obj[key];
}
});
return obj;
}
function build() {
// Parse buildArgs from cli
const buildArgs = stripEmptyKeys({
esm: args.esm && { type: args.esm === true ? 'rollup' : args.esm },
cjs: args.cjs && { type: args.cjs === true ? 'rollup' : args.cjs },
umd: args.umd && { name: args.umd === true ? undefined : args.umd },
file: args.file,
target: args.target,
entry: args._,
config: args.config,
});
if (buildArgs.file && buildArgs.entry && buildArgs.entry.length > 1) {
signale.error(
new Error(`Cannot specify file when have multiple entries (${buildArgs.entry.join(', ')})`)
);
process.exit(1);
}
require('../lib/build')
.default({
cwd: args.root || process.cwd(),
watch: args.w || args.watch,
clean: args.clean,
buildArgs,
packages: args._ || [],
})
.catch((e) => {
signale.error(e);
process.exit(1);
});
}
build();

View File

@ -8,76 +8,33 @@
}, },
"typings": "./index.d.ts", "typings": "./index.d.ts",
"dependencies": { "dependencies": {
"@babel/core": "7.22.5",
"@babel/plugin-proposal-class-properties": "7.18.6",
"@babel/plugin-proposal-decorators": "7.22.5",
"@babel/plugin-proposal-do-expressions": "7.22.5",
"@babel/plugin-proposal-export-default-from": "7.22.5",
"@babel/plugin-proposal-export-namespace-from": "7.18.9",
"@babel/plugin-proposal-nullish-coalescing-operator": "7.18.6",
"@babel/plugin-proposal-optional-chaining": "7.21.0",
"@babel/plugin-syntax-dynamic-import": "7.8.3",
"@babel/plugin-transform-modules-commonjs": "7.22.5",
"@babel/plugin-transform-runtime": "7.22.5",
"@babel/preset-env": "7.12.1",
"@babel/preset-react": "7.22.5",
"@babel/preset-typescript": "7.22.5",
"@babel/register": "7.22.5",
"@hapi/topo": "^6.0.0", "@hapi/topo": "^6.0.0",
"@lerna/filter-packages": "4.0.0",
"@lerna/project": "4.0.0", "@lerna/project": "4.0.0",
"@lerna/query-graph": "4.0.0",
"@rollup/plugin-babel": "6.0.3",
"@rollup/plugin-commonjs": "25.0.1",
"@rollup/plugin-inject": "5.0.3",
"@rollup/plugin-json": "6.0.0",
"@rollup/plugin-node-resolve": "15.1.0",
"@rollup/plugin-replace": "5.0.2",
"@rollup/plugin-url": "8.0.1",
"@svgr/rollup": "5.5.0",
"@vercel/ncc": "0.36.1", "@vercel/ncc": "0.36.1",
"ajv": "6.12.6",
"autoprefixer": "9.6.0",
"babel-plugin-istanbul": "^5.2.0",
"babel-plugin-react-require": "3.1.1",
"chalk": "2.4.2", "chalk": "2.4.2",
"chokidar": "^3.0.2",
"es5-imcompatible-versions": "^0.1.37",
"fast-glob": "3.3.0", "fast-glob": "3.3.0",
"glob": "^7.1.4", "glob": "^7.1.4",
"gulp-if": "2.0.2", "gulp": "4.0.2",
"gulp-less": "^5.0.0", "@types/gulp": "^4.0.13",
"gulp-plumber": "^1.2.1",
"gulp-typescript": "6.0.0-alpha.1", "gulp-typescript": "6.0.0-alpha.1",
"less": "4.1.3",
"less-plugin-npm-import": "2.1.0",
"lodash": "4.17.21",
"pkg-up": "3.1.0", "pkg-up": "3.1.0",
"rimraf": "2.6.3", "tsup": "7.2.0",
"rollup": "3.25.1",
"rollup-plugin-postcss": "4.0.2",
"rollup-plugin-terser": "7.0.2",
"rollup-plugin-typescript2": "0.34.1",
"semver": "6.1.1",
"signale": "1.4.0",
"slash2": "2.0.0",
"temp-dir": "2.0.0",
"through2": "3.0.1",
"ts-loader": "^9.4.3",
"tsup": "7.1.0",
"typescript": "5.1.3", "typescript": "5.1.3",
"update-notifier": "3.0.0", "update-notifier": "3.0.0",
"vinyl-fs": "3.0.3", "vite-plugin-lib-inject-css": "1.2.0",
"vite-plugin-css-injected-by-js": "^3.2.1", "vite-plugin-css-injected-by-js": "^3.2.1",
"yargs-parser": "13.1.2" "yargs-parser": "13.1.2",
"@types/lerna__project": "5.1.0",
"@types/lerna__package": "5.1.0",
"@babel/core": "7.22.10",
"@babel/preset-env": "7.22.10",
"@babel/plugin-transform-modules-amd": "7.22.5"
}, },
"license": "Apache-2.0", "license": "Apache-2.0",
"scripts": { "scripts": {
"build": "umi-tools build" "build": "tsup"
}, },
"devDependencies": { "devDependencies": {
"@types/gulp-plumber": "^0.0.32",
"umi-tools": "^0.4.0"
}, },
"gitHead": "ce588eefb0bfc50f7d5bbee575e0b5e843bf6644" "gitHead": "ce588eefb0bfc50f7d5bbee575e0b5e843bf6644"
} }

View File

@ -1,272 +0,0 @@
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 fs from 'fs-extra'
import getBabelConfig from "./getBabelConfig";
import { Dispose, IBundleOptions } from "./types";
import * as ts from "typescript";
interface IBabelOpts {
cwd: string;
rootPath?: string;
type: "esm" | "cjs";
target?: "browser" | "node";
log?: (string) => void;
watch?: boolean;
dispose?: Dispose[];
isPlugin?: boolean;
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,
isPlugin = false,
bundleOpts: {
target = "browser",
runtimeHelpers,
extraBabelPresets = [],
extraBabelPlugins = [],
browserFiles = [],
nodeFiles = [],
nodeVersion,
disableTypeCheck,
cjs,
lessInBabelMode,
},
} = opts;
const srcPath = join(cwd, "src");
const targetDir = type === "esm" ? "es" : isPlugin ? 'dist' : "lib";
const targetPath = join(cwd, targetDir);
if (!isPlugin) {
log(chalk.gray(`Clean ${targetDir} directory`));
rimraf.sync(targetPath);
} else {
if (fs.existsSync(targetPath)) {
// exclude node_modules
const files = fs.readdirSync(targetPath, { recursive: false }) as string[];
files.forEach((file) => {
if (file !== 'node_modules') {
rimraf.sync(join(targetPath, file));
}
})
}
}
function transform(opts: ITransformOpts) {
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,
});
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
)}`
);
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");
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) {
const tsConfig = getTSConfig();
const babelTransformRegexp = disableTypeCheck ? /\.(t|j)sx?$/ : /\.jsx?$/;
function isTsFile(path) {
return /\.tsx?$/.test(path) && !path.endsWith(".d.ts");
}
function isTransform(path) {
if (isPlugin) return false
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,
})
);
// .jsx -> .js
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 = [
isPlugin ? join(srcPath, "**/*.{ts,tsx}") : 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).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();
});
});
}

View File

@ -1,52 +0,0 @@
import { join, basename, sep } from 'path';
import { getPackagesSync } from '@lerna/project';
import { existsSync, readdirSync, renameSync, statSync } from 'fs';
import mkdirp from 'mkdirp';
import rimraf from 'rimraf';
import build from './build';
function moveEsLibToDist(cwd) {
['es', 'lib'].forEach(dir => {
const absDirPath = join(cwd, dir);
const absDistPath = join(cwd, 'dist');
if (existsSync(absDirPath)) {
mkdirp.sync(absDistPath);
renameSync(absDirPath, join(absDistPath, dir));
}
});
}
describe('father build', () => {
const rootConfigMapping = {
'lerna-root-config-override': { cjs: 'rollup', esm: false },
};
require('test-build-result')({
root: join(__dirname, './fixtures/build'),
build({ cwd }) {
process.chdir(cwd);
rimraf.sync(join(cwd, 'dist'));
return build({ cwd, rootConfig: rootConfigMapping[basename(cwd)] }).then(() => {
// babel
moveEsLibToDist(cwd);
// lerna
if (existsSync(join(cwd, 'lerna.json'))) {
mkdirp.sync(join(cwd, 'dist'));
const pkgs = getPackagesSync(cwd)
for (let pkg of pkgs) {
const pkgPath = pkg.contents;
if (!statSync(pkgPath).isDirectory()) continue;
moveEsLibToDist(pkgPath);
renameSync(
join(pkgPath, 'dist'),
join(cwd, 'dist', pkgPath.split(sep).pop())
);
}
}
});
},
});
});

View File

@ -1,287 +1,103 @@
import * as assert from 'assert'; import execa from 'execa';
import chalk from 'chalk'; import chalk from 'chalk';
import { existsSync, readFileSync } from 'fs'; import path from 'path';
import { merge } from 'lodash'; import {
import { isAbsolute, join, sep } from 'path'; PACKAGES_PATH,
import rimraf from 'rimraf'; getPluginPackages,
import signale from 'signale'; CORE_CLIENT,
import babel from './babel'; CORE_APP,
import { buildPluginClient, buildPluginServer, deleteJsFiles } from './buildPlugin'; getCjsPackages,
import getUserConfig, { CONFIG_FILES } from './getUserConfig'; getPresetsPackages,
import randomColor from './randomColor'; ROOT_PATH,
import registerBabel from './registerBabel'; } from './constant';
import rollup from './rollup'; import { buildClient } from './buildClient';
import { Dispose, IBundleOptions, IBundleTypeOutput, ICjs, IEsm, IOpts } from './types'; import { buildCjs } from './buildCjs';
import { getExistFiles, getLernaPackages } from './utils'; import { buildPlugin } from './buildPlugin';
import { buildDeclaration } from './buildDeclaration';
import { PkgLog, getPkgLog, toUnixPath, getPackageJson } from './utils';
import { getPackages } from './utils/getPackages';
import { Package } from '@lerna/package';
export function getBundleOpts(opts: IOpts): IBundleOptions[] { export async function build(pkgs: string[]) {
const { cwd, buildArgs = {}, rootConfig = {} } = opts; const packages = getPackages(pkgs);
const entry = getExistFiles({ const pluginPackages = getPluginPackages(packages);
cwd, const cjsPackages = getCjsPackages(packages);
files: [ const presetsPackages = getPresetsPackages(packages);
'src/index.tsx',
'src/index.ts', // core/*
'src/index.jsx', await buildPackages(cjsPackages, 'lib', buildCjs);
'src/index.js', const clientCore = packages.find((item) => item.location === CORE_CLIENT);
'src/server/index.ts', if (clientCore) {
'src/server/index.js', await buildPackage(clientCore, 'es', buildClient);
'src/client/index.js', }
'src/client/index.ts',
'src/client/index.tsx', // plugins/*、samples/*
], await buildPackages(pluginPackages, 'dist', buildPlugin);
onlyOne: false,
returnRelative: true, // presets/*
await buildPackages(presetsPackages, 'lib', buildCjs);
// core/app
const appClient = packages.find((item) => item.location === CORE_APP);
if (appClient) {
await runScript(['umi', 'build'], ROOT_PATH, {
APP_ROOT: path.join(CORE_APP, 'client'),
}); });
const userConfig = getUserConfig({ cwd, customPath: buildArgs.config }); }
const userConfigs = Array.isArray(userConfig) ? userConfig : [userConfig]; }
return (userConfigs as any).map((userConfig) => {
const bundleOpts = merge( export async function buildPackages(
{ packages: Package[],
entry, targetDir: string,
doBuildPackage: (cwd: string, sourcemap: boolean, log?: PkgLog) => Promise<any>,
) {
for await (const pkg of packages) {
await buildPackage(pkg, targetDir, doBuildPackage);
}
}
export async function buildPackage(
pkg: Package,
targetDir: string,
doBuildPackage: (cwd: string, sourcemap: boolean, log?: PkgLog) => Promise<any>,
) {
const sourcemap = process.argv.includes('--sourcemap');
const noDeclaration = process.argv.includes('--no-dts');
const log = getPkgLog(pkg.name);
const packageJson = getPackageJson(pkg.location);
log(`${chalk.bold(toUnixPath(pkg.location.replace(PACKAGES_PATH, '').slice(1)))} build start`);
// prebuild
if (packageJson?.scripts?.prebuild) {
log('prebuild');
await runScript(['prebuild'], pkg.location);
await packageJson.prebuild(pkg.location);
}
// build source
await doBuildPackage(pkg.location, sourcemap, log);
// build declaration
if (!noDeclaration) {
log('build declaration');
await buildDeclaration(pkg.location, targetDir);
}
// postbuild
if (packageJson?.scripts?.postbuild) {
log('postbuild');
await runScript(['postbuild'], pkg.location);
}
}
function runScript(args: string[], cwd: string, envs: Record<string, string> = {}) {
return execa('yarn', args, {
cwd,
stdio: 'inherit',
env: {
...process.env,
...envs,
NODE_ENV: 'production',
}, },
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')) {
continue;
}
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());
}

View File

@ -0,0 +1,36 @@
import { build } from 'tsup';
import fg from 'fast-glob';
import path from 'path';
import chalk from 'chalk';
import { globExcludeFiles, EsbuildSupportExts } from './constant';
import { PkgLog } from './utils';
export function buildCjs(cwd: string, sourcemap: boolean = false, log: PkgLog) {
log('build cjs');
const entry = fg.globSync(['src/**', ...globExcludeFiles], { cwd, absolute: true });
const outDir = path.join(cwd, 'lib');
const otherExts = Array.from(new Set(entry.map((item) => path.extname(item)).filter((item) => !EsbuildSupportExts.includes(item))));
if (otherExts.length) {
log('%s will not be processed, only be copied to the lib directory.', chalk.yellow(otherExts.join(',')));
}
return build({
entry,
splitting: false,
clean: true,
bundle: false,
silent: true,
sourcemap,
treeshake: false,
target: 'node16',
keepNames: true,
outDir,
loader: {
...otherExts.reduce((prev, cur) => ({ ...prev, [cur]: 'copy' }), {}),
'.json': 'copy',
},
format: 'cjs',
skipNodeModulesBundle: true,
});
}

View File

@ -0,0 +1,71 @@
import path from 'path';
import fg from 'fast-glob';
import { build as viteBuild } from 'vite';
import { build as tsupBuild } from 'tsup';
import { libInjectCss } from 'vite-plugin-lib-inject-css';
import react from '@vitejs/plugin-react';
import { PkgLog } from './utils';
import { globExcludeFiles } from './constant';
export async function buildClient(cwd: string, sourcemap: boolean = false, log: PkgLog) {
log('build client');
await Promise.all([buildLib(cwd, sourcemap, 'cjs'), buildLib(cwd, sourcemap, 'es')]);
await buildLocale(cwd);
}
export function buildLib(cwd: string, sourcemap: boolean, format: 'cjs' | 'es') {
const outDir = path.resolve(cwd, format === 'cjs' ? 'lib' : 'es');
const entry = path.join(cwd, 'src/index.ts').replaceAll(/\\/g, '/');
const cwdWin = cwd.replaceAll(/\\/g, '/');
const cwdUnix = cwd.replaceAll(/\//g, '\\');
return viteBuild({
mode: 'production',
define: {
'process.env.NODE_ENV': JSON.stringify('production'),
},
build: {
minify: false,
outDir,
cssCodeSplit: true,
emptyOutDir: true,
sourcemap,
lib: {
entry,
formats: [format],
fileName: 'index',
},
target: ['es2015', 'edge88', 'firefox78', 'chrome87', 'safari14'],
rollupOptions: {
cache: true,
treeshake: true,
external(id) {
if (id.startsWith('.') || id.startsWith(cwdUnix) || id.startsWith(cwdWin)) {
return false;
}
return true;
},
},
},
plugins: [react(), libInjectCss()],
});
}
export function buildLocale(cwd: string) {
const entry = fg.globSync(['src/locale/**', ...globExcludeFiles], { cwd, absolute: true });
const outDir = path.resolve(cwd, 'lib', 'locale');
return tsupBuild({
entry,
splitting: false,
clean: false,
bundle: false,
silent: true,
treeshake: false,
target: 'node16',
keepNames: true,
outDir,
format: 'cjs',
skipNodeModulesBundle: true,
});
}

View File

@ -0,0 +1,33 @@
import gulp from 'gulp';
import path from 'path';
import fs from 'fs';
import gulpTs from 'gulp-typescript';
import { ROOT_PATH } from './constant';
export const buildDeclaration = (cwd: string, targetDir: string) => {
return new Promise((resolve, reject) => {
const srcPath = path.join(cwd, 'src');
const targetPath = path.join(cwd, targetDir);
const tsConfig = gulpTs.createProject(path.join(ROOT_PATH, 'tsconfig.json'));
delete tsConfig.config.compilerOptions.paths;
const patterns = [
path.join(srcPath, '**/*.{ts,tsx}'),
`!${path.join(srcPath, '**/fixtures{,/**}')}`,
`!${path.join(srcPath, '**/demos{,/**}')}`,
`!${path.join(srcPath, '**/__test__{,/**}')}`,
`!${path.join(srcPath, '**/__tests__{,/**}')}`,
`!${path.join(srcPath, '**/*.mdx')}`,
`!${path.join(srcPath, '**/*.md')}`,
`!${path.join(srcPath, '**/*.+(test|e2e|spec).+(js|jsx|ts|tsx)')}`,
`!${path.join(srcPath, '**/tsconfig{,.*}.json')}`,
`!${path.join(srcPath, '.umi{,-production,-test}{,/**}')}`,
];
gulp
.src(patterns, { base: srcPath, allowEmpty: true })
.pipe(gulpTs(tsConfig.config.compilerOptions))
.dts.pipe(gulp.dest(targetPath))
.on('end', resolve)
.on('error', reject);
});
};

View File

@ -1,26 +1,30 @@
import ncc from '@vercel/ncc';
import react from '@vitejs/plugin-react';
import chalk from 'chalk';
import fg from 'fast-glob';
import fs from 'fs-extra'; import fs from 'fs-extra';
import chalk from 'chalk';
import ncc from '@vercel/ncc';
import path from 'path'; import path from 'path';
import react from '@vitejs/plugin-react';
import { build as tsupBuild } from 'tsup'; import { build as tsupBuild } from 'tsup';
import { build as viteBuild } from 'vite'; import { build as viteBuild } from 'vite';
import fg from 'fast-glob';
import cssInjectedByJsPlugin from 'vite-plugin-css-injected-by-js'; import cssInjectedByJsPlugin from 'vite-plugin-css-injected-by-js';
import { transformAsync } from '@babel/core';
import { import {
buildCheck, buildCheck,
formatFileSize, checkFileSize,
checkRequire,
getExcludePackages, getExcludePackages,
getFileSize,
getIncludePackages, getIncludePackages,
getPackageJson, getPackagesFromFiles,
getSourcePackages, getSourcePackages,
} from './utils/buildPluginUtils'; } from './utils/buildPluginUtils';
import { getDepsConfig } from './utils/getDepsConfig'; import { getDepsConfig } from './utils/getDepsConfig';
import { EsbuildSupportExts, globExcludeFiles } from './constant';
import { PkgLog, getPackageJson } from './utils';
const serverGlobalFiles: string[] = ['src/**', '!src/**/__tests__', '!src/client/**']; const serverGlobalFiles: string[] = ['src/**', '!src/client/**', ...globExcludeFiles];
const clientGlobalFiles: string[] = ['src/**', '!src/server/**', ...globExcludeFiles];
const clientGlobalFiles: string[] = ['src/client/**', '!src/**/__tests__']; const dynamicImportRegexp = /import\((["'])(.*?)\1\)/g;
const external = [ const external = [
// nocobase // nocobase
@ -116,17 +120,14 @@ const external = [
'ahooks', 'ahooks',
'lodash', 'lodash',
'china-division', 'china-division',
'cronstrue',
]; ];
const pluginPrefix = ( const pluginPrefix = (
process.env.PLUGIN_PACKAGE_PREFIX || '@nocobase/plugin-,@nocobase/preset-,@nocobase/plugin-pro-' process.env.PLUGIN_PACKAGE_PREFIX || '@nocobase/plugin-,@nocobase/preset-,@nocobase/plugin-pro-'
).split(','); ).split(',');
type Log = (msg: string, ...args: any) => void;
const target_dir = 'dist'; const target_dir = 'dist';
export function deleteJsFiles(cwd: string, log: Log) { export function deleteJsFiles(cwd: string, log: PkgLog) {
log('delete babel js files'); log('delete babel js files');
const jsFiles = fg.globSync(['**/*', '!**/*.d.ts', '!node_modules'], { const jsFiles = fg.globSync(['**/*', '!**/*.d.ts', '!node_modules'], {
cwd: path.join(cwd, target_dir), cwd: path.join(cwd, target_dir),
@ -137,23 +138,24 @@ export function deleteJsFiles(cwd: string, log: Log) {
}); });
} }
export async function buildServerDeps(cwd: string, serverFiles: string[], log: Log) { export async function buildServerDeps(cwd: string, serverFiles: string[], log: PkgLog) {
log('build server dependencies'); log('build plugin server dependencies');
const outDir = path.join(cwd, target_dir, 'node_modules'); const outDir = path.join(cwd, target_dir, 'node_modules');
const sourcePackages = getSourcePackages(serverFiles); const serverFileSource = serverFiles.map((item) => fs.readFileSync(item, 'utf-8'));
const sourcePackages = getSourcePackages(serverFileSource);
const includePackages = getIncludePackages(sourcePackages, external, pluginPrefix); const includePackages = getIncludePackages(sourcePackages, external, pluginPrefix);
const excludePackages = getExcludePackages(sourcePackages, external, pluginPrefix); const excludePackages = getExcludePackages(sourcePackages, external, pluginPrefix);
let tips = []; let tips = [];
if (includePackages.length) { if (includePackages.length) {
tips.push( tips.push(
`These packages ${chalk.yellow(includePackages.join(', '))} will be ${chalk.bold( `These packages ${chalk.yellow(includePackages.join(', '))} will be ${chalk.italic(
'bundled', 'bundled',
)} to dist/node_modules.`, )} to dist/node_modules.`,
); );
} }
if (excludePackages.length) { if (excludePackages.length) {
tips.push(`These packages ${chalk.yellow(excludePackages.join(', '))} will be ${chalk.bold('exclude')}.`); tips.push(`These packages ${chalk.yellow(excludePackages.join(', '))} will be ${chalk.italic('exclude')}.`);
} }
tips.push(`For more information, please refer to: ${chalk.blue('https://docs.nocobase.com/development/deps')}.`); tips.push(`For more information, please refer to: ${chalk.blue('https://docs.nocobase.com/development/deps')}.`);
log(tips.join(' ')); log(tips.join(' '));
@ -228,11 +230,15 @@ export async function buildServerDeps(cwd: string, serverFiles: string[], log: L
} }
} }
export async function buildPluginServer(cwd: string, log: Log) { export async function buildPluginServer(cwd: string, sourcemap: boolean, log: PkgLog) {
log('build server source'); log('build plugin server source');
const packageJson = getPackageJson(cwd); const packageJson = getPackageJson(cwd);
const serverFiles = fg.globSync(serverGlobalFiles, { cwd, absolute: true }); const serverFiles = fg.globSync(serverGlobalFiles, { cwd, absolute: true });
buildCheck({ cwd, packageJson, entry: 'server', files: serverFiles, log }); buildCheck({ cwd, packageJson, entry: 'server', files: serverFiles, log });
const otherExts = Array.from(new Set(serverFiles.map((item) => path.extname(item)).filter((item) => !EsbuildSupportExts.includes(item))));
if (otherExts.length) {
log('%s will not be processed, only be copied to the dist directory.', chalk.yellow(otherExts.join(',')));
}
await tsupBuild({ await tsupBuild({
entry: serverFiles, entry: serverFiles,
@ -240,26 +246,73 @@ export async function buildPluginServer(cwd: string, log: Log) {
clean: false, clean: false,
bundle: false, bundle: false,
silent: true, silent: true,
treeshake: true, treeshake: false,
target: 'node16', target: 'node16',
sourcemap,
outDir: path.join(cwd, target_dir), outDir: path.join(cwd, target_dir),
format: 'cjs', format: 'cjs',
skipNodeModulesBundle: true, skipNodeModulesBundle: true,
loader: {
...otherExts.reduce((prev, cur) => ({ ...prev, [cur]: 'copy' }), {}),
'.json': 'copy',
},
}); });
await buildServerDeps(cwd, serverFiles, log); await buildServerDeps(cwd, serverFiles, log);
} }
export function buildPluginClient(cwd: string, log: Log) { export function transformClientFilesToAmd(outDir: string, outputFileName: string, packageName: string, log: PkgLog) {
log('build client'); const files = fs.readdirSync(outDir);
return Promise.all(files.map(async file => {
const filePath = path.join(outDir, file);
// 只编译 JavaScript 文件
if (path.extname(filePath) === '.mjs' || path.extname(filePath) === '.js') {
let fileContent = fs.readFileSync(filePath, 'utf-8');
// import('./dayjs.mjs') => import(window.staticBaseUrl + '/@nocobase/plugin-acl/dayjs.mjs?noExt')
if (file === outputFileName) {
fileContent = fileContent.replace(dynamicImportRegexp, (match, _1, dynamicImportPath) => {
let absolutePath = path.join(outDir, dynamicImportPath).replace(outDir, '');
if (absolutePath.startsWith(path.sep)) {
absolutePath = absolutePath.slice(1);
}
return `import(window.staticBaseUrl + '/${packageName}/${absolutePath}?noExt')`;
})
}
const { code } = await transformAsync(fileContent, {
presets: [['@babel/preset-env', {
modules: 'amd',
targets: {
'edge': 88,
'firefox': 78,
'chrome': 87,
'safari': 14,
}
}]],
plugins: ['@babel/plugin-transform-modules-amd'],
});
fs.writeFileSync(filePath, code, 'utf-8');
}
}));
}
export async function buildPluginClient(cwd: string, sourcemap: boolean, log: PkgLog) {
log('build plugin client');
const packageJson = getPackageJson(cwd); const packageJson = getPackageJson(cwd);
const clientFiles = fg.globSync(clientGlobalFiles, { cwd, absolute: true }); const clientFiles = fg.globSync(clientGlobalFiles, { cwd, absolute: true });
const sourcePackages = getSourcePackages(clientFiles); const clientFileSource = clientFiles.map((item) => fs.readFileSync(item, 'utf-8'));
const sourcePackages = getPackagesFromFiles(clientFileSource);
const excludePackages = getExcludePackages(sourcePackages, external, pluginPrefix); const excludePackages = getExcludePackages(sourcePackages, external, pluginPrefix);
checkRequire(clientFiles, log);
buildCheck({ cwd, packageJson, entry: 'client', files: clientFiles, log }); buildCheck({ cwd, packageJson, entry: 'client', files: clientFiles, log });
const hasDynamicImport = false;
// const hasDynamicImport = clientFileSource.some((source) => {
// return source.match(dynamicImportRegexp);
// });
const outDir = path.join(cwd, target_dir, 'client'); const outDir = path.join(cwd, target_dir, 'client');
const globals = excludePackages.reduce<Record<string, string>>((prev, curr) => { const globals = excludePackages.reduce<Record<string, string>>((prev, curr) => {
@ -270,48 +323,63 @@ export function buildPluginClient(cwd: string, log: Log) {
return prev; return prev;
}, {}); }, {});
const entry = fg.globSync('src/client/index.{ts,tsx,js,jsx}', { cwd }); const entry = fg.globSync('src/client/index.{ts,tsx,js,jsx}', { absolute: true, cwd });
const outputFileName = 'index.js'; const outputFileName = 'index.js';
return viteBuild({ await viteBuild({
mode: 'production', mode: 'production',
define: { define: {
'process.env.NODE_ENV': JSON.stringify('production'), 'process.env.NODE_ENV': JSON.stringify('production'),
}, },
logLevel: 'warn', logLevel: 'warn',
build: { build: {
minify: false, minify: true,
outDir, outDir,
cssCodeSplit: false, cssCodeSplit: false,
emptyOutDir: false, emptyOutDir: true,
sourcemap,
lib: { lib: {
entry, entry,
formats: ['umd'], formats: [hasDynamicImport ? 'es' : 'umd'],
name: packageJson.name, name: packageJson.name,
fileName: () => outputFileName, fileName: () => outputFileName,
}, },
target: ['es2015', 'edge88', 'firefox78', 'chrome87', 'safari14'],
rollupOptions: { rollupOptions: {
cache: true, cache: true,
external: Object.keys(globals), external: [...Object.keys(globals), 'react', 'react/jsx-runtime'],
output: { output: {
exports: 'named', exports: 'named',
globals, globals: {
react: 'React',
'react/jsx-runtime': 'jsxRuntime',
...globals,
},
}, },
}, },
}, },
plugins: [ plugins: [
react(), react(),
cssInjectedByJsPlugin({ styleId: packageJson.name }), cssInjectedByJsPlugin({ styleId: packageJson.name }),
{
name: 'check-file-size',
closeBundle() {
const file = path.join(outDir, outputFileName);
if (!fs.existsSync(file)) return;
const fileSize = getFileSize(path.join(outDir, outputFileName));
if (fileSize > 1024 * 1024) {
log('The bundle file size exceeds 1MB %s. ', chalk.red(formatFileSize(fileSize)));
}
},
},
], ],
}); });
checkFileSize(outDir, log);
// if (hasDynamicImport) {
// await transformClientFilesToAmd(outDir, outputFileName, packageJson.name, log);
// }
}
export async function buildPlugin(cwd: string, sourcemap: boolean, log: PkgLog) {
await buildPluginClient(cwd, sourcemap, log);
await buildPluginServer(cwd, sourcemap, log);
const buildFile = path.join(cwd, 'build.js');
if (fs.existsSync(buildFile)) {
log('build others');
try {
await require(buildFile).run(log);
} catch (error) {
console.error(error);
}
}
} }

View File

@ -0,0 +1,46 @@
import path from 'path';
import { Package } from '@lerna/package';
export const globExcludeFiles = [
'!src/**/__tests__',
'!src/**/__test__',
'!src/**/demos',
'!src/**/fixtures',
'!src/**/*.mdx',
'!src/**/*.md',
'!src/**/*.+(test|e2e|spec).+(js|jsx|ts|tsx)',
];
export const EsbuildSupportExts = [
'.ts',
'.tsx',
'.js',
'.jsx',
'.json',
'.css',
'.less',
'.sass',
'.scss',
'.styl',
'.txt',
'.data',
];
export const ROOT_PATH = path.join(__dirname, '../../../../');
export const PACKAGES_PATH = path.join(ROOT_PATH, 'packages');
export const PLUGINS_DIR = [path.join(PACKAGES_PATH, 'plugins'), path.join(PACKAGES_PATH, 'samples')];
export const PRESETS_DIR = path.join(PACKAGES_PATH, 'presets');
export const getPluginPackages = (packages: Package[]) =>
packages.filter((item) => PLUGINS_DIR.some((pluginDir) => item.location.startsWith(pluginDir)));
export const getPresetsPackages = (packages: Package[]) =>
packages.filter((item) => item.location.startsWith(PRESETS_DIR));
export const CORE_APP = path.join(PACKAGES_PATH, 'core/app');
export const CORE_CLIENT = path.join(PACKAGES_PATH, 'core/client');
export const CJS_EXCLUDE_PACKAGES = [
path.join(PACKAGES_PATH, 'core/build'),
path.join(PACKAGES_PATH, 'core/cli'),
CORE_CLIENT,
];
export const getCjsPackages = (packages: Package[]) =>
packages
.filter((item) => !PLUGINS_DIR.some((dir) => item.location.startsWith(dir)))
.filter((item) => !item.location.startsWith(PRESETS_DIR))
.filter((item) => !CJS_EXCLUDE_PACKAGES.includes(item.location));

View File

@ -1,33 +0,0 @@
// 参考:
// https://github.com/umijs/umi/blob/2.x/packages/af-webpack/src/getWebpackConfig/es5ImcompatibleVersions.js
import { dirname } from 'path';
import pkgUp from 'pkg-up';
import { satisfies } from 'semver';
const pkgPathCache = {};
const pkgCache = {};
const {
config: { 'es5-imcompatible-versions': config },
} = require('es5-imcompatible-versions/package.json');
export function getPkgPath(filePath: string) {
const dir = dirname(filePath);
if (dir in pkgPathCache) return pkgPathCache[dir];
pkgPathCache[dir] = pkgUp.sync({ cwd: filePath });
return pkgPathCache[dir];
}
export function shouldTransform(pkgPath: string) {
if (pkgPath in pkgCache) return pkgCache[pkgPath];
const { name, version } = require(pkgPath);
pkgCache[pkgPath] = isMatch(name, version);
return pkgCache[pkgPath];
}
function isMatch(name, version) {
if (config[name]) {
return Object.keys(config[name]).some((sv) => satisfies(version, sv));
} else {
return false;
}
}

View File

@ -1,8 +0,0 @@
export default {
esm: 'babel',
target: 'node',
browserFiles: [
'src/browser.js',
],
}

View File

@ -1,2 +0,0 @@
const a = 'foo';

View File

@ -1,8 +0,0 @@
export default {
target: 'node',
cjs: { type: 'babel', lazy: true },
browserFiles: [
'src/foo.js',
],
};

View File

@ -1,3 +0,0 @@
import bar from 'bar';
bar();

View File

@ -1,3 +0,0 @@
import bar from 'bar';
bar();

View File

@ -1,10 +0,0 @@
export default {
esm: { type: 'babel' },
extraBabelPresets: [
require.resolve('./preset'),
],
extraBabelPlugins: [
require.resolve('./p2'),
],
};

View File

@ -1,2 +0,0 @@
console.log("p1", "p2", 1);
alert(2);

View File

@ -1,20 +0,0 @@
module.exports = function ({ types: t }) {
function isConsoleLog(node) {
const { callee, callee: { object, property } } = node;
return t.isMemberExpression(callee)
&& t.isIdentifier(object) && object.name === 'console'
&& t.isIdentifier(property) && property.name === 'log';
}
return {
visitor: {
CallExpression(path, state) {
const { node, node: { callee, callee: { object, property } } } = path;
if (isConsoleLog(node)) {
node.arguments.unshift(t.stringLiteral('p1'));
}
},
},
};
}

View File

@ -1,20 +0,0 @@
module.exports = function ({ types: t }) {
function isConsoleLog(node) {
const { callee, callee: { object, property } } = node;
return t.isMemberExpression(callee)
&& t.isIdentifier(object) && object.name === 'console'
&& t.isIdentifier(property) && property.name === 'log';
}
return {
visitor: {
CallExpression(path, state) {
const { node, node: { callee, callee: { object, property } } } = path;
if (isConsoleLog(node)) {
node.arguments.unshift(t.stringLiteral('p2'));
}
},
},
};
}

View File

@ -1,8 +0,0 @@
module.exports = function () {
return {
plugins: [
require.resolve('./p1'),
],
};
}

View File

@ -1,5 +0,0 @@
export default {
cjs: { type: 'babel' },
esm: { type: 'babel', importLibToEs: true },
};

View File

@ -1,2 +0,0 @@
import foo from "foo/es/foo";
console.log(foo());

View File

@ -1,3 +0,0 @@
import foo from 'foo/lib/foo';
console.log(foo());

View File

@ -1,6 +0,0 @@
export default {
cjs: { type: 'babel' },
esm: { type: 'babel' },
lessInBabelMode: false,
};

View File

@ -1,6 +0,0 @@
import "./index.less";
import "./foo.module.less";
export default function () {
return 'foo';
}

View File

@ -1,6 +0,0 @@
@link-color: green;
.foo {
color: @link-color;
}

View File

@ -1,6 +0,0 @@
import "./index.less";
import "./foo.module.less";
export default function () {
return 'foo';
}

View File

@ -1,6 +0,0 @@
@link-color: green;
.foo {
color: @link-color;
}

View File

@ -1,6 +0,0 @@
export default {
cjs: { type: 'babel' },
esm: { type: 'babel' },
lessInBabelMode: true,
};

View File

@ -1,6 +0,0 @@
import "./index.css";
import "./foo.module.css";
export default function () {
return 'foo';
}

View File

@ -1,3 +0,0 @@
.foo {
color: green;
}

View File

@ -1,6 +0,0 @@
import "./index.less";
import "./foo.module.less";
export default function () {
return 'foo';
}

View File

@ -1,3 +0,0 @@
.foo {
color: green;
}

View File

@ -1,6 +0,0 @@
@link-color: green;
.foo {
color: @link-color;
}

View File

@ -1,8 +0,0 @@
export default {
esm: 'babel',
target: 'browser',
nodeFiles: [
'src/node.js',
],
}

View File

@ -1,2 +0,0 @@
const b = 'foo';

View File

@ -1,2 +0,0 @@
const a = 'foo';

View File

@ -1,5 +0,0 @@
export default {
esm: 'babel',
target: 'node',
}

View File

@ -1 +0,0 @@
declare const b = "foo";

View File

@ -1,2 +0,0 @@
const b = 'foo';

View File

@ -1,2 +0,0 @@
const b = 'foo';

View File

@ -1,2 +0,0 @@
const a = 'foo';

View File

@ -1,9 +0,0 @@
{
"compilerOptions": {
"target": "esnext",
"moduleResolution": "node",
"jsx": "preserve",
"esModuleInterop": true,
"declaration": true
}
}

View File

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

View File

@ -1,3 +0,0 @@
export default function () {
return 'foo';
}

View File

@ -1,3 +0,0 @@
import 'bar';
import foo from './foo';
console.log(foo());

View File

@ -1,3 +0,0 @@
export default function() {
return "foo";
}

View File

@ -1,4 +0,0 @@
export default function () {
return 'foo';
}

View File

@ -1,4 +0,0 @@
import 'bar';
import foo from './foo';
console.log(foo());

View File

@ -1,6 +0,0 @@
export default {
runtimeHelpers: true,
esm: { type: 'babel' },
cjs: { type: 'babel' },
}

View File

@ -1,21 +0,0 @@
import _objectSpread from "@babel/runtime/helpers/esm/objectSpread2";
import _classCallCheck from "@babel/runtime/helpers/esm/classCallCheck";
import _createClass from "@babel/runtime/helpers/esm/createClass";
var A = /*#__PURE__*/function () {
function A() {
_classCallCheck(this, A);
}
_createClass(A, [{
key: "foo",
value: function foo() {}
}]);
return A;
}();
new A().foo();
var a = {};
var b = _objectSpread({}, a);

View File

@ -1,5 +0,0 @@
{
"dependencies": {
"@babel/runtime": "7.10.4"
}
}

View File

@ -1,9 +0,0 @@
class A {
foo() {}
}
(new A()).foo();
const a = {};
const b = {...a};

View File

@ -1,4 +0,0 @@
export default {
esm: { type: 'rollup' },
};

View File

@ -1,84 +0,0 @@
import React from 'react';
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
function _objectWithoutPropertiesLoose(source, excluded) {
if (source == null) return {};
var target = {};
var sourceKeys = Object.keys(source);
var key, i;
for (i = 0; i < sourceKeys.length; i++) {
key = sourceKeys[i];
if (excluded.indexOf(key) >= 0) continue;
target[key] = source[key];
}
return target;
}
function _objectWithoutProperties(source, excluded) {
if (source == null) return {};
var target = _objectWithoutPropertiesLoose(source, excluded);
var key, i;
if (Object.getOwnPropertySymbols) {
var sourceSymbolKeys = Object.getOwnPropertySymbols(source);
for (i = 0; i < sourceSymbolKeys.length; i++) {
key = sourceSymbolKeys[i];
if (excluded.indexOf(key) >= 0) continue;
if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue;
target[key] = source[key];
}
}
return target;
}
function a () {
alert('a');
}
function b1() {
alert('b1');
}
function b2() {
alert('b2');
}
var b = /*#__PURE__*/Object.freeze({
__proto__: null,
b1: b1,
b2: b2
});
var _class;
// babel-plugin-react-require
var Foo = function Foo() {
return /*#__PURE__*/React.createElement("div", null);
}; // Don't support multiple chunks for now
// @babel/plugin-syntax-dynamic-import
// import('./a');
// object-rest-spread
var _bar = bar,
foo = _bar.foo,
z = _objectWithoutProperties(_bar, ["foo"]);
console.log(z); // @babel/plugin-proposal-decorators + class
var A = foo(_class = function A() {
_classCallCheck(this, A);
}) || _class; // export default from
var a$1 = x > 10 ? 'big' : 'small';
console.log(a$1); // export namespace from
export { A, Foo, a, b };

View File

@ -1,5 +0,0 @@
{
"dependencies": {
"react": "^18.0.0"
}
}

View File

@ -1,4 +0,0 @@
export default function () {
alert('a');
}

View File

@ -1,8 +0,0 @@
export function b1() {
alert('b1');
}
export function b2() {
alert('b2');
}

View File

@ -1,32 +0,0 @@
// babel-plugin-react-require
export const Foo = () => <div />;
// Don't support multiple chunks for now
// @babel/plugin-syntax-dynamic-import
// import('./a');
// object-rest-spread
const { foo, ...z } = bar;
console.log(z);
// @babel/plugin-proposal-decorators + class
@foo
export class A {}
// export default from
export a from './a';
// do expression
let a = do {
if(x > 10) {
'big';
} else {
'small';
}
};
console.log(a);
// export namespace from
export * as b from './b';

View File

@ -1,4 +0,0 @@
export default {
cjs: { type: 'babel' },
esm: { type: 'babel' },
};

View File

@ -1,2 +0,0 @@
export declare const optionalChaining: string;
export declare const nullishCoalescing: string | boolean;

View File

@ -1,6 +0,0 @@
var _foo$test;
var foo = {};
export var optionalChaining = foo === null || foo === void 0 ? void 0 : (_foo$test = foo.test) === null || _foo$test === void 0 ? void 0 : _foo$test.abc;
var bar = false;
export var nullishCoalescing = bar !== null && bar !== void 0 ? bar : 'default';

View File

@ -1,11 +0,0 @@
type Foo = {
test?: {
abc?: string;
}
}
const foo: Foo = {};
export const optionalChaining = foo?.test?.abc;
const bar = false;
export const nullishCoalescing = bar ?? 'default';

View File

@ -1,6 +0,0 @@
export default {
cjs: { type: 'babel' },
esm: { type: 'babel' },
disableTypeCheck: true,
};

View File

@ -1,3 +0,0 @@
export default function foo(opts) {
return opts.foo ? 'foo' : 'bar';
}

View File

@ -1,7 +0,0 @@
interface IOpts {
foo: boolean;
}
export default function foo(opts: IOpts): string {
return opts.foo ? 'foo' : 'bar';
}

View File

@ -1,8 +0,0 @@
{
"compilerOptions": {
"target": "esnext",
"moduleResolution": "node",
"jsx": "preserve",
"esModuleInterop": true
}
}

Some files were not shown because too many files have changed in this diff Show More