Skip to content

prettier-pre-warm 'none still starts the server and blocks when prettier-mode is enabled #143

Description

@mrcnski

Summary

prettier-pre-warm documents none as "No pre-warming, everything on-demand", and its docstring says that with none "you tend to wait for it on first save". That doesn't hold while prettier-mode-sync-config-flag is at its default of t: enabling prettier-mode still starts the Node server and blocks on a synchronous round-trip, because the config-sync path starts the process independently of the pre-warm setting.

This was quite noticeable when I had desktop-save-mode restoring buffers across ~10 TypeScript projects. Startup does one blocking round-trip per restored buffer, each flashing "Prettier syncing config" in the echo area, even with prettier-pre-warm set to none specifically to avoid paying that cost at startup.

Note: I'm willing to create the PR for this.

Possibly related: #81 ("Pre-warm idea").

Environment

  • prettier.el 20240902.1516 (MELPA)
  • GNU Emacs 30.2, macOS (aarch64)
  • prettier-pre-warm set to none, prettier-mode-sync-config-flag left at its default t

The code path

The two branches in prettier-mode are independent:

(if prettier-mode
    (progn
      (unless (eq prettier-pre-warm 'none)
        (prettier--get-process
         (eq prettier-pre-warm 'full)))
      (when prettier-mode-sync-config-flag
        (prettier--maybe-sync-config)
        ...

With pre-warm set to none the first branch is correctly skipped, but the second one runs prettier--maybe-sync-configprettier--load-config-cachedprettier--load-config, and that function calls prettier--get-process itself:

(defun prettier--load-config (&optional override-parsers)
  ...
  (with-temp-message "Prettier syncing config"
    (let* ((start-time (current-time))
           (prettier-process (prettier--get-process))
           ...

So the server starts and is waited on regardless of prettier-pre-warm.

Worth noting that prettier--load-config-cached only caches for buffers not visiting a file; file-visiting buffers take the (buffer-file-name) branch and call prettier--load-config unconditionally. Ten files in one project therefore cost ten round-trips, not one, even though they resolve to the same Prettier config.

Reproduction

  1. (setq prettier-pre-warm 'none)
  2. Open a .ts file in a project with a Prettier config and enable prettier-mode.
  3. Observe "Prettier syncing config" and a live *prettier (local)* process, before any edit or save.

Expected per the docstring: no server, no wait, until the first save.

Suggested fix

Rather than adding another option, let the existing prettier-pre-warm govern the config sync as well:

  • full / some: sync at prettier-mode activation, as today.
  • none: defer it, e.g. via a one-shot buffer-local first-change-hook, so the settings land before the user's first edit rather than at mode activation.

That keeps the documented meaning of none ("everything on-demand") true, adds no new user-facing surface, and preserves the sync. The sync is what derives indent_style / indent_size / max_line_length via editorconfig-set-local-variables for projects that have a .prettierrc but no .editorconfig, and what sets the coding system from endOfLine.

Caveat: this is a mild behavior change for anyone currently running none and relying on the sync happening at activation. If that's a concern it could be a distinct prettier-mode-sync-config-flag value (e.g. on-first-change) instead, leaving t untouched. Happy to send a PR for whichever shape you prefer.

Workaround

For anyone hitting the same startup stall, deferring the sync from outside the package:

(setq prettier-pre-warm 'none
      prettier-mode-sync-config-flag nil)

(defun my-prettier-sync-config-now (buffer)
  (when (buffer-live-p buffer)
    (with-current-buffer buffer
      (when (and prettier-mode (fboundp 'prettier--maybe-sync-config))
        ;; `prettier--maybe-sync-config' checks the flag itself.
        (let ((prettier-mode-sync-config-flag t))
          (prettier--maybe-sync-config))))))

(defun my-prettier-sync-config-on-first-change ()
    "Sync this buffer's Prettier config, once, when it is first edited.
Runs off an idle timer rather than inline: `first-change-hook' fires
from inside `prepare-to-modify-buffer', which is no place for a
blocking round-trip to the Prettier server."
  (remove-hook 'first-change-hook #'my-prettier-sync-config-on-first-change 'local)
  (run-with-idle-timer 0 nil #'my-prettier-sync-config-now (current-buffer)))

(add-hook 'prettier-mode-hook
          (lambda ()
            (when prettier-mode
              (add-hook 'first-change-hook
                        #'my-prettier-sync-config-on-first-change
                        nil 'local))))

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions