Problem/Motivation

Currently, the module operates with the assumption that vite is configured per module/theme and resolves paths relative to the module/theme dir. While it works fine, in some cases it would be more convenient to create a single vite configuration for the entire project (instead of separate configs for each module/theme) and place it, for example, in the project root.

Proposed resolution

  • allow configuring vite root dir (with default value of theme/module dir)
  • resolve paths relative to vite root
  • deprecate manifestPath config option in favor of a new distDir config option that will be the same as the build.outDir in vite config (vite manifest file is always in the same location in the dist/out directory)
  • automatically resolve dist asset paths relative to drupal app root so that there's no need to configure baseUrl

With these changes, configuring vite setup in project root dir with dist output in libraries dir could look like this:

/vite.config.js
/web/...
/web/themes/custom/my_theme/...
/web/themes/custom/my_theme/ts/script.ts
# settings.php
$settings['vite']['viteRoot'] = '/..'; # if starts with / it's resolved relative to Drupal app root, othervise relative to theme/module dir
$settings['vite']['distDir'] = 'web/libraries/dist/';
# this can also be configured on per extension/library basis in .info.yml or .libraries.yml same as other configuration options
# web/themes/custom/my_theme/my_theme.info.yml
...
vite:
  enableInAllLibraries: true
# web/themes/custom/my_theme/my_theme.libraries.yml
my_library:
  js:
    ts/script.ts: {}

Remaining tasks

  • add automated tests for the new config options
  • update error messages to account for new config options
  • add warning when distDir is configured outside drupal app root
  • create follow-up for adding automatic detection of distDir based on regexing vite.config.(ts|js) in viteRoot
  • create follow-up for removing deprecated manifestPath config option

Issue fork vite-3511730

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

wotnak created an issue. See original summary.

wotnak’s picture

Pushed initial implementation of the proposed solution to MR !22.

Still needs more testing, some cleanup, docs and automated test.

It adds a new `viteRoot` config option that defaults to extension directory but can be set to for example, project root. If the value starts with /, then it is resolved relative to drupal root, otherwise relative to the extension dir.
The vite root path is then used to resolve path to manifest (configured manifest path is treated as relative to vite root) and paths to assets.

For example, to use setup with vite configured in project root and dist files generated to libraries dir (web/libraries/dist) we just need to set vite root to /.. and manifest path to web/libraries/dist/.vite/manifest.json.

/vite.config.js
/web/...
/web/themes/custom/my_theme/...
/web/themes/custom/my_theme/ts/script.ts
# settings.php
$settings['vite']['viteRoot'] = '/..';
$settings['vite']['manifest'] = 'web/libraries/dist/.vite/manifest.json';
# this can also be configured on per extension/library basis in .info.yml or .libraries.yml same as other configuration options
# web/themes/custom/my_theme/my_theme.info.yml
...
vite:
  enableInAllLibraries: true
# web/themes/custom/my_theme/my_theme.libraries.yml
my_library:
  js:
    ts/script.ts: {}
wotnak’s picture

Added another config option `distPath` that should be set to dist directory path relative to vite root, the same as the build.outDir in vite config. The `manifest` config option is now deprecated in favor of `distPath` since vite manifest file is always in the same location in the dist directory.

Tests currently pass locally in an actual project but fail in gitlab ci because of the symlinked module source. Will probably need to replace `realpath` usage when resolving paths to skip resolving symlinks.

wotnak’s picture

Title: Add support for using vite from the root of the project » Add support for using vite outside of the theme/module directory
Issue summary: View changes
Issue tags: +Needs tests

Recent changes:

  • removed use of realpath function and tests are now passing also in symlinked environment
  • renamed distPath config option to distDir to more closely match Vite's build.outDir config option

Also updated IS to match the proposed resolution to the current approach and to add remaining tasks.

I will work on adding tests, but otherwise this should be ready for a review.

wotnak’s picture

Issue summary: View changes

reinfate made their first commit to this issue’s fork.

reinfate’s picture

Tested the changes in a real project with Vite configured in the project root (originally project has multiple webpack setups)
Seems to work as expected. All entries are properly attached to the page.

I see the issue is assigned, but I tried to write a test anyway and pushed it in a separate branch just in case.
One issue I came across. \Drupal\Tests\vite\ViteKernelTestBase::assertLibraryAssetPath expects the file to always be relative to the module root, even if viteRoot is outside the module. That works for libraries since \Drupal\vite\Vite::resolveDistAssetPath makes a library file path relative to the module. But, for components, files are relative to the core directory.
So I've added an optional parameter for assertLibraryAssetPath to indicate the viteRoot path.

I think it would be nicer if all paths were absolute, unless I don't see a reason why paths should be relative. Especially with vite in the project root, the links in the HTML look weird, because there is a lot of .. in the path in that case.

reinfate’s picture

Status: Active » Needs review

I noticed that @vite/client resolved with the absolute path to the vite contrib module if the vite root is globally configured to another path. For example, if Vite root is modules/custom, the generated link is {devServerUrl}/var/www/html/web/modules/contrib/vite/@vite/client.
Funny thing, if any CSS is included in the page, it will import the @vite/client itself, so the HMR will work anyway in that case.
Fixed that in MR !25
I guess this needs review now.

mh_nichts’s picture

Hello,

Just to say I tested the last MR (25) on a local test project and it worked for me, with Vite config at the root of the project.

One note though : as I'm using DDEV , I had to add root: 'web' in the config file, on top of following the instructions on https://www.drupal.org/docs/extending-drupal/contributed-modules/contrib... .
This root parameter seems necessary for the manifest paths to match the server expectations (= the manifest paths should not contain "web/").

darvanen’s picture

We closed #3490107: Make settings override for manifest an absolute value in favour of this approach.

I've finally got around to trying it out and I have a problem.

All the assets are being optimised and the chunk addresses in the built JS are relative so the browser is throwing a heap of 404s.

How did y'all stop Drupal from optimising your built assets?

Also, I had to spend a long time with xdebug to figure out how the settings were supposed to work for this.

darvanen’s picture

I've added a commit to generate some discussion here, it may end up being reverted and that's fine, but I do not understand anyone is getting their vite server to compile to an absolute location and keeping their library paths relative to the various modules/themes?

Because we're compiling at root, all our of custom libraries reference their entrypoints from root, which allows the vite server to scan the library files for entrypoints, and create a manifest which transforms those absolute paths into a dist path.

Am I missing something simple here?

As for the aggregation, it seems the only way to get Drupal to avoid doing that is to mark a file as type: external which directly conflicts with shouldBeManagedByVite(), so I'm going to open a core issue to address that because there needs to be another bypass in my opinion. edit: I was using code related to an old aggregator, I see the bypass is now preprocess: false

darvanen’s picture

I see that failed testing, can someone help me understand how this is supposed to work? I'm guessing we continue to follow the convention of relative links within library yml files - how do you get your vite server to

  1. find all the entrypoints
  2. avoid namespace collisions e.g. two js/table.entry.js files in different modules
wotnak’s picture

@darvanen
An example configuration with ViteJS setup in the project root for compiling assets in custom themes and modules could look like this:

composer.json
vite.config.js
web/
  index.php
  core/
  themes/
    contrib/
    custom/
  modules/
    contrib/
    custom/
      my_module/
        my_module.info.yml
        my_module.libraries.yml
        assets/
          my_module.css
          my_module.js
  libraries/
    dist/
      .vite/manifest.json
      assets/
        my_module-G0XxVM1l.css
        my_module-BbupU5xU.js
        ...

Configure ViteJS
In vite.config.js:

import { globSync } from "tinyglobby";
import { defineConfig } from "vite";

const entrypoints = [
  // Scan all .js files in custom themes and modules.
  "./web/{themes,modules}/custom/**/*.js",
  // Scan all .css files in custom themes and modules.
  "./web/{themes,modules}/custom/**/*.css",
];

export default defineConfig({
  build: {
    // Generate asset manifest.
    manifest: true,
    // Save compiled assets and asset manifest to the web/libraries/dist/ directory.
    outDir: 'web/libraries/dist',
    // Configure entrypoints.
    rollupOptions: {
      input: globSync(entrypoints),
    },
  },
  ...
});

Configure custom themes/modules
In theme/module.info.yml:

...
vite:
  // Set vite root to the parent directory of Drupal web root directory.
  viteRoot: '/..'
  // Set vite dist directory same as outDir option in vite config (it is resolved relative to viteRoot).
  distDir: web/libraries/dist
  // Enable vite support for all libraries and components.
  enableInAllLibraries: true
  enableInAllComponents: true

In theme/module.libraries.yml:

// Provide relative asset paths as normally, Vite module will automatically make them relative to viteRoot and depending on mode prepend vite dev server url or rewrite them based on asset manifest.
my_library:
  css:
    theme:
      assets/my_module.css: {}
  js:
    assets/my_module.js: {}

How Vite module works
In this example, the Vite module will:

- check if my_module/my_library library should be managed by vite module
- for each asset in library
  - trasform asset path (based on configured viteRoot and drupal web root) so it is relative to vite root
  - in dev mode:
    - prepend dev server url to the transformed path
  - in dist mode:
    - match transformed source path to the dist path based on vite asset manifest
    - transform dist path so that it is relative to the module directory
wotnak’s picture

Issue tags: -Needs tests

@reinfate
Tests and fix for @vite/client look good. Thanks.

wotnak’s picture

Assigned: wotnak » Unassigned
Status: Needs review » Reviewed & tested by the community

Reverted the last commit that broke the functionality and pushed small fixes for issues reported by phpstan.

I'm already using changes from the MR on a few work projects, and it works fine.
Since reinfate and mh_nichts also reported that the changes work for them, I think we can merge this.

And maybe as a follow-up in a separate issue, work on adding some examples to the docs to make it easier to start using functionality added in this issue and the module in general.

  • wotnak committed 11c3f6c5 on 1.x
    [#3511730] feat: Add support for using vite outside of the theme/module...
wotnak’s picture

Status: Reviewed & tested by the community » Fixed

Merged and released in 1.5.0.

Now that this issue is closed, please review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, please credit people who helped resolve this issue.

darvanen’s picture

@wotnak how do you reliably prevent name collisions in your manifest?

Never mind, I was confused to start with.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.