2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Vivliostyle CLI v9+ スタイル読み込みの挙動の変化について

Last updated at Posted at 2025-06-02

Vivliostyle CLI v9以降では、デフォルトのworkspaceDir.vivliostyleに変更されています。この変更により、「workspaceDir未指定」かつ「MarkdownのYAML FrontmatterにCSSファイルのパスを直接書く」という簡易な指定方法を使用している場合、挙動が大きく変わります。

(従来の挙動)workspaceDirを指定しない場合

v9以前では、workspaceDirが未指定の場合、Markdownファイルと同じディレクトリに変換済みのHTMLが出力されていました。YAML Frontmatterの内容はほぼそのままHTMLのhead要素に展開されるため、一見直感的にCSSを指定できました。また、CSSファイル内のローカルファイル参照もなりゆきで機能していました。

$ mkdir example-v8-without-workspaceDir && cd example-v8-without-workspaceDir
$ npm init --yes && npm install @vivliostyle/cli@8
$ npx vivliostyle init
vivliostyle.config.js
// @ts-check
/** @type {import('@vivliostyle/cli').VivliostyleConfigSchema} */
const vivliostyleConfig = {
  title: "example-v8-without-workspaceDir",
  author: "u1f992",
  language: "ja",
  entry: ["manuscript.md"],
};

module.exports = vivliostyleConfig;
manuscript.md
---
link:
  - rel: stylesheet
    href: style.css
---

Vivliostyle CLI v9以前では、YAML Frontmatterで一見直感的にCSSを指定できました。
base.css
@page {
    size: A5;
}
style.css
@import url("./base.css");
body {
    background-color: aqua;
}
$ npx vivliostyle build
$ tree -I node_modules
.
├── base.css
├── example-v8-without-workspaceDir.pdf
├── manuscript.html
├── manuscript.md
├── package-lock.json
├── package.json
├── publication.json
├── style.css
└── vivliostyle.config.js

1 directory, 9 files
$ head -n 8 manuscript.html
<!doctype html>
<html lang="ja">
  <head>
    <meta charset="utf-8">
    <title>example-v8-without-workspaceDir</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="style.css">  ⇐位置関係が正しいのでとりあえず読み込まれていた
  </head>

(従来の挙動)workspaceDirを指定する場合

ただしv9以前でも、workspaceDirを明示的に指定した場合はスタイルの解決に失敗していました。

$ cd .. && mkdir example-v8-with-workspaceDir && cd example-v8-with-workspaceDir
$ npm init --yes && npm install @vivliostyle/cli@8
$ npx vivliostyle init
$ cp ../example-v8-without-workspaceDir/{base.css,style.css,manuscript.md} .
vivliostyle.config.js
// @ts-check
/** @type {import('@vivliostyle/cli').VivliostyleConfigSchema} */
const vivliostyleConfig = {
  title: "example-v8-with-workspaceDir",
  author: "u1f992",
  language: "ja",
  entry: ["manuscript.md"],
  workspaceDir: ".vivliostyle",
};

module.exports = vivliostyleConfig;
$ npx vivliostyle build
$ tree -a -I node_modules
.
├── .vivliostyle
│   ├── manuscript.html
│   └── publication.json
├── base.css
├── example-v8-with-workspaceDir.pdf
├── manuscript.md
├── package-lock.json
├── package.json
├── style.css
└── vivliostyle.config.js

2 directories, 9 files
$ head -n 8 .vivliostyle/manuscript.html
<!doctype html>
<html lang="ja">
  <head>
    <meta charset="utf-8">
    <title>example-v8-with-workspaceDir</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="style.css">  ⇐位置関係が正しくないので読み込まれない
  </head>
npx vivliostyle previewの開発者ツールの出力
Failed to load resource: net::ERR_FILE_NOT_FOUND  / ... /style.css:1

CSSファイルをvivliostyle.config.jsのthemeに指定することで、style.cssについてはworkspaceDirにコピーされるようになりますが、その中で参照されているbase.cssについては依然として正しく読み込まれません。

  // @ts-check
  /** @type {import('@vivliostyle/cli').VivliostyleConfigSchema} */
  const vivliostyleConfig = {
    title: "example-v8-with-workspaceDir",
    author: "u1f992",
    language: "ja",
+   theme: "style.css",
    entry: ["manuscript.md"],
    workspaceDir: ".vivliostyle",
  };
  
  module.exports = vivliostyleConfig;

なおthemeを指定する場合はYAML Frontmatterは不要です。

- ---
- link:
-   - rel: stylesheet
-     href: style.css
- ---
- 
  Vivliostyle CLI v9以前では、YAML Frontmatterで一見直感的にCSSを指定できました。
$ npx vivliostyle build
$ head -n 8 .vivliostyle/manuscript.html
<!doctype html>
<html lang="ja">
  <head>
    <meta charset="utf-8">
    <title>example-v8-with-workspaceDir</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" type="text/css" href="style.css">
  </head>
$ tree -a -I node_modules
.
├── .vivliostyle
│   ├── manuscript.html
│   ├── publication.json
│   └── style.css
├── base.css
├── example-v8-with-workspaceDir.pdf
├── manuscript.md
├── package-lock.json
├── package.json
├── style.css
└── vivliostyle.config.js
npx vivliostyle previewの開発者ツールの出力
Failed to load resource: net::ERR_FILE_NOT_FOUND  / ... /base.css:1

これはVivliostyle CLI側ではHTMLやCSS内部で参照されているファイルまでは解析しないという、考えてみれば当然の仕様による挙動です。仮にこれを実装しても不具合が多く発生しそうです。

v9における破壊的変更点

前述の通り、v9では未指定の場合のworkspaceDirのデフォルト値が.vivliostyleに変更されました1。これにより、workspaceDirを指定せずYAML FrontmatterでCSSのパスを直接指定したり、themeで(後述のテーマではなく)単純にCSSファイルを指定していた場合の挙動は、正しく壊れています。また、workspaceDirの内部のディレクトリ構造も変更されています。次の例で確認してください。

解決方法

筆者が理解している筋の良い解決方法は、Vivliostyle CLIのスタイル指定の基本単位がテーマ=Node.jsパッケージであると認識を改めることです(特にCSSが別ファイルを参照する場合)。

スタイルに対してディレクトリを切り、package.jsonに上記Issueで紹介されている最低限の指定を行います。"name"にはディレクトリ名など、"main"には起点となるCSSファイルを指定します。

$ cd .. && mkdir example-correct && cd example-correct
$ npm init --yes && npm install @vivliostyle/cli@9
$ npx vivliostyle init
$ mkdir css
$ cp ../example-v8-with-workspaceDir/{base.css,style.css} css/
$ cp ../example-v8-with-workspaceDir/manuscript.md .
css/package.json
{
    "name": "css",
    "main": "style.css"
}

themeにローカルのディレクトリを指定する際は./を明示する点に注意してください。themeは本来、npmレジストリに公開されているパッケージ名を指定する箇所です(Vivliostyleが提供しているテーマとは、この記事で紹介している「テーマのディレクトリ」を所定の手順でnpmレジストリに公開したものに他なりません)。そのため、カレントディレクトリのcssディレクトリを意図して単にtheme: "css"とすると、レジストリに公開されているcssパッケージがダウンロードされて失敗します。参考

vivliostyle.config.js
// @ts-check
/** @type {import('@vivliostyle/cli').VivliostyleConfigSchema} */
const vivliostyleConfig = {
  title: "example-correct",
  author: "u1f992",
  language: "ja",
  theme: "./css",
  entry: ["manuscript.md"],
};

module.exports = vivliostyleConfig;

これで./cssへのリンクが.vivliostyle以下に追加され、HTMLからも正しく参照されるようになります。

$ npx vivliostyle build
$ tree -a
.
├── .vivliostyle
│   ├── manuscript.html
│   ├── publication.json
│   └── themes
│       ├── node_modules
│       │   ├── .package-lock.json
│       │   └── css -> / ... /example-correct/css
│       ├── package-lock.json
│       └── package.json
├── css
│   ├── base.css
│   ├── package.json
│   └── style.css
├── example-correct.pdf
├── manuscript.md
├── node_modules
├── package-lock.json
├── package.json
└── vivliostyle.config.js

1777 directories, 12494 files
$ head -n 12 .vivliostyle/manuscript.html        
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8" />
    <title>example-correct</title>
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <link
      rel="stylesheet"
      type="text/css"
      href="themes/node_modules/css/style.css"
    />
  </head>

例1:ファイルごとにテーマを切り替えたい

entryに、Markdownファイルと適用したいテーマの組み合わせを指定できます。

vivliostyle.config.js
// @ts-check
/** @type {import('@vivliostyle/cli').VivliostyleConfigSchema} */
const vivliostyleConfig = {
  title: "example-correct",
  author: "u1f992",
  language: "ja",
  entry: [
    { path: "chapter1.md", theme: "./css/chapter1" },
    { path: "chapter2.md", theme: "./css/chapter2" },
  ],
};

module.exports = vivliostyleConfig;

例2:chapter1とchapter2のテーマで、共通部をくくりたい

テーマのpackage.jsonに"dependencies"を追加するのがVivliostyle Themesにも採用されている方法ですが、執筆時点で最新のv9.1.1では、ローカルのテーマがさらにローカルのパスで依存関係を指定すると正しく解決されません。参考

$ tree css
css
├── base
│   ├── package.json
│   └── style.css
├── chapter1
│   ├── package.json
│   └── style.css
└── chapter2
    ├── package.json
    └── style.css

4 directories, 6 files
css/base/package.json
{
    "name": "base",
    "main": "style.css"
}
css/base/style.css
@page {
    size: A5;
}

/* ... */
css/chapter1/package.json
{
    "name": "chapter1",
    "main": "style.css",
    "dependencies": {
        "base": "../base"
    }
}
css/chapter1/style.css
@import url("../base/style.css");
/* ... */
css/chapter2/package.json
{
    "name": "chapter2",
    "main": "style.css",
    "dependencies": {
        "base": "../base"
    }
}
css/chapter2/style.css
@import url("../base/style.css");
/* ... */

この例は、現状次のように失敗します。node_modules以下にbaseのリンクが作成されず、CSSもリンク切れになります。

$ npx vivliostyle build
INFO Start building
INFO Launching PDF build environment
INFO Building pages
ERROR 404 http://localhost:13000/vivliostyle/themes/node_modules/base/style.css
INFO Building PDF
INFO Processing PDF
SUCCESS Finished building u1f992.pdf
📗 Built successfully!

$ tree .vivliostyle/
.vivliostyle/
├── chapter.html
├── publication.json
└── themes
    ├── node_modules
    │   ├── chapter1 -> ../../../css/chapter1
    │   └── chapter2 -> ../../../css/chapter2
    ├── package-lock.json
    └── package.json

暫定の解決方法は2つあります。base.cssをchapter1chapter2の両方に追加するか、baseテーマを文書全体のthemeに指定することでworkspaceDirに取り込まれるようにします。

  1. なお筆者はこの決定を強く支持します。Vivliostyle CLIにおいて、Markdownを原稿とする出力はMarkdownそれ自体から透過的に得られると理解すべきものであり、HTMLファイルはあくまで中間表現です。我々は、実装の都合としてHTMLがファイルに出力された後に、そのファイルを直接見たり手を加えたりするべきではありません。それらは、v8.16以降で追加されたdocumentProcessorや、v9以降で行われたVite統合によって、自動生成パイプラインに組み込まれるべき作業です。とはいえ、CSSを書くにはもちろん変換後のHTMLを理解する必要がありますし、他に手段がないこともありますので、現実はこの限りではありません。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?