@@ -2,7 +2,7 @@ const { build } = require('esbuild');
2
2
const { spawn } = require ( 'child_process' ) ;
3
3
const { join } = require ( 'path' ) ;
4
4
const { watchFile } = require ( 'fs' ) ;
5
- const { cp, lstat, readdir } = require ( 'fs/promises' ) ;
5
+ const { cp, lstat, readdir, opendir , readFile } = require ( 'fs/promises' ) ;
6
6
7
7
const production = ! process . env . NODE_WATCH || process . env . NODE_ENV === 'production' ;
8
8
@@ -15,12 +15,84 @@ if (!production) {
15
15
copy_targets . push ( { src : 'build/*' , dest : 'examples/build' } ) ;
16
16
}
17
17
18
+ /**
19
+ * List out everything in a directory, but skip __pycache__ directory. Used to
20
+ * list out the directory paths and the [file path, file contents] pairs in the
21
+ * Python package. All paths are relative to the directory we are listing. The
22
+ * directories are sorted topologically so that a parent directory always
23
+ * appears before its children.
24
+ *
25
+ * This is consumed in main.ts which calls mkdir for each directory and then
26
+ * writeFile to create each file.
27
+ *
28
+ * @param {string } dir The path to the directory we want to list out
29
+ * @returns {dirs: string[], files: [string, string][] }
30
+ */
31
+ async function directoryManifest ( dir ) {
32
+ const result = { dirs : [ ] , files : [ ] } ;
33
+ await _directoryManifestHelper ( dir , '.' , result ) ;
34
+ return result ;
35
+ }
36
+
37
+ /**
38
+ * Recursive helper function for directoryManifest
39
+ */
40
+ async function _directoryManifestHelper ( root , dir , result ) {
41
+ const dirObj = await opendir ( join ( root , dir ) ) ;
42
+ for await ( const d of dirObj ) {
43
+ const entry = join ( dir , d . name ) ;
44
+ if ( d . isDirectory ( ) ) {
45
+ if ( d . name === '__pycache__' ) {
46
+ continue ;
47
+ }
48
+ result . dirs . push ( entry ) ;
49
+ await _directoryManifestHelper ( root , entry , result ) ;
50
+ } else if ( d . isFile ( ) ) {
51
+ result . files . push ( [ entry , await readFile ( join ( root , entry ) , { encoding : 'utf-8' } ) ] ) ;
52
+ }
53
+ }
54
+ }
55
+
56
+ /**
57
+ * An esbuild plugin that injects the Pyscript Python package.
58
+ *
59
+ * It uses onResolve to attach our custom namespace to the import and then uses
60
+ * onLoad to inject the file contents.
61
+ */
62
+ function bundlePyscriptPythonPlugin ( ) {
63
+ const namespace = 'bundlePyscriptPythonPlugin' ;
64
+ return {
65
+ name : namespace ,
66
+ setup ( build ) {
67
+ // Resolve the pyscript_package to our custom namespace
68
+ // The path doesn't really matter, but we need a separate namespace
69
+ // or else the file system resolver will raise an error.
70
+ build . onResolve ( { filter : / ^ p y s c r i p t _ p y t h o n _ p a c k a g e .e s b u i l d _ i n j e c t e d .j s o n $ / } , args => {
71
+ return { path : 'dummy' , namespace } ;
72
+ } ) ;
73
+ // Inject our manifest as JSON contents, and use the JSON loader.
74
+ // Also tell esbuild to watch the files & directories we've listed
75
+ // for updates.
76
+ build . onLoad ( { filter : / ^ d u m m y $ / , namespace } , async args => {
77
+ const manifest = await directoryManifest ( './src/python' ) ;
78
+ return {
79
+ contents : JSON . stringify ( manifest ) ,
80
+ loader : 'json' ,
81
+ watchFiles : manifest . files . map ( ( [ k , v ] ) => k ) ,
82
+ watchDirs : manifest . dirs ,
83
+ } ;
84
+ } ) ;
85
+ } ,
86
+ } ;
87
+ }
88
+
18
89
const pyScriptConfig = {
19
90
entryPoints : [ 'src/main.ts' ] ,
20
91
loader : { '.py' : 'text' } ,
21
92
bundle : true ,
22
93
format : 'iife' ,
23
94
globalName : 'pyscript' ,
95
+ plugins : [ bundlePyscriptPythonPlugin ( ) ] ,
24
96
} ;
25
97
341A
26
98
const copyPath = ( source , dest , ...rest ) => cp ( join ( __dirname , source ) , join ( __dirname , dest ) , ...rest ) ;
0 commit comments