◆ JavaScript から実行する polymer-build は自由度高そう
◆ 使ってみたらそんなことなかった
◆ polymer.json でできることばかりで それ以上の設定は簡単にはできない

lit-html/lit-element も webpack で動かせるようになったのですが polymer の build 処理で polymer-build という JavaScript から使うタイプのライブラリを見つけたのでこっちももう少し調べてみました
使った感じ polymer-cli 自体ユーザが少なそうなのに JavaScript からカスタマイズして使うようの polymer-build はさらにユーザがいないのか情報が少なく 使ってみた感じもあまり親切じゃなかったです

polymer-build のつくり

polymer-build は polymer-cli が内部で使っています
polymer build コマンドが実行されたときに呼び出しています
https://github.com/Polymer/tools/blob/master/packages/cli/src/build/build.ts

polymer-build の処理は Node.js の stream を使って行われます
使う側が stream に変換や追加の処理を pipe でつなぎます

stream 処理なのはいいのですが stream 中のファイルオブジェクトが vinyl で gulp 系です
最後に書き出すために vinyl-fs とか gulp パッケージが必要です
いまさら gulp 系触れたくないなというのが素直な感想です

と言っても polymer 自体がもう結構古めで lit-element 出てからは本来の polymer の element は maintenance 扱いで積極的に開発されてないようです
作りが古いのは仕方ないのかも知れません
どうせなら lit-element 用に新しいビルダー作って欲しいくらい なのですが webpack とかがあるので新たに作るメリットもないのでしょうね

stream 処理部分だけを取り出すとこういう感じです

const sourcesStream = forkStream(polymerProject.sources());
const depsStream = forkStream(polymerProject.dependencies());

let buildStream: NodeJS.ReadableStream =
mergeStream(sourcesStream, depsStream);

if (compiledToES5) {
buildStream =
buildStream.pipe(polymerProject.addCustomElementsEs5Adapter());
}

ソースファイルと依存ファイル(たぶん node_modules)をマージしてから オプションに応じて pipe していってます

polymer-build をインポートして こういう感じの処理を書いて Node.js で実行すればビルドできます
実行対象はただの JavaScript ファイルなので polymer-cli とかは要らず

node build.js

という感じで普通に Node.js で実行できます

gulp と連携しやすいので gulp から watch すれば polymer-cli にない watch 処理は補えそうです

JavaScript の変換処理

JavaScript を babel でトランスパイルする部分はどうなのか探してみると getOptimizeStreams で行ってました

buildStream = pipeStreams([
buildStream,
htmlSplitter.split(),

getOptimizeStreams({
html: options.html,
css: options.css,
js: {
...options.js,
moduleResolution: polymerProject.config.moduleResolution,
},
entrypointPath: polymerProject.config.entrypoint,
rootDir: polymerProject.config.root,
}),

htmlSplitter.rejoin()
]);

https://github.com/Polymer/tools/blob/master/packages/build/src/optimize-streams.ts#L267
https://github.com/Polymer/tools/blob/master/packages/build/src/js-transform.ts

ここを自分で置き換えれば Babel のプラグイン追加はできそうですけど これ全部置き換えるのって大変そうです

この前後で行っている htmlSplitter.split() と htmlSplitter.rejoin() が JavaScript や CSS を HTML から切り出して再結合する処理らしいです
バンドルが有効なら この処理の前にバンドルされているので バンドル済みから JavaScript を切り出して babel で変換というわけです

つまり依存パッケージのモジュールも全部 babel 通してるようですね
lit-html なども ES5 に変換されてるわけです

出力

ファイル出力部分のコードはこうなってます

export const mainBuildDirectoryName = 'build';

const buildName = options.name || 'default';
const buildDirectory = path.join(mainBuildDirectoryName, buildName);

// Finish the build stream by piping it into the final build directory.
buildStream = buildStream.pipe(dest(buildDirectory));

build フォルダの中にビルド名のフォルダが作られてそこに出力されます
出力先は名前しか変更できないようです がコード的にフルパスを書けばそこに出力できそうにも見えます

ここは自分で polymer-build を使うなら好きに変えられます
ただし そのフォルダの中の構造は設定できないようです

package.json
src/app.html
src/components/foo.js
src/components/bar.js
node_modules/**

という構造のプロジェクトで src/app.html をエントリポイントにすると ⇩ のような構造になってしまいます

src/app.html
shared_bundle_1.js
node_modules/...

src フォルダはいらないのに構造が維持されてしまいます
shared_bundle_1.js や node_modules も src の中ならいいのですがこれらは src の外に出てきます
相対パスの都合もあるので src/app.html を外側に移動するだけで済ませられません

出力については JavaScript から操作にしても制限があってあまり自由にいじれないようです

エラーがでない

エラーが一切出ず正常に終了するのに何も出力されてないということがありました
stream の最後で完了のイベントも起きていません
わかりやすいエラーをいくつか起こしてみると例外が出るのですが 設定ミスみたいな場合はエラーでてくれません

polymer-cli の方を見たのですが pipe の error イベントを検出してるくらいでした
error イベントのハンドリングは自作の方でもやっているのですが それでも検出できないです

出力できてない時点でエラーはなのはわかってるのだから何かの情報を出してほしいですね

あと amd 変換してるときに ミスで getOptimizeStreams の entryPointPath に undefined を渡していたときは HTML ファイルが変換されず amd の define が未定義のエラーになってました
これも define の出力しないと動かないのがわかってるのだから警告とか出してくれたらなーと思います

sources

エントリポイントから到達不可能ないらないものまで build フォルダにコピーされていましたが デフォルトで sources に src/**/* が設定されてるからのようです
sources を明示的にエントリポイントのファイルだけに設定すると不要なファイルはコピーされなくなりました

これを見る限り sources は import してなくても build に含めるフォルダ のようですが extraDependencies も同じようなもので違いがよくわかりません
build 時に .sources() で取れるものと .dependencies() で取れるものの違い程度でしょうか

ファイル分割

HTML をエントリポイントに設定してバンドルすると全部 HTML に含まれるのがイヤだったのですが bundler のオプションを見ると inlineScripts や inlineCss を false にしたら分割してくれるようです
さっそくやってみると HTML は amd の define の定義などくらいで メインの JavaScript は別のファイルになりました

……が 実行してみると require が存在しないというエラーでした

require("./noConflict");

というコードがバンドルした JavaScript ファイルに存在します
なのに require 関数は定義されていないようで エラーです

調べてみても 特に解決策は見当たらずでした

さらに webcomponents の es5-adapter を追加するように設定していたのですが html にそれをロードする処理は入るのに node_modules に es5-adapter は出力されないので 404 のエラーになってました

build.js

一応用意した build.js ファイルはこれです
build を行うからというだけの名前なのでこの名前じゃないといけないとかはないです
ほとんどは polymer-cli のコードと同じことをやってます
if で分岐してたようなのを必要なもののみ追加してる感じです

const path = require("path")

const { PolymerProject, HtmlSplitter, getOptimizeStreams } = require("polymer-build")
const mergeStream = require("merge-stream")
const vinyl_fs = require("vinyl-fs")

const pp = new PolymerProject({
entrypoint: path.join(__dirname, "src/app.html"),
root: __dirname,
sources: [path.join(__dirname, "src/app.html")],
})

const htmlSplitter = new HtmlSplitter()
const pipeStream = (...sa) => sa.reduce((a, b) => a.pipe(b))

const build_stream = pipeStream(
mergeStream(pp.sources(), pp.dependencies()),
pp.addCustomElementsEs5Adapter(),
pp.bundler({
inlineScripts: false,
inlineCss: false,
}),
htmlSplitter.split(),
...getOptimizeStreams({
js: {
compile: true,
transformModulesToAmd: true,
moduleResolution: pp.config.moduleResolution,
},
entrypointPath: pp.config.entrypoint,
rootDir: pp.config.root,
}),
htmlSplitter.rejoin(),
vinyl_fs.dest(path.join(__dirname, "dist/"))
)

build_stream.on("end", e => console.log("done"))
build_stream.on("error", e => console.error(e))

結局 babel の設定みたいな複雑なことはしなかったので cli のほうでも polymer.json を用意すればほぼ同じことができました

{
"sources": ["src/app.html"],
"entrypoint": "src/app.html",
"root": ".",
"builds": [
{
"js": {
"compile": true,
"transformModulesToAmd": true
},
"bundle": {
"inlineScripts": false,
"inlineCss": false
}
}
]
}

polymer-build のコード書いて追加でできるようになった部分は出力先のフォルダを変えられるくらいでしょうか
それも細かい設定はできず出力先のフォルダだけなので polymer-cli の処理のあとに mv を実行するようにしておけばいいだけです

まとめ

polymer-build を使う必要は特になかったです
やっぱり自由度足りないままでした
前後や途中で JavaScript で処理を行いたい場合には有効ですが ビルド自体の処理をカスタマイズはほぼ polymer.json で十分です
それでできない部分は無理かとても面倒です

前記事で書いた不満点について

ビルドのフォルダ周り

⇨ 出力先フォルダは変えられるけど中の構造は変えられなさそう
  出力先フォルダならビルド後に移動すれば十分

エントリポイントから到達不可能なものも出力されていた

⇨ sources を省略せずに書けば解決できる
  省略したら src フォルダ全体になってる

babel を設定できない

⇨ できそうだけどとても大変そう

watch がない

⇨ gulp 使えばできる けど watch で変更検出時に polymer-cli の build 呼び出してもよさそう
  polymer-build 必須というわけでもない

1 ファイルにまとめられる

⇨ 分割機能はあって分割されたけど require がないとか es5-adapter が追加されないとかでまともに動かない


というわけで特に解決できてないですね
まぁ webpack で動くことがわかったので polymer-cli はもう使わないでしょう きっと