UNPKG

utquidem

Version:

The meta-framework suite designed from scratch for frontend-focused modern web development.

231 lines (197 loc) 6.2 kB
import path from 'path'; // eslint-disable-next-line node/no-deprecated-api import { parse as parseUrl } from 'url'; import { fs, chalk, signale as logger } from '@modern-js/utils'; import { Loader } from 'esbuild'; import { Server } from '@modern-js/server'; import type { IAppContext, NormalizedConfig } from '@modern-js/core'; import { CSS_REGEX, CSS_MODULE_REGEX, WEB_MODULES_DIR, META_DATA_FILE_NAME, BFF_API_DIR, } from './constants'; export const jsLangRE = /\.(jsx?|tsx?)($|\?)/; export const isJsRequest = (url: string): boolean => jsLangRE.test(url); export const isCSSRequest = (url: string): boolean => CSS_REGEX.test(url) || CSS_MODULE_REGEX.test(url); export const directCSSQueryRE = /(\?|&)direct(&|$)/; export const isDirectCSSRequest = (url: string): boolean => { if (!isCSSRequest(url)) { return false; } return directCSSQueryRE.test(url); }; export const assetQueryRE = /(\?|&)assets(&|$)/; export const isAssetRequest = (url: string): boolean => assetQueryRE.test(url); export const ensureLeadingSlash = (s: string) => s.startsWith('/') ? s : `/${s}`; export const cleanUrl = (s: string) => s.replace(/(\?.*$|#.*$)/, ''); export const isFunction = (o: unknown) => Object.prototype.toString.call(o) === '[object Function]'; export const hasBffPlugin = (appDirectory: string): boolean => { const packageJson = require(path.join(appDirectory, 'package.json')); const { dependencies, devDependencies } = packageJson; const hasPlugin = [ ...Object.keys(dependencies), ...Object.keys(devDependencies), ].some(name => name.includes('plugin-bff')); return hasPlugin; }; export const shouldUseBff = (appDirectory: string): boolean => { const existBffPlugin = hasBffPlugin(appDirectory); return ( fs.existsSync(path.resolve(appDirectory, BFF_API_DIR)) && existBffPlugin ); }; export const getBFFMiddleware = async ( config: NormalizedConfig, appContext: IAppContext, ) => { const server = new Server({ dev: true, apiOnly: true, config, pwd: appContext.appDirectory, plugins: appContext.plugins.filter(p => p.server).map(p => p.server), routes: appContext.serverRoutes as any, }); await server.init(); const handler = server.getRequestHandler(); return handler; }; export const replaceAsync = ( str: string, searchValue: RegExp, replacer: (match: string, $1: string) => Promise<string>, ) => { try { if (typeof replacer === 'function') { // 1. Run fake pass of `replace`, collect values from `replacer` calls // 2. Resolve them with `Promise.all` // 3. Run `replace` with resolved values const values: any[] = []; String.prototype.replace.call(str, searchValue, (...args) => { // eslint-disable-next-line prefer-spread values.push(replacer.apply(undefined, args as any)); return ''; }); return Promise.all(values).then(resolvedValues => String.prototype.replace.call(str, searchValue, () => resolvedValues.shift(), ), ); } else { return Promise.resolve( String.prototype.replace.call(str, searchValue, replacer), ); } } catch (error) { return Promise.reject(error); } }; export const addQuery = (urlPath: string, query: string) => { const { search, pathname } = parseUrl(urlPath); return search ? `${pathname!}${search}&${query}` : `${pathname!}?${query}`; }; const pkgMap: Map<string, string> = new Map(); export const findPackageJson = (file: string) => { const cached = pkgMap.get(file); if (cached) { return cached; } let current = file; let dirname: string; while ((dirname = path.dirname(current)) !== '/') { const pkgFile = path.resolve(dirname, 'package.json'); if (fs.existsSync(pkgFile)) { const json = JSON.parse(fs.readFileSync(pkgFile, 'utf8')); if (json.name && json.version && !json.private) { pkgMap.set(file, pkgFile); return pkgFile; } } current = dirname; } }; export const normalizePackageName = (specifier: string): string => { const splits = specifier.split('/'); if (specifier.startsWith('@')) { return `${splits[0]}/${splits[1]}`; } else { return splits[0]; } }; export const getEsbuildLoader = (filename: string): Loader => { const ext = path.extname(filename); switch (ext) { case '.svg': { return 'jsx' as Loader; } case '.js': { return 'jsx' as Loader; } default: { return ext.slice(1) as Loader; } } }; export const shouldEnableBabelMacros = (appDirectory: string): boolean => { const metaFilePath = path.resolve( appDirectory, WEB_MODULES_DIR, META_DATA_FILE_NAME, ); try { const json = JSON.parse(fs.readFileSync(metaFilePath, 'utf8')); return Boolean(json.enableBabelMacros); } catch (err) { return false; } }; export const hasDependency = (appDirectory: string, depName: string) => { const { dependencies = {}, devDependencies = {} } = fs.readJSONSync( path.resolve(appDirectory, './package.json'), ); return ( dependencies.hasOwnProperty(depName) || devDependencies.hasOwnProperty(depName) ); }; export const pathToUrl = (p: string) => p.split(path.sep).join('/'); export const logWithHistory = () => { let count = 1; let history = ''; return (message: string) => { process.stdout.moveCursor(0, -1); // up one line process.stdout.clearLine(1); // clear last line if (message === history) { count += 1; logger.info(chalk.green(`${message} x${count}`)); } else { history = message; count = 1; logger.info(chalk.green(message)); } }; }; export const setIgnoreDependencies = ( userConfig: NormalizedConfig, virtualDeps: Record<string, string>, ) => { const ignore = userConfig?.dev?.unbundle?.ignore; if (!ignore) { return; } const normalizeIgnore = Array.isArray(ignore) ? ignore : [ignore]; normalizeIgnore.forEach(dependencyToIgnore => { virtualDeps[dependencyToIgnore] = ` /** * Dependency ${dependencyToIgnore} ignored via user config * / export {}; export default {}; `; }); };