Skip to content

Commit

Permalink
feat: ✨ sync add/delete ts file
Browse files Browse the repository at this point in the history
  • Loading branch information
hemengke1997 committed Nov 8, 2022
1 parent 848e26f commit eaa8688
Show file tree
Hide file tree
Showing 7 changed files with 171 additions and 91 deletions.
1 change: 0 additions & 1 deletion playground/vite-project/public/test.bb9057b2.js

This file was deleted.

1 change: 1 addition & 0 deletions playground/vite-project/public/test.e7681e84.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
console.log("this is haha!!!");
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"test": "/test.bb9057b2.js"
"test": "/test.e7681e84.js"
}
4 changes: 3 additions & 1 deletion playground/vite-project/publicTypescript/test.ts
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
console.log('this is test')
console.log('this is haha!!!')

export {}
141 changes: 57 additions & 84 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,65 +1,13 @@
import path from 'node:path'
import type { PluginOption, ResolvedConfig, TransformOptions } from 'vite'
import { normalizePath, transformWithEsbuild } from 'vite'
import { normalizePath } from 'vite'
import fg from 'fast-glob'
import fs from 'fs-extra'
import { getContentHash } from './utils'
import { addJsFile, build, deleteOldFiles, isPublicTypescript, reloadPage, ts } from './utils'
import { ManifestCache } from './utils/manifestCache'

let buildLength = 0
let current = 0

type BuildOptions = {
filePath: string
publicDir: string
cache: ManifestCache
code?: string
} & VitePluginOptions

function build(options: BuildOptions) {
const { filePath, publicDir, cache, transformOptions, outputDir, inputDir, manifestName } = options
const code = options.code || fs.readFileSync(filePath, 'utf-8')
const fileName = path.basename(filePath, path.extname(filePath))
transformWithEsbuild(code, fileName, {
loader: 'ts',
format: 'esm',
minify: true,
platform: 'browser',
sourcemap: false,
...transformOptions,
}).then(async (res) => {
let outPath = ''
if (options.hash) {
const hash = getContentHash(res.code)
outPath = normalizePath(`${outputDir}/${fileName}.${hash}.js`)
} else {
outPath = normalizePath(`${outputDir}/${fileName}.js`)
}

const fp = normalizePath(path.join(publicDir, outPath))
const oldFiles = fg.sync(normalizePath(path.join(publicDir, `${outputDir}/${fileName}.?(*.)js`)))
// if exits old files
if (oldFiles.length) {
const oldFile = oldFiles.filter((t) => t !== fp)
// delete old files
oldFile.forEach(async (f) => {
if (fs.existsSync(f)) {
await fs.remove(f)
}
})
}
await fs.ensureDir(path.dirname(fp))
await fs.writeFile(fp, res.code)
cache.setCache({ key: fileName, value: outPath })
// write cache
current++
if (current === buildLength) {
cache.writeCache(`${inputDir}/${manifestName}.json`)
}
})
}

interface VitePluginOptions {
export interface VitePluginOptions {
/**
* @description vite ssrBuild
* @see https://vitejs.dev/config/#conditional-config
Expand Down Expand Up @@ -91,65 +39,90 @@ interface VitePluginOptions {
hash?: boolean
}

const defaultOptions: Required<VitePluginOptions> = {
inputDir: 'publicTypescript',
outputDir: '/',
manifestName: 'manifest',
hash: true,
ssrBuild: false,
transformOptions: {},
}

export function publicTypescript(options: VitePluginOptions): PluginOption {
const {
ssrBuild = false,
inputDir = 'publicTypescript',
outputDir = '/',
manifestName = 'manifest',
hash = true,
} = options
const opts = {
...defaultOptions,
...options,
}

let config: ResolvedConfig

let files: string[]
const cache = new ManifestCache()

return {
name: 'vite:public-typescript',
configResolved(c) {
config = c
},
buildStart() {
if (ssrBuild || config.build.ssr) return
const outDir = config.publicDir
const root = config.root
const files = fg.sync(normalizePath(path.resolve(root, `${inputDir}/*.ts`)), {
cwd: root,
files = fg.sync(normalizePath(path.resolve(config.root, `${opts.inputDir}/*.ts`)), {
cwd: config.root,
absolute: true,
})

buildLength = files.length
},
buildStart() {
if (opts.ssrBuild || config.build.ssr) return
const outDir = config.publicDir

files.forEach((f) => {
build({
...options,
...opts,
filePath: f,
publicDir: outDir,
cache,
inputDir,
outputDir,
hash,
manifestName,
buildLength,
})
})
},
configureServer(server) {
const { watcher, ws } = server
watcher.on('unlink', async (f) => {
// ts file deleted
if (isPublicTypescript({ filePath: f, root: config.root, inputDir: opts.inputDir! })) {
const fileName = path.basename(f, ts)
// need to delete js
await deleteOldFiles({ publicDir: config.publicDir, fileName, cache, ...opts })
reloadPage(ws)
}
})

watcher.on('add', async (f) => {
// ts file added
if (isPublicTypescript({ filePath: f, root: config.root, inputDir: opts.inputDir! })) {
const fileName = path.basename(f, ts)
// need to add js
await addJsFile({ cache, fileName, buildLength, publicDir: config.publicDir, ...opts })
reloadPage(ws)
}
})
},
async handleHotUpdate(ctx) {
if (path.extname(ctx.file) === '.ts' && ctx.file.includes(normalizePath(path.resolve(config.root, inputDir)))) {
if (
isPublicTypescript({
filePath: ctx.file,
inputDir: opts.inputDir!,
root: config.root,
})
) {
const code = await ctx.read()
build({
...options,
...opts,
filePath: ctx.file,
publicDir: config.publicDir,
cache,
code,
inputDir,
outputDir,
hash,
manifestName,
})
ctx.server.ws.send({
type: 'full-reload',
buildLength,
})
reloadPage(ctx.server.ws)
return []
}
},
Expand Down
105 changes: 105 additions & 0 deletions src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,110 @@
import { createHash } from 'node:crypto'
import path from 'node:path'
import type { WebSocketServer } from 'vite'
import { normalizePath, transformWithEsbuild } from 'vite'

import fg from 'fast-glob'
import fs from 'fs-extra'
import type { VitePluginOptions } from '..'
import type { ManifestCache } from './manifestCache'

export function getContentHash(chunk: string | Uint8Array) {
return createHash('sha256').update(chunk).digest('hex').substring(0, 8)
}

type BuildOptions = {
filePath: string
publicDir: string
cache: ManifestCache
code?: string
buildLength: number
} & Required<VitePluginOptions>

export async function build(options: BuildOptions) {
const { filePath, publicDir, transformOptions, outputDir } = options
const code = options.code || fs.readFileSync(filePath, 'utf-8')
const fileName = path.basename(filePath, path.extname(filePath))
await transformWithEsbuild(code, fileName, {
loader: 'ts',
format: 'esm',
minify: true,
platform: 'browser',
sourcemap: false,
...transformOptions,
}).then(async (res) => {
await deleteOldFiles({
...options,
publicDir,
fileName,
outputDir,
})

await addJsFile({ code: res.code, fileName, ...options })
})
}

type TDeleteFile = {
publicDir: string
outputDir: string
fileName: string
cache: ManifestCache
} & Required<VitePluginOptions>

export const ts = '.ts'

export async function deleteOldFiles(args: TDeleteFile) {
const { publicDir, outputDir, fileName, cache, inputDir, manifestName } = args
const oldFiles = fg.sync(normalizePath(path.join(publicDir, `${outputDir}/${fileName}.?(*.)js`)))
// if exits old files
if (oldFiles.length) {
// delete old files
for (const f of oldFiles) {
if (fs.existsSync(f)) {
// and modify manifest
cache.removeCache(fileName)
cache.writeCache(`${inputDir}/${manifestName}.json`)
await fs.remove(f)
}
}
}
}

type TAddFile = {
code?: string
fileName: string
publicDir: string
cache: ManifestCache
buildLength: number
} & Required<VitePluginOptions>

let currentBuildTimes = 0
export async function addJsFile(args: TAddFile) {
const { hash, code = '', outputDir, fileName, publicDir, cache, buildLength, manifestName, inputDir } = args
let outPath = ''
if (hash) {
const hash = getContentHash(code)
outPath = normalizePath(`${outputDir}/${fileName}.${hash}.js`)
} else {
outPath = normalizePath(`${outputDir}/${fileName}.js`)
}

const fp = normalizePath(path.join(publicDir, outPath))
await fs.ensureDir(path.dirname(fp))
await fs.writeFile(fp, code)
cache.setCache({ key: fileName, value: outPath })
// write cache
currentBuildTimes++
if (currentBuildTimes >= buildLength) {
cache.writeCache(`${inputDir}/${manifestName}.json`)
}
}

export function reloadPage(ws: WebSocketServer) {
ws.send({
type: 'full-reload',
})
}

export function isPublicTypescript({ filePath, root, inputDir }: { filePath: string; root: string; inputDir: string }) {
return path.extname(filePath) === ts && normalizePath(filePath).includes(normalizePath(path.resolve(root, inputDir)))
}
8 changes: 4 additions & 4 deletions src/utils/manifestCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ let cacheMap = new Map<CacheType['key'], CacheType['value']>()

export class ManifestCache {
setCache(c: CacheType) {
this.removeCache(c)
this.removeCache(c.key)

cacheMap.set(c.key, c.value)
}
Expand All @@ -19,9 +19,9 @@ export class ManifestCache {
return cacheMap.get(k)
}

removeCache(c: CacheType) {
if (cacheMap.has(c.key)) {
cacheMap.delete(c.key)
removeCache(k: CacheType['key']) {
if (cacheMap.has(k)) {
cacheMap.delete(k)
}
}

Expand Down

0 comments on commit eaa8688

Please sign in to comment.