From 4b1730b2e344b839af7f9f75c76395309b24fe6f Mon Sep 17 00:00:00 2001 From: hemengke1997 <23536175@qq.com> Date: Thu, 21 Sep 2023 17:16:11 +0800 Subject: [PATCH] refactor: decouple --- README-en.md | 125 ------------------ __test__/utils.spec.ts | 4 +- migrate-1.3.x-1.5.x.md | 20 +-- .../GlobalConfigBuilder.ts | 28 ++-- src/global-config/index.ts | 6 + src/helper/AbsCacheProcessor.ts | 73 ---------- src/helper/FileCacheProcessor.ts | 81 ------------ src/helper/MemoryCacheProcessor.ts | 19 --- src/helper/build.ts | 53 +++++--- src/helper/processor.ts | 17 --- src/helper/utils.ts | 21 +-- src/index.ts | 88 ++++++------ .../ManifestCache.ts | 49 ++++--- src/manifest-cache/index.ts | 12 ++ src/processor/BaseCacheProcessor.ts | 41 ++++++ src/processor/FileCacheProcessor.ts | 82 ++++++++++++ src/processor/ManifestCacheProcessor.ts | 56 ++++++++ src/processor/MemoryCacheProcessor.ts | 20 +++ src/processor/processor.ts | 17 +++ 19 files changed, 370 insertions(+), 442 deletions(-) delete mode 100644 README-en.md rename src/{helper => global-config}/GlobalConfigBuilder.ts (51%) create mode 100644 src/global-config/index.ts delete mode 100644 src/helper/AbsCacheProcessor.ts delete mode 100644 src/helper/FileCacheProcessor.ts delete mode 100644 src/helper/MemoryCacheProcessor.ts delete mode 100644 src/helper/processor.ts rename src/{helper => manifest-cache}/ManifestCache.ts (72%) create mode 100644 src/manifest-cache/index.ts create mode 100644 src/processor/BaseCacheProcessor.ts create mode 100644 src/processor/FileCacheProcessor.ts create mode 100644 src/processor/ManifestCacheProcessor.ts create mode 100644 src/processor/MemoryCacheProcessor.ts create mode 100644 src/processor/processor.ts diff --git a/README-en.md b/README-en.md deleted file mode 100644 index 5adad4e..0000000 --- a/README-en.md +++ /dev/null @@ -1,125 +0,0 @@ -# vite-plugin-public-typescript - -![npm][npm-img] - - -**English** | [中文](./README-zh.md) - -## Features - -- Transform typescript to javascript at runtime and build time -- Support HMR -- Output js with hash, no worry about cache -- Customize esbuild build options, specify target browser ranges - -## Install - -```bash -pnpm add vite-plugin-public-typescript -D -``` - - -## Preview - - - -## Usage - -```typescript -import { defineConfig } from 'vite' -import { publicTypescript } from 'vite-plugin-public-typescript' - -export default defineConfig({ - plugins: [publicTypescript()], -}) -``` - -### SPA - -For `SPA`, you can inject script in vite `transformIndexHtml` hook. -Or you can use [`vite-plugin-html`](https://github.com/vbenjs/vite-plugin-html) that make injecting easy - -For full example, please see [spa playground](./playground/spa/vite.config.ts) - -#### vite config - -```typescript -import type { HtmlTagDescriptor } from 'vite' -import { defineConfig } from 'vite' -import { publicTypescript } from 'vite-plugin-public-typescript' -import manifest from './publicTypescript/manifest.json' - -export default defineConfig({ - plugins: [ - publicTypescript(), - { - name: 'add-script', - async transformIndexHtml(html) { - const tags: HtmlTagDescriptor[] = [ - { - tag: 'script', - attrs: { - src: manifest.spa, - }, - injectTo: 'head-prepend', - }, - ] - return { - html, - tags, - } - }, - }, - ], -}) -``` - -### SSR - -We can easily change the html in SSR mode, because `html` is just a string template - -For full example, please see [ssr playground](./playground/ssr/index.html) - -#### vite config - -```typescript -import { HtmlTagDescriptor, defineConfig } from 'vite' -import { publicTypescript } from 'vite-plugin-public-typescript' - -// https://vitejs.dev/config/ -export default defineConfig({ - plugins: [ - publicTypescript({ - manifestName: 'custom-manifest', - }), - ], -}) -``` - -#### server.js - -```js -import manifest from './publicTypescript/custom-manifest.json' assert { type: 'json' } - -const html = template - // inject js - .replace('', ``) -``` - -## Options - -| Parameter | Types | Default | Description | -| -------------- | -------------- | ------------------ | ----------------------------------------------------- | -| ssrBuild | `boolean` | `true` | whether is ssrBuild | -| inputDir | `string` | `publicTypescript` | input public typescript dir | -| outputDir | `string` | `/` | output public javascript dir, relative to `publicDir` | -| manifestName | `string` | `manifest` | js manifest fileName | -| hash | `boolean` | `true` | whether generate js fileName with hash | -| esbuildOptions | `BuildOptions` | `{}` | esbuild BuildOptions | - -## License - -MIT - - -[npm-img]: https://img.shields.io/npm/v/vite-plugin-public-typescript.svg diff --git a/__test__/utils.spec.ts b/__test__/utils.spec.ts index 4e4ae96..7ababec 100644 --- a/__test__/utils.spec.ts +++ b/__test__/utils.spec.ts @@ -9,7 +9,7 @@ import { setEol, validateOptions, } from '../src/helper/utils' -import { globalConfigBuilder } from '../src/helper/GlobalConfigBuilder' +import { globalConfig } from '../src/global-config' describe('vite-plugin-public-typescript', () => { it('should return true when filePath is a public typescript file', () => { @@ -71,7 +71,7 @@ describe('vite-plugin-public-typescript', () => { }) test('should get globalConfig', () => { - expect(() => globalConfigBuilder.get()).not.toThrowError() + expect(() => globalConfig.get()).not.toThrowError() }) test('should extract hash', () => { diff --git a/migrate-1.3.x-1.5.x.md b/migrate-1.3.x-1.5.x.md index bc0379f..4f67bf3 100644 --- a/migrate-1.3.x-1.5.x.md +++ b/migrate-1.3.x-1.5.x.md @@ -4,23 +4,15 @@ **更新点:** -- vite环境下,支持内存编译模式,默认不会在 `publicDir` 下生成 `js` 文件了 +- vite环境下,支持内存编译模式( `destination` 配置项),不会在 `publicDir` 下生成 `js` 文件了 - 导出了 `injectScripts` 插件,用于插入脚本(尽量使用这个,不要再使用 `vite-plugin-html` 插脚本了) - 修复了一些奇怪的bug -## 使用 `@minko-fe/vite-config` ? - -为了方便,以下称为 `vite-config` - -此情况下: -1. 升级 `vite-config` 至最新版本 -2. 从 `vite-config` 中引入 `injectScripts` 替换 `vite-plugin-html` -3. 注意 `publicTypescript` 的配置项。新版本默认 `inputDir: 'public-typescript'`(旧版本是 `publicTypescript`)。如果已经设置 `inputDir`,则不需要修改 - ## 直接使用? -跟上述操作一样,除了第一条 +1. 从 `vite-config` 中引入 `injectScripts` 替换 `vite-plugin-html` +2. 注意 `publicTypescript` 的配置项。新版本默认 `inputDir: 'public-typescript'`(旧版本是 `publicTypescript`)。如果已经设置 `inputDir`,则不需要修改 ## 与 `modern-flexible` 一起使用? @@ -28,9 +20,5 @@ ## 参考 -### with vite-config -[playground]([./playground/spa/vite.config.ts](https://github.com/hemengke1997/util/blob/master/playground/spa/vite.config.ts)) - - -### only with vite +### with vite [playground](./playground/spa/vite.config.ts) diff --git a/src/helper/GlobalConfigBuilder.ts b/src/global-config/GlobalConfigBuilder.ts similarity index 51% rename from src/helper/GlobalConfigBuilder.ts rename to src/global-config/GlobalConfigBuilder.ts index 48fefd4..ce1896c 100644 --- a/src/helper/GlobalConfigBuilder.ts +++ b/src/global-config/GlobalConfigBuilder.ts @@ -1,30 +1,30 @@ import path from 'path' import type { ResolvedConfig } from 'vite' import type { VPPTPluginOptions } from '..' -import type { AbsCacheProcessor } from './AbsCacheProcessor' -import type { ManifestCache } from './ManifestCache' +import type { BaseCacheProcessor } from '../processor/BaseCacheProcessor' +import type { CacheValue, ManifestCache } from '../manifest-cache/ManifestCache' -export type UserConfig = +export type UserConfig = | { - cache: ManifestCache - tsFilesGlob: string[] + manifestCache: ManifestCache + originFilesGlob: string[] viteConfig: ResolvedConfig - cacheProcessor: AbsCacheProcessor + cacheProcessor: BaseCacheProcessor } & Required -export type TGlobalConfig = UserConfig & { +export type GlobalConfig = UserConfig & { absOutputDir: string absInputDir: string } -class GlobalConfigBuilder { - private globalConfig: TGlobalConfig +export class GlobalConfigBuilder { + private globalConfig: GlobalConfig constructor() { - this.globalConfig = {} as TGlobalConfig + this.globalConfig = {} as GlobalConfig } - init(c: UserConfig) { + init(c: UserConfig) { const root = c.viteConfig.root || process.cwd() const absOutputDir = path.join(root, c.outputDir) const absInputDir = path.join(root, c.inputDir) @@ -41,7 +41,7 @@ class GlobalConfigBuilder { return this.globalConfig } - set(c: UserConfig) { + set(c: UserConfig) { this.globalConfig = { ...this.get(), ...c, @@ -49,7 +49,3 @@ class GlobalConfigBuilder { return this } } - -const globalConfigBuilder = new GlobalConfigBuilder() - -export { globalConfigBuilder } diff --git a/src/global-config/index.ts b/src/global-config/index.ts new file mode 100644 index 0000000..34bb600 --- /dev/null +++ b/src/global-config/index.ts @@ -0,0 +1,6 @@ +import type { CacheValueEx } from '../manifest-cache' +import { GlobalConfigBuilder } from './GlobalConfigBuilder' + +const globalConfig = new GlobalConfigBuilder() + +export { globalConfig } diff --git a/src/helper/AbsCacheProcessor.ts b/src/helper/AbsCacheProcessor.ts deleted file mode 100644 index eaff5f5..0000000 --- a/src/helper/AbsCacheProcessor.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { normalizePath } from 'vite' -import createDebug from 'debug' -import type { ManifestCache } from './ManifestCache' -import type { TGlobalConfig } from './GlobalConfigBuilder' - -const debug = createDebug('vite-plugin-public-typescript:AbsCacheProcessor ===> ') - -export type BuildEndArgs = { - tsFileName: string - jsFileNameWithHash: string - code: string - contentHash: string -} - -export interface IDeleteFile { - tsFileName: string - jsFileName?: string - /** - * if true, will not write file to disk - */ - silent?: boolean -} - -export interface IAddFile { - code?: string - tsFileName: string - contentHash: string -} - -export abstract class AbsCacheProcessor { - cache: ManifestCache - abstract deleteOldJs(args: IDeleteFile): Promise - abstract addNewJs(args: IAddFile): Promise - - constructor(cache: ManifestCache) { - this.cache = cache - } - - async onTsBuildEnd(args: BuildEndArgs) { - const { tsFileName, jsFileNameWithHash, code, contentHash } = args - - debug('onTsBuildEnd:', args) - - await this.deleteOldJs({ tsFileName, jsFileName: jsFileNameWithHash, silent: true }) - - await this.addNewJs({ code, tsFileName, contentHash }) - } - - setCache(args: IAddFile, config: TGlobalConfig): string { - const { contentHash, code = '', tsFileName } = args - const { - outputDir, - viteConfig: { base }, - } = config - - function getOutputPath(p: string, hash?: string) { - hash = hash ? `.${hash}` : '' - return normalizePath(`${p}/${tsFileName}${hash}.js`) - } - - const path = getOutputPath(base + outputDir, contentHash) - - this.cache.set({ - [tsFileName]: { - path, - _code: code, - _hash: contentHash, - }, - }) - - return this.cache.get()[tsFileName]._pathToDisk || '' - } -} diff --git a/src/helper/FileCacheProcessor.ts b/src/helper/FileCacheProcessor.ts deleted file mode 100644 index 4b45e66..0000000 --- a/src/helper/FileCacheProcessor.ts +++ /dev/null @@ -1,81 +0,0 @@ -import path from 'path' -import fs from 'fs-extra' -import { normalizePath } from 'vite' -import createDebug from 'debug' -import { assert } from './assert' -import { globalConfigBuilder } from './GlobalConfigBuilder' -import { AbsCacheProcessor } from './AbsCacheProcessor' -import type { IAddFile, IDeleteFile } from './AbsCacheProcessor' -import { findAllOldJsFile, writeFile } from './utils' -import type { ManifestCache } from './ManifestCache' - -const debug = createDebug('FileCacheProcessor ===> ') - -// file-based processor -// the final output dir is base on `publicDir` -export class FileCacheProcessor extends AbsCacheProcessor { - constructor(cache: ManifestCache) { - super(cache) - } - - async deleteOldJs(args: IDeleteFile): Promise { - const { tsFileName, jsFileName = '' } = args - - const { - outputDir, - viteConfig: { publicDir }, - } = globalConfigBuilder.get() - - let oldFiles: string[] = [] - try { - fs.ensureDirSync(path.join(publicDir, outputDir)) - oldFiles = await findAllOldJsFile({ - outputDir, - publicDir, - tsFileNames: [tsFileName], - }) - } catch (e) { - console.error(e) - } - - debug('deleteOldJsFile - oldFiles:', oldFiles) - - assert(Array.isArray(oldFiles)) - - debug('cache:', this.cache.get()) - - if (oldFiles.length) { - for (const f of oldFiles) { - if (path.parse(f).name === jsFileName) { - debug('deleteOldJsFile - skip file:', jsFileName) - continue - } // skip repeat js file - if (fs.existsSync(f)) { - debug('deleteOldJsFile - file exists:', f, tsFileName) - this.cache.remove(tsFileName) - debug('deleteOldJsFile - cache removed:', tsFileName) - fs.remove(f) - debug('deleteOldJsFile -file removed:', f) - } - } - } else { - this.cache.remove(tsFileName) - debug('cache removed:', tsFileName) - } - } - - async addNewJs(args: IAddFile): Promise { - const { code = '' } = args - const { - viteConfig: { publicDir }, - } = globalConfigBuilder.get() - - const pathToDisk = this.setCache(args, globalConfigBuilder.get()) - - const jsFilePath = normalizePath(path.join(publicDir, pathToDisk)) - - fs.ensureDirSync(path.dirname(jsFilePath)) - - writeFile(jsFilePath, code) - } -} diff --git a/src/helper/MemoryCacheProcessor.ts b/src/helper/MemoryCacheProcessor.ts deleted file mode 100644 index fa5416b..0000000 --- a/src/helper/MemoryCacheProcessor.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { AbsCacheProcessor } from './AbsCacheProcessor' -import type { IAddFile, IDeleteFile } from './AbsCacheProcessor' -import { globalConfigBuilder } from './GlobalConfigBuilder' -import type { ManifestCache } from './ManifestCache' - -export class MemoryCacheProcessor extends AbsCacheProcessor { - constructor(cache: ManifestCache) { - super(cache) - } - - async deleteOldJs(args: IDeleteFile): Promise { - const { tsFileName } = args - this.cache.remove(tsFileName) - } - - async addNewJs(args: IAddFile): Promise { - this.setCache(args, globalConfigBuilder.get()) - } -} diff --git a/src/helper/build.ts b/src/helper/build.ts index 4e386bb..721683e 100644 --- a/src/helper/build.ts +++ b/src/helper/build.ts @@ -5,9 +5,9 @@ import { build as esbuild } from 'esbuild' import createDebug from 'debug' import type { VPPTPluginOptions } from '..' import { name } from '../../package.json' -import { globalConfigBuilder } from './GlobalConfigBuilder' +import type { BaseCacheProcessor } from '../processor/BaseCacheProcessor' +import { globalConfig } from '../global-config' import { getContentHash } from './utils' -import type { BuildEndArgs } from './AbsCacheProcessor' const debug = createDebug('vite-plugin-public-typescript:build ===> ') @@ -70,7 +70,7 @@ export async function esbuildTypescript(buildOptions: IBuildOptions) { const define = transformEnvToDefine(viteConfig) - debug('esbuild define:', define) + debug('tsFile:', filePath, 'esbuild define:', define) let res: BuildResult try { @@ -101,38 +101,49 @@ export async function esbuildTypescript(buildOptions: IBuildOptions) { return code } -export async function build(options: { filePath: string }, onBuildEnd?: (args: BuildEndArgs) => Promise) { +export async function build(options: { filePath: string }, onBuildEnd?: BaseCacheProcessor['onTsBuildEnd']) { const { filePath } = options - const globalConfig = globalConfigBuilder.get() + const getGlobalConfig = globalConfig.get() const originFileName = path.basename(filePath, path.extname(filePath)) let contentHash = '' - let fileNameWithHash = originFileName + let compiledFileName = originFileName - const code = (await esbuildTypescript({ filePath, ...globalConfig })) || '' + const code = (await esbuildTypescript({ filePath, ...getGlobalConfig })) || '' - if (globalConfig.hash) { - contentHash = getContentHash(code, globalConfig.hash) - fileNameWithHash = `${originFileName}.${contentHash}` + if (getGlobalConfig.hash) { + contentHash = getContentHash(code, getGlobalConfig.hash) + compiledFileName = `${originFileName}.${contentHash}` } - debug('before onBuildEnd cache:', globalConfig.cache.get()) + debug('before onBuildEnd manifest-cache:', getGlobalConfig.manifestCache.get()) - await onBuildEnd?.({ - tsFileName: originFileName, - jsFileNameWithHash: fileNameWithHash, - code, - contentHash, - }) + await onBuildEnd?.( + { + compiledFileName, + originFileName, + silent: true, + }, + { contentHash, code, silent: false, originFileName }, + ) - debug('after onBuildEnd cache:', globalConfig.cache.get()) + debug('after onBuildEnd manifest-cache:', getGlobalConfig.manifestCache.get()) } -export async function buildAll(tsFilesGlob: string[]) { - const { cacheProcessor } = globalConfigBuilder.get() +export async function buildAllOnce(tsFilesGlob: string[]) { + const { cacheProcessor } = globalConfig.get() + + const toBuildList: (() => Promise)[] = [] for (const file of tsFilesGlob) { - await build({ filePath: file }, (args) => cacheProcessor.onTsBuildEnd(args)) + toBuildList.push(() => + build({ filePath: file }, (deleteArgs, addArgs) => + cacheProcessor.onTsBuildEnd({ ...deleteArgs, silent: true }, { ...addArgs, silent: true }), + ), + ) } + + await Promise.all(toBuildList.map((fn) => fn())) + cacheProcessor.manifestCache.writeManifestJSON() } diff --git a/src/helper/processor.ts b/src/helper/processor.ts deleted file mode 100644 index 3e28274..0000000 --- a/src/helper/processor.ts +++ /dev/null @@ -1,17 +0,0 @@ -import type { VPPTPluginOptions } from '..' -import type { AbsCacheProcessor } from './AbsCacheProcessor' -import { FileCacheProcessor } from './FileCacheProcessor' -import type { ManifestCache } from './ManifestCache' -import { MemoryCacheProcessor } from './MemoryCacheProcessor' - -export function initCacheProcessor(options: Required, cache: ManifestCache): AbsCacheProcessor { - const { destination } = options - switch (destination) { - case 'file': - return new FileCacheProcessor(cache) - case 'memory': - return new MemoryCacheProcessor(cache) - default: - return new MemoryCacheProcessor(cache) - } -} diff --git a/src/helper/utils.ts b/src/helper/utils.ts index c29db41..79f268e 100644 --- a/src/helper/utils.ts +++ b/src/helper/utils.ts @@ -6,7 +6,7 @@ import fs from 'fs-extra' import createDebug from 'debug' import glob from 'tiny-glob' import type { VPPTPluginOptions } from '..' -import { globalConfigBuilder } from './GlobalConfigBuilder' +import { globalConfig } from '../global-config' import { assert } from './assert' const debug = createDebug('vite-plugin-public-typescript:util ===> ') @@ -28,9 +28,12 @@ export function isPublicTypescript(args: { filePath: string; inputDir: string; r } export function _isPublicTypescript(filePath: string) { - const globalConfig = globalConfigBuilder.get() - assert(!!globalConfig) - return isPublicTypescript({ filePath, inputDir: globalConfig.inputDir, root: globalConfig.viteConfig.root }) + assert(!!globalConfig.get()) + return isPublicTypescript({ + filePath, + inputDir: globalConfig.get().inputDir, + root: globalConfig.get().viteConfig.root, + }) } export function isWindows() { @@ -95,7 +98,7 @@ export function writeFile(filename: string, content: string): void { const newContent = setEol(content) if (fs.existsSync(filename)) { - const { hash } = globalConfigBuilder.get() + const { hash } = globalConfig.get() if (extractHashFromFileName(filename, hash)) { // if filename has hash, skip write file debug('skip writeFile, filename has hash') @@ -173,13 +176,13 @@ export function getInputDir(resolvedRoot: string, originInputDir: string, suffix return normalizePath(path.resolve(resolvedRoot, `${originInputDir}${suffix}`)) } -export async function findAllOldJsFile(args: { publicDir: string; outputDir: string; tsFileNames: string[] }) { - const { publicDir, outputDir, tsFileNames } = args +export async function findAllOldJsFile(args: { publicDir: string; outputDir: string; originFilesName: string[] }) { + const { publicDir, outputDir, originFilesName } = args const dir = path.join(publicDir, outputDir) const oldFiles: string[] = [] if (fs.existsSync(dir)) { - for (const tsFileName of tsFileNames) { - const old = await glob(normalizePath(path.join(publicDir, `${outputDir}/${tsFileName}.?(*.)js`))) + for (const originFileName of originFilesName) { + const old = await glob(normalizePath(path.join(publicDir, `${outputDir}/${originFileName}.?(*.)js`))) if (old.length) { oldFiles.push(...old) } diff --git a/src/index.ts b/src/index.ts index afb26ad..4274ecb 100644 --- a/src/index.ts +++ b/src/index.ts @@ -21,12 +21,12 @@ import { removeOldJsFiles, validateOptions, } from './helper/utils' -import { build, buildAll, esbuildTypescript } from './helper/build' +import { build, buildAllOnce, esbuildTypescript } from './helper/build' import { assert } from './helper/assert' -import { globalConfigBuilder } from './helper/GlobalConfigBuilder' -import { initCacheProcessor } from './helper/processor' -import { ManifestCache } from './helper/ManifestCache' import { getScriptInfo, nodeIsElement, traverseHtml } from './helper/html' +import { initCacheProcessor } from './processor/processor' +import { globalConfig } from './global-config' +import { manifestCache } from './manifest-cache' const debug = createDebug('vite-plugin-public-typescript:index ===> ') @@ -93,15 +93,6 @@ export const DEFAULT_OPTIONS: Required = { let previousOpts: VPPTPluginOptions -type CacheItemType = { - path: string - _code?: string - _hash?: string - _pathToDisk?: string -} - -const cache = new ManifestCache() - export default function publicTypescript(options: VPPTPluginOptions = {}) { const opts = { ...DEFAULT_OPTIONS, @@ -125,36 +116,36 @@ export default function publicTypescript(options: VPPTPluginOptions = {}) { fs.ensureDirSync(getInputDir(resolvedRoot, opts.inputDir)) - const tsFilesGlob = await glob(getInputDir(resolvedRoot, opts.inputDir, `/*.ts`), { + const originFilesGlob = await glob(getInputDir(resolvedRoot, opts.inputDir, `/*.ts`), { cwd: resolvedRoot, absolute: true, }) - const cacheProcessor = initCacheProcessor(opts, cache) + const cacheProcessor = initCacheProcessor(opts, manifestCache) - globalConfigBuilder.init({ - cache, - tsFilesGlob, + globalConfig.init({ + manifestCache, + originFilesGlob, viteConfig, cacheProcessor, ...opts, }) - cache.setManifestPath(normalizePath(`${globalConfigBuilder.get().absInputDir}/${opts.manifestName}.json`)) + manifestCache.setManifestPath(normalizePath(`${globalConfig.get().absInputDir}/${opts.manifestName}.json`)) - cache.beforeChange = (value) => { + // no need to set `_pathToDisk` manually anymore + manifestCache.setBeforeSet((value) => { if (value?.path) { value._pathToDisk = removeBase(value.path, viteConfig.base) } - } - - disableManifestHmr(c, cache.getManifestPath()) + return value + }) - debug('cache manifestPath:', cache.getManifestPath()) + disableManifestHmr(c, manifestCache.getManifestPath()) - debug('cache:', cache.get()) + debug('manifestCache manifestPath:', manifestCache.getManifestPath()) - assert(cache.getManifestPath().includes('.json')) + assert(manifestCache.getManifestPath().includes('.json')) }, configureServer(server) { @@ -165,7 +156,7 @@ export default function publicTypescript(options: VPPTPluginOptions = {}) { const { ws } = server try { - const watcher = new Watcher(globalConfigBuilder.get().absInputDir, { + const watcher = new Watcher(globalConfig.get().absInputDir, { ignoreInitial: true, recursive: true, renameDetection: true, @@ -177,7 +168,7 @@ export default function publicTypescript(options: VPPTPluginOptions = {}) { if (_isPublicTypescript(filePath)) { const fileName = path.parse(filePath).name debug('unlink:', fileName) - await globalConfigBuilder.get().cacheProcessor.deleteOldJs({ tsFileName: fileName }) + await globalConfig.get().cacheProcessor.deleteOldJs({ originFileName: fileName }) reloadPage(ws) } } @@ -185,7 +176,7 @@ export default function publicTypescript(options: VPPTPluginOptions = {}) { async function handleFileAdded(filePath: string) { if (_isPublicTypescript(filePath)) { debug('file added:', filePath) - await build({ filePath }, (args) => globalConfigBuilder.get().cacheProcessor.onTsBuildEnd(args)) + await build({ filePath }, (...args) => globalConfig.get().cacheProcessor.onTsBuildEnd(...args)) reloadPage(ws) } } @@ -217,40 +208,49 @@ export default function publicTypescript(options: VPPTPluginOptions = {}) { previousOpts = opts - const manifestPath = cache.getManifestPath() + const manifestPath = manifestCache.getManifestPath() fs.ensureFileSync(manifestPath) - const parsedCacheJson = cache.readManifestFile() + const parsedCacheJson = manifestCache.readManifestFile() debug('buildStart - parsedCacheJson:', parsedCacheJson) if (isEmptyObject(parsedCacheJson)) { // write empty json object to manifest.json - cache.writeManifestJSON() + manifestCache.writeManifestJSON() } - const { tsFilesGlob } = globalConfigBuilder.get() + const { originFilesGlob } = globalConfig.get() - const tsFileNames = tsFilesGlob.map((file) => path.parse(file).name) + const originFilesName = originFilesGlob.map((file) => path.parse(file).name) - debug('buildStart - tsFilesGlob:', tsFilesGlob) - debug('buildStart - tsFileNames:', tsFileNames) + debug('buildStart - originFilesGlob:', originFilesGlob) + debug('buildStart - originFilesName:', originFilesName) if (opts.destination === 'memory') { const oldFiles = await findAllOldJsFile({ outputDir: opts.outputDir, publicDir: viteConfig.publicDir, - tsFileNames, + originFilesName, }) removeOldJsFiles(oldFiles) + + // if dir is empty, delete it + const dir = path.join(viteConfig.publicDir, opts.outputDir) + if (fs.existsSync(dir) && opts.outputDir !== '/') { + const files = fs.readdirSync(dir) + if (!files.length) { + fs.removeSync(dir) + } + } } - await buildAll(tsFilesGlob) + buildAllOnce(originFilesGlob) }, generateBundle() { if (opts.destination === 'memory') { - const c = cache.get() + const c = manifestCache.get() Object.keys(c).forEach((key) => { this.emitFile({ type: 'asset', @@ -274,8 +274,8 @@ export default function publicTypescript(options: VPPTPluginOptions = {}) { const { src, vppt } = getScriptInfo(node) if (vppt?.name && src?.value) { - const c = cache.get() - let cacheItem = cache.findCacheItemByPath(src.value) + const c = manifestCache.get() + let cacheItem = manifestCache.findCacheItemByPath(src.value) if (!cacheItem) { const fileName = path.basename(src.value).split('.')[0] @@ -314,7 +314,7 @@ export default function publicTypescript(options: VPPTPluginOptions = {}) { if (_isPublicTypescript(file)) { debug('hmr:', file) - await build({ filePath: file }, (args) => globalConfigBuilder.get().cacheProcessor.onTsBuildEnd(args)) + await build({ filePath: file }, (...args) => globalConfig.get().cacheProcessor.onTsBuildEnd(...args)) reloadPage(server.ws) @@ -327,7 +327,7 @@ export default function publicTypescript(options: VPPTPluginOptions = {}) { apply: 'serve', enforce: 'post', load(id) { - const cacheItem = cache.findCacheItemByPath(id) + const cacheItem = manifestCache.findCacheItemByPath(id) if (cacheItem) { return { code: '', @@ -339,7 +339,7 @@ export default function publicTypescript(options: VPPTPluginOptions = {}) { server.middlewares.use((req, res, next) => { try { if (req?.url?.startsWith('/') && req?.url?.endsWith('.js')) { - const cacheItem = cache.findCacheItemByPath(req.url) + const cacheItem = manifestCache.findCacheItemByPath(req.url) if (cacheItem) { return send(req, res, addCodeHeader(cacheItem._code || ''), 'js', { cacheControl: 'max-age=31536000,immutable', diff --git a/src/helper/ManifestCache.ts b/src/manifest-cache/ManifestCache.ts similarity index 72% rename from src/helper/ManifestCache.ts rename to src/manifest-cache/ManifestCache.ts index 3776b80..0921170 100644 --- a/src/helper/ManifestCache.ts +++ b/src/manifest-cache/ManifestCache.ts @@ -1,17 +1,14 @@ import path from 'path' import fs from 'fs-extra' -import type { ApplyData } from 'on-change' import onChange from 'on-change' import createDebug from 'debug' -import { writeFile } from './utils' +import { writeFile } from '../helper/utils' const debug = createDebug('vite-plugin-public-typescript:ManifestCache ===> ') type PathOnlyCache = Record -type _OnChangeType = (path: string, value: ValueType, previousValue: ValueType, applyData: ApplyData) => void - -export interface IManifestConstructor { +export interface ManifestConstructor { write?: boolean } @@ -20,40 +17,38 @@ export interface IManifestConstructor { * { * fileName: { * path: '/some-path', - * _code: 'compiled code' * // and more * } * } */ -export type TCacheValue = { +export type CacheValue = { path: string } & Partial<{ [_key: string]: string }> -export type TDefaultCache = { +export type CacheObject = { [fileName in string]: V } -const DEFAULT_OPTIONS: IManifestConstructor = { +const DEFAULT_OPTIONS: ManifestConstructor = { write: true, } -export class ManifestCache = TDefaultCache> { +export class ManifestCache = CacheObject> { private cache: U private manifestPath = '' + private beforeSet = (value: T | undefined) => { + return value + } - beforeChange: (value: T | undefined) => void = () => {} - - constructor(options?: IManifestConstructor) { + constructor(options?: ManifestConstructor) { options = { ...DEFAULT_OPTIONS, ...options, } this.cache = onChange({} as U, (...args) => { - debug('cache changed:', this.cache) - - this.beforeChange(args[1] as T) + debug('cache changed:', this.cache, 'onChange args:', args) if (options!.write) { this.writeManifestJSON() @@ -65,6 +60,11 @@ export class ManifestCache { + sortedCache[k] = c[k] + }) + return sortedCache + } + writeManifestJSON() { const targetPath = this.getManifestPath() const cacheObj = this.getManifestJson() - const orderdCache = Object.assign({}, cacheObj) + const orderdCache = this.sortObjectByKey(cacheObj) writeFile(targetPath, JSON.stringify(orderdCache || {}, null, 2)) diff --git a/src/manifest-cache/index.ts b/src/manifest-cache/index.ts new file mode 100644 index 0000000..b0c1060 --- /dev/null +++ b/src/manifest-cache/index.ts @@ -0,0 +1,12 @@ +import type { CacheValue } from './ManifestCache' +import { ManifestCache } from './ManifestCache' + +export type CacheValueEx = { + _code?: string + _hash?: string + _pathToDisk?: string +} & CacheValue + +const manifestCache = new ManifestCache() + +export { manifestCache } diff --git a/src/processor/BaseCacheProcessor.ts b/src/processor/BaseCacheProcessor.ts new file mode 100644 index 0000000..772ba2d --- /dev/null +++ b/src/processor/BaseCacheProcessor.ts @@ -0,0 +1,41 @@ +import createDebug from 'debug' +import type { CacheValue, ManifestCache } from '../manifest-cache/ManifestCache' + +const debug = createDebug('vite-plugin-public-typescript:BaseCacheProcessor ===> ') + +export interface DeleteFileArgs { + originFileName: string + /** + * if true, will not write file to disk + */ + silent?: boolean + compiledFileName?: string +} + +export interface AddFileArgs { + originFileName: string + contentHash: string + silent?: boolean + code?: string +} + +export abstract class BaseCacheProcessor< + T extends CacheValue = CacheValue, + U extends ManifestCache = ManifestCache, +> { + manifestCache: U + abstract deleteOldJs(args: DeleteFileArgs): Promise + abstract addNewJs(args: AddFileArgs): Promise + + constructor(manifestCache: U) { + this.manifestCache = manifestCache + } + + async onTsBuildEnd(deleteArgs: DeleteFileArgs, addArgs: AddFileArgs) { + debug('onTsBuildEnd deleteArgs:', deleteArgs) + debug('onTsBuildEnd addArgs:', addArgs) + + await this.deleteOldJs(deleteArgs) + await this.addNewJs(addArgs) + } +} diff --git a/src/processor/FileCacheProcessor.ts b/src/processor/FileCacheProcessor.ts new file mode 100644 index 0000000..cb4a9e7 --- /dev/null +++ b/src/processor/FileCacheProcessor.ts @@ -0,0 +1,82 @@ +import path from 'path' +import fs from 'fs-extra' +import { normalizePath } from 'vite' +import createDebug from 'debug' +import { findAllOldJsFile, writeFile } from '../helper/utils' +import { assert } from '../helper/assert' +import type { ManifestCache } from '../manifest-cache/ManifestCache' +import type { CacheValueEx } from '../manifest-cache' +import { globalConfig } from '../global-config' +import type { AddFileArgs, DeleteFileArgs } from './ManifestCacheProcessor' +import { ManifestCacheProcessor } from './ManifestCacheProcessor' + +const debug = createDebug('FileCacheProcessor ===> ') + +// file-based processor +// the final output dir is base on `publicDir` +export class FileCacheProcessor extends ManifestCacheProcessor { + constructor(manifestCache: ManifestCache) { + super(manifestCache) + } + + async deleteOldJs(args: DeleteFileArgs): Promise { + const { originFileName, compiledFileName = '', silent } = args + + const { + outputDir, + viteConfig: { publicDir }, + } = globalConfig.get() + + let oldFiles: string[] = [] + try { + fs.ensureDirSync(path.join(publicDir, outputDir)) + oldFiles = await findAllOldJsFile({ + outputDir, + publicDir, + originFilesName: [originFileName], + }) + } catch (e) { + console.error(e) + } + + debug('deleteOldJsFile - oldFiles:', oldFiles) + + assert(Array.isArray(oldFiles)) + + debug('manifestCache:', this.manifestCache.get()) + + if (oldFiles.length) { + for (const f of oldFiles) { + if (path.parse(f).name === compiledFileName) { + debug('deleteOldJsFile - skip file:', compiledFileName) + continue + } // skip repeat js file + if (fs.existsSync(f)) { + debug('deleteOldJsFile - file exists:', f, originFileName) + this.manifestCache.remove(originFileName, { silent }) + debug('deleteOldJsFile - manifestCache removed:', originFileName) + fs.remove(f) + debug('deleteOldJsFile -file removed:', f) + } + } + } else { + this.manifestCache.remove(originFileName, { silent }) + debug('manifestCache removed:', originFileName) + } + } + + async addNewJs(args: AddFileArgs): Promise { + const { code = '' } = args + const { + viteConfig: { publicDir }, + } = globalConfig.get() + + const pathToDisk = this.setCache(args, globalConfig.get()) + + const jsFilePath = normalizePath(path.join(publicDir, pathToDisk)) + + fs.ensureDirSync(path.dirname(jsFilePath)) + + writeFile(jsFilePath, code) + } +} diff --git a/src/processor/ManifestCacheProcessor.ts b/src/processor/ManifestCacheProcessor.ts new file mode 100644 index 0000000..4ccfbbe --- /dev/null +++ b/src/processor/ManifestCacheProcessor.ts @@ -0,0 +1,56 @@ +import { normalizePath } from 'vite' +// import createDebug from 'debug' +import type { ManifestCache } from '../manifest-cache/ManifestCache' +import type { CacheValueEx } from '../manifest-cache' +import type { GlobalConfig } from '../global-config/GlobalConfigBuilder' +import { BaseCacheProcessor } from './BaseCacheProcessor' + +// const debug = createDebug('vite-plugin-public-typescript:ManifestCacheProcessor ===> ') + +export interface DeleteFileArgs { + originFileName: string + /** + * if true, will not write file to disk + */ + silent?: boolean + compiledFileName?: string +} + +export interface AddFileArgs { + originFileName: string + contentHash: string + silent?: boolean + code?: string +} + +export abstract class ManifestCacheProcessor extends BaseCacheProcessor { + constructor(manifestCache: ManifestCache) { + super(manifestCache) + this.manifestCache = manifestCache + } + + genCacheItemPath(args: { contentHash: string; originFileName: string; outputDir: string; base: string }) { + const { contentHash, originFileName, outputDir, base } = args + const hash = contentHash ? `.${contentHash}` : '' + return normalizePath(`${base + outputDir}/${originFileName}${hash}.js`) + } + + setCache(args: AddFileArgs, config: GlobalConfig) { + const { contentHash, originFileName, code } = args + const { + outputDir, + viteConfig: { base }, + } = config + + const pathWithBase = this.genCacheItemPath({ base, contentHash, originFileName, outputDir }) + + this.manifestCache.set({ + [originFileName]: { + path: pathWithBase, + _code: code || '', + _hash: contentHash, + }, + }) + return this.manifestCache.get()[originFileName]._pathToDisk || '' + } +} diff --git a/src/processor/MemoryCacheProcessor.ts b/src/processor/MemoryCacheProcessor.ts new file mode 100644 index 0000000..1cc0935 --- /dev/null +++ b/src/processor/MemoryCacheProcessor.ts @@ -0,0 +1,20 @@ +import { globalConfig } from '../global-config' +import type { CacheValueEx } from '../manifest-cache' +import type { ManifestCache } from '../manifest-cache//ManifestCache' +import { ManifestCacheProcessor } from './ManifestCacheProcessor' +import type { AddFileArgs, DeleteFileArgs } from './ManifestCacheProcessor' + +export class MemoryCacheProcessor extends ManifestCacheProcessor { + constructor(manifestCache: ManifestCache) { + super(manifestCache) + } + + async deleteOldJs(args: DeleteFileArgs): Promise { + const { originFileName, silent } = args + this.manifestCache.remove(originFileName, { silent }) + } + + async addNewJs(args: AddFileArgs): Promise { + this.setCache(args, globalConfig.get()) + } +} diff --git a/src/processor/processor.ts b/src/processor/processor.ts new file mode 100644 index 0000000..afdc54c --- /dev/null +++ b/src/processor/processor.ts @@ -0,0 +1,17 @@ +import type { VPPTPluginOptions } from '..' +import type { CacheValueEx } from '../manifest-cache' +import type { ManifestCache } from '../manifest-cache/ManifestCache' +import type { ManifestCacheProcessor } from './ManifestCacheProcessor' +import { FileCacheProcessor } from './FileCacheProcessor' +import { MemoryCacheProcessor } from './MemoryCacheProcessor' + +export function initCacheProcessor(options: Required, manifestCache: ManifestCache) { + const { destination } = options + + const processorContainer: Record = { + file: new FileCacheProcessor(manifestCache), + memory: new MemoryCacheProcessor(manifestCache), + } + + return processorContainer[destination] +}