Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(compat): remove generatorBundle hook order. #20

Merged
merged 6 commits into from
Feb 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
10 changes: 10 additions & 0 deletions __tests__/plugin.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import test from 'ava'
import path from 'path'
import zlib, { BrotliOptions } from 'zlib'
import fs from 'fs'
import fsp from 'fs/promises'
import { build } from 'vite'
import { compression } from '../src'
Expand Down Expand Up @@ -174,3 +175,12 @@ test('dynamic', async (t) => {
const compressed = len(r.filter((s) => s.endsWith('.gz')))
t.is(compressed, 4)
})

test('dynamic diff', async (t) => {
const ids = await Promise.all([mockBuild({ deleteOriginalAssets: true }, 'dynamic'), mockBuild({}, 'dynamic')])
await sleep(3000)
const [diff1, diff2] = await Promise.all(ids.map((id) => readAll(path.join(dist, id))))
const diff1Js = diff1.filter((v) => v.endsWith('.js.gz')).map((v) => zlib.unzipSync(fs.readFileSync(v)))
const diff2Js = diff2.filter((v) => v.endsWith('.js')).map((v) => fs.readFileSync(v))
t.deepEqual(diff1Js, diff2Js)
})
1 change: 1 addition & 0 deletions example/client/src/app.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
<DynamicComponent />
<fe-spacer :x="2" :y="2" />
<fe-button type="success" auto size="mini">Action</fe-button>
<text />
</div>
</template>

Expand Down
3 changes: 3 additions & 0 deletions example/client/src/dynamic.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
p {
font-size: 30px;
}
2 changes: 2 additions & 0 deletions example/client/src/dynamic.js
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
import('./dynamic.css')

export const d = 'dynamic'
4 changes: 4 additions & 0 deletions example/client/src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ import App from './app.vue'
import '@fect-ui/themes'
import '@fect-ui/vue/dist/cjs/main.css'

import { seq } from './seq'

import('./dynamic').then((re) => console.log(re.d))

createApp(App).mount('#app')

console.log(seq)
1 change: 1 addition & 0 deletions example/client/src/seq.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const seq = 1
10 changes: 5 additions & 5 deletions example/package.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
{
"name": "example",
"scripts": {
"dev":"vite",
"build":"vite build",
"preview":"node ./server/app.js"
"dev": "vite",
"build": "vite build",
"preview": "node ./server/app.js"
},
"devDependencies": {
"vite": "^3.2.3",
"vite": "3.0.9",
"@vitejs/plugin-vue": "^3.1.2",
"vite-plugin-compression2":"file:../"
"vite-plugin-compression2": "file:../"
},
"dependencies": {
"@fect-ui/vue": "^1.6.1",
Expand Down
9 changes: 4 additions & 5 deletions example/vite.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,9 @@ import { compression } from 'vite-plugin-compression2'
export default defineConfig({
plugins: [
vue(),
process.env.NODE_ENV === 'production' &&
compression({
include: [/\.(js)$/, /\.(css)$/],
deleteOriginalAssets: true
})
compression({
include: [/\.(js)$/, /\.(css)$/],
deleteOriginalAssets: true
})
]
})
299 changes: 151 additions & 148 deletions example/yarn.lock

Large diffs are not rendered by default.

3 changes: 0 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,6 @@
"dependencies": {
"@rollup/pluginutils": "^5.0.2"
},
"peerDependencies": {
"vite": "^3.0.0 || ^4.0.0"
},
"ava": {
"files": [
"__tests__/*.spec.ts"
Expand Down
105 changes: 60 additions & 45 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import path from 'path'
import { createFilter } from '@rollup/pluginutils'
import { len, replaceFileName, slash } from './utils'
import { defaultCompressionOptions, ensureAlgorithm, transfer } from './compress'
import type { Plugin } from 'vite'
import type { Plugin, ChunkMetadata } from 'vite'
import type {
Algorithm,
AlgorithmFunction,
Expand All @@ -15,6 +15,15 @@ import type {
UserCompressionOptions
} from './interface'

const VITE_INTERNAL_CHUNK_META = 'viteMetadata'

type HandleCompressInvork = ([file, meta]: [string, CompressMetaInfo]) => Promise<void>

async function handleCompress(tasks: Map<string, CompressMetaInfo>, invork: HandleCompressInvork) {
if (!tasks.size) return
await Promise.all(Array.from(tasks.entries()).map(invork))
}

function compression(): Plugin
function compression<A extends Algorithm>(opts: ViteCompressionPluginConfigAlgorithm<A>): Plugin
function compression<T = UserCompressionOptions>(opts: ViteCompressionPluginConfigFunction<T>): Plugin
Expand Down Expand Up @@ -55,60 +64,66 @@ function compression<T, A extends Algorithm>(opts: ViteCompressionPluginConfig<T
configResolved(config) {
zlib.dest = config.build.outDir
},
// We need specify the order of the hook
// Becasue in vite's intenral logic it will
// trigger _preload logic. So that if we run the hook
// before vite's importAnalysisBuild function it will
// cause loose all of link map.
// Please see: https://github.com/vitejs/vite/blob/HEAD/packages/vite/src/node/plugins/index.ts#L94-L98
generateBundle: {
order: 'post',
async handler(_, bundles) {
for (const fileName in bundles) {
if (!filter(fileName)) continue
const bundle = bundles[fileName]
const result = bundle.type == 'asset' ? bundle.source : bundle.code
const size = len(result)
if (size < threshold) continue
const effect = bundle.type === 'chunk' && !!len(bundle.dynamicImports)
const meta: CompressMetaInfo = Object.create(null)
meta.effect = effect
if (meta.effect) {
meta.file = slash(path.join(zlib.dest, fileName))
}
schedule.set(fileName, meta)
}
try {
if (!schedule.size) return
await Promise.all(
Array.from(schedule.entries()).map(async ([file, meta]) => {
if (meta.effect) return
const bundle = bundles[file]
const source = bundle.type === 'asset' ? bundle.source : bundle.code
const compressed = await transfer(Buffer.from(source), zlib.algorithm, zlib.options)
const fileName = replaceFileName(file, zlib.filename)
this.emitFile({ type: 'asset', source: compressed, fileName })
if (deleteOriginalAssets) Reflect.deleteProperty(bundles, file)
schedule.delete(file)
// Unfortunately. Vite support using object as hooks to change execution order need at least 3.1.0
// So we should record that with side Effect bundle file. (Because file with dynamic import will trigger vite's internal importAnalysisBuild logic and it will generator vite's placeholder.)
// Vite importAnalysisBuild source code: https://github.com/vitejs/vite/blob/main/packages/vite/src/node/plugins/importAnalysisBuild.ts
// Vite's plugin order see: https://github.com/vitejs/vite/blob/HEAD/packages/vite/src/node/plugins/index.ts#L94-L98
async generateBundle(_, bundles) {
for (const fileName in bundles) {
if (!filter(fileName)) continue
const bundle = bundles[fileName]
const result = bundle.type == 'asset' ? bundle.source : bundle.code
const size = len(result)
if (size < threshold) continue
const effect = bundle.type === 'chunk' && !!len(bundle.dynamicImports)
const meta: CompressMetaInfo = Object.create(null)
meta.effect = effect
if (meta.effect) {
meta.file = slash(path.join(zlib.dest, fileName))
if (VITE_INTERNAL_CHUNK_META in bundle) {
// This is a hack logic. We get all bundle file reference relation. And record them with effect.
// Some case like virtual module we should ignored them.
const { importedAssets, importedCss } = bundle[VITE_INTERNAL_CHUNK_META] as ChunkMetadata
// @ts-ignored
const imports = [...importedAssets, ...importedCss, ...bundle.dynamicImports]
imports.forEach((importer) => {
importer in bundles &&
schedule.set(importer, { effect: true, file: slash(path.join(zlib.dest, importer)) })
})
)
} catch (error) {
this.error(error)
}
}
if (!schedule.has(fileName) && bundle) schedule.set(fileName, meta)
}
try {
await handleCompress(schedule, async ([file, meta]) => {
if (meta.effect) return
const bundle = bundles[file]
const source = bundle.type === 'asset' ? bundle.source : bundle.code
const compressed = await transfer(Buffer.from(source), zlib.algorithm, zlib.options)
const fileName = replaceFileName(file, zlib.filename)
this.emitFile({ type: 'asset', source: compressed, fileName })
if (deleteOriginalAssets) Reflect.deleteProperty(bundles, file)
schedule.delete(file)
})
} catch (error) {
this.error(error)
}
},
async closeBundle() {
if (!schedule.size) return
await Promise.all(
Array.from(schedule.entries()).map(async ([file, meta]) => {
if (!meta.effect) return
await handleCompress(schedule, async ([file, meta]) => {
if (!meta.effect) return
try {
const buf = await fsp.readFile(meta.file)
const compressed = await transfer(buf, zlib.algorithm, zlib.options)
const fileName = replaceFileName(file, zlib.filename)
await fsp.writeFile(path.join(zlib.dest, fileName), compressed)
if (deleteOriginalAssets) await fsp.rm(meta.file, { recursive: true, force: true })
})
)
} catch {
// issue #18
// In somecase. Like vuepress it will called vite build with `Promise.all`. But it's concurrency. when we record the
// file fd. It had been changed. So that we should catch the error
}
})
schedule.clear()
}
}
Expand Down