1
1
import fs from 'node:fs'
2
2
import path from 'node:path'
3
+ import crypto from 'node:crypto'
3
4
import { createRequire , builtinModules } from 'node:module'
4
5
import type { Alias , Plugin , UserConfig } from 'vite'
5
6
import esbuild from 'esbuild'
6
7
import libEsm from 'lib-esm'
8
+ import { COLOURS } from 'vite-plugin-utils/function'
7
9
import { builtins } from './build-config'
8
10
9
11
export type DepOptimizationConfig = {
@@ -27,6 +29,7 @@ const name = 'vite-plugin-electron-renderer:optimizer'
27
29
28
30
let root : string
29
31
let node_modules_path : string
32
+ let cache : Cache
30
33
31
34
export default function optimizer ( options : DepOptimizationConfig = { } ) : Plugin [ ] | undefined {
32
35
const { include, buildOptions } = options
@@ -38,8 +41,7 @@ export default function optimizer(options: DepOptimizationConfig = {}): Plugin[]
38
41
config ( config ) {
39
42
root = config . root ? path . resolve ( config . root ) : process . cwd ( )
40
43
node_modules_path = node_modules ( root )
41
-
42
- fs . rmSync ( path . join ( node_modules_path , CACHE_DIR ) , { recursive : true , force : true } )
44
+ cache = new Cache ( path . join ( node_modules_path , CACHE_DIR ) )
43
45
44
46
const aliases : Alias [ ] = [
45
47
{
@@ -97,10 +99,10 @@ export default function optimizer(options: DepOptimizationConfig = {}): Plugin[]
97
99
continue
98
100
}
99
101
100
- const pkgId = path . join ( node_modules_path , name , 'package.json' )
101
- if ( fs . existsSync ( pkgId ) ) {
102
+ const pkgJson = path . join ( node_modules_path , name , 'package.json' )
103
+ if ( fs . existsSync ( pkgJson ) ) {
102
104
// bare module
103
- const pkg = cjs_require ( pkgId )
105
+ const pkg = cjs_require ( pkgJson )
104
106
if ( pkg . type === 'module' ) {
105
107
deps . push ( { esm : name } )
106
108
continue
@@ -109,29 +111,30 @@ export default function optimizer(options: DepOptimizationConfig = {}): Plugin[]
109
111
continue
110
112
}
111
113
112
- const tmp = path . join ( node_modules_path , name )
114
+ const pkgPath = path . join ( node_modules_path , name )
113
115
try {
114
116
// dirname or filename 🤔
115
117
// `foo/bar` or `foo/bar/index.js`
116
- const filename = cjs_require . resolve ( tmp )
118
+ const filename = cjs_require . resolve ( pkgPath )
117
119
if ( path . extname ( filename ) === '.mjs' ) {
118
120
deps . push ( { esm : name , filename } )
119
121
continue
120
122
}
121
123
deps . push ( { cjs : name , filename } )
122
124
continue
123
125
} catch ( error ) {
124
- console . log ( 'Can not resolve path:' , tmp )
126
+ console . log ( COLOURS . red ( 'Can not resolve path:' ) , pkgPath )
125
127
}
126
128
}
127
129
128
130
for ( const dep of deps ) {
129
131
if ( ! dep . filename ) {
130
132
const module = ( dep . cjs || dep . esm ) as string
131
133
try {
134
+ // TODO: resolve(, [paths condition])
132
135
dep . filename = cjs_require . resolve ( module )
133
136
} catch ( error ) {
134
- console . log ( 'Can not resolve module:' , module )
137
+ console . log ( COLOURS . red ( 'Can not resolve module:' ) , module )
135
138
}
136
139
}
137
140
if ( ! dep . filename ) {
@@ -173,9 +176,16 @@ function cjsBundling(args: {
173
176
requireId : string
174
177
} ) {
175
178
const { name, require, requireId } = args
179
+ const { destpath, destname } = dest ( name )
180
+ if ( cache . checkHash ( destname ) ) return
181
+
176
182
const { exports } = libEsm ( { exports : Object . keys ( cjs_require ( requireId ) ) } )
177
183
const code = `const _M_ = require("${ require } ");\n${ exports } `
178
- writeFile ( { name, code } )
184
+
185
+ ! fs . existsSync ( destpath ) && fs . mkdirSync ( destpath , { recursive : true } )
186
+ fs . writeFileSync ( destname , code )
187
+ cache . writeCache ( destname )
188
+ console . log ( COLOURS . cyan ( 'Pre-bundling:' ) , COLOURS . yellow ( name ) )
179
189
}
180
190
181
191
async function esmBundling ( args : {
@@ -185,6 +195,8 @@ async function esmBundling(args: {
185
195
} ) {
186
196
const { name, entry, buildOptions } = args
187
197
const { name_cjs, destname_cjs } = dest ( name )
198
+ if ( cache . checkHash ( destname_cjs ) ) return
199
+
188
200
return esbuild . build ( {
189
201
entryPoints : [ entry ] ,
190
202
outfile : destname_cjs ,
@@ -199,6 +211,7 @@ async function esmBundling(args: {
199
211
...buildOptions ,
200
212
} ) . then ( result => {
201
213
if ( ! result . errors . length ) {
214
+ cache . writeCache ( destname_cjs )
202
215
cjsBundling ( {
203
216
name,
204
217
require : `${ CACHE_DIR } /${ name } /${ name_cjs } ` ,
@@ -209,17 +222,6 @@ async function esmBundling(args: {
209
222
} )
210
223
}
211
224
212
- function writeFile ( args : {
213
- name : string
214
- code : string
215
- } ) {
216
- const { name, code } = args
217
- const { destpath, destname } = dest ( name )
218
- ! fs . existsSync ( destpath ) && fs . mkdirSync ( destpath , { recursive : true } )
219
- fs . writeFileSync ( destname , code )
220
- console . log ( 'Pre-bundling:' , name )
221
- }
222
-
223
225
function dest ( name : string ) {
224
226
const destpath = path . join ( node_modules_path , CACHE_DIR , name )
225
227
const name_js = 'index.js'
@@ -266,3 +268,72 @@ function node_modules(root: string, count = 0): string {
266
268
}
267
269
// For ts-check
268
270
node_modules . p = ''
271
+
272
+ // ----------------------------------------
273
+
274
+ export interface ICache {
275
+ timestamp ?: number
276
+ optimized ?: {
277
+ [ filename : string ] : {
278
+ hash : string
279
+ }
280
+ }
281
+ }
282
+
283
+ class Cache {
284
+ static getHash ( filename : string ) {
285
+ return crypto . createHash ( 'md5' ) . update ( fs . readFileSync ( filename ) ) . digest ( 'hex' )
286
+ }
287
+
288
+ constructor (
289
+ public root : string ,
290
+ public cacheFile = path . join ( root , '_metadata.json' ) ,
291
+ ) {
292
+ // TODO: cleanup meta
293
+ }
294
+
295
+ checkHash ( filename : string ) {
296
+ if ( ! fs . existsSync ( filename ) ) {
297
+ return false
298
+ }
299
+ let hash : string
300
+ try {
301
+ hash = Cache . getHash ( filename )
302
+ } catch {
303
+ return false
304
+ }
305
+ const { optimized = { } } = this . readCache ( )
306
+ for ( const [ file , meta ] of Object . entries ( optimized ) ) {
307
+ if ( filename === file && hash === meta . hash ) {
308
+ return true
309
+ }
310
+ }
311
+ return false
312
+ }
313
+
314
+ readCache ( ) : ICache {
315
+ const cache : ICache = { }
316
+ try {
317
+ const json = JSON . parse ( fs . readFileSync ( this . cacheFile , 'utf8' ) )
318
+ Object . assign ( cache , { optimized : json . optimized } )
319
+ } catch { }
320
+ return cache
321
+ }
322
+
323
+ writeCache ( filename : string ) {
324
+ if ( ! fs . existsSync ( filename ) ) {
325
+ throw new Error ( `${ filename } is not exist!` )
326
+ }
327
+ const { optimized = { } } = this . readCache ( )
328
+ const newCache : ICache = {
329
+ timestamp : Date . now ( ) ,
330
+ optimized : {
331
+ ...optimized ,
332
+ [ filename ] : {
333
+ hash : Cache . getHash ( filename ) ,
334
+ } ,
335
+ } ,
336
+ }
337
+ fs . writeFileSync ( this . cacheFile , JSON . stringify ( newCache , null , 2 ) )
338
+ }
339
+ }
0 commit comments