mirror of
https://github.com/pyscript/pyscript.git
synced 2025-12-19 18:27:29 -05:00
Followup to pyscript#1232. Closes pyscript#1226. Use node to make a manifest of the src/python dir and then use an esbuild plugin to resolve an import called `pyscript_python_package.esbuild_injected.json` to an object indicating the directories and files in the package folder. This object is then used to govern runtime installation of the package.
157 lines
5.1 KiB
JavaScript
157 lines
5.1 KiB
JavaScript
const { build } = require('esbuild');
|
|
const { spawn } = require('child_process');
|
|
const { join } = require('path');
|
|
const { watchFile } = require('fs');
|
|
const { cp, lstat, readdir, opendir, readFile } = require('fs/promises');
|
|
|
|
const production = !process.env.NODE_WATCH || process.env.NODE_ENV === 'production';
|
|
|
|
const copy_targets = [
|
|
{ src: 'public/index.html', dest: 'build' },
|
|
{ src: 'src/plugins/python/*', dest: 'build/plugins/python' },
|
|
];
|
|
|
|
if (!production) {
|
|
copy_targets.push({ src: 'build/*', dest: 'examples/build' });
|
|
}
|
|
|
|
/**
|
|
* List out everything in a directory, but skip __pycache__ directory. Used to
|
|
* list out the directory paths and the [file path, file contents] pairs in the
|
|
* Python package. All paths are relative to the directory we are listing. The
|
|
* directories are sorted topologically so that a parent directory always
|
|
* appears before its children.
|
|
*
|
|
* This is consumed in main.ts which calls mkdir for each directory and then
|
|
* writeFile to create each file.
|
|
*
|
|
* @param {string} dir The path to the directory we want to list out
|
|
* @returns {dirs: string[], files: [string, string][]}
|
|
*/
|
|
async function directoryManifest(dir) {
|
|
const result = { dirs: [], files: [] };
|
|
await _directoryManifestHelper(dir, '.', result);
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Recursive helper function for directoryManifest
|
|
*/
|
|
async function _directoryManifestHelper(root, dir, result) {
|
|
const dirObj = await opendir(join(root, dir));
|
|
for await (const d of dirObj) {
|
|
const entry = join(dir, d.name);
|
|
if (d.isDirectory()) {
|
|
if (d.name === '__pycache__') {
|
|
continue;
|
|
}
|
|
result.dirs.push(entry);
|
|
await _directoryManifestHelper(root, entry, result);
|
|
} else if (d.isFile()) {
|
|
result.files.push([entry, await readFile(join(root, entry), { encoding: 'utf-8' })]);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* An esbuild plugin that injects the Pyscript Python package.
|
|
*
|
|
* It uses onResolve to attach our custom namespace to the import and then uses
|
|
* onLoad to inject the file contents.
|
|
*/
|
|
function bundlePyscriptPythonPlugin() {
|
|
const namespace = 'bundlePyscriptPythonPlugin';
|
|
return {
|
|
name: namespace,
|
|
setup(build) {
|
|
// Resolve the pyscript_package to our custom namespace
|
|
// The path doesn't really matter, but we need a separate namespace
|
|
// or else the file system resolver will raise an error.
|
|
build.onResolve({ filter: /^pyscript_python_package.esbuild_injected.json$/ }, args => {
|
|
return { path: 'dummy', namespace };
|
|
});
|
|
// Inject our manifest as JSON contents, and use the JSON loader.
|
|
// Also tell esbuild to watch the files & directories we've listed
|
|
// for updates.
|
|
build.onLoad({ filter: /^dummy$/, namespace }, async args => {
|
|
const manifest = await directoryManifest('./src/python');
|
|
return {
|
|
contents: JSON.stringify(manifest),
|
|
loader: 'json',
|
|
watchFiles: manifest.files.map(([k, v]) => k),
|
|
watchDirs: manifest.dirs,
|
|
};
|
|
});
|
|
},
|
|
};
|
|
}
|
|
|
|
const pyScriptConfig = {
|
|
entryPoints: ['src/main.ts'],
|
|
loader: { '.py': 'text' },
|
|
bundle: true,
|
|
format: 'iife',
|
|
globalName: 'pyscript',
|
|
plugins: [bundlePyscriptPythonPlugin()],
|
|
};
|
|
|
|
const copyPath = (source, dest, ...rest) => cp(join(__dirname, source), join(__dirname, dest), ...rest);
|
|
|
|
const esbuild = async () => {
|
|
const timer = `\x1b[1mpyscript\x1b[0m \x1b[2m(${production ? 'prod' : 'dev'})\x1b[0m built in`;
|
|
console.time(timer);
|
|
|
|
await Promise.all([
|
|
build({
|
|
...pyScriptConfig,
|
|
sourcemap: false,
|
|
minify: false,
|
|
outfile: 'build/pyscript.js',
|
|
}),
|
|
build({
|
|
...pyScriptConfig,
|
|
sourcemap: true,
|
|
minify: true,
|
|
outfile: 'build/pyscript.min.js',
|
|
}),
|
|
]);
|
|
|
|
const copy = [];
|
|
for (const { src, dest } of copy_targets) {
|
|
if (src.endsWith('/*')) {
|
|
copy.push(copyPath(src.slice(0, -2), dest, { recursive: true }));
|
|
} else {
|
|
copy.push(copyPath(src, dest + src.slice(src.lastIndexOf('/'))));
|
|
}
|
|
}
|
|
await Promise.all(copy);
|
|
|
|
console.timeEnd(timer);
|
|
};
|
|
|
|
esbuild().then(() => {
|
|
if (!production) {
|
|
(async function watchPath(path) {
|
|
for (const file of await readdir(path)) {
|
|
const whole = join(path, file);
|
|
if (/\.(js|ts|css|py)$/.test(file)) {
|
|
watchFile(whole, async () => {
|
|
await esbuild();
|
|
});
|
|
} else if ((await lstat(whole)).isDirectory()) {
|
|
watchPath(whole);
|
|
}
|
|
}
|
|
})('src');
|
|
|
|
const server = spawn('python', ['-m', 'http.server', '--directory', './examples', '8080'], {
|
|
stdio: 'inherit',
|
|
detached: false,
|
|
});
|
|
|
|
process.on('exit', () => {
|
|
server.kill();
|
|
});
|
|
}
|
|
});
|