Quantcast
Channel: Node.jsタグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 8898

vue-loader 15で、テンプレート内の任意の属性(data-testなど)を除外する

$
0
0

概要

<template><divclass="hoge"data-test="Hoge"></template>

上記のdata-testのような、テストでは使いたいけどプロダクションコードには反映させたくないような属性を、vue-loaderの設定でプロダクションビルド時には除外し、以下のようにします。

<template><divclass="hoge"></template>

内容はVueコンポーネントのビルド時に不要な属性をtemplateから取り除くにかかれていることを、vue-loader 15系に合わせて書き直しつつ深堀りしただけになります。

バージョン情報

version
node12.16.3
vue2.6.11
vue-loader15.9.1
webpack4.43.0

対象コンポーネント

以下のような、いくつかの要素があって入れ子があってdata-test属性以外にもちょいちょい属性が付与されているようなサンプルコードをビルドする際に、data-test属性だけはE2Eテスト専用の情報なので除外したいと思います。

<template><divclass="root"><pdata-test="Hoge">hoge</p><divdata-test="Fuga"><h1id="foo"data-test="Foo">foo</h1><h2id="bar"data-test="Bar">bar</h2><p:data-test="value">Hello</p></div></div></template><script>exportdefault{data(){return{value:"Hello"}}}</script>

vue-loaderの構成について

本記事では便宜上、webpackvue-loaderを使うための設定を、vue.jsに以下のように分離していると仮定します。

module.exports={test:/\.vue$/,use:[{loader:'vue-loader',options:{}}]}

テンプレートのコンパイルに干渉する

vue-loaderは内部的に、vue-template-compilerを使って、Vueファイルのtemplateを、 createElementを使ったピュアなJavascriptに変換します。

変換の際に一度ASTを経由しますが、vue-template-compilerでは、その変換過程に任意の関数をフックすることができます。

vue-loaderの設定からvue-template-compilerの設定を指定する場合、 compilerOptionsを指定します。

干渉できるタイミングはいくつかありますが、今回はテンプレートをASTになったばかりのタイミングを使いたいので、 preTransformNodeを使用します。

module.exports={test:/\.vue$/,use:[{loader:'vue-loader',options:{compilerOptions:{modules:[{preTransformNode(astEl){console.log(astEl)}}]}}}]}

この関数は、テンプレートをコンパイルする際のElement1個に付き、そのElementのASTを引数にして一度呼ばれます。

<template><divclass="root"><pdata-test="Hoge">hoge</p><divdata-test="Fuga"><h1id="foo"data-test="Foo">foo</h1><h2id="bar"data-test="Bar">bar</h2><p:data-test="value">Hello</p></div></div></template>

の場合、親子合わせて全部で6要素あるので、6回関数が呼ばれるわけですね。

これでWebpackビルドをすると以下のように、対象コンポーネントのASTオブジェクト(6種)がドバっと出てきます。

{type:1,tag:'div',attrsList:[{name:'class',value:'root',start:5,end:17}],attrsMap:{class:'root'},rawAttrsMap:{class:{name:'class',value:'root',start:5,end:17}},parent:undefined,children:[],start:0,end:18}{type:1,tag:'p',attrsList:[{name:'data-test',value:'Hoge',start:24,end:40}],attrsMap:{'data-test':'Hoge'},rawAttrsMap:{'data-test':{name:'data-test',value:'Hoge',start:24,end:40}},parent:{type:1,tag:'div',attrsList:[[Object]],attrsMap:{class:'root'},rawAttrsMap:{class:[Object]},parent:undefined,children:[],start:0,end:18},children:[],start:21,end:41}{type:1,tag:'div',attrsList:[{name:'data-test',value:'Fuga',start:57,end:73}],attrsMap:{'data-test':'Fuga'},rawAttrsMap:{'data-test':{name:'data-test',value:'Fuga',start:57,end:73}},parent:{type:1,tag:'div',attrsList:[[Object]],attrsMap:{class:'root'},rawAttrsMap:{class:[Object]},parent:undefined,children:[[Object],[Object]],start:0,end:18},children:[],start:52,end:74}{type:1,tag:'h1',attrsList:[{name:'id',value:'foo',start:83,end:91},{name:'data-test',value:'Foo',start:92,end:107}],attrsMap:{id:'foo','data-test':'Foo'},rawAttrsMap:{id:{name:'id',value:'foo',start:83,end:91},'data-test':{name:'data-test',value:'Foo',start:92,end:107}},parent:{type:1,tag:'div',attrsList:[[Object]],attrsMap:{'data-test':'Fuga'},rawAttrsMap:{'data-test':[Object]},parent:{type:1,tag:'div',attrsList:[Array],attrsMap:[Object],rawAttrsMap:[Object],parent:undefined,children:[Array],start:0,end:18},children:[],start:52,end:74},children:[],start:79,end:108}{type:1,tag:'h2',attrsList:[{name:'id',value:'bar',start:125,end:133},{name:'data-test',value:'Bar',start:134,end:149}],attrsMap:{id:'bar','data-test':'Bar'},rawAttrsMap:{id:{name:'id',value:'bar',start:125,end:133},'data-test':{name:'data-test',value:'Bar',start:134,end:149}},parent:{type:1,tag:'div',attrsList:[[Object]],attrsMap:{'data-test':'Fuga'},rawAttrsMap:{'data-test':[Object]},parent:{type:1,tag:'div',attrsList:[Array],attrsMap:[Object],rawAttrsMap:[Object],parent:undefined,children:[Array],start:0,end:18},children:[[Object],[Object]],start:52,end:74},children:[],start:121,end:150}{type:1,tag:'p',attrsList:[{name:':data-test',value:'value',start:166,end:184}],attrsMap:{':data-test':'value'},rawAttrsMap:{':data-test':{name:':data-test',value:'value',start:166,end:184}},parent:{type:1,tag:'div',attrsList:[[Object]],attrsMap:{'data-test':'Fuga'},rawAttrsMap:{'data-test':[Object]},parent:{type:1,tag:'div',attrsList:[Array],attrsMap:[Object],rawAttrsMap:[Object],parent:undefined,children:[Array],start:0,end:18},children:[[Object],[Object],[Object],[Object]],start:52,end:74},children:[],start:163,end:185}

今回は各Elementの属性情報だけが知りたいので、astElのうち、必要なプロパティだけ抜き出してもう一度見てみましょう。

preTransformNode(astEl){const{attrsList,attrsMap}=astElconsole.log({attrsList,attrsMap})console.log('------')}
{attrsList:[{name:'class',value:'root',start:5,end:17}],attrsMap:{class:'root'}}------{attrsList:[{name:'data-test',value:'Hoge',start:24,end:40}],attrsMap:{'data-test':'Hoge'}}------{attrsList:[{name:'data-test',value:'Fuga',start:57,end:73}],attrsMap:{'data-test':'Fuga'}}------{attrsList:[{name:'id',value:'foo',start:83,end:91},{name:'data-test',value:'Foo',start:92,end:107}],attrsMap:{id:'foo','data-test':'Foo'}}------{attrsList:[{name:'id',value:'bar',start:125,end:133},{name:'data-test',value:'Bar',start:134,end:149}],attrsMap:{id:'bar','data-test':'Bar'}}------{attrsList:[{name:':data-test',value:'value',start:166,end:184}],attrsMap:{':data-test':'value'}}------

わかりやすくなってきました。
attrsListは対象Elementに設定されている属性の一覧で、attrsMapは属性名に対する設定値のマッピングですね。

ビルド結果を確認する①

webpackでのビルドが完了すると、 outputで指定したパスにアセットファイルが生成されます。

それを覗いてみると、先程出力したASTをベースに、ピュアなJavascriptでVueオブジェクトを生成しているコードが見つかります。

varrender=function(){var_vm=thisvar_h=_vm.$createElementvar_c=_vm._self._c||_hreturn_c("div",{staticClass:"root"},[_c("p",{attrs:{"data-test":"Hoge"}},[_vm._v("hoge")]),_vm._v(""),_c("div",{attrs:{"data-test":"Fuga"}},[_c("h1",{attrs:{id:"foo","data-test":"Foo"}},[_vm._v("foo")]),_vm._v(""),_c("h2",{attrs:{id:"bar","data-test":"Bar"}},[_vm._v("bar")]),_vm._v(""),_c("p",{attrs:{"data-test":_vm.value}},[_vm._v("Hello")]),]),])}

この状態だと、プロダクションコードにも data-test属性が入ってしまうので、それをASTの時点で削除しましょう。

data-test属性を削除する

preTransformNodeのような関数は、ASTオブジェクトを受け取ってASTオブジェクトを戻します。その際、戻したほうのASTオブジェクトを使用してコンパイルが継続されるので、この関数内で、 data-test属性を削除したASTオブジェクトを戻してあげればよいわけです。

と言っても非常に簡単で、先程の attrsListattrsMapから該当のデータだけ削除してあげればOKです。

preTransformNode(astEl){const{attrsList,attrsMap}=astElif(attrsMap['data-test']){deleteattrsMap['data-test']constindex=attrsList.findIndex(x=>x.name==='data-test')attrsList.splice(index,1)}returnastEl}

これによって、もし対象Elementがdata-test属性を持っていた場合、attrsMap及び attrsListから削除し、編集後のASTオブジェクトを戻すようになります。

これでビルドしてみましょう。

{attrsList:[{name:'class',value:'root',start:5,end:17}],attrsMap:{class:'root'}}-----{attrsList:[],attrsMap:{}}-----{attrsList:[],attrsMap:{}}-----{attrsList:[{name:'id',value:'foo',start:83,end:91}],attrsMap:{id:'foo'}}-----{attrsList:[{name:'id',value:'bar',start:125,end:133}],attrsMap:{id:'bar'}}-----{attrsList:[{name:':data-test',value:'value',start:166,end:184}],attrsMap:{':data-test':'value'}}-----

惜しい! data-test属性は見事に消えていますが、動的バインディングの :data-test属性が残ってしまいました。

これらはVueコンポーネント描画後のDOM上では同じ属性になりますが、この段階のASTでは全く異なる属性として扱われます。

やや冗長になっちゃうので、汎用的なコードを抜き出しましょう。

functionremoveAttr(astEl,name){const{attrsList,attrsMap}=astElif(attrsMap[name]){deleteattrsMap[name]constindex=attrsList.findIndex(x=>x.name===name)attrsList.splice(index,1)}returnastEl}

preTransformNodeからは、二つの属性を指定して関数を呼び出すだけにしてあげます。

preTransformNode(astEl){const{attrsList,attrsMap}=astElremoveAttr(astEl,'data-test')removeAttr(astEl,':data-test')console.log({attrsList,attrsMap})console.log('-----')returnastEl}
{attrsList:[{name:'class',value:'root',start:5,end:17}],attrsMap:{class:'root'}}-----{attrsList:[],attrsMap:{}}-----{attrsList:[],attrsMap:{}}-----{attrsList:[{name:'id',value:'foo',start:83,end:91}],attrsMap:{id:'foo'}}-----{attrsList:[{name:'id',value:'bar',start:125,end:133}],attrsMap:{id:'bar'}}-----{attrsList:[],attrsMap:{}}-----

data-test:data-testともに削除できました。完璧そうですね。
しかもこの構成なら test以外に消しておきたい属性が出てきても汎用的に対応できます。

ビルド結果を確認する①

ではこの状態でWebpackのビルド結果をもう一度見てみましょう。

varstaticRenderFns=[function(){var_vm=thisvar_h=_vm.$createElementvar_c=_vm._self._c||_hreturn_c("div",{staticClass:"root"},[_c("p",[_vm._v("hoge")]),_vm._v(""),_c("div",[_c("h1",{attrs:{id:"foo"}},[_vm._v("foo")]),_vm._v(""),_c("h2",{attrs:{id:"bar"}},[_vm._v("bar")]),_vm._v(""),_c("p",[_vm._v("Hello")]),]),])},]

しっかし data-test:data-test属性だけが除外され、 idclassといった属性は残っていることが確認できました!


Viewing all articles
Browse latest Browse all 8898

Trending Articles