@@ -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,43 @@ 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 are uniquely stored as JSON key 
104+             const  key  =  stringify ( packages ) ; 
105+             if  ( storage . has ( key ) )  { 
106+                 const  blob  =  new  Blob ( 
107+                     [ storage . get ( key ) ] , 
108+                     {  type : 'application/json'  } , 
109+                 ) ; 
110+                 // this should be used to bootstrap loadPyodide 
111+                 options . lockFileURL  =  URL . createObjectURL ( blob ) ; 
112+                 // no need to use micropip manually here 
113+                 options . packages  =  packages ; 
114+                 packages  =  null ; 
115+             } 
116+         } 
117+         progress ( 'Loaded Storage' ) ; 
118+         const  {  stderr,  stdout,  get }  =  stdio ( ) ; 
93119        const  interpreter  =  await  get ( 
94-             loadPyodide ( {  stderr,  stdout,  indexURL  } ) , 
120+             loadPyodide ( {  stderr,  stdout,  ... options  } ) , 
95121        ) ; 
96122        const  py_imports  =  importPackages . bind ( interpreter ) ; 
97123        loader . set ( interpreter ,  py_imports ) ; 
98124        await  loadProgress ( this ,  progress ,  interpreter ,  config ,  baseURL ) ; 
99-         if  ( config . packages )  await  py_imports ( config . packages ) ; 
125+         // if cache wasn't know, import and freeze it for the next time 
126+         if  ( packages )  await  py_imports ( packages ,  storage ,  save ) ; 
127+         await  storage . close ( ) ; 
100128        progress ( 'Loaded Pyodide' ) ; 
101129        return  interpreter ; 
102130    } , 
@@ -130,7 +158,7 @@ function transform(value) {
130158} 
131159
132160// exposed utility to import packages via polyscript.lazy_py_modules 
133- async  function  importPackages ( packages )  { 
161+ async  function  importPackages ( packages ,   storage ,   save   =   false )  { 
134162    // temporary patch/fix console.log which is used 
135163    // not only by Pyodide but by micropip too and there's 
136164    // no way to intercept those calls otherwise 
@@ -146,6 +174,10 @@ async function importPackages(packages) {
146174    const  micropip  =  this . pyimport ( 'micropip' ) ; 
147175    await  micropip . install ( packages ,  {  keep_going : true  } ) ; 
148176    console . log  =  log ; 
177+     if  ( save  &&  ( storage  instanceof  IDBMapSync ) )  { 
178+         const  frozen  =  micropip . freeze ( ) ; 
179+         storage . set ( stringify ( packages ) ,  frozen ) ; 
180+     } 
149181    micropip . destroy ( ) ; 
150182} 
151183/* c8 ignore stop */ 
0 commit comments