UNPKG

@rebuildjs/tailwindcss

Version:

Tailwindcss integration with rebuildjs

251 lines (250 loc) 8.23 kB
import { file_exists_, file_exists__waitfor } from 'ctx-core/fs' import { calling, Cancel, memo_, ns_id_be, ns_id_be_memo_pair_, ns_id_be_sig_triple_, nullish__none_, promise__cancel__throw, ref__bind, rmemo__wait, sleep, tup } from 'ctx-core/rmemo' import { readFile, writeFile } from 'node:fs/promises' import { basename, join } from 'node:path' import postcss from 'postcss' import { app_ctx, browser__metafile_, browser__metafile__update, browser__output_, browser__output__relative_path_, build_id_, cssBundle__annotate, cwd_, rebuildjs__esbuild__build_id_, rebuildjs__esbuild__done_, rebuildjs__esbuild__done__wait, rebuildjs__ready__add, server__metafile_, server__metafile__update, server__output_, server__output__relative_path_, server__output__relative_path_M_middleware_ctx_ } from 'rebuildjs/server' import tailwind from 'tailwindcss' export const [ rebuild_tailwind_plugin__build_id$_, rebuild_tailwind_plugin__build_id_, rebuild_tailwind_plugin__build_id__set, ] = ns_id_be_sig_triple_( 'app', 'rebuild_tailwind_plugin__build_id', ()=>undefined) export const [ rebuildjs_tailwind__ready$_, rebuildjs_tailwind__ready_, ] = ns_id_be_memo_pair_( 'app', 'rebuildjs_tailwind__ready', ctx=> !!( build_id_(ctx) && rebuildjs__esbuild__done_(ctx) && build_id_(ctx) === rebuild_tailwind_plugin__build_id_(ctx))) export function rebuildjs_tailwind__ready__wait(timeout) { return rmemo__wait( rebuildjs_tailwind__ready$_(app_ctx), ready=>ready, timeout ?? 10_000) } /** * @param {rebuild_tailwind_plugin__config_T}[config] * @returns {{name: string, setup: setup}} * @private */ export function rebuild_tailwind_plugin_(config) { return { name: 'rebuild_tailwind_plugin', setup: setup_() } function setup_() { rebuildjs__ready__add(rebuildjs_tailwind__ready$_) /** * @param {import('esbuild').PluginBuild}build */ const setup = (build)=>{ build.onEnd(result=>{ if (result.errors.length) { throw Error('Build errors: ' + result.errors.length + ' errors') } }) } ref__bind(setup, tailwind__build$_()) return setup function tailwind__build$_() { return ns_id_be( app_ctx, 'app', 'tailwind__build$', app_ctx=>{ return calling(memo_(()=>{ if (!rebuildjs__esbuild__done_(app_ctx)) return nullish__none_(tup( build_id_(app_ctx), rebuildjs__esbuild__build_id_(app_ctx), server__output__relative_path_M_middleware_ctx_(app_ctx), ), async ( build_id, rebuildjs__build_id, server__output__relative_path_M_middleware_ctx, )=>{ try { await cmd(rebuildjs__esbuild__done__wait()) const _server__metafile = server__metafile_(app_ctx) const _browser__metafile = browser__metafile_(app_ctx) const server_output__process_promise_a1 = [] const browser_output__process_promise_a1 = [] for (const middleware_ctx of server__output__relative_path_M_middleware_ctx.values()) { server_output__process_promise_a1.push( output__process( _server__metafile, server__output__relative_path_(middleware_ctx), server__output_(middleware_ctx))) browser_output__process_promise_a1.push( output__process( _browser__metafile, browser__output__relative_path_(middleware_ctx), browser__output_(middleware_ctx))) } const [ server__metafile_updated_a1, browser__metafile_updated_a1, ] = await Promise.all([ Promise.all(server_output__process_promise_a1), Promise.all(browser_output__process_promise_a1) ]) const update_promise_a1 = [] if (server__metafile_updated_a1.some($=>$)) { update_promise_a1.push(server__metafile__update(_server__metafile)) } if (browser__metafile_updated_a1.some($=>$)) { update_promise_a1.push(browser__metafile__update(_browser__metafile)) } if (update_promise_a1.length) { await cmd(Promise.all(update_promise_a1)) } rebuild_tailwind_plugin__build_id__set(app_ctx, build_id) } catch (err) { if (err instanceof Cancel) return throw err } /** * @param {rebuildjs_metafile_T['outputs'][string]}output * @returns {Promise<void>} */ async function output__process( metafile, output__relative_path, output, ) { let metafile_updated = false const cssBundle = output?.cssBundle if (!cssBundle) return metafile_updated const esbuild_cssBundle = output.esbuild_cssBundle ?? cssBundle const esbuild_cssBundle_path = join(cwd_(app_ctx), esbuild_cssBundle) await cmd(file_exists__waitfor( esbuild_cssBundle_path, Infinity, ()=>cmd(sleep(0)))) const esbuild_cssBundle_map_path = esbuild_cssBundle_path + '.map' const esbuild_cssBundle_map_exists = await cmd(file_exists_(esbuild_cssBundle_map_path)) const output_hash = basename(output__relative_path, '.js') .split('-') .slice(-1)[0] const tailwind_instance = tailwind({ ...config?.tailwindcss_config, content: [ ...output.cssBundle_content.map(content__relative_path=> join(cwd_(app_ctx), content__relative_path)), ...(config?.content ?? []) ] }) const result = await file_exists__waitfor( async ()=>cmd( postcss( config?.postcss_plugin_a1_?.(tailwind_instance) ?? [tailwind_instance] ).process( await readFile(esbuild_cssBundle_path), { from: esbuild_cssBundle_path, to: join(cwd_(app_ctx), cssBundle), map: esbuild_cssBundle_map_exists ? { prev: await file_json__parse__wait(esbuild_cssBundle_map_path) } : false, })), Infinity) metafile_updated = !cssBundle.includes('_' + output_hash) const annotated_cssBundle = metafile_updated ? cssBundle__annotate(cssBundle, '_' + output_hash) : cssBundle const annotated_cssBundle_path = join(cwd_(app_ctx), annotated_cssBundle) const annotated_cssBundle_map_path = join(cwd_(app_ctx), annotated_cssBundle) + '.map' await cmd(writeFile(annotated_cssBundle_path, result.css)) const map_json = result.map ? JSON.stringify(result.map) : null if (map_json) { await cmd(writeFile(annotated_cssBundle_map_path, map_json)) await file_exists__waitfor(()=> cmd(readFile(annotated_cssBundle_map_path)) .then(buf=>'' + buf === map_json), 5_000) } await file_exists__waitfor(()=> cmd(readFile(annotated_cssBundle_path)) .then(buf=>'' + buf === result.css), 5_000) output.cssBundle = annotated_cssBundle if (metafile_updated) { metafile.outputs[annotated_cssBundle] = metafile.outputs[cssBundle] metafile.outputs[annotated_cssBundle + '.map'] = metafile.outputs[cssBundle + '.map'] } return metafile_updated } async function file_json__parse__wait(path) { // eslint-disable-next-line no-constant-condition while (1) { try { return JSON.parse( await file_exists__waitfor(()=> cmd(readFile(path)))) } catch (err) { if (err.name === 'SyntaxError') continue throw err } } } async function cmd(promise) { if (cancel_()) promise__cancel__throw(promise) const rv = await promise if (cancel_()) promise__cancel__throw(promise) return rv } function cancel_() { return ( build_id_(app_ctx) !== build_id || rebuildjs__esbuild__build_id_(app_ctx) !== rebuildjs__build_id || server__output__relative_path_M_middleware_ctx_( app_ctx) !== server__output__relative_path_M_middleware_ctx ) } }) })) }) } } }