From 90228d383828ca10fa9455d9f94c8b7e33aab18b Mon Sep 17 00:00:00 2001 From: Jinjiang Date: Wed, 15 May 2024 18:30:26 +0800 Subject: [PATCH] Improve preview entries (#8691) --- scopes/preview/preview/generate-link.ts | 87 ++++++++++++++----- scopes/preview/preview/mk-temp-dir.ts | 11 ++- scopes/preview/preview/pre-bundle.ts | 5 +- .../preview/preview/preview.main.runtime.ts | 16 +++- 4 files changed, 92 insertions(+), 27 deletions(-) diff --git a/scopes/preview/preview/generate-link.ts b/scopes/preview/preview/generate-link.ts index defedbca0d0..055539e1242 100644 --- a/scopes/preview/preview/generate-link.ts +++ b/scopes/preview/preview/generate-link.ts @@ -1,6 +1,12 @@ -import { toWindowsCompatiblePath } from '@teambit/toolbox.path.to-windows-compatible-path'; -import camelcase from 'camelcase'; import type { ComponentMap } from '@teambit/component'; +import { join } from 'path'; +import { outputFileSync } from 'fs-extra'; +import objectHash from 'object-hash'; +import camelcase from 'camelcase'; +import { toWindowsCompatiblePath } from '@teambit/toolbox.path.to-windows-compatible-path'; +import { getPreviewDistDir } from './mk-temp-dir'; + +const previewDistDir = getPreviewDistDir(); export type MainModulesMap = { /** @@ -10,14 +16,29 @@ export type MainModulesMap = { [envId: string]: string; }; +type ModuleLink = { + envId: string; + varName: string; + resolveFrom: string; +}; + +type ComponentLink = { + componentIdentifier: string; + modules: { + varName: string; + resolveFrom: string; + }[]; +}; + // :TODO refactor to building an AST and generate source code based on it. export function generateLink( prefix: string, componentMap: ComponentMap, mainModulesMap?: MainModulesMap, - isSplitComponentBundle = false + isSplitComponentBundle = false, + tempPackageDir?: string ): string { - const links = componentMap.toArray().map(([component, modulePath], compIdx) => ({ + const componentLinks: ComponentLink[] = componentMap.toArray().map(([component, modulePath], compIdx) => ({ componentIdentifier: component.id.fullName, modules: modulePath.map((path, pathIdx) => ({ varName: moduleVarName(compIdx, pathIdx), @@ -25,41 +46,41 @@ export function generateLink( })), })); - let modulesLinks; - if (mainModulesMap) { - modulesLinks = Object.entries(mainModulesMap).map(([envId, path]) => { - const resolveFrom = toWindowsCompatiblePath(path); - const varName = getEnvVarName(envId); - return { envId, varName, resolveFrom }; - }); - } + const moduleLinks: ModuleLink[] = Object.entries(mainModulesMap || {}).map(([envId, path]) => { + const resolveFrom = toWindowsCompatiblePath(path); + const varName = getEnvVarName(envId); + return { envId, varName, resolveFrom }; + }); - return ` -import { linkModules } from '@teambit/preview/dist/preview.preview.runtime.js'; + const contents = ` +import { linkModules } from '${previewDistDir}/preview.preview.runtime.js'; -${links - .map((link) => link.modules.map((module) => `import * as ${module.varName} from "${module.resolveFrom}";`).join('\n')) - .filter((line) => line !== '') // prevent empty lines - .join('\n')} +${getModuleImports(moduleLinks, tempPackageDir)} -${modulesLinks.map((module) => `import * as ${module.varName} from "${module.resolveFrom}";`).join('\n')} +${getComponentImports(componentLinks)} linkModules('${prefix}', { modulesMap: { - ${modulesLinks + ${moduleLinks // must include all components, including empty - .map((module) => `"${module.envId}": ${module.varName}`) + .map((moduleLink) => `"${moduleLink.envId}": ${moduleLink.varName}`) .join(',\n ')} }, isSplitComponentBundle: ${isSplitComponentBundle}, componentMap: { -${links +${componentLinks // must include all components, including empty - .map((link) => ` "${link.componentIdentifier}": [${link.modules.map((module) => module.varName).join(', ')}]`) + .map( + (componentLink) => + ` "${componentLink.componentIdentifier}": [${componentLink.modules + .map((module) => module.varName) + .join(', ')}]` + ) .join(',\n')} } }); `; + return contents; } function moduleVarName(componentIdx: number, fileIdx: number) { @@ -71,3 +92,23 @@ function getEnvVarName(envId: string) { const varName = `${envNameFormatted}MainModule`; return varName; } + +function getModuleImports(moduleLinks: ModuleLink[] = [], tempPackageDir?: string): string { + const hash = objectHash(moduleLinks); + const tempFileName = `preview-modules-${hash}.mjs`; + const tempFilePath = join(tempPackageDir || previewDistDir, tempFileName); + const tempFileContents = moduleLinks + .map((module) => `export * as ${module.varName} from "${module.resolveFrom}";`) + .join('\n'); + outputFileSync(tempFilePath, tempFileContents); + return `import {${moduleLinks.map((moduleLink) => moduleLink.varName).join(', ')}} from "${tempFilePath}";`; +} + +function getComponentImports(componentLinks: ComponentLink[] = []): string { + return componentLinks + .map((link) => + link.modules.map((module) => `import * as ${module.varName} from "${module.resolveFrom}";`).join('\n') + ) + .filter((line) => line !== '') // prevent empty lines + .join('\n'); +} diff --git a/scopes/preview/preview/mk-temp-dir.ts b/scopes/preview/preview/mk-temp-dir.ts index 136de3e7642..08c5246eefd 100644 --- a/scopes/preview/preview/mk-temp-dir.ts +++ b/scopes/preview/preview/mk-temp-dir.ts @@ -1,7 +1,16 @@ import { mkdtempSync } from 'fs-extra'; import { tmpdir } from 'os'; -import { sep } from 'path'; +import { sep, join } from 'path'; +import { getAspectDirFromBvm } from '@teambit/aspect-loader'; export function makeTempDir(prefix = '') { return mkdtempSync(`${tmpdir()}${sep}${prefix}`); } + +export function getPreviewDistDir(): string { + try { + return join(getAspectDirFromBvm('@teambit/preview'), 'dist'); + } catch (err) { + return __dirname; + } +} diff --git a/scopes/preview/preview/pre-bundle.ts b/scopes/preview/preview/pre-bundle.ts index a3619d0b105..7b8e6d93770 100644 --- a/scopes/preview/preview/pre-bundle.ts +++ b/scopes/preview/preview/pre-bundle.ts @@ -14,6 +14,9 @@ import { promisify } from 'util'; import { PreviewAspect } from './preview.aspect'; import { createWebpackConfig } from './webpack/webpack.config'; import { clearConsole } from './pre-bundle-utils'; +import { getPreviewDistDir } from './mk-temp-dir'; + +const previewDistDir = getPreviewDistDir(); export const RUNTIME_NAME = 'preview'; export const PUBLIC_DIR = join('public', 'bit-preview'); @@ -131,7 +134,7 @@ export async function generateBundlePreviewEntry(rootAspectId: string, previewPr config['teambit.harmony/bit'] = rootAspectId; const contents = [imports, `run(${JSON.stringify(config, null, 2)});`].join('\n'); - const previewRuntime = resolve(join(__dirname, `preview.entry.${sha1(contents)}.js`)); + const previewRuntime = resolve(join(previewDistDir, `preview.entry.${sha1(contents)}.js`)); if (!existsSync(previewRuntime)) { outputFileSync(previewRuntime, contents); diff --git a/scopes/preview/preview/preview.main.runtime.ts b/scopes/preview/preview/preview.main.runtime.ts index 93810748845..48f6097f50f 100644 --- a/scopes/preview/preview/preview.main.runtime.ts +++ b/scopes/preview/preview/preview.main.runtime.ts @@ -20,7 +20,7 @@ import { CACHE_ROOT } from '@teambit/legacy/dist/constants'; import { BitError } from '@teambit/bit-error'; import objectHash from 'object-hash'; import { uniq } from 'lodash'; -import { writeFileSync, existsSync, mkdirSync } from 'fs-extra'; +import { writeFileSync, existsSync, mkdirSync, ensureDirSync, writeJSONSync } from 'fs-extra'; import { join } from 'path'; import { PkgAspect, PkgMain } from '@teambit/pkg'; import { AspectLoaderAspect, getAspectDir, getAspectDirFromBvm } from '@teambit/aspect-loader'; @@ -639,6 +639,17 @@ export class PreviewMain { private writeHash = new Map(); private timestamp = Date.now(); + private ensureTempPackage() { + const workspacePath = this.workspace?.path; + const tempPackageDir = workspacePath ? join(workspacePath, 'node_modules', '@teambit', '_local') : ''; + if (tempPackageDir) { + ensureDirSync(tempPackageDir); + writeJSONSync(join(tempPackageDir, 'package.json'), { name: '@teambit/_local' }); + writeFileSync(join(tempPackageDir, 'index.js'), 'module.exports = {};'); + return tempPackageDir; + } + } + /** * write a link to load custom modules dynamically. * @param prefix write @@ -653,7 +664,8 @@ export class PreviewMain { dirName: string, isSplitComponentBundle: boolean ) { - const contents = generateLink(prefix, moduleMap, mainModulesMap, isSplitComponentBundle); + const tempPackageDir = this.ensureTempPackage(); + const contents = generateLink(prefix, moduleMap, mainModulesMap, isSplitComponentBundle, tempPackageDir); return this.writeLinkContents(contents, dirName, prefix); }