From 77583ee9f0ec8332f0282d0a950264c414f196f2 Mon Sep 17 00:00:00 2001 From: Liam Mitchell Date: Mon, 30 Dec 2024 16:26:27 +0100 Subject: [PATCH 1/4] Load templates via plugin --- .github/workflows/ci.yml | 1 - assets/.eslintrc.js | 3 - assets/build/build.js | 81 ++++++------------- assets/build/load-handlebars.js | 27 +++++++ assets/js/autocomplete/autocomplete-list.js | 3 +- assets/js/handlebars/helpers.js | 2 + assets/js/modal.js | 3 +- assets/js/quick-switch.js | 6 +- assets/js/search-page.js | 3 +- assets/js/settings.js | 3 +- assets/js/sidebar/sidebar-list.js | 3 +- assets/js/sidebar/sidebar-version-select.js | 3 +- assets/js/tabsets.js | 4 +- assets/js/tooltips/tooltips.js | 6 +- .../html/templates/head_template.eex | 2 - mix.exs | 2 +- 16 files changed, 79 insertions(+), 73 deletions(-) create mode 100644 assets/build/load-handlebars.js diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 831406539..41fe83806 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -94,7 +94,6 @@ jobs: with: path: ~/.npm key: ${{ runner.os }}-node-${{ hashFiles('asssets/package-lock.json') }} - - run: mkdir -p tmp/handlebars - run: npm ci --prefix assets - run: npm run build --prefix assets - name: Push updated assets diff --git a/assets/.eslintrc.js b/assets/.eslintrc.js index 7436d4593..b36d1cfd2 100644 --- a/assets/.eslintrc.js +++ b/assets/.eslintrc.js @@ -16,8 +16,5 @@ module.exports = { 'no-throw-literal': 0, 'no-useless-escape': 0, 'object-curly-spacing': 0 - }, - globals: { - 'Handlebars': 'readonly' } } diff --git a/assets/build/build.js b/assets/build/build.js index b490cd034..1b81ef1e4 100644 --- a/assets/build/build.js +++ b/assets/build/build.js @@ -1,12 +1,12 @@ const path = require('node:path') const process = require('node:process') -const child_process = require('node:child_process') +const cp = require('node:child_process') const esbuild = require('esbuild') const util = require('./utilities') +const loadHandlebars = require('./load-handlebars') const watchMode = Boolean(process.env.npm_config_watch) - /** * Configuration variables */ @@ -16,20 +16,11 @@ const commonOptions = { entryNames: '[name]-[hash]', bundle: true, minify: true, - logLevel: watchMode ? 'warning' : 'info', + logLevel: watchMode ? 'warning' : 'info' } const epubOutDir = path.resolve('../formatters/epub/dist') const htmlOutDir = path.resolve('../formatters/html/dist') -// Handlebars template paths -const templates = { - sourceDir: path.resolve('js/handlebars/templates'), - compiledDir: path.resolve('../tmp/handlebars'), - filename: 'handlebars.templates.js', -} -templates.compiledPath = path.join(templates.compiledDir, templates.filename) - - /** * Build: Plugins */ @@ -37,14 +28,13 @@ templates.compiledPath = path.join(templates.compiledDir, templates.filename) // Empty outdir directories before both normal and watch-mode builds const epubOnStartPlugin = { name: 'epubOnStart', - setup(build) { build.onStart(() => util.ensureEmptyDirsExistSync([epubOutDir])) }, + setup (build) { build.onStart(() => util.ensureEmptyDirsExistSync([epubOutDir])) } } const htmlOnStartPlugin = { name: 'htmlOnStart', - setup(build) { build.onStart(() => util.ensureEmptyDirsExistSync([htmlOutDir])) }, + setup (build) { build.onStart(() => util.ensureEmptyDirsExistSync([htmlOutDir])) } } - /** * Build */ @@ -53,12 +43,12 @@ const htmlOnStartPlugin = { const epubBuildOptions = { ...commonOptions, outdir: epubOutDir, - plugins: [epubOnStartPlugin], + plugins: [epubOnStartPlugin, loadHandlebars], entryPoints: [ 'js/entry/epub.js', 'css/entry/epub-elixir.css', - 'css/entry/epub-erlang.css', - ], + 'css/entry/epub-erlang.css' + ] } // ePub: esbuild (conditionally configuring watch mode and rebuilding of docs) @@ -68,63 +58,57 @@ if (!watchMode) { esbuild.build({ ...epubBuildOptions, watch: { - onRebuild(error, result) { + onRebuild (error, result) { if (error) { console.error('[watch] epub build failed:', error) } else { console.log('[watch] epub assets rebuilt') if (result.errors.length > 0) console.log('[watch] epub build errors:', result.errors) if (result.warnings.length > 0) console.log('[watch] epub build warnings:', result.warnings) - generateDocs("epub") + generateDocs('epub') } - }, - }, - }).then(() => generateDocs("epub")).catch(() => process.exit(1)) + } + } + }).then(() => generateDocs('epub')).catch(() => process.exit(1)) } -// HTML: Precompile Handlebars templates -util.runShellCmdSync(`npx handlebars ${templates.sourceDir} --output ${templates.compiledPath}`) - // HTML: esbuild options const htmlBuildOptions = { ...commonOptions, outdir: htmlOutDir, - plugins: [htmlOnStartPlugin], + plugins: [htmlOnStartPlugin, loadHandlebars], entryPoints: [ - templates.compiledPath, 'js/entry/html.js', 'css/entry/html-elixir.css', - 'css/entry/html-erlang.css', + 'css/entry/html-erlang.css' ], loader: { '.woff2': 'file', // TODO: Remove when @fontsource/* removes legacy .woff - '.woff': 'file', - }, + '.woff': 'file' + } } // HTML: esbuild (conditionally configuring watch mode and rebuilding of docs) if (!watchMode) { - esbuild.build(htmlBuildOptions).then(() => buildTemplatesRuntime()).catch(() => process.exit(1)) + esbuild.build(htmlBuildOptions).catch(() => process.exit(1)) } else { esbuild.build({ ...htmlBuildOptions, watch: { - onRebuild(error, result) { + onRebuild (error, result) { if (error) { console.error('[watch] html build failed:', error) } else { console.log('[watch] html assets rebuilt') if (result.errors.length > 0) console.log('[watch] html build errors:', result.errors) if (result.warnings.length > 0) console.log('[watch] html build warnings:', result.warnings) - buildTemplatesRuntime() - generateDocs("html") + generateDocs('html') } - }, - }, + } + } }).then(() => { - buildTemplatesRuntime() - generateDocs("html") + generateDocs('html') }).catch(() => process.exit(1)) } @@ -132,24 +116,11 @@ if (!watchMode) { * Functions */ -// HTML: Handlebars runtime -// The Handlebars runtime from the local module dist directory is used to ensure -// the version matches that which was used to compile the templates. -// 'bundle' must be false in order for 'Handlebar' to be available at runtime. -function buildTemplatesRuntime() { - esbuild.build({ - ...commonOptions, - outdir: htmlOutDir, - entryPoints: ['node_modules/handlebars/dist/handlebars.runtime.js'], - bundle: false, - }).catch(() => process.exit(1)) -} - // Docs generation (used in watch mode only) -function generateDocs(formatter) { +function generateDocs (formatter) { console.log(`Building ${formatter} docs`) process.chdir('../') - child_process.execSync('mix compile --force') - child_process.execSync(`mix docs --formatter ${formatter}`) + cp.execSync('mix compile --force') + cp.execSync(`mix docs --formatter ${formatter}`) process.chdir('./assets/') } diff --git a/assets/build/load-handlebars.js b/assets/build/load-handlebars.js new file mode 100644 index 000000000..802f25a32 --- /dev/null +++ b/assets/build/load-handlebars.js @@ -0,0 +1,27 @@ +const fs = require('node:fs/promises') +const handlebars = require('handlebars') + +module.exports = { + name: 'load-handlebars', + /** + * @param {import('esbuild').PluginBuild} build + */ + setup (build) { + build.onLoad({ + filter: /\.handlebars$/ + }, async ({ path: filename }) => { + try { + const source = await fs.readFile(filename, 'utf-8') + const template = handlebars.precompile(source) + const contents = [ + "import * as Handlebars from 'handlebars/runtime'", + "import '../helpers'", + `export default Handlebars.template(${template})` + ].join('\n') + return { contents } + } catch (error) { + return { errors: [{ text: error.message }] } + } + }) + } +} diff --git a/assets/js/autocomplete/autocomplete-list.js b/assets/js/autocomplete/autocomplete-list.js index 8d9f5cf05..693c94715 100644 --- a/assets/js/autocomplete/autocomplete-list.js +++ b/assets/js/autocomplete/autocomplete-list.js @@ -1,6 +1,7 @@ import { getSuggestions } from './suggestions' import { isBlank, qs } from '../helpers' import { currentTheme } from '../theme' +import autocompleteSuggestionsTemplate from '../handlebars/templates/autocomplete-suggestions.handlebars' export const AUTOCOMPLETE_CONTAINER_SELECTOR = '.autocomplete' export const AUTOCOMPLETE_SUGGESTION_LIST_SELECTOR = '.autocomplete-suggestions' @@ -56,7 +57,7 @@ export function updateAutocompleteList (searchTerm) { // Updates list of suggestions inside the autocomplete. function renderSuggestions ({ term, suggestions }) { - const autocompleteContainerHtml = Handlebars.templates['autocomplete-suggestions']({ suggestions, term }) + const autocompleteContainerHtml = autocompleteSuggestionsTemplate({ suggestions, term }) const autocompleteContainer = qs(AUTOCOMPLETE_CONTAINER_SELECTOR) autocompleteContainer.innerHTML = autocompleteContainerHtml diff --git a/assets/js/handlebars/helpers.js b/assets/js/handlebars/helpers.js index 6452cb49b..17c355e4b 100644 --- a/assets/js/handlebars/helpers.js +++ b/assets/js/handlebars/helpers.js @@ -1,3 +1,5 @@ +import * as Handlebars from 'handlebars/runtime' + Handlebars.registerHelper('groupChanged', function (context, nodeGroup, options) { const group = nodeGroup || '' if (context.group !== group) { diff --git a/assets/js/modal.js b/assets/js/modal.js index 22b314d26..1bc62dc29 100644 --- a/assets/js/modal.js +++ b/assets/js/modal.js @@ -1,4 +1,5 @@ import { qs } from './helpers' +import modalLayoutTemplate from './handlebars/templates/modal-layout.handlebars' const MODAL_SELECTOR = '.modal' const MODAL_CLOSE_BUTTON_SELECTOR = '.modal .modal-close' @@ -22,7 +23,7 @@ export function initialize () { * Adds the modal to DOM, initially it's hidden. */ function renderModal () { - const modalLayoutHtml = Handlebars.templates['modal-layout']() + const modalLayoutHtml = modalLayoutTemplate() document.body.insertAdjacentHTML('beforeend', modalLayoutHtml) qs(MODAL_SELECTOR).addEventListener('keydown', event => { diff --git a/assets/js/quick-switch.js b/assets/js/quick-switch.js index a59ff028f..8871c8e14 100644 --- a/assets/js/quick-switch.js +++ b/assets/js/quick-switch.js @@ -1,5 +1,7 @@ import { debounce, qs, qsAll } from './helpers' import { openModal } from './modal' +import quickSwitchModalBodyTemplate from './handlebars/templates/quick-switch-modal-body.handlebars' +import quickSwitchResultsTemplate from './handlebars/templates/quick-switch-results.handlebars' const HEX_DOCS_ENDPOINT = 'https://hexdocs.pm/%%' const OTP_DOCS_ENDPOINT = 'https://www.erlang.org/doc/apps/%%' @@ -115,7 +117,7 @@ function handleInput (event) { export function openQuickSwitchModal () { openModal({ title: 'Go to package docs', - body: Handlebars.templates['quick-switch-modal-body']() + body: quickSwitchModalBodyTemplate() }) qs(QUICK_SWITCH_INPUT_SELECTOR).focus() @@ -185,7 +187,7 @@ function queryForAutocomplete (packageSlug) { function renderResults ({ results }) { const resultsContainer = qs(QUICK_SWITCH_RESULTS_SELECTOR) - const resultsHtml = Handlebars.templates['quick-switch-results']({ results }) + const resultsHtml = quickSwitchResultsTemplate({ results }) resultsContainer.innerHTML = resultsHtml qsAll(QUICK_SWITCH_RESULT_SELECTOR).forEach(result => { diff --git a/assets/js/search-page.js b/assets/js/search-page.js index aa91066b8..17ee6d7ee 100644 --- a/assets/js/search-page.js +++ b/assets/js/search-page.js @@ -3,6 +3,7 @@ import lunr from 'lunr' import { qs, escapeHtmlEntities, isBlank, getQueryParamByName, getProjectNameAndVersion } from './helpers' import { setSearchInputValue } from './search-bar' +import searchResultsTemplate from './handlebars/templates/search-results.handlebars' const EXCERPT_RADIUS = 80 const SEARCH_CONTAINER_SELECTOR = '#search' @@ -48,7 +49,7 @@ async function search (value) { function renderResults ({ value, results, errorMessage }) { const searchContainer = qs(SEARCH_CONTAINER_SELECTOR) - const resultsHtml = Handlebars.templates['search-results']({ value, results, errorMessage }) + const resultsHtml = searchResultsTemplate({ value, results, errorMessage }) searchContainer.innerHTML = resultsHtml } diff --git a/assets/js/settings.js b/assets/js/settings.js index 365e3c12e..ad383134c 100644 --- a/assets/js/settings.js +++ b/assets/js/settings.js @@ -2,6 +2,7 @@ import { qs, qsAll } from './helpers' import { openModal } from './modal' import { settingsStore } from './settings-store' import { keyboardShortcuts } from './keyboard-shortcuts' +import settingsModalBodyTemplate from './handlebars/templates/settings-modal-body.handlebars' const SETTINGS_LINK_SELECTOR = '.display-settings' const SETTINGS_MODAL_BODY_SELECTOR = '#settings-modal-content' @@ -53,7 +54,7 @@ function showKeyboardShortcutsTab () { export function openSettingsModal () { openModal({ title: modalTabs.map(({id, title}) => ``).join(''), - body: Handlebars.templates['settings-modal-body']({ shortcuts: keyboardShortcuts }) + body: settingsModalBodyTemplate({ shortcuts: keyboardShortcuts }) }) const modal = qs(SETTINGS_MODAL_BODY_SELECTOR) diff --git a/assets/js/sidebar/sidebar-list.js b/assets/js/sidebar/sidebar-list.js index 86720a5d8..397cf5a57 100644 --- a/assets/js/sidebar/sidebar-list.js +++ b/assets/js/sidebar/sidebar-list.js @@ -1,5 +1,6 @@ import { qs, getCurrentPageSidebarType, getLocationHash, findSidebarCategory } from '../helpers' import { getSidebarNodes } from '../globals' +import sidebarItemsTemplate from '../handlebars/templates/sidebar-items.handlebars' const SIDEBAR_TYPE = { search: 'search', @@ -42,7 +43,7 @@ function renderSidebarNodeList (nodesByType, type) { // Render the list const nodeList = qs(sidebarNodeListSelector(type)) if (!nodeList) { return } - const listContentHtml = Handlebars.templates['sidebar-items']({ nodes, group: '' }) + const listContentHtml = sidebarItemsTemplate({ nodes, group: '' }) nodeList.innerHTML = listContentHtml // Removes the "expand" class from links belonging to single-level sections diff --git a/assets/js/sidebar/sidebar-version-select.js b/assets/js/sidebar/sidebar-version-select.js index 9b3d48e88..f10ade61e 100644 --- a/assets/js/sidebar/sidebar-version-select.js +++ b/assets/js/sidebar/sidebar-version-select.js @@ -1,5 +1,6 @@ import { qs, checkUrlExists } from '../helpers' import { getVersionNodes } from '../globals' +import versionsDropdownTemplate from '../handlebars/templates/versions-dropdown.handlebars' const VERSIONS_CONTAINER_SELECTOR = '.sidebar-projectVersion' const VERSIONS_DROPDOWN_SELECTOR = '.sidebar-projectVersionsDropdown' @@ -22,7 +23,7 @@ export function initialize () { function renderVersionsDropdown ({ nodes }) { const versionsContainer = qs(VERSIONS_CONTAINER_SELECTOR) - const versionsDropdownHtml = Handlebars.templates['versions-dropdown']({ nodes }) + const versionsDropdownHtml = versionsDropdownTemplate({ nodes }) versionsContainer.innerHTML = versionsDropdownHtml qs(VERSIONS_DROPDOWN_SELECTOR).addEventListener('change', handleVersionSelected) diff --git a/assets/js/tabsets.js b/assets/js/tabsets.js index 8c33c51a3..251ed2023 100644 --- a/assets/js/tabsets.js +++ b/assets/js/tabsets.js @@ -1,3 +1,5 @@ +import tabsetTemplate from './handlebars/templates/tabset.handlebars' + const CONTENT_CONTAINER_ID = 'content' const TABSET_OPEN_COMMENT = 'tabs-open' const TABSET_CLOSE_COMMENT = 'tabs-close' @@ -78,7 +80,7 @@ function processTabset (element, tabSetIndex, _array) { wrapElements(allSetNodes, container) // Apply template to tabset container element. - container.innerHTML = Handlebars.templates.tabset({tabs: tabSet}) + container.innerHTML = tabsetTemplate({tabs: tabSet}) // Return tabset container element. return container diff --git a/assets/js/tooltips/tooltips.js b/assets/js/tooltips/tooltips.js index f7a38d82f..a220c62bc 100644 --- a/assets/js/tooltips/tooltips.js +++ b/assets/js/tooltips/tooltips.js @@ -1,6 +1,8 @@ import { qs, qsAll } from '../helpers' import { settingsStore } from '../settings-store' import { cancelHintFetchingIfAny, getHint, HINT_KIND, isValidHintHref } from './hints' +import tooltipLayoutTemplate from '../handlebars/templates/tooltip-layout.handlebars' +import tooltipBodyTemplate from '../handlebars/templates/tooltip-body.handlebars' // Elements that can activate the tooltip. const TOOLTIP_ACTIVATORS_SELECTOR = '.content a' @@ -41,7 +43,7 @@ export function initialize () { } function renderTooltipLayout () { - const tooltipLayoutHtml = Handlebars.templates['tooltip-layout']() + const tooltipLayoutHtml = tooltipLayoutTemplate() qs(CONTENT_INNER_SELECTOR).insertAdjacentHTML('beforeend', tooltipLayoutHtml) } @@ -108,7 +110,7 @@ function shouldShowTooltips () { } function renderTooltip (hint) { - const tooltipBodyHtml = Handlebars.templates['tooltip-body']({ + const tooltipBodyHtml = tooltipBodyTemplate({ isPlain: hint.kind === HINT_KIND.plain, hint }) diff --git a/lib/ex_doc/formatter/html/templates/head_template.eex b/lib/ex_doc/formatter/html/templates/head_template.eex index 48e8e9be0..b8aa6414e 100644 --- a/lib/ex_doc/formatter/html/templates/head_template.eex +++ b/lib/ex_doc/formatter/html/templates/head_template.eex @@ -17,8 +17,6 @@ <%= if config.canonical do %> <% end %> - - diff --git a/mix.exs b/mix.exs index 2c3fcf449..7112a3b9a 100644 --- a/mix.exs +++ b/mix.exs @@ -56,7 +56,7 @@ defmodule ExDoc.Mixfile do clean: [&clean_test_fixtures/1, "clean"], fix: ["format", "cmd --cd assets npm run lint:fix"], lint: ["format --check-formatted", "cmd --cd assets npm run lint"], - setup: ["deps.get", "cmd mkdir -p tmp/handlebars", "cmd --cd assets npm install"] + setup: ["deps.get", "cmd --cd assets npm install"] ] end From 50bce35d88f29ce5c926a38ae28eade6c8ce32e9 Mon Sep 17 00:00:00 2001 From: Liam Mitchell Date: Mon, 30 Dec 2024 20:46:15 +0100 Subject: [PATCH 2/4] Refactor assets build script --- assets/build/build.js | 191 +++++++++++++------------------- assets/build/load-handlebars.js | 27 ----- assets/build/utilities.js | 21 ---- 3 files changed, 77 insertions(+), 162 deletions(-) delete mode 100644 assets/build/load-handlebars.js delete mode 100644 assets/build/utilities.js diff --git a/assets/build/build.js b/assets/build/build.js index 1b81ef1e4..f74ca7529 100644 --- a/assets/build/build.js +++ b/assets/build/build.js @@ -2,125 +2,88 @@ const path = require('node:path') const process = require('node:process') const cp = require('node:child_process') const esbuild = require('esbuild') -const util = require('./utilities') -const loadHandlebars = require('./load-handlebars') +const fsExtra = require('fs-extra') +const fs = require('node:fs/promises') +const handlebars = require('handlebars') +const util = require('node:util') -const watchMode = Boolean(process.env.npm_config_watch) - -/** - * Configuration variables - */ - -// Basic build configuration and values -const commonOptions = { - entryNames: '[name]-[hash]', - bundle: true, - minify: true, - logLevel: watchMode ? 'warning' : 'info' -} -const epubOutDir = path.resolve('../formatters/epub/dist') -const htmlOutDir = path.resolve('../formatters/html/dist') - -/** - * Build: Plugins - */ +const exec = util.promisify(cp.exec) -// Empty outdir directories before both normal and watch-mode builds -const epubOnStartPlugin = { - name: 'epubOnStart', - setup (build) { build.onStart(() => util.ensureEmptyDirsExistSync([epubOutDir])) } -} -const htmlOnStartPlugin = { - name: 'htmlOnStart', - setup (build) { build.onStart(() => util.ensureEmptyDirsExistSync([htmlOutDir])) } -} - -/** - * Build - */ - -// ePub: esbuild options -const epubBuildOptions = { - ...commonOptions, - outdir: epubOutDir, - plugins: [epubOnStartPlugin, loadHandlebars], - entryPoints: [ - 'js/entry/epub.js', - 'css/entry/epub-elixir.css', - 'css/entry/epub-erlang.css' - ] -} +const watchMode = Boolean(process.env.npm_config_watch) -// ePub: esbuild (conditionally configuring watch mode and rebuilding of docs) -if (!watchMode) { - esbuild.build(epubBuildOptions).catch(() => process.exit(1)) -} else { - esbuild.build({ - ...epubBuildOptions, - watch: { - onRebuild (error, result) { - if (error) { - console.error('[watch] epub build failed:', error) - } else { - console.log('[watch] epub assets rebuilt') - if (result.errors.length > 0) console.log('[watch] epub build errors:', result.errors) - if (result.warnings.length > 0) console.log('[watch] epub build warnings:', result.warnings) - generateDocs('epub') - } - } +/** @type {import('esbuild').BuildOptions[]} */ +const formatters = [ + { + formatter: 'epub', + outdir: path.resolve('../formatters/epub/dist'), + entryPoints: [ + 'js/entry/epub.js', + 'css/entry/epub-elixir.css', + 'css/entry/epub-erlang.css' + ] + }, + { + formatter: 'html', + outdir: path.resolve('../formatters/html/dist'), + entryPoints: [ + 'js/entry/html.js', + 'css/entry/html-elixir.css', + 'css/entry/html-erlang.css' + ], + loader: { + '.woff2': 'file', + // TODO: Remove when @fontsource/* removes legacy .woff + '.woff': 'file' } - }).then(() => generateDocs('epub')).catch(() => process.exit(1)) -} - -// HTML: esbuild options -const htmlBuildOptions = { - ...commonOptions, - outdir: htmlOutDir, - plugins: [htmlOnStartPlugin, loadHandlebars], - entryPoints: [ - 'js/entry/html.js', - 'css/entry/html-elixir.css', - 'css/entry/html-erlang.css' - ], - loader: { - '.woff2': 'file', - // TODO: Remove when @fontsource/* removes legacy .woff - '.woff': 'file' } -} +] -// HTML: esbuild (conditionally configuring watch mode and rebuilding of docs) -if (!watchMode) { - esbuild.build(htmlBuildOptions).catch(() => process.exit(1)) -} else { - esbuild.build({ - ...htmlBuildOptions, - watch: { - onRebuild (error, result) { - if (error) { - console.error('[watch] html build failed:', error) - } else { - console.log('[watch] html assets rebuilt') - if (result.errors.length > 0) console.log('[watch] html build errors:', result.errors) - if (result.warnings.length > 0) console.log('[watch] html build warnings:', result.warnings) - generateDocs('html') - } - } - } - }).then(() => { - generateDocs('html') - }).catch(() => process.exit(1)) -} +Promise.all(formatters.map(async ({formatter, ...options}) => { + // Clean outdir. + await fsExtra.emptyDir(options.outdir) -/** - * Functions - */ + await esbuild.build({ + entryNames: watchMode ? '[name]-dev' : '[name]-[hash]', + bundle: true, + minify: !watchMode, + logLevel: watchMode ? 'warning' : 'info', + watch: watchMode, + ...options, + plugins: [{ + name: 'ex_doc', + setup (build) { + // Pre-compile handlebars templates. + build.onLoad({ + filter: /\.handlebars$/ + }, async ({ path: filename }) => { + try { + const source = await fs.readFile(filename, 'utf-8') + const template = handlebars.precompile(source) + const contents = [ + "import * as Handlebars from 'handlebars/runtime'", + "import '../helpers'", + `export default Handlebars.template(${template})` + ].join('\n') + return { contents } + } catch (error) { + return { errors: [{ text: error.message }] } + } + }) -// Docs generation (used in watch mode only) -function generateDocs (formatter) { - console.log(`Building ${formatter} docs`) - process.chdir('../') - cp.execSync('mix compile --force') - cp.execSync(`mix docs --formatter ${formatter}`) - process.chdir('./assets/') -} + // Generate docs with new assets (watch mode only). + if (watchMode) { + build.onEnd(async result => { + if (result.errors.length) return + console.log(`[watch] ${formatter} assets built`) + await exec('mix compile --force', {cwd: '../'}) + await exec(`mix docs --formatter ${formatter}`, {cwd: '../'}) + console.log(`[watch] ${formatter} docs built`) + }) + } + } + }] + }) +})).catch((error) => { + console.error(error) + process.exit(1) +}) diff --git a/assets/build/load-handlebars.js b/assets/build/load-handlebars.js deleted file mode 100644 index 802f25a32..000000000 --- a/assets/build/load-handlebars.js +++ /dev/null @@ -1,27 +0,0 @@ -const fs = require('node:fs/promises') -const handlebars = require('handlebars') - -module.exports = { - name: 'load-handlebars', - /** - * @param {import('esbuild').PluginBuild} build - */ - setup (build) { - build.onLoad({ - filter: /\.handlebars$/ - }, async ({ path: filename }) => { - try { - const source = await fs.readFile(filename, 'utf-8') - const template = handlebars.precompile(source) - const contents = [ - "import * as Handlebars from 'handlebars/runtime'", - "import '../helpers'", - `export default Handlebars.template(${template})` - ].join('\n') - return { contents } - } catch (error) { - return { errors: [{ text: error.message }] } - } - }) - } -} diff --git a/assets/build/utilities.js b/assets/build/utilities.js deleted file mode 100644 index 9ac0d11a7..000000000 --- a/assets/build/utilities.js +++ /dev/null @@ -1,21 +0,0 @@ -const child_process = require('node:child_process') -const fs = require('fs-extra') - -module.exports.runShellCmdSync = (command) => { - child_process.execSync(command, (err, stdout, stderr) => { - if (err) { - console.error(err) - process.exit(1) - } else { - if (stdout) { console.log('\n' + stdout) } - if (stderr) { console.log('\n' + stderr) } - return true - } - }) -} - -module.exports.ensureEmptyDirsExistSync = (dirs) => { - dirs.forEach(dir => { - fs.emptyDirSync(dir) - }) -} From b0ecddcebaa8a6829378761106e982ec70e46fce Mon Sep 17 00:00:00 2001 From: Liam Mitchell Date: Tue, 31 Dec 2024 08:59:58 +0100 Subject: [PATCH 3/4] Remove [watch] log prefix --- assets/build/build.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/assets/build/build.js b/assets/build/build.js index f74ca7529..c46619056 100644 --- a/assets/build/build.js +++ b/assets/build/build.js @@ -74,10 +74,10 @@ Promise.all(formatters.map(async ({formatter, ...options}) => { if (watchMode) { build.onEnd(async result => { if (result.errors.length) return - console.log(`[watch] ${formatter} assets built`) + console.log(`${formatter} assets built`) await exec('mix compile --force', {cwd: '../'}) await exec(`mix docs --formatter ${formatter}`, {cwd: '../'}) - console.log(`[watch] ${formatter} docs built`) + console.log(`${formatter} docs built`) }) } } From 70ac12ed8dc55415593c299ff8b72ee19116617a Mon Sep 17 00:00:00 2001 From: Liam Mitchell Date: Tue, 31 Dec 2024 09:16:45 +0100 Subject: [PATCH 4/4] Remove unneeded npx prefixes in package scripts --- assets/package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/assets/package.json b/assets/package.json index a41c67550..82b64e553 100644 --- a/assets/package.json +++ b/assets/package.json @@ -9,9 +9,9 @@ "npm": ">= 9.0.0" }, "scripts": { - "lint": "npx eslint './js/**/*.js'", - "lint:fix": "npx eslint --fix './js/**/*.js'", - "test": "npx karma start ./karma.conf.js --single-run", + "lint": "eslint './js/**/*.js'", + "lint:fix": "eslint --fix './js/**/*.js'", + "test": "karma start ./karma.conf.js --single-run", "build:watch": "npm run build --watch", "build": "node build/build.js" },