UNPKG

@codelet/cli-service

Version:
265 lines (264 loc) 11 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.createOptimization = exports.RewriteExternalRequestPlugin = exports.createWatchIgnored = exports.createNpmDirRequestResolver = exports.createExternalRequestResolver = exports.createNpmDirCopyPatterns = exports.createExternalCopyPatterns = exports.resolveExternalFiles = exports.parseDir = exports.resolveSourceRoot = exports.resolveEntryRoot = exports.resolve = exports.externalRequestPlaceholderPrefix = exports.cmd = void 0; exports.getOptionValue = getOptionValue; exports.parseArgv = parseArgv; exports.getConfig = getConfig; const fast_glob_1 = __importDefault(require("fast-glob")); const fs_1 = __importDefault(require("fs")); const path_1 = __importDefault(require("path")); const webpack_1 = require("webpack"); const webpack_merge_1 = __importDefault(require("webpack-merge")); const config_1 = require("./config"); exports.cmd = process.cwd(); exports.externalRequestPlaceholderPrefix = '__CODELET_EXTERNAL__/'; const resolve = (...args) => path_1.default.resolve(exports.cmd, ...args); exports.resolve = resolve; const resolveEntryRoot = (entryPath) => (0, exports.resolve)(entryPath); exports.resolveEntryRoot = resolveEntryRoot; const resolveSourceRoot = (entryPath) => (0, exports.resolveEntryRoot)(entryPath); exports.resolveSourceRoot = resolveSourceRoot; const parseDir = (entryPath, source) => { entryPath = (0, exports.resolveEntryRoot)(entryPath); const filepaths = fast_glob_1.default.sync(source.map((item) => (0, exports.resolve)(entryPath, item).replace(/\\/g, '/'))); const entry = filepaths.reduce((res, filepath) => { const relPath = path_1.default.relative(entryPath, filepath); const { dir, name } = path_1.default.parse(relPath); res[path_1.default.join(dir, name)] = { import: filepath, runtime: 'runtime', }; return res; }, {}); return { entry }; }; exports.parseDir = parseDir; const resolveExternalFiles = (entryPath, externalSource) => { const entryRoot = (0, exports.resolveEntryRoot)(entryPath); return fast_glob_1.default .sync(externalSource.map((item) => path_1.default.join(entryRoot, item).replace(/\\/g, '/'))) .map((filepath) => path_1.default.resolve(filepath)); }; exports.resolveExternalFiles = resolveExternalFiles; const createExternalCopyPatterns = (entryPath, externalSource) => { const entryRoot = (0, exports.resolveEntryRoot)(entryPath); return externalSource.map((pattern) => ({ context: entryRoot, from: pattern, to: '[path][name][ext]', noErrorOnMissing: true, info: { minimized: true, }, })); }; exports.createExternalCopyPatterns = createExternalCopyPatterns; const createNpmDirCopyPatterns = (npmDir) => { if (!npmDir) { return []; } return [ { from: (0, exports.resolve)(npmDir), to: path_1.default.basename(npmDir), noErrorOnMissing: true, info: { minimized: true, }, }, ]; }; exports.createNpmDirCopyPatterns = createNpmDirCopyPatterns; const createExternalRequestResolver = (options) => { const entryRoot = (0, exports.resolveEntryRoot)(options.entryPath); const externalFileSet = new Set(options.externalFiles); return (context, request) => { const candidates = []; // 同时支持 "@/libs/foo" 这类别名引用和 "./libs/foo" 这类相对引用。 if (request.startsWith('@/')) { candidates.push(path_1.default.join(entryRoot, request.slice(2))); } else if (request.startsWith('.')) { candidates.push(path_1.default.resolve(context, request)); } else { return ''; } for (const basePath of candidates) { for (const ext of ['', '.js']) { const candidate = ext ? `${basePath}${ext}` : basePath; if (externalFileSet.has(path_1.default.resolve(candidate))) { return path_1.default.resolve(candidate); } } } return ''; }; }; exports.createExternalRequestResolver = createExternalRequestResolver; const createNpmDirRequestResolver = (npmDir) => { const npmRoot = (0, exports.resolve)(npmDir); return (request) => { if (!request || request.startsWith('.') || request.startsWith('@/') || path_1.default.isAbsolute(request)) { return ''; } const requestPath = request.replace(/\\/g, '/'); const candidates = [ path_1.default.join(npmRoot, requestPath), path_1.default.join(npmRoot, `${requestPath}.js`), path_1.default.join(npmRoot, `${requestPath}.json`), path_1.default.join(npmRoot, requestPath, 'index.js'), path_1.default.join(npmRoot, requestPath, 'index.json'), ]; return candidates.some((candidate) => fs_1.default.existsSync(candidate)) ? request : ''; }; }; exports.createNpmDirRequestResolver = createNpmDirRequestResolver; const createWatchIgnored = (dirs) => { return dirs.filter(Boolean).map((dir) => (0, exports.resolve)(dir).replace(/\\/g, '/')); }; exports.createWatchIgnored = createWatchIgnored; class RewriteExternalRequestPlugin { apply(compiler) { compiler.hooks.thisCompilation.tap('RewriteExternalRequestPlugin', (compilation) => { compilation.hooks.processAssets.tap({ name: 'RewriteExternalRequestPlugin', stage: webpack_1.Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_COMPATIBILITY, }, (assets) => { for (const assetName of Object.keys(assets)) { if (!assetName.endsWith('.js')) { continue; } const source = assets[assetName].source().toString(); if (!source.includes(exports.externalRequestPlaceholderPrefix)) { continue; } // 基于最终产物文件的位置重新计算外部 require 路径, // 这样像 bundle.js 这类公共 chunk 也能拿到正确的运行时相对路径。 const rewritten = source.replace(/__CODELET_EXTERNAL__\/([^"'`]+)/g, (_match, target) => { return path_1.default .relative(path_1.default.dirname(assetName), target) .replace(/\\/g, '/') .replace(/^(?!\.)/, './'); }); assets[assetName] = new webpack_1.sources.RawSource(rewritten); } }); }); } } exports.RewriteExternalRequestPlugin = RewriteExternalRequestPlugin; const createOptimization = () => { const packageRootPattern = /[\\/]src[\\/]packages[\\/]([^\\/]+)[\\/]/; const getPackageBundleName = (name) => { const normalizedName = name.replace(/\\/g, '/'); if (normalizedName.startsWith('packages/')) { const [, packageName] = normalizedName.split('/'); if (packageName) { return `packages/${packageName}/bundle`; } } return null; }; const getModuleResource = (module) => { if (!module || typeof module !== 'object') { return ''; } return 'resource' in module && typeof module.resource === 'string' ? module.resource : ''; }; return { splitChunks: { chunks: 'all', minChunks: 2, minSize: 0, cacheGroups: { subpackage: { test(module) { return Boolean(getModuleResource(module).match(packageRootPattern)); }, name(module, chunks) { const match = getModuleResource(module).match(packageRootPattern); if (!match) { return 'bundle'; } const packageBundleName = `packages/${match[1]}/bundle`; const bundleNames = new Set(chunks .map((chunk) => chunk.name) .filter((name) => typeof name === 'string' && Boolean(name)) .map((name) => getPackageBundleName(name)) .filter((name) => typeof name === 'string' && Boolean(name))); return bundleNames.size <= 1 && bundleNames.has(packageBundleName) ? packageBundleName : 'bundle'; }, priority: 10, minChunks: 2, chunks: 'all', }, main: { name: 'bundle', minChunks: 2, chunks: 'all', }, }, }, }; }; exports.createOptimization = createOptimization; function getOptionValue(argv, option) { const index = argv.findIndex((item) => item === option); if (index > -1) { return argv[index + 1]; } } function parseArgv(argv) { const isWatch = argv.includes('--watch'); const isDev = argv.includes('dev'); const pageIndex = getOptionValue(argv, '--pageIndex') || ''; const configRelativePath = getOptionValue(argv, '--config') || 'codelet.config.js'; const configPath = (0, exports.resolve)(configRelativePath); return { configPath, isWatch, pageIndex, isDev }; } function getConfig(options) { const { configPath, isWatch } = options; let config; if (fs_1.default.existsSync(configPath)) { const cfg = require(configPath); if (typeof cfg === 'function') { config = (0, config_1.getDefaultConfig)(options); config = cfg(config); } else { if (options.pageIndex) { cfg.pageIndex = options.pageIndex; } if (options.isDev) { cfg.isDev = options.isDev; } config = (0, config_1.getDefaultConfig)(cfg); config = { ...config, ...cfg, ...(0, webpack_merge_1.default)(config.webpack ?? {}, cfg.webpack ?? {}), }; } } else { config = (0, config_1.getDefaultConfig)(options); } const { entryPath, source, webpack } = config; if (!webpack.entry) { const { entry } = (0, exports.parseDir)(entryPath, source); webpack.entry = entry; } if (isWatch) { webpack.watch = true; } return webpack; }