Skip to content

Commit

Permalink
feat: implement cp
Browse files Browse the repository at this point in the history
  • Loading branch information
nonzzz committed Jan 30, 2024
1 parent 6576ee4 commit 9b31ead
Show file tree
Hide file tree
Showing 8 changed files with 449 additions and 9 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@ dist
temp
types
.tmpl
.dist
coverage

yarn-debug.log*
yarn-error.log*

.yarn/cache
.yarn/install-state.gz

.DS_Store
49 changes: 49 additions & 0 deletions __tests__/cp.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import path from 'path'
import fsp from 'fs/promises'
import fs from 'fs'
import zlib from 'zlib'
import { build } from 'vite'
import test from 'ava'
import tar from 'tar'
import { compression, cp } from '../src'
import { len, readAll } from '../src/utils'

const getId = () => Math.random().toString(32).slice(2, 10)

const dist = path.join(__dirname, 'dist')
const dest = path.join(__dirname, '.dist')

async function mockBuild(dir = 'public-assets-nest') {
const id = getId()
await build({
root: path.join(__dirname, 'fixtures', dir),
plugins: [compression({ skipIfLargerOrEqual: false }), cp({ dest: path.join(dest, id) })],
configFile: false,
logLevel: 'silent',
build: {
outDir: path.join(dist, id)
}
})
return id
}

test.after(async () => {
await fsp.rm(dist, { recursive: true })
await fsp.rm(dest, { recursive: true })
})

test('cp', async (t) => {
const id = await mockBuild('dynamic')
const files = await readAll(path.join(dist, id))
const destPath = path.join(dest, id)
await fsp.mkdir(destPath, { recursive: true })
await tar.extract({ file: `${destPath}.tar.gz`, cwd: destPath })
const dests = await readAll(destPath)
t.is(len(files), len(dests))
const gz = files.filter(s => s.endsWith('.gz'))
const diffGz = dests.filter(s => s.endsWith('.gz'))
t.is(len(gz), len(diffGz))
const diff1Js = files.filter((v) => v.endsWith('.js.gz')).map((v) => zlib.unzipSync(fs.readFileSync(v)))
const diff2Js = dests.filter((v) => v.endsWith('.js')).map((v) => fs.readFileSync(v))
t.deepEqual(diff1Js, diff2Js)
})
6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,20 +46,24 @@
},
"devDependencies": {
"@nolyfill/es-aggregate-error": "^1.0.21",
"@types/archiver": "^6.0.2",
"@types/node": "^17.0.14",
"@types/tar": "^6.1.11",
"ava": "^5.2.0",
"c8": "^7.13.0",
"eslint": "^8.40.0",
"eslint-config-kagura": "^2.1.1",
"playwright": "^1.32.3",
"sirv": "^2.0.3",
"tar": "^6.2.0",
"tsup": "^7.2.0",
"tsx": "^4.7.0",
"typescript": "^5.3.3",
"vite": "^4.4.9"
},
"dependencies": {
"@rollup/pluginutils": "^5.0.2"
"@rollup/pluginutils": "^5.0.2",
"archiver": "^6.0.1"
},
"ava": {
"files": [
Expand Down
46 changes: 46 additions & 0 deletions src/compress.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import zlib from 'zlib'
import util from 'util'
import fs from 'fs'
import fsp from 'fs/promises'
import path from 'path'
import type { BrotliOptions, ZlibOptions } from 'zlib'
import archiver from 'archiver'
import type { Algorithm, AlgorithmFunction, UserCompressionOptions } from './interface'
import { slash } from './utils'

export function ensureAlgorithm(userAlgorithm: Algorithm) {
const algorithm = userAlgorithm in zlib ? userAlgorithm : 'gzip'
Expand Down Expand Up @@ -42,3 +47,44 @@ export const defaultCompressionOptions: {
level: zlib.constants.Z_BEST_COMPRESSION
}
}

interface ArchiveOptions {
zlib: ZlibOptions
root: string
dest: string
}

// https://github.com/archiverjs/node-archiver/blob/master/examples/pack-tgz.js
export function createArchive(options: ArchiveOptions) {
const { root, zlib } = options
const pack = archiver('tar', { zlib })
return {
add(filename: string, content: Buffer) {
pack.append(content, { name: filename, mode: 0o755, date: new Date() })
},
async wait() {
if (!path.extname(options.dest)) {
options.dest = `${options.dest}.tar.gz`
}
const expected = slash(path.resolve(root, options.dest))
if (!fs.existsSync(expected)) {
const parent = slash(path.dirname(expected))
if (root !== parent) {
await fsp.mkdir(parent, { recursive: true })
}
}
const output = fs.createWriteStream(expected)
pack.pipe(output)
await new Promise((resolve, reject) => {
pack.finalize()
pack.on('finish', resolve)
pack.on('error', reject)
pack.on('warning', (err) => {
if (err.code !== 'ENOENT') {
reject(err)
}
})
})
}
}
}
60 changes: 57 additions & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,19 @@ import path from 'path'
import { createFilter } from '@rollup/pluginutils'
import type { Plugin, ResolvedConfig } from 'vite'
import { len, readAll, replaceFileName, slash } from './utils'
import { compress, defaultCompressionOptions, ensureAlgorithm } from './compress'
import { compress, createArchive, defaultCompressionOptions, ensureAlgorithm } from './compress'
import { createConcurrentQueue } from './task'
import type {
Algorithm,
AlgorithmFunction,
GenerateBundle,
Pretty,
StaticContent,
UserCompressionOptions,
ViteCompressionPluginConfig,
ViteCompressionPluginConfigAlgorithm,
ViteCompressionPluginConfigFunction,
ViteCpPluginOptions,
ViteWithoutCompressionPluginConfigFunction
} from './interface'

Expand Down Expand Up @@ -69,6 +71,58 @@ async function hijackGenerateBundle(plugin: Plugin, afterHook: GenerateBundle) {
}
}

function cp(opts: ViteCpPluginOptions): Plugin {
const { dest, zlib = defaultCompressionOptions.gzip } = opts
const statics: Map<string, StaticContent> = new Map()
const outputs: string[] = []
let root = process.cwd()
return {
name: 'vite-plugin-cp',
enforce: 'post',
buildStart() {
statics.clear()
if (!dest) {
this.error('vite-plugin-cp loose option `dest`')
}
},
async configResolved(config) {
outputs.push(...handleOutputOption(config))
root = config.root
const baseCondit = VITE_COPY_PUBLIC_DIR in config.build ? config.build.copyPublicDir : true
if (config.publicDir && baseCondit && fs.existsSync(config.publicDir)) {
const staticAssets = await readAll(config.publicDir)
const publicPath = path.join(config.root, path.relative(config.root, config.publicDir))
Promise.all(staticAssets.map(async (assets) => {
const content = await fsp.readFile(assets)
const file = slash(path.relative(publicPath, assets))
if (!statics.has(file)) {
statics.set(file, { content, filename: file })
}
}))
}
const plugin = config.plugins.find(p => p.name === VITE_INTERNAL_ANALYSIS_PLUGIN)
if (!plugin) throw new Error('vite-plugin-cp can\'t be work in versions lower than vite2.0.0')
hijackGenerateBundle(plugin, function (_, bundles) {
for (const fileName in bundles) {
const bundle = bundles[fileName]
if (!statics.has(fileName)) {
statics.set(fileName, { content: Buffer.from(bundle.type === 'asset' ? bundle.source : bundle.code), filename: fileName })
}
}
})
},
async closeBundle() {
const archive = createArchive({ zlib: typeof zlib === 'boolean' ? defaultCompressionOptions.gzip : zlib, root, dest })
// eslint-disable-next-line no-unused-vars
for (const [_, { filename, content }] of statics) {
archive.add(filename, content)
}
await archive.wait()
statics.clear()
}
}
}

function compression(): Plugin
function compression<A extends Algorithm>(opts: Pretty<ViteCompressionPluginConfigAlgorithm<A>>): Plugin
function compression<T extends UserCompressionOptions = NonNullable<unknown>>(opts: Pretty<ViteCompressionPluginConfigFunction<T>>): Plugin
Expand Down Expand Up @@ -176,8 +230,8 @@ function compression<T extends UserCompressionOptions, A extends Algorithm>(opts
}
}

export { compression }
export { compression, cp }

export default compression

export type { CompressionOptions, Algorithm, ViteCompressionPluginConfig } from './interface'
export type { CompressionOptions, Algorithm, ViteCompressionPluginConfig, ViteCpPluginOptions } from './interface'
10 changes: 10 additions & 0 deletions src/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,13 @@ export type ViteCompressionPluginConfig<T, A extends Algorithm> =
| ViteCompressionPluginConfigAlgorithm<A>

export type GenerateBundle = HookHandler<Plugin['generateBundle']>

export interface ViteCpPluginOptions {
dest: string,
zlib?: ZlibOptions
}

export interface StaticContent {
content: Buffer,
filename: string
}
2 changes: 1 addition & 1 deletion src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export function slash(path: string) {
export async function readAll(entry: string) {
const paths = await Promise.all((await fsp.readdir(entry)).map((dir) => path.join(entry, dir)))
let pos = 0
const result = []
const result: string[] = []
while (pos !== len(paths)) {
const dir = paths[pos]
const stat = await fsp.stat(dir)
Expand Down
Loading

0 comments on commit 9b31ead

Please sign in to comment.