190 lines
7.0 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import fs from 'node:fs';
import { createRequire } from 'node:module';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { build as buildVite } from 'vite';
import vue from '@vitejs/plugin-vue';
import minimist from 'minimist';
import rimraf from 'rimraf';
import * as sass from 'sass-embedded';
const args = minimist(process.argv.slice(2));
const toPascalCase = (str) => str.replace(/(^\w|-\w)/g, (text) => text.replace(/-/, '').toUpperCase());
const dirname = path.dirname(fileURLToPath(import.meta.url));
const packagesDir = path.resolve(dirname, '../packages');
const runtimeDir = path.resolve(dirname, '../runtime');
if (args.package) {
const pkgRoot = path.resolve(packagesDir, args.package);
if (fs.statSync(pkgRoot).isDirectory()) {
rimraf.sync(path.resolve(packagesDir, `./${args.package}/dist`));
const pkg = createRequire(import.meta.url)(`../packages/${args.package}/package.json`);
build({ packageName: args.package, format: 'es', pkg, packagesDir });
build({ packageName: args.package, format: 'umd', pkg, packagesDir });
buildThemes({ packageName: args.package, packagesDir });
}
} else {
const packages = getPackageNames(packagesDir);
const runtimeHelpers = getPackageNames(runtimeDir);
for (const packageName of packages) {
rimraf.sync(path.resolve(packagesDir, `./${packageName}/dist`));
const pkg = createRequire(import.meta.url)(`../packages/${packageName}/package.json`);
build({ packageName, format: 'es', pkg, packagesDir });
build({ packageName, format: 'umd', pkg, packagesDir });
buildThemes({ packageName, packagesDir });
}
for (const packageName of runtimeHelpers) {
rimraf.sync(path.resolve(runtimeDir, `./${packageName}/dist`));
const pkg = createRequire(import.meta.url)(`../runtime/${packageName}/package.json`);
build({ packageName, format: 'es', pkg, packagesDir: runtimeDir });
build({ packageName, format: 'umd', pkg, packagesDir: runtimeDir });
}
}
// rolldown 在 UMD 输出顶部会注入
// Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
// 当内联的依赖(如 lodash-es 的 _Symbol.js声明 `var Symbol = root.Symbol;`
// 时,由于 var hoisting该局部 `Symbol` 会把上面一行引用到的全局 `Symbol`
// 遮蔽掉(此时局部变量还未赋值),运行时抛出
// TypeError: Cannot read properties of undefined (reading 'toStringTag')
// 这里通过后处理把该引用改为 `globalThis.Symbol.toStringTag`,绕开被 hoist
// 的局部绑定。rolldown 修好前先用此 workaround。
function fixUmdSymbolShadow() {
return {
name: 'tmagic:fix-umd-symbol-shadow',
generateBundle(outputOptions, bundle) {
if (outputOptions.format !== 'umd') return;
for (const file of Object.values(bundle)) {
if (file.type !== 'chunk' || typeof file.code !== 'string') continue;
file.code = file.code.replace(
/Object\.defineProperty\(exports,\s*Symbol\.toStringTag,/g,
'Object.defineProperty(exports, globalThis.Symbol.toStringTag,',
);
}
},
};
}
async function build({ packageName, format, pkg, packagesDir }) {
await buildVite({
root: path.resolve(packagesDir, `./${packageName}`),
clearScreen: false,
configFile: false,
plugins: [vue()],
build: {
outDir: format === 'es' ? 'dist/es' : 'dist',
emptyOutDir: false,
cssCodeSplit: false,
sourcemap: false,
minify: false,
target: 'esnext',
lib: {
entry: 'src/index.ts',
name: `TMagic${toPascalCase(packageName)}`,
fileName: `tmagic-${packageName}`,
formats: [format],
cssFileName: 'style',
},
rolldownOptions: {
plugins: [fixUmdSymbolShadow()],
// 确保外部化处理那些你不想打包进库的依赖
external(id) {
if (format === 'umd' && id === 'lodash-es') {
return false;
}
return Object.keys({
...pkg.dependencies,
...pkg.peerDependencies,
}).some((k) => new RegExp(`^${k}`).test(id));
},
output: {
// 在 UMD 构建模式下为这些外部化的依赖提供一个全局变量
globals: {
vue: 'Vue',
'element-plus': 'ElementPlus',
},
// ES 格式保留模块结构,让消费者的 bundler 按模块粒度 tree-shake
...(format === 'es'
? {
preserveModules: true,
preserveModulesRoot: 'src',
entryFileNames: '[name].js',
}
: {}),
},
},
},
resolve: {
alias: [
{ find: /^@data-source/, replacement: path.join(packagesDir, '/data-source/src') },
{ find: /^@editor/, replacement: path.join(packagesDir, './editor/src') },
],
},
});
}
/**
* 扫描 `<package>/src/theme/themes/<theme>/index.scss`
* 为每个主题单独编译输出 `<package>/dist/themes/<theme>.css`。
*
* 主题样式独立于主样式包dist/style.css消费者通过
* `import '@tmagic/<pkg>/dist/themes/<theme>.css'` 按需引入,
* 并在 `<MEditor :theme="..." />` / `<MForm :theme="..." />`
* 上挂载对应的 `m-editor--<theme>` / `m-form--<theme>` 修饰类后生效。
*/
function buildThemes({ packageName, packagesDir }) {
const themesDir = path.resolve(packagesDir, `./${packageName}/src/theme/themes`);
if (!fs.existsSync(themesDir)) return;
const themes = fs.readdirSync(themesDir).filter((name) => {
const themePath = path.resolve(themesDir, name);
return fs.statSync(themePath).isDirectory() && fs.existsSync(path.resolve(themePath, 'index.scss'));
});
if (themes.length === 0) return;
const outputDir = path.resolve(packagesDir, `./${packageName}/dist/themes`);
fs.mkdirSync(outputDir, { recursive: true });
// 把 `<pkg>/node_modules` 也加到 loadPaths使主题 SCSS 内
// `@use "@tmagic/<pkg>/src/.../index.scss"` 能通过 pnpm 在 node_modules
// 下的 symlink 解析到对应的源码包。
const nodeModulesDir = path.resolve(packagesDir, `./${packageName}/node_modules`);
const loadPaths = [path.resolve(packagesDir, `./${packageName}/src`)];
if (fs.existsSync(nodeModulesDir)) loadPaths.push(nodeModulesDir);
for (const theme of themes) {
const input = path.resolve(themesDir, theme, 'index.scss');
const result = sass.compile(input, {
style: 'expanded',
sourceMap: false,
loadPaths: [path.resolve(themesDir, theme), ...loadPaths],
});
fs.writeFileSync(path.resolve(outputDir, `${theme}.css`), result.css);
console.log(`[themes] ${packageName}: ${theme}.css`);
}
}
function getPackageNames(packagesDir) {
return fs.readdirSync(packagesDir).filter((p) => {
const pkgRoot = path.resolve(packagesDir, p);
if (fs.statSync(pkgRoot).isDirectory()) {
const pkg = JSON.parse(fs.readFileSync(path.resolve(pkgRoot, 'package.json'), 'utf-8'));
return !pkg.private && !pkg.bin;
}
return false;
});
}