Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 0 additions & 3 deletions assets/.eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,5 @@ module.exports = {
'no-throw-literal': 0,
'no-useless-escape': 0,
'object-curly-spacing': 0
},
globals: {
'Handlebars': 'readonly'
}
}
228 changes: 81 additions & 147 deletions assets/build/build.js
Original file line number Diff line number Diff line change
@@ -1,155 +1,89 @@
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 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')

// 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
*/

// 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])) },
}
const fsExtra = require('fs-extra')
const fs = require('node:fs/promises')
const handlebars = require('handlebars')
const util = require('node:util')

const exec = util.promisify(cp.exec)

/**
* Build
*/

// ePub: esbuild options
const epubBuildOptions = {
...commonOptions,
outdir: epubOutDir,
plugins: [epubOnStartPlugin],
entryPoints: [
'js/entry/epub.js',
'css/entry/epub-elixir.css',
'css/entry/epub-erlang.css',
],
}

// 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")
}
},
},
}).then(() => generateDocs("epub")).catch(() => process.exit(1))
}

// HTML: Precompile Handlebars templates
util.runShellCmdSync(`npx handlebars ${templates.sourceDir} --output ${templates.compiledPath}`)
const watchMode = Boolean(process.env.npm_config_watch)

// HTML: esbuild options
const htmlBuildOptions = {
...commonOptions,
outdir: htmlOutDir,
plugins: [htmlOnStartPlugin],
entryPoints: [
templates.compiledPath,
'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',
/** @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'
]
},
}

// HTML: esbuild (conditionally configuring watch mode and rebuilding of docs)
if (!watchMode) {
esbuild.build(htmlBuildOptions).then(() => buildTemplatesRuntime()).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)
buildTemplatesRuntime()
generateDocs("html")
{
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'
}
}
]

Promise.all(formatters.map(async ({formatter, ...options}) => {
// Clean outdir.
await fsExtra.emptyDir(options.outdir)

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 }] }
}
})

// Generate docs with new assets (watch mode only).
if (watchMode) {
build.onEnd(async result => {
if (result.errors.length) return
console.log(`${formatter} assets built`)
await exec('mix compile --force', {cwd: '../'})
await exec(`mix docs --formatter ${formatter}`, {cwd: '../'})
console.log(`${formatter} docs built`)
})
}
},
},
}).then(() => {
buildTemplatesRuntime()
generateDocs("html")
}).catch(() => process.exit(1))
}

/**
* 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) {
console.log(`Building ${formatter} docs`)
process.chdir('../')
child_process.execSync('mix compile --force')
child_process.execSync(`mix docs --formatter ${formatter}`)
process.chdir('./assets/')
}
}
}]
})
})).catch((error) => {
console.error(error)
process.exit(1)
})
21 changes: 0 additions & 21 deletions assets/build/utilities.js

This file was deleted.

3 changes: 2 additions & 1 deletion assets/js/autocomplete/autocomplete-list.js
Original file line number Diff line number Diff line change
@@ -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'
Expand Down Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions assets/js/handlebars/helpers.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import * as Handlebars from 'handlebars/runtime'

Handlebars.registerHelper('groupChanged', function (context, nodeGroup, options) {
const group = nodeGroup || ''
if (context.group !== group) {
Expand Down
3 changes: 2 additions & 1 deletion assets/js/modal.js
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -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 => {
Expand Down
6 changes: 4 additions & 2 deletions assets/js/quick-switch.js
Original file line number Diff line number Diff line change
@@ -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/%%'
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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 => {
Expand Down
3 changes: 2 additions & 1 deletion assets/js/search-page.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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
}

Expand Down
3 changes: 2 additions & 1 deletion assets/js/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -53,7 +54,7 @@ function showKeyboardShortcutsTab () {
export function openSettingsModal () {
openModal({
title: modalTabs.map(({id, title}) => `<button id="${id}">${title}</button>`).join(''),
body: Handlebars.templates['settings-modal-body']({ shortcuts: keyboardShortcuts })
body: settingsModalBodyTemplate({ shortcuts: keyboardShortcuts })
})

const modal = qs(SETTINGS_MODAL_BODY_SELECTOR)
Expand Down
3 changes: 2 additions & 1 deletion assets/js/sidebar/sidebar-list.js
Original file line number Diff line number Diff line change
@@ -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',
Expand Down Expand Up @@ -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
Expand Down
Loading