Skip to content
Snippets Groups Projects
Commit 038b2fa9 authored by andree.klattenhoff's avatar andree.klattenhoff
Browse files

fixup: Support gzip precompress

parent 716fd43f
No related branches found
No related tags found
1 merge request!316Draft: Run brotli compression during the build
{
"name": "@open-xchange/vite-plugin-ox-bundle",
"version": "0.1.4-pre1",
"version": "0.1.4-pre2",
"description": "vite plugin to create a custom bundle file to reduce js requests for App Suite UI",
"main": "dist/index.js",
"types": "dist/index.d.ts",
......
import { createReadStream, createWriteStream } from 'node:fs'
import { mkdir, readFile, stat, writeFile } from 'node:fs/promises'
import { readFile, stat, writeFile } from 'node:fs/promises'
import { pipeline } from 'node:stream/promises'
import zlib from 'node:zlib'
import { createBrotliCompress, createGzip } from 'node:zlib'
import type { Plugin } from 'vite'
import { glob } from 'glob'
export const PROJECT_NAME = '@open-xchange/vite-plugin-ox-bundle'
/**
* Configuration options for the Vite plugin "vite-plugin-ox-bundle".
*/
export interface VitePluginOxBundleOptions {
/** Path to predefined bundles file. Default: `./bundles.json` */
src?: string
/** html files to be added to manifest */
htmlFiles?: string[]
/** Paths to be ignored. Default: `[]`. */
ignore?: string[]
}
/**
* This plugin creates bundles fom a list of css/js/svg files.
*
......@@ -26,85 +14,17 @@ export interface VitePluginOxBundleOptions {
* delimiter splits between different files inside the string. The second delimiter
* is used to split between fileName and content from a file.
*/
export default function pluginOxBundle (options?: VitePluginOxBundleOptions): Plugin {
const { src = './bundles.json', htmlFiles = [] } = options || {}
const ignore = new Set<string>(options?.ignore)
export default function pluginOxBundle (): Plugin {
return {
name: PROJECT_NAME,
writeBundle: {
order: 'post',
sequential: true,
async handler ({ dir }, viteBundle) {
async handler ({ dir }) {
if (!dir) throw new Error('missing output directory')
const dirName = `${dir}/bundles`
const file = await readFile(src, 'utf8')
const bundles = JSON.parse(file) as unknown
if (!Array.isArray(bundles)) throw new Error(`array expected in ${src}`)
await mkdir(dirName)
const data = await readFile(`${dir}/manifest.json`, 'utf8')
const manifest = JSON.parse(data) as Record<string, unknown>
for (const bundle of bundles as unknown[]) {
if (!bundle || typeof bundle !== 'object') throw new Error(`objects expected in ${src}`)
interface OxBundle {
name: string
files: string[]
}
const { name, files } = bundle as OxBundle
const paths: string[] = []
while (files.length > 0) {
const moduleId = files.shift()
if (!moduleId || ignore.has(moduleId)) continue
const module = viteBundle[moduleId]
if (module?.type === 'chunk') {
paths.push(module.fileName)
} else {
paths.push(moduleId)
ignore.add(moduleId)
continue
}
// Traverse tree
module.imports?.forEach(key => files.push(key))
module.viteMetadata?.importedCss?.forEach(id => paths.push(id))
// Do not process them again
ignore.add(moduleId)
}
const result: string[] = []
await Promise.all(paths.map(async path => {
try {
const content = (await readFile(`${dir}/${path}`, 'utf8')).toString()
result.push(`${path}%&$2${content}`)
} catch (err) {
if (!err || typeof err !== 'object' || !('code' in err) || err.code !== 'ENOENT') throw err
console.warn(`[${PROJECT_NAME}][${name}] ${path} not found`)
}
}))
await writeFile(`${dirName}/${name}`, result.join('%&$1'))
const jsCount = paths.filter((path) => path.endsWith('.js')).length
const cssCount = paths.filter((path) => path.endsWith('.css')).length
const svgCount = paths.filter((path) => path.endsWith('.svg')).length
if (process.env.LOG_LEVEL === 'info') console.log(`[${PROJECT_NAME}][${name}] includes ${result.length} files (${jsCount}/js | ${cssCount}/css | ${svgCount}/svg}`)
// Create manifest.json entry
const manifestName = `bundles/${name}`
manifest[manifestName] = {
src: manifestName,
file: manifestName
}
}
// Add static files to manifest.json for warmup cache of ui-middleware
const staticFiles = await glob(
[
......@@ -125,29 +45,22 @@ export default function pluginOxBundle (options?: VitePluginOxBundleOptions): Pl
}
}
// Run brotli compression
if (staticFile.endsWith('json') || staticFile.endsWith('html')) continue
const { size } = await stat(staticFile)
if (size > 600) {
const readStream = createReadStream(staticFile)
const writeStream = createWriteStream(`${staticFile}.br`)
const brotli = zlib.createBrotliCompress()
pipeline(readStream, brotli, writeStream).catch((err) => console.log(`Error during compression of ${staticFile}: ${err}`))
}
}
for (const staticFile of htmlFiles) {
if (staticFile === 'index.html' && 'index.html' in manifest) {
// Recreate this entry because vite added index.html as reference
// to index.html.js in manifest.json before.
manifest['index.html.js'] = {
src: 'index.html.js',
file: 'index.html.js'
if (staticFile.endsWith('json') || staticFile.endsWith('mp3')) continue
try {
const { size } = await stat(staticFile)
if (size > 600) {
const readStream = createReadStream(staticFile)
if (staticFile.endsWith('.html')) {
const writeStream = createWriteStream(`${staticFile}.gz`)
pipeline(readStream, createGzip(), writeStream).catch((err) => console.log(err))
} else {
const writeStream = createWriteStream(`${staticFile}.br`)
pipeline(readStream, createBrotliCompress(), writeStream).catch((err) => console.log(err))
}
}
}
manifest[staticFile] = {
src: staticFile,
file: staticFile
} catch (err) {
console.log(`Error during compression of ${staticFile}, `, err)
}
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment