@@ -3,11 +3,14 @@ import { create } from 'gc-hook';
33import { RUNNING_IN_WORKER , createProgress , writeFile } from './_utils.js' ;
44import { getFormat , loader , loadProgress , registerJSModule , run , runAsync , runEvent } from './_python.js' ;
55import { stdio } from './_io.js' ;
6- import { isArray } from '../utils.js' ;
6+ import { IDBMapSync , isArray } from '../utils.js' ;
77
88const type = 'pyodide' ;
99const toJsOptions = { dict_converter : Object . fromEntries } ;
1010
11+ const { stringify } = JSON ;
12+
13+ // REQUIRES INTEGRATION TEST
1114/* c8 ignore start */
1215let overrideFunction = false ;
1316const overrideMethod = method => ( ...args ) => {
@@ -75,10 +78,7 @@ const applyOverride = () => {
7578} ;
7679
7780const progress = createProgress ( 'py' ) ;
78- /* c8 ignore stop */
7981
80- // REQUIRES INTEGRATION TEST
81- /* c8 ignore start */
8282export default {
8383 type,
8484 module : ( version = '0.26.2' ) =>
@@ -88,15 +88,45 @@ export default {
8888 if ( ! RUNNING_IN_WORKER && config . experimental_create_proxy === 'auto' )
8989 applyOverride ( ) ;
9090 progress ( 'Loading Pyodide' ) ;
91- const { stderr, stdout, get } = stdio ( ) ;
91+ let { packages } = config ;
92+ progress ( 'Loading Storage' ) ;
9293 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 ( ) ;
93120 const interpreter = await get (
94- loadPyodide ( { stderr, stdout, indexURL } ) ,
121+ loadPyodide ( { stderr, stdout, ... options } ) ,
95122 ) ;
96123 const py_imports = importPackages . bind ( interpreter ) ;
97124 loader . set ( interpreter , py_imports ) ;
98125 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 ) ;
100130 progress ( 'Loaded Pyodide' ) ;
101131 return interpreter ;
102132 } ,
@@ -130,7 +160,7 @@ function transform(value) {
130160}
131161
132162// exposed utility to import packages via polyscript.lazy_py_modules
133- async function importPackages ( packages ) {
163+ async function importPackages ( packages , storage , save = false ) {
134164 // temporary patch/fix console.log which is used
135165 // not only by Pyodide but by micropip too and there's
136166 // no way to intercept those calls otherwise
@@ -146,6 +176,10 @@ async function importPackages(packages) {
146176 const micropip = this . pyimport ( 'micropip' ) ;
147177 await micropip . install ( packages , { keep_going : true } ) ;
148178 console . log = log ;
179+ if ( save && ( storage instanceof IDBMapSync ) ) {
180+ const frozen = micropip . freeze ( ) ;
181+ storage . set ( stringify ( packages ) , frozen ) ;
182+ }
149183 micropip . destroy ( ) ;
150184}
151185/* c8 ignore stop */
0 commit comments