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
// @ts-check
/** @type {import('@vivliostyle/cli').VivliostyleConfigSchema} */
const vivliostyleConfig = {
title: "example-v8-without-workspaceDir",
author: "u1f992",
language: "ja",
entry: ["manuscript.md"],
};
module.exports = vivliostyleConfig;
---
link:
- rel: stylesheet
href: style.css
---
Vivliostyle CLI v9以前では、YAML Frontmatterで一見直感的にCSSを指定できました。
@page {
size: A5;
}
@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} .
// @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 .
{
"name": "css",
"main": "style.css"
}
theme
にローカルのディレクトリを指定する際は./
を明示する点に注意してください。theme
は本来、npmレジストリに公開されているパッケージ名を指定する箇所です(Vivliostyleが提供しているテーマとは、この記事で紹介している「テーマのディレクトリ」を所定の手順でnpmレジストリに公開したものに他なりません)。そのため、カレントディレクトリのcss
ディレクトリを意図して単にtheme: "css"
とすると、レジストリに公開されているcssパッケージがダウンロードされて失敗します。参考
// @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ファイルと適用したいテーマの組み合わせを指定できます。
// @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
{
"name": "base",
"main": "style.css"
}
@page {
size: A5;
}
/* ... */
{
"name": "chapter1",
"main": "style.css",
"dependencies": {
"base": "../base"
}
}
@import url("../base/style.css");
/* ... */
{
"name": "chapter2",
"main": "style.css",
"dependencies": {
"base": "../base"
}
}
@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をchapter1
とchapter2
の両方に追加するか、baseテーマを文書全体のtheme
に指定することでworkspaceDir
に取り込まれるようにします。
-
なお筆者はこの決定を強く支持します。Vivliostyle CLIにおいて、Markdownを原稿とする出力はMarkdownそれ自体から透過的に得られると理解すべきものであり、HTMLファイルはあくまで中間表現です。我々は、実装の都合としてHTMLがファイルに出力された後に、そのファイルを直接見たり手を加えたりするべきではありません。それらは、v8.16以降で追加された
documentProcessor
や、v9以降で行われたVite統合によって、自動生成パイプラインに組み込まれるべき作業です。とはいえ、CSSを書くにはもちろん変換後のHTMLを理解する必要がありますし、他に手段がないこともありますので、現実はこの限りではありません。 ↩