From c4f8ccf6dcbcc6edf270ea0506f2ee81f7a5158a Mon Sep 17 00:00:00 2001 From: Ian Thomas Date: Thu, 20 Feb 2025 12:40:55 +0000 Subject: [PATCH] Add vanilla_vite typescript recipe --- ci/prepare_playwright.sh | 1 + ci/single_example.sh | 15 +++- ci/typescript/create_vanilla_rspack.sh | 2 +- ci/typescript/create_vanilla_vite.sh | 77 ++++++++++++++++ ci/typescript/create_vanilla_webpack.sh | 2 +- recipes/src/recipes/typescript/common.ts | 13 +-- recipes/src/recipes/typescript/index.ts | 1 + .../typescript/vanilla_rspack_recipe.ts | 4 +- .../recipes/typescript/vanilla_vite_recipe.ts | 65 ++++++++++++++ .../typescript/vanilla_webpack_recipe.ts | 4 +- recipes/src/step.ts | 70 ++++++++++++--- typescript/vanilla_vite/README.md | 88 +++++++++++++++++++ 12 files changed, 320 insertions(+), 22 deletions(-) create mode 100755 ci/typescript/create_vanilla_vite.sh create mode 100644 recipes/src/recipes/typescript/vanilla_vite_recipe.ts create mode 100644 typescript/vanilla_vite/README.md diff --git a/ci/prepare_playwright.sh b/ci/prepare_playwright.sh index ebe8978..36b33ca 100755 --- a/ci/prepare_playwright.sh +++ b/ci/prepare_playwright.sh @@ -5,4 +5,5 @@ set -eux ./single_example.sh typescript vanilla_rspack +./single_example.sh typescript vanilla_vite ./single_example.sh typescript vanilla_webpack diff --git a/ci/single_example.sh b/ci/single_example.sh index 06db8f9..0a6da3d 100755 --- a/ci/single_example.sh +++ b/ci/single_example.sh @@ -12,6 +12,15 @@ fi export TYPE=$1 export EXAMPLE=$2 +if [ $EXAMPLE == "vanilla_vite" ]; then + export PORT=5173 + export SERVE_CMD="npm run dev" +else + export PORT=4500 + export SERVE_CMD="npm run serve" +fi + + function merge-json() { # merge the second json file into the first. TEMP_FILE=$(mktemp) @@ -44,7 +53,7 @@ export default defineConfig({ workers: process.env.CI ? 1 : undefined, reporter: 'html', use: { - baseURL: 'http://localhost:4500', + baseURL: 'http://localhost:$PORT', trace: 'on-first-retry', }, projects: [ @@ -55,7 +64,7 @@ export default defineConfig({ ], webServer: { command: 'npm run serve', - url: 'http://localhost:4500', + url: 'http://localhost:$PORT', reuseExistingServer: !process.env.CI } }); @@ -65,7 +74,7 @@ EOF cat > temp.json << EOF { "scripts": { - "serve": "npm explore $EXAMPLE -- npm run serve", + "serve": "npm explore $EXAMPLE -- $SERVE_CMD", "test": "playwright test", "test:ui": "playwright test --ui" } diff --git a/ci/typescript/create_vanilla_rspack.sh b/ci/typescript/create_vanilla_rspack.sh index b96f206..598000f 100755 --- a/ci/typescript/create_vanilla_rspack.sh +++ b/ci/typescript/create_vanilla_rspack.sh @@ -96,7 +96,7 @@ rm temp.json # 8. Build and run basic example without any BokehJS # npm install # npm run build -# npm run serve +# # npm run serve # In a web browser navigate to http://localhost:4500/ # 9. Add BokehJS dependency to this project. This assumes the package has been built and copied to the root directory of this repository as outlined in the top-level README.md. diff --git a/ci/typescript/create_vanilla_vite.sh b/ci/typescript/create_vanilla_vite.sh new file mode 100755 index 0000000..d4bc436 --- /dev/null +++ b/ci/typescript/create_vanilla_vite.sh @@ -0,0 +1,77 @@ +#!/usr/bin/env bash + +set -eux + +export OUTPUT_DIRECTORY=../temp/typescript/vanilla_vite + +mkdir -p $OUTPUT_DIRECTORY +cd $OUTPUT_DIRECTORY +rm -rf * + +function merge-json() { + # merge the second json file into the first. + TEMP_FILE=$(mktemp) + jq '. * input' $1 $2 > TEMP_FILE && mv TEMP_FILE $1 +} + +# 1. Create base vite project +npm create vite@latest . -- --template vanilla-ts --yes + +# 2. Build and run initial basic project +# npm install +# # npm run dev +# In a web browser navigate to http://localhost:5173/ + +# 3. Simplify by removing some unwanted files +rm public/vite.svg src/counter.ts src/style.css src/typescript.svg + +# 4. Replace src/main.ts with a simple hello example +cat > src/main.ts << EOF +document.querySelector('#app')!.innerHTML = \`
Hello
\` +EOF + +# 5. Build and run the minimal example +# # npm run dev +# In a web browser navigate to http://localhost:5173/ + +# 6. Add BokehJS dependency to this project. This assumes the package has been built and copied to the root directory of this repository as outlined in the top-level README.md. +npm install ../../../../bokeh-bokehjs-3.7.0-dev.5.tgz + +# 7. Replace src/main.ts with a simple hello example +cat > src/main.ts << EOF +import * as Bokeh from "@bokeh/bokehjs"; + +console.info("BokehJS version:", Bokeh.version); + +function create_bokehjs_plot(target_id: string) { + const source = new Bokeh.ColumnDataSource({data: { x: [0.1, 0.9], y: [0.1, 0.9], size: [40, 10] }}); + + const plot = Bokeh.Plotting.figure({ + title: "Example BokehJS plot", height: 500, width: 500, + x_range: [0, 1], y_range: [0, 1], sizing_mode: "stretch_width", + }); + + plot.scatter({ field: "x" }, { field: "y" }, {source, size: { field: "size" }}); + + const button = new Bokeh.Widgets.Button({label: "Click me to add a point", button_type: "primary"}); + function button_callback() { + const data = source.data as any; + data.x.push(Math.random()); + data.y.push(Math.random()); + data.size.push(10 + Math.random()*30); + source.change.emit(); + } + button.on_click(button_callback); + + const column = new Bokeh.Column({children: [plot, button], sizing_mode: "stretch_width"}); + Bokeh.Plotting.show(column, target_id); +} + +document.querySelector('#app')!.innerHTML = \`
Hello
\`; + +create_bokehjs_plot("#target"); +EOF + +# 8. Rebuild and serve +# npm run dev +# In a web browser navigate to http://localhost:5173/ diff --git a/ci/typescript/create_vanilla_webpack.sh b/ci/typescript/create_vanilla_webpack.sh index de00768..109495b 100755 --- a/ci/typescript/create_vanilla_webpack.sh +++ b/ci/typescript/create_vanilla_webpack.sh @@ -97,7 +97,7 @@ rm temp.json # 8. Build and run basic example without any BokehJS # npm install # npm run build -# npm run serve +# # npm run serve # In a web browser navigate to http://localhost:4500/ # 9. Add BokehJS dependency to this project. This assumes the package has been built and copied to the root directory of this repository as outlined in the top-level README.md. diff --git a/recipes/src/recipes/typescript/common.ts b/recipes/src/recipes/typescript/common.ts index 4d9c087..f9bf3a8 100644 --- a/recipes/src/recipes/typescript/common.ts +++ b/recipes/src/recipes/typescript/common.ts @@ -11,10 +11,10 @@ export const baseTSConfig = "include": ["src"] }`; -export const baseTypeScriptExample = -`import * as Bokeh from "@bokeh/bokehjs"; - -console.info("BokehJS version:", Bokeh.version); +export const baseTypeScriptExample = { + import: 'import * as Bokeh from "@bokeh/bokehjs";\n', + function: +`console.info("BokehJS version:", Bokeh.version); function create_bokehjs_plot(target_id: string) { const source = new Bokeh.ColumnDataSource({data: { x: [0.1, 0.9], y: [0.1, 0.9], size: [40, 10] }}); @@ -38,4 +38,7 @@ function create_bokehjs_plot(target_id: string) { const column = new Bokeh.Column({children: [plot, button], sizing_mode: "stretch_width"}); Bokeh.Plotting.show(column, target_id); -}`; +} +`, + create: 'create_bokehjs_plot("#target");' +}; diff --git a/recipes/src/recipes/typescript/index.ts b/recipes/src/recipes/typescript/index.ts index a1a2bb9..84b62aa 100644 --- a/recipes/src/recipes/typescript/index.ts +++ b/recipes/src/recipes/typescript/index.ts @@ -1,2 +1,3 @@ export * from './vanilla_rspack_recipe'; +export * from './vanilla_vite_recipe'; export * from './vanilla_webpack_recipe'; diff --git a/recipes/src/recipes/typescript/vanilla_rspack_recipe.ts b/recipes/src/recipes/typescript/vanilla_rspack_recipe.ts index 88f04bb..feb6a87 100644 --- a/recipes/src/recipes/typescript/vanilla_rspack_recipe.ts +++ b/recipes/src/recipes/typescript/vanilla_rspack_recipe.ts @@ -102,7 +102,9 @@ export default config;`) this.add(new CreateFileStep( 'Replace contents of `src/index.ts` with code to create BokehJS plot', 'src/index.ts', - baseTypeScriptExample + '\n\ncreate_bokehjs_plot("#target");' + baseTypeScriptExample.import + "\n" + + baseTypeScriptExample.function + "\n" + + baseTypeScriptExample.create )); this.add(new CommandStep( diff --git a/recipes/src/recipes/typescript/vanilla_vite_recipe.ts b/recipes/src/recipes/typescript/vanilla_vite_recipe.ts new file mode 100644 index 0000000..0bc638a --- /dev/null +++ b/recipes/src/recipes/typescript/vanilla_vite_recipe.ts @@ -0,0 +1,65 @@ +import { Recipe } from '../../recipe'; +import { CommandStep, RemoveFilesStep, ReplaceFileStep } from '../../step'; +import { baseTypeScriptExample } from './common'; + +export class VanillaViteRecipe extends Recipe { + constructor() { + super( + 'typescript', + 'vanilla', + 'vite', + 'Create an initial basic project using `create-vite`.' + ); + + this.add(new CommandStep( + 'Create base `vite` project', + ['npm create vite@latest . -- --template vanilla-ts --yes'] + )); + + this.add(new CommandStep( + 'Build and run initial basic project', + ['npm install', 'npm run dev'], + 'In a web browser navigate to http://localhost:5173/', + true + )); + + this.add(new RemoveFilesStep( + 'Simplify by removing some unwanted files', + ['public/vite.svg', 'src/counter.ts', 'src/style.css', 'src/typescript.svg'] + )); + + this.add(new ReplaceFileStep( + 'Replace `src/main.ts` with a simple hello example', + 'src/main.ts', + "document.querySelector('#app')!.innerHTML = \\`
Hello
\\`") + ); + + this.add(new CommandStep( + 'Build and run the minimal example', + ['npm run dev'], + 'In a web browser navigate to http://localhost:5173/', + true + )); + + this.add(new CommandStep( + 'Add BokehJS dependency to this project. This assumes the package has been built and ' + + 'copied to the root directory of this repository as outlined in the top-level `README.md`.', + ['npm install ../../../../bokeh-bokehjs-3.7.0-dev.5.tgz'] + )); + + this.add(new ReplaceFileStep( + 'Replace `src/main.ts` with a simple hello example', + 'src/main.ts', + baseTypeScriptExample.import + "\n" + + baseTypeScriptExample.function + "\n" + + "document.querySelector('#app')!.innerHTML = \\`
Hello
\\`;\n\n" + + 'create_bokehjs_plot("#target");' + )); + + this.add(new CommandStep( + 'Rebuild and serve', + ['npm run dev'], + 'In a web browser navigate to http://localhost:5173/' + )); + } +} diff --git a/recipes/src/recipes/typescript/vanilla_webpack_recipe.ts b/recipes/src/recipes/typescript/vanilla_webpack_recipe.ts index 9faeec8..75fd180 100644 --- a/recipes/src/recipes/typescript/vanilla_webpack_recipe.ts +++ b/recipes/src/recipes/typescript/vanilla_webpack_recipe.ts @@ -101,7 +101,9 @@ export default config;`) this.add(new CreateFileStep( 'Replace contents of `src/index.ts` with code to create BokehJS plot', 'src/index.ts', - baseTypeScriptExample + '\n\ncreate_bokehjs_plot("#target");' + baseTypeScriptExample.import + "\n" + + baseTypeScriptExample.function + "\n" + + baseTypeScriptExample.create )); this.add(new CommandStep( diff --git a/recipes/src/step.ts b/recipes/src/step.ts index de2fa69..9f1d8c1 100644 --- a/recipes/src/step.ts +++ b/recipes/src/step.ts @@ -42,7 +42,10 @@ export class CommandStep extends Step { const allPrefix = this.ignoreIfBash ? '# ' : ''; for (const command of this.commands) { - const prefix = command === 'npm run serve' ? '# ' : allPrefix; + let prefix = allPrefix; + if (command === 'npm run serve' || command === 'npm run dev') { + prefix = '# ' + prefix; + } fs.writeSync(fd, prefix + command + '\n'); } if (this.postscript) { @@ -66,20 +69,24 @@ export class CommandStep extends Step { } } -/** - * Step to create a file. - */ -export class CreateFileStep extends Step { - constructor(readonly description: string, readonly filename: string, readonly contents: string) { +abstract class CreateOrReplaceFileStep extends Step { + constructor( + readonly description: string, + readonly filename: string, + readonly contents: string, + readonly alreadyExists: boolean + ) { super(description); } writeToBash(fd: number, index: number): void { this.writeDescriptionBash(fd, index); - const dirname = path.dirname(this.filename); - if (dirname !== '.') { - fs.writeSync(fd, `mkdir -p ${dirname}\n`); + if (!this.alreadyExists) { + const dirname = path.dirname(this.filename); + if (dirname !== '.') { + fs.writeSync(fd, `mkdir -p ${dirname}\n`); + } } fs.writeSync(fd, `cat > ${this.filename} << EOF\n`); @@ -96,8 +103,9 @@ export class CreateFileStep extends Step { const { spacer } = this; const language = languageFromExtension(this.filename); fs.writeSync(fd, '\n' + spacer + '```' + language + '\n'); - for (const line of this.contents.split('\n')) { + for (let line of this.contents.split('\n')) { if (line) { + line = line.replaceAll('\\`', '`'); fs.writeSync(fd, spacer + line + '\n'); } else { fs.writeSync(fd, '\n'); @@ -107,6 +115,15 @@ export class CreateFileStep extends Step { } } +/** + * Step to create a file. + */ +export class CreateFileStep extends CreateOrReplaceFileStep { + constructor(description: string, filename: string, contents: string) { + super(description, filename, contents, false); + } +} + /** * Step to create a file. */ @@ -145,3 +162,36 @@ export class MergeJsonStep extends Step { fs.writeSync(fd, spacer + '```\n'); } } + +/** + * Step to remove files. + */ +export class RemoveFilesStep extends Step { + constructor(readonly description: string, readonly filenames: string[]) { + super(description); + } + + writeToBash(fd: number, index: number): void { + this.writeDescriptionBash(fd, index); + + fs.writeSync(fd, 'rm ' + this.filenames.join(' ') + '\n'); + } + + writeToReadme(fd: number, index: number): void { + this.writeDescriptionReadme(fd, index); + + const { spacer } = this; + fs.writeSync(fd, '\n' + spacer + '```bash\n'); + fs.writeSync(fd, spacer + 'rm ' + this.filenames.join(' ') + '\n'); + fs.writeSync(fd, spacer + '```\n'); + } +} + +/** + * Step to replace the contents of an existing file. + */ +export class ReplaceFileStep extends CreateOrReplaceFileStep { + constructor(description: string, filename: string, contents: string) { + super(description, filename, contents, true); + } +} diff --git a/typescript/vanilla_vite/README.md b/typescript/vanilla_vite/README.md new file mode 100644 index 0000000..d953c4f --- /dev/null +++ b/typescript/vanilla_vite/README.md @@ -0,0 +1,88 @@ +# Vanilla (no framework) vite typescript example + +Create an initial basic project using `create-vite`. + +1. Create base `vite` project + + ```bash + npm create vite@latest . -- --template vanilla-ts --yes + ``` + +2. Build and run initial basic project + + ```bash + npm install + npm run dev + ``` + + In a web browser navigate to http://localhost:5173/ + +3. Simplify by removing some unwanted files + + ```bash + rm public/vite.svg src/counter.ts src/style.css src/typescript.svg + ``` + +4. Replace `src/main.ts` with a simple hello example containing + + ```typescript + document.querySelector('#app')!.innerHTML = `
Hello
` + ``` + +5. Build and run the minimal example + + ```bash + npm run dev + ``` + + In a web browser navigate to http://localhost:5173/ + +6. Add BokehJS dependency to this project. This assumes the package has been built and copied to the root directory of this repository as outlined in the top-level `README.md`. + + ```bash + npm install ../../../../bokeh-bokehjs-3.7.0-dev.5.tgz + ``` + +7. Replace `src/main.ts` with a simple hello example containing + + ```typescript + import * as Bokeh from "@bokeh/bokehjs"; + + console.info("BokehJS version:", Bokeh.version); + + function create_bokehjs_plot(target_id: string) { + const source = new Bokeh.ColumnDataSource({data: { x: [0.1, 0.9], y: [0.1, 0.9], size: [40, 10] }}); + + const plot = Bokeh.Plotting.figure({ + title: "Example BokehJS plot", height: 500, width: 500, + x_range: [0, 1], y_range: [0, 1], sizing_mode: "stretch_width", + }); + + plot.scatter({ field: "x" }, { field: "y" }, {source, size: { field: "size" }}); + + const button = new Bokeh.Widgets.Button({label: "Click me to add a point", button_type: "primary"}); + function button_callback() { + const data = source.data as any; + data.x.push(Math.random()); + data.y.push(Math.random()); + data.size.push(10 + Math.random()*30); + source.change.emit(); + } + button.on_click(button_callback); + + const column = new Bokeh.Column({children: [plot, button], sizing_mode: "stretch_width"}); + Bokeh.Plotting.show(column, target_id); + } + + document.querySelector('#app')!.innerHTML = `
Hello
`; + + create_bokehjs_plot("#target"); + ``` + +8. Rebuild and serve + + ```bash + npm run dev + ``` + + In a web browser navigate to http://localhost:5173/