@@ -3,11 +3,14 @@ import { create } from 'gc-hook';
3
3
import { RUNNING_IN_WORKER , createProgress , writeFile } from './_utils.js' ;
4
4
import { getFormat , loader , loadProgress , registerJSModule , run , runAsync , runEvent } from './_python.js' ;
5
5
import { stdio } from './_io.js' ;
6
- import { isArray } from '../utils.js' ;
6
+ import { IDBMapSync , isArray } from '../utils.js' ;
7
7
8
8
const type = 'pyodide' ;
9
9
const toJsOptions = { dict_converter : Object . fromEntries } ;
10
10
11
+ const { stringify } = JSON ;
12
+
13
+ // REQUIRES INTEGRATION TEST
11
14
/* c8 ignore start */
12
15
let overrideFunction = false ;
13
16
const overrideMethod = method => ( ...args ) => {
@@ -75,10 +78,7 @@ const applyOverride = () => {
75
78
} ;
76
79
77
80
const progress = createProgress ( 'py' ) ;
78
- /* c8 ignore stop */
79
81
80
- // REQUIRES INTEGRATION TEST
81
- /* c8 ignore start */
82
82
export default {
83
83
type,
84
84
module : ( version = '0.26.2' ) =>
@@ -88,15 +88,45 @@ export default {
88
88
if ( ! RUNNING_IN_WORKER && config . experimental_create_proxy === 'auto' )
89
89
applyOverride ( ) ;
90
90
progress ( 'Loading Pyodide' ) ;
91
- const { stderr, stdout, get } = stdio ( ) ;
91
+ let { packages } = config ;
92
+ progress ( 'Loading Storage' ) ;
92
93
const indexURL = url . slice ( 0 , url . lastIndexOf ( '/' ) ) ;
94
+ // each pyodide version shares its own cache
95
+ const storage = new IDBMapSync ( indexURL ) ;
96
+ const options = { indexURL } ;
97
+ const save = config . packages_cache !== 'never' ;
98
+ await storage . sync ( ) ;
99
+ // packages_cache = 'never' means: erase the whole DB
100
+ if ( ! save ) storage . clear ( ) ;
101
+ // otherwise check if cache is known
102
+ else if ( packages ) {
103
+ packages = packages . slice ( 0 ) . sort ( ) ;
104
+ // packages are uniquely stored as JSON key
105
+ const key = stringify ( packages ) ;
106
+ if ( storage . has ( key ) ) {
107
+ const blob = new Blob (
108
+ [ storage . get ( key ) ] ,
109
+ { type : 'application/json' } ,
110
+ ) ;
111
+ // this should be used to bootstrap loadPyodide
112
+ options . lockFileURL = URL . createObjectURL ( blob ) ;
113
+ // no need to use micropip manually here
114
+ options . packages = packages ;
115
+ packages = null ;
116
+ }
117
+ }
118
+ progress ( 'Loaded Storage' ) ;
119
+ const { stderr, stdout, get } = stdio ( ) ;
93
120
const interpreter = await get (
94
- loadPyodide ( { stderr, stdout, indexURL } ) ,
121
+ loadPyodide ( { stderr, stdout, ... options } ) ,
95
122
) ;
96
123
const py_imports = importPackages . bind ( interpreter ) ;
97
124
loader . set ( interpreter , py_imports ) ;
98
125
await loadProgress ( this , progress , interpreter , config , baseURL ) ;
99
- if ( config . packages ) await py_imports ( config . packages ) ;
126
+ // if cache wasn't know, import and freeze it for the next time
127
+ if ( packages ) await py_imports ( packages , storage , save ) ;
128
+ await storage . close ( ) ;
129
+ if ( options . lockFileURL ) URL . revokeObjectURL ( options . lockFileURL ) ;
100
130
progress ( 'Loaded Pyodide' ) ;
101
131
return interpreter ;
102
132
} ,
@@ -130,7 +160,7 @@ function transform(value) {
130
160
}
131
161
132
162
// exposed utility to import packages via polyscript.lazy_py_modules
133
- async function importPackages ( packages ) {
163
+ async function importPackages ( packages , storage , save = false ) {
134
164
// temporary patch/fix console.log which is used
135
165
// not only by Pyodide but by micropip too and there's
136
166
// no way to intercept those calls otherwise
@@ -146,6 +176,10 @@ async function importPackages(packages) {
146
176
const micropip = this . pyimport ( 'micropip' ) ;
147
177
await micropip . install ( packages , { keep_going : true } ) ;
148
178
console . log = log ;
179
+ if ( save && ( storage instanceof IDBMapSync ) ) {
180
+ const frozen = micropip . freeze ( ) ;
181
+ storage . set ( stringify ( packages ) , frozen ) ;
182
+ }
149
183
micropip . destroy ( ) ;
150
184
}
151
185
/* c8 ignore stop */
0 commit comments